fix: stabilize performance domain practice ui
This commit is contained in:
@@ -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,10 +631,11 @@ export default function PerformanceDomainPracticePage() {
|
||||
</div>
|
||||
<p className="text-sm leading-6 text-gray-600 dark:text-gray-300">
|
||||
{answerState.isCorrect
|
||||
? currentDomain.description
|
||||
? '这题已答对。'
|
||||
: '这题已放到后面,稍后会再出现。'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => advanceToNext(answerState.isCorrect)}
|
||||
@@ -682,10 +644,9 @@ export default function PerformanceDomainPracticePage() {
|
||||
下一个
|
||||
</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>
|
||||
<div className="flex justify-center">
|
||||
<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"
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user