Files
gongxue-base/apps/admin/src/pages/Roles/index.tsx

291 lines
8.2 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import {
Table,
Modal,
Form,
Input,
Space,
Tag,
Popconfirm,
message,
Card,
Checkbox,
} from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import api from '../../api';
import PermissionButton from '../../components/PermissionButton';
interface PermissionItem {
id: number;
code: string;
name: string;
group: string;
}
interface RoleItem {
id: number;
name: string;
description: string;
isSystem: boolean;
status: number;
permissions: PermissionItem[];
}
const RolesPage: React.FC = () => {
const [data, setData] = useState<RoleItem[]>([]);
const [loading, setLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [editing, setEditing] = useState<RoleItem | null>(null);
const [allPerms, setAllPerms] = useState<{ group: string; permissions: PermissionItem[] }[]>([]);
const [form] = Form.useForm();
const [selectedPermIds, setSelectedPermIds] = useState<number[]>([]);
const fetchData = async () => {
setLoading(true);
try {
const [roles, permTree] = await Promise.all([
api.get('/rbac/roles') as Promise<RoleItem[]>,
api.get('/rbac/permissions/tree') as Promise<
{ group: string; permissions: PermissionItem[] }[]
>,
]);
setData(roles);
setAllPerms(permTree);
} catch (e) {
console.error(e);
}
setLoading(false);
};
useEffect(() => {
fetchData();
}, []);
const handleAdd = () => {
setEditing(null);
form.resetFields();
setSelectedPermIds([]);
setModalOpen(true);
};
const handleEdit = (record: RoleItem) => {
setEditing(record);
form.setFieldsValue({ name: record.name, description: record.description });
setSelectedPermIds(record.permissions.map((p) => p.id));
setModalOpen(true);
};
const handleSubmit = async () => {
const values = await form.validateFields();
try {
if (editing) {
await api.put(`/rbac/roles/${editing.id}`, {
name: values.name,
description: values.description,
permissionIds: selectedPermIds,
});
message.success('角色更新成功');
} else {
await api.post('/rbac/roles', {
name: values.name,
description: values.description,
permissionIds: selectedPermIds,
});
message.success('角色创建成功');
}
setModalOpen(false);
fetchData();
} catch (e: any) {
message.error(e.message || '操作失败');
}
};
const handleDelete = async (id: number) => {
try {
await api.delete(`/rbac/roles/${id}`);
message.success('角色已删除');
fetchData();
} catch (e: any) {
message.error(e.message || '删除失败');
}
};
const groupNames: Record<string, string> = {
dashboard: '数据面板',
student: '学生管理',
room: '宿舍管理',
occupancy: '入住管理',
expense: '费用管理',
bill: '账单管理',
deposit: '押金管理',
classroom: '教室管理',
tenant: '租赁方',
rental: '租赁订单',
log: '操作日志',
user: '用户管理',
role: '角色管理',
};
const columns = [
{ title: 'ID', dataIndex: 'id', width: 60 },
{ title: '名称', dataIndex: 'name', width: 120 },
{ title: '描述', dataIndex: 'description', width: 200, ellipsis: true },
{
title: '权限标签',
dataIndex: 'permissions',
width: 150,
ellipsis: true,
render: (perms: PermissionItem[]) =>
perms?.length > 0 ? (
<Tag color="blue">{perms.length} </Tag>
) : (
<Tag color="default"></Tag>
),
},
{
title: '系统',
dataIndex: 'isSystem',
width: 70,
render: (v: boolean) => (v ? <Tag color="orange"></Tag> : null),
},
{
title: '操作',
width: 160,
fixed: 'right' as const,
render: (_: any, record: RoleItem) => (
<Space>
<PermissionButton
permission="role:edit"
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</PermissionButton>
{!record.isSystem && (
<Popconfirm title="确认删除该角色?" onConfirm={() => handleDelete(record.id)}>
<PermissionButton permission="role:delete" type="link" size="small" danger icon={<DeleteOutlined />}>
</PermissionButton>
</Popconfirm>
)}
</Space>
),
},
];
const handleGroupCheckAll = (group: string, checked: boolean) => {
const groupPermIds =
allPerms.find((g) => g.group === group)?.permissions.map((p) => p.id) || [];
if (checked) {
setSelectedPermIds((prev) => [...new Set([...prev, ...groupPermIds])]);
} else {
setSelectedPermIds((prev) => prev.filter((id) => !groupPermIds.includes(id)));
}
};
const isGroupAllChecked = (group: string) => {
const groupPermIds =
allPerms.find((g) => g.group === group)?.permissions.map((p) => p.id) || [];
return groupPermIds.length > 0 && groupPermIds.every((id) => selectedPermIds.includes(id));
};
const isGroupIndeterminate = (group: string) => {
const groupPermIds =
allPerms.find((g) => g.group === group)?.permissions.map((p) => p.id) || [];
const checkedCount = groupPermIds.filter((id) => selectedPermIds.includes(id)).length;
return checkedCount > 0 && checkedCount < groupPermIds.length;
};
return (
<div>
<div
style={{
marginBottom: 16,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'wrap',
gap: 8,
}}
>
<h2 style={{ margin: 0 }}></h2>
<PermissionButton
permission="role:create"
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
>
</PermissionButton>
</div>
<Table
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
scroll={{ x: 'max-content' }}
pagination={false}
/>
<Modal
title={editing ? '编辑角色' : '新增角色'}
open={modalOpen}
onOk={handleSubmit}
onCancel={() => setModalOpen(false)}
width={700}
destroyOnHidden
>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label="角色名称"
rules={[{ required: true, message: '请输入角色名称' }]}
>
<Input disabled={editing?.isSystem} />
</Form.Item>
<Form.Item name="description" label="角色描述">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="权限分配">
<div style={{ maxHeight: 400, overflow: 'auto' }}>
{allPerms.map((group) => (
<Card
key={group.group}
size="small"
title={
<Checkbox
checked={isGroupAllChecked(group.group)}
indeterminate={isGroupIndeterminate(group.group)}
onChange={(e) => handleGroupCheckAll(group.group, e.target.checked)}
>
{groupNames[group.group] || group.group}
</Checkbox>
}
style={{ marginBottom: 8 }}
>
<Checkbox.Group
value={selectedPermIds}
onChange={(vals) => setSelectedPermIds(vals as number[])}
>
<Space wrap>
{group.permissions.map((p) => (
<Checkbox key={p.id} value={p.id}>
{p.name}
</Checkbox>
))}
</Space>
</Checkbox.Group>
</Card>
))}
</div>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default RolesPage;