您的位置:首页 > Web前端 > Vue.js

Vue 2.x折腾记 - (19) 基于Antd Design Vue 封装一个符合业务的树形组件

2019-04-23 14:25 821 查看
版权声明:版权所有:CRPER(crper@outlook.com); 掘金|Github:CRPER; https://blog.csdn.net/bomess/article/details/89471747

前言

原型上有个权限分配的功能;

仔细翻了下对应的文档(

antd vue
),发现有那么一个树形控件,但是没有上面部分全局控制的功能。

那么只能自己动手改造出一个符合业务的了,有兴趣的看官可以瞅瞅。

效果图

实现的思路

首先先梳理下要实现的功能点

  • 要考虑默认值的传递以及产生的联动
  • 全局开关对树控件产生的影响
  • 子项操作要反馈给全局实现联动;
  • 最后避免太多服务器资源(若是勾选一次触发一次有点大),回调改由按钮触发提交到外部

确定了功能点就可以开始搞起了,为此我实现过三个版本;

第一版是

switch
开关控件来控制, 为此树组件和开关组是抽离出两个独立组件,发现很难避免一些极端的操作行为;

第二版用的

.sync
+
switch
来实现,发现一样难避免一些极端的操作行为;

第三版是改由按钮组去实现,发现可以很好的解决极端的情况,可以分解成三种情况去实现。

代码实现

TreePanel.vue

<template>
<div :bordered="false" :bodyStyle="{ padding: 0 }">
<a-row type="flex" justify="space-between" align="middle">
<a-col :span="20">
<a-radio-group :size="size" @change="onCheckedBtnGroupChange" v-model="allChecked" buttonStyle="solid">
<a-radio-button value="2" :disabled="true">局部选中</a-radio-button>
<a-radio-button value="1">全选</a-radio-button>
<a-radio-button value="0">全不选</a-radio-button>
</a-radio-group>
<a-divider type="vertical" />
<a-radio-group :size="size" @change="onExpandedBtnGroupChange" v-model="allExpanded" buttonStyle="solid">
<a-radio-button value="2" :disabled="true">局部展开</a-radio-button>
<a-radio-button value="1">展开所有</a-radio-button>
<a-radio-button value="0">折叠所有</a-radio-button>
</a-radio-group>
</a-col>

<a-col>
<a-button type="primary" :size="size" href="javascript:;" @click="callBackData">
<span>提交改动</span>
</a-button>
</a-col>
</a-row>
<a-tree
:expandedKeys="innerExpandedKeys"
:treeData="treeData"
checkable
:checkedKeys="innerCheckedKeys"
@expand="onExpand"
@check="onCheck"
/>
</div>
</template>
<script>
import { getTreeKey } from './utils';

