@@ -215,7 +215,9 @@ const getStudentStatus = (value?: string | null): { text: string; color: string
const formatEnrollmentDisplayName = ( enrollment : EnrollmentRecord ) : string = >
enrollment . className ||
( enrollment . courseCategory ? getCourseCategoryLabel ( enrollment . courseCategory ) : String ( enrollment . id ) ) ;
( enrollment . courseCategory
? getCourseCategoryLabel ( enrollment . courseCategory )
: String ( enrollment . id ) ) ;
const ATTACHMENT_CATEGORY_OPTIONS = [
{ value : 'id_card' , label : '身份证' } ,
@@ -253,16 +255,31 @@ const SESSION_LABELS: Record<string, string> = {
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 ,
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 : '打卡时间' ,
dataIndex : 'punchTime' ,
width : 170 ,
render : ( value? : string | null ) = > ( value ? dayjs ( value ) . format ( 'YYYY-MM-DD HH:mm:ss' ) : '-' ) ,
} ,
{
title : '打卡设备' ,
render : ( _ : unknown , record ) = > {
@@ -283,7 +300,9 @@ const AttendanceTab: React.FC<{ data: AttendanceRecordItem[] }> = ({ data }) =>
scroll = { { x : 900 } }
pagination = { { defaultPageSize : 15 , showSizeChanger : true , pageSizeOptions : [ 15 , 30 , 50 ] } }
/ >
) : < Empty description = "暂无出勤记录" / > ;
) : (
< Empty description = "暂无出勤记录" / >
) ;
} ;
interface TabProps {
@@ -291,11 +310,11 @@ interface TabProps {
onRefresh : ( ) = > void ;
}
const ProfileTab : React.FC < { data : ProfileData | null ; studentId : number ; onRefresh : ( ) = > void } > = ( {
data ,
studentId ,
onRefresh ,
} ) = > {
const ProfileTab : React.FC < {
data : ProfileData | null ;
studentId : number ;
onRefresh : ( ) = > void ;
} > = ( { data , studentId , onRefresh } ) = > {
const [ form ] = Form . useForm ( ) ;
const [ saving , setSaving ] = useState ( false ) ;
@@ -439,10 +458,18 @@ const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> = ({
confirmLoading = { saving }
>
< Form form = { form } layout = "vertical" >
< Form.Item name = "courseCategory" label = "课程类别" rules = { [ { required : true , message : '请选择课程类别' } ] } >
< Form.Item
name = "courseCategory"
label = "课程类别"
rules = { [ { required : true , message : '请选择课程类别' } ] }
>
< Select options = { COURSE_CATEGORY_OPTIONS } placeholder = "请选择" / >
< / Form.Item >
< Form.Item name = "classType" label = "班型" rules = { [ { required : true , message : '请选择班型' } ] } >
< Form.Item
name = "classType"
label = "班型"
rules = { [ { required : true , message : '请选择班型' } ] }
>
< Select options = { CLASS_TYPE_OPTIONS } placeholder = "请选择" / >
< / Form.Item >
< Form.Item name = "className" label = "班级名称" >
@@ -466,12 +493,9 @@ const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> = ({
) ;
} ;
const ExamScoresTab : React.FC <TabProps & { data : ExamScoreRecord [ ] ; enrollments : EnrollmentRecord [ ] } > = ( {
data ,
studentId ,
enrollments ,
onRefresh ,
} ) = > {
const ExamScoresTab : React.FC <
TabProps & { data : ExamScoreRecord [ ] ; enrollments : EnrollmentRecord [ ] }
> = ( { data , studentId , enrollments , onRefresh } ) = > {
const [ modalOpen , setModalOpen ] = useState ( false ) ;
const [ form ] = Form . useForm ( ) ;
const [ saving , setSaving ] = useState ( false ) ;
@@ -505,8 +529,16 @@ const ExamScoresTab: React.FC<TabProps & { data: ExamScoreRecord[]; enrollments:
{ title : '考试名称' , dataIndex : 'examName' , render : ( v : string ) = > v || '-' } ,
{ title : '科目' , dataIndex : 'subject' } ,
{ title : '成绩' , dataIndex : 'score' } ,
{ title : '班级均分' , dataIndex : 'classAvg' , render : ( v : number | undefined ) = > ( v !== undefined ? v : '-' ) } ,
{ title : '排名' , dataIndex : 'rank' , render : ( v : number | undefined ) = > ( v !== undefined ? v : '-' ) } ,
{
title : '班级均分' ,
dataIndex : 'classAvg' ,
render : ( v : number | undefined ) = > ( v !== undefined ? v : '-' ) ,
} ,
{
title : '排名' ,
dataIndex : 'rank' ,
render : ( v : number | undefined ) = > ( v !== undefined ? v : '-' ) ,
} ,
{ title : '考试日期' , dataIndex : 'examDate' , render : ( v : string ) = > v || '-' } ,
{
title : '关联报读' ,
@@ -550,13 +582,21 @@ const ExamScoresTab: React.FC<TabProps & { data: ExamScoreRecord[]; enrollments:
confirmLoading = { saving }
>
< Form form = { form } layout = "vertical" >
< Form.Item name = "examType" label = "考试类型" rules = { [ { required : true , message : '请选择考试类型' } ] } >
< Form.Item
name = "examType"
label = "考试类型"
rules = { [ { required : true , message : '请选择考试类型' } ] }
>
< Select options = { EXAM_TYPE_OPTIONS } placeholder = "请选择" / >
< / Form.Item >
< Form.Item name = "examName" label = "考试名称" >
< Input placeholder = "如: 2024第一次月考" / >
< / Form.Item >
< Form.Item name = "subject" label = "科目" rules = { [ { required : true , message : '请输入科目' } ] } >
< Form.Item
name = "subject"
label = "科目"
rules = { [ { required : true , message : '请输入科目' } ] }
>
< Input placeholder = "如:数学" / >
< / Form.Item >
< Form.Item name = "score" label = "成绩" rules = { [ { required : true , message : '请输入成绩' } ] } >
@@ -587,7 +627,11 @@ const ExamScoresTab: React.FC<TabProps & { data: ExamScoreRecord[]; enrollments:
) ;
} ;
const LearningTab : React.FC < TabProps & { data : LearningRecord [ ] } > = ( { data , studentId , onRefresh } ) = > {
const LearningTab : React.FC < TabProps & { data : LearningRecord [ ] } > = ( {
data ,
studentId ,
onRefresh ,
} ) = > {
const [ modalOpen , setModalOpen ] = useState ( false ) ;
const [ form ] = Form . useForm ( ) ;
const [ saving , setSaving ] = useState ( false ) ;
@@ -655,13 +699,25 @@ const LearningTab: React.FC<TabProps & { data: LearningRecord[] }> = ({ data, st
confirmLoading = { saving }
>
< Form form = { form } layout = "vertical" >
< Form.Item name = "recordDate" label = "记录日期" rules = { [ { required : true , message : '请选择日期' } ] } >
< Form.Item
name = "recordDate"
label = "记录日期"
rules = { [ { required : true , message : '请选择日期' } ] }
>
< DatePicker style = { { width : '100%' } } / >
< / Form.Item >
< Form.Item name = "recordType" label = "记录类型" rules = { [ { required : true , message : '请选择记录类型' } ] } >
< Form.Item
name = "recordType"
label = "记录类型"
rules = { [ { required : true , message : '请选择记录类型' } ] }
>
< Select options = { RECORD_TYPE_OPTIONS } placeholder = "请选择" / >
< / Form.Item >
< Form.Item name = "content" label = "内容" rules = { [ { required : true , message : '请输入内容' } ] } >
< Form.Item
name = "content"
label = "内容"
rules = { [ { required : true , message : '请输入内容' } ] }
>
< Input.TextArea rows = { 4 } placeholder = "请记录学情内容" / >
< / Form.Item >
< Form.Item name = "followUpMethod" label = "跟进方式" >
@@ -676,7 +732,11 @@ const LearningTab: React.FC<TabProps & { data: LearningRecord[] }> = ({ data, st
) ;
} ;
const ResultTab : React.FC < TabProps & { data : ResultData | null } > = ( { data , studentId , onRefresh } ) = > {
const ResultTab : React.FC < TabProps & { data : ResultData | null } > = ( {
data ,
studentId ,
onRefresh ,
} ) = > {
const [ form ] = Form . useForm ( ) ;
const [ saving , setSaving ] = useState ( false ) ;
@@ -741,7 +801,11 @@ const ResultTab: React.FC<TabProps & { data: ResultData | null }> = ({ data, stu
) ;
} ;
const AttachmentsTab : React.FC < TabProps & { data : AttachmentRecord [ ] } > = ( { data , studentId , onRefresh } ) = > {
const AttachmentsTab : React.FC < TabProps & { data : AttachmentRecord [ ] } > = ( {
data ,
studentId ,
onRefresh ,
} ) = > {
const [ uploading , setUploading ] = useState ( false ) ;
const handleDelete = async ( attachmentId : number ) = > {
@@ -802,7 +866,12 @@ const AttachmentsTab: React.FC<TabProps & { data: AttachmentRecord[] }> = ({ dat
showUploadList = { false }
customRequest = { async ( options ) = > {
const formData = new FormData ( ) ;
formData . append ( 'file' , options . file instanceof File ? options.file : new File ( [ options . file as Blob ] , 'attachment' ) ) ;
formData . append (
'file' ,
options . file instanceof File
? options.file
: new File ( [ options . file as Blob ] , 'attachment' ) ,
) ;
setUploading ( true ) ;
try {
await api . post ( ` /archive/ ${ studentId } /attachments ` , formData , {
@@ -883,63 +952,60 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
const tabItems = useMemo ( ( ) = > {
if ( ! aggregateData ) return [ ] ;
const { profile , enrollments , examScores , learningRecords , result , attachments , attendances } = aggregateData ;
const { profile , enrollments , examScores , learningRecords , result , attachments , attendances } =
aggregateData ;
return [
{
key : 'profile' ,
label : '扩展档案' ,
children : < ProfileTab data = { profile } studentId = { studentId } onRefresh = { fetchData } / > ,
} ,
{
key : 'enrollments' ,
label : ` 报读班型 ( ${ enrollments . length } ) ` ,
children : (
< EnrollmentsTab data = { enrollments } studentId = { studentId } onRefresh = { fetchData } / >
) ,
} ,
{
key : 'exams' ,
label : ` 考试成绩 ( ${ examScores . length } ) ` ,
children : (
< ExamScoresTab
data = { examScore s}
studentId = { studentId }
enrollments = { enrollments }
onRefresh = { fetchData }
/ >
) ,
} ,
{
key : 'attendance' ,
label : ` 出勤记录 ( ${ attendances . length } ) ` ,
children : < AttendanceTab data = { attendances } / > ,
} ,
{
key : 'learning' ,
label : ` 课堂回访 ( ${ learningRecords . length } ) ` ,
children : (
< LearningTab data = { learningRecords } studentId = { studentId } onRefresh = { fetchData } / >
) ,
} ,
{
key : 'result' ,
label : '录取归档' ,
children : < ResultTab data = { result } studentId = { studentId } onRefresh = { fetchData } / > ,
} ,
{
key : 'attachments' ,
label : ` 附件 ( ${ attachments . length } ) ` ,
children : (
< AttachmentsTab data = { attachments } studentId = { studentId } onRefresh = { fetchData } / >
) ,
} ,
{
key : 'reports' ,
label : '报告版本' ,
children : < Empty description = "暂无报告版本" / > ,
} ,
] ;
} , [ aggregateData , studentId , fetchData ] ) ;
{
key : 'profile' ,
label : '扩展档案' ,
children : < ProfileTab data = { profile } studentId = { studentId } onRefresh = { fetchData } / > ,
} ,
{
key : 'enrollments' ,
label : ` 报读班型 ( ${ enrollments . length } ) ` ,
children : < EnrollmentsTab data = { enrollments } studentId = { studentId } onRefresh = { fetchData } / > ,
} ,
{
key : 'exams' ,
label : ` 考试成绩 ( ${ examScores . length } ) ` ,
children : (
< ExamScoresTab
data = { examScores }
studentId = { studentId }
enrollments = { enrollment s }
onRefresh = { fetchData }
/ >
) ,
} ,
{
key : 'attendance' ,
label : ` 出勤记录 ( ${ attendances . length } ) ` ,
children : < AttendanceTab data = { attendances } / > ,
} ,
{
key : 'learning' ,
label : ` 课堂回访 ( ${ learningRecords . length } ) ` ,
children : (
< LearningTab data = { learningRecords } studentId = { studentId } onRefresh = { fetchData } / >
) ,
} ,
{
key : 'result' ,
label : '录取归档' ,
children : < ResultTab data = { result } studentId = { studentId } onRefresh = { fetchData } / > ,
} ,
{
key : 'attachments' ,
label : ` 附件 ( ${ attachments . length } ) ` ,
children : < AttachmentsTab data = { attachments } studentId = { studentId } onRefresh = { fetchData } / > ,
} ,
{
key : 'reports' ,
label : '报告版本' ,
children : < Empty description = "暂无报告版本" / > ,
} ,
] ;
} , [ aggregateData , studentId , fetchData ] ) ;
if ( ! aggregateData ) {
if ( loading ) {
@@ -1000,7 +1066,9 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
< EyeOutlined style = { { fontSize : 12 , color : '#999' } } / >
< / a >
< / span >
) : '-' }
) : (
'-'
) }
< / Descriptions.Item >
< Descriptions.Item label = "身份证号" >
{ student . idNumber ? (
@@ -1010,7 +1078,9 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
< EyeOutlined style = { { fontSize : 12 , color : '#999' } } / >
< / a >
< / span >
) : '-' }
) : (
'-'
) }
< / Descriptions.Item >
< Descriptions.Item label = "状态" >
{ ( ( ) = > {
@@ -1024,18 +1094,13 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
{ profile ? . targetMajor && (
< Descriptions.Item label = "目标专业" > { profile . targetMajor } < / Descriptions.Item >
) }
{ profile ? . grade && (
< Descriptions.Item label = "年级" > { profile . grade } < / Descriptions.Item >
) }
{ profile ? . grade && < Descriptions.Item label = "年级" > { profile . grade } < / Descriptions.Item > }
{ profile ? . subjectDirection && (
< Descriptions.Item label = "选科方向" > { profile . subjectDirection } < / Descriptions.Item >
) }
< / Descriptions >
< Tabs
defaultActiveKey = "profile"
items = { tabItems }
/ >
< Tabs defaultActiveKey = "profile" items = { tabItems } / >
< / div >
) ;
} ;