diff --git a/src/components/practice/ProficientInputArea.tsx b/src/components/practice/ProficientInputArea.tsx index 8f48e8e..8f753c3 100644 --- a/src/components/practice/ProficientInputArea.tsx +++ b/src/components/practice/ProficientInputArea.tsx @@ -7,7 +7,6 @@ interface ProficientInputAreaProps { isComposing: boolean inputLocked: boolean hasError: boolean - statusText?: string | null onChange: (value: string) => void onCompositionStart: () => void onCompositionEnd: (value: string) => void @@ -18,7 +17,6 @@ export function ProficientInputArea({ isComposing, inputLocked, hasError, - statusText, onChange, onCompositionStart, onCompositionEnd, @@ -40,7 +38,7 @@ export function ProficientInputArea({ onCompositionStart={onCompositionStart} onCompositionEnd={(e) => onCompositionEnd(e.currentTarget.value)} disabled={inputLocked} - placeholder="输入当前分组中的一个完整条目,停顿 800ms 自动核对" + placeholder="输入当前分组中的一个完整条目" className={clsx( 'w-full rounded-xl border px-4 py-3 text-base md:text-lg', 'bg-white dark:bg-gray-800/80 text-gray-900 dark:text-gray-100', @@ -56,33 +54,18 @@ export function ProficientInputArea({ spellCheck="false" /> -
- - {inputLocked ? ( - - 答案显示中,输入已锁定 - - ) : statusText ? ( - - {statusText} - - ) : null} - -
+ + {inputLocked && ( + + 答案显示中,输入已锁定 + + )} + ) } diff --git a/src/data/changelog.json b/src/data/changelog.json index e715826..7aa3363 100644 --- a/src/data/changelog.json +++ b/src/data/changelog.json @@ -1,5 +1,19 @@ { "changelogEntries": [ + { + "id": "2026-03-24-process-detail-shortcut-restore", + "date": "2026-03-24", + "type": "fix", + "title": "恢复过程详情练习模式的 Ctrl+H 查看答案快捷键", + "scope": "过程详情" + }, + { + "id": "2026-03-24-proficient-mode-status-cleanup", + "date": "2026-03-24", + "type": "fix", + "title": "移除熟练模式底部多余说明与命中提示,精简输入区反馈", + "scope": "过程详情" + }, { "id": "2026-03-23-process-detail-proficient-mode", "date": "2026-03-23", diff --git a/src/pages/ProcessDetailPage.tsx b/src/pages/ProcessDetailPage.tsx index 232b9c1..81a3d4a 100644 --- a/src/pages/ProcessDetailPage.tsx +++ b/src/pages/ProcessDetailPage.tsx @@ -169,7 +169,6 @@ export function ProcessDetailPage() { const [currentSection, setCurrentSection] = useState('inputs') const [proficientInput, setProficientInput] = useState('') const [proficientHasError, setProficientHasError] = useState(false) - const [proficientStatusText, setProficientStatusText] = useState(null) const [inputLocked, setInputLocked] = useState(false) const [lastErrorTimestamp, setLastErrorTimestamp] = useState(null) @@ -253,7 +252,6 @@ export function ProcessDetailPage() { setCurrentSection(getFirstAvailableSection(itemsBySection)) setProficientInput('') setProficientHasError(false) - setProficientStatusText(null) setLastErrorTimestamp(null) setShowAnswer(false) setInputLocked(false) @@ -292,12 +290,10 @@ export function ProcessDetailPage() { setCurrentPracticeId(null) setProficientInput('') setProficientHasError(false) - setProficientStatusText('当前分组内可乱序输入,停顿 800ms 自动核对。') } else { setCurrentPracticeId(practiceItems[0].id) setProficientInput('') setProficientHasError(false) - setProficientStatusText(null) } }, [ practiceItems, @@ -326,6 +322,48 @@ export function ProcessDetailPage() { exitPractice() }, [id, practiceMode, exitPractice]) + useEffect(() => { + if (!isPracticeMode) return + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.ctrlKey && event.key.toLowerCase() === 'h') { + event.preventDefault() + clearLongPressTimer() + clearDebounceTimer() + clearAnswerRevealTimer() + + if (practiceMode === 'proficient') { + setProficientInput('') + setProficientHasError(false) + } + + setShowAnswer(true) + setInputLocked(true) + } + } + + const handleKeyUp = (event: KeyboardEvent) => { + if (event.key === 'Control' || event.key.toLowerCase() === 'h') { + hideAnswerAndUnlock() + } + } + + window.addEventListener('keydown', handleKeyDown) + window.addEventListener('keyup', handleKeyUp) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + window.removeEventListener('keyup', handleKeyUp) + } + }, [ + isPracticeMode, + practiceMode, + hideAnswerAndUnlock, + clearLongPressTimer, + clearDebounceTimer, + clearAnswerRevealTimer, + ]) + const revealAnswersForThreeSeconds = useCallback(() => { if (!isPracticeMode) return clearLongPressTimer() @@ -464,7 +502,6 @@ export function ProcessDetailPage() { if (!matchedItem) { setProficientHasError(true) - setProficientStatusText(`未匹配到当前${SECTION_LABELS[currentSection]}分组中的条目,请继续修改。`) return } @@ -478,7 +515,6 @@ export function ProcessDetailPage() { const allDone = practiceItems.every((item) => nextAnsweredItems.has(item.id)) if (allDone) { - setProficientStatusText('全部条目已完成,正在结束练习。') finishPractice() return } @@ -487,15 +523,9 @@ export function ProcessDetailPage() { const nextSection = getNextSection(currentSection, itemsBySection, nextAnsweredItems) if (nextSection) { setCurrentSection(nextSection) - setProficientStatusText(`当前${SECTION_LABELS[currentSection]}已完成,已切换到${SECTION_LABELS[nextSection]}。`) - } else { - setProficientStatusText(`当前${SECTION_LABELS[currentSection]}已完成。`) } return } - - const doneCount = itemsBySection[currentSection].filter((item) => nextAnsweredItems.has(item.id)).length - setProficientStatusText(`已命中:${matchedItem.name}(${SECTION_LABELS[currentSection]} ${doneCount}/${itemsBySection[currentSection].length})`) }, [answeredItems, currentSection, inputLocked, itemsBySection, practiceItems, finishPractice]) useEffect(() => { @@ -682,7 +712,6 @@ export function ProcessDetailPage() { setCurrentSection(section) setProficientInput('') setProficientHasError(false) - setProficientStatusText(`已切换到${SECTION_LABELS[section]}分组。`) }} className={`flex items-center gap-2 text-left ${canSwitchSection ? 'cursor-pointer' : 'cursor-default'}`} > @@ -794,13 +823,9 @@ export function ProcessDetailPage() { isComposing={isComposing} inputLocked={inputLocked} hasError={proficientHasError} - statusText={proficientStatusText} onChange={(value) => { setProficientInput(value) setProficientHasError(false) - if (!value) { - setProficientStatusText('当前分组内可乱序输入,停顿 800ms 自动核对。') - } }} onCompositionStart={() => handleCompositionStart()} onCompositionEnd={(value) => handleCompositionEnd(value)}