From 6a93967271255ce440f32ff1c7439317b25dc59d Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 2 Jul 2026 12:04:35 +0800 Subject: [PATCH] feat(frontend): add Roles management page and Permissions overview page --- frontend/src/pages/Permissions/index.tsx | 77 +++++++++ frontend/src/pages/Roles/index.tsx | 206 +++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 frontend/src/pages/Permissions/index.tsx create mode 100644 frontend/src/pages/Roles/index.tsx diff --git a/frontend/src/pages/Permissions/index.tsx b/frontend/src/pages/Permissions/index.tsx new file mode 100644 index 0000000..9783d27 --- /dev/null +++ b/frontend/src/pages/Permissions/index.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from 'react'; +import { Card, Tag, Input, Space, Spin } from 'antd'; +import api from '../../api'; + +interface PermissionItem { + id: number; + code: string; + name: string; + group: string; + description: string; +} + +const PermissionsPage: React.FC = () => { + const [permTree, setPermTree] = useState<{ group: string; permissions: PermissionItem[] }[]>([]); + const [loading, setLoading] = useState(false); + const [search, setSearch] = useState(''); + + const groupNames: Record = { + dashboard: '数据面板', student: '学生管理', room: '宿舍管理', occupancy: '入住管理', + expense: '费用管理', bill: '账单管理', deposit: '押金管理', + classroom: '教室管理', tenant: '租赁方', rental: '租赁订单', + log: '操作日志', user: '用户管理', role: '角色管理', + }; + + useEffect(() => { + setLoading(true); + api.get('/rbac/permissions/tree') + .then((res: any) => setPermTree(res)) + .catch(console.error) + .finally(() => setLoading(false)); + }, []); + + const filteredTree = search + ? permTree.map(g => ({ + ...g, + permissions: g.permissions.filter(p => + p.name.includes(search) || p.code.includes(search) + ), + })).filter(g => g.permissions.length > 0) + : permTree; + + if (loading) return ; + + return ( +
+
+

权限一览

+ !e.target.value && setSearch('')} + /> +
+ + {filteredTree.map(group => ( + {groupNames[group.group] || group.group} ({group.permissions.length})} + size="small" + > + + {group.permissions.map(p => ( + + {p.name} {p.code} + + ))} + + + ))} + +
+ ); +}; + +export default PermissionsPage; diff --git a/frontend/src/pages/Roles/index.tsx b/frontend/src/pages/Roles/index.tsx new file mode 100644 index 0000000..cb706fd --- /dev/null +++ b/frontend/src/pages/Roles/index.tsx @@ -0,0 +1,206 @@ +import React, { useEffect, useState } from 'react'; +import { Table, Button, 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([]); + const [loading, setLoading] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + const [editing, setEditing] = useState(null); + const [allPerms, setAllPerms] = useState<{ group: string; permissions: PermissionItem[] }[]>([]); + const [form] = Form.useForm(); + const [selectedPermIds, setSelectedPermIds] = useState([]); + + const fetchData = async () => { + setLoading(true); + try { + const [roles, permTree] = await Promise.all([ + api.get('/rbac/roles') as Promise, + 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 = { + 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 + ? {perms.length} 个权限 + : 无权限, + }, + { + title: '系统', dataIndex: 'isSystem', width: 70, + render: (v: boolean) => v ? 系统 : null, + }, + { + title: '操作', width: 160, fixed: 'right' as const, + render: (_: any, record: RoleItem) => ( + + } onClick={() => handleEdit(record)}> + 编辑 + + {!record.isSystem && ( + + handleDelete(record.id)}> + + + + )} + + ), + }, + ]; + + 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 ( +
+
+

角色管理

+ } onClick={handleAdd}> + 新增角色 + +
+ + + setModalOpen(false)} + width={700} + destroyOnClose + > +
+ + + + + + + +
+ {allPerms.map(group => ( + handleGroupCheckAll(group.group, e.target.checked)} + > + {groupNames[group.group] || group.group} + + } + style={{ marginBottom: 8 }} + > + setSelectedPermIds(vals as number[])} + > + + {group.permissions.map(p => ( + {p.name} + ))} + + + + ))} +
+
+ +
+ + ); +}; + +export default RolesPage;