forked from wangziqi/gongxue-base
86 lines
4.0 KiB
TypeScript
86 lines
4.0 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import Taro, { useRouter } from '@tarojs/taro';
|
||
import { Button, Text, View } from '@tarojs/components';
|
||
import { loadPracticeReports, loadPracticeSessionReport, type PracticeReport } from '@/services/learning';
|
||
import '../student.css';
|
||
|
||
function percent(value?: number) {
|
||
return `${Math.round((value || 0) * 100)}%`;
|
||
}
|
||
|
||
export default function StudentReportsPage() {
|
||
const router = useRouter();
|
||
const practiceSessionId = router.params?.practiceSessionId || '';
|
||
const [report, setReport] = useState<PracticeReport | null>(null);
|
||
const [reports, setReports] = useState<PracticeReport[]>([]);
|
||
const [error, setError] = useState('');
|
||
|
||
useEffect(() => {
|
||
if (practiceSessionId) {
|
||
loadPracticeSessionReport(practiceSessionId)
|
||
.then(payload => setReport(payload.item || null))
|
||
.catch(nextError => setError(nextError instanceof Error ? nextError.message : '报告加载失败'));
|
||
return;
|
||
}
|
||
loadPracticeReports({ limit: 20 })
|
||
.then(payload => setReports(payload.items || []))
|
||
.catch(nextError => setError(nextError instanceof Error ? nextError.message : '报告列表加载失败'));
|
||
}, [practiceSessionId]);
|
||
|
||
const current = report || reports[0] || null;
|
||
|
||
return (
|
||
<View className='student-page'>
|
||
<View className='student-topbar'>
|
||
<View className='student-title-block'>
|
||
<Text className='student-kicker'>Report</Text>
|
||
<Text className='student-title'>练习报告</Text>
|
||
<Text className='student-subtitle'>分数、正确率、题型分段和错题明细均以后端 session 快照评分为准。</Text>
|
||
</View>
|
||
<Button className='secondary-button' onClick={() => Taro.navigateBack()}>返回</Button>
|
||
</View>
|
||
|
||
{current ? (
|
||
<View className='report-panel'>
|
||
<Text className='metric-value'>{String(current.score)} / {String(current.totalScore)}</Text>
|
||
<Text className='metric-label'>{current.mode} · 正确率 {percent(current.accuracy)} · 已答 {current.answeredCount}/{current.totalQuestions}</Text>
|
||
<View className='grid-two section-block'>
|
||
<View className='metric'><Text className='metric-value'>{String(current.correctCount)}</Text><Text className='metric-label'>正确</Text></View>
|
||
<View className='metric'><Text className='metric-value'>{String(current.wrongCount)}</Text><Text className='metric-label'>错误</Text></View>
|
||
</View>
|
||
</View>
|
||
) : <View className='empty-state'>暂无练习报告,完成一次练习后会在这里展示。</View>}
|
||
|
||
{current?.sectionStats?.length ? (
|
||
<View className='section-block'>
|
||
<Text className='section-heading'>分段统计</Text>
|
||
<View className='list-stack'>
|
||
{current.sectionStats.map((section, index) => (
|
||
<View className='list-row' key={String(section.key || index)}>
|
||
<Text className='row-main'>{String(section.title || section.key || '分段')}</Text>
|
||
<Text className='row-meta'>得分 {String(section.score || 0)} / {String(section.totalScore || 0)} · 正确 {String(section.correctCount || 0)} / {String(section.questionCount || 0)}</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
) : null}
|
||
|
||
{!practiceSessionId && reports.length > 1 ? (
|
||
<View className='section-block'>
|
||
<Text className='section-heading'>历史报告</Text>
|
||
<View className='list-stack'>
|
||
{reports.slice(1).map(item => (
|
||
<View className='list-row' key={item.id} onClick={() => setReport(item)}>
|
||
<Text className='row-main'>{item.mode} · {String(item.score)} / {String(item.totalScore)}</Text>
|
||
<Text className='row-meta'>正确率 {percent(item.accuracy)} · {item.submittedAt ? item.submittedAt.slice(0, 10) : ''}</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
) : null}
|
||
|
||
{error ? <Text className='error-text'>{error}</Text> : null}
|
||
</View>
|
||
);
|
||
}
|