feat: 添加部门管理功能、主题切换和多语言支持
refactor(dashboard): 重构用户管理页面和路由结构 feat(server): 实现部门管理API和RBAC增强功能 docs: 更新用户手册和管理员指南文档 style: 统一图标使用和组件命名规范 test: 添加部门服务和数据隔离测试用例 chore: 更新依赖和配置文件
This commit is contained in:
411
dashboard/src/pages/Settings/RoleManagement.tsx
Normal file
411
dashboard/src/pages/Settings/RoleManagement.tsx
Normal file
@@ -0,0 +1,411 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
Button,
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Checkbox,
|
||||
Tree,
|
||||
message,
|
||||
Space,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Typography,
|
||||
Divider,
|
||||
Collapse,
|
||||
} from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, SaveOutlined, CloseOutlined, KeyOutlined, LockOutlined, DownOutlined, UpOutlined, LineChartOutlined } from '@ant-design/icons';
|
||||
import { useRequest } from 'umi';
|
||||
import {
|
||||
createRole as createRoleAPI,
|
||||
updateRole as updateRoleAPI,
|
||||
deleteRole as deleteRoleAPI,
|
||||
getAllRoles as getAllRolesAPI,
|
||||
getPermissionDefinitions as getPermissionsAPI,
|
||||
} from '@/services/roleDataSource';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Option } = Select;
|
||||
const { TreeNode } = Tree;
|
||||
const { Panel } = Collapse;
|
||||
|
||||
const RoleManagement: React.FC = () => {
|
||||
const [roles, setRoles] = useState<any[]>([]);
|
||||
const [permissions, setPermissions] = useState<any[]>([]);
|
||||
const [selectedRole, setSelectedRole] = useState<any>(null);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [deletingRole, setDeletingRole] = useState<string>('');
|
||||
|
||||
// 加载角色列表
|
||||
const { run: loadRoles, loading: loadingRoles } = useRequest(getAllRolesAPI, {
|
||||
onSuccess: (data) => {
|
||||
setRoles(data);
|
||||
},
|
||||
});
|
||||
|
||||
// 加载权限列表
|
||||
const { run: loadPermissions, loading: loadingPermissions } = useRequest(getPermissionsAPI, {
|
||||
onSuccess: (data) => {
|
||||
setPermissions(data);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadRoles();
|
||||
loadPermissions();
|
||||
}, []);
|
||||
|
||||
// 打开创建/编辑角色模态框
|
||||
const showModal = (role?: any) => {
|
||||
if (role) {
|
||||
setSelectedRole(role);
|
||||
form.setFieldsValue({
|
||||
roleCode: role.code,
|
||||
roleName: role.name,
|
||||
permissions: role.permissions || [],
|
||||
});
|
||||
} else {
|
||||
setSelectedRole(null);
|
||||
form.resetFields();
|
||||
}
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
// 关闭模态框
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
setSelectedRole(null);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
// 提交角色表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setConfirmLoading(true);
|
||||
|
||||
if (selectedRole) {
|
||||
// 更新角色
|
||||
await updateRoleAPI(values.roleCode, values.roleName, values.permissions);
|
||||
message.success('Role updated successfully');
|
||||
} else {
|
||||
// 创建角色
|
||||
await createRoleAPI(values.roleCode, values.roleName, values.permissions);
|
||||
message.success('Role created successfully');
|
||||
}
|
||||
|
||||
setIsModalVisible(false);
|
||||
form.resetFields();
|
||||
loadRoles();
|
||||
} catch (error) {
|
||||
message.error('Failed to save role');
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 打开删除确认模态框
|
||||
const showDeleteConfirm = (roleCode: string) => {
|
||||
setDeletingRole(roleCode);
|
||||
setIsDeleteModalVisible(true);
|
||||
};
|
||||
|
||||
// 确认删除角色
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
setConfirmLoading(true);
|
||||
await deleteRoleAPI(deletingRole);
|
||||
message.success('Role deleted successfully');
|
||||
setIsDeleteModalVisible(false);
|
||||
loadRoles();
|
||||
} catch (error) {
|
||||
message.error('Failed to delete role');
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 构建权限树
|
||||
const buildPermissionTree = () => {
|
||||
const modules = new Map<string, any[]>();
|
||||
|
||||
// 按模块分组权限
|
||||
permissions.forEach(permission => {
|
||||
if (!modules.has(permission.module)) {
|
||||
modules.set(permission.module, []);
|
||||
}
|
||||
modules.get(permission.module)?.push(permission);
|
||||
});
|
||||
|
||||
return Array.from(modules.entries()).map(([module, perms]) => (
|
||||
<TreeNode key={module} title={module}>
|
||||
{perms.map(perm => (
|
||||
<TreeNode
|
||||
key={perm.id}
|
||||
title={
|
||||
<Space size="small">
|
||||
<Text>{perm.name}</Text>
|
||||
<Tooltip title={perm.description}>
|
||||
<Tag size="small" color="blue">{perm.action}</Tag>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</TreeNode>
|
||||
));
|
||||
};
|
||||
|
||||
// 检查角色是否为系统预设角色
|
||||
const isSystemRole = (roleCode: string) => {
|
||||
const systemRoles = ['ADMIN', 'MANAGER', 'OPERATOR', 'FINANCE', 'SOURCING', 'LOGISTICS', 'ANALYST'];
|
||||
return systemRoles.includes(roleCode);
|
||||
};
|
||||
|
||||
// 计算权限继承关系
|
||||
const calculatePermissionInheritance = (role: any) => {
|
||||
const inheritedPermissions = new Set<string>();
|
||||
const roleHierarchy = {
|
||||
'ADMIN': [],
|
||||
'MANAGER': ['OPERATOR'],
|
||||
'OPERATOR': [],
|
||||
'FINANCE': [],
|
||||
'SOURCING': [],
|
||||
'LOGISTICS': [],
|
||||
'ANALYST': [],
|
||||
};
|
||||
|
||||
// 递归计算继承的权限
|
||||
const calculateInheritance = (roleCode: string) => {
|
||||
const childRoles = roleHierarchy[roleCode as keyof typeof roleHierarchy] || [];
|
||||
childRoles.forEach(childRoleCode => {
|
||||
const childRole = roles.find(r => r.code === childRoleCode);
|
||||
if (childRole) {
|
||||
childRole.permissions?.forEach((perm: string) => {
|
||||
inheritedPermissions.add(perm);
|
||||
});
|
||||
calculateInheritance(childRoleCode);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
calculateInheritance(role.code);
|
||||
return Array.from(inheritedPermissions);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Role Code',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
render: (code: string) => (
|
||||
<Space>
|
||||
<KeyOutlined />
|
||||
<Text strong>{code}</Text>
|
||||
{isSystemRole(code) && <Tag color="green">System</Tag>}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Role Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Permissions',
|
||||
dataIndex: 'permissions',
|
||||
key: 'permissions',
|
||||
render: (permissions: string[]) => (
|
||||
<Tag color="blue">{permissions?.length || 0} permissions</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
render: (_: any, record: any) => (
|
||||
<Space size="middle">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showModal(record)}
|
||||
disabled={isSystemRole(record.code)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => showDeleteConfirm(record.code)}
|
||||
disabled={isSystemRole(record.code)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<LockOutlined />
|
||||
<Title level={4}>Role Management</Title>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => showModal()}
|
||||
>
|
||||
Create Role
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Alert
|
||||
message="Role Management"
|
||||
description="Create and manage custom roles with specific permissions. System roles cannot be modified."
|
||||
type="info"
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={roles}
|
||||
rowKey="code"
|
||||
loading={loadingRoles}
|
||||
pagination={{ pageSize: 10 }}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<div style={{ padding: 20 }}>
|
||||
<Collapse defaultActiveKey={['permissions', 'inheritance']}>
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
<LockOutlined />
|
||||
<Text strong>Assigned Permissions</Text>
|
||||
</Space>
|
||||
}
|
||||
key="permissions"
|
||||
>
|
||||
<Space wrap>
|
||||
{record.permissions?.map((perm: string) => {
|
||||
const permInfo = permissions.find(p => p.id === perm);
|
||||
return (
|
||||
<Tag key={perm} color="blue">
|
||||
{permInfo?.name || perm}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</Panel>
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
<LineChartOutlined />
|
||||
<Text strong>Permission Inheritance</Text>
|
||||
</Space>
|
||||
}
|
||||
key="inheritance"
|
||||
>
|
||||
<div>
|
||||
<Text>Role Hierarchy:</Text>
|
||||
<div style={{ marginLeft: 20, marginTop: 10 }}>
|
||||
{calculatePermissionInheritance(record).length > 0 ? (
|
||||
<Space wrap>
|
||||
{calculatePermissionInheritance(record).map((perm: string) => {
|
||||
const permInfo = permissions.find(p => p.id === perm);
|
||||
return (
|
||||
<Tag key={perm} color="green">
|
||||
{permInfo?.name || perm}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
) : (
|
||||
<Text type="secondary">No inherited permissions</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Create/Edit Role Modal */}
|
||||
<Modal
|
||||
title={selectedRole ? 'Edit Role' : 'Create Role'}
|
||||
open={isModalVisible}
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
width={800}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="roleCode"
|
||||
label="Role Code"
|
||||
rules={[
|
||||
{ required: true, message: 'Please enter role code' },
|
||||
{ pattern: /^[A-Z_]+$/, message: 'Role code must be uppercase with underscores' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="e.g., SALES_MANAGER" disabled={!!selectedRole} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="roleName"
|
||||
label="Role Name"
|
||||
rules={[{ required: true, message: 'Please enter role name' }]}
|
||||
>
|
||||
<Input placeholder="e.g., Sales Manager" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="permissions"
|
||||
label="Permissions"
|
||||
rules={[{ required: true, message: 'Please select permissions' }]}
|
||||
>
|
||||
<Tree
|
||||
checkable
|
||||
treeData={buildPermissionTree()}
|
||||
placeholder="Select permissions"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* Delete Role Modal */}
|
||||
<Modal
|
||||
title="Delete Role"
|
||||
open={isDeleteModalVisible}
|
||||
onOk={handleDelete}
|
||||
onCancel={() => setIsDeleteModalVisible(false)}
|
||||
confirmLoading={confirmLoading}
|
||||
okText="Delete"
|
||||
cancelText="Cancel"
|
||||
okType="danger"
|
||||
>
|
||||
<p>Are you sure you want to delete the role <strong>{deletingRole}</strong>?</p>
|
||||
<p>All users assigned to this role will lose these permissions.</p>
|
||||
</Modal>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoleManagement;
|
||||
Reference in New Issue
Block a user