fix(auth): 改用 zTree 原生级联替代手动 checkNode,彻底修复级联错乱
build / build (push) Successful in 1m58s

手动代码级联与 zTree 内部状态机(checkNode 提前 return、setChkClass、repairParentChkClassWithSelf)反复交互,
导致除 open 外级联视觉完全失效、点击父节点无法联动子孙,整体错乱。

改为 zTree 原生 chkboxType {Y:"ps", N:"s"}:
- Y="ps":勾选任意节点 -> 父/祖先全部勾选 + 子孙全部勾选
- N="s":取消节点 -> 子孙全部取消,父/祖先不受影响
恰好对应需求 4 条规则(勾选子->祖先勾选;勾选父->子孙勾选;取消父->子孙取消;取消子->父/祖不动),
且视觉由 zTree 原生刷新,不再依赖手动 checkNode。

onTreeCheck 退化为只做本地数据同步:原生级联完成后把应用子树最终勾选态
(syncAppModulesFromTree)写回 AppItem.modules 作为提交依据;GroupAuth 仍按模块明细 diff,
角色授权页仍全量防抖保存。
This commit is contained in:
chenxing
2026-09-15 17:45:43 +08:00
parent af367ddd19
commit 6750a735c2
3 changed files with 42 additions and 119 deletions
+9 -19
View File
@@ -66,7 +66,7 @@ import Apis from "@/api";
import GroupTreeSearchPanel from "@/components/GroupTreeSearchPanel.vue";
import MyTree from "@/components/MyTree.vue";
import { useDefaultStore } from "@/store";
import { applyModuleCascade, checkNodeWithCascade, setAppModulesChecked, walkModules } from "@/utils/treeCascade";
import { syncAppModulesFromTree, walkModules } from "@/utils/treeCascade";
defineOptions({ name: "GroupAuth" });
@@ -330,9 +330,9 @@ const treeSetting = {
check: {
enable: true,
chkStyle: "checkbox",
// zTree 不级联,父子联动由 onTreeCheck 里的代码实现(checkNodeWithCascade):
// 勾选子 -> 祖先全部勾选;勾选父 -> 子孙全部勾选;取消父 -> 子孙全部取消;取消子 -> 父不受影响
chkboxType: { Y: "", N: "" },
// 原生级联即可满足需求:
// Y="ps" 勾选子->父/祖先全部勾选、勾选父->子孙全部勾选;N="s" 取消父->子孙全部取消、取消子->父/祖先不受影响
chkboxType: { Y: "ps", N: "s" },
},
callback: {},
};
@@ -400,7 +400,7 @@ function findLocalApp(appId: number): AppItem | null {
return null;
}
// walkModules / 本地级联(setAppModulesChecked、applyModuleCascade)统一在 utils/treeCascade.ts
// walkModules / 本地勾选态同步(syncAppModulesFromTree)统一在 utils/treeCascade.ts
function setLocalAppInherit(appId: number, inherit: boolean) {
const app = findLocalApp(appId);
@@ -421,17 +421,13 @@ function setLocalModuleState(appId: number, moduleId: number, patch: Partial<App
// ---------------- 授权操作 ----------------
/**
* 勾选/取消勾选(代码级联,仅提交模块明细):
* - 勾选子节点 -> 祖先/子孙全部勾选;取消子节点 -> 子孙取消、祖先不受影响
* - 勾选/取消应用节点 -> 该应用全部模块勾选/取消
* 提交:对比本次级联前后该应用内各模块的勾选状态,仅把发生变化的模块按 追加/删除 提交。
* 勾选/取消勾选:级联由 zTree 原生 chkboxType(Y:"ps" N:"s")完成,
* 这里把该应用最终勾选态同步回本地数据(提交依据),仅按模块明细 diff 提交 追加/删除。
*/
async function onTreeCheck(node: any) {
const groupId = selectedGroupId.value;
if (!groupId || !node) return;
const checked = !!node.checked;
const isApp = node.nodeType === "app";
const treeObj = getTreeObjByNode(node);
const app = findLocalApp(node.appId);
if (!app) {
@@ -444,14 +440,8 @@ async function onTreeCheck(node: any) {
walkModules(app.modules, (m) => before.set(Number(m.id), !!m.checked));
try {
// 本地数据(提交依据)与 zTree 视觉状态同步级联
if (isApp) {
setAppModulesChecked(app, checked);
checkNodeWithCascade(treeObj, node, checked);
} else {
applyModuleCascade(app, node.rawId, checked);
checkNodeWithCascade(treeObj, node, checked);
}
// zTree 原生级联已生效,同步最终勾选态到本地数据
syncAppModulesFromTree(app, node);
syncInheritDoms(treeObj);
const after = new Map<number, boolean>();
+9 -23
View File
@@ -47,7 +47,7 @@ import { computed, ref, shallowRef, reactive, watch } from "vue";
import { debounce } from "lodash";
import Apis from "@/api";
import MyTree from "@/components/MyTree.vue";
import { applyModuleCascade, checkNodeWithCascade, setAppModulesChecked, walkModules } from "@/utils/treeCascade";
import { syncAppModulesFromTree, walkModules } from "@/utils/treeCascade";
const props = defineProps<{ roleId: number | null; roleName?: string }>();
@@ -73,7 +73,6 @@ type AuthzTreeNode = {
type CategoryTreeItem = { cat: CategoryItem; treeData: AuthzTreeNode[]; weight: number };
const COLUMN_COUNT = 3;
const jq = () => (window as any).$;
const loading = ref(false);
const categoryList = shallowRef<CategoryItem[]>([]);
@@ -87,9 +86,9 @@ const treeSetting = {
check: {
enable: true,
chkStyle: "checkbox",
// zTree 不级联,父子联动由 onTreeCheck 里的代码实现(checkNodeWithCascade):
// 勾选子 -> 祖先全部勾选;勾选父 -> 子孙全部勾选;取消父 -> 子孙全部取消;取消子 -> 父不受影响
chkboxType: { Y: "", N: "" },
// 原生级联即可满足需求:
// Y="ps" 勾选子->父/祖先全部勾选、勾选父->子孙全部勾选;N="s" 取消父->子孙全部取消、取消子->父/祖先不受影响
chkboxType: { Y: "ps", N: "s" },
},
callback: {},
};
@@ -230,29 +229,16 @@ function findLocalApp(appId: number): AppItem | null {
}
return null;
}
function getTreeObjByNode(node: any) {
const $ = jq();
const treeId = String(node?.tId || "").replace(/_\d+$/, "");
return $?.fn?.zTree?.getZTreeObj(treeId) || null;
}
// walkModules / 本地级联(setAppModulesChecked、applyModuleCascade)统一在 utils/treeCascade.ts
// walkModules / 本地勾选态同步(syncAppModulesFromTree)统一在 utils/treeCascade.ts
// ---------------- 授权操作(代码级联,勾选即保存,全量覆盖 + 防抖) ----------------
// ---------------- 授权操作(级联由 zTree 原生 chkboxType 完成,勾选即保存,全量覆盖 + 防抖) ----------------
function onTreeCheck(node: any) {
const roleId = props.roleId;
if (!roleId || !node) return;
const checked = !!node.checked;
const treeObj = getTreeObjByNode(node);
// zTree 原生级联已生效,把该应用最终勾选态同步回本地数据(saveNow 的提交依据)
const app = findLocalApp(node.appId);
// 本地数据(saveNow 的提交依据)与 zTree 视觉状态同步级联
if (node.nodeType === "app") {
setAppModulesChecked(app, checked);
checkNodeWithCascade(treeObj, node, checked);
} else {
applyModuleCascade(app, node.rawId, checked);
checkNodeWithCascade(treeObj, node, checked);
}
if (!app) return;
syncAppModulesFromTree(app, node);
scheduleSave();
}