打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
VUE实现Studio管理后台(七):树形结构,文件树,节点树共用一套代码NodeTree

本次介绍的内容,稍稍复杂了一点,用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 = 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,把整个节点的数据传入该控件。该组件递归调用自身,从而形成树形结构。三个状态:opened(展开),closed(闭合),selected(选中)存于model数据中,这样在控件外部,通过修改model,也可以控制节点状态。

本功能介绍完毕,代码请自行到github获取相应历史版本:
https://github.com/vularsoft/studio-ui

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ASP.NET中TreeView控件使用小结
用户控件-树形结构封装
jQuery自定义编辑的树形菜单代码
递归示例(二):WinForm之TreeView的应用
HashMap深入理解
EXT表单组件常见属性介绍(三)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服