fix(role): 优化角色资源树选中ID处理逻辑
This commit is contained in:
@@ -4,7 +4,7 @@ import type { TreeInstance } from 'element-plus';
|
|||||||
import { menuTypeRecord } from '@/constants/business';
|
import { menuTypeRecord } from '@/constants/business';
|
||||||
import { fetchAssignRoleMenus, fetchGetRoleMenuIds } from '@/service/api';
|
import { fetchAssignRoleMenus, fetchGetRoleMenuIds } from '@/service/api';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { resolveRoleMenuSubmitIds } from './role-resource-tree';
|
import { normalizeRoleMenuCheckedIds, resolveRoleMenuSubmitIds } from './role-resource-tree';
|
||||||
|
|
||||||
defineOptions({ name: 'RoleResourcePanel' });
|
defineOptions({ name: 'RoleResourcePanel' });
|
||||||
|
|
||||||
@@ -127,9 +127,14 @@ async function loadRoleMenus() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
baselineMenuIds.value = [...data];
|
const normalizedMenuIds = normalizeRoleMenuCheckedIds({
|
||||||
|
menuTree: props.menuTree,
|
||||||
|
checkedIds: data
|
||||||
|
});
|
||||||
|
|
||||||
|
baselineMenuIds.value = normalizedMenuIds;
|
||||||
dirtyMenuIds.value = new Set();
|
dirtyMenuIds.value = new Set();
|
||||||
await applyCheckedKeys(data);
|
await applyCheckedKeys(normalizedMenuIds);
|
||||||
treeRef.value?.filter(filterKeyword.value);
|
treeRef.value?.filter(filterKeyword.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,13 +149,11 @@ async function handleSave() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkedMenuIds = (treeRef.value?.getCheckedKeys(false) as string[]) ?? [];
|
const checkedMenuIds = (treeRef.value?.getCheckedKeys(false) as string[]) ?? [];
|
||||||
const halfCheckedMenuIds = (treeRef.value?.getHalfCheckedKeys() as string[]) ?? [];
|
|
||||||
const menuIds = resolveRoleMenuSubmitIds({
|
const menuIds = resolveRoleMenuSubmitIds({
|
||||||
menuTree: props.menuTree,
|
menuTree: props.menuTree,
|
||||||
baselineIds: baselineMenuIds.value,
|
baselineIds: baselineMenuIds.value,
|
||||||
dirtyIds: [...dirtyMenuIds.value],
|
dirtyIds: [...dirtyMenuIds.value],
|
||||||
checkedIds: checkedMenuIds,
|
checkedIds: checkedMenuIds
|
||||||
halfCheckedIds: halfCheckedMenuIds
|
|
||||||
});
|
});
|
||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
@@ -190,7 +193,13 @@ watch(
|
|||||||
() => props.menuTree.length,
|
() => props.menuTree.length,
|
||||||
async value => {
|
async value => {
|
||||||
if (value && props.role) {
|
if (value && props.role) {
|
||||||
await applyCheckedKeys(baselineMenuIds.value);
|
const normalizedMenuIds = normalizeRoleMenuCheckedIds({
|
||||||
|
menuTree: props.menuTree,
|
||||||
|
checkedIds: baselineMenuIds.value
|
||||||
|
});
|
||||||
|
|
||||||
|
baselineMenuIds.value = normalizedMenuIds;
|
||||||
|
await applyCheckedKeys(normalizedMenuIds);
|
||||||
treeRef.value?.filter(filterKeyword.value);
|
treeRef.value?.filter(filterKeyword.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ type ResolveRoleMenuSubmitIdsInput = {
|
|||||||
baselineIds: string[];
|
baselineIds: string[];
|
||||||
dirtyIds: string[];
|
dirtyIds: string[];
|
||||||
checkedIds: string[];
|
checkedIds: string[];
|
||||||
halfCheckedIds: string[];
|
};
|
||||||
|
|
||||||
|
type NormalizeRoleMenuCheckedIdsInput = {
|
||||||
|
menuTree: RoleResourceTreeNode[];
|
||||||
|
checkedIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type TreeIndex = {
|
type TreeIndex = {
|
||||||
@@ -17,6 +21,39 @@ type TreeIndex = {
|
|||||||
subtreeIdsById: Map<string, string[]>;
|
subtreeIdsById: Map<string, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function normalizeRoleMenuCheckedIds(input: NormalizeRoleMenuCheckedIdsInput) {
|
||||||
|
const checkedIds = normalizeIds(input.checkedIds);
|
||||||
|
|
||||||
|
if (!checkedIds.length) {
|
||||||
|
return checkedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeIndex = buildTreeIndex(input.menuTree);
|
||||||
|
const normalizedIdSet = new Set(checkedIds);
|
||||||
|
|
||||||
|
treeIndex.orderedIds.forEach(id => {
|
||||||
|
if (!normalizedIdSet.has(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const descendantIds = (treeIndex.subtreeIdsById.get(id) ?? [id]).slice(1);
|
||||||
|
|
||||||
|
if (!descendantIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedDescendantCount = descendantIds.reduce((count, descendantId) => {
|
||||||
|
return normalizedIdSet.has(descendantId) ? count + 1 : count;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (selectedDescendantCount > 0 && selectedDescendantCount < descendantIds.length) {
|
||||||
|
normalizedIdSet.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortIdsByTreeOrder(treeIndex.orderedIds, normalizedIdSet);
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveRoleMenuSubmitIds(input: ResolveRoleMenuSubmitIdsInput) {
|
export function resolveRoleMenuSubmitIds(input: ResolveRoleMenuSubmitIdsInput) {
|
||||||
const baselineIds = normalizeIds(input.baselineIds);
|
const baselineIds = normalizeIds(input.baselineIds);
|
||||||
|
|
||||||
@@ -39,7 +76,8 @@ export function resolveRoleMenuSubmitIds(input: ResolveRoleMenuSubmitIdsInput) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
normalizeIds([...input.checkedIds, ...input.halfCheckedIds]).forEach(id => {
|
// 半选父节点只用于树态展示,提交它会把整棵子树误当成完整授权。
|
||||||
|
normalizeIds(input.checkedIds).forEach(id => {
|
||||||
if (affectedIds.has(id)) {
|
if (affectedIds.has(id)) {
|
||||||
nextIdSet.add(id);
|
nextIdSet.add(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { describe, it } from 'node:test';
|
import { describe, it } from 'node:test';
|
||||||
import { resolveRoleMenuSubmitIds } from '../src/views/system/role/modules/role-resource-tree';
|
import * as roleResourceTree from '../src/views/system/role/modules/role-resource-tree';
|
||||||
|
|
||||||
type MenuNode = {
|
type MenuNode = {
|
||||||
id: string;
|
id: string;
|
||||||
children?: MenuNode[];
|
children?: MenuNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NormalizeRoleMenuCheckedIds = (input: { menuTree: MenuNode[]; checkedIds: string[] }) => string[];
|
||||||
|
|
||||||
const menuTree: MenuNode[] = [
|
const menuTree: MenuNode[] = [
|
||||||
{
|
{
|
||||||
id: 'personal',
|
id: 'personal',
|
||||||
@@ -27,14 +29,18 @@ const menuTree: MenuNode[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const normalizeRoleMenuCheckedIds = (roleResourceTree as { normalizeRoleMenuCheckedIds?: NormalizeRoleMenuCheckedIds })
|
||||||
|
.normalizeRoleMenuCheckedIds;
|
||||||
|
|
||||||
|
const { resolveRoleMenuSubmitIds } = roleResourceTree;
|
||||||
|
|
||||||
describe('resolveRoleMenuSubmitIds', () => {
|
describe('resolveRoleMenuSubmitIds', () => {
|
||||||
it('keeps original ids when there is no user interaction', () => {
|
it('keeps original ids when there is no user interaction', () => {
|
||||||
const result = resolveRoleMenuSubmitIds({
|
const result = resolveRoleMenuSubmitIds({
|
||||||
menuTree,
|
menuTree,
|
||||||
baselineIds: ['weekly', 'monthly'],
|
baselineIds: ['weekly', 'monthly'],
|
||||||
dirtyIds: [],
|
dirtyIds: [],
|
||||||
checkedIds: ['personal', 'weekly', 'monthly'],
|
checkedIds: ['personal', 'weekly', 'monthly']
|
||||||
halfCheckedIds: []
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(result, ['weekly', 'monthly']);
|
assert.deepEqual(result, ['weekly', 'monthly']);
|
||||||
@@ -45,8 +51,7 @@ describe('resolveRoleMenuSubmitIds', () => {
|
|||||||
menuTree,
|
menuTree,
|
||||||
baselineIds: ['weekly', 'monthly'],
|
baselineIds: ['weekly', 'monthly'],
|
||||||
dirtyIds: ['stateMachine'],
|
dirtyIds: ['stateMachine'],
|
||||||
checkedIds: ['personal', 'weekly', 'monthly', 'stateMachine'],
|
checkedIds: ['personal', 'weekly', 'monthly', 'stateMachine']
|
||||||
halfCheckedIds: ['infra']
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(result, ['weekly', 'monthly', 'stateMachine']);
|
assert.deepEqual(result, ['weekly', 'monthly', 'stateMachine']);
|
||||||
@@ -57,8 +62,7 @@ describe('resolveRoleMenuSubmitIds', () => {
|
|||||||
menuTree,
|
menuTree,
|
||||||
baselineIds: ['personal'],
|
baselineIds: ['personal'],
|
||||||
dirtyIds: ['weekly'],
|
dirtyIds: ['weekly'],
|
||||||
checkedIds: ['personal', 'weekly', 'weeklyDetail'],
|
checkedIds: ['personal', 'weekly', 'weeklyDetail']
|
||||||
halfCheckedIds: []
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(result, ['personal', 'weekly', 'weeklyDetail']);
|
assert.deepEqual(result, ['personal', 'weekly', 'weeklyDetail']);
|
||||||
@@ -69,10 +73,44 @@ describe('resolveRoleMenuSubmitIds', () => {
|
|||||||
menuTree,
|
menuTree,
|
||||||
baselineIds: ['monthly'],
|
baselineIds: ['monthly'],
|
||||||
dirtyIds: ['weeklyDetail'],
|
dirtyIds: ['weeklyDetail'],
|
||||||
checkedIds: ['personal', 'weekly', 'weeklyDetail', 'monthly', 'monthlyDetail'],
|
checkedIds: ['personal', 'weekly', 'weeklyDetail', 'monthly', 'monthlyDetail']
|
||||||
halfCheckedIds: []
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(result, ['weeklyDetail', 'monthly']);
|
assert.deepEqual(result, ['weeklyDetail', 'monthly']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not submit half-checked parent ids when a fully authorized branch becomes partial', () => {
|
||||||
|
const result = resolveRoleMenuSubmitIds({
|
||||||
|
menuTree,
|
||||||
|
baselineIds: ['personal'],
|
||||||
|
dirtyIds: ['monthlyDetail'],
|
||||||
|
checkedIds: ['weekly', 'weeklyDetail']
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(result, ['weekly', 'weeklyDetail']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normalizeRoleMenuCheckedIds', () => {
|
||||||
|
it('removes partially covered parent ids before tree rendering', () => {
|
||||||
|
assert.equal(typeof normalizeRoleMenuCheckedIds, 'function');
|
||||||
|
|
||||||
|
const result = normalizeRoleMenuCheckedIds?.({
|
||||||
|
menuTree,
|
||||||
|
checkedIds: ['personal', 'weekly', 'weeklyDetail']
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(result, ['weekly', 'weeklyDetail']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps parent ids when the backend uses a parent-only full-branch representation', () => {
|
||||||
|
assert.equal(typeof normalizeRoleMenuCheckedIds, 'function');
|
||||||
|
|
||||||
|
const result = normalizeRoleMenuCheckedIds?.({
|
||||||
|
menuTree,
|
||||||
|
checkedIds: ['personal']
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(result, ['personal']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user