]> acesimba.cloud Git - words.git/commitdiff
fix: 修复进度重复记录 bug (数据+前端+后端三层)
authorwords <words@local>
Sun, 9 Aug 2026 13:23:15 +0000 (21:23 +0800)
committerwords <words@local>
Sun, 9 Aug 2026 13:23:15 +0000 (21:23 +0800)
问题: 完成按钮被按住Enter时浏览器原生key-repeat触发多次finishTest,
后端盲插导致 study_logs 出现重复记录(8月共25条多余行)

修复:
- 数据: 清理 study_logs 25条重复, 247->222, 每组保留最早一条
- 前端 StudyMode/TestMode: 加 submittedRef 防重入, 完成按钮 disabled
- 后端 /api/progress/update: 插入前查当天同(group_id,op_type)记录, 存在则跳过
- 备份: bcd.db.bak_pre-dedup_20260809_210635

backend/db/bcd.db
backend/main.py
frontend/src/components/StudyMode.js
frontend/src/components/TestMode.js

index 3b50439fb4906353f73d6d51d530bc3a49f3e00b..a82b56e446dd1fdac4193cad89740d988a88082c 100644 (file)
Binary files a/backend/db/bcd.db and b/backend/db/bcd.db differ
index dbf72325f430fb1c3254cbb65b6afcfe5fb41406..28f46415723578b6b521bbc9f23a264f246eb538 100644 (file)
@@ -256,6 +256,15 @@ async def update_progress(data: ProgressUpdate):
             )
         """)
         now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        today_prefix = now[:10]
+        # 去重: 当天已有同 (group_id, op_type) 的完成记录则跳过,防止前端重复提交产生脏数据
+        cursor.execute(
+            "SELECT id FROM study_logs WHERE group_id=? AND op_type=? AND status='completed' AND substr(created_at,1,10)=?",
+            (data.group_id, data.op_type, today_prefix)
+        )
+        if cursor.fetchone():
+            conn.close()
+            return {"status": "skipped", "date": now, "reason": "duplicate same-day record"}
         cursor.execute(
             "INSERT INTO study_logs (group_id, op_type, status, created_at) VALUES (?, ?, ?, ?)",
             (data.group_id, data.op_type, 'completed', now)
index b64de3bee0e58ec7e00614ae819ab215dda2f599..70328d0bd724d9b0378fee76e1b740fc9aaaddff 100644 (file)
@@ -17,6 +17,7 @@ function StudyMode() {
 
     const hasSpokenRef = useRef(null);
     const transitioningRef = useRef(false);
+    const submittedRef = useRef(false);  // 防止完成时重复提交进度
     const currentWord = words[currentIndex];
 
     // 朗读逻辑:通过后端 TTS 代理(有道美式发音)播放两遍,间隔 500ms
@@ -80,6 +81,8 @@ function StudyMode() {
             setCorrectionInput("");
             setTimeout(() => { transitioningRef.current = false; }, 300);
         } else {
+            if (submittedRef.current) return;   // 防重入(按住Enter等场景)
+            submittedRef.current = true;
             await axios.post(`${API_BASE}/api/progress/update`, {
                 group_id: groupId,
                 op_type: 'study'
index c45b4f87ede88a6880690b2e377ef6d5f7991356..303a741bf9d5c048b8f4741de8e1fae58c181cc5 100644 (file)
@@ -24,6 +24,7 @@ function TestMode() {
 
     const inputRef = useRef(null);
     const copyRef = useRef(null);
+    const submittedRef = useRef(false);  // 防止完成时重复提交进度
     const currentWord = queue[currentIndex];
 
     // 获取单词数据
@@ -124,6 +125,8 @@ function TestMode() {
 
     // 完成测试,记录进度(op_type 依据活动类型:test / review1 / review2 / review3)
     const finishTest = useCallback(async () => {
+        if (submittedRef.current) return;   // 防重入(按住Enter等场景)
+        submittedRef.current = true;
         await axios.post(`${API_BASE}/api/progress/update`, {
             group_id: groupId,
             op_type: activityType
@@ -203,7 +206,7 @@ function TestMode() {
                 <div className="test-card test-complete-card">
                     <h1>🎉 测试完成!</h1>
                     <p>Group {groupId} 全部通过!</p>
-                    <button className="action-btn" onClick={finishTest}>完成</button>
+                    <button className="action-btn" onClick={finishTest} disabled={submittedRef.current}>完成</button>
                 </div>
             </div>
         );