ant-design-vue中实现a-tree树形控件父子关联选中过滤的算法

news/2024/11/13 5:32:04/

在使用ant-design-vue的框架时,a-tree是比较常用的组件,比较适合处理树形结构的数据。

但是在与后台数据进行授权交互时,就不友好了。

在这里插入图片描述
在原生官方文档的例子中,若子项被勾选,则父级节点会被关联勾选,但这勾选并不一定是选中的意思。有可能是半选中,通过方框样式选中,也就是说父级节点的值不会出现在checkedKeys的数组中。

<a-treev-model:checkedKeys="checkedKeys"checkable:tree-data="treeData"
>
</a-tree>

这对后端数据授权处理是不友好的。因为一般授权子节点时,父级节点必须是也授权的,否则应用无法到达子节点的功能。也就是说,关联被授权的父子节点的ID都要传给后端。

提交的时候,到还好说。将所有选中的列表和半选中的列表合并起来提交就可以了。

const handleCheck = (checkedKeys, e) => {const {halfCheckedKeys } = e;const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys];
};

但是数据回写的时候就难受了,数据库里的授权列表回写到a-tree组件时,由于父节点被回传过来,那么当父节点被选中时,所有子节点都会被选中,这与实际不符。

所以需要将服务传过来的数据allCheckedKeys处理,再还原成选中checkedKeys和半选中halfCheckedKeys的两部分。

这个业务逻辑比较复杂,需要比较授权列表和树形结构数据。
在这里插入图片描述
我在网上找了很久都没找到关于此业务的算法逻辑。但是找到了另外的两种解决办法:

一是更改数据库结构,添加授权列表的字段分为选中和半选中的状态,传给后端服务时ID分类传输。
二是父子节点不再关联,即添加a-tree的属性为checkStrictly="true",分别独立处理单个节点的逻辑。

第一种方法缺点就是数据表设计不规范,数据库存储了适应前端页面的数据。优点是避免了js编写筛出未全部选中的父级id的工作。

第二种方法缺点是勾选父级节点时,子节点不会被关联勾选,层级很多时,操作不方便。同时子节点勾选时,可能遗漏父节点的勾选。当然可以再写代码,补上往上和往下关联的逻辑。但是还是有缺点,父节点被勾选时,是通过“√”表示,若不展开下级列表,会不知道子节点是否被全选上。

最后思来想去,感觉两种方法都有缺点。我还是觉得自己将这个业务逻辑的算法写出来吧。。。

代码如下:

