From: root Date: Tue, 18 Aug 2026 13:21:42 +0000 (+0800) Subject: fix: 支持进度撤销-服务端删除已确认进度回写本地; 删除G54复习1 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=c37ea6c60b5103ea2a70057c0862fdd5e04b1f3a;p=words.git fix: 支持进度撤销-服务端删除已确认进度回写本地; 删除G54复习1 - 新增 deleteProgress(); sync pull 在拉取成功后, 对本地非 dirty 且服务端已不存在的进度执行本地删除 仅当 serverEvents 非空时执行, 防止服务端异常空响应误删; 绝不删 dirty(未推送)记录 - 删除 study_logs 中 G54 review1(id 401), 该进度记为未执行; 下次同步自动从本地移除 --- diff --git a/backend/db/bcd.db b/backend/db/bcd.db index da4027c..0c595cd 100644 Binary files a/backend/db/bcd.db and b/backend/db/bcd.db differ diff --git a/frontend/src/db.js b/frontend/src/db.js index 9eccef7..325fcbb 100644 --- a/frontend/src/db.js +++ b/frontend/src/db.js @@ -112,6 +112,12 @@ export async function clearDirty(group_id, op_type) { }); } +// 删除某条进度(用于服务端已删除记录回写本地, 实现撤销/未执行) +export async function deleteProgress(group_id, op_type) { + const key = `${group_id}:${op_type}`; + return singleReq('progress', 'readwrite', s => s.delete(key)); +} + // ---------- meta store ---------- export async function getMeta(key) { diff --git a/frontend/src/sync.js b/frontend/src/sync.js index a7c30ca..d349a2e 100644 --- a/frontend/src/sync.js +++ b/frontend/src/sync.js @@ -1,12 +1,12 @@ // 离线优先 + 自动同步 统一入口 -// 数据规则 (布尔并集): (group,op) 任一方完成即完成; 不删本地、不比时间 +// 数据规则 (布尔并集): (group,op) 任一方完成即完成; 云端删除已确认进度会回写本地(撤销) // dirty = "这条本地完成记录尚未确认上传到云端" // syncNow: 上传本地 dirty → 拉云端并集合并 → 更新 lastSyncAt → 重算 learnable // triggerAutoSync: 幂等并发锁, 供首页/联网恢复/完成学习 调用, 失败时置 failed import { api } from './api'; import { - getDirtyProgress, clearDirty, getAllProgress, putProgress, + getDirtyProgress, clearDirty, getAllProgress, putProgress, deleteProgress, putWords, setMeta, getMeta, setLearnableGroups, getAllWordsFromIDB, getWordsCount, } from './db'; @@ -78,6 +78,18 @@ export async function syncNow() { } } + // 阶段2b: 服务端已删除(且本地已确认/非 dirty)的进度, 本地同步删除。 + // 仅在成功拉取到非空集合时才执行, 避免服务端异常返回空时误删本地进度; + // 仅删非 dirty 记录(本地未推送完成的进度绝不删, 保护离线工作)。 + if (serverEvents.length > 0) { + const serverKeys = new Set(serverEvents.map(e => `${e.group_id}:${e.op_type}`)); + for (const p of localAll) { + if (!p.dirty && !serverKeys.has(p.key)) { + await deleteProgress(p.group_id, p.op_type); + } + } + } + // 阶段3: 首次 warmup 词库 (仅 IDB 空时, 避免每次重拉 1852 词) if ((await getWordsCount()) === 0) { const allWords = await api.getAllWords();