组件视图样式

当我们做多级菜单或者权限列表管理的时候,大多会采用树形结构来实现,有的朋友为了省事,不想多费脑力,多费时间,直接几层for循环就做了个差不多的树形组件,更省事的朋友直接拿ElementUI中的Tree树形组件来完成需求,这时,稍微有点要求的产品就来要求了:不行,这组件样式都是竖着的,要是权限列表多了,这还了得?改。
这里笔者仿照ElementUI的风格,自定义了个简单的树形组件,先看样式:

递归组件说明

什么是递归组件?简单的说就是组件自己调用自己。有些朋友可能就懵了,哎呦卧槽,组件还能调用自己?这么牛X?对,就是这么牛X。
咱们先不谈如何实现,先看工作中会碰到的一种需求。
看如下示例数据

list: [{id: "1",label: "一级菜单1",checked: false,children: [{id: "11",label: "二级菜单1",checked: false,children: [{id: "111",label: "三级菜单1",checked: false,},],},{id: "12",label: "二级菜单2",checked: false,children: [{id: "121",label: "三级菜单1",checked: false,},{id: "122",label: "三级菜单2",checked: false,},],},],},],

按照以上数据来实现视图,很多朋友一看这有啥难度,然后直接上手写代码,最终写了如下代码:

  <div class="example"><div v-for="item in list" :key="item.id" class="example-item"><el-checkbox v-model="item.checked">{{item.label}}</el-checkbox><div v-for="it in item.children" :key="it.id" class="example-item"><el-checkbox v-model="it.checked">{{it.label}}</el-checkbox><div v-for="i in it.children" :key="i.id" class="example-item"><el-checkbox v-model="it.checked">{{i.label}}</el-checkbox></div></div></div></div>

这么简答的需求我要是做不出来,那简直是侮辱我80的智商。这时,有朋友瞪着眼睛多看了两眼,咦?我瞅着怎么这代码结构好像有规律可循?这要是多几层数据,我岂不是要继续重复嵌套下去?咱是有水平的程序员,怎么能总是干这种重复的事情呢?

于是,递归的思想冒出来了,我可以把上述代码的基本结构抽离出来,成一个组件,然后让这个组件嵌套调用自己,这样无论有多少层数据,我都不用担心了。

请看如下:
创建ExmapleItem.vue

<template><div class="example-item"><el-checkbox v-model="item.checked">{{item.label}}</el-checkbox><div v-if="item.children"><example-item v-for="it in item.children" :key="it.id" :item="it"></example-item></div></div>
</template>
<script>
export default {name: "example-item",props:{item: {type: Object,}}
}
</script>

上述示例代码是递归组件的一个子项,咱们要实现上述list数据内容,还需要另外创建一个组件,在这个组件引入递归组件,这么使用:

<example-item v-for="item in list" :key="item.id" :item="item"></example-item>

以上简单示例就实现了一个简单的递归组件方案,这里需要重点说明的一点就是,在组件内部要加上name名称,该名称写的是什么,递归调用的组件名称就是什么,注意递归组件内部数据的传递参数要保持一致。

自定义CTree树形组件

上述示例看明白了,递归组件也明白了,那么,就可以进行自定义树形组件的开发工作。

不想看下面内容的朋友,可以直接点此下载完整内容。

新建一个CTree文件夹,在内部创建两个文件index.vue和CTreeItem.vue。

index.vue

