feat: add performance challenge mode
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user