fix(练习): 修复中文输入法和字符验证问题

- 移除 input maxLength 限制,支持输入法多字符输入
- 使用 ref 保存最新输入状态,避免闭包导致的状态滞后
- 重构验证逻辑,修复字符对比错误(对比原始答案而非标准化答案)
- 修复输入法确认后验证使用旧数据的问题

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
ittoview
2026-03-01 15:15:37 +00:00
parent 977187b2d5
commit a38e275642
2 changed files with 34 additions and 27 deletions

View File

@@ -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"

View File

@@ -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<string[]>([])
// 初始化输入框
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(