fix(permission): 角色节点联动已授权应用、所属部门按行收窄、树滚动条收敛
build / build (push) Successful in 2m53s

- 权限一览:formatRoleAuthzTree 子节点键 apps→children(zTree 只认 children,导致除分类外全不渲染)
- 权限一览:所属部门按行所在组请求 UserGroupTree,多组用户只显示该组链路,(本组)标记指向该组
- 角色授权:用户管理新增「部门」列,添加/删除按(用户,组)维度,切组清空勾选避免误绑
- 编辑用户:切「所在组」时身份与角色属性双回显,该组角色未就绪拦截提交防误存;保存按组覆盖
- 组用户管理:左侧树滚动条收敛到树节点(页面级新增 group-user-tree 类,不动全局 .tree-container)
This commit is contained in:
chenxing
2026-09-16 16:33:58 +08:00
parent 4237615238
commit af94e1d2cb
8 changed files with 134 additions and 53 deletions
+18 -25
View File
@@ -20,10 +20,8 @@ const userGroups = ref<{ id: number; name: string }[]>([]);
const userGroupIdsArr = ref<number[]>([]);
// 角色属性下拉:来自角色授权页的全量角色
const roleOptions = ref<{ id: number; name: string }[]>([]);
// 角色属性是否成功加载(控制编辑后是否回写 saveUserRoles,避免加载失败时误清空)
// 角色属性是否成功加载(控制编辑后是否回写 saveUserRoles,避免下拉失败时误清空)
const roleOptionsLoaded = ref(false);
// 已绑定角色是否成功加载(与 roleOptionsLoaded 同时为真才回写,防止误清空)
const userRolesLoaded = ref(false);
// 待保存的角色属性(用户多选结果)
let appRolesToSave: number[] = [];
@@ -62,7 +60,6 @@ async function open(row: any, groups: any[], curGroupId?: number | null, onSucce
const groupList = groups || [];
onSuccessCallback = onSuccess || null;
roleOptionsLoaded.value = false;
userRolesLoaded.value = false;
userGroups.value = [];
userGroupIdsArr.value = [];
roleOptions.value = [];
@@ -104,28 +101,17 @@ async function open(row: any, groups: any[], curGroupId?: number | null, onSucce
console.warn('[EditUserContainer] 加载角色列表失败:', e?.message || '未知错误');
}
// 3) 已绑定角色 -> appRoles 回填(相互独立)
// 所在组(只读)默认选中「当前组」:优先用进入编辑时的左侧树/列表组(curGroupId),否则取用户所属组的第一个
// 3) 角色属性回显不再在此加载:UserForm 内部监听 userGroupId(初始选中即触发),
// 按组调 GET UserRoles 回显该组角色,并对「该组回显未就绪」拦截提交,防止误清空。
// 所在组默认选中「当前组」:优先用进入编辑时的左侧树/列表组(curGroupId),否则取用户所属组的第一个
const defaultGroupId = (curGroupId != null && userGroupIdsArr.value.includes(curGroupId))
? curGroupId
: (userGroupIdsArr.value[0] ?? null);
try {
const userRolesResp = await Apis.GroupAuthz.userRoles({ params: { userId: row.id } });
const userRoleIds: number[] = (Array.isArray(userRolesResp) ? userRolesResp : [])
.map((r: any) => r.id)
.filter((x: any): x is number => x != null);
userRolesLoaded.value = true;
detailData.value = buildInitForm(row, userRoleIds, defaultGroupId);
} catch (e: any) {
// 已绑定角色加载失败:仍回填其它字段,但标记未加载成功(提交时不回写,避免误清空)
detailData.value = buildInitForm(row, [], defaultGroupId);
// 全局拦截器已弹窗,这里仅记录日志避免二次弹窗
console.warn('[EditUserContainer] 加载用户已绑定角色失败:', e?.message || '未知错误');
}
detailData.value = buildInitForm(row, defaultGroupId);
}
/* ---------- 把行数据映射成表单回填数据 ---------- */
function buildInitForm(row: any, userRoleIds: number[], defaultGroupId: number | null = null) {
function buildInitForm(row: any, defaultGroupId: number | null = null) {
return {
id: row.id,
account: row.account || '',
@@ -135,10 +121,10 @@ function buildInitForm(row: any, userRoleIds: number[], defaultGroupId: number |
password: '',
confirmPassword: '',
gender: row.gender ?? 0,
// 用户身份回显唯一来源是 GET UserIdentity(UserForm 内部按「所在组」加载):
// 用户身份/角色属性回显唯一来源是 GET UserIdentity / GET UserRoles(UserForm 内部按「所在组」加载):
// roles 列已停写,row.roles 为冻结历史值,误用回显会把废弃值固化进权威表
userType: [],
appRoles: userRoleIds || [],
appRoles: [],
userGroupId: defaultGroupId ?? null,
};
}
@@ -154,10 +140,17 @@ async function handleSubmit(formData: Record<string, any>) {
// 编辑失败(onError 已提示),不继续回写角色
return;
}
// 编辑成功后再回写业务角色(全量覆盖);仅当角色列表与已绑定角色都成功加载才回写,防止误清空
if (roleOptionsLoaded.value && userRolesLoaded.value) {
// 编辑成功后再回写业务角色(按组覆盖:只覆盖当前所在组下的绑定,不影响其他组);
// 仅当角色下拉成功加载才回写,防止误清空(该组角色回显未就绪时 UserForm 已拦截提交)
if (roleOptionsLoaded.value) {
try {
await Apis.AppRole.saveUserRoles({ data: { userId: currentId.value as number, roleIds: appRolesToSave } });
await Apis.AppRole.saveUserRoles({
data: {
userId: currentId.value as number,
groupId: (formData.userGroupId as number | null) ?? undefined,
roleIds: appRolesToSave,
},
});
} catch (e: any) {
// 全局拦截器已弹窗,这里仅记录日志避免二次弹窗
console.warn('[EditUserContainer] 用户已更新,但角色属性保存失败:', e?.message || '未知错误');