feat: add practice session resume

This commit is contained in:
Codex
2026-06-29 15:19:35 +08:00
parent b96897b2be
commit 07a6edeea2
12 changed files with 291 additions and 12 deletions

View File

@@ -4,6 +4,7 @@ import { Button, Text, Textarea, View } from '@tarojs/components';
import { loadCollectionQuestions, loadQuestions } from '@/services/catalog';
import {
createPracticeSession,
loadPracticeSessionDetail,
submitAnswer,
submitPracticeSession,
toggleQuestionFavorite,
@@ -66,6 +67,27 @@ function answerLabel(state?: AnswerState) {
return state.selfJudged ? '已自评' : '已答';
}
function answersFromBackend(answers?: Record<string, AnswerResult>): Record<string, AnswerState> {
const result: Record<string, AnswerState> = {};
for (const [questionId, answer] of Object.entries(answers || {})) {
result[questionId] = {
selectedOptions: Array.isArray(answer.selectedOptions) ? answer.selectedOptions : [],
answerText: answer.answerText || '',
isCorrect: answer.isCorrect,
answeredAt: answer.answeredAt,
selfJudged: answer.selfJudged,
};
}
return result;
}
function secondsUntil(value?: string | null) {
if (!value) return null;
const parsed = new Date(value).getTime();
if (!Number.isFinite(parsed)) return null;
return Math.max(0, Math.ceil((parsed - Date.now()) / 1000));
}
export default function StudentPracticePage() {
const router = useRouter();
const params = router.params || {};
@@ -85,6 +107,7 @@ export default function StudentPracticePage() {
const [error, setError] = useState('');
useEffect(() => {
const practiceSessionId = params.practiceSessionId || undefined;
const collectionId = params.collectionId || undefined;
const body = {
blueprintId: params.blueprintId || undefined,
@@ -94,6 +117,27 @@ export default function StudentPracticePage() {
mode: params.mode || 'sequential',
questionLimit: 50,
};
if (practiceSessionId) {
loadPracticeSessionDetail(practiceSessionId)
.then(payload => {
const nextSession = payload.item;
const backendAnswers = answersFromBackend(nextSession.answersByQuestion);
setSession(nextSession);
setQuestions(nextSession.questions || []);
setAnswerByQuestion(backendAnswers);
const savedIndex = getStorage<number>(indexStorageKey(nextSession.id));
const firstUnanswered = (nextSession.questionIds || []).findIndex(questionId => !backendAnswers[questionId]);
setIndex(typeof savedIndex === 'number' ? savedIndex : Math.max(0, firstUnanswered));
const remaining = secondsUntil(nextSession.expiresAt);
if (remaining !== null) setTimeLeft(remaining);
if (nextSession.status === 'finished') setCompleted(true);
})
.catch(nextError => setError(nextError instanceof Error ? nextError.message : '练习恢复失败'))
.finally(() => setLoading(false));
return;
}
createPracticeSession(body)
.then(async payload => {
const nextSession = payload.item;