内容简介:本次介绍的内容,稍稍复杂了一点,用VUE实现树形结构。目前这个属性结构还没有编辑功能,仅仅是展示。明天再开一篇文章,介绍如何增加编辑功能,标题都想好了。先看今天的展示效果:构建树必须用到递归,使用slot这种直观明了的方式,已经行不通了。只能通过属性参数,传递一个树形的数据结构给组件,传入的数据结构大致是这个样子:
本次介绍的内容,稍稍复杂了一点,用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> <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="inputValue.selected ? 'selected' :''" > <div @click="click" @contextmenu.prevent = 'onContextMenu' > <div @click="iconClick"> <i v-show="icon" :class="icon"></i> </div> {{inputValue.title}} </div> <div v-show="showChild"> <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以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- MyBatis collection 集合嵌套查询树形节点
- MySQL 实现树形的遍历
- 巧用 MyBatis 构建树形结构
- js 将线性数据转为树形
- 树形结构数据存储方案(五):区间嵌套
- 树形结构数据存储方案(三):闭包表
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
京东平台运营攻略(全彩)
京东商学院 / 电子工业出版社 / 2015-5 / 69.00元
2014 年年末,京东POP 开放平台的入驻商家已超过6 万,京东平台被广泛关注和认可的同时,在电商江湖中仍颇具神秘色彩。面对碎片化的信息,京东的店铺经营者及希望入驻京东的准商家们,对于在京东如何利用丰富的各类平台资源,搭建并运营京东店铺,一直很难找到全面而系统的资料。 《京东平台运营攻略(全彩)》由京东官方出品,动员了京东内部涉及第三方店铺业务线的众多部门,由多位业务精英参与撰写,保证了内......一起来看看 《京东平台运营攻略(全彩)》 这本书的介绍吧!