fix: align permission navigation and page access
This commit is contained in:
@@ -21,6 +21,7 @@ import api from '../../api';
|
||||
import { downloadBlob } from '../../utils/download';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
|
||||
interface UnavailableDatesResponse {
|
||||
dates: string[];
|
||||
@@ -29,6 +30,7 @@ export const unavailableDatesCacheKey = (classroomId: number, date: Dayjs) =>
|
||||
`${classroomId}:${date.format('YYYY-MM')}`;
|
||||
|
||||
const ClassroomRentalsPage: React.FC = () => {
|
||||
const { hasAnyPermission } = usePermission();
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [classrooms, setClassrooms] = useState<any[]>([]);
|
||||
const [organizations, setOrganizations] = useState<any[]>([]);
|
||||
@@ -82,8 +84,8 @@ const ClassroomRentalsPage: React.FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchMeta();
|
||||
}, []);
|
||||
if (hasAnyPermission('rental:create', 'rental:edit')) fetchMeta();
|
||||
}, [hasAnyPermission]);
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [filterMonth]);
|
||||
|
||||
@@ -21,6 +21,7 @@ import api from '../../api';
|
||||
import { maskPhone, maskIdNumber } from '../../utils/sensitive';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
|
||||
const statusMap: Record<string, { text: string; color: string }> = {
|
||||
paid: { text: '已缴', color: 'green' },
|
||||
@@ -51,6 +52,7 @@ interface PendingRefund {
|
||||
}
|
||||
|
||||
const DepositsPage: React.FC = () => {
|
||||
const { hasPermission } = usePermission();
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [students, setStudents] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -73,7 +75,10 @@ const DepositsPage: React.FC = () => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [d, s]: any[] = await Promise.all([api.get('/deposits'), api.get('/students')]);
|
||||
const [d, s]: any[] = await Promise.all([
|
||||
api.get('/deposits'),
|
||||
hasPermission('deposit:create') ? api.get('/deposits/student-lookups') : Promise.resolve([]),
|
||||
]);
|
||||
setData(d);
|
||||
setStudents(s);
|
||||
} catch (e: any) {
|
||||
@@ -95,7 +100,7 @@ const DepositsPage: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
}, [hasPermission]);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter((d: any) => {
|
||||
@@ -444,20 +449,22 @@ const DepositsPage: React.FC = () => {
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'pending',
|
||||
label: '待审批退款',
|
||||
children: (
|
||||
<Table
|
||||
columns={pendingColumns}
|
||||
dataSource={pendingRefunds}
|
||||
rowKey="id"
|
||||
loading={pendingLoading}
|
||||
scroll={{ x: 1200 }}
|
||||
pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(hasPermission('deposit:approve')
|
||||
? [{
|
||||
key: 'pending',
|
||||
label: '待审批退款',
|
||||
children: (
|
||||
<Table
|
||||
columns={pendingColumns}
|
||||
dataSource={pendingRefunds}
|
||||
rowKey="id"
|
||||
loading={pendingLoading}
|
||||
scroll={{ x: 1200 }}
|
||||
pagination={{ pageSize: 15, showTotal: (total) => `共 ${total} 条` }}
|
||||
/>
|
||||
),
|
||||
}]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
@@ -114,16 +114,15 @@ const ExpensesPage: React.FC = () => {
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [re, pe, rm, st]: any[] = await Promise.all([
|
||||
const [re, pe, lookups]: any[] = await Promise.all([
|
||||
api.get('/expenses/room'),
|
||||
api.get('/expenses/personal'),
|
||||
api.get('/rooms'),
|
||||
api.get('/students'),
|
||||
api.get('/expenses/lookups').catch(() => ({ rooms: [], students: [] })),
|
||||
]);
|
||||
setRoomExpenses(re);
|
||||
setPersonalExpenses(pe);
|
||||
setRooms(rm);
|
||||
setStudents(st);
|
||||
setRooms(lookups.rooms || []);
|
||||
setStudents(lookups.students || []);
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '加载失败,请稍后重试');
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import type { DataNode } from 'antd/es/tree';
|
||||
import type { TreeSelectProps } from 'antd/es/tree-select';
|
||||
import api from '../../api';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
|
||||
interface DingTalkConfig {
|
||||
agentId: string;
|
||||
@@ -63,6 +65,7 @@ interface ImportResult {
|
||||
}
|
||||
|
||||
const IntegrationConfigPage: React.FC = () => {
|
||||
const { hasAllPermissions } = usePermission();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
@@ -279,7 +282,7 @@ const IntegrationConfigPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const syncTabItems = config
|
||||
const syncTabItems = config && hasAllPermissions('sync:read', 'class:view', 'class:edit')
|
||||
? [
|
||||
{
|
||||
key: 'sync-users',
|
||||
@@ -461,9 +464,9 @@ const IntegrationConfigPage: React.FC = () => {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={handleSave}>
|
||||
<PermissionButton permission="integration:trigger" type="primary" icon={<SaveOutlined />} loading={saving} onClick={handleSave}>
|
||||
保存配置
|
||||
</Button>
|
||||
</PermissionButton>
|
||||
<Button icon={<ApiOutlined />} loading={testing} onClick={handleTest}>
|
||||
测试连接
|
||||
</Button>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import api from '../../api';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { writePermissions } from '../../auth/permission-store';
|
||||
import { findFirstAccessiblePath } from '../../auth/permission-navigation';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@@ -18,9 +19,10 @@ const LoginPage: React.FC = () => {
|
||||
const res: any = await api.post('/auth/login', values);
|
||||
localStorage.setItem('token', res.access_token);
|
||||
localStorage.setItem('user', JSON.stringify(res.user));
|
||||
writePermissions(res.user.permissions || []);
|
||||
const permissions = res.user.permissions || [];
|
||||
writePermissions(permissions);
|
||||
message.success('登录成功');
|
||||
navigate('/dashboard');
|
||||
navigate(findFirstAccessiblePath(permissions) || '/', { replace: true });
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '登录失败');
|
||||
} finally {
|
||||
|
||||
@@ -30,6 +30,17 @@ const PermissionsPage: React.FC = () => {
|
||||
log: '操作日志',
|
||||
user: '用户管理',
|
||||
role: '角色管理',
|
||||
class: '班级管理',
|
||||
schedule: '排课管理',
|
||||
attendance: '考勤管理',
|
||||
learning: '学习记录',
|
||||
exam: '考试管理',
|
||||
sync: '数据同步',
|
||||
integration: '集成配置',
|
||||
department: '部门管理',
|
||||
notification: '通知中心',
|
||||
profile: '个人资料',
|
||||
ai: 'AI 模型配置',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import api from '../../api';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { message } from '../../ui/app-message';
|
||||
import {
|
||||
buildSchedulePayload,
|
||||
@@ -99,6 +100,7 @@ const WEEKDAY_NUMBERS = [1, 2, 3, 4, 5, 6, 7];
|
||||
// ---- Component ----
|
||||
|
||||
const SchedulesPage: React.FC = () => {
|
||||
const { hasPermission } = usePermission();
|
||||
// View mode and navigation
|
||||
const [viewMode, setViewMode] = useState<'week' | 'month'>('week');
|
||||
const [viewDate, setViewDate] = useState<Dayjs>(() => dayjs().weekday(1).startOf('day'));
|
||||
@@ -235,9 +237,8 @@ const SchedulesPage: React.FC = () => {
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [classroomsRes, classesRes, schedulesRes] = await Promise.all([
|
||||
api.get('/classrooms') as Promise<ClassroomItem[]>,
|
||||
api.get('/classes') as Promise<ClassItem[]>,
|
||||
const [lookups, schedulesRes] = await Promise.all([
|
||||
api.get('/class-schedules/lookups') as Promise<{ classrooms: ClassroomItem[]; classes: ClassItem[] }>,
|
||||
api.get('/class-schedules/weekly', {
|
||||
params: {
|
||||
startDate: startDateStr,
|
||||
@@ -247,8 +248,8 @@ const SchedulesPage: React.FC = () => {
|
||||
}) as Promise<Record<string, Record<string, ClassScheduleItem[]>>>,
|
||||
]);
|
||||
|
||||
setClassrooms(classroomsRes);
|
||||
setClasses(classesRes);
|
||||
setClassrooms(lookups.classrooms);
|
||||
setClasses(lookups.classes);
|
||||
|
||||
// Convert string keys to numbers
|
||||
const typedMatrix: Record<number, Record<number, ClassScheduleItem[]>> = {};
|
||||
@@ -326,7 +327,7 @@ const SchedulesPage: React.FC = () => {
|
||||
setSelectedSchedules(schedules);
|
||||
setModalMode('detail');
|
||||
setModalOpen(true);
|
||||
} else {
|
||||
} else if (hasPermission('schedule:create')) {
|
||||
setSelectedSchedules([]);
|
||||
setEditingSchedule(null);
|
||||
setModalMode('create');
|
||||
@@ -948,7 +949,8 @@ const SchedulesPage: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 500 }}>已有排课</span>
|
||||
<Button
|
||||
<PermissionButton
|
||||
permission="schedule:create"
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
@@ -964,7 +966,7 @@ const SchedulesPage: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
新增排课
|
||||
</Button>
|
||||
</PermissionButton>
|
||||
</div>
|
||||
{selectedSchedules.length === 0 ? (
|
||||
<Empty description="该时段暂无排课" />
|
||||
|
||||
Reference in New Issue
Block a user