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