feat: add performance challenge mode

This commit is contained in:
ittoview
2026-05-16 12:19:01 +01:00
parent 3f90031185
commit 35c0146a96

View File

@@ -49,6 +49,7 @@ interface AnswerState {
const STORAGE_KEY = 'performance-domain-practice-progress-v3'
const CORRECT_AUTO_NEXT_DELAY = 1000
const CHALLENGE_RESTART_DELAY = 3000
const scopeOptions: Array<{ value: PracticeScope; label: string }> = [
{ value: 'all', label: '全部' },
@@ -190,7 +191,9 @@ export default function PerformanceDomainPracticePage() {
)
const [answerState, setAnswerState] = useState<AnswerState | null>(null)
const [showCelebration, setShowCelebration] = useState(false)
const [challengeMode, setChallengeMode] = useState(false)
const autoNextTimerRef = useRef<number | null>(null)
const challengeRestartTimerRef = useRef<number | null>(null)
const currentQuestionId = progress.queue[0] ?? null
const currentQuestion = currentQuestionId
@@ -246,6 +249,10 @@ export default function PerformanceDomainPracticePage() {
window.clearTimeout(autoNextTimerRef.current)
autoNextTimerRef.current = null
}
if (challengeRestartTimerRef.current) {
window.clearTimeout(challengeRestartTimerRef.current)
challengeRestartTimerRef.current = null
}
setProgress(createProgress(scope, questionBank))
setAnswerState(null)
setShowCelebration(false)
@@ -256,6 +263,25 @@ export default function PerformanceDomainPracticePage() {
restartPractice(scope)
}
useEffect(() => {
if (!challengeMode || !answerState || answerState.isCorrect) return
if (challengeRestartTimerRef.current) {
window.clearTimeout(challengeRestartTimerRef.current)
}
challengeRestartTimerRef.current = window.setTimeout(() => {
restartPractice(progress.scope)
}, CHALLENGE_RESTART_DELAY)
return () => {
if (challengeRestartTimerRef.current) {
window.clearTimeout(challengeRestartTimerRef.current)
challengeRestartTimerRef.current = null
}
}
}, [answerState, challengeMode, progress.scope])
const advanceToNext = (isCorrect: boolean) => {
if (!currentQuestionId) return
@@ -263,6 +289,10 @@ export default function PerformanceDomainPracticePage() {
window.clearTimeout(autoNextTimerRef.current)
autoNextTimerRef.current = null
}
if (challengeRestartTimerRef.current) {
window.clearTimeout(challengeRestartTimerRef.current)
challengeRestartTimerRef.current = null
}
setAnswerState(null)
setProgress((prev) => {
if (prev.queue[0] !== currentQuestionId) return prev
@@ -478,6 +508,20 @@ export default function PerformanceDomainPracticePage() {
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
aria-pressed={challengeMode}
onClick={() => setChallengeMode((value) => !value)}
className={clsx(
'rounded-lg border px-3 py-2 text-sm font-medium transition-colors',
challengeMode
? 'border-indigo-500 bg-indigo-600 text-white'
: 'border-gray-200 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700'
)}
>
</button>
<button
type="button"
onClick={() => restartPractice()}
@@ -487,6 +531,7 @@ export default function PerformanceDomainPracticePage() {
</button>
</div>
</div>
<div className="rounded-2xl bg-white p-4 shadow-sm ring-1 ring-gray-100 dark:bg-gray-800 dark:ring-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3">