fix(过程详情): 恢复练习模式查看答案快捷键

This commit is contained in:
ittoview
2026-03-24 05:49:41 +00:00
parent d9bbd28da5
commit 566eb3492f
3 changed files with 69 additions and 47 deletions

View File

@@ -7,7 +7,6 @@ interface ProficientInputAreaProps {
isComposing: boolean isComposing: boolean
inputLocked: boolean inputLocked: boolean
hasError: boolean hasError: boolean
statusText?: string | null
onChange: (value: string) => void onChange: (value: string) => void
onCompositionStart: () => void onCompositionStart: () => void
onCompositionEnd: (value: string) => void onCompositionEnd: (value: string) => void
@@ -18,7 +17,6 @@ export function ProficientInputArea({
isComposing, isComposing,
inputLocked, inputLocked,
hasError, hasError,
statusText,
onChange, onChange,
onCompositionStart, onCompositionStart,
onCompositionEnd, onCompositionEnd,
@@ -40,7 +38,7 @@ export function ProficientInputArea({
onCompositionStart={onCompositionStart} onCompositionStart={onCompositionStart}
onCompositionEnd={(e) => onCompositionEnd(e.currentTarget.value)} onCompositionEnd={(e) => onCompositionEnd(e.currentTarget.value)}
disabled={inputLocked} disabled={inputLocked}
placeholder="输入当前分组中的一个完整条目,停顿 800ms 自动核对" placeholder="输入当前分组中的一个完整条目"
className={clsx( className={clsx(
'w-full rounded-xl border px-4 py-3 text-base md:text-lg', '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', 'bg-white dark:bg-gray-800/80 text-gray-900 dark:text-gray-100',
@@ -56,33 +54,18 @@ export function ProficientInputArea({
spellCheck="false" spellCheck="false"
/> />
<div className="min-h-6 text-sm"> <AnimatePresence>
<AnimatePresence mode="wait"> {inputLocked && (
{inputLocked ? (
<motion.div <motion.div
key="locked"
initial={{ opacity: 0, y: -6 }} initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }} exit={{ opacity: 0, y: -6 }}
className="text-gray-500 dark:text-gray-400" className="text-sm text-gray-500 dark:text-gray-400"
> >
</motion.div> </motion.div>
) : statusText ? (
<motion.div
key={statusText}
initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
className={clsx(
hasError ? 'text-red-500 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'
)} )}
>
{statusText}
</motion.div>
) : null}
</AnimatePresence> </AnimatePresence>
</div> </div>
</div>
) )
} }

View File

@@ -1,5 +1,19 @@
{ {
"changelogEntries": [ "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", "id": "2026-03-23-process-detail-proficient-mode",
"date": "2026-03-23", "date": "2026-03-23",

View File

@@ -169,7 +169,6 @@ export function ProcessDetailPage() {
const [currentSection, setCurrentSection] = useState<IttoSection>('inputs') const [currentSection, setCurrentSection] = useState<IttoSection>('inputs')
const [proficientInput, setProficientInput] = useState('') const [proficientInput, setProficientInput] = useState('')
const [proficientHasError, setProficientHasError] = useState(false) const [proficientHasError, setProficientHasError] = useState(false)
const [proficientStatusText, setProficientStatusText] = useState<string | null>(null)
const [inputLocked, setInputLocked] = useState(false) const [inputLocked, setInputLocked] = useState(false)
const [lastErrorTimestamp, setLastErrorTimestamp] = useState<number | null>(null) const [lastErrorTimestamp, setLastErrorTimestamp] = useState<number | null>(null)
@@ -253,7 +252,6 @@ export function ProcessDetailPage() {
setCurrentSection(getFirstAvailableSection(itemsBySection)) setCurrentSection(getFirstAvailableSection(itemsBySection))
setProficientInput('') setProficientInput('')
setProficientHasError(false) setProficientHasError(false)
setProficientStatusText(null)
setLastErrorTimestamp(null) setLastErrorTimestamp(null)
setShowAnswer(false) setShowAnswer(false)
setInputLocked(false) setInputLocked(false)
@@ -292,12 +290,10 @@ export function ProcessDetailPage() {
setCurrentPracticeId(null) setCurrentPracticeId(null)
setProficientInput('') setProficientInput('')
setProficientHasError(false) setProficientHasError(false)
setProficientStatusText('当前分组内可乱序输入,停顿 800ms 自动核对。')
} else { } else {
setCurrentPracticeId(practiceItems[0].id) setCurrentPracticeId(practiceItems[0].id)
setProficientInput('') setProficientInput('')
setProficientHasError(false) setProficientHasError(false)
setProficientStatusText(null)
} }
}, [ }, [
practiceItems, practiceItems,
@@ -326,6 +322,48 @@ export function ProcessDetailPage() {
exitPractice() exitPractice()
}, [id, practiceMode, 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(() => { const revealAnswersForThreeSeconds = useCallback(() => {
if (!isPracticeMode) return if (!isPracticeMode) return
clearLongPressTimer() clearLongPressTimer()
@@ -464,7 +502,6 @@ export function ProcessDetailPage() {
if (!matchedItem) { if (!matchedItem) {
setProficientHasError(true) setProficientHasError(true)
setProficientStatusText(`未匹配到当前${SECTION_LABELS[currentSection]}分组中的条目,请继续修改。`)
return return
} }
@@ -478,7 +515,6 @@ export function ProcessDetailPage() {
const allDone = practiceItems.every((item) => nextAnsweredItems.has(item.id)) const allDone = practiceItems.every((item) => nextAnsweredItems.has(item.id))
if (allDone) { if (allDone) {
setProficientStatusText('全部条目已完成,正在结束练习。')
finishPractice() finishPractice()
return return
} }
@@ -487,15 +523,9 @@ export function ProcessDetailPage() {
const nextSection = getNextSection(currentSection, itemsBySection, nextAnsweredItems) const nextSection = getNextSection(currentSection, itemsBySection, nextAnsweredItems)
if (nextSection) { if (nextSection) {
setCurrentSection(nextSection) setCurrentSection(nextSection)
setProficientStatusText(`当前${SECTION_LABELS[currentSection]}已完成,已切换到${SECTION_LABELS[nextSection]}`)
} else {
setProficientStatusText(`当前${SECTION_LABELS[currentSection]}已完成。`)
} }
return 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]) }, [answeredItems, currentSection, inputLocked, itemsBySection, practiceItems, finishPractice])
useEffect(() => { useEffect(() => {
@@ -682,7 +712,6 @@ export function ProcessDetailPage() {
setCurrentSection(section) setCurrentSection(section)
setProficientInput('') setProficientInput('')
setProficientHasError(false) setProficientHasError(false)
setProficientStatusText(`已切换到${SECTION_LABELS[section]}分组。`)
}} }}
className={`flex items-center gap-2 text-left ${canSwitchSection ? 'cursor-pointer' : 'cursor-default'}`} className={`flex items-center gap-2 text-left ${canSwitchSection ? 'cursor-pointer' : 'cursor-default'}`}
> >
@@ -794,13 +823,9 @@ export function ProcessDetailPage() {
isComposing={isComposing} isComposing={isComposing}
inputLocked={inputLocked} inputLocked={inputLocked}
hasError={proficientHasError} hasError={proficientHasError}
statusText={proficientStatusText}
onChange={(value) => { onChange={(value) => {
setProficientInput(value) setProficientInput(value)
setProficientHasError(false) setProficientHasError(false)
if (!value) {
setProficientStatusText('当前分组内可乱序输入,停顿 800ms 自动核对。')
}
}} }}
onCompositionStart={() => handleCompositionStart()} onCompositionStart={() => handleCompositionStart()}
onCompositionEnd={(value) => handleCompositionEnd(value)} onCompositionEnd={(value) => handleCompositionEnd(value)}