/*** 提供两个方法:* 一是转换自定义树形对象数据为a-tree识别的树形对象列表* 二是将数据库存储的已分配id列表重新转化为checkedList* * @param {string} idKey - 数据项 ID 的键名,默认为 'id'* @param {string} nameKey - 数据项名称的键名,默认为 'name'* @param {string} childrenKey - 子节点列表的键名,默认为 'children'*/
export const useTreeConverter = (idKey: string = 'id',nameKey: string = 'name',childrenKey: string = 'children'
) => {/*** 转换对象* @param data 树形结构数据* @returns 返回UI组件认可的包含key、title、children属性的树形结构数据*/const convertTree = (data: any[]): any[] => {return data.map((item) => ({key: item[idKey],title: item[nameKey],children:item[childrenKey] && item[childrenKey].length > 0 ? convertTree(item[childrenKey]) : []}))}/**** @param savedKeys 授权已分配的ID列表* @param treeData 框架规定的treeData* @returns*/const loadCheckState = (savedKeys: number[] = [], treeData: any[]) => {//选中数组const checkedKeysTemp: number[] = []//半选中数组const halfCheckedKeysTemp: number[] = []const checkNodeStatus = (node) => {//若本节点为叶子节点且ID列表包含节点的key值,则加入到选中数组中if (node.children.length === 0 && savedKeys.includes(node.key)) {checkedKeysTemp.push(node.key)}//若本节点为非叶子节点if (node.children.length > 0) {const isAllLeaf = node.children.every((child) => child.children.length === 0)//子节点都为叶子节点if (isAllLeaf) {//若叶子节点被选中,则加入到选中数组中for (let item of node.children) {if (savedKeys.includes(item.key)) {checkedKeysTemp.push(item.key)}}//若子节点都被选中,则该节点为被选中const allChildrenChecked = node.children.every((child) => savedKeys.includes(child.key))if (allChildrenChecked) {checkedKeysTemp.push(node.key)console.log(checkedKeysTemp)} else {//若子节点部分被选中,则该节点为半选中const someChildrenChecked = node.children.some((child) => savedKeys.includes(child.key))if (someChildrenChecked) {halfCheckedKeysTemp.push(node.key)}}} else {//若子节点不是都为叶子节点for (let item of node.children) {//子节点进行迭代if (item.children.length > 0) {item.children.forEach(checkNodeStatus)} else {checkNodeStatus(item)}}//迭代完子节点,继续判断该节点是否被选中const allChildrenChecked = node.children.every((child) =>checkedKeysTemp.includes(child.key))//若子节点都被选中且不是半选中,则该节点为被选中if (allChildrenChecked) {checkedKeysTemp.push(node.key)} else {//若子节点部分被选中,则该节点为半选中const someChildrenChecked = node.children.some((child) => savedKeys.includes(child.key))if (someChildrenChecked) {halfCheckedKeysTemp.push(node.key)}}}}}// treeData 是你的树形结构的数据treeData.forEach(checkNodeStatus)return checkedKeysTemp}return {convertTree,loadCheckState}
}

由于a-tree的组件还是经常使用的,我将此算法封装为组合式函数useTreeConverter.ts中了,后面其他代码,也能复用该算法逻辑。

它包含两个逻辑:

一是将自定义的树形结构转换为组件识别的形如{children:‘children’, title:‘title’, key:‘key’ }的树形结构TreeData

二是给定形如[1,3,5,7,11]的授权ID列表和转换后的树形结构TreeData通过递归迭代的方式还原出哪些是checkedKeys的值。

该组合式函数的代码写一次就够了,后面其他使用a-tree的组件都用得上。提供2个服务端返回的授权列表和树形结构的参数输入,再调用提供的convertTreeloadCheckState的2个函数就可以得出结果了。


http://www.ppmy.cn/news/1523908.html

相关文章

网络安全基础—加解密原理与数字证书

目录 1&#xff09; 对称加密和非对称加密 Ⅰ 对称加密算法 Ⅱ 非对称加密算法 Ⅲ 对称和非对称加密比较: 2&#xff09;数据加密--数字信封 3&#xff09;数据验证 - 数字签名 4&#xff09;数字证书 Ⅰ 数字证书格式 Ⅱ 证书的颁发 Ⅲ 证书验证&#xff1a; .验证…

vue3开发uniapp转字节小程序注意事项

vue3开发uniapp转字节小程序注意事项 1.provide-inject 跨层通信不支持问题2.不能自定义头部&#xff0c;需要去申请 开发相关地址 1.抖音开放平台 2.开发者平台 项目本身是vue3tsuniapp写的微信小程序&#xff0c;因产品需求要转换成抖音小程序 1.provide-inject 跨层通信不支…

P3275 [SCOI2011] 糖果(差分约束 + tarjan + 缩点 + 拓扑排序 + dp)

P3275 [SCOI2011] 糖果 放一个传送门 https://www.luogu.com.cn/problem/P3275 这道题看起来很像是差分约束 s p f a spfa spfa 的模板题目&#xff0c;但是我们可以从数据范围发现&#xff0c;最坏情况下 N K 1 0 10 N \times K 10^{10} NK1010 。所以显然是不能用 s…

sliding window 滑动窗口——从LeetCode题海中总结常见套路

滑动窗口开山鼻祖:LeetCode3.无重复字符的最长子串 以前只会暴力瞎几把模拟,看一下心酸又励志的优化过程: 还有通过所有测试用例的超时: class Solution { public:int lengthOfLongestSubstring(string s) {if (s.empty())return 0;// if (s.size() == 1)// return 1;/…

各种无人机飞行服务技术详解

随着科技的飞速发展&#xff0c;无人机&#xff08;Unmanned Aerial Vehicles, UAVs&#xff09;技术已成为推动多个行业变革的重要力量。从军事侦察到商业应用&#xff0c;再到日常生活中的娱乐拍摄&#xff0c;无人机的身影无处不在。本文将详细解析无人机飞行服务所涉及的关…

gdb 调试带有 fork 的进程,如何在父进程和子进程之间切换?

使用 gdb 启动程序后&#xff0c;执行下面的命令&#xff1a; set follow-fork-mode child可以使 gdb 跟踪子进程 若要让 gdb 跟踪父进程&#xff0c;则执行下面命令&#xff1a; set follow-fork-mode parent如果已经执行了 fork() API&#xff0c;可以使用下面的命令来查看…

[Postman]接口自动化测试入门

文章大多用作个人学习分享&#xff0c;如果大家觉得有不足或错漏的地方欢迎评论指出或补充 此文章将完整的走一遍一个web页面的接口测试流程 大致路径为&#xff1a; 创建集合->调用接口登录获取token->保存token->带着token去完成其他接口的自动化测试->断言-&g…

STM32 的 CAN 通讯全攻略

目录 一、CAN 通讯概述 二、 CAN 通讯原理 1.ISO11898 标准下的物理层特征 2.CAN 协议的帧类型 3. 总线仲裁介绍 4.位时序 5.STM32 CAN 控制器简介 6.标识符筛选器 三、软件设计 1.发送流程 1.1初始化 CAN 控制器 1.2准备发送数据 1.3 将数据填充到发送缓冲区 1.4…