首页 >> 大全

VUE 实现 Studio 管理后台(七):树形结构,文件树

2023-10-15 大全 30 作者:考证青年

本次介绍的内容,稍稍复杂了一点,用 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",},],},
]

每个节点通过 嵌套子节点。需要注意的是,我们希望这颗树是可以被编辑的,可以增加、删除、编辑其节点,所以需要数据的双向绑定,不能通过普通属性 props 传递给组件,而是通过 v-model 传递。

项目中,只有两个地方用到了树形结构,要制作的组件满足这两处需求就可以,因为不是构建一个通用类库,就可以相对简单些。这两处地方一处用于展示并编辑文件目录结构,一处是节点树,纯显示,没有编辑功能。文件树只有叶子节点可以被选中,节点树所有节点都可以被选中。都是单选,无复选需求。

给这个控件取个大气的名字,叫 吧,先看如何使用 。

第一处调用:

_VUE 实现 Studio 管理后台(七):树形结构,文件树_VUE 实现 Studio 管理后台(七):树形结构,文件树

<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 传递树形数据结构, 是节点展开时的图标, 是节点闭合时的图标, 是没有子节点时的图标。这些图标如果不设置,会有缺省值,是文件夹跟文件的样子。为了增加可扩展性,树形数据结构也可以放置图标,数据结构里的图标设置优先级高,可以覆盖控件的设置。明白个原理,想做成什么样子,看自己的项目需求。 参数是指含有子节点的节点(比如文件夹)是否可以被选中。

在 src 目录下新建 tree 目录,放两个文件:

是树形控件, 是树形控件内部的节点,名字稍微优点绕,但是是我喜欢的命名方式。

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

这个代码逻辑很简单,就是接收外面参数,循环调用 。要自定义 v-model 的话,需要用到属性 (props) value,计算属性 用于修改 value,具体原理,可以参考 VUE 官方文档。

需要特殊注意的是 事件,这个事件在子节点产生,通过冒泡的方式层层往父节点发送,最后到达 组件。 组件再通过 $emit 方法,分发到外层调用组件。

这次实现的控件是单选,排他的,需要递归调用 方法消除其它节点的选中状态。

组件的代码如下(省略 CSS,如需要,请到 获取):

<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 = truethis.$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,把整个节点的数据传入该控件。该组件递归调用自身,从而形成树形结构。三个状态:(展开),(闭合),(选中)存于 model 数据中,这样在控件外部,通过修改 model,也可以控制节点状态。

本功能介绍完毕,代码请自行到 获取相应历史版本:

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了