最近在作一個管理系統,頁面左側須要一個目錄樹,便於文件的操做,不想從頭開始造輪子,因而就考慮採用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
這就是這一個龐大的組件咱們遇到的坑,固然還有不少沒有寫下來,本文只是做爲紀錄重要的幾個點,也方便有遇到一樣的問題的同窗查看,能提供一點小小的思路也是很榮幸哈~