- 新增 useCrudDrawer hook 封装 CRUD Drawer 通用模式(状态管理/提交/错误处理) - 新增 useListData hook 封装非分页列表数据获取 - 11 个页面统一迁移到 DrawerForm + 共享 hooks,消除重复代码 - 错误处理统一使用 useApiRequest.execute(),移除内联 try-catch - Modal 全部替换为 DrawerForm,保持 UI 一致性 - 净减少 ~1300 行代码(858 增 / 2136 删)
214 lines
7.6 KiB
TypeScript
214 lines
7.6 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import {
|
|
Table,
|
|
Button,
|
|
Space,
|
|
Form,
|
|
Input,
|
|
Tag,
|
|
Popconfirm,
|
|
Checkbox,
|
|
} from 'antd';
|
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
|
|
import {
|
|
listRoles,
|
|
createRole,
|
|
updateRole,
|
|
deleteRole,
|
|
assignPermissions,
|
|
getRolePermissions,
|
|
listPermissions,
|
|
type RoleInfo,
|
|
type PermissionInfo,
|
|
} from '../api/roles';
|
|
import { PageContainer } from '../components/PageContainer';
|
|
import { DrawerForm } from '../components/DrawerForm';
|
|
import { useCrudDrawer } from '../hooks/useCrudDrawer';
|
|
import { useApiRequest } from '../hooks/useApiRequest';
|
|
import { useThemeMode } from '../hooks/useThemeMode';
|
|
import { useListData } from '../hooks/useListData';
|
|
|
|
export default function Roles() {
|
|
const isDark = useThemeMode();
|
|
const { execute } = useApiRequest();
|
|
|
|
const { data: roles, loading, refresh } = useListData<RoleInfo>(async () => {
|
|
const result = await listRoles();
|
|
return result.data;
|
|
});
|
|
|
|
const [permissions, setPermissions] = useState<PermissionInfo[]>([]);
|
|
const [permDrawerOpen, setPermDrawerOpen] = useState(false);
|
|
const [selectedRole, setSelectedRole] = useState<RoleInfo | null>(null);
|
|
const [selectedPermIds, setSelectedPermIds] = useState<string[]>([]);
|
|
|
|
useEffect(() => {
|
|
listPermissions().then(setPermissions).catch(() => {});
|
|
}, []);
|
|
|
|
const roleDrawer = useCrudDrawer<RoleInfo>({
|
|
getId: (r) => r.id,
|
|
onCreate: async (values) => {
|
|
await createRole(values as unknown as { name: string; code: string; description?: string });
|
|
},
|
|
onUpdate: async (id, values) => {
|
|
await updateRole(id, values as unknown as { name: string; code: string; description?: string; version: number });
|
|
},
|
|
onSuccess: refresh,
|
|
});
|
|
|
|
const handleDelete = async (id: string) => {
|
|
await execute(() => deleteRole(id), '角色已删除');
|
|
refresh();
|
|
};
|
|
|
|
const openPermDrawer = async (role: RoleInfo) => {
|
|
setSelectedRole(role);
|
|
try {
|
|
const rolePerms = await getRolePermissions(role.id);
|
|
setSelectedPermIds(rolePerms.map((p) => p.id));
|
|
} catch {
|
|
setSelectedPermIds([]);
|
|
}
|
|
setPermDrawerOpen(true);
|
|
};
|
|
|
|
const savePermissions = async () => {
|
|
if (!selectedRole) return;
|
|
await execute(() => assignPermissions(selectedRole.id, selectedPermIds), '权限分配成功');
|
|
setPermDrawerOpen(false);
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: '角色名称', dataIndex: 'name', key: 'name',
|
|
render: (v: string, record: RoleInfo) => (
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
<div style={{
|
|
width: 32, height: 32, borderRadius: 8,
|
|
background: record.is_system ? 'linear-gradient(135deg, #2563eb, #60a5fa)' : isDark ? '#0f172a' : '#f8fafc',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
color: record.is_system ? '#fff' : isDark ? '#94a3b8' : '#475569', fontSize: 14,
|
|
}}>
|
|
<SafetyCertificateOutlined />
|
|
</div>
|
|
<span style={{ fontWeight: 500 }}>{v}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
title: '编码', dataIndex: 'code', key: 'code',
|
|
render: (v: string) => <Tag style={{ background: isDark ? '#0f172a' : '#f8fafc', border: 'none', color: isDark ? '#94a3b8' : '#475569', fontFamily: 'monospace', fontSize: 12 }}>{v}</Tag>,
|
|
},
|
|
{
|
|
title: '描述', dataIndex: 'description', key: 'description', ellipsis: true,
|
|
render: (v?: string) => <span style={{ color: isDark ? '#475569' : '#94a3b8' }}>{v || '-'}</span>,
|
|
},
|
|
{
|
|
title: '类型', dataIndex: 'is_system', key: 'is_system', width: 100,
|
|
render: (v: boolean) => (
|
|
<Tag style={{ color: v ? '#2563eb' : isDark ? '#94a3b8' : '#475569', background: v ? '#eff6ff' : isDark ? '#0f172a' : '#f8fafc', border: 'none', fontWeight: 500 }}>
|
|
{v ? '系统' : '自定义'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '操作', key: 'actions', width: 180,
|
|
render: (_: unknown, record: RoleInfo) => (
|
|
<Space size={4}>
|
|
<Button size="small" type="text" icon={<SafetyCertificateOutlined />} onClick={() => openPermDrawer(record)} style={{ color: '#2563eb' }}>权限</Button>
|
|
{!record.is_system && (
|
|
<>
|
|
<Button size="small" type="text" icon={<EditOutlined />} onClick={() => roleDrawer.openEdit(record, (r) => ({
|
|
name: r.name, code: r.code, description: r.description,
|
|
}))} style={{ color: isDark ? '#94a3b8' : '#475569' }} />
|
|
<Popconfirm title="确定删除此角色?" onConfirm={() => handleDelete(record.id)}>
|
|
<Button size="small" type="text" icon={<DeleteOutlined />} danger />
|
|
</Popconfirm>
|
|
</>
|
|
)}
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
const groupedPermissions = permissions.reduce<Record<string, PermissionInfo[]>>((acc, p) => {
|
|
if (!acc[p.resource]) acc[p.resource] = [];
|
|
acc[p.resource].push(p);
|
|
return acc;
|
|
}, {});
|
|
|
|
return (
|
|
<PageContainer
|
|
title="角色管理"
|
|
subtitle="管理系统角色和权限分配"
|
|
actions={<Button type="primary" icon={<PlusOutlined />} onClick={() => roleDrawer.openCreate()}>新建角色</Button>}
|
|
>
|
|
<Table
|
|
columns={columns}
|
|
dataSource={roles}
|
|
rowKey="id"
|
|
loading={loading}
|
|
pagination={{ pageSize: 20, showTotal: (t) => `共 ${t} 条记录` }}
|
|
/>
|
|
|
|
{/* 新建/编辑角色 Drawer */}
|
|
<DrawerForm
|
|
title={roleDrawer.editingRecord ? '编辑角色' : '新建角色'}
|
|
open={roleDrawer.open}
|
|
onClose={roleDrawer.close}
|
|
onSubmit={roleDrawer.handleSubmit}
|
|
initialValues={roleDrawer.initialValues}
|
|
loading={roleDrawer.loading}
|
|
width={480}
|
|
columns={1}
|
|
>
|
|
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入角色名称' }]}>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item name="code" label="编码" rules={[{ required: true, message: '请输入角色编码' }]}>
|
|
<Input disabled={!!roleDrawer.editingRecord} />
|
|
</Form.Item>
|
|
<Form.Item name="description" label="描述">
|
|
<Input.TextArea rows={3} />
|
|
</Form.Item>
|
|
</DrawerForm>
|
|
|
|
{/* 权限分配 Drawer */}
|
|
<DrawerForm
|
|
title={`权限分配 - ${selectedRole?.name || ''}`}
|
|
open={permDrawerOpen}
|
|
onClose={() => setPermDrawerOpen(false)}
|
|
onSubmit={savePermissions}
|
|
initialValues={{}}
|
|
loading={false}
|
|
width={600}
|
|
columns={1}
|
|
>
|
|
<div style={{ marginTop: 8 }}>
|
|
{Object.entries(groupedPermissions).map(([resource, perms]) => (
|
|
<div key={resource} style={{
|
|
marginBottom: 16, padding: 16, borderRadius: 10,
|
|
border: `1px solid ${isDark ? '#0f172a' : '#E2E8F0'}`,
|
|
background: isDark ? '#0B0F1A' : '#f1f5f9',
|
|
}}>
|
|
<div style={{ fontWeight: 600, marginBottom: 12, textTransform: 'capitalize', color: isDark ? '#E2E8F0' : '#334155', fontSize: 14 }}>
|
|
{resource}
|
|
</div>
|
|
<Checkbox.Group
|
|
value={selectedPermIds}
|
|
onChange={(values) => setSelectedPermIds(values as string[])}
|
|
style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}
|
|
>
|
|
{perms.map((p) => (
|
|
<Checkbox key={p.id} value={p.id} style={{ marginRight: 0 }}>{p.name}</Checkbox>
|
|
))}
|
|
</Checkbox.Group>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</DrawerForm>
|
|
</PageContainer>
|
|
);
|
|
}
|