fix: 修复系统管理审查发现的权限一致性与数据同步问题

统一角色与权限配置的标识体系,并修正菜单树勾选、菜单级联清理与用户备注持久化。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-26 03:27:23 +08:00
parent 77f718965d
commit 8a03781577
8 changed files with 74 additions and 60 deletions

View File

@@ -19,6 +19,12 @@ test.describe('系统管理 / 权限配置', () => {
await page.getByRole('button', { name: '保 存' }).click();
await expect(page.getByText('菜单已保存')).toBeVisible();
await expect(page.getByRole('tree').getByText('用户中心')).toBeVisible();
await page.getByRole('tab', { name: '按钮权限' }).click();
const buttonPanel = page.getByRole('tabpanel', { name: '按钮权限' });
await expect(buttonPanel.getByRole('row').filter({ hasText: 'system:user:add' })).toContainText(
'系统管理 / 用户中心',
);
});
test('可新增同级菜单', async ({ page }) => {
@@ -42,4 +48,15 @@ test.describe('系统管理 / 权限配置', () => {
await expect(buttonPanel.getByText('system:user:import')).toBeVisible();
await expect(page.getByText('按钮权限已添加')).toBeVisible();
});
test('删除菜单时同步清理按钮权限', async ({ page }) => {
await page.getByRole('button', { name: '删除' }).first().click();
await page.getByRole('button', { name: '确 定' }).click();
await expect(page.getByText('菜单已删除')).toBeVisible();
await page.getByRole('tab', { name: '按钮权限' }).click();
const buttonPanel = page.getByRole('tabpanel', { name: '按钮权限' });
await expect(buttonPanel.getByText('system:user:add')).toHaveCount(0);
await expect(buttonPanel.getByText('system:user:edit')).toHaveCount(0);
});
});

View File

@@ -40,6 +40,9 @@ test.describe('系统管理 / 角色管理', () => {
await expect(page.getByText('配置权限 - 运营')).toBeVisible();
await expect(page.getByText('菜单权限')).toBeVisible();
await expect(page.getByText('按钮权限')).toBeVisible();
await expect(page.getByRole('treeitem', { name: '用量明细' })).toHaveAttribute('aria-checked', 'false');
await expect(page.getByRole('treeitem', { name: '模型列表' })).toHaveAttribute('aria-checked', 'true');
await expect(page.getByRole('checkbox', { name: 'system:user:add' })).toBeChecked();
await page.getByRole('button', { name: '保 存' }).click();
await expect(page.getByText('权限已保存')).toBeVisible();

View File

@@ -51,9 +51,14 @@ test.describe('系统管理 / 用户管理', () => {
await operatorRow.getByRole('button', { name: '编辑' }).click();
const dialog = page.getByRole('dialog', { name: '编辑用户' });
await dialog.getByLabel('昵称').fill('新运营');
await dialog.getByLabel('备注').fill('负责日常运营');
await dialog.getByRole('button', { name: '保 存' }).click();
await expect(page.getByText('新运营', { exact: true })).toBeVisible();
await operatorRow.getByRole('button', { name: '编辑' }).click();
await expect(page.getByRole('dialog', { name: '编辑用户' }).getByLabel('备注')).toHaveValue('负责日常运营');
await page.getByRole('dialog', { name: '编辑用户' }).getByRole('button', { name: '取 消' }).click();
const viewerRow = page.getByRole('row').filter({ hasText: 'viewer' });
await viewerRow.getByRole('button', { name: '删除' }).click();
await page.getByRole('button', { name: '确 定' }).click();

View File

@@ -93,6 +93,11 @@ const removeNode = (nodes: MenuPermissionNode[], key: string): MenuPermissionNod
.filter(node => node.key !== key)
.map(node => (node.children ? { ...node, children: removeNode(node.children, key) } : node));
const collectNodeKeys = (node: MenuPermissionNode): string[] => [
node.key,
...(node.children?.flatMap(collectNodeKeys) ?? []),
];
const toTreeData = (nodes: MenuPermissionNode[]): DataNode[] =>
nodes.map(node => ({
key: node.key,
@@ -212,8 +217,10 @@ export default function PermissionManagement() {
const deleteMenu = () => {
if (!selectedNode) return;
const removedKeys = new Set(collectNodeKeys(selectedNode));
const nextTree = removeNode(menuTree, selectedKey);
setMenuTree(nextTree);
setButtons(current => current.filter(item => !removedKeys.has(item.menuKey)));
const fallback = nextTree[0]?.key ?? nextTree[0]?.children?.[0]?.key;
if (fallback) {
setSelectedKey(fallback);
@@ -228,10 +235,17 @@ export default function PermissionManagement() {
const saveMenu = async () => {
if (!selectedKey) return;
const values = await menuForm.validateFields();
setMenuTree(current =>
updateNode(current, selectedKey, node => ({
const nextTree = updateNode(menuTree, selectedKey, node => ({
...node,
...values,
}));
const menuPaths = new Map(flattenMenus(nextTree).map(item => [item.key, item.label]));
setMenuTree(nextTree);
setButtons(current =>
current.map(item => ({
...item,
menuPath: menuPaths.get(item.menuKey) ?? item.menuPath,
})),
);
message.success('菜单已保存');

View File

@@ -287,6 +287,7 @@ export default function RoleManagement() {
</Typography.Title>
<Tree
checkable
checkStrictly
defaultExpandAll
checkedKeys={menuKeys}
treeData={menuPermissionTree}

View File

@@ -1,3 +1,9 @@
import {
initialButtonPermissions,
initialMenuTree,
type MenuPermissionNode as PermissionMenuNode,
} from '../permission/mock';
export interface RoleItem {
id: string;
name: string;
@@ -16,36 +22,19 @@ export interface MenuPermissionNode {
children?: MenuPermissionNode[];
}
export const menuPermissionTree: MenuPermissionNode[] = [
{ key: 'dashboard', title: '总览' },
{
key: 'llm',
title: '大模型管理',
children: [
{ key: 'llm-models', title: '模型列表' },
{ key: 'llm-providers', title: '供应商管理' },
{ key: 'llm-usage', title: '用量统计' },
{ key: 'llm-usage-details', title: '用量明细' },
{ key: 'llm-quota', title: '限流配额' },
],
},
{
key: 'system',
title: '系统管理',
children: [
{ key: 'system-user', title: '用户管理' },
{ key: 'system-role', title: '角色管理' },
{ key: 'system-permission', title: '权限配置' },
],
},
];
const toRoleMenuTree = (nodes: PermissionMenuNode[]): MenuPermissionNode[] =>
nodes.map(node => ({
key: node.key,
title: node.title,
children: node.children ? toRoleMenuTree(node.children) : undefined,
}));
export const buttonPermissionOptions = [
{ label: 'user:create', value: 'user:create' },
{ label: 'user:edit', value: 'user:edit' },
{ label: 'user:delete', value: 'user:delete' },
{ label: 'llm:create', value: 'llm:create' },
];
export const menuPermissionTree = toRoleMenuTree(initialMenuTree);
export const buttonPermissionOptions = initialButtonPermissions.map(item => ({
label: item.code,
value: item.code,
}));
export const initialRoles: RoleItem[] = [
{
@@ -56,20 +45,8 @@ export const initialRoles: RoleItem[] = [
enabled: true,
description: '拥有系统全部权限',
updatedAt: '2026-07-20 10:00',
menuKeys: [
'dashboard',
'llm',
'llm-models',
'llm-providers',
'llm-usage',
'llm-usage-details',
'llm-quota',
'system',
'system-user',
'system-role',
'system-permission',
],
buttonKeys: ['user:create', 'user:edit', 'user:delete', 'llm:create'],
menuKeys: ['1', '2', '2-1', '2-2', '2-3', '2-4', '2-5', '3', '3-1', '3-2', '3-3'],
buttonKeys: initialButtonPermissions.map(item => item.code),
},
{
id: 'role-2',
@@ -79,18 +56,8 @@ export const initialRoles: RoleItem[] = [
enabled: true,
description: '负责模型与用户日常运营',
updatedAt: '2026-07-22 15:30',
menuKeys: [
'dashboard',
'llm',
'llm-models',
'llm-providers',
'llm-usage',
'llm-quota',
'system',
'system-user',
'system-role',
],
buttonKeys: ['user:create', 'user:edit', 'llm:create'],
menuKeys: ['1', '2', '2-1', '2-2', '2-3', '2-5', '3', '3-1', '3-2'],
buttonKeys: ['system:user:add', 'system:user:edit', 'llm:models:add'],
},
{
id: 'role-3',
@@ -100,7 +67,7 @@ export const initialRoles: RoleItem[] = [
enabled: true,
description: '仅可查看数据,不可修改',
updatedAt: '2026-07-18 09:12',
menuKeys: ['dashboard', 'llm', 'llm-models', 'llm-usage'],
menuKeys: ['1', '2', '2-1', '2-3'],
buttonKeys: [],
},
];

View File

@@ -118,6 +118,7 @@ export default function UserManagement() {
phone: values.phone,
roles: values.roles,
enabled: values.enabled,
remark: values.remark,
}
: user,
),
@@ -134,6 +135,7 @@ export default function UserManagement() {
phone: values.phone,
roles: values.roles,
enabled: values.enabled,
remark: values.remark,
lastLoginAt: '从未登录',
createdAt: '2026-07-26',
},

View File

@@ -10,6 +10,7 @@ export interface UserItem {
enabled: boolean;
lastLoginAt: string;
createdAt: string;
remark?: string;
}
export const roleOptions: UserRole[] = ['超级管理员', '运营', '只读'];
@@ -25,6 +26,7 @@ export const initialUsers: UserItem[] = [
enabled: true,
lastLoginAt: '2026-07-25 22:10',
createdAt: '2025-01-01',
remark: '系统内置管理员',
},
{
id: 'user-2',
@@ -36,6 +38,7 @@ export const initialUsers: UserItem[] = [
enabled: true,
lastLoginAt: '2026-07-24 18:32',
createdAt: '2025-02-15',
remark: '',
},
{
id: 'user-3',
@@ -47,6 +50,7 @@ export const initialUsers: UserItem[] = [
enabled: false,
lastLoginAt: '2026-07-20 09:15',
createdAt: '2025-03-10',
remark: '',
},
{
id: 'user-4',
@@ -58,5 +62,6 @@ export const initialUsers: UserItem[] = [
enabled: true,
lastLoginAt: '2026-07-25 16:08',
createdAt: '2025-05-21',
remark: '',
},
];