众所周知,elementUI里面,级联下拉选数据只有两种形式。
只传叶子结点的值
['code1','code2']
或者,把路径上的值也传递过去
[['parent1','code1'],['parent1','code2']]
但是现在后端想要这样的扁平
数据结构:
['parent1','code1','code2']
至于原因,别问,问就是,历史原因
思路
怎么实现呢?
如果是在传参的时候,把二维数组转换成一维数组,回显的时候就没办法回显,我总不能把一维数组转换成二维数组。
第二个想法是在change
事件手动选中子结点,然后选中父节点。(找了个偏门,用的是没有暴露出来的doCheck
方法)
这个的问题
在于,选中子结点没问题,但是在选中父节点的时候,因为change
里面有【选中所有子结点】
的逻辑存在,导致父节点的所有子结点(即当前结点的所有兄弟结点)也都被选中了
这不是我们想看到的。
为了解决这个,我们提出一个折中的方案(不能魔改组件太多)
:
在change
事件里面通过代码手动选中子结点,在提交的时候通过path
把父组件的值取出来、去重传给后端即可。
至于一点显示小问题,产品不在意,还是toB,后端只要数据,又不想刷数据(也没法刷,影响太大了),在这些压力下,一点显示小问题,无人在意。也就这么办了。
话不多说,来看看代码
html
提前说一下,下面的emitPath: false, checkStrictly: true,
是必须的。
作用
: 父子结点互不关联主要是回显的时候,会根据传回来的值回显样式
&& 只返回当前结点的值不然就是二维数组了
<el-cascader
ref="codeRef"
options={this.options}
{...{
props: {
value: this.codeArr,
props: {
multiple: true,
value: "code",
label: "name",
emitPath: false,
checkStrictly: true,
},
},
}}
clearable
collapseTags
filterable
onChange={this.handleChange}
></el-cascader>
处理函数
// 当列表发生变化时,调用此方法更新选中状态并同步视图
handleChange(newArr) {
console.log("自己点击也会触发这个方法吗");
// 调用辅助函数 findCurrentOption 找到新增或取消的部门信息
const current = this.findCurrentOption(newArr, this.codeArr);
// 更新当前部门列表
this.codeArr = newArr;
// 如果没有变化,直接返回
if (!current) return;
// 延迟执行,确保 DOM 更新完成
this.$nextTick(() => {
// 根据当前值找到对应的节点
const targetNode = this.$refs.codeRef.panel.getNodeByValue(current.value);
// 如果未找到目标节点,直接返回
if (!targetNode) return;
// 根据类型递归勾选或取消子节点
if (current.type === "checked") {
this.checkedChildren([targetNode], true); // 勾选所有下级节点
} else if (current.type === "cancel") {
this.checkedChildren([targetNode], false); // 取消所有下级节点
}
// 拿出来是为了统一注释。这个就是手动选中上级结点的代码了
// // 根据类型递归勾选或取消子节点
// if (current.type === "checked") {
// this.checkedParent(targetNode, true); // 勾选所有上级节点
// } else if (current.type === "cancel") {
// this.checkedParent(targetNode, false); // 取消所有上级节点
// }
// 重新计算多选值以更新视图
this.$refs.organCodeRef.$refs.panel.calculateMultiCheckedValue();
});
},
// 辅助函数:比较新旧数组,返回新增或取消的信息
findCurrentOption(newArr, oldArr) {
const catchNewArr = [...newArr];
const catchOldArr = [...oldArr];
// 如果新数组长度大于旧数组,表示有新增部门
if (catchNewArr.length > catchOldArr.length) {
for (let i = 0; i < catchNewArr.length; i++) {
const targetIndex = catchOldArr.indexOf(catchNewArr[i]);
// 如果在旧数组中找不到当前值,表示新增
if (targetIndex === -1) {
return {
value: catchNewArr[i], // 新增的值
type: "checked", // 类型为新增
};
}
}
} else {
// 如果新数组长度小于旧数组,表示有取消部门
for (let i = 0; i < catchOldArr.length; i++) {
const targetIndex = catchNewArr.indexOf(catchOldArr[i]);
// 如果在新数组中找不到当前值,表示取消
if (targetIndex === -1) {
return {
value: catchOldArr[i], // 取消的值
type: "cancel", // 类型为取消
};
}
}
}
},
// 辅助函数:递归勾选或取消节点及其子节点
checkedChildren(list = [], chekced = true) {
if (!Array.isArray(list)) return;
list.forEach((item) => {
console.log("???", list, item);
item.doCheck(chekced); // 设置当前节点的选中状态(true选中; false取消选中)
if (item.children.length > 0) {
// 如果存在子节点,递归调用
this.checkedChildren(item.children, chekced);
}
});
},
// // 辅助函数:递归勾选或取消节点及其上级节点
// checkedParent(item, chekced = true) {
// if (item.parent) {
// item.parent.doCheck(chekced); // 如果有上级结点,则设置上级节点的选中状态(true选中; false取消选中)
// // 如果存在上级节点,递归调用
// this.checkedParent(item.parent, chekced);
// }
// },
提交
在submit
的时候,处理选中的结点,获取选中结点的path
:
// 提交申请
async submitApprove() {
// 表单校验
this.$refs.form.validate(async (valid) => {
if (!valid) return;
// 选中的结点
const checkedNodes = this.$refs.organCodeRef?.getCheckedNodes() || [];
// 获取选中结点的path(包括父级及当前结点)
activityOrganCodes = _.uniq(_.flattenDeep(_.map(checkedNodes, "path")));
});
},