fix(练习): 修复中文输入法和字符验证问题
- 移除 input maxLength 限制,支持输入法多字符输入 - 使用 ref 保存最新输入状态,避免闭包导致的状态滞后 - 重构验证逻辑,修复字符对比错误(对比原始答案而非标准化答案) - 修复输入法确认后验证使用旧数据的问题 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -134,7 +134,6 @@ export function InputArea({
|
|||||||
!isComposing && isError && 'border-red-500',
|
!isComposing && isError && 'border-red-500',
|
||||||
inputLocked && 'cursor-not-allowed opacity-50'
|
inputLocked && 'cursor-not-allowed opacity-50'
|
||||||
)}
|
)}
|
||||||
maxLength={1}
|
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import {
|
import {
|
||||||
generateCellSequence,
|
generateCellSequence,
|
||||||
@@ -39,6 +39,7 @@ export default function ProcessPracticePage() {
|
|||||||
expiresAt: number
|
expiresAt: number
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const [inputLocked, setInputLocked] = useState(false)
|
const [inputLocked, setInputLocked] = useState(false)
|
||||||
|
const latestInputRef = useRef<string[]>([])
|
||||||
|
|
||||||
// 初始化输入框
|
// 初始化输入框
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,6 +50,11 @@ export default function ProcessPracticePage() {
|
|||||||
}
|
}
|
||||||
}, [currentCellId, cellSequence])
|
}, [currentCellId, cellSequence])
|
||||||
|
|
||||||
|
// 同步当前输入快照,供输入法确认后复用
|
||||||
|
useEffect(() => {
|
||||||
|
latestInputRef.current = userInput
|
||||||
|
}, [userInput])
|
||||||
|
|
||||||
// 切换到指定格子
|
// 切换到指定格子
|
||||||
const switchToCell = useCallback(
|
const switchToCell = useCallback(
|
||||||
(cell: CellInfo) => {
|
(cell: CellInfo) => {
|
||||||
@@ -94,55 +100,57 @@ export default function ProcessPracticePage() {
|
|||||||
switchToCell(prevCell)
|
switchToCell(prevCell)
|
||||||
}, [currentCellId, cellSequence, switchToCell])
|
}, [currentCellId, cellSequence, switchToCell])
|
||||||
|
|
||||||
// 输入验证
|
// 统一的输入验证逻辑
|
||||||
const handleInputChange = useCallback(
|
const validateInput = useCallback(
|
||||||
(newInput: string[]) => {
|
(input: string[]) => {
|
||||||
setUserInput(newInput)
|
|
||||||
|
|
||||||
// 等待输入法确认后再验证
|
|
||||||
if (isComposing) return
|
|
||||||
|
|
||||||
const currentCell = cellSequence.find((c) => c.id === currentCellId)
|
const currentCell = cellSequence.find((c) => c.id === currentCellId)
|
||||||
if (!currentCell || !currentCellId) return
|
if (!currentCell || !currentCellId) return
|
||||||
|
|
||||||
// 使用原始答案长度渲染横线,使用标准化答案验证
|
|
||||||
const originalAnswer = currentCell.answer
|
const originalAnswer = currentCell.answer
|
||||||
const normalizedInput = normalizeAnswer(
|
const normalizedInput = normalizeAnswer(
|
||||||
newInput.join(''),
|
input.join(''),
|
||||||
currentCell.type === 'knowledge-area'
|
currentCell.type === 'knowledge-area'
|
||||||
)
|
)
|
||||||
const normalizedAnswer = currentCell.normalizedAnswer
|
const normalizedAnswer = currentCell.normalizedAnswer
|
||||||
|
const normalizeChar = (char: string) =>
|
||||||
|
normalizeAnswer(char, false) || char
|
||||||
|
|
||||||
// 逐字符验证状态(基于原始答案)
|
const newCharStatuses = input.map((char, i) => {
|
||||||
const newCharStatuses = newInput.map((char, i) => {
|
|
||||||
if (!char) return 'pending' as CharStatus
|
if (!char) return 'pending' as CharStatus
|
||||||
// 对比时使用标准化后的字符
|
const expectedChar = originalAnswer[i] || ''
|
||||||
const normalizedChar = normalizeAnswer(char, false)
|
if (!expectedChar) return 'error' as CharStatus
|
||||||
const expectedChar = normalizedAnswer[i]
|
return normalizeChar(char) === normalizeChar(expectedChar)
|
||||||
return normalizedChar === expectedChar
|
|
||||||
? ('correct' as CharStatus)
|
? ('correct' as CharStatus)
|
||||||
: ('error' as CharStatus)
|
: ('error' as CharStatus)
|
||||||
})
|
})
|
||||||
setCharStatuses(newCharStatuses)
|
setCharStatuses(newCharStatuses)
|
||||||
|
|
||||||
// 完整答案验证
|
|
||||||
const isComplete =
|
const isComplete =
|
||||||
newInput.every((c) => c !== '') &&
|
input.every((c) => c !== '') && input.length === originalAnswer.length
|
||||||
newInput.length === originalAnswer.length
|
|
||||||
const isCorrect = isComplete && normalizedInput === normalizedAnswer
|
const isCorrect = isComplete && normalizedInput === normalizedAnswer
|
||||||
|
|
||||||
if (isCorrect) {
|
if (isCorrect) {
|
||||||
// 答对:标记格子,延迟跳转(给用户反馈时间)
|
|
||||||
setAnsweredCells((prev) => new Map(prev).set(currentCellId, true))
|
setAnsweredCells((prev) => new Map(prev).set(currentCellId, true))
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
moveToNextCell()
|
moveToNextCell()
|
||||||
}, 300)
|
}, 300)
|
||||||
} else if (isComplete) {
|
} else if (isComplete) {
|
||||||
// 答错:记录错误时间戳,触发红线动画
|
|
||||||
setLastErrorTimestamp(Date.now())
|
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(() => {
|
const handleCompositionEnd = useCallback(() => {
|
||||||
setIsComposing(false)
|
setIsComposing(false)
|
||||||
// 输入法确认后立即验证(使用当前完整输入)
|
// 输入法确认后根据最新快照重新验证
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
handleInputChange(userInput)
|
validateInput(latestInputRef.current)
|
||||||
})
|
})
|
||||||
}, [userInput, handleInputChange])
|
}, [validateInput])
|
||||||
|
|
||||||
// 批量粘贴处理
|
// 批量粘贴处理
|
||||||
const handlePaste = useCallback(
|
const handlePaste = useCallback(
|
||||||
|
|||||||
Reference in New Issue
Block a user