export default {
props: {
size: {
// 控件规格
type: String,
default: 'small'
},
checkedKeys: {
// 传递选中的key
type: Array,
default: function() {
return [];
}
},
expandedKeys: {
// 传递需要展开的key
type: Array,
default: function() {
return [];
}
},
treeData: {
type: Array,
default: function() {
return [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0' },
{ title: '0-0-0-1', key: '0-0-0-1' },
{ title: '0-0-0-2', key: '0-0-0-2' }
]
},
{
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0' },
{ title: '0-0-1-1', key: '0-0-1-1' },
{ title: '0-0-1-2', key: '0-0-1-2' }
]
},
{
title: '0-0-2',
key: '0-0-2'
}
]
},
{
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0' },
{ title: '0-1-0-1', key: '0-1-0-1' },
{ title: '0-1-0-2', key: '0-1-0-2' }
]
},
{
title: '0-2',
key: '0-2'
}
];
}
}
},
data() {
return {
allChecked: '', // 全选按钮组
allExpanded: '', // 全部展开按钮组
innerCheckedKeys: [], // 选中的值
innerExpandedKeys: [] //  展开的值
};
},
watch: {
checkedKeys: {
// 复制props
immediate: true,
deep: true,
handler(newValue, oldValue) {
if (newValue) {
if (newValue.length === this.getTreeAllKey.length) {
this.allChecked = '1';
} else if (newValue.length === 0) {
this.allChecked = '0';
} else {
this.allChecked = '2';
}
this.innerCheckedKeys = newValue;
}
}
},
expandedKeys: {
// 复制props
immediate: true,
deep: true,
handler(newValue, oldValue) {
if (newValue) {
if (newValue.length === this.getTreeAllGroupKey.length) {
this.allExpanded = '1';
} else if (newValue.length === 0) {
this.allExpanded = '0';
} else {
this.allExpanded = '2';
}
this.innerExpandedKeys = newValue;
}
}
}
},
computed: {
switchDataSource() {
// 勾选数据源
return [
{
type: 'ALL_CHECKED',
labelText: this.isAllChecked ? '全不选' : '全选',
checked: this.isAllChecked
},
{
type: 'ALL_EXPAND',
labelText: this.isAllExpanded ? '折叠所有' : '展开所有',
checked: this.isAllExpanded
}
];
},
cacheEmitValue() {
// 缓存响应的值
return {
checkedKeys: this.innerCheckedKeys,
expandedKeys: this.innerExpandedKeys
};
},
getTreeAllKey() {
// 获取树的所有key
return getTreeKey(this.treeData);
},
getTreeAllGroupKey() {
// 获取树的所有组key
return getTreeKey(this.treeData, true);
},
isAllChecked() {
// 是否全部勾选
return this.innerCheckedKeys.length === this.getTreeAllKey.length;
},
isAllExpanded() {
// 是否全部展开
return this.innerExpandedKeys.length === this.getTreeAllGroupKey.length;
}
},

methods: {
onExpandedBtnGroupChange({ target: { value: expanedBtnGroupValue } }) {
console.log('expanedBtnGroupValue: ', expanedBtnGroupValue);
switch (expanedBtnGroupValue) {
case '0':
this.innerExpandedKeys = [];
break;
case '1':
this.innerExpandedKeys = this.getTreeAllGroupKey;
break;
default:
break;
}
},
onCheckedBtnGroupChange({ target: { value: checkedBtnGroupValue } }) {
console.log('checkedBtnGroupValue: ', checkedBtnGroupValue);
switch (checkedBtnGroupValue) {
case '0':
this.innerCheckedKeys = [];
break;
case '1':
this.innerCheckedKeys = this.getTreeAllKey;
break;
default:
break;
}
},
callBackData(emit) {
// 响应改动的值
if (emit) {
this.$emit('change', this.cacheEmitValue);
}
return false;
},
onExpand(expandedKeys) {
if (expandedKeys.length === this.getTreeAllGroupKey.length) {
this.allExpanded = '1';
} else {
this.allExpanded = '2';
}
this.innerExpandedKeys = expandedKeys;
},
onCheck(checkedKeys) {
if (checkedKeys.length === this.getTreeAllKey.length) {
this.allChecked = '1';
} else {
this.allChecked = '2';
}
this.innerCheckedKeys = checkedKeys;
}
}
};
</script>

utils.js
:递归获取需要的数据

/**
* @param arr 数组对象
* @param parent 是否只获取父一层的属性
* @description 递归获取需要的数据
*/
export function getTreeKey(arr, parent = false) {
const dataList = [];
const generateList = data => {
for (let i = 0; i < data.length; i++) {
const { key, title, children } = data[i];
if (!parent) dataList.push({ key, title });
if (Array.isArray(children) && children.length > 0) {
if (parent) dataList.push({ key, title });
generateList(children);
}
}
};
generateList(arr);
return dataList.map(item => item.key);
}

用法

<tree-panel
:treeData="treeData"
:expandedKeys="treeExpandedKeys"
:checkedKeys="treeCheckedKeys"
@change="onTreePanelChange"
/>
props 类型 介绍
treeData
数组对象 整个树的数据
expandedKeys
数组 展开的数组key
checkedKeys
数组 选中的数组key
@change
自定义事件 拿到返回值的回调函数

总结

至此,符合我们业务的一个树组件封装已经可以正常使用。

有时候思路不通的时候,换个角度切入,发现会更美好;

有不对之处请留言,会及时修正,谢谢阅读。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: