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 |
自定义事件 | 拿到返回值的回调函数 |
总结
至此,符合我们业务的一个树组件封装已经可以正常使用。
有时候思路不通的时候,换个角度切入,发现会更美好;
有不对之处请留言,会及时修正,谢谢阅读。
相关文章推荐
- 基于vue-upload-component封装一个图片上传组件的示例
- 基于cropper.js封装vue在线图片裁剪组件
- 一个基于封装CSLA框架后,设计业务类的例子:users
- 基于Vue封装分页组件
- vue 基于element-ui 分页组件封装的实例代码
- 初探 amaze-vue( 基于vue.js封装的Amaze UI 组件库)
- 一个基于封装CSLA框架后,设计业务类的例子:User
- 2019最新 基于 Vue.js 2.0 的 UI 组件库快速开发一个 Vue.js Web
- vue 组件的封装之基于axios的ajax请求
- 精心设计的基于组件的C# Win Forms实践 一个框架数据库驱动多个业务逻辑数据库
- Vue 封装一个自己写的组件或方法
- 一个基于封装CSLA框架后,设计业务类的例子:users .
- 用Vue.js递归组件构建一个可折叠的树形菜单
- 基于 Vue 的树形选择组件的示例代码
- Vue 折腾记 - (8) 写一个挺靠谱的多地区选择组件
- vue用mint-ui的picker组件封装一个省市区三级联动组件
- 基于Vue的图片放大镜组件封装
- Vue 折腾记 - (9) 写一个挺靠谱的typeahead组件
- 2018年基于 Vue.js 2.0 的 UI 组件库快速开发一个 Vue.js Web 应用 Element UI
- 一个基于封装CSLA框架后,设计业务类的例子:User .