feat(auth): 树勾选级联+点击文字勾选+树默认闭合
build / build (push) Successful in 3m19s

- 新增共享级联工具 utils/treeCascade.ts:勾选子->祖先全部勾选;勾选父->子孙全部勾选/取消;取消子->父/祖不受影响(zTree 侧 chkboxType 置空,由代码级联)
- GroupAuth.vue:接入代码级联,模块明细按 diff 提交(追加/删除),取消时同步清理子组继承标记
- AppModuleCheckTree.vue:角色授权接入同一套级联逻辑
- 组/角色授权树与应用节点默认闭合,点击文字等价勾选框(MyTree onClick 同步触发 onCheck)
- PermissionView.vue「已授权应用」应用一级默认不展开模块
This commit is contained in:
chenxing
2026-09-15 16:51:43 +08:00
parent e0252eb997
commit e1cdc26697
4 changed files with 172 additions and 99 deletions
+14 -41
View File
@@ -28,7 +28,7 @@
<span class="cat-count">{{ item.treeData.length }}个应用</span>
</div>
<div v-show="!collapsedMap[String(item.cat.id)]" class="cat-card-body">
<MyTree :treeData="item.treeData" :setting="treeSetting" :autoClick="false"
<MyTree :treeData="item.treeData" :setting="treeSetting" :autoClick="false" :isCheck="true"
@on-tree-check="onTreeCheck" />
</div>
</div>
@@ -47,6 +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";
const props = defineProps<{ roleId: number | null; roleName?: string }>();
@@ -86,7 +87,8 @@ const treeSetting = {
check: {
enable: true,
chkStyle: "checkbox",
// 不自动级联:模块逐条独立授权(角色无"子组继承"概念)
// zTree 不级联,父子联动由 onTreeCheck 里的代码实现(checkNodeWithCascade):
// 勾选子 -> 祖先全部勾选;勾选父 -> 子孙全部勾选;取消父 -> 子孙全部取消;取消子 -> 父不受影响
chkboxType: { Y: "", N: "" },
},
callback: {},
@@ -131,7 +133,7 @@ function buildAppNode(app: AppItem): AuthzTreeNode {
return {
id: `app_${appId}`,
name: String(app.name || ""),
open: true,
open: false, // 一级默认闭合,点击才展开模块
checked: isAppChecked(app),
nodeType: "app",
rawId: appId,
@@ -228,57 +230,28 @@ function findLocalApp(appId: number): AppItem | null {
}
return null;
}
function walkModules(nodes: AppModuleVo[], fn: (n: AppModuleVo) => void) {
for (const node of nodes) {
fn(node);
walkModules(node.children || [], fn);
}
}
function setLocalAppChecked(appId: number, checked: boolean) {
const app = findLocalApp(appId);
if (!app) return;
walkModules(app.modules || [], (n) => { n.checked = checked; });
}
function setLocalModuleState(appId: number, moduleId: number, patch: Partial<AppModuleVo>) {
const app = findLocalApp(appId);
if (!app) return;
walkModules(app.modules || [], (n) => { if (Number(n.id) === moduleId) Object.assign(n, patch); });
}
function getTreeObjByNode(node: any) {
const $ = jq();
const treeId = String(node?.tId || "").replace(/_\d+$/, "");
return $?.fn?.zTree?.getZTreeObj(treeId) || null;
}
function syncAppNodeState(treeObj: any, appId: number) {
if (!treeObj) return;
const app = findLocalApp(appId);
if (!app) return;
const appNode = treeObj.getNodesByFilter((n: any) => n.id === `app_${appId}`, true);
if (!appNode) return;
appNode.checked = isAppChecked(app);
treeObj.checkNode(appNode, appNode.checked, false);
}
// walkModules / 本地级联(setAppModulesChecked、applyModuleCascade)统一在 utils/treeCascade.ts
// ---------------- 授权操作(勾选即保存,全量覆盖 + 防抖) ----------------
// ---------------- 授权操作(代码级联,勾选即保存,全量覆盖 + 防抖) ----------------
function onTreeCheck(node: any) {
const roleId = props.roleId;
if (!roleId || !node) return;
const checked = !!node.checked;
const isApp = node.nodeType === "app";
const treeObj = getTreeObjByNode(node);
const app = findLocalApp(node.appId);
if (isApp) {
setLocalAppChecked(node.appId, checked);
if (treeObj) {
const affected = treeObj.transformToArray([node]) || [];
affected.forEach((n: any) => {
if (n !== node) treeObj.checkNode(n, checked, false);
n.checked = checked;
});
}
// 本地数据(saveNow 的提交依据)与 zTree 视觉状态同步级联
if (node.nodeType === "app") {
setAppModulesChecked(app, checked);
checkNodeWithCascade(treeObj, node, checked);
} else {
setLocalModuleState(node.appId, node.rawId, { checked });
syncAppNodeState(treeObj, node.appId);
applyModuleCascade(app, node.rawId, checked);
checkNodeWithCascade(treeObj, node, checked);
}
scheduleSave();
}