| @@ -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> | |||||