import { useState } from 'react';
|
import { PlusOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
|
import {
|
App as AntdApp,
|
Card,
|
Form,
|
Input,
|
Modal,
|
Table,
|
Tag,
|
} from 'antd';
|
import type { TableProps } from 'antd';
|
import dayjs from 'dayjs';
|
import AuthButton from '@/components/AuthButton';
|
import { PERMISSION } from '@/constants/permissions';
|
import { usePermission } from '@/hooks/usePermission';
|
import './Roles.scss';
|
|
type RoleStatus = 'active' | 'disabled';
|
|
interface RoleRecord {
|
key: number;
|
name: string;
|
code: string;
|
description: string;
|
members: number;
|
permissions: number;
|
isSuper: boolean;
|
status: RoleStatus;
|
createdAt: string;
|
}
|
|
interface RoleFormValues {
|
name: string;
|
code: string;
|
description?: string;
|
}
|
|
const initialRoles: RoleRecord[] = [
|
{
|
key: 1,
|
name: '超级管理员',
|
code: 'super_admin',
|
description: '拥有平台全部功能与数据权限',
|
members: 2,
|
permissions: 18,
|
isSuper: true,
|
status: 'active',
|
createdAt: '2026-01-08 09:30',
|
},
|
{
|
key: 2,
|
name: '运营管理员',
|
code: 'operator',
|
description: '负责客户、订单及日常运营管理',
|
members: 6,
|
permissions: 12,
|
isSuper: false,
|
status: 'active',
|
createdAt: '2026-02-14 10:12',
|
},
|
{
|
key: 3,
|
name: '销售专员',
|
code: 'sales',
|
description: '维护客户资料并跟进销售订单',
|
members: 24,
|
permissions: 8,
|
isSuper: false,
|
status: 'active',
|
createdAt: '2026-02-20 16:45',
|
},
|
{
|
key: 4,
|
name: '访客',
|
code: 'guest',
|
description: '仅可查看工作台与公开数据',
|
members: 11,
|
permissions: 3,
|
isSuper: false,
|
status: 'disabled',
|
createdAt: '2026-04-06 11:08',
|
},
|
];
|
|
const statusMeta: Record<RoleStatus, { label: string; color: string }> = {
|
active: { label: '启用', color: 'green' },
|
disabled: { label: '停用', color: 'default' },
|
};
|
|
export default function Roles() {
|
const [roles, setRoles] = useState<RoleRecord[]>(initialRoles);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [editingRole, setEditingRole] = useState<RoleRecord | null>(null);
|
const [form] = Form.useForm<RoleFormValues>();
|
const { message } = AntdApp.useApp();
|
const { hasPermission } = usePermission();
|
|
const canEdit = hasPermission(PERMISSION.ROLE_EDIT);
|
|
const openCreateModal = () => {
|
setEditingRole(null);
|
form.resetFields();
|
setIsModalOpen(true);
|
};
|
|
const openEditModal = (record: RoleRecord) => {
|
setEditingRole(record);
|
form.setFieldsValue({
|
name: record.name,
|
code: record.code,
|
description: record.description,
|
});
|
setIsModalOpen(true);
|
};
|
|
const closeModal = () => {
|
setIsModalOpen(false);
|
form.resetFields();
|
};
|
|
const handleSubmit = async () => {
|
try {
|
const values = await form.validateFields();
|
const duplicate = roles.some(
|
(role) => role.code === values.code && role.key !== editingRole?.key,
|
);
|
if (duplicate) {
|
message.error('角色标识已存在,请更换一个');
|
return;
|
}
|
|
if (editingRole) {
|
setRoles((prev) =>
|
prev.map((role) =>
|
role.key === editingRole.key
|
? { ...role, ...values, description: values.description ?? '' }
|
: role,
|
),
|
);
|
message.success('角色已更新');
|
} else {
|
setRoles((prev) => [
|
{
|
key: Date.now(),
|
name: values.name,
|
code: values.code,
|
description: values.description ?? '',
|
members: 0,
|
permissions: 0,
|
isSuper: false,
|
status: 'active',
|
createdAt: dayjs().format('YYYY-MM-DD HH:mm'),
|
},
|
...prev,
|
]);
|
message.success('角色创建成功');
|
}
|
closeModal();
|
} catch {
|
// 表单校验失败时由 antd 自动展示错误信息
|
}
|
};
|
|
const columns: TableProps<RoleRecord>['columns'] = [
|
{
|
title: '角色',
|
dataIndex: 'name',
|
width: 250,
|
render: (_value, record) => (
|
<div className="role-cell">
|
<span className="role-cell__icon">
|
<SafetyCertificateOutlined />
|
</span>
|
<div className="role-cell__meta">
|
<span className="role-cell__name">
|
{record.name}
|
{record.isSuper && (
|
<Tag color="purple" bordered={false} className="role-cell__tag">
|
内置
|
</Tag>
|
)}
|
</span>
|
<span className="role-cell__desc">{record.description}</span>
|
</div>
|
</div>
|
),
|
},
|
{
|
title: '角色标识',
|
dataIndex: 'code',
|
width: 150,
|
render: (value: string) => <code className="role-code">{value}</code>,
|
},
|
{
|
title: '成员',
|
dataIndex: 'members',
|
width: 80,
|
align: 'center',
|
render: (value: number) => <span>{value} 人</span>,
|
},
|
{
|
title: '权限范围',
|
dataIndex: 'permissions',
|
width: 120,
|
render: (value: number, record) => (
|
<Tag color={record.isSuper ? 'purple' : 'blue'} bordered={false}>
|
{record.isSuper ? '全部权限' : `${value} 项`}
|
</Tag>
|
),
|
},
|
{
|
title: '状态',
|
dataIndex: 'status',
|
width: 90,
|
render: (value: RoleStatus) => (
|
<Tag color={statusMeta[value].color} bordered={false}>
|
{statusMeta[value].label}
|
</Tag>
|
),
|
},
|
{ title: '创建时间', dataIndex: 'createdAt', width: 170 },
|
{
|
title: '操作',
|
key: 'action',
|
width: 100,
|
render: (_value, record) =>
|
canEdit ? (
|
<AuthButton
|
type="link"
|
size="small"
|
permission={PERMISSION.ROLE_EDIT}
|
onClick={() => openEditModal(record)}
|
>
|
编辑
|
</AuthButton>
|
) : (
|
<span className="text-muted">-</span>
|
),
|
},
|
];
|
|
return (
|
<div className="roles-page">
|
<div className="page-header">
|
<div>
|
<h1 className="page-header__title">角色管理</h1>
|
<p className="page-header__desc">
|
为不同岗位配置角色与权限范围,成员将继承角色对应的菜单与按钮权限。
|
</p>
|
</div>
|
<div className="page-header__actions">
|
<AuthButton
|
type="primary"
|
icon={<PlusOutlined />}
|
permission={PERMISSION.ROLE_ADD}
|
onClick={openCreateModal}
|
>
|
新增角色
|
</AuthButton>
|
</div>
|
</div>
|
|
<Card className="table-card" variant="borderless">
|
<Table<RoleRecord>
|
rowKey="key"
|
columns={columns}
|
dataSource={roles}
|
scroll={{ x: 960 }}
|
pagination={false}
|
/>
|
</Card>
|
|
<Modal
|
title={editingRole ? '编辑角色' : '新增角色'}
|
open={isModalOpen}
|
onOk={handleSubmit}
|
onCancel={closeModal}
|
okText={editingRole ? '保存' : '创建'}
|
cancelText="取消"
|
forceRender
|
>
|
<Form form={form} layout="vertical" preserve={false}>
|
<Form.Item
|
name="name"
|
label="角色名称"
|
rules={[{ required: true, message: '请输入角色名称' }]}
|
>
|
<Input placeholder="例如:销售主管" maxLength={20} />
|
</Form.Item>
|
<Form.Item
|
name="code"
|
label="角色标识"
|
rules={[
|
{ required: true, message: '请输入角色标识' },
|
{
|
pattern: /^[a-z][a-z0-9_]{2,19}$/,
|
message: '3-20 位小写字母、数字或下划线',
|
},
|
]}
|
>
|
<Input placeholder="例如:sales_manager" disabled={Boolean(editingRole)} />
|
</Form.Item>
|
<Form.Item name="description" label="角色描述">
|
<Input.TextArea
|
placeholder="简要描述该角色的职责与权限范围"
|
rows={3}
|
maxLength={100}
|
showCount
|
/>
|
</Form.Item>
|
</Form>
|
</Modal>
|
</div>
|
);
|
}
|