|
|
@@ -1,5 +1,5 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, ref, watch, nextTick } from "vue";
|
|
|
+import { onMounted, ref, watch, nextTick, computed } from "vue";
|
|
|
import { getSystemRoleDeptTree } from "@/api/system/role";
|
|
|
import { type CheckboxValueType } from "element-plus";
|
|
|
|
|
|
@@ -16,16 +16,13 @@ const getDeptList = async () => {
|
|
|
deptList.value = res.depts;
|
|
|
};
|
|
|
|
|
|
-const model = defineModel();
|
|
|
+const model = defineModel<number[]>();
|
|
|
|
|
|
watch(
|
|
|
model,
|
|
|
(value: number[]) => {
|
|
|
nextTick(() => {
|
|
|
setCheckedKeys(value, true);
|
|
|
- const allKeys = getAllKeys(deptList.value);
|
|
|
- menuNodeAll.value = value.length === allKeys.length;
|
|
|
- isIndeterminate.value = value.length > 0 && value.length < allKeys.length;
|
|
|
});
|
|
|
},
|
|
|
{ immediate: true, deep: true }
|
|
|
@@ -34,9 +31,7 @@ watch(
|
|
|
const menuRef = ref(null);
|
|
|
|
|
|
const menuCheckStrictly = ref(true);
|
|
|
-const menuNodeAll = ref(false);
|
|
|
const menuExpand = ref(false);
|
|
|
-const isIndeterminate = ref(false);
|
|
|
const handleCheckedTreeExpand = (value: CheckboxValueType) => {
|
|
|
// 递归展开
|
|
|
for (let i = 0; i < deptList.value.length; i++) {
|
|
|
@@ -44,38 +39,75 @@ const handleCheckedTreeExpand = (value: CheckboxValueType) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-const handleCheckedTreeNodeAll = (value: CheckboxValueType) => {
|
|
|
- menuRef.value.setCheckedNodes(value ? deptList.value : []);
|
|
|
- isIndeterminate.value = false;
|
|
|
- const allKeys = getAllKeys(deptList.value);
|
|
|
- model.value = value ? allKeys : [];
|
|
|
- console.log(model.value);
|
|
|
- console.log(menuNodeAll.value);
|
|
|
+// 只返回所有叶子节点 id
|
|
|
+const getLeafKeys = (data: any[]) => {
|
|
|
+ const keys: number[] = [];
|
|
|
+ data.forEach(node => {
|
|
|
+ if (node.children && node.children.length) {
|
|
|
+ keys.push(...getLeafKeys(node.children));
|
|
|
+ } else {
|
|
|
+ keys.push(node.id);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return keys;
|
|
|
};
|
|
|
|
|
|
-const handleTreeCheck = (_: any, checkedState: any) => {
|
|
|
- const allKeys = getAllKeys(deptList.value);
|
|
|
- const checkedCount = checkedState.checkedKeys.length;
|
|
|
- menuNodeAll.value = checkedCount === allKeys.length;
|
|
|
- isIndeterminate.value = checkedCount > 0 && checkedCount < allKeys.length;
|
|
|
- model.value = checkedState.checkedKeys;
|
|
|
- emit("update:modelValue", checkedState.checkedKeys);
|
|
|
+// 叶子节点总集合(随菜单树变化动态计算)
|
|
|
+const allLeafKeys = computed(() => getLeafKeys(deptList.value));
|
|
|
+
|
|
|
+// 全选状态:由 model 与叶子总集合计算;设置时驱动 model 更新
|
|
|
+const allSelected = computed({
|
|
|
+ get() {
|
|
|
+ const leafKeys = allLeafKeys.value;
|
|
|
+ if (!leafKeys.length) return false;
|
|
|
+ // model 中的所有 key 都在叶子集合,并且数量一致视为全选
|
|
|
+ return (
|
|
|
+ model.value.length === leafKeys.length &&
|
|
|
+ model.value.every(k => leafKeys.includes(k))
|
|
|
+ );
|
|
|
+ },
|
|
|
+ set(checked: boolean) {
|
|
|
+ model.value = checked ? [...allLeafKeys.value] : [];
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 半选状态:仅基于数量计算
|
|
|
+const indeterminate = computed(() => {
|
|
|
+ const total = allLeafKeys.value.length;
|
|
|
+ const count = model.value.length;
|
|
|
+ return count > 0 && count < total;
|
|
|
+});
|
|
|
+
|
|
|
+// 通过 watch(model) 来同步 UI 勾选状态,因此这里不再需要手动方法
|
|
|
+
|
|
|
+const handleTreeCheck = (_: any, _checkedState: any) => {
|
|
|
+ const leafKeys = menuRef.value?.getCheckedKeys(true) ?? [];
|
|
|
+ model.value = leafKeys;
|
|
|
+ emit("update:modelValue", leafKeys);
|
|
|
};
|
|
|
|
|
|
// 递归获取所有节点key
|
|
|
-const getAllKeys = (data: any) => {
|
|
|
- return data.reduce((keys: any, node: any) => {
|
|
|
- keys.push(node.id);
|
|
|
- if (node.children && menuCheckStrictly) {
|
|
|
- keys.push(...getAllKeys(node.children));
|
|
|
- }
|
|
|
- return keys;
|
|
|
- }, []);
|
|
|
+const getAllKeys = () => {
|
|
|
+ // 目前被选中的菜单节点
|
|
|
+ let checkedKeys = menuRef.value.getCheckedKeys();
|
|
|
+ // 半选中的菜单节点
|
|
|
+ let halfCheckedKeys = menuRef.value.getHalfCheckedKeys();
|
|
|
+ checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
|
|
|
+ return checkedKeys;
|
|
|
};
|
|
|
|
|
|
const setCheckedKeys = (keys: number[], leafOnly: boolean) => {
|
|
|
return menuRef.value.setCheckedKeys(keys, leafOnly);
|
|
|
};
|
|
|
+
|
|
|
+// 级联开关变化时,重新应用当前 model 的叶子集合到树上
|
|
|
+watch(menuCheckStrictly, () => {
|
|
|
+ nextTick(() => setCheckedKeys(model.value, true));
|
|
|
+});
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ getAllKeys
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
@@ -89,10 +121,7 @@ const setCheckedKeys = (keys: number[], leafOnly: boolean) => {
|
|
|
</el-checkbox>
|
|
|
</el-col>
|
|
|
<el-col :span="4">
|
|
|
- <el-checkbox
|
|
|
- v-model="menuNodeAll"
|
|
|
- :indeterminate="isIndeterminate"
|
|
|
- @change="handleCheckedTreeNodeAll($event)"
|
|
|
+ <el-checkbox v-model="allSelected" :indeterminate="indeterminate"
|
|
|
>全选/全不选
|
|
|
</el-checkbox>
|
|
|
</el-col>
|