feat(students): add multi-enrollment comparison view in student list expand row
This commit is contained in:
@@ -12,6 +12,10 @@ import {
|
|||||||
Popconfirm,
|
Popconfirm,
|
||||||
Upload,
|
Upload,
|
||||||
App,
|
App,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Card,
|
||||||
|
Descriptions,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
@@ -43,6 +47,25 @@ const statusMap: Record<string, { text: string; color: string }> = {
|
|||||||
archived: { text: '已归档', color: '#999' },
|
archived: { text: '已归档', color: '#999' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface EnrollmentInfo {
|
||||||
|
classId: number;
|
||||||
|
className: string;
|
||||||
|
classType: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
joinDate: string;
|
||||||
|
leaveDate: string;
|
||||||
|
status: string;
|
||||||
|
attendanceStats: {
|
||||||
|
total: number;
|
||||||
|
present: number;
|
||||||
|
absent: number;
|
||||||
|
late: number;
|
||||||
|
leave: number;
|
||||||
|
rate: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const StudentsPage: React.FC = () => {
|
const StudentsPage: React.FC = () => {
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
@@ -54,6 +77,7 @@ const StudentsPage: React.FC = () => {
|
|||||||
const [showArchived, setShowArchived] = useState(false);
|
const [showArchived, setShowArchived] = useState(false);
|
||||||
const [archivedCount, setArchivedCount] = useState(0);
|
const [archivedCount, setArchivedCount] = useState(0);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
||||||
|
const [enrollmentData, setEnrollmentData] = useState<Record<number, EnrollmentInfo[]>>({});
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const handleViewSensitive = (studentId: number, field: string, value: string) => {
|
const handleViewSensitive = (studentId: number, field: string, value: string) => {
|
||||||
@@ -395,7 +419,59 @@ const StudentsPage: React.FC = () => {
|
|||||||
onChange: (keys) => setSelectedRowKeys(keys as number[]),
|
onChange: (keys) => setSelectedRowKeys(keys as number[]),
|
||||||
getCheckboxProps: (record: any) => ({ disabled: record.status === 'archived' }),
|
getCheckboxProps: (record: any) => ({ disabled: record.status === 'archived' }),
|
||||||
}}
|
}}
|
||||||
/>
|
expandable={{
|
||||||
|
rowExpandable: () => true,
|
||||||
|
expandedRowRender: (record) => {
|
||||||
|
const enrollments = enrollmentData[record.id];
|
||||||
|
if (!enrollments) return null;
|
||||||
|
if (enrollments.length < 2) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 8, color: '#999', fontSize: 13 }}>
|
||||||
|
当前仅 {enrollments.length} 个班型,无可对比数据
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Card title="多班型对比" size="small" style={{ margin: '8px 0' }}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
{enrollments.map((enr, idx) => (
|
||||||
|
<Col span={12} key={enr.classId}>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title={enr.classType || `班型 ${idx + 1}`}
|
||||||
|
style={{ background: idx === 0 ? '#f0f5ff' : '#f6ffed' }}
|
||||||
|
>
|
||||||
|
<Descriptions column={1} size="small">
|
||||||
|
<Descriptions.Item label="班级">{enr.className || '-'}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="开班日期">{enr.startDate || enr.joinDate || '-'}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="结课日期">{enr.endDate || enr.leaveDate || '-'}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="状态">
|
||||||
|
<Tag color={enr.status === 'active' ? 'green' : 'default'}>
|
||||||
|
{enr.status || '-'}
|
||||||
|
</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onExpand: async (expanded, record) => {
|
||||||
|
if (expanded && !enrollmentData[record.id]) {
|
||||||
|
try {
|
||||||
|
const res = await api.get<{ enrollments: EnrollmentInfo[] }>(
|
||||||
|
`/students/${record.id}/compare-classes`,
|
||||||
|
);
|
||||||
|
setEnrollmentData((prev) => ({ ...prev, [record.id]: res.enrollments }));
|
||||||
|
} catch {
|
||||||
|
setEnrollmentData((prev) => ({ ...prev, [record.id]: [] }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<style>{`.archived-row { opacity: 0.6; background: #fafafa !important; }`}</style>
|
<style>{`.archived-row { opacity: 0.6; background: #fafafa !important; }`}</style>
|
||||||
<Modal
|
<Modal
|
||||||
title={editing ? '编辑学生' : '添加学生'}
|
title={editing ? '编辑学生' : '添加学生'}
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ export class StudentsService {
|
|||||||
classId: e.classId,
|
classId: e.classId,
|
||||||
className: e.class?.name || '',
|
className: e.class?.name || '',
|
||||||
classType: e.class?.classType || '',
|
classType: e.class?.classType || '',
|
||||||
|
startDate: e.class?.startDate || '',
|
||||||
|
endDate: e.class?.endDate || '',
|
||||||
joinDate: e.joinDate,
|
joinDate: e.joinDate,
|
||||||
leaveDate: e.leaveDate,
|
leaveDate: e.leaveDate,
|
||||||
status: e.status,
|
status: e.status,
|
||||||
|
|||||||
Reference in New Issue
Block a user