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,10 +1,11 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DatePicker, Input, InputNumber, Select, Spin, Tooltip } from 'antd';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import dayjs, { type Dayjs } from 'dayjs';
import equal from 'fast-deep-equal';
import { usePermission } from '../../hooks/usePermission';
import { message } from '../../ui/app-message';
import './style.css';
import { getErrorMessage } from '../../utils/error';
export type EditableCellEditor =
| 'text'
@@ -66,7 +67,7 @@ export function serializeEditableValue(value: unknown, editor: EditableCellEdito
}
export function editableValuesEqual(left: unknown, right: unknown) {
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
return equal(left ?? null, right ?? null);
}
function isEditorOverlay(target: EventTarget | null) {
@@ -112,43 +113,40 @@ const EditableCell = <Value,>({
[editor, formatValue, value],
);
useEffect(() => {
if (!editing) {
setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
}
}, [editing, editor, formatValue, value]);
const cancel = useCallback(() => {
setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
if (activeCell?.id === idRef.current) activeCell = null;
setEditing(false);
}, [editor, formatValue, value]);
const saveValue = useCallback(async (nextDraft: unknown) => {
if (saving) return false;
const serialized = serializeEditableValue(nextDraft, editor);
if (required && (serialized === '' || serialized === undefined || serialized === null)) {
message.error('该字段不能为空');
return false;
}
if (editableValuesEqual(serialized, original)) {
if (activeCell?.id === idRef.current) activeCell = null;
setEditing(false);
return true;
}
setSaving(true);
try {
await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
if (activeCell?.id === idRef.current) activeCell = null;
setEditing(false);
return true;
} catch (error) {
message.error((error as { message?: string })?.message || '保存失败');
return false;
} finally {
setSaving(false);
}
}, [editor, onSave, original, parseValue, required, saving]);
const saveValue = useCallback(
async (nextDraft: unknown) => {
if (saving) return false;
const serialized = serializeEditableValue(nextDraft, editor);
if (required && (serialized === '' || serialized === undefined || serialized === null)) {
message.error('该字段不能为空');
return false;
}
if (editableValuesEqual(serialized, original)) {
if (activeCell?.id === idRef.current) activeCell = null;
setEditing(false);
return true;
}
setSaving(true);
try {
await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
if (activeCell?.id === idRef.current) activeCell = null;
setEditing(false);
return true;
} catch (error) {
message.error(getErrorMessage(error, '保存失败'));
return false;
} finally {
setSaving(false);
}
},
[editor, onSave, original, parseValue, required, saving],
);
const save = useCallback(() => saveValue(draft), [draft, saveValue]);
@@ -199,6 +197,7 @@ const EditableCell = <Value,>({
if (!saved) return;
}
activeCell = { id: idRef.current, save };
setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
setEditing(true);
};