最近在作一个管理系统,页面左侧须要一个目录树,便于文件的操做,不想从头开始造轮子,因而就考虑采用iview或者element的tree,调研后发现iview的tree仍是有点局限,没有拖拽移动功能,没有懒加载子目录的功能等等,而element则比较符合咱们的需求,虽然坑也是有点多...html
在<el-tree>
中加入lazy属性,可让树变成懒加载的tree,即默认渲染左边的下拉小箭头,点击每一个小箭头能够触发一次load操做,能够实现动态获取树下面节点的操做前端
这里遇到了第一个问题:怎么获取每一个节点对应的路径?vue
咱们须要根据每一个节点所在的路径向后台发送请求,节点的路径就是咱们请求的资源路径,固然拿到这个路径的方法就是字符串拼接,拿到当前节点的node对象,根据它是否存在parent,将它的parent推入咱们的currentPath数组中,每次推动数组以后须要将当前节点设置为它的parentnode
# 获取当前文件所在路径
getCurrentPath (node) {
if (node && node.data && node.data.name) {
let nodeParent = node.parent
this.currentPath = [node.data.name]
while (nodeParent && nodeParent.data && nodeParent.data.name
&& typeof nodeParent.data === 'object') {
this.currentPath.unshift(nodeParent.data.name)
nodeParent = nodeParent.parent
}
}
}
复制代码
咱们建立文件的时候,是不须要lazy load
时生成的小箭头的,由于文件下面是不能建立文件的,所以,须要作一下配置,在建立文件的时候给el-tree传一下类型,跟它说我要建立的是文件,不要给我渲染一个小箭头了,那要怎么配置呢?其实这个问题element官方文档有具体的例子react
第二个问题:怎么选择性渲染
lazy load
生成的小箭头?后端
<el-tree
:props="props1"
:load="loadNode1"
lazy>
</el-tree>
<script>
export default {
data() {
return {
props1: {
label: 'name',
children: 'zones',
isLeaf: 'leaf'
},
};
},
methods: {
loadNode1(node, resolve) {
if (node.level === 0) {
return resolve([{ name: 'region' }]);
}
if (node.level > 1) return resolve([]);
setTimeout(() => {
const data = [{
name: 'leaf',
leaf: true
}, {
name: 'zone'
}];
resolve(data);
}, 500);
}
}
};
</script>
复制代码
renderContent会监听data里面的属性值来决定是否渲染和渲染对应的视图,若是有对某个data的属性值判断的须要,须要对那个属性值进行初始化数组
例如: 根据node节点的data.type决定渲染的内容,一开始须要给data.type赋初始值,若是不赋值则监听不到变化(我就是由于一开始没有初始化type,直接设置data.type='edit',而后视图一直没更新......)浏览器
第三个问题: 为何data.type变化了,视图一直没更新?bash
# 重命名编辑框
if (data.type === 'edit') {
return h('input', {
attrs: {
id: 'treeInput',
value: this.currentNodeData.name
},
on: {
blur: (e) => {
this.updateCurrentNode(e.target.value || data.name)
},
keyup: (e) => {
if (e.keyCode === 13 || e.keyCode === 27) {
e.target.blur()
}
}
}
})
}
# 新建编辑框
if (data.type === 'input') {
return h('input', {
attrs: {
id: 'treeInput'
},
on: {
blur: (e) => {
this.createNewNode(e.target.value)
},
keyup: (e) => {
if (e.keyCode === 13 || e.keyCode === 27) {
e.target.blur()
}
}
}
})
}
复制代码
这里还有一个小问题:on-blur
和on-keyup
原本我写的都是this.createNewNode(e.target.value)
,可是触发了两次create
操做,原来是keyup
的同时输入框也会失去焦点,因此就触发了blur
,所以就用e.target.blur()
代替了本来的写法,统一用blur
来实现触发create
的操做数据结构
第四个问题: 为何须要setTimeout?
咱们对树的操做的过程常常须要使用到setTimeout(fn, 0),例如:
tryToCreateNode (type) {
this.$refs.tree.append({
id: 'treeInput',
type: 'create'
}, this.currentNodeData.id)
this.currentNode.expanded = true
this.createNewWay = 'append'
setTimeout(() => {
this.$el.querySelector('#treeInput').focus()
}, 0)
}
复制代码
这是由于当咱们执行了append操做时,触发了浏览器的重排和重绘,须要从新构建dom树,这个过程是比较耗费时间的,若是咱们接下来直接执行this.$el.querySelector('#treeInput').focus()
,这时候dom树是尚未treeInput这个元素的,setTimeout会将querySelector操做放进任务队列中去,等到主进程完成dom树的构建后再执行setTimeout里面的操做,这时候咱们就能够拿到咱们的treeInput了
第五个问题:若是不用懒加载,咱们怎么渲染目录树?
由于咱们这个项目后端存储文件的方式就是一个文件系统,就跟咱们在本地看到的同样,一层一层地存文件和文件夹,所以咱们前端获取文件也是得一层一层地发请求,拿到对应层级的文件,这就得考虑经过递归的方式,将每次获取获得的数据保存在一个treeData对象中,如第一层的数据就是treeData[0]
,treeData[1]
,第二层的数据就是treeData[0].children
, treeData[1].children
等等
获取某一层数据
将上一层获取到的文件夹类型的数据再传入递归函数
当获取那个层级的文件数为0, 或者都是文件的时候,结束递归
# 递归获取目录树数据
async getTreeDataRecursively (path) {
# getDirByPath是咱们本身定义的获取对应目录文件的函数
let dataList = await this.getDirByPath(path)
if (dataList && dataList.length >= 0) {
if (!dataList || dataList.length === 0 || dataList.every(el => el.type === 'file')) {
return dataList
} else {
for (let i = 0; i < dataList.length; i++) {
let path = dataList[i].id
if (dataList[i].isDir) {
this.$set(dataList[i], 'children', await this.getTreeDataRecursively(path))
}
}
return dataList
}
}
}
复制代码
myObj.name = 'aaa'
这样的方式为一个对象的新增某个属性,不会触发视图的更新,能够经过$set
来新增,从而触发更新,详细见官方文档递归获取到的数据要怎么插入到对应的节点呢?
在上一个问题中,咱们解决了每一个节点下面子节点的获取,获得了某个路径下包含全部子节点的一个对象,这个对象须要插入到对应的目录结构,例如:
--- a
--- aa
--- aaa
--- aaa1
--- aaaa
复制代码
咱们经过getTreeDataRecursively('/a/aa')
获取到了/a/aa
下面的目录结构: aaa
和aaa1.children(aaaa)
, 那么咱们如今想要把它插入到对应的路径/a/aa
下面,要怎么实现呢?
一开始的思路是经过路径跟每一个节点的id比较,由于咱们在上面的递归函数中,把路径赋给了每一个节点的id,若是id = '/a/aa',那么咱们就将数据dataList插入到下面,总体思路是没有问题的,可是要改变treeData对应层级的数据这一步卡住了,没法实现...
既然无法经过改变treeData的数据结构,我就翻看起了element tree的官方文档,终于找到了解决方案...
咱们能够官方提供的这个方法,这里的key
就是咱们的id
(/a/aa
), 而value
则是dataList
这就是这一个庞大的组件咱们遇到的坑,固然还有不少没有写下来,本文只是做为纪录重要的几个点,也方便有遇到一样的问题的同窗查看,能提供一点小小的思路也是很荣幸哈~