<template><div class="c-tree-component"><c-tree-item v-for="item in tree" :key="item.value" :treeObj="item" :size="size"></c-tree-item></div>
</template>
<script>
import treeStore from "./tree-store";
import CTreeItem from "./CTreeItem.vue";export default {name: "CTree",data() {return {tree: [],};},props: {data: {type: Array,default: [],},size: {type: String,default: "normal", // small 小},mode: {type: String,default: "normal", // auth 权限模式,此模式下,子组件被选中,父组件一定被选中},expanded: {type: Boolean,default: true,},},mounted() {this.tree = this.initData(this.data);// 组件事件监听,监听子组件状态变化,获取和更改父组件数据treeStore.$on("tree-change", (res) => {// res当前点击项的数据// 如果当前模式为auth,执行auth模式组件选择方式if (this.mode === "auth") {let result = this.reInitParentCheck(this.tree, res.value);this.tree = result.data;} else {// 拿到更新后的部分数据,normal模式下,子组件的check随父组件变化res = this.updateSonCheck(res, res.checked);// 更新数据,同步显示组件样式this.tree = this.updateData(this.tree, res);// 更新数据,更新所有checkBox状态this.tree = this.reInitCheck(this.tree);}// console.log(this.tree);// 向外部引用父组件传递选中的value值let result = this.getAllValue(this.tree);this.$emit("change", result);// console.log(result);});},methods: {/*** 采用递归方式重新修改data数据* 方便组件进行选中,展开等操作*/initData(e) {let temp = [];e.forEach((ele) => {if (ele.children && ele.children.length > 0) {ele.children = this.initData(ele.children);}temp.push({...ele,checked: false, // 是否选中expanded: this.expanded, // 是否展开indeterminate: false, // checkBox的中间态});});return temp;},/*** 更新全部数据内容*/updateData(e, target) {let isFind = false;e.forEach((ele) => {if (ele.value === target.value) {ele = target;isFind = true;}});if (!isFind && e.children && e.children.length > 0) {e.children = this.updateData(e.children, target);}return e;},/*** 更新数据的check状态* 判断是否有children,如果有,全部更新为父级数据的check状态*/updateSonCheck(e, checked) {if (e.children && e.children.length > 0) {e.children.forEach((i) => {i.checked = checked;if (i.children && i.children.length > 0) {i = this.updateSonCheck(i, checked);}});}e.checked = checked;return e;},/*** 更新全部数据的视图check状态* none all half*/reInitCheck(e) {e.forEach((ele) => {if (ele.children && ele.children.length > 0) {ele.children = this.reInitCheck(ele.children);let res = "";res = this.getNodeStatus(ele.children);if (res === "none") {ele.checked = false;ele.indeterminate = false;} else if (res === "all") {ele.checked = true;ele.indeterminate = false;} else if (res === "half") {ele.checked = false;ele.indeterminate = true;}}});return e;},/*** 更新全部父级数据的视图check状态* mode为auth模式时,调用此方法* 根据当前级别所有项check状态更新父组件check状态* 父组件check不影响子组件,子组件check必定更新父组件check*/reInitParentCheck(e, current) {let isChecked = false;for (let i = 0; i < e.length; i++) {let ele = e[i];// 如果遍历选项与当前操作项的value相等,将所有子项置为同样状态if (ele.value === current) {// 更新所有子项数据ele = this.updateSonCheck(ele, ele.checked);// 如果当前操作项check为true,将标记isChecked置为true,表示当前级别有选中项if (ele.checked) {isChecked = true;continue;}}if (ele.children && ele.children.length > 0) {let res = this.reInitParentCheck(ele.children, current);ele.children = res.data;ele.checked = res.isChecked;}if (ele.checked) {isChecked = true;}}return {data: e,isChecked: isChecked,};},/*** 获取每一节点的状态,用于更新checkBox中间态* 如果该节点所有同级checkBox都被选中,返回all* 0个选中,返回none* 其余情况返回half*/getNodeStatus(e) {let checkedNum = 0;e.forEach((ele) => {if (ele.checked) {++checkedNum;}});if (checkedNum === 0) {return "none";} else if (checkedNum === e.length) {return "all";} else {return "half";}},/*** 获取所有被选中项的value*/getAllValue(e) {let temp = [];e.forEach((ele) => {if (ele.checked) {temp.push(ele.value);}if (ele.children && ele.children.length > 0) {let res = this.getAllValue(ele.children);temp = temp.concat(res);}});return temp;},},components: {CTreeItem,},
};
</script>

CTreeItem.vue

<template><div class="c-tree" @click.stop="_handleClick"><div class="c-tree-level" :style="{height:size=='small'?'26px':'40px'}"><i class="el-icon-caret-right c-tree-level-icon"></i><el-checkbox @click.native.stop v-model="obj.checked" :indeterminate="obj.indeterminate" @change="_handleChange"></el-checkbox><span>{{obj.label}}</span></div><el-collapse-transition><div v-if="obj.children" v-show="obj.expanded" class="c-tree-level-child" :class="{'level-last':!obj.children[0].children}"><c-tree-item v-for="it in obj.children" :key="it.value" :treeObj="it" :size="size"></c-tree-item></div></el-collapse-transition></div>
</template>
<script>
import treeStore from "./tree-store";export default {name: "CTreeItem",data() {return {obj: {children: [],checked: false,expanded: false,},};},props: {treeObj: {type: Object,},size: {type: String,default: "normal", // small 小},},watch: {"treeObj.checked": function (e) {this.obj = this.treeObj;},},created() {this.obj = this.treeObj;},methods: {/*** 点击操作,控制展开和收缩,并更新父组件数据*/_handleClick() {// 修改当前点击项的收缩展开状态this.obj.expanded = !this.obj.expanded;this._handleChange();},/*** checkBox数据修改监听* 更新局部组件视图,将更新的组件状态传给父组件*/_handleChange() {this.$nextTick(() => {treeStore.$emit("tree-change", this.obj);});},},
};
</script>
<style lang="scss" scoped>
.c-tree {&-level {display: flex;align-items: center;cursor: pointer;height: 40px;&:hover,&.focus {background-color: #f5f7fa;}&.expanded {.c-tree-level-icon {transform: rotate(90deg);}}&-icon {color: #c0c4cc;font-size: 12px;transition: 0.3s;transform: rotate(0deg);padding: 6px;}.el-checkbox {margin-right: 8px;}&-child {margin-left: 24px;&.level-last {display: flex;align-items: center;.c-tree-level {padding-right: 12px;margin-right: 24px;&-icon {color: transparent;}}}}}
}
</style>

递归组件事件传递

在递归组件中,寻常使用的this.$emit()就无法生效,我们这里可以采用eventBus方案来实现,有朋友可能疑惑:eventBus是个啥玩意?你就当它是一种解决方案就可以,方法很简单,另外创建一个Vue实例,让emit和on绑定同一个实例就可以在递归组件内部发送事件了。
如上述代码中的tree-store.js.

import Vue from 'vue';
export default new Vue();

外部按照正常组件引用方式,引入index.vue组件就可以。

<c-tree :data="tree" mode="auth" :expanded="true"></c-tree>

本组件提供了两种mode,默认“normal”,checkBox有中间态,子组件不全部选中情况下,父组件check状态为false,“auth”为权限列表选择模式,一旦子组件有选中项,父组件一定为选中状态。

expanded控制是否展开。

size:默认normal,间隔较大,如上图所示,“small”表示各级之间间隔较小,表现形式如同ElementUI中的tree组件。

直接使用本案例的朋友这里要注意一点,外部传入数据可以按照如下方式传入,label表示名称,value表示key或者id等:
示例参数:

tree: [{label: "一级1",value: "level_1",children: [{label: "二级1",value: "level_1_1",children: [{label: "三级1",value: "level_1_1_1",},{label: "三级2",value: "level_1_1_2",},],},{label: "二级2",value: "level_1_2",children: [{label: "三级2-1",value: "level_1_2_1",},{label: "三级2-2",value: "level_1_2_2",},{label: "三级2-3",value: "level_1_2_3",},],},{label: "二级3",value: "level_1_3",children: [{label: "三级3-1",value: "level_1_3_1",},{label: "三级3-2",value: "level_1_3_2",},{label: "三级3-3",value: "level_1_3_3",},{label: "三级3-4",value: "level_1_3_4",},],},],},{label: "一级2",value: "level_2",children: [{label: "二级2-1",value: "level_2_1",children: [{label: "三级2-1-1",value: "level_2_1_1",},],},],},],

el-collapse-transition不生效问题

细心的朋友会发现,上述代码中用到了el-collapse-transition标签,这是ElementUI提供的内置过度动画中的一个标签,用来进行展开折叠操作。

有朋友自己写组件用到了此标签,引用后发现不生效,并没有展开折叠的动效。

解决方法:
1.用一个单独的简单示例操作此标签,查看是否生效,如果生效,说明问题出在你引用此标签的组件内。
2.检查组件的最外层div是否有v-if操作,如果有,干掉,再次检查是否生效。

el-collapse-transition标签的外层,最好不要有v-if操作,尤其是当你采用了局部视图更新方案时,此标签会因为视图更新失效。

点此下完源码资源


欢迎关注本博主:小圣贤君,有问题可以留言哦~

自定义ElementUI风格树形组件,详解递归组件的使用及事件数据传递,视图更新等问题相关推荐

  1. 用友二次开发_详解ERP系统与MES系统的数据传递——永康用友

    详解ERP系统与MES系统的数据传递--永康用友 摘要:本文将按照数据的传递方向"从ERP到MES"和"MES到ERP"分别介绍MES系统如何与其他软件连接,才 ...

  2. vue.js实战 第一篇 第七章 组件详解_组件通信

    正向数据传递props <div id="app"><my-component message="来自父组件的数据"></my-c ...

  3. android studio 跳转后保留原页面数据_Intent详解以及Activity的跳转与数据传递

    在上一次讲述Activity的时候,还有一个非常重要且常用的知识点没有讲,就是不同Activity之间的跳转和数据传递.我们在平常在使用app应用的时候,Activity的跳转和数据传递是经常会接触到 ...

  4. 跟着小马哥学系列之 Spring AOP(Pointcut 组件详解)

    学好路更宽,钱多少加班. --小马哥 版本修订 2021.5.19:去除目录 2021.5.21:引用 Spring 官方 Pointcut 概念,修改 Pointcut 功能表述 简介 大家好,我是 ...

  5. ue4移动到一定距离_UE4移动组件详解(一)——移动框架与实现原理

    原文链接(转载请标明):UE4移动组件详解(一)--移动框架与实现原理_Jerish的博客-CSDN博客​blog.csdn.net 前言 关于UE4的移动组件,我写了一篇非常详细的分析文档.由于篇幅 ...

  6. Android基础四大组件详解

    Android四大组件详解 博主接触Android开发将近一年,从最初的JavaSE开始,到Android基础,一直学的糊糊涂涂,最近想整理一番 android基础, 顺便把自己的学习开发经验分享给大 ...

  7. Flume常用组件详解之Source

    Flume常用组件详解:Source Flume支持众多的source.sink.拦截器等组件具体实现,详细手册可参考官方文档http://flume.apache.org/FlumeUserGuid ...

  8. Ansible 实战案例--Ansible Ad-Hoc 组件详解

    Ansible Ad-Hoc 组件详解 前言 一.命令执行 1.shell 2.command 3.remove 二.包管理 1.yum_repository 2.yum 三.服务管理模块 1.ser ...

  9. UE4移动组件详解(三)——RootMotion与特殊移动模式的实现思路

    更多相关内容参考 UE4移动组件详解(一)--移动框架与实现原理 UE4移动组件详解(二)--移动同步机制 五.特殊移动模式的实现思路 这一章节不是详细的实现教程,只是给大家提供常见游戏玩法的一些设计 ...

最新文章

  1. vue 输入框限制3位小数_vue+element 中 el-input框 限制 只能输入数字及几位小数(自定义)和输入框之键盘...
  2. Aliyun Java Initializr 和 Spring 官方的到底有什么区别?
  3. 细说Android apk四代签名:APK v1、APK v2、APK v3、APK v4
  4. centos安装python3.4 pip3
  5. DevExpress GridView 添加和设置右键菜单
  6. matlab里面的if跟几个end,在编程中写两个end if 是什么意思
  7. Java Jersey2使用总结
  8. 《PIC微控制器项目设计:C语言》一导读
  9. python数据分析特训营课件,Python数据分析PPT学习课件
  10. 前端地图之色斑图渲染(数据格式为.tif的栅格数据)(一)——以leaflet为例
  11. VaR、CoVaR、delta CoVaR计算方法综述 案例与代码
  12. 靠天收粮江西 国稻种芯·中国水稻节:锐变高标准农田示范省
  13. 右键桌面的计算机图标管理没用,计算机桌面图标消失了,鼠标右键没有响应
  14. 奈奎斯特曲线怎么确定w的值matlab,用MATLAB绘制Nyquist图.ppt
  15. 【历史上的今天】4 月 23 日:YouTube 上传第一个视频;网易云音乐正式上线;数字音频播放器的发明者出生
  16. python计算log2×_带有Python示例的math.log2()方法
  17. AI燃到爆!中关村人工智能产业论坛十位大咖演讲干货
  18. openstack policy机制
  19. “apt-get update”命令
  20. NLTK11《Python自然语言处理》code10 分析语句的含义

热门文章

  1. STM32 HAL库使用IIC
  2. 【声学基础】概述——传播
  3. 计算机网络(一)——一些概念
  4. linux内核中打开文件 及属性控制
  5. linux用户命令解释器,Linux下的命令解释器 ash.exe
  6. node.js查询oracle,nodejs操作oracle数据库示例
  7. php7 验证url格式,url的组成格式为
  8. 【Spring】模块
  9. 力扣401.二进制手表
  10. java并发之CopyOnWriteArraySet