| @@ -0,0 +1,156 @@ | |||
| <template> | |||
| <div class="treeNode"> | |||
| <span @click="addNode">添加节点</span> | |||
| <vue-tree-list | |||
| :model="data" | |||
| default-tree-node-name="new file" | |||
| default-leaf-node-name="new folder" | |||
| :default-expanded="false" | |||
| @click="onClick" | |||
| @change-name="onChangeName" | |||
| @delete-node="onDel" | |||
| @add-node="onAddNode" | |||
| /> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {VueTreeList, Tree, TreeNode} from 'vue-tree-list'; | |||
| export default { | |||
| components: { | |||
| VueTreeList | |||
| }, | |||
| props: { | |||
| treeData: { | |||
| type: Array, | |||
| default: () => [] | |||
| }, | |||
| fileInfoParams: { | |||
| type: Object, | |||
| default: () => {} | |||
| } | |||
| }, | |||
| data() { | |||
| return { | |||
| newTree: {}, | |||
| fileParams: {}, // 文件相关的信息 | |||
| data: new Tree([ | |||
| { | |||
| name: 'Node 1', | |||
| id: 1, | |||
| pid: 0, | |||
| children: [ | |||
| { | |||
| name: 'Node 1-2', | |||
| id: 2, | |||
| isLeaf: true, | |||
| pid: 0 | |||
| } | |||
| ] | |||
| }, | |||
| { | |||
| name: 'Node 2', | |||
| id: 3, | |||
| pid: 0, | |||
| }, | |||
| { | |||
| name: 'Node 3', | |||
| id: 4, | |||
| pid: 0 | |||
| } | |||
| ]) | |||
| }; | |||
| }, | |||
| watch: { | |||
| treeData(val) { | |||
| this.data = new Tree(val); | |||
| }, | |||
| fileInfoParams(val) { | |||
| this.fileParams = val; | |||
| } | |||
| }, | |||
| mounted() { | |||
| console.log('treeData', this.data); | |||
| }, | |||
| methods: { | |||
| onDel(node) { | |||
| const paramsInfo = []; | |||
| if (!node.children?.length > 0) { // 文件还是文件夹 | |||
| paramsInfo[0] = {...node}; | |||
| } else { // 文件夹 | |||
| node.children.filter((item) => item.sha).map((items) => { // 过滤是新增的元素进行删除 | |||
| paramsInfo.push({ | |||
| filePath: items.path, | |||
| content: '', | |||
| name: items.name, | |||
| operation: 'delete' | |||
| }); | |||
| }); | |||
| } | |||
| this.$emit('deleteNode', paramsInfo); | |||
| node.remove(); | |||
| }, | |||
| onChangeName(params) { | |||
| console.log(params); | |||
| }, | |||
| onAddNode(params) { | |||
| console.log(params, '节点还是树'); | |||
| }, | |||
| // 点击每个文件/夹事件 | |||
| onClick(params) { | |||
| if (!params.isLeaf) return; | |||
| if (this.fileParams?.sha === params.sha) return; | |||
| this.$emit('handleChangFile', params); | |||
| }, | |||
| addNode() { | |||
| console.log('我是子节点'); | |||
| const node = new TreeNode({name: 'new node', isLeaf: false}); | |||
| if (!this.data.children) this.data.children = []; | |||
| this.data.addChildren(node); | |||
| }, | |||
| } | |||
| }; | |||
| </script> | |||
| <style lang="less" rel="stylesheet/less"> | |||
| .vtl { | |||
| .vtl-drag-disabled { | |||
| background-color: #d0cfcf; | |||
| &:hover { | |||
| background-color: #d0cfcf; | |||
| } | |||
| } | |||
| .vtl-disabled { | |||
| background-color: #d0cfcf; | |||
| } | |||
| } | |||
| </style> | |||
| <style lang="less" rel="stylesheet/less" scoped> | |||
| .icon { | |||
| &:hover { | |||
| cursor: pointer; | |||
| } | |||
| } | |||
| .muted { | |||
| color: gray; | |||
| font-size: 80%; | |||
| } | |||
| </style> | |||
| <style scoped> | |||
| .treeNode{ | |||
| padding:20px 20px 0; | |||
| overflow-y: auto; | |||
| overflow-x: hidden; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,214 @@ | |||
| <template> | |||
| <div ref="container" class="monaco-editor" style="height: calc(100vh - 42px)" /> | |||
| </template> | |||
| <script> | |||
| // import * as monaco from "monaco-editor"; | |||
| import "monaco-editor/min/vs/loader.js"; | |||
| import "monaco-editor/min/vs/editor/editor.main.nls.js"; | |||
| import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; | |||
| // import "monaco-editor/esm/vs/basic-languages/mysql/mysql.contribution"; | |||
| // import "monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js"; | |||
| // import "monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js"; | |||
| // import "monaco-editor/esm/vs/editor/contrib/hover/hover.js"; | |||
| import { isBase64 } from "../utils.js"; | |||
| export default { | |||
| name: "AcMonaco", | |||
| inject: ["reload"], | |||
| props: { | |||
| opts: { | |||
| type: Object, | |||
| default() { | |||
| return {}; | |||
| }, | |||
| }, | |||
| height: { | |||
| type: Number, | |||
| default: 300, | |||
| }, | |||
| monacaValue: { | |||
| type: String, | |||
| default: "false", | |||
| }, | |||
| onChange: { | |||
| type: Function, | |||
| default: new Function | |||
| } | |||
| }, | |||
| data() { | |||
| return { | |||
| isDiff: false, | |||
| // 主要配置 | |||
| defaultOpts: { | |||
| lineNumbersMinChars: 0, | |||
| lineDecorationsWidth: 0, | |||
| glyphMargin: false, | |||
| value: "", // 编辑器的值 | |||
| wordWrap: "on", | |||
| theme: "vs-dark", // 编辑器主题:vs, hc-black, or vs-dark,更多选择详见官网 | |||
| roundedSelection: false, // 右侧不显示编辑器预览框 | |||
| autoIndent: true, // 自动缩进 | |||
| quickSuggestions: true, // 快速搜索 | |||
| enableSplitViewResizing: false, | |||
| scrollBeyondLastLine: false, | |||
| quickSuggestions: false, //智能提示 | |||
| renderLineHighlight: false, //选中行外部边框 | |||
| lineHeight: 22 | |||
| }, | |||
| // 编辑器对象 | |||
| monacoEditor: {}, | |||
| oldValue: "123123/n\n", | |||
| newValue: "123121311111111", | |||
| }; | |||
| }, | |||
| watch: { | |||
| opts: { | |||
| }, | |||
| monacaValue: { | |||
| handler(value) { | |||
| if(this.isDiff) return ; | |||
| const editor = this.monacoEditor | |||
| const output = isBase64(value) | |||
| ? Buffer.from(value, "base64").toString("utf8") | |||
| : value; | |||
| const positions = this.monacoEditor.getPosition() | |||
| this.monacoEditor.setValue(output); | |||
| this.monacoEditor.setPosition({ | |||
| lineNumber: positions.lineNumber, | |||
| column: positions.column | |||
| }) | |||
| }, | |||
| deep: true | |||
| }, | |||
| // monacaValue(value) { | |||
| // alert(value) | |||
| // console.log(isBase64); | |||
| // const output = isBase64(value) | |||
| // ? Buffer.from(value, "base64").toString("utf8") | |||
| // : value; | |||
| // this.monacoEditor.setValue(output); | |||
| // }, | |||
| }, | |||
| beforeDestroy() { | |||
| document.removeEventListener("keyup", this.onSaveHandler); | |||
| }, | |||
| mounted() { | |||
| document.addEventListener("keyup", this.onSaveHandler, false); | |||
| this.init(); | |||
| }, | |||
| methods: { | |||
| init() { | |||
| // 初始化container的内容,销毁之前生成的编辑器 | |||
| this.$refs.container.innerHTML = ""; | |||
| // 生成编辑器配置 | |||
| const editorOptions = Object.assign(this.defaultOpts, this.opts); | |||
| if (!this.isDiff) { | |||
| // 初始化编辑器实例 | |||
| this.monacoEditor = monaco.editor.create( | |||
| this.$refs.container, | |||
| editorOptions | |||
| ); | |||
| this.monacoEditor.onDidChangeModelContent( | |||
| event => { | |||
| this.$emit("onChange", this.monacoEditor.getValue()) | |||
| }, | |||
| ); | |||
| // 编辑器内容发生改变时触发 | |||
| } else { | |||
| editorOptions.readOnly = true; | |||
| editorOptions.language = "javascript"; | |||
| // editorOptions.inlineHints = true; | |||
| // 初始化编辑器实例 | |||
| this.monacoDiffInstance = monaco.editor.createDiffEditor( | |||
| this.$refs.container, | |||
| editorOptions | |||
| ); | |||
| this.monacoDiffInstance.setModel({ | |||
| original: monaco.editor.createModel( | |||
| this.oldValue, | |||
| editorOptions.language | |||
| ), | |||
| modified: monaco.editor.createModel( | |||
| this.newValue, | |||
| editorOptions.language | |||
| ), | |||
| }); | |||
| } | |||
| if (this.monacaValue) { | |||
| const output = isBase64(this.monacaValue) | |||
| ? Buffer.from(value, "base64").toString("utf8") | |||
| : this.monacaValue; | |||
| this.monacoEditor.setValue(output); | |||
| } | |||
| }, | |||
| upDateDiff(val) { | |||
| this.monacoDiffInstance.updateOptions({ | |||
| renderSideBySide: !val, | |||
| }); | |||
| }, | |||
| onSaveHandler(e) { | |||
| if(this.isDiff) return; | |||
| if ( | |||
| (window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && | |||
| e.keyCode === 83 | |||
| ) { | |||
| e.preventDefault(); | |||
| this.$emit("handleSave", this.monacoEditor.getValue()); | |||
| } | |||
| this.$emit("handleSave", this.monacoEditor.getValue()); | |||
| }, | |||
| // 供父组件调用手动获取值 | |||
| getVal() { | |||
| return this.monacoEditor.getValue(); | |||
| }, | |||
| setLanguage(lang) { | |||
| setTimeout(() => { | |||
| if (monaco.editor.setModelLanguage) { | |||
| monaco.editor.setModelLanguage(this.monacoEditor.getModel(), lang) | |||
| } | |||
| }), 300; | |||
| // alert(JSON.stringify(this.monacoEditor.getModel())) | |||
| }, | |||
| updateOptions(opts) { | |||
| const editorOptions = Object.assign(this.defaultOpts, opts); | |||
| this.monacoEditor.updateOptions(editorOptions); | |||
| }, | |||
| setTheme(theme) { | |||
| monaco.editor.setTheme(theme); | |||
| }, | |||
| setDiff(oldContent, newContent) { | |||
| this.oldValue = isBase64(oldContent) ? Buffer.from(oldContent, "base64").toString("utf8") : oldContent; | |||
| this.newValue = newContent; | |||
| this.isDiff = true; | |||
| this.init(); | |||
| }, | |||
| setCloseDiff() { | |||
| if (this.isDiff) { | |||
| this.isDiff = false; | |||
| this.init(); | |||
| } | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style> | |||
| .margin-view-overlays { | |||
| background: #f5f5f5; | |||
| } | |||
| /* .view-lines { | |||
| padding-left: 10px; | |||
| } */ | |||
| </style> | |||
| @@ -0,0 +1,256 @@ | |||
| <template> | |||
| <div class="myTrees"> | |||
| <el-tree | |||
| ref="tree" | |||
| :data="treeData" | |||
| node-key="filePath" | |||
| empty-text="暂无数据" | |||
| highlight-current | |||
| @node-click="handleLeftclick" | |||
| > | |||
| <div slot-scope="{ node , data }" class="custom-tree-node"> | |||
| <span> | |||
| <i :class="getIcon(node.data)" /> | |||
| {{ node.label }} | |||
| </span> | |||
| <span> | |||
| <el-dropdown trigger="click" class="el-dropdown" | |||
| v-show="!isLeaf && data.type == 'tree'" | |||
| > | |||
| <span class="el-dropdown-link"> | |||
| <svg class="icon el-dropdown-svg" aria-hidden="true"> | |||
| <use xlink:href="#icon-a-bianzu31"></use> | |||
| </svg> | |||
| </span> | |||
| <el-dropdown-menu slot="dropdown"> | |||
| <el-dropdown-item | |||
| @click.native="addChildNode('leaf')" | |||
| >新建文件</el-dropdown-item | |||
| > | |||
| <el-dropdown-item | |||
| @click.native="addChildNode('')" | |||
| >新建文件夹</el-dropdown-item | |||
| > | |||
| <!-- <el-dropdown-item @click.native="deleteNode">删除</el-dropdown-item> --> | |||
| </el-dropdown-menu> | |||
| </el-dropdown> | |||
| </span> | |||
| </div> | |||
| </el-tree> | |||
| </div> | |||
| </template> | |||
| <!-- file-icon ide-icon word-icon dark-blue --> | |||
| <link rel="stylesheet" href="/web_src/js/components/treeIcon.css" /> | |||
| <script> | |||
| import {icons} from "./icons"; | |||
| console.log("icons:",icons) | |||
| export default { | |||
| name: "List", | |||
| props: { | |||
| treeListData: { | |||
| type: Array, | |||
| default: () => [], | |||
| }, | |||
| fileInfoParams: { | |||
| type: Object, | |||
| default: () => {}, | |||
| }, | |||
| }, | |||
| data() { | |||
| return { | |||
| fileParams: {}, | |||
| treeData: [], | |||
| isShow: false, | |||
| currentData: "", | |||
| currentNode: "", | |||
| menuVisible: false, | |||
| firstLevel: false, | |||
| lastLevel: false, | |||
| filterText: "", | |||
| isLeaf: false, | |||
| }; | |||
| }, | |||
| watch: { | |||
| treeListData(val) { | |||
| this.treeData = val; | |||
| }, | |||
| fileInfoParams(val) { | |||
| this.fileParams = val; | |||
| }, | |||
| }, | |||
| methods: { | |||
| // 鼠标左击事件 | |||
| handleLeftclick(data, node) { | |||
| this.currentData = data; | |||
| this.currentNode = node; | |||
| this.firstLevel = false; | |||
| this.isLeaf = data.isLeaf; | |||
| this.lastLevel = false; | |||
| if (data.type === 'tree') return; | |||
| // if (data.sha) { | |||
| this.$emit("handleChangFile", data, this.treeData); | |||
| // } | |||
| }, | |||
| getIcon(data){ | |||
| console.log("data:",data.name.split(".")) | |||
| let icon = ''; | |||
| if(data.type === 'tree'){ | |||
| return 'fa fa-folder ide-icon ide-icon-folder' | |||
| } | |||
| try { | |||
| let suffix = data.name.split(".").pop(); | |||
| if(data.name.indexOf(".") > -1){ | |||
| suffix = "." + suffix | |||
| } | |||
| icons.forEach(element => { | |||
| if(element[2].test(suffix)){ | |||
| icon = element[0] + ' ' + element[1].join(' '); | |||
| throw('') | |||
| } | |||
| }) | |||
| } catch (error) { | |||
| } | |||
| return "file-icon ide-icon " + icon; | |||
| }, | |||
| // 增加子级节点事件 | |||
| addChildNode(shape) { | |||
| const id = Math.ceil(Math.random() * 100); | |||
| this.$prompt("请输入名称", "提示", { | |||
| confirmButtonText: "确定", | |||
| cancelButtonText: "取消", | |||
| }) | |||
| .then(({ value }) => { | |||
| if (value.trim().indexOf("") === -1) | |||
| return this.$message.warning("名称不能包含空格!"); | |||
| const treeD = { | |||
| id, | |||
| label: value, | |||
| operation: "add", | |||
| isEdit: true, | |||
| type: shape === "leaf" ? "blob" : "tree", | |||
| filePath: `${this.currentData.filePath}/${value}`, | |||
| isLeaf: shape === "leaf", | |||
| name: value, | |||
| fileType:"txt", | |||
| children: [], | |||
| }; | |||
| if(shape === "leaf"){ | |||
| treeD.content = ""; | |||
| treeD.oldContent = ""; | |||
| treeD.newContent = ""; | |||
| treeD.fileType = 'txt' | |||
| } | |||
| treeD.path = treeD.filePath; | |||
| this.$refs.tree.append(treeD, this.currentData.filePath); | |||
| this.$emit("handleAddNode", treeD, this.currentData.filePath); // 触发父组件更改提交界面的数据变化 | |||
| }) | |||
| .catch(() => {}); | |||
| }, | |||
| // 删除节点 | |||
| deleteNode() { | |||
| this.$confirm(`确定删除当前文件,是否继续?`, "提示", { | |||
| confirmButtonText: "确定", | |||
| cancelButtonText: "取消", | |||
| type: "warning", | |||
| }) | |||
| .then(() => { | |||
| if (this.currentData.isLeaf) { | |||
| // isLeaf为true 代表文件类型 | |||
| if (this.currentData.operation === "add") { | |||
| // 新增的节点删去 | |||
| this.$emit("handleDeleteAddNode", this.currentData); | |||
| } else if (this.currentData.sha) { | |||
| // 原本存在的数据 触发父组件更新已存在数据的状态 | |||
| this.$emit("handleDeleteOldNode", this.currentData); | |||
| } | |||
| } | |||
| this.$refs.tree.remove(this.currentNode); | |||
| }) | |||
| .catch(() => {}); | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="less" scoped> | |||
| @import "./treeIcon.css"; | |||
| .myTrees { | |||
| /*background: transparent;*/ | |||
| height: 100%; | |||
| overflow: auto; | |||
| background: #FFFFFF; | |||
| } | |||
| .el-tree { | |||
| /* padding: 20px; */ | |||
| /*background: transparent;*/ | |||
| background: #FFFFFF; | |||
| color: black; | |||
| } | |||
| .el-tree .is-current{ | |||
| background: #f5f7fa; | |||
| } | |||
| .custom-tree-node { | |||
| flex: 1; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| padding-right: 8px; | |||
| font-size: 14px; | |||
| height: 34px; | |||
| line-height: 34px; | |||
| .el-dropdown-svg{ | |||
| height: 16px; | |||
| width: 16px; | |||
| color: #979797; | |||
| } | |||
| .el-dropdown-svg:hover{ | |||
| color: #007aff; | |||
| } | |||
| } | |||
| /deep/ .el-tree-node__content{ | |||
| height: 34px; | |||
| line-height: 34px; | |||
| font-size: 14px; | |||
| font-family: PingFangSC-Regular, PingFang SC; | |||
| font-weight: 400; | |||
| } | |||
| /deep/ .el-tree-node.is-current > .el-tree-node__content{ | |||
| background: #F5F7FA; | |||
| color: #2285D0 !important; | |||
| font-family: PingFangSC-Regular, PingFang SC; | |||
| font-weight: 400; | |||
| font-size: 14px; | |||
| color: #2285D0; | |||
| } | |||
| .el-dropdown-menu{ | |||
| padding: 0px !important; | |||
| } | |||
| .el-popper[x-placement^=bottom] .popper__arrow { | |||
| display: none !important; | |||
| } | |||
| .el-dropdown-menu__item{ | |||
| color: #333333 !important; | |||
| height: 34px !important; | |||
| } | |||
| li.el-dropdown-menu__item:hover { | |||
| background: #f2f2f2 !important; | |||
| } | |||
| .icon { | |||
| width: 1em; | |||
| height: 1em; | |||
| vertical-align: -0.15em; | |||
| fill: currentColor; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <style> | |||
| .myTrees .el-tree-node__expand-icon{ | |||
| visibility: hidden; | |||
| } | |||
| .monaco-editor .line-numbers{ | |||
| color: #999; | |||
| } | |||
| </style> | |||