fix: 修复系统管理审查发现的权限一致性与数据同步问题
统一角色与权限配置的标识体系,并修正菜单树勾选、菜单级联清理与用户备注持久化。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 => ({
|
||||
...node,
|
||||
...values,
|
||||
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('菜单已保存');
|
||||
|
||||
@@ -287,6 +287,7 @@ export default function RoleManagement() {
|
||||
</Typography.Title>
|
||||
<Tree
|
||||
checkable
|
||||
checkStrictly
|
||||
defaultExpandAll
|
||||
checkedKeys={menuKeys}
|
||||
treeData={menuPermissionTree}
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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: '',
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user