前言
原型上有個許可權分配的功能;
仔細翻了下對應的文件(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 |
自定義事件 | 拿到返回值的回撥函式 |
總結
至此,符合我們業務的一個樹元件封裝已經可以正常使用。
有時候思路不通的時候,換個角度切入,發現會更美好;
有不對之處請留言,會及時修正,謝謝閱讀