fix: 同步应用循环数组解构错误(.for is not iterable)

This commit is contained in:
root
2026-10-16 13:38:49 +08:00
parent f387ac0194
commit 4d2bea139a
9 changed files with 10170 additions and 31 deletions
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-DNeKtcdd.js","./index-B3Rf3B1G.js","./index-DWsZ3Bb0.css"])))=>i.map(i=>d[i]);
import{r as s,_ as i}from"./index-B3Rf3B1G.js";var t;(function(e){e.Documents="DOCUMENTS",e.Data="DATA",e.Library="LIBRARY",e.Cache="CACHE",e.External="EXTERNAL",e.ExternalStorage="EXTERNAL_STORAGE"})(t||(t={}));var a;(function(e){e.UTF8="utf8",e.ASCII="ascii",e.UTF16="utf16"})(a||(a={}));const E=s("Filesystem",{web:()=>i(()=>import("./web-DNeKtcdd.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./web-DOIpoVam.js","./index-Cy5zm918.js","./index-DWsZ3Bb0.css"])))=>i.map(i=>d[i]);
import{r as s,_ as i}from"./index-Cy5zm918.js";var t;(function(e){e.Documents="DOCUMENTS",e.Data="DATA",e.Library="LIBRARY",e.Cache="CACHE",e.External="EXTERNAL",e.ExternalStorage="EXTERNAL_STORAGE"})(t||(t={}));var a;(function(e){e.UTF8="utf8",e.ASCII="ascii",e.UTF16="utf16"})(a||(a={}));const E=s("Filesystem",{web:()=>i(()=>import("./web-DOIpoVam.js"),__vite__mapDeps([0,1,2]),import.meta.url).then(e=>new e.FilesystemWeb)});export{t as Directory,a as Encoding,E as Filesystem};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -8,7 +8,7 @@
<link rel="icon" href="./icons/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="./icons/icon-192.png" />
<title>实习工作日志</title>
<script type="module" crossorigin src="./assets/index-B3Rf3B1G.js"></script>
<script type="module" crossorigin src="./assets/index-Cy5zm918.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DWsZ3Bb0.css">
</head>
<body>
+8 -7
View File
@@ -12,19 +12,20 @@
"cap:sync": "vite build && npx cap sync android"
},
"dependencies": {
"dexie": "^4.0.8",
"vue": "^3.5.13",
"@capacitor/core": "^6.2.0",
"@capacitor/filesystem": "^6.0.2"
"@capacitor/filesystem": "^6.0.2",
"dexie": "^4.0.8",
"vue": "^3.5.13"
},
"devDependencies": {
"@capacitor/android": "^6.2.0",
"@capacitor/cli": "^6.2.0",
"@vitejs/plugin-vue": "^5.2.1",
"typescript": "~5.6.3",
"vite": "^5.4.11",
"electron": "^33.2.0",
"electron-builder": "^25.1.8",
"@capacitor/cli": "^6.2.0",
"@capacitor/android": "^6.2.0"
"fake-indexeddb": "^6.2.5",
"typescript": "~5.6.3",
"vite": "^5.4.11"
},
"description": "实习工作日志:打卡、记录、备忘录与密码库",
"main": "electron/main.cjs",
+9
View File
@@ -36,6 +36,9 @@ importers:
electron-builder:
specifier: ^25.1.8
version: 25.1.8(bluebird@3.7.2)(electron-builder-squirrel-windows@25.1.8)(supports-color@7.2.0)
fake-indexeddb:
specifier: ^6.2.5
version: 6.2.5
typescript:
specifier: ~5.6.3
version: 5.6.3
@@ -1012,6 +1015,10 @@ packages:
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
engines: {'0': node >=0.6.0}
fake-indexeddb@6.2.5:
resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==}
engines: {node: '>=18'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -3090,6 +3097,8 @@ snapshots:
extsprintf@1.4.1:
optional: true
fake-indexeddb@6.2.5: {}
fast-deep-equal@3.1.3: {}
fast-json-stable-stringify@2.1.0: {}
File diff suppressed because one or more lines are too long
+48
View File
@@ -0,0 +1,48 @@
import 'fake-indexeddb/auto'
import { bindDatabase, WorklogDB } from '../src/db/schema'
async function main() {
bindDatabase(new WorklogDB('repro_db'))
const mod = await import('../src/db/schema')
const db = mod.db
await db.project.bulkAdd([{ name: 'P1', archived: 0, created_at: '2025-01-01T00:00:00' }])
await db.work_log.bulkAdd([{ date: '2025-01-02', created_at: '2025-01-02T00:00:00' }])
await db.setting.bulkAdd([
{ key: 'work_start', value: '09:00' },
{ key: 'sync_base_url', value: 'http://127.0.0.1:3780' }
])
await db.attendance.bulkAdd([{ date: '2025-01-02', clock_in_at: '2025-01-02T09:00:00', clock_out_at: '2025-01-02T18:00:00', kind: 'normal', note: null, created_at: '2025-01-02T09:00:00' }])
await db.table('transaction').bulkAdd([{ type: 'expense', category: '餐饮', amount: 2500, date: '2025-01-02', note: 'lunch', reimbursable: 1, reimbursed: 0, created_at: '2025-01-02T12:00:00' }])
const engine = await import('../src/sync/engine')
engine.enableRealtimeSync()
await fetch('http://127.0.0.1:3780/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'rt', password: 'rt123456' })
}).catch(() => { /* 已注册则忽略 */ })
const r1 = await engine.runSync({ baseUrl: 'http://127.0.0.1:3780', username: 'rt', password: 'rt123456' })
console.log('SYNC1 OK:', JSON.stringify(r1))
// 触发本地变更,走实时推送路径
await db.table('log_item').add({
work_log_id: 1, content: '实时测试条目', project_id: null, category: 'dev',
minutes: null, is_milestone: 0, needs_followup: 0, takeaway: null,
remark: null, sort_order: 0, created_at: '2025-01-02T10:00:00'
})
await new Promise(r => setTimeout(r, 4500))
console.log('REALTIME PATH DONE')
// 二次同步幂等验证
const r2 = await engine.runSync({ baseUrl: 'http://127.0.0.1:3780', username: 'rt', password: 'rt123456' })
console.log('SYNC2 OK:', JSON.stringify(r2))
process.exit(0)
}
main().catch(e => {
console.error('FAILED:', (e && (e.stack || e.message)) || e)
process.exit(1)
})
+21 -10
View File
@@ -1,5 +1,6 @@
// 多端同步引擎:全量差异对比 + 记录级新者胜 + 软删除墓碑
// 实时性:本地变更 3 秒内推送;服务端变更通过 SSE 秒级通知其他端;60 秒轮询兜底
import Dexie, { liveQuery } from 'dexie'
import { db } from '../db/schema'
import { getSetting, setSetting } from '../db/repo'
import { getCloudCreds } from '../db/auth'
@@ -167,6 +168,7 @@ let changeDebounce: number | null = null
let sse: EventSource | null = null
let sseKey = ''
let realtimeAttached = false
let realtimeSub: { unsubscribe: () => void } | null = null
export async function autoSyncNow(): Promise<void> {
if (syncRunning) {
@@ -179,20 +181,29 @@ export async function autoSyncNow(): Promise<void> {
} catch { /* 自动同步静默失败,等待下次触发 */ }
}
// 实时触发:本地任何数据变更 3 秒后自动推送
// 实时触发:本地任何数据变更 3 秒后自动推送(Dexie 4 核心 liveQuery,跨全部业务表)
export function enableRealtimeSync(): void {
if (realtimeAttached) return
realtimeAttached = true
;(db as unknown as AnyRow).on('changes', () => {
if (changesSuppressed || syncRunning) return
if (changeDebounce) window.clearTimeout(changeDebounce)
changeDebounce = window.setTimeout(() => { void autoSyncNow() }, 3000)
const sub = liveQuery(async () => {
const counts: Record<string, number> = {}
for (const t of TABLES) counts[t] = await db.table(t).count()
return counts
})
realtimeSub = sub.subscribe({
next: () => {
if (changesSuppressed || syncRunning) return
if (changeDebounce) globalThis.clearTimeout(changeDebounce)
changeDebounce = globalThis.setTimeout(() => { void autoSyncNow() }, 3000)
},
error: () => { /* 忽略监听错误,轮询兜底 */ }
})
}
export function stopRealtime(): void {
if (realtimeSub) { realtimeSub.unsubscribe(); realtimeSub = null }
if (sse) { sse.close(); sse = null; sseKey = '' }
if (changeDebounce) { window.clearTimeout(changeDebounce); changeDebounce = null }
if (changeDebounce) { globalThis.clearTimeout(changeDebounce); changeDebounce = null }
}
// 服务端 SSE 通知:其他端有变更时立即拉取
@@ -220,7 +231,7 @@ export async function runSync(config: SyncConfig): Promise<SyncResult> {
const result = await doRunSync(config)
if (pendingSync) {
pendingSync = false
window.setTimeout(() => { void autoSyncNow() }, 800)
globalThis.setTimeout(() => { void autoSyncNow() }, 800)
}
return result
} finally {
@@ -308,9 +319,9 @@ async function doRunSync(config: SyncConfig): Promise<SyncResult> {
removed++
}
}
for (const [u, srow] of serverRows) {
if (dead.has(u)) continue
const local = byUuid.get(u)
for (const srow of serverRows) {
if (dead.has(srow.uuid)) continue
const local = byUuid.get(srow.uuid)
if (!local) {
await table.add({ ...srow })
pulled++