forked from wangziqi/gongxue-base
103 lines
4.2 KiB
TypeScript
103 lines
4.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import Taro, { useRouter } from '@tarojs/taro';
|
||
import { Button, Text, View } from '@tarojs/components';
|
||
import {
|
||
loadFavoriteQuestions,
|
||
loadWrongQuestionReviewPlan,
|
||
resolveWrongQuestion,
|
||
type QuestionItem,
|
||
type WrongQuestionReviewItem,
|
||
} from '@/services/learning';
|
||
import '../student.css';
|
||
|
||
type ReviewMode = 'wrong' | 'favorite';
|
||
|
||
function questionIdOf(item: QuestionItem | WrongQuestionReviewItem) {
|
||
return 'questionId' in item && item.questionId ? item.questionId : (item as QuestionItem).id;
|
||
}
|
||
|
||
export default function StudentReviewPage() {
|
||
const router = useRouter();
|
||
const initialMode = router.params?.type === 'favorite' ? 'favorite' : 'wrong';
|
||
const [mode, setMode] = useState<ReviewMode>(initialMode);
|
||
const [wrongItems, setWrongItems] = useState<WrongQuestionReviewItem[]>([]);
|
||
const [favoriteItems, setFavoriteItems] = useState<QuestionItem[]>([]);
|
||
const [error, setError] = useState('');
|
||
|
||
function reload(nextMode = mode) {
|
||
setError('');
|
||
if (nextMode === 'wrong') {
|
||
loadWrongQuestionReviewPlan(30)
|
||
.then(payload => setWrongItems(payload.items || []))
|
||
.catch(nextError => setError(nextError instanceof Error ? nextError.message : '错题加载失败'));
|
||
return;
|
||
}
|
||
loadFavoriteQuestions(50)
|
||
.then(payload => setFavoriteItems(payload.items || []))
|
||
.catch(nextError => setError(nextError instanceof Error ? nextError.message : '收藏加载失败'));
|
||
}
|
||
|
||
useEffect(() => {
|
||
reload(mode);
|
||
}, [mode]);
|
||
|
||
function startReview() {
|
||
const reviewMode = mode === 'wrong' ? 'wrong_review' : 'favorite_review';
|
||
Taro.navigateTo({ url: `/pages/student/practice/index?mode=${reviewMode}&questionLimit=30` });
|
||
}
|
||
|
||
async function markResolved(questionId: string) {
|
||
try {
|
||
await resolveWrongQuestion(questionId);
|
||
Taro.showToast({ title: '已移出错题', icon: 'success' });
|
||
reload('wrong');
|
||
} catch (nextError) {
|
||
setError(nextError instanceof Error ? nextError.message : '移出错题失败');
|
||
}
|
||
}
|
||
|
||
const items = mode === 'wrong' ? wrongItems : favoriteItems;
|
||
|
||
return (
|
||
<View className='student-page review-page'>
|
||
<View className='student-topbar'>
|
||
<View className='student-title-block'>
|
||
<Text className='student-kicker'>Review</Text>
|
||
<Text className='student-title'>{mode === 'wrong' ? '错题本' : '收藏夹'}</Text>
|
||
<Text className='student-subtitle'>复习组卷由后端按当前用户错题/收藏安全生成,前端不传题目列表。</Text>
|
||
</View>
|
||
<Button className='secondary-button' onClick={() => Taro.navigateBack()}>返回</Button>
|
||
</View>
|
||
|
||
<View className='toolbar wrap'>
|
||
<Button className={`pill-button ${mode === 'wrong' ? 'active' : ''}`} onClick={() => setMode('wrong')}>错题</Button>
|
||
<Button className={`pill-button ${mode === 'favorite' ? 'active' : ''}`} onClick={() => setMode('favorite')}>收藏</Button>
|
||
<Button className='primary-button' onClick={startReview}>开始复习</Button>
|
||
</View>
|
||
|
||
<View className='section-block'>
|
||
<Text className='section-heading'>{mode === 'wrong' ? '建议复习' : '收藏题目'}</Text>
|
||
<View className='list-stack'>
|
||
{items.map(item => {
|
||
const questionId = questionIdOf(item);
|
||
return (
|
||
<View className='list-row' key={questionId}>
|
||
<Text className='row-main'>{item.content || '未提供题干'}</Text>
|
||
<Text className='row-meta'>{item.typeLabel || item.type || '题目'}{mode === 'wrong' ? ` · 错 ${String((item as WrongQuestionReviewItem).wrongCount || 0)} 次` : ''}</Text>
|
||
{mode === 'wrong' ? (
|
||
<View className='toolbar'>
|
||
<Button className='secondary-button' onClick={() => markResolved(questionId)}>移出错题</Button>
|
||
</View>
|
||
) : null}
|
||
</View>
|
||
);
|
||
})}
|
||
</View>
|
||
{!items.length ? <View className='empty-state'>{mode === 'wrong' ? '暂无待复习错题。' : '暂无收藏题目。'}</View> : null}
|
||
</View>
|
||
|
||
{error ? <Text className='error-text'>{error}</Text> : null}
|
||
</View>
|
||
);
|
||
}
|