fix(过程详情): 恢复练习模式查看答案快捷键
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
|
||||
<div className="min-h-6 text-sm">
|
||||
<AnimatePresence mode="wait">
|
||||
{inputLocked ? (
|
||||
<motion.div
|
||||
key="locked"
|
||||
initial={{ opacity: 0, y: -6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -6 }}
|
||||
className="text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
答案显示中,输入已锁定
|
||||
</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>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{inputLocked && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -6 }}
|
||||
className="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
答案显示中,输入已锁定
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -169,7 +169,6 @@ export function ProcessDetailPage() {
|
||||
const [currentSection, setCurrentSection] = useState<IttoSection>('inputs')
|
||||
const [proficientInput, setProficientInput] = useState('')
|
||||
const [proficientHasError, setProficientHasError] = useState(false)
|
||||
const [proficientStatusText, setProficientStatusText] = useState<string | null>(null)
|
||||
|
||||
const [inputLocked, setInputLocked] = useState(false)
|
||||
const [lastErrorTimestamp, setLastErrorTimestamp] = useState<number | null>(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)}
|
||||
|
||||
Reference in New Issue
Block a user