From: root Date: Sun, 7 Jun 2026 03:00:11 +0000 (+0800) Subject: codebuddy开始接手 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=9825543a50f5189f7e57e0436161cd1910b93fa5;p=words.git codebuddy开始接手 --- diff --git a/frontend/src/components/StudyMode.css b/frontend/src/components/StudyMode.css index 76ad199..af328dd 100644 --- a/frontend/src/components/StudyMode.css +++ b/frontend/src/components/StudyMode.css @@ -1,3 +1,5 @@ +/* /home/words/frontend/src/components/StudyMode.css */ + .study-container { display: flex; flex-direction: column; @@ -16,6 +18,7 @@ max-width: 1100px; min-height: 600px; overflow: hidden; + transition: all 0.3s ease; /* 增加平滑过渡 */ } .image-section { @@ -38,6 +41,7 @@ padding: 40px; display: flex; flex-direction: column; + justify-content: center; /* 居中对齐内容 */ } .word-text { @@ -46,6 +50,10 @@ margin-bottom: 10px; } +.meaning-row { + margin-bottom: 20px; +} + .chinese { font-size: 28px; font-weight: 500; @@ -76,19 +84,63 @@ border-radius: 10px; font-size: 18px; cursor: pointer; + transition: background 0.2s; +} + +.action-btn:hover { + background: #1e3a8a; +} + +/* 闪记模式专用样式 */ +.flashcard-area { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.flashcard-title { + font-size: 24px; + color: #64748b; + margin-bottom: 20px; +} + +.flashcard-hint { + font-size: 36px; + color: #1e293b; + font-weight: bold; + margin-bottom: 30px; } input { - width: 100%; + width: 80%; + max-width: 400px; padding: 15px; font-size: 24px; border: 2px solid #ddd; border-radius: 10px; margin-top: 20px; + outline: none; +} + +input:focus { + border-color: #1e40af; +} + +.correction-area { + text-align: center; +} + +.correction-area .error-tip { + color: #dc2626; + font-size: 28px; + margin-bottom: 20px; } -.correction-area .error-tip { color: #dc2626; } -.correction-area input.correct { border-color: #10b981; background: #ecfdf5; } +.correction-area input.correct { + border-color: #10b981; + background: #ecfdf5; +} .back-home { margin-top: 20px; @@ -98,3 +150,9 @@ input { text-decoration: underline; cursor: pointer; } + +.loading { + font-size: 24px; + margin-top: 100px; + color: #64748b; +} diff --git a/frontend/src/components/StudyMode.js b/frontend/src/components/StudyMode.js index a410ccf..463fab4 100644 --- a/frontend/src/components/StudyMode.js +++ b/frontend/src/components/StudyMode.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import axios from 'axios'; import './StudyMode.css'; @@ -13,38 +13,62 @@ function StudyMode() { const [phase, setPhase] = useState('learning'); // learning, flashcard, correction const [userInput, setUserInput] = useState(""); const [correctionInput, setCorrectionInput] = useState(""); - const [showDetails, setShowDetails] = useState(false); - + + const hasSpokenRef = useRef(null); const currentWord = words[currentIndex]; + // 朗读逻辑:语速 0.6,间隔 500ms const speak = useCallback((text) => { - const utterance = new SpeechSynthesisUtterance(text); - utterance.lang = 'en-US'; - utterance.rate = 0.8; - window.speechSynthesis.speak(utterance); + window.speechSynthesis.cancel(); + + const utterance1 = new SpeechSynthesisUtterance(text); + utterance1.lang = 'en-US'; + utterance1.rate = 0.6; + + utterance1.onend = () => { + setTimeout(() => { + const utterance2 = new SpeechSynthesisUtterance(text); + utterance2.lang = 'en-US'; + utterance2.rate = 0.6; + window.speechSynthesis.speak(utterance2); + }, 500); + }; + + window.speechSynthesis.speak(utterance1); }, []); + // 获取单词数据 useEffect(() => { axios.get(`${API_BASE}/api/words/${groupId}`).then(res => setWords(res.data)); }, [groupId]); + // 自动朗读逻辑 useEffect(() => { if (currentWord && phase === 'learning') { - setShowDetails(false); - speak(currentWord.word); - setTimeout(() => speak(currentWord.word), 1500); - setTimeout(() => { - setShowDetails(true); + if (hasSpokenRef.current !== currentWord.id) { speak(currentWord.word); - }, 3000); + hasSpokenRef.current = currentWord.id; + } } }, [currentIndex, currentWord, phase, speak]); + // 全局键盘监听:在学习模式下按回车进入闪记 + useEffect(() => { + const handleGlobalKeyDown = (event) => { + if (phase === 'learning' && event.key === 'Enter') { + setPhase('flashcard'); + } + }; + window.addEventListener('keydown', handleGlobalKeyDown); + return () => window.removeEventListener('keydown', handleGlobalKeyDown); + }, [phase]); + const handleNext = async () => { if (currentIndex < words.length - 1) { setCurrentIndex(currentIndex + 1); setPhase('learning'); setUserInput(""); + setCorrectionInput(""); } else { await axios.post(`${API_BASE}/api/progress/update`, { group_id: groupId, @@ -61,6 +85,11 @@ function StudyMode() { } else { setPhase('correction'); setCorrectionInput(""); + // 错误时读一遍正确发音提醒 + const utterance = new SpeechSynthesisUtterance(currentWord.word); + utterance.lang = 'en-US'; + utterance.rate = 0.6; + window.speechSynthesis.speak(utterance); } }; @@ -69,11 +98,14 @@ function StudyMode() { return (
-
- {currentWord.picture_id && ( - mindmap - )} -
+ {/* 只有在非闪记模式下显示图片 */} + {phase !== 'flashcard' && ( +
+ {currentWord.picture_id && ( + mindmap + )} +
+ )}
Group {groupId}: {currentIndex + 1} / {words.length}
@@ -85,25 +117,27 @@ function StudyMode() { {currentWord.chinese_meaning} ({currentWord.part_of_speech})
- {showDetails && ( -
- {currentWord.word_root &&

词根: {currentWord.word_root}

} - {currentWord.affix &&

组成: {currentWord.affix}

} - -
- )} + +
+ {currentWord.word_root &&

词根: {currentWord.word_root}

} + {currentWord.affix &&

组成: {currentWord.affix}

} + +
) : phase === 'flashcard' ? (
-

请拼写单词

+

闪记模式:请拼写单词

+

中文含义:{currentWord.chinese_meaning}

setUserInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && checkFlashcard()} - placeholder="输入英文..." + placeholder="输入英文单词..." /> - +
) : (
@@ -114,10 +148,17 @@ function StudyMode() { className={correctionInput === currentWord.word ? "correct" : "typing"} value={correctionInput} onChange={(e) => setCorrectionInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && correctionInput === currentWord.word) { + setPhase('flashcard'); + } + }} placeholder="请在此抄写..." /> {correctionInput === currentWord.word && ( - + )}
)}