本次介紹的內容,稍稍復雜了一點,用VUE實作樹形結構,目前這個屬性結構還沒有編輯功能,僅僅是展示,明天再開一篇文章,介紹如何增加編輯功能,標題都想好了,先看今天的展示效果:

構建樹必須用到遞回,使用slot這種直觀明了的方式,已經行不通了,只能通過屬性引數,傳遞一個樹形的資料結構給組件,傳入的資料結構大致是這個樣子:
[ { title:‘頁面 ’ selected:false, opened:false, isFolder:true, children:[ { title:'index.html', selected:false, opened:false, icon:"far fa-file-code", }, { title:'product.html', selected:false, opened:false, icon:"far fa-file-code", }, ], }, { title:‘樣式’ selected:false, opened:false, isFolder:true, children:[ { title:'style.css', selected:false, opened:false, icon:"far fa-file-code", }, ], }, ]
每個節點通過children嵌套子節點,需要注意的是,我們希望這顆樹是可以被編輯的,可以增加、洗掉、編輯其節點,所以需要資料的雙向系結,不能通過普通屬性props傳遞給組件,而是通過v-model傳遞,
RXEditor專案中,只有兩個地方用到了樹形結構,要制作的組件滿足這兩處需求就可以,因為不是構建一個通用類別庫,就可以相對簡單些,這兩處地方一處用于展示并編輯檔案目錄結構,一處是節點樹,純顯示,沒有編輯功能,檔案樹只有葉子節點可以被選中,節點樹所有節點都可以被選中,都是單選,無復選需求,
給這個控制元件取個大氣的名字,叫NodeTree吧,先看如何使用NodeTree,
第一處呼叫:
<NodeTree v-model="files" :openIcon="'fas fa-folder-open'" :closeIcon="'fas fa-folder'" > </NodeTree>
第二處呼叫:
<NodeTree v-model="nodes" :openIcon="'fas fa-caret-down'" :closeIcon="'fas fa-caret-right'" :leafIcon="''" :folderCanbeSelected = 'true'> </NodeTree>
通過v-model傳遞樹形資料結構,openIcon是節點展開時的圖示,closeIcion是節點閉合時的圖示,leafIcon是沒有子節點時的圖示,這些圖示如果不設定,會有預設值,是檔案夾跟檔案的樣子,為了增加可擴展性,樹形資料結構也可以放置圖示,資料結構里的圖示設定優先級高,可以覆寫控制元件的設定,明白個原理,想做成什么樣子,看自己的專案需求,folderCanbeSelected 引數是指含有子節點的節點(比如檔案夾)是否可以被選中,
在src目錄下新建tree目錄,放兩個檔案:

NodeTree是樹形控制元件,TreeNode是樹形控制元件內部的節點,名字稍微優點繞,但是是我喜歡的命名方式,
NodeTree.vue的代碼(省略CSS):
<template> <div class="node-tree"> <TreeNode v-for = "(node, i) in inputValue" :key = "i" v-model = "inputValue[i]" :openIcon = "openIcon" :closeIcon = "closeIcon" :leafIcon = "leafIcon" :folderCanbeSelected = "folderCanbeSelected" @nodeSelected = "nodeSelected" ></TreeNode> </div> </template> <script> import TreeNode from "./TreeNode.vue" export default { name: 'FileTree', props: { value: { default: []}, openIcon:{ default: 'fas fa-folder-open'}, closeIcon:{ default: 'fas fa-folder'}, leafIcon:{ default: 'fas fa-file' }, folderCanbeSelected:{ default:false } }, components:{ TreeNode }, data() { return { }; }, computed:{ inputValue: { get:function() { return this.value; }, set:function(val) { this.$emit('input', val); }, }, }, methods: { nodeSelected(selectedNode){ this.inputValue.forEach(child=>{ this.resetSelected(selectedNode, child) }) this.$emit('nodeSelected', selectedNode) }, //遞回充置選擇狀態 resetSelected(selectedNode, node){ node.selected = (node === selectedNode) if(node.children){ node.children.forEach(child=>{ this.resetSelected(selectedNode, child) }) } } }, } </script>
這個代碼邏輯很簡單,就是接收外面引數,回圈呼叫TreeNode,要自定義v-model的話,需要用到屬性(props)value,計算屬性inputValue用于修改value,具體原理,可以參考VUE官方檔案,
需要特殊注意的是nodeSelected事件,這個事件在子節點產生,通過冒泡的方式層層往父節點發送,最后到達NodeTree組件,NodeTree組件再通過$emit方法,分發到外層呼叫組件,
這次實作的控制元件是單選,排他的,需要遞回呼叫resetSelected方法消除其它節點的選中狀態,
TreeNode組件的代碼如下(省略CSS,如需要,請到GIthub獲取):
<template> <div class="tree-node" :class="inputValue.selected ? 'selected' :''" > <div class="node-title" @click="click" @contextmenu.prevent = 'onContextMenu' > <div class="node-icon" @click="iconClick"> <i v-show="icon" :class="icon"></i> </div> {{inputValue.title}} </div> <div v-show="showChild" class="children-nodes"> <TreeNode v-for="(child, i) in inputValue.children" :openIcon = "openIcon" :closeIcon = "closeIcon" :leafIcon = "leafIcon" :key="i" :folderCanbeSelected = "folderCanbeSelected" v-model="inputValue.children[i]" @nodeSelected = "nodeSelected" ></TreeNode> </div> </div> </template> <script> export default { name: 'TreeNode', props: { value: { default: {}}, openIcon:{ default: 'fas fa-folder-open'}, closeIcon:{ default: 'fas fa-folder'}, leafIcon:{ default: 'fas fa-file' }, folderCanbeSelected:{default: false}, }, data() { return { } }, computed:{ inputValue: { get:function() { return this.value; }, set:function(val) { this.$emit('input', val); }, }, icon(){ if(this.hasChildren){ return this.inputValue.opened ? this.openIcon : this.closeIcon } return this.inputValue.icon !== undefined ? this.inputValue.icon : this.leafIcon }, showChild(){ return this.hasChildren && this.inputValue.opened }, hasChildren(){ return this.inputValue.children &&this.inputValue.children.length > 0 }, }, methods: { click(){ if((this.hasChildren && this.folderCanbeSelected) || !this.hasChildren){ this.inputValue.selected = true this.$emit('nodeSelected', this.inputValue) } else { this.inputValue.opened = !this.inputValue.opened } }, iconClick(event){ if(this.hasChildren && this.folderCanbeSelected){ event.stopPropagation() this.inputValue.opened = !this.inputValue.opened } }, nodeSelected(node){ this.$emit('nodeSelected', node) }, onContextMenu(event){ console.log(event) } }, } </script>
父組件呼叫時通過v-mode,把整個節點的資料傳入該控制元件,該組件遞回呼叫自身,從而形成樹形結構,三個狀態:opened(展開),closed(閉合),selected(選中)存于model資料中,這樣在控制元件外部,通過修改model,也可以控制節點狀態,
本功能介紹完畢,代碼請自行到github獲取相應歷史版本:
https://github.com/vularsoft/studio-ui
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/144321.html
標籤:JavaScript
