From 50e38ac4cbb5db7d4f8cff2ea6e227aa40416dff Mon Sep 17 00:00:00 2001 From: studyhill Date: Sat, 29 Aug 2026 19:36:53 +0800 Subject: [PATCH] =?utf8?q?=E5=AD=A6=E8=AF=8D=E9=98=B6=E6=AE=B5=EF=BC=9A?= =?utf8?q?=E8=AF=AD=E9=9F=B3=E5=BC=80=E5=85=B3=20+=20=E9=97=AA=E8=AE=B0?= =?utf8?q?=E6=A8=A1=E5=BC=8F=20+=20=E5=AD=97=E5=8F=B7=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - 语音开关默认关闭,打开后才在新词进入时自动播报;关闭时纯自己看, 需要发音随时点「重听」 - 新增闪记子阶段:看词卡 → 点「下一步 · 闪记」→ 给词性与中文写英文, 答对进下一词,答错退回词卡重看并逐字母标红,再点下一步重新闪记 - 闪记输入框禁粘贴、只接受 ASCII(参考 words 工程),防照抄与中文输入法混入 - 闪记阶段隐藏「重听」,否则播出读音等于直接给答案 - 中文释义 13→15、例句 13→15、例句中文 12→14、搭配 11.5→13.5 --- frontend/src/renderers/VocabDrill.jsx | 178 +++++++++++++++++++++----- 1 file changed, 148 insertions(+), 30 deletions(-) diff --git a/frontend/src/renderers/VocabDrill.jsx b/frontend/src/renderers/VocabDrill.jsx index 73ee170..a425a8e 100644 --- a/frontend/src/renderers/VocabDrill.jsx +++ b/frontend/src/renderers/VocabDrill.jsx @@ -5,7 +5,8 @@ import LoopPlayer from '../components/LoopPlayer' /** * vocab_drill_group:一组词的五阶段训练。 * - * ① learn 学词(顺序,自动播 单词→例句→搭配) + * ① learn 学词(顺序):看词卡 → 点「下一步」进入闪记(给中文+词性写英文) + * → 答对进下一词,答错退回词卡重看,再点下一步重新闪记,直到答对 * ② spell 拼写(乱序,听音写词) * ③ choice 选择(乱序,四选一中文释义) * ④ dictation 听写(仅 is_key 的 10 词,只放音不显示词) @@ -84,6 +85,12 @@ export default function VocabDrill({ payload, taskType = 'new', onReadyChange }) const [options, setOptions] = useState([]) const [revealed, setRevealed] = useState(false) // learn 阶段是否已看释义 const [looping, setLooping] = useState(false) // 循环播放(听学模式) + // 语音开关:默认关闭,打开后才在新词进入时自动播报 + const [autoPlay, setAutoPlay] = useState(false) + // 学词阶段内部子阶段:'learn' 看词卡 / 'flash' 闪记(给中文写英文) + const [phase, setPhase] = useState('learn') + const [flashInput, setFlashInput] = useState('') + const [flashWrong, setFlashWrong] = useState(null) // {input, answer} const startedAt = useRef(Date.now()) const inputRef = useRef(null) @@ -106,11 +113,14 @@ export default function VocabDrill({ payload, taskType = 'new', onReadyChange }) : stage === 'dictation' ? dictationOrder.length : pool.length - // 进入每题时重置输入态 + // 进入每题时重置输入态(闪记子阶段只切 phase,不触发这里,故其输入单独清空) useEffect(() => { setInput('') setFeedback(null) setRevealed(false) + setPhase('learn') + setFlashInput('') + setFlashWrong(null) if (stage === 'choice' && choiceOrder[idx]) { const target = choiceOrder[idx] const distractors = shuffle( @@ -121,12 +131,12 @@ export default function VocabDrill({ payload, taskType = 'new', onReadyChange }) } }, [stage, idx, choiceOrder, words]) - // learn 阶段进入新词自动播放序列 + // 学词阶段:仅在语音开关打开且处于看词卡子阶段时自动播报 useEffect(() => { - if (stage === 'learn' && words[idx]) { + if (stage === 'learn' && phase === 'learn' && autoPlay && words[idx]) { playSeq(buildLearnSeq(words[idx])) } - }, [stage, idx, words]) + }, [stage, phase, idx, words, autoPlay]) // dictation 进入即放音(不显示词) useEffect(() => { @@ -231,6 +241,31 @@ export default function VocabDrill({ payload, taskType = 'new', onReadyChange }) playSeq(stage === 'learn' ? buildLearnSeq(current) : [{ text: current.word, lang: 'en' }]) } + /** 学词阶段推进:本词过关则下一个,全部过完进入拼写阶段。 */ + const advanceLearnWord = () => { + setFlashInput('') + setFlashWrong(null) + setPhase('learn') + if (idx + 1 < words.length) { + setIdx(idx + 1) + } else { + nextStage('learn') + } + } + + /** 闪记提交:答对进下一词,答错退回词卡重看,再点下一步重新闪记。 */ + const submitFlash = () => { + const w = current + if (!w || !flashInput.trim()) return + if (flashInput.trim().toLowerCase() === w.word.toLowerCase()) { + advanceLearnWord() + } else { + setFlashWrong({ input: flashInput, answer: w.word }) + setPhase('learn') + setFlashInput('') + } + } + if (!words.length) return
本课没有词条。
// ---------- 各阶段渲染 ---------- @@ -253,6 +288,13 @@ export default function VocabDrill({ payload, taskType = 'new', onReadyChange }) 错题池 {pool.length} + )}
- {stage === 'error_redo' && pool.length > 0 && ( - - )} - {canSubmit && ( + {isLearnCard && ( + )} + {isFlash && ( + )} - + {!isLearnCard && !isFlash && ( + <> + {stage === 'error_redo' && pool.length > 0 && ( + + )} + {canSubmit && ( + + )} + + + )}
-- 2.43.0