之前按照项目需求使用element中的tree来创建目录列表,如今记录一下。
一、项目需求
1.完整展示目录列表
2.右击节点选择重命名,删除,创建文件夹三个选项
3.拖拽文件夹,其中拖拽文件夹有以下要求:
a. 如果该文件夹内已存在上传文件,则其他文件夹不能拖拽进入该文件夹内
b.整个目录中有且仅有一个根目录,拖拽文件夹的范围只能在该根目录里面
4.重命名文件夹要求:
a.重命名完成后按enter键完成
5.删除文件夹要求:
a.如果该文件夹内已含有上传文件,则删除失败
6.创建文件夹要求:
a.如果该文件夹内已含有上传文件,则创建失败
二、思路整理
1.在mounted中向后台请求项目列表,然后存储treeData
2.鼠标右键弹出的是menu菜单选项框,对于获取点击节点的信息可以使用el-tree中的 @node-contextmenu="rightClick"
3.选择删除文件夹的时候要注意在发送请求的同时也要完成前端的“删除”动作,使用map,indexOf, slice
4.重命名中使用@keyup.enter.native来触发请求,使用focus()聚焦输入框
5.鼠标右击的时候menu的位置是随之改变的,且当鼠标位置加上menu的高度或宽度超出视频范围的时候要合适调节menu的位置。
/* 菜单定位基于鼠标点击位置 */ let height = document.documentElement.clientHeight || document.body.clientHeight if (event.clientY + 168 > height) { menu.style.left = event.clientX - 5 + 'px' menu.style.top = event.clientY - 10 - 168 + 'px' } else { menu.style.left = event.clientX + 10 + 'px' menu.style.top = event.clientY + 5 + 'px' }
6.menu的显示与隐藏,当鼠标点击其他位置的时候,menu隐藏
document.addEventListener('click', this.hide, true) document.removeEventListener('click', this.hide)
7.当文件夹命名过长的时候,出现横向滚动条
.comp-tree { margin-top: 1em; overflow-y: hidden; overflow-x: scroll; } .el-tree { min-width: 100%; display:inline-block !important; }
三、源码分析
参考:
在此基础上做的修改
<template> <div v-loading="isLoading" class="comp-tree" @click="hiddenMenu"> <el-tree ref="SlotTree" :data="treeData" :props="defaultProps" :expand-on-click-node="true" highlight-current :node-key="NODE_KEY" @node-click="handleNodeClick" @node-contextmenu="rightClick" @node-drag-start = "handleDragStart" @node-drop="handleDrop" :draggable = draggable default-expand-all > <div class="comp-tr-node" slot-scope="{ node, data }"> <!-- 编辑状态 --> <template v-if="node.isEdit"> <el-input v-model="data.menuName" autofocus size="mini" :ref="'slotTreeInput'+data.recordId" @keyup.enter.native="handleInput(node, data)"> </el-input> </template> <!-- 非编辑状态 --> <template v-else> <!-- 名称: 新增节点增加class(is-new) --> <span> <span> {{ node.label }} </span> </span> </template> </div> </el-tree> <!-- 鼠标右键产生的选项 --> <div v-show="menuVisible" id="menu"> <el-menu class="el-menu-vertical rightClickMenu" @select="handleRightSelect" text-color="#303133" active-text-color = "#303133" > <el-menu-item index="1" class="menuItem"> <i class="el-icon-edit"></i> <span slot="title">重命名</span> </el-menu-item> <el-menu-item index="2" class="menuItem"> <i class="el-icon-folder-add"></i> <span slot="title">新建文件夹</span> </el-menu-item> <el-menu-item index="3" class="menuItem"> <i class="el-icon-delete"></i> <span slot="title">删除</span> </el-menu-item> </el-menu> </div> <detail-win v-if="isShow" @close="closeWin" :node = NODE v-bind="$attrs" v-on="$listeners"></detail-win> </div> </template> <script> import detailWin from './detail-win' import { baseWarn } from 'src/common/fn' export default { components: { detailWin }, data () { return { isLoading: false, // 是否加载 NODE_KEY: 'recordId', // id对应字段 defaultProps: {// 默认设置 children: 'children', label: 'menuName' }, menuVisible: false, isShow: false, clickNode: '', DATA: null, NODE: null, objectID: null, dragPid: null, dragIndex: null, newParentId: null, oldMenuName: null } }, computed: { treeData () { return this.$sysStore.fileTreeData }, addNode () { return this.$sysStore.addNode }, draggable () { if (!this.$store.state.user.isOuterUser) { return true } else { return false } } }, watch: { }, created () { }, mounted () { }, methods: { // 修改节点 handleInput (node, data) { // 退出编辑状态 if (node.isEdit) { this.$set(node, 'isEdit', false) } if (this.oldMenuName === data.menuName) { } else { this.$axios({ url: '........', method: 'post', data: { ... } }) .then(res => { this.$message({ type: 'success', message: '重命名成功' }) }) .catch(function (error) { this.$message({ type: 'error', message: '重命名失败' }) console.log(error) }) } }, // 重命名 handleEdit (node, data) { // 模板文件夹名称不能重命名 if (data.isTemplate === 1) { this.$message.error('该文件夹无法重命名') } else { // 设置编辑状态 if (!node.isEdit) { this.$set(node, 'isEdit', true) } // 输入框聚焦 this.$nextTick(() => { if (this.$refs['slotTreeInput' + data.recordId]) { this.$refs['slotTreeInput' + data.recordId].$refs.input.focus() } }) } }, // 新增节点 handleAdd (node, data) { // console.log(node,data) if (data.fileSum !== 0) { this.$message.error('此文件夹已存在上传文件,不能新增文件夹!') return } this.isShow = true }, // 删除节点 handleDelete (node, data) { if (data.children && data.children.length !== 0) { this.$message.error('此文件夹内含有其他文件夹,不可删除!') return } if (data.fileSum !== 0) { this.$message.error('此文件夹内含有上传文件,不可删除!') } else { this.$confirm(`是否删除${node.label}?`, '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }).then(() => { this.deleteProj(node, data) }).catch(() => {}) } }, deleteProj (node, data) { this.$axios({ url: '...', method: 'post', data: { ... } }) .then(res => { if (res.success) { let _list = node.parent.data.children || node.parent.data// 节点同级数据 let _index = _list.map((c) => c.recordId).indexOf(data.recordId) _list.splice(_index, 1) this.$message.success('删除成功') // 使右侧列表消失 this.$parent.$parent.disabled = true // 直接改变vuex中treeData的数值 let fileTreeData = this.filter(this.treeData, node.data.kid) this.$store.dispatch(`${this.wpstore}/setFileTreeData`, fileTreeData) } else { this.$message.error('删除失败,请重试') } }) }, /** * 删除或者更改treeData中指定kid的子节点 * * @returns arr */ filter (arr, kid) { for (var i = 0; i < arr.length; i++) { var el = arr[i] if (el.kid === kid) { arr.splice(i, 1) } else { if (el.children && el.children.length) { this.filter(el.children, kid) } } } return arr }, // 点击节点 handleNodeClick (node) { this.menuVisible = false this.$store.dispatch(`${this.wpstore}/selectNode`, node) }, // 拖拽开始时记录该节点的pid及其在原来数组中的原始数据 handleDragStart (node) { this.dragPid = node.data.recordPid let p = this.$refs.SlotTree.getNode(this.dragPid) let _list = p.data.children // 节点同级数据 let _index = _list.map((c) => c.recordId).indexOf(node.data.recordId) this.dragIndex = _index }, // 拖拽成功时触发请求 handleDrop (draggingNode, dropNode, dropType, e) { // debugger if (dropNode.data.fileSum !== 0 && dropType === 'inner') { baseWarn('该文件夹内含有上传文件,拖拽失败', null, null, null) let _list = dropNode.data.children // 节点同级数据 let _index = _list.map((c) => c.recordId).indexOf(draggingNode.data.recordId) _list.splice(_index, 1) // 将节点返回到原来的位置上 let p = this.$refs.SlotTree.getNode(this.dragPid) // console.log(p,'p') p.data.children.splice(this.dragIndex, 0, draggingNode.data) } else if (dropNode.data.recordPid === 'root') { baseWarn('无法拖拽为根文件夹', null, null, null) this.$refs.SlotTree.remove(draggingNode.data.recordId) let p = this.$refs.SlotTree.getNode(this.dragPid) p.data.children.splice(this.dragIndex, 0, draggingNode.data) } else { // 算出子节点对于父节点的相对位置 let _list = dropNode.parent.data.children // 拖拽节点同级数据 let _index = _list.map((c) => c.recordId).indexOf(draggingNode.data.recordId) if (_index === -1) { _list = dropNode.childNodes _index = _list.map((c) => c.data.recordId).indexOf(draggingNode.data.recordId) } // 得出目标节点的recordId if (dropNode.parent.data.recordId === this.dragPid) { // 相同的父节点 this.newParentId = this.dragPid } else { this.newParentId = dropNode.parent.data.recordId } // 发送请求 this.$axios({ url: '...', method: 'post', data: { ... } }) .then(res => { this.$message({ type: 'success', message: '拖拽成功' }) }) .catch(function (error) { console.log(error) }) } }, // 鼠标右击 rightClick (event, object, value, element) { this.oldMenuName = object.menuName if (this.$store.state.user.isOuterUser) { this.menuVisible = false } else { if (this.objectID !== object.recordId) { this.objectID = object.recordId this.menuVisible = true this.DATA = object this.NODE = value } else { this.menuVisible = !this.menuVisible } // document.addEventListener('click', (e) => { // this.menuVisible = false // }) this.hiddenMenu() let menu = document.querySelector('.rightClickMenu') /* 菜单定位基于鼠标点击位置 */ let height = document.documentElement.clientHeight || document.body.clientHeight if (event.clientY + 168 > height) { menu.style.left = event.clientX - 5 + 'px' menu.style.top = event.clientY - 10 - 168 + 'px' } else { menu.style.left = event.clientX + 10 + 'px' menu.style.top = event.clientY + 5 + 'px' } menu.style.position = 'fixed' } }, handleRightSelect (key) { this.menuVisible = false if (key == 1) { this.handleEdit(this.NODE, this.DATA) } else if (key == 2) { this.handleAdd(this.NODE, this.DATA) } else if (key == 3) { this.handleDelete(this.NODE, this.DATA) } }, hiddenMenu () { document.addEventListener('click', this.hide, true) document.removeEventListener('click', this.hide) }, hide() { this.menuVisible = false }, closeWin (val) { this.isShow = val } } } </script> <style lang="scss" scoped> .el-menu { border: solid 1px #e6e6e6 } .comp-tree { margin-top: 1em; overflow-y: hidden; overflow-x: scroll; } .el-tree { min- 100%; display:inline-block !important; } .rightClickMenu { z-index: 999 } </style>
这里仅供参考,具体的编写代码还需根据各位实际项目需求来修改