fix(过程详情): 恢复练习模式查看答案快捷键
This commit is contained in:
@@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
Reference in New Issue
Block a user