文章先发布于我的个人博客站点上,戳这里
最近,由于公司后台管理系统重构,需要一个用到如图所示的一个功能:
项目使用的Vue和elementUI
一共有4级菜单,要实现全选和反选功能,当下级有一个被选中时,上级也要被选中;当下级全部未被选中时,上级也不能被选中。
网上搜了一圈也没有找到合适的,elementUI的tree组件虽然功能能实现,但是改样式又不方便,于是,只好自己撸一个了。。。。不写不知道自己有多菜!这个东西居然耗时一天。。
这个功能被抽离出来,做成了一个单独的组件。
下面记录下主要的代码: 后台返回的菜单数据格式
let menuData = [
{
"id": 1,
"menName": "管理模式",
"path": "manageMode",
"businessCode": "manageMode",
"parentId": 0,
"isChecked": null,
"menLevel": 1,
"subMenuPermissionsVO": [
{
"id": 105,
"menName": "首页",
"path": "/index",
"businessCode": "index",
"parentId": 1,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": []
},
{
"id": 106,
"menName": "工作报表",
"path": "/report",
"businessCode": "report",
"parentId": 1,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": [
{
"id": 107,
"menName": "数据总揽",
"path": "/reportOverView",
"businessCode": "reportOverView",
"parentId": 106,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
},
{
"id": 109,
"menName": "客服",
"path": "/reportService",
"businessCode": "客服",
"parentId": 106,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": [
{
"id": 110,
"menName": "工作报表",
"path": "/report-service-workreport",
"businessCode": "工作报表",
"parentId": 109,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
},
{
"id": 111,
"menName": "工作质量报表",
"path": "/report-service-qualityreport",
"businessCode": "工作质量报表",
"parentId": 109,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
}
]
},
{
"id": 112,
"menName": "留言报表",
"path": "/reportLeaveWords",
"businessCode": "留言报表",
"parentId": 106,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
},
{
"id": 113,
"menName": "访问轨迹",
"path": "/reportWebsite",
"businessCode": "访问轨迹",
"parentId": 106,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
}
]
},
{
"id": 114,
"menName": "历史会话",
"path": "/history",
"businessCode": "历史会话",
"parentId": 1,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": []
},
{
"id": 115,
"menName": "客服管理",
"path": "/service",
"businessCode": "客服管理",
"parentId": 1,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": [
{
"id": 116,
"menName": "所有客服",
"path": "/service-all",
"businessCode": "所有客服",
"parentId": 115,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
},
{
"id": 117,
"menName": "角色设置",
"path": "/service-role-setting",
"businessCode": "角色设置",
"parentId": 115,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
}
]
},
{
"id": 118,
"menName": "企业账户",
"path": "/business",
"businessCode": "企业账户",
"parentId": 1,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": [
{
"id": 119,
"menName": "团队信息",
"path": "/business-team",
"businessCode": "团队信息",
"parentId": 118,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
},
{
"id": 120,
"menName": "当前服务详情",
"path": "/business-service",
"businessCode": "当前服务详情",
"parentId": 118,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
},
{
"id": 121,
"menName": "团队联系人",
"path": "/business-contacts",
"businessCode": "团队联系人",
"parentId": 118,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": []
}
]
},
{
"id": 122,
"menName": "设置",
"path": "/setting",
"businessCode": "设置",
"parentId": 1,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": [
{
"id": 123,
"menName": "系统设置",
"path": "/setting-system",
"businessCode": "系统设置",
"parentId": 122,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": [
{
"id": 124,
"menName": "个人信息",
"path": "/setting-userinfo",
"businessCode": "个人信息",
"parentId": 123,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
},
{
"id": 125,
"menName": "修改密码",
"path": "/setting-change-pwd",
"businessCode": "修改密码",
"parentId": 123,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
},
{
"id": 126,
"menName": "通知中心",
"path": "/setting-notice",
"businessCode": "通知中心",
"parentId": 123,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
}
]
},
{
"id": 127,
"menName": "在线服务",
"path": "/setting-online",
"businessCode": "在线服务",
"parentId": 122,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": [
{
"id": 128,
"menName": "基础设置",
"path": "/setting-online-base",
"businessCode": "基础设置",
"parentId": 127,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
}
]
},
{
"id": 129,
"menName": "接入设置",
"path": "/setting-join",
"businessCode": "接入设置",
"parentId": 122,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": [
{
"id": 130,
"menName": "网站接入",
"path": "/setting-join-website",
"businessCode": "网站接入",
"parentId": 129,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
}
]
},
{
"id": 131,
"menName": "高级设置",
"path": "/setting-advanced",
"businessCode": "高级设置",
"parentId": 122,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": [
{
"id": 132,
"menName": "短信服务",
"path": "/setting-advanced-msg",
"businessCode": "短信服务",
"parentId": 131,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
},
{
"id": 133,
"menName": "二维码名片",
"path": "/setting-advanced-qrcode",
"businessCode": "二维码名片",
"parentId": 131,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
}
]
}
]
}
]
},
{
"id": 2,
"menName": "客服模式",
"path": "customerMode",
"businessCode": "customer",
"parentId": 0,
"isChecked": null,
"menLevel": 1,
"subMenuPermissionsVO": [
{
"id": 102,
"menName": "留言",
"path": "儿",
"businessCode": "分隔符",
"parentId": 2,
"isChecked": null,
"menLevel": 2,
"subMenuPermissionsVO": [
{
"id": 103,
"menName": "小计成",
"path": "34",
"businessCode": "大幅度",
"parentId": 102,
"isChecked": null,
"menLevel": 3,
"subMenuPermissionsVO": [
{
"id": 104,
"menName": "76657",
"path": "76868",
"businessCode": "658568",
"parentId": 103,
"isChecked": null,
"menLevel": 4,
"subMenuPermissionsVO": []
}
]
}
]
}
]
}
]
复制代码
父组件主要代码:
<tree-table :menuData="menuData" :level="1"></tree-table>
// 其中menuData为后台返回的数据,level为菜单层级,在menuData中有返回,这里主要是用来写样式用的。。
复制代码
子组件组件代码(一个递归组件):
子组件全部代码(说明都在代中有注释):
<template>
<ul class="tree-container" :class="'level'+level">
<li class="" v-for="(item, Index) in menuData" :key="item.id" :class="'level'+item.menLevel">
<div class="tree-node-title" :class="'level'+item.menLevel"><label>
<input type="checkbox" @click.stop="nodeClick(item,Index)" name="menuSelected" :checked="item.isChecked == 1 ? true : false" :value="item.id">
{{item.menName}}-level{{item.menLevel}}
</label></div>
<div class="tree-childnode-container " :class="'level'+item.menLevel">
<CheckBox :menuData="item.subMenuPermissionsVO" :level="item.menLevel"></CheckBox>
</div>
</li>
</ul>
</template>
<script>
export default {
name: 'CheckBox',
props: ["menuData", "level"],
data() {
return {
checkedVal:[]
}
},
created() {
},
mounted() {
},
methods: {
nodeClick(node, index) {
// 通过isChecked判断是否为选中状态:1-选中;0-未选中。这个字段是后端接口返回的
if (node.isChecked == 1) {
this.$set(node, "isChecked", 0)
} else {
this.$set(node, "isChecked", 1)
}
// 个人理解:组件每次递归调用会生成一个实例,this指向这个实例,这些实例相互独立的,所以每次的this都不一样,具体可以控制台打印看下。
this.refreshAllParentNodes(this) // 设置父级状态
this.refreshAllSonNodes(this.$children[index], node.isChecked);// 根据当前点击层级的状态设置自己所有子级的状态
},
refreshAllSonNodes(node, status) {
if (node && node.$children.length) {
node.menuData.map((meunItem, j) => {
this.$set(meunItem, 'isChecked', status);
this.refreshAllSonNodes(node.$children[j], status);
})
}
},
refreshAllParentNodes(node) {
if (node) {
var status = 0;
var nullCount = 0;
var fullCount = 0;
if (node.menuData && node.$parent.menuData) {
node.menuData.map((meunItem, j) => {
if (typeof meunItem.isChecked == 'undefined') {
nullCount++
} else if (meunItem.isChecked == 0) {
nullCount++
} else if (meunItem.isChecked == 1) {
fullCount++
}
})
if (nullCount === node.$children.length) {
// 该状态说明当前点击层级和兄弟层级都没选中,所以父级也是未选中状态
status = 0;
} else {
//该状态说明当前点击层级和兄弟层级只要有一个选中,父级也是选中状态
status = 1;
}
node.$parent.menuData.map(child => {
node.menuData.map((meunItem, j) => {
if (child.id == meunItem.parentId) {
// 根据父级id和子级的parentId是否相等,来确定当前点击的层级的父级是哪一个
this.$set(child, 'isChecked', status);
}
})
})
}
// 递归计算父级
this.refreshAllParentNodes(node.$parent);
}
},
}
}
</script>
<style lang="scss">
.tree-container {
$borderColor: #ebeef5;
width: 100%;
padding: 0;
&.level3 {
display: flex;
flex-wrap: wrap;
& > .level4 {
width: 25%;
padding: 0 20px 0 40px;
}
}
& > .level2 {
display: flex;
align-items: center;
border: 1px solid $borderColor;
line-height: 45px;
.tree-node-title {
&.level2 {
width: 160px;
text-align: center;
}
}
}
.level3 {
.level3 + .level3 {
border-top: 1px solid $borderColor;
}
}
.tree-node-title {
&.level3 {
background-color: #f2f2f2;
width: 100%;
padding: 0 20px;
}
&.level1 {
padding: 20px 0;
}
}
.level2 + .level2 {
border-top: none;
}
.tree-childnode-container {
width: 100%;
&.level2 {
border-left: 1px solid $borderColor;
}
}
input {
margin-right: 5px;
}
label {
cursor: pointer;
}
}
</style>
复制代码
注意 在当前组件中使用组件本身(递归),注意图中圈出的地方,上面有两种写法