fix(练习): 修复输入法组合期间焦点跳转导致字母分散问题

- 组合输入期间禁止useEffect自动聚焦到下一个空输入框
- onCompositionEnd直接传入index和value,不再扫描数组查找
- 确保拼音字母留在同一输入框,确认后正确分散成中文

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

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
ittoview
2026-03-02 01:34:31 +00:00
parent b4dcd565d6
commit 5d97c70e06
2 changed files with 32 additions and 27 deletions

View File

@@ -11,8 +11,8 @@ interface InputAreaProps {
inputLocked: boolean inputLocked: boolean
lastErrorTimestamp: number | null lastErrorTimestamp: number | null
onInputChange: (newInput: string[]) => void onInputChange: (newInput: string[]) => void
onCompositionStart: () => void onCompositionStart: (index: number) => void
onCompositionEnd: () => void onCompositionEnd: (index: number, value: string) => void
onPaste: (e: React.ClipboardEvent) => void onPaste: (e: React.ClipboardEvent) => void
} }
@@ -31,11 +31,12 @@ export function InputArea({
// 自动聚焦到第一个空输入框 // 自动聚焦到第一个空输入框
useEffect(() => { useEffect(() => {
if (isComposing || inputLocked) return
const firstEmptyIndex = userInput.findIndex((char) => !char) const firstEmptyIndex = userInput.findIndex((char) => !char)
if (firstEmptyIndex !== -1 && inputRefs.current[firstEmptyIndex]) { if (firstEmptyIndex !== -1 && inputRefs.current[firstEmptyIndex]) {
inputRefs.current[firstEmptyIndex]?.focus() inputRefs.current[firstEmptyIndex]?.focus()
} }
}, [userInput]) }, [userInput, isComposing, inputLocked])
const handleCharInput = ( const handleCharInput = (
index: number, index: number,
@@ -137,8 +138,10 @@ export function InputArea({
value={char} value={char}
onChange={(e) => handleCharInput(index, e)} onChange={(e) => handleCharInput(index, e)}
onKeyDown={(e) => handleKeyDown(index, e)} onKeyDown={(e) => handleKeyDown(index, e)}
onCompositionStart={onCompositionStart} onCompositionStart={() => onCompositionStart(index)}
onCompositionEnd={onCompositionEnd} onCompositionEnd={(e) =>
onCompositionEnd(index, e.currentTarget.value)
}
onPaste={onPaste} onPaste={onPaste}
disabled={inputLocked} disabled={inputLocked}
className={clsx( className={clsx(

View File

@@ -207,39 +207,41 @@ export default function ProcessPracticePage() {
) )
// 输入法状态管理 // 输入法状态管理
const handleCompositionStart = useCallback(() => { const handleCompositionStart = useCallback((_index: number) => {
isComposingRef.current = true isComposingRef.current = true
setIsComposing(true) setIsComposing(true)
}, []) }, [])
const handleCompositionEnd = useCallback(() => { const handleCompositionEnd = useCallback(
(index: number, value: string) => {
isComposingRef.current = false isComposingRef.current = false
setIsComposing(false) setIsComposing(false)
// 输入法确认后,需要将当前输入框中的所有字符分散到后续输入框 // 输入法确认后,需要将当前输入框中的所有字符分散到后续输入框
requestAnimationFrame(() => { requestAnimationFrame(() => {
const currentInput = latestInputRef.current const currentInput = latestInputRef.current
// 找到第一个有内容且长度>1的输入框(输入法组合的结果) const composedText = value
const composedIndex = currentInput.findIndex((char) => char.length > 1)
if (composedIndex !== -1) {
const composedText = currentInput[composedIndex]
const chars = composedText.split('')
const newInput = [...currentInput] const newInput = [...currentInput]
// 将组合的字符分散到后续输入框 if (composedText) {
for (let i = 0; i < chars.length && composedIndex + i < newInput.length; i++) { const chars = composedText.split('')
newInput[composedIndex + i] = chars[i] for (
let i = 0;
i < chars.length && index + i < newInput.length;
i++
) {
newInput[index + i] = chars[i]
}
} else {
newInput[index] = ''
} }
latestInputRef.current = newInput latestInputRef.current = newInput
setUserInput(newInput) setUserInput(newInput)
validateInput(newInput) validateInput(newInput)
} else {
// 没有多字符组合,直接验证当前输入
validateInput(currentInput)
}
}) })
}, [validateInput]) },
[validateInput]
)
// 批量粘贴处理 // 批量粘贴处理
const handlePaste = useCallback( const handlePaste = useCallback(