feat: improve attendance scheduling and API validation
This commit is contained in:
@@ -105,6 +105,20 @@ interface AttachmentRecord {
|
||||
fileSize: number;
|
||||
}
|
||||
|
||||
interface AttendanceRecordItem {
|
||||
id: number;
|
||||
attendanceDate: string;
|
||||
session: string;
|
||||
status: string;
|
||||
source?: string;
|
||||
remark?: string | null;
|
||||
punchTime?: string | null;
|
||||
punchDeviceName?: string | null;
|
||||
punchDeviceId?: string | null;
|
||||
schedule?: { subject?: string } | null;
|
||||
class?: { name?: string } | null;
|
||||
}
|
||||
|
||||
interface StudentProfileAggregate {
|
||||
student: StudentInfo;
|
||||
profile: ProfileData | null;
|
||||
@@ -113,6 +127,7 @@ interface StudentProfileAggregate {
|
||||
learningRecords: LearningRecord[];
|
||||
result: ResultData | null;
|
||||
attachments: AttachmentRecord[];
|
||||
attendances: AttendanceRecordItem[];
|
||||
}
|
||||
|
||||
export interface StudentProfileContentProps {
|
||||
@@ -178,6 +193,58 @@ const formatFileSize = (bytes: number): string => {
|
||||
|
||||
// ---- Tab Components ----
|
||||
|
||||
const ATTENDANCE_STATUS_MAP: Record<string, { text: string; color: string }> = {
|
||||
present: { text: '出勤', color: 'green' },
|
||||
late: { text: '迟到', color: 'orange' },
|
||||
absent: { text: '缺勤', color: 'red' },
|
||||
leave: { text: '请假', color: 'blue' },
|
||||
pending: { text: '待确认', color: 'default' },
|
||||
};
|
||||
|
||||
const SESSION_LABELS: Record<string, string> = {
|
||||
morning_reading: '早自习',
|
||||
morning: '上午',
|
||||
afternoon: '下午',
|
||||
evening_study: '晚自习',
|
||||
night_check: '晚寝',
|
||||
};
|
||||
|
||||
const AttendanceTab: React.FC<{ data: AttendanceRecordItem[] }> = ({ data }) => {
|
||||
const columns: ColumnsType<AttendanceRecordItem> = [
|
||||
{ title: '日期', dataIndex: 'attendanceDate', width: 120 },
|
||||
{ title: '课程', render: (_: unknown, record) => record.schedule?.subject || record.class?.name || '课程考勤' },
|
||||
{ title: '时段', dataIndex: 'session', width: 100, render: (value: string) => SESSION_LABELS[value] || value || '-' },
|
||||
{
|
||||
title: '结果', dataIndex: 'status', width: 90,
|
||||
render: (value: string) => {
|
||||
const meta = ATTENDANCE_STATUS_MAP[value] || { text: value || '-', color: 'default' };
|
||||
return <Tag color={meta.color}>{meta.text}</Tag>;
|
||||
},
|
||||
},
|
||||
{ title: '打卡时间', dataIndex: 'punchTime', width: 170, render: (value?: string | null) => value ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') : '-' },
|
||||
{
|
||||
title: '打卡设备',
|
||||
render: (_: unknown, record) => {
|
||||
const name = record.punchDeviceName?.trim();
|
||||
const id = record.punchDeviceId?.trim();
|
||||
if (name && id && name !== id) return `${name}(${id})`;
|
||||
return name || id || (record.source === 'manual' ? '老师手动标记' : '-');
|
||||
},
|
||||
},
|
||||
{ title: '备注', dataIndex: 'remark', render: (value?: string | null) => value || '-' },
|
||||
];
|
||||
|
||||
return data.length > 0 ? (
|
||||
<Table<AttendanceRecordItem>
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
scroll={{ x: 900 }}
|
||||
pagination={{ defaultPageSize: 15, showSizeChanger: true, pageSizeOptions: [15, 30, 50] }}
|
||||
/>
|
||||
) : <Empty description="暂无出勤记录" />;
|
||||
};
|
||||
|
||||
interface TabProps {
|
||||
studentId: number;
|
||||
onRefresh: () => void;
|
||||
@@ -779,7 +846,7 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
|
||||
|
||||
const tabItems = useMemo(() => {
|
||||
if (!aggregateData) return [];
|
||||
const { profile, enrollments, examScores, learningRecords, result, attachments } = aggregateData;
|
||||
const { profile, enrollments, examScores, learningRecords, result, attachments, attendances } = aggregateData;
|
||||
return [
|
||||
{
|
||||
key: 'profile',
|
||||
@@ -807,8 +874,8 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
|
||||
},
|
||||
{
|
||||
key: 'attendance',
|
||||
label: '出勤记录',
|
||||
children: <Empty description="暂无出勤记录" />,
|
||||
label: `出勤记录 (${attendances.length})`,
|
||||
children: <AttendanceTab data={attendances} />,
|
||||
},
|
||||
{
|
||||
key: 'learning',
|
||||
|
||||
Reference in New Issue
Block a user