forked from wangziqi/gongxue-base
- H4: scoped SSE import progress to exact userId match; non-HTTP events excluded from all subscribers - H2: moved PRAGMA foreign_key_check inside SQLite transaction before COMMIT; violations rollback preserving old tables - M1: removed dead axios-style error branch from extractErrorMessage (interceptor already unwraps) - M2: split handleSave try/catch — save errors vs reload errors shown distinctly - M3: added provider field validation before AI config test request - Added SSE scoping regression tests (import service + controller) - Added FK check failure rollback test (database-migrations.spec) - Updated controller spec expectations for userId parameter Co-authored-by: Code Review <branch-review>
127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Card, Tag, Input, Space, Spin, Empty } from 'antd';
|
|
import api from '../../api';
|
|
import { message } from '../../ui/app-message';
|
|
|
|
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<string, string> = {
|
|
dashboard: '数据面板',
|
|
student: '学生管理',
|
|
'student-scope': '学生数据范围',
|
|
teacher: '教师管理',
|
|
'teacher-workspace': '教师工作台',
|
|
'attendance-scope': '考勤数据范围',
|
|
room: '宿舍管理',
|
|
occupancy: '入住管理',
|
|
expense: '费用管理',
|
|
bill: '账单管理',
|
|
deposit: '押金管理',
|
|
classroom: '教室管理',
|
|
organization: '机构管理',
|
|
rental: '租赁订单',
|
|
log: '操作日志',
|
|
user: '用户管理',
|
|
role: '角色管理',
|
|
class: '班级管理',
|
|
schedule: '排课管理',
|
|
attendance: '考勤管理',
|
|
learning: '学习记录',
|
|
exam: '考试管理',
|
|
sync: '数据同步',
|
|
integration: '集成配置',
|
|
department: '部门管理',
|
|
notification: '通知中心',
|
|
profile: '个人资料',
|
|
ai: 'AI 模型配置',
|
|
};
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
api
|
|
.get('/rbac/permissions/tree')
|
|
.then((res: any) => setPermTree(res))
|
|
.catch((e: unknown) => {
|
|
const err = e as { message?: string };
|
|
message.error(err?.message || '加载权限失败');
|
|
})
|
|
.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 <Spin style={{ display: 'block', margin: '40px auto' }} />;
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
style={{
|
|
marginBottom: 16,
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
flexWrap: 'wrap',
|
|
gap: 8,
|
|
}}
|
|
>
|
|
<h2 style={{ margin: 0 }}>权限一览</h2>
|
|
<Input.Search
|
|
placeholder="搜索权限名称或编码"
|
|
aria-label="搜索权限名称或编码"
|
|
allowClear
|
|
style={{ width: 280 }}
|
|
onSearch={setSearch}
|
|
onChange={(e) => !e.target.value && setSearch('')}
|
|
/>
|
|
</div>
|
|
<Space orientation="vertical" style={{ width: '100%' }} size={16}>
|
|
{filteredTree.map((group) => (
|
|
<Card
|
|
key={group.group}
|
|
title={
|
|
<span style={{ fontWeight: 600 }}>
|
|
{groupNames[group.group] || group.group} ({group.permissions.length})
|
|
</span>
|
|
}
|
|
size="small"
|
|
>
|
|
<Space wrap>
|
|
{group.permissions.map((p) => (
|
|
<Tag key={p.id} color="blue" style={{ marginBottom: 8 }}>
|
|
{p.name}{' '}
|
|
<Tag color="geekblue" style={{ marginLeft: 4 }}>
|
|
{p.code}
|
|
</Tag>
|
|
</Tag>
|
|
))}
|
|
</Space>
|
|
</Card>
|
|
))}
|
|
{filteredTree.length === 0 && <Empty description="未找到匹配的权限" />}
|
|
</Space>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PermissionsPage;
|