feat: 学生档案与报告生成

This commit is contained in:
2026-08-05 17:11:15 +08:00
parent 0e6e3e2d96
commit c622e40a12
23 changed files with 2491 additions and 2148 deletions

View File

@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useParams, useNavigate } from 'react-router';
import { Card, Button, Space } from 'antd';
import { ArrowLeftOutlined, EyeOutlined } from '@ant-design/icons';
import StudentProfileContent from '../../components/StudentProfileContent';

View File

@@ -35,22 +35,47 @@ describe('归档数据视图', () => {
});
it('正常与归档视图的批量动作互斥,且归档视图只读', () => {
expect(archiveViewPolicy('active')).toEqual({ batchAction: 'archive', readonly: false });
expect(archiveViewPolicy('archived')).toEqual({ batchAction: 'restore', readonly: true });
expect(archiveViewPolicy('active')).toEqual({
batchAction: 'archive',
readonly: false,
purgeBatch: false,
});
expect(archiveViewPolicy('archived')).toEqual({
batchAction: 'restore',
readonly: true,
purgeBatch: true,
});
});
it('入住三态分别只提供退宿、归档和恢复动作', () => {
expect(occupancyViewPolicy('active')).toEqual({
batchAction: 'checkout',
readonly: false,
purgeBatch: false,
});
expect(occupancyViewPolicy('all')).toEqual({
batchAction: 'archive',
readonly: false,
purgeBatch: false,
});
expect(occupancyViewPolicy('all')).toEqual({ batchAction: 'archive', readonly: false });
expect(occupancyViewPolicy('archived')).toEqual({
batchAction: 'restore',
readonly: true,
purgeBatch: true,
});
});
it('批量删除只出现在归档视图,且与批量恢复互斥', () => {
expect(archiveViewPolicy('active').purgeBatch).toBe(false);
expect(archiveViewPolicy('archived').purgeBatch).toBe(true);
expect(occupancyViewPolicy('active').purgeBatch).toBe(false);
expect(occupancyViewPolicy('all').purgeBatch).toBe(false);
expect(occupancyViewPolicy('archived').purgeBatch).toBe(true);
// 归档视图中批量动作固定为恢复,不会同时出现归档;批量删除只在归档视图开启
expect(archiveViewPolicy('archived').batchAction).toBe('restore');
expect(occupancyViewPolicy('archived').batchAction).toBe('restore');
});
it('只有实际切换视图时才要求清空选择', () => {
expect(shouldClearSelectionOnViewChange('active', 'archived')).toBe(true);
expect(shouldClearSelectionOnViewChange('archived', 'archived')).toBe(false);

View File

@@ -5,6 +5,8 @@ export type BatchAction = 'archive' | 'restore' | 'checkout';
export interface ViewPolicy {
batchAction: BatchAction;
readonly: boolean;
/** 批量永久删除只在已归档视图中出现,与批量恢复互斥 */
purgeBatch: boolean;
}
export const selectArchiveRecords = <T extends { status?: string }>(
@@ -20,11 +22,13 @@ export const expenseStatusForView = (view: ArchiveView) => view;
export const archiveViewPolicy = (view: ArchiveView): ViewPolicy => ({
batchAction: view === 'archived' ? 'restore' : 'archive',
readonly: view === 'archived',
purgeBatch: view === 'archived',
});
export const occupancyViewPolicy = (view: OccupancyView): ViewPolicy => ({
batchAction: view === 'active' ? 'checkout' : view === 'all' ? 'archive' : 'restore',
readonly: view === 'archived',
purgeBatch: view === 'archived',
});
export const shouldClearSelectionOnViewChange = <T extends string>(current: T, next: T) =>