From d1798349dd9b35cb608d2f7d3243825e95178460 Mon Sep 17 00:00:00 2001 From: words Date: Sun, 9 Aug 2026 21:23:15 +0800 Subject: [PATCH] =?utf8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=BF=9B=E5=BA=A6?= =?utf8?q?=E9=87=8D=E5=A4=8D=E8=AE=B0=E5=BD=95=20bug=20(=E6=95=B0=E6=8D=AE?= =?utf8?q?+=E5=89=8D=E7=AB=AF+=E5=90=8E=E7=AB=AF=E4=B8=89=E5=B1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 问题: 完成按钮被按住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 | Bin 9457664 -> 9457664 bytes backend/main.py | 9 +++++++++ frontend/src/components/StudyMode.js | 3 +++ frontend/src/components/TestMode.js | 5 ++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/backend/db/bcd.db b/backend/db/bcd.db index 3b50439fb4906353f73d6d51d530bc3a49f3e00b..a82b56e446dd1fdac4193cad89740d988a88082c 100644 GIT binary patch delta 3936 zcmb7`Yj9NM8ONX9bJ>J!ZbFQjh$~6ePjSnW{6LX~#~dOs9scf>&;qwtkTI1JizhsW6}-sO=Z}-}COCJ#U77=$`yO zJZJyU^ZeiQzR%fge)Xul8NAfuIlH5EhUe_w$F}r%zIo<+&$jbD9^eHMkbweJ-~&~l z8cYB+zz=Fc9jFHlpb^{zCW1+z3ET{t!7boca2sd=t>AVr8QcK^;7%|Fw1IZe0fHa| z!XN^oAO_+f0g|8-Xn+A77$61GAOo_X3*>+a?gDp%sbCto2iyy~!E`VK%mlN*Y%mAR z1@pjtU_Q7XJOCDe;74YsG_CNU zHaxpQ-J^D?9_56Rlkb%FN)HrX(T1nrqzKx@|(UwN7ZNM9v*88P)j_pE`J3{(d`dbcZI?R!|LYIkflg1pMsD(W2M5*;Ch2Rx z-0bnL9USW4kfg8bl^zND ztWNH&G>Ow?SDqfDOVU;HQ|aT>h||SNjW}I!<**oiDr=N@xjPUkypb8MsnVl#PCi3> zaA#+OQzAy6C@mDi&9*?LR*XJ&*NW0#mAN!|NB3=|QV}{0FHUiPK1?TF2}X!cxWgbF zcN#|NSfx*tK6L3=gg$VEdYImK(jxR;B`relx^yf|`FC8g9-^a8UYL$l^1}4COUFX= zmMheQbl4Rd9dyVYw$nj(*hUAO9wCbFuQUnKK9`UMY0MSs9rWflY{%FenUTJnD!0*# z(x!rM)yN<7>VK+?s-W+r?^X4J?;GDm-%Gx<`gh-9-|u}L>IwB$-_yP!^?*96R{NTL z6V$xgtNdI2t@=xKrm|l;qpnu_mF@Bi(lIroM&$YOL}{P0QJE=A(y!&IvL@}6u1Wur zo|iVs8>LM*xLR&>vvd#R5%T8!il>RZ$>M;}*R_hL8G$_^^i{p$X(4Z-G$M43$8jF( zbd|?39vk!}kE7-I3Xdb@_zT`DVYm96w@NJGy*?9aGrBC)*6EV`2-TJ+g?OR3UFae& z6z6p=2zda;PdVKTHX48`Yn#-(A17-l)4? zcd~9*-Lkq=9o3$y-C5gTtJiw`ANhCrm-;h)Z_S@-_SCGb$<{~{PFG*AUQ*4fRw(=B zPvtRrD}I(PNe3}FebN-~W$yv+R&TGjP1)jY``_238-0Z#8uc_Lt@X9!X}Y5dpK3BF zS$M5?c;e))gq91iteH-kn$~HpgQnS~+1oQb$tfarG%b!MQTTJ;@XZyCZZe%& zgMubr_~&Am#>kqw#;mo1CRX^%BChEQ=*%>jnPSXZBWR+cM$ecm#jMqWCNfT=n?@#S zt%9bq_Jp(cY(Uq|v~Fs-PU}f%Dr-*^%4@eZskE6&GwTVVSKL`U*Ps{H^|+vk*=uja zbG9{xX0nWOJ28xK0-Lf@Fcx==Wdf|rG>U(n)(XK`+%d*AnwicpYx%gQ#Z4(TwKaH9 zdRn=}e}WsCH6VzijzccS4jNUvy_;}uHh5uiv0yP~TP&7N2UyNbrOb4$xk zv=#|oV~$r_jEzs_nDx-O_GPbJgUv`!FlZSH_QPwBAdWa*Z7~*RWbn?l|F?j6z90@e zMwntGSXaHQ@NG@)@Cbk8zto}dYen&PzRNoy`Nl_M2(cFqVeoP(69<4DbcrgEgB z&eO0pML#&cvxQy}=SXo)%1pt}EI|`?#))e*>~7MUDfo#ve)IstY~uvL&kVs&#PP#5 z8lF6}rjP4Y9&cz2GtEc0ThN3YKei@?N1aVt_X>W(jvp3am~8`|JFR;JKVip@tub=2 z&8%tTdX@cfjbSpJsHuV`==iZUdKPaJX5B4lI{2Pu69x`k jgHCXq5i}iz*A|W3Qp2kh_knFQMn}MlW5(#{M@Rk(sY(dO delta 718 zcmWmAIa3pH0EY2IL;eB81vx~65I~SaC1D{Fkt0AjBas_KFsKm4i&G92BUb=72C5x~ zhMq=;Nw(1$tQp3yV3me1V97)A>)xk%{ri6mer%idyE_^6`rV)2HIF{L67>Y49z8DL zA`FOu5dtQ}A`bDmgallMghV9a3X+k6RHWf5(s2#fVMYdSAQM@zAR9Tz#ZBZPAGc6| zLKLAGw{Zt|Q35M$D20qNl*5jDsDK0aQHckrLN#ho3n%JOj|RBVh$b|n1+8d<8xPTr z^Y0#^1D)tXH+s;EKJ;S%k1>cR7{V}~!iy0+!*jgAD8}H!OT5B3UgHhk;vL>&0+X1+ zG-fc1517L|7O;qq_=L|`!Wa1Q70Xz`D%P-$05-6REiLN#X8vc?>2zV^fhw#@i|Vy1 zjcUZI)Twbc#jTFnl=efX#Sx=A%9Z#)#I9%&d$7%M-i!*DDkg&AG?Q$~7sp~>{1>NU vTl^M3#Il$rn>K2$x!^C8#M&UqI_a;pC;22a9<> diff --git a/backend/main.py b/backend/main.py index dbf7232..28f4641 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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) diff --git a/frontend/src/components/StudyMode.js b/frontend/src/components/StudyMode.js index b64de3b..70328d0 100644 --- a/frontend/src/components/StudyMode.js +++ b/frontend/src/components/StudyMode.js @@ -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' diff --git a/frontend/src/components/TestMode.js b/frontend/src/components/TestMode.js index c45b4f8..303a741 100644 --- a/frontend/src/components/TestMode.js +++ b/frontend/src/components/TestMode.js @@ -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() {

🎉 测试完成!

Group {groupId} 全部通过!

- +
); -- 2.43.0