feat: 新增系统配置角色管理页面,包含增删改查功能

This commit is contained in:
2026-05-15 19:00:06 +08:00
parent ff54a2bba5
commit dead892f4e
6 changed files with 358 additions and 1 deletions

48
src/api/system/role.ts Normal file
View File

@@ -0,0 +1,48 @@
import { get, post } from '@/utils/request';
/** 角色记录数据结构(不导出,仅供本文件内使用) */
interface RoleRecord {
/** 字符串 ID */
id: string;
/** 角色名称 */
roleName: string;
/** 角色标识 */
roleKey: string;
/** 状态:启用 | 禁用 */
status: string;
/** 备注 */
remark?: string;
}
/** 新增/编辑角色的请求参数(不导出) */
interface RoleParams {
roleName: string;
roleKey: string;
status: string;
remark?: string;
}
// ───────────── 角色接口 ─────────────
/** 获取角色列表 */
export const listRole = () => get<RoleRecord[]>('/api/system/role/list');
/**
* 新增角色
* @param params 角色参数
*/
export const addRole = (params: RoleParams) => post<RoleRecord>('/api/system/role/add', params);
/**
* 编辑角色
* @param id 角色 ID
* @param params 角色参数
*/
export const editRole = (id: string, params: RoleParams) =>
post<RoleRecord>('/api/system/role/edit', { id, ...params });
/**
* 删除角色
* @param id 角色 ID
*/
export const delRole = (id: string) => post('/api/system/role/del', { id });

View File

@@ -19,6 +19,15 @@ interface MockUserRecord {
deptId?: string;
}
/** 角色记录mock 本地定义) */
interface MockRoleRecord {
id: string;
roleName: string;
roleKey: string;
status: string;
remark?: string;
}
// ── mock 数据 ────────────────────────────────
/** 部门树 mock 数据 */
@@ -56,6 +65,14 @@ const userMap: Record<string, MockUserRecord[]> = {
'1-3': [],
};
/** 角色列表 mock 数据(运行时可变) */
const roleList: MockRoleRecord[] = [
{ id: '1', roleName: '超级管理员', roleKey: 'admin', status: '启用', remark: '拥有所有权限' },
{ id: '2', roleName: '编辑员', roleKey: 'editor', status: '启用', remark: '内容编辑权限' },
{ id: '3', roleName: '审核员', roleKey: 'reviewer', status: '启用', remark: '内容审核权限' },
{ id: '4', roleName: '访客', roleKey: 'guest', status: '禁用', remark: '只读权限' },
];
/** 包装为统一响应格式 */
const ok = (data: unknown) => ({ code: '0', msg: 'ok', data, time: Date.now(), ok: true });
@@ -154,4 +171,37 @@ export const systemHandlers = [
}
return HttpResponse.json(ok({ id: body.id }));
}),
// ───── 角色接口 ─────
http.get('/api/system/role/list', () => {
return HttpResponse.json(ok(roleList));
}),
http.post('/api/system/role/add', async ({ request }) => {
const body = await request.json() as Record<string, unknown>;
const newRole: MockRoleRecord = {
id: `role_${Date.now()}`,
roleName: body.roleName as string,
roleKey: body.roleKey as string,
status: body.status as string,
remark: body.remark as string | undefined,
};
roleList.push(newRole);
return HttpResponse.json(ok(newRole));
}),
http.post('/api/system/role/edit', async ({ request }) => {
const body = await request.json() as MockRoleRecord;
const idx = roleList.findIndex((r) => r.id === body.id);
if (idx !== -1) roleList[idx] = body;
return HttpResponse.json(ok(body));
}),
http.post('/api/system/role/del', async ({ request }) => {
const body = await request.json() as { id: string };
const idx = roleList.findIndex((r) => r.id === body.id);
if (idx !== -1) roleList.splice(idx, 1);
return HttpResponse.json(ok({ id: body.id }));
}),
];

View File

@@ -0,0 +1,77 @@
import { Form, Input, Modal, Select } from 'antd';
import { useEffect } from 'react';
/** 角色表单字段(页面本地定义) */
export interface RoleFormValues {
roleName: string;
roleKey: string;
status: string;
remark?: string;
}
interface RoleModalProps {
/** 弹窗是否可见 */
open: boolean;
/** 弹窗标题 */
title: string;
/** 编辑时的初始值 */
initialValues?: Partial<RoleFormValues>;
/** 确认回调 */
onOk: (values: RoleFormValues) => void;
/** 取消回调 */
onCancel: () => void;
}
const RoleModal = ({ open, title, initialValues, onOk, onCancel }: RoleModalProps) => {
const [form] = Form.useForm<RoleFormValues>();
/** 每次打开时重置并填充表单 */
useEffect(() => {
if (open) {
form.resetFields();
if (initialValues) form.setFieldsValue(initialValues);
}
}, [open, initialValues, form]);
const handleOk = async () => {
const values = await form.validateFields();
onOk(values);
};
const isEdit = !!initialValues;
return (
<Modal title={title} open={open} onOk={handleOk} onCancel={onCancel} destroyOnClose>
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item
label="角色名称"
name="roleName"
rules={[{ required: true, message: '请输入角色名称' }]}
>
<Input placeholder="请输入角色名称" />
</Form.Item>
<Form.Item
label="角色标识"
name="roleKey"
rules={[{ required: true, message: '请输入角色标识' }]}
>
{/* 编辑时角色标识不可修改 */}
<Input placeholder="请输入角色标识" disabled={isEdit} />
</Form.Item>
<Form.Item
label="状态"
name="status"
initialValue="启用"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select options={[{ label: '启用', value: '启用' }, { label: '禁用', value: '禁用' }]} />
</Form.Item>
<Form.Item label="备注" name="remark">
<Input.TextArea placeholder="请输入备注" rows={3} />
</Form.Item>
</Form>
</Modal>
);
};
export default RoleModal;

View File

@@ -0,0 +1,169 @@
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import { App, Button, Card, Popconfirm, Space, Table, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { useEffect, useState } from 'react';
import { addRole, delRole, editRole, listRole } from '@/api/system/role';
import RoleModal from './RoleModal';
import type { RoleFormValues } from './RoleModal';
/** 角色记录(页面本地定义,不依赖 API 层类型) */
interface RoleRecord {
id: string;
roleName: string;
roleKey: string;
status: string;
remark?: string;
}
/** 状态值与 Tag 颜色的映射 */
const statusColorMap: Record<string, string> = {
: 'success',
: 'error',
};
const RoleTable = () => {
const { message } = App.useApp();
const [loading, setLoading] = useState(false);
const [data, setData] = useState<RoleRecord[]>([]);
// tick 变化时触发角色列表重新加载
const [tick, setTick] = useState(0);
/** 触发角色列表刷新 */
const refresh = () => setTick((n) => n + 1);
useEffect(() => {
let cancelled = false;
const load = async () => {
setLoading(true);
try {
const res = await listRole();
// 用 cancelled 标志位防止组件卸载后的竞态更新
if (!cancelled) setData(res.data ?? []);
} finally {
if (!cancelled) setLoading(false);
}
};
load();
return () => { cancelled = true; };
}, [tick]);
// 弹窗状态
const [modalOpen, setModalOpen] = useState(false);
const [modalTitle, setModalTitle] = useState('新增角色');
const [editingRecord, setEditingRecord] = useState<RoleRecord | undefined>();
/** 打开新增弹窗 */
const handleAdd = () => {
setModalTitle('新增角色');
setEditingRecord(undefined);
setModalOpen(true);
};
/** 打开编辑弹窗 */
const handleEdit = (record: RoleRecord) => {
setModalTitle('编辑角色');
setEditingRecord(record);
setModalOpen(true);
};
/** 删除角色 */
const handleDelete = async (id: string) => {
await delRole(id);
message.success('删除成功');
refresh();
};
/** Modal 确认,根据是否有 editingRecord 判断新增/编辑 */
const handleModalOk = async (values: RoleFormValues) => {
if (editingRecord) {
await editRole(editingRecord.id, values);
message.success('编辑成功');
} else {
await addRole(values);
message.success('新增成功');
}
setModalOpen(false);
refresh();
};
const columns: ColumnsType<RoleRecord> = [
{
title: '序列',
width: 80,
align: 'center' as const,
render: (_: unknown, _record: RoleRecord, index: number) => index + 1,
},
{ title: '角色名称', dataIndex: 'roleName', key: 'roleName' },
{ title: '角色标识', dataIndex: 'roleKey', key: 'roleKey' },
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={statusColorMap[status] ?? 'default'}>{status}</Tag>
),
},
{
title: '操作',
key: 'action',
width: 120,
render: (_: unknown, record: RoleRecord) => (
<Space>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Popconfirm
title="确认删除"
description={`确定要删除角色「${record.roleName}」吗?`}
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true }}
onConfirm={() => handleDelete(record.id)}
>
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
</Button>
</Popconfirm>
</Space>
),
},
];
return (
<Card
title="角色列表"
extra={
<Button type="primary" size="small" icon={<PlusOutlined />} onClick={handleAdd}>
</Button>
}
>
<Table<RoleRecord>
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
pagination={{ pageSize: 10 }}
/>
<RoleModal
open={modalOpen}
title={modalTitle}
initialValues={editingRecord}
onOk={handleModalOk}
onCancel={() => setModalOpen(false)}
/>
</Card>
);
};
export default RoleTable;

View File

@@ -0,0 +1,6 @@
import RoleTable from './RoleTable';
/** 角色管理主页面 */
const RoleManagement = () => <RoleTable />;
export default RoleManagement;

View File

@@ -1,8 +1,9 @@
import { HomeOutlined, InfoCircleOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
import { HomeOutlined, InfoCircleOutlined, SettingOutlined, TeamOutlined, UserOutlined } from '@ant-design/icons';
import SystemLayout from '@/layouts/SystemLayout';
import About from '@/pages/about';
import Home from '@/pages/home';
import NotFound from '@/pages/not-found';
import RoleManagement from '@/pages/system/role';
import UserManagement from '@/pages/system/user';
import type { RouteItem } from './types';
@@ -31,6 +32,12 @@ export const routes: RouteItem[] = [
icon: <UserOutlined />,
component: <UserManagement />,
},
{
path: '/system/role',
label: '角色管理',
icon: <TeamOutlined />,
component: <RoleManagement />,
},
],
},
{