From a38e275642e8f8181dfcfb2f566ae876dbb02a51 Mon Sep 17 00:00:00 2001 From: ittoview Date: Sun, 1 Mar 2026 15:15:37 +0000 Subject: [PATCH] =?UTF-8?q?fix(=E7=BB=83=E4=B9=A0):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E8=BE=93=E5=85=A5=E6=B3=95=E5=92=8C=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E9=AA=8C=E8=AF=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 input maxLength 限制,支持输入法多字符输入 - 使用 ref 保存最新输入状态,避免闭包导致的状态滞后 - 重构验证逻辑,修复字符对比错误(对比原始答案而非标准化答案) - 修复输入法确认后验证使用旧数据的问题 via [HAPI](https://hapi.run) Co-Authored-By: HAPI --- src/components/practice/InputArea.tsx | 1 - src/pages/ProcessPracticePage.tsx | 60 +++++++++++++++------------ 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/components/practice/InputArea.tsx b/src/components/practice/InputArea.tsx index d65d8dc..a3b8c37 100644 --- a/src/components/practice/InputArea.tsx +++ b/src/components/practice/InputArea.tsx @@ -134,7 +134,6 @@ export function InputArea({ !isComposing && isError && 'border-red-500', inputLocked && 'cursor-not-allowed opacity-50' )} - maxLength={1} autoComplete="off" autoCorrect="off" autoCapitalize="off" diff --git a/src/pages/ProcessPracticePage.tsx b/src/pages/ProcessPracticePage.tsx index aae801b..06e14c4 100644 --- a/src/pages/ProcessPracticePage.tsx +++ b/src/pages/ProcessPracticePage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useCallback, useRef } from 'react' import { motion } from 'framer-motion' import { generateCellSequence, @@ -39,6 +39,7 @@ export default function ProcessPracticePage() { expiresAt: number } | null>(null) const [inputLocked, setInputLocked] = useState(false) + const latestInputRef = useRef([]) // 初始化输入框 useEffect(() => { @@ -49,6 +50,11 @@ export default function ProcessPracticePage() { } }, [currentCellId, cellSequence]) + // 同步当前输入快照,供输入法确认后复用 + useEffect(() => { + latestInputRef.current = userInput + }, [userInput]) + // 切换到指定格子 const switchToCell = useCallback( (cell: CellInfo) => { @@ -94,55 +100,57 @@ export default function ProcessPracticePage() { switchToCell(prevCell) }, [currentCellId, cellSequence, switchToCell]) - // 输入验证 - const handleInputChange = useCallback( - (newInput: string[]) => { - setUserInput(newInput) - - // 等待输入法确认后再验证 - if (isComposing) return - + // 统一的输入验证逻辑 + const validateInput = useCallback( + (input: string[]) => { const currentCell = cellSequence.find((c) => c.id === currentCellId) if (!currentCell || !currentCellId) return - // 使用原始答案长度渲染横线,使用标准化答案验证 const originalAnswer = currentCell.answer const normalizedInput = normalizeAnswer( - newInput.join(''), + input.join(''), currentCell.type === 'knowledge-area' ) const normalizedAnswer = currentCell.normalizedAnswer + const normalizeChar = (char: string) => + normalizeAnswer(char, false) || char - // 逐字符验证状态(基于原始答案) - const newCharStatuses = newInput.map((char, i) => { + const newCharStatuses = input.map((char, i) => { if (!char) return 'pending' as CharStatus - // 对比时使用标准化后的字符 - const normalizedChar = normalizeAnswer(char, false) - const expectedChar = normalizedAnswer[i] - return normalizedChar === expectedChar + const expectedChar = originalAnswer[i] || '' + if (!expectedChar) return 'error' as CharStatus + return normalizeChar(char) === normalizeChar(expectedChar) ? ('correct' as CharStatus) : ('error' as CharStatus) }) setCharStatuses(newCharStatuses) - // 完整答案验证 const isComplete = - newInput.every((c) => c !== '') && - newInput.length === originalAnswer.length + input.every((c) => c !== '') && input.length === originalAnswer.length const isCorrect = isComplete && normalizedInput === normalizedAnswer if (isCorrect) { - // 答对:标记格子,延迟跳转(给用户反馈时间) setAnsweredCells((prev) => new Map(prev).set(currentCellId, true)) setTimeout(() => { moveToNextCell() }, 300) } else if (isComplete) { - // 答错:记录错误时间戳,触发红线动画 setLastErrorTimestamp(Date.now()) } }, - [isComposing, currentCellId, cellSequence, moveToNextCell] + [cellSequence, currentCellId, moveToNextCell] + ) + + const handleInputChange = useCallback( + (newInput: string[]) => { + latestInputRef.current = newInput + setUserInput(newInput) + + if (isComposing) return + + validateInput(newInput) + }, + [isComposing, validateInput] ) // 输入法状态管理 @@ -152,11 +160,11 @@ export default function ProcessPracticePage() { const handleCompositionEnd = useCallback(() => { setIsComposing(false) - // 输入法确认后立即验证(使用当前完整输入) + // 输入法确认后根据最新快照重新验证 requestAnimationFrame(() => { - handleInputChange(userInput) + validateInput(latestInputRef.current) }) - }, [userInput, handleInputChange]) + }, [validateInput]) // 批量粘贴处理 const handlePaste = useCallback(