1. 父级选中态(两页口径统一)
isAppChecked 由「全部子模块都勾选才 true」改为「任一子孙被勾选即 true」,
新增 isModuleAnyChecked 替代不再使用的 isModuleAllChecked;buildModuleNode 同步。
样式把 zTree checkbox_true_part / _focus 的 background-position 指向 checkbox_true_full
的蓝勾位置——父节点 checked=true 但子节点未全选时,原生是"蓝底横杠"半选,
折叠态几乎看不出部分授权,改成蓝勾后与全选视觉一致。
2. 无子模块应用(如「一周安排」)勾选后刷新即失效
- GroupAuth:这类应用没有模块明细,原 buildModuleDiff 前后快照恒为空、根本不发请求。
改为单独走 moduleId=0 的「整应用」提交(后端翻译成 -appId 哨兵记录);
回显读后端应用级 checked / inheritFlag;setLocalAppInherit 支持应用级。
- AppModuleCheckTree:onTreeCheck 直接记 app.checked;saveNow 把后端约定的 -appId
并入 moduleIds —— 角色授权是模块 id 全量覆盖,不带哨兵每一项保存都会被静默清掉。
后端配套改动见 UserBaseService 同名提交(AppLevelModuleId / resolveModuleIds / VO 新增 checked)。
This commit is contained in:
+55
-7
@@ -90,6 +90,10 @@ type AppItem = {
|
||||
id?: number;
|
||||
name?: string;
|
||||
sortNo?: number;
|
||||
/** 应用级勾选态:仅当 modules 为空时有意义(对应后端 module_id=0 的整应用授权记录) */
|
||||
checked?: boolean;
|
||||
/** 应用级子组继承:仅当 modules 为空时有意义 */
|
||||
inheritFlag?: boolean;
|
||||
modules?: AppModuleVo[];
|
||||
};
|
||||
|
||||
@@ -221,19 +225,22 @@ function countTotalModules(nodes: AppModuleVo[]): number {
|
||||
|
||||
function isAppChecked(app: AppItem): boolean {
|
||||
const modules = app.modules || [];
|
||||
if (modules.length === 0) return false;
|
||||
return modules.every((m) => isModuleAllChecked(m));
|
||||
// 无子模块应用(如「一周安排」):勾选态来自后端返回的整应用授权记录
|
||||
if (modules.length === 0) return !!app.checked;
|
||||
// 父级展示口径:只要有一个子节点被勾选就算选中(不采用 zTree 半选,见样式里 true_part 的覆写)
|
||||
return modules.some((m) => isModuleAnyChecked(m));
|
||||
}
|
||||
|
||||
function isAppInherit(app: AppItem): boolean {
|
||||
const modules = app.modules || [];
|
||||
if (modules.length === 0) return false;
|
||||
if (modules.length === 0) return !!app.inheritFlag;
|
||||
return modules.every((m) => isModuleAllInherit(m));
|
||||
}
|
||||
|
||||
function isModuleAllChecked(node: AppModuleVo): boolean {
|
||||
if (!node.checked) return false;
|
||||
return (node.children || []).every((c) => isModuleAllChecked(c));
|
||||
/** 自身或任一子孙被勾选 */
|
||||
function isModuleAnyChecked(node: AppModuleVo): boolean {
|
||||
if (node.checked) return true;
|
||||
return (node.children || []).some((c) => isModuleAnyChecked(c));
|
||||
}
|
||||
|
||||
function isModuleAllInherit(node: AppModuleVo): boolean {
|
||||
@@ -248,7 +255,8 @@ function buildModuleNode(m: AppModuleVo, appId: number): AuthzTreeNode {
|
||||
id: `mod_${m.id}`,
|
||||
name: String(m.name || ""),
|
||||
open: true,
|
||||
checked: !!m.checked,
|
||||
// 父模块节点沿用「有子节点选中即选中」的展示口径,与顶层应用节点一致
|
||||
checked: isModuleAnyChecked(m),
|
||||
inheritFlag: !!m.inheritFlag,
|
||||
nodeType: "module",
|
||||
rawId: Number(m.id),
|
||||
@@ -423,6 +431,10 @@ function findLocalApp(appId: number): AppItem | null {
|
||||
function setLocalAppInherit(appId: number, inherit: boolean) {
|
||||
const app = findLocalApp(appId);
|
||||
if (!app) return;
|
||||
if ((app.modules || []).length === 0) {
|
||||
app.inheritFlag = inherit;
|
||||
return;
|
||||
}
|
||||
walkModules(app.modules || [], (n) => {
|
||||
if (n.checked) n.inheritFlag = inherit;
|
||||
});
|
||||
@@ -453,6 +465,33 @@ async function onTreeCheck(node: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 无子模块应用(如「一周安排」):授权粒度即整个应用,落 moduleId=0 的整应用记录。
|
||||
// 这类应用没有可 diff 的模块明细,必须单独走应用级提交,否则勾选只是个视觉状态、刷新即失效。
|
||||
const appModules = app.modules || [];
|
||||
if (appModules.length === 0) {
|
||||
const checked = !!node.checked;
|
||||
try {
|
||||
if (checked !== !!app.checked) {
|
||||
app.checked = checked;
|
||||
await Apis.GroupAuthz.setGroupModule({
|
||||
data: [{
|
||||
groupId,
|
||||
appId: node.appId,
|
||||
moduleId: 0,
|
||||
mode: checked ? addMode.value : deleteMode.value,
|
||||
isAll: 0,
|
||||
}],
|
||||
cacheFor: null,
|
||||
});
|
||||
}
|
||||
syncInheritDoms(treeObj);
|
||||
} catch (e: any) {
|
||||
console.warn(e?.message || "操作失败");
|
||||
await reloadCurrent();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 级联前快照:用于计算本次实际变化的模块明细
|
||||
const before = new Map<number, boolean>();
|
||||
walkModules(app.modules, (m) => before.set(Number(m.id), !!m.checked));
|
||||
@@ -736,6 +775,15 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 父级勾选样式(2026-10-20):
|
||||
zTree 的「半选」在两个状态位各有一套图标——true_part(父已勾但子未全勾)/
|
||||
false_part(父未勾但子有勾)。这里把 true_part 的图标换成 true_full 的蓝底勾,
|
||||
让"只要有一个子节点被选中"的父节点统一呈现为选中态,避免折叠时看着像没授权。 */
|
||||
:deep(.ztree li span.button.chk.checkbox_true_part),
|
||||
:deep(.ztree li span.button.chk.checkbox_true_part_focus) {
|
||||
background-position: -26px -5px;
|
||||
}
|
||||
|
||||
/* zTree 节点行尾的"子组继承"复选框:
|
||||
float 会跟随节点缩进产生阶梯,改为绝对定位锚定卡片体右边界,所有层级右对齐成一列 */
|
||||
:deep(.inherit-wrap) {
|
||||
|
||||
@@ -64,7 +64,7 @@ type AppModuleVo = {
|
||||
checked?: boolean;
|
||||
children?: AppModuleVo[];
|
||||
};
|
||||
type AppItem = { id?: number; name?: string; sortNo?: number; modules?: AppModuleVo[] };
|
||||
type AppItem = { id?: number; name?: string; sortNo?: number; checked?: boolean; modules?: AppModuleVo[] };
|
||||
type CategoryItem = { id?: number; name?: string; apps?: AppItem[] };
|
||||
type AuthzTreeNode = {
|
||||
id: string;
|
||||
@@ -118,12 +118,16 @@ function countTotalModules(nodes: AppModuleVo[]): number {
|
||||
}
|
||||
function isAppChecked(app: AppItem): boolean {
|
||||
const modules = app.modules || [];
|
||||
if (modules.length === 0) return false;
|
||||
return modules.every((m) => isModuleAllChecked(m));
|
||||
// 无子模块应用(如「一周安排」):勾选态来自后端返回的整应用授权记录
|
||||
if (modules.length === 0) return !!app.checked;
|
||||
// 父级展示口径:只要有一个子节点被勾选就算选中(不采用 zTree 半选,见样式里 true_part 的覆写)
|
||||
return modules.some((m) => isModuleAnyChecked(m));
|
||||
}
|
||||
function isModuleAllChecked(node: AppModuleVo): boolean {
|
||||
if (!node.checked) return false;
|
||||
return (node.children || []).every((c) => isModuleAllChecked(c));
|
||||
|
||||
/** 自身或任一子孙被勾选 */
|
||||
function isModuleAnyChecked(node: AppModuleVo): boolean {
|
||||
if (node.checked) return true;
|
||||
return (node.children || []).some((c) => isModuleAnyChecked(c));
|
||||
}
|
||||
// 空 children 不挂键:zTree 只要见到 children 是数组就强制 isParent=true 而渲染 +/−(叶子也会出现展开图标)
|
||||
function buildModuleNode(m: AppModuleVo, appId: number): AuthzTreeNode {
|
||||
@@ -132,7 +136,7 @@ function buildModuleNode(m: AppModuleVo, appId: number): AuthzTreeNode {
|
||||
id: `mod_${m.id}`,
|
||||
name: String(m.name || ""),
|
||||
open: true,
|
||||
checked: !!m.checked,
|
||||
checked: isModuleAnyChecked(m),
|
||||
nodeType: "module",
|
||||
rawId: Number(m.id),
|
||||
appId,
|
||||
@@ -255,7 +259,12 @@ function onTreeCheck(node: any) {
|
||||
// zTree 原生级联已生效,把该应用最终勾选态同步回本地数据(saveNow 的提交依据)
|
||||
const app = findLocalApp(node.appId);
|
||||
if (!app) return;
|
||||
syncAppModulesFromTree(app, node);
|
||||
if ((app.modules || []).length === 0) {
|
||||
// 无子模块应用:没有模块明细可同步,直接记整应用勾选态
|
||||
app.checked = !!node.checked;
|
||||
} else {
|
||||
syncAppModulesFromTree(app, node);
|
||||
}
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
@@ -265,7 +274,14 @@ const saveNow = async () => {
|
||||
const moduleIds: number[] = [];
|
||||
for (const cat of categoryList.value || []) {
|
||||
for (const app of cat.apps || []) {
|
||||
walkModules(app.modules || [], (n) => { if (n.checked) moduleIds.push(Number(n.id)); });
|
||||
const mods = app.modules || [];
|
||||
if (mods.length === 0) {
|
||||
// 无子模块应用:用 -appId 哨兵表示「整应用授权」(后端 AppLevelModuleId 约定),
|
||||
// 否则这类应用永远收集不到 id、保存时被整表覆盖清掉。
|
||||
if (app.checked) moduleIds.push(-Number(app.id));
|
||||
continue;
|
||||
}
|
||||
walkModules(mods, (n) => { if (n.checked) moduleIds.push(Number(n.id)); });
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -441,6 +457,13 @@ watch(
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 父级勾选样式(2026-10-20):把 zTree 半选状态位 true_part 的图标换成 true_full 的蓝底勾,
|
||||
让「只要有一个子节点被选中」的父节点统一呈现为选中态(与组授权页口径一致)。 */
|
||||
:deep(.ztree li span.button.chk.checkbox_true_part),
|
||||
:deep(.ztree li span.button.chk.checkbox_true_part_focus) {
|
||||
background-position: -26px -5px;
|
||||
}
|
||||
|
||||
:deep(.ztree li a) {
|
||||
max-width: calc(100% - 30px);
|
||||
overflow: hidden;
|
||||
|
||||
Reference in New Issue
Block a user