fix: stabilize performance domain practice ui

This commit is contained in:
ittoview
2026-05-13 18:37:56 +01:00
parent f5d0e5bc0c
commit 67436d20fc

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react' import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { AnimatePresence, motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import clsx from 'clsx' import clsx from 'clsx'
import { import {
@@ -55,8 +55,6 @@ interface CircularProgressProps {
} }
const STORAGE_KEY = 'performance-domain-practice-progress-v2' const STORAGE_KEY = 'performance-domain-practice-progress-v2'
const CORRECT_DELAY = 900
const WRONG_DELAY = 1800
const scopeOptions: Array<{ value: PracticeScope; label: string }> = [ const scopeOptions: Array<{ value: PracticeScope; label: string }> = [
{ value: 'all', label: '全部' }, { value: 'all', label: '全部' },
@@ -264,7 +262,6 @@ export default function PerformanceDomainPracticePage() {
() => new Map(questionBank.map((question) => [question.id, question])), () => new Map(questionBank.map((question) => [question.id, question])),
[questionBank] [questionBank]
) )
const autoNextTimerRef = useRef<number | null>(null)
const [progress, setProgress] = useState<PracticeProgress>(() => const [progress, setProgress] = useState<PracticeProgress>(() =>
getStoredProgress(questionBank) getStoredProgress(questionBank)
@@ -304,23 +301,7 @@ export default function PerformanceDomainPracticePage() {
} }
}, [isFinished]) }, [isFinished])
useEffect(() => {
return () => {
if (autoNextTimerRef.current) {
window.clearTimeout(autoNextTimerRef.current)
}
}
}, [])
const clearAutoNextTimer = () => {
if (autoNextTimerRef.current) {
window.clearTimeout(autoNextTimerRef.current)
autoNextTimerRef.current = null
}
}
const restartPractice = (scope = progress.scope) => { const restartPractice = (scope = progress.scope) => {
clearAutoNextTimer()
setProgress(createProgress(scope, questionBank)) setProgress(createProgress(scope, questionBank))
setAnswerState(null) setAnswerState(null)
setShowCelebration(false) setShowCelebration(false)
@@ -334,7 +315,6 @@ export default function PerformanceDomainPracticePage() {
const advanceToNext = (isCorrect: boolean) => { const advanceToNext = (isCorrect: boolean) => {
if (!currentQuestionId) return if (!currentQuestionId) return
clearAutoNextTimer()
setAnswerState(null) setAnswerState(null)
setProgress((prev) => { setProgress((prev) => {
@@ -359,19 +339,6 @@ export default function PerformanceDomainPracticePage() {
}) })
} }
useEffect(() => {
if (!answerState) return
clearAutoNextTimer()
autoNextTimerRef.current = window.setTimeout(() => {
advanceToNext(answerState.isCorrect)
}, answerState.isCorrect ? CORRECT_DELAY : WRONG_DELAY)
return () => {
clearAutoNextTimer()
}
}, [answerState, currentQuestionId])
const handleSelect = (domainId: string) => { const handleSelect = (domainId: string) => {
if (!currentQuestion || answerState) return if (!currentQuestion || answerState) return
@@ -627,25 +594,19 @@ export default function PerformanceDomainPracticePage() {
</div> </div>
</div> </div>
<AnimatePresence mode="wait"> <div className="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100 dark:bg-gray-800 dark:ring-gray-700">
<motion.div <div className="flex min-h-[160px] items-center">
key={currentQuestion.id}
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -16 }}
className="rounded-3xl bg-white px-5 py-6 shadow-sm ring-1 ring-gray-100 dark:bg-gray-800 dark:ring-gray-700"
>
<p className="text-xl font-semibold leading-9 text-gray-900 dark:text-white"> <p className="text-xl font-semibold leading-9 text-gray-900 dark:text-white">
{currentQuestion.text} {currentQuestion.text}
</p> </p>
</motion.div> </div>
</AnimatePresence> </div>
<motion.div <motion.div
initial={false} initial={false}
animate={{ opacity: answerState ? 1 : 0.96 }} animate={{ opacity: answerState ? 1 : 0.96 }}
className={clsx( className={clsx(
'mt-4 rounded-2xl border px-4 py-4 shadow-sm', 'mt-4 min-h-[124px] rounded-2xl border px-4 py-4 shadow-sm',
answerState?.isCorrect answerState?.isCorrect
? 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-950/30' ? 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-950/30'
: answerState : answerState
@@ -654,7 +615,7 @@ export default function PerformanceDomainPracticePage() {
)} )}
> >
{answerState && currentDomain ? ( {answerState && currentDomain ? (
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex h-full flex-col justify-between gap-3">
<div className="space-y-1"> <div className="space-y-1">
<div <div
className={clsx( className={clsx(
@@ -670,22 +631,22 @@ export default function PerformanceDomainPracticePage() {
</div> </div>
<p className="text-sm leading-6 text-gray-600 dark:text-gray-300"> <p className="text-sm leading-6 text-gray-600 dark:text-gray-300">
{answerState.isCorrect {answerState.isCorrect
? currentDomain.description ? '这题已答对。'
: '这题已放到后面,稍后会再出现。'} : '这题已放到后面,稍后会再出现。'}
</p> </p>
</div> </div>
<button <div className="flex justify-center">
type="button" <button
onClick={() => advanceToNext(answerState.isCorrect)} type="button"
className="rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-indigo-500" onClick={() => advanceToNext(answerState.isCorrect)}
> className="rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-indigo-500"
>
</button>
</button>
</div>
</div> </div>
) : ( ) : (
<p className="text-sm text-gray-600 dark:text-gray-300"> <div className="h-full" />
</p>
)} )}
</motion.div> </motion.div>
@@ -714,25 +675,19 @@ export default function PerformanceDomainPracticePage() {
</span> </span>
</div> </div>
<AnimatePresence mode="wait"> <div className="rounded-[2rem] bg-white/95 px-6 py-7 text-center shadow-xl ring-1 ring-gray-100 backdrop-blur-sm dark:bg-gray-800/95 dark:ring-gray-700">
<motion.div <div className="flex min-h-[220px] items-center justify-center">
key={currentQuestion.id}
initial={{ opacity: 0, scale: 0.96, y: 16 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.96, y: -16 }}
className="rounded-[2rem] bg-white/95 px-6 py-7 text-center shadow-xl ring-1 ring-gray-100 backdrop-blur-sm dark:bg-gray-800/95 dark:ring-gray-700"
>
<p className="text-2xl font-semibold leading-10 text-gray-900 dark:text-white"> <p className="text-2xl font-semibold leading-10 text-gray-900 dark:text-white">
{currentQuestion.text} {currentQuestion.text}
</p> </p>
</motion.div> </div>
</AnimatePresence> </div>
<motion.div <motion.div
initial={false} initial={false}
animate={{ opacity: answerState ? 1 : 0.96 }} animate={{ opacity: answerState ? 1 : 0.96 }}
className={clsx( className={clsx(
'mt-4 rounded-2xl border px-4 py-4 shadow-sm', 'mt-4 min-h-[132px] rounded-2xl border px-4 py-4 shadow-sm',
answerState?.isCorrect answerState?.isCorrect
? 'border-emerald-200 bg-emerald-50/95 dark:border-emerald-800 dark:bg-emerald-950/40' ? 'border-emerald-200 bg-emerald-50/95 dark:border-emerald-800 dark:bg-emerald-950/40'
: answerState : answerState
@@ -741,7 +696,7 @@ export default function PerformanceDomainPracticePage() {
)} )}
> >
{answerState && currentDomain ? ( {answerState && currentDomain ? (
<div className="flex items-center justify-between gap-4"> <div className="flex h-full flex-col justify-between gap-3">
<div className="space-y-1"> <div className="space-y-1">
<div <div
className={clsx( className={clsx(
@@ -757,22 +712,22 @@ export default function PerformanceDomainPracticePage() {
</div> </div>
<p className="text-sm leading-6 text-gray-600 dark:text-gray-300"> <p className="text-sm leading-6 text-gray-600 dark:text-gray-300">
{answerState.isCorrect {answerState.isCorrect
? currentDomain.description ? '这题已答对。'
: '这题已放到后面,稍后会再出现。'} : '这题已放到后面,稍后会再出现。'}
</p> </p>
</div> </div>
<button <div className="flex justify-center">
type="button" <button
onClick={() => advanceToNext(answerState.isCorrect)} type="button"
className="shrink-0 rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-indigo-500" onClick={() => advanceToNext(answerState.isCorrect)}
> className="rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-indigo-500"
>
</button>
</button>
</div>
</div> </div>
) : ( ) : (
<p className="text-center text-sm text-gray-600 dark:text-gray-300"> <div className="h-full" />
</p>
)} )}
</motion.div> </motion.div>
</div> </div>