| | |
| | | import { getPendingSyncRecords, markRecordSynced, cleanOldRecords, getOfflineConfig, saveOfflineConfig } from './db'; |
| | | import { getPendingSyncRecords, getPendingSyncRecordCount, markRecordSynced, cleanOldRecords, getOfflineConfig, saveOfflineConfig } from './db'; |
| | | import { deleteLocalImage } from './fs'; |
| | | import { uploadImage } from '../api/upload'; |
| | | import { addVisitorRecord, idCardSingOut, pullSigninData, pullSignoutData } from '../api/visitorRecord'; |
| | | import { getVisitAreaTree } from '../api/visitArea'; |
| | | |
| | | let isSyncing = false; |
| | | |
| | | const getCurrentNetworkType = async () => { |
| | | return new Promise(resolve => { |
| | | uni.getNetworkType({ |
| | | success: (res) => resolve(res.networkType), |
| | | fail: () => resolve('unknown') |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * 启动离线数据同步队列 |
| | |
| | | * 4. 成功后:将数据库记录置为已同步,并删除本地沙盒图片释放内存 |
| | | */ |
| | | export const startSyncQueue = async () => { |
| | | console.log('[Sync] startSyncQueue 进入,当前同步状态:', { |
| | | isSyncing, |
| | | }); |
| | | if (isSyncing) { |
| | | console.warn('[Sync] 同步队列已在执行中,本次跳过'); |
| | | return; |
| | | } |
| | | |
| | | // 简单判断当前网络是否可用 |
| | | const networkType = await new Promise(resolve => { |
| | | uni.getNetworkType({ |
| | | success: (res) => resolve(res.networkType), |
| | | fail: () => resolve('unknown') |
| | | }); |
| | | }); |
| | | const networkType = await getCurrentNetworkType(); |
| | | console.log('[Sync] 当前网络状态:', networkType); |
| | | |
| | | if (networkType === 'none') { |
| | | console.warn('[Sync] 当前离线,跳过同步队列'); |
| | | return; |
| | | } |
| | | |
| | |
| | | let pendingRecords = []; |
| | | try { |
| | | pendingRecords = await getPendingSyncRecords(); |
| | | console.log('[Sync] 读取到待同步记录:', { |
| | | count: pendingRecords.length, |
| | | records: pendingRecords.map((record) => ({ |
| | | id: record.id, |
| | | type: record.type, |
| | | localImgPath: record.local_img_path, |
| | | createTime: record.create_time, |
| | | })), |
| | | }); |
| | | } catch (dbErr) { |
| | | console.error('[Sync] 读取待同步记录失败:', dbErr); |
| | | return; |
| | | } |
| | | |
| | | if (pendingRecords.length === 0) { |
| | | isSyncing = false; |
| | | console.warn('[Sync] 当前没有待同步记录'); |
| | | return; |
| | | } |
| | | |
| | |
| | | |
| | | // 1. 如果存在本地图片,先上传图片,再把云端地址回填到业务 payload |
| | | if (record.local_img_path) { |
| | | console.log('[Sync] 开始上传本地图片:', { |
| | | recordId: record.id, |
| | | type: record.type, |
| | | localImgPath: record.local_img_path, |
| | | }); |
| | | const uploadRes = await uploadImage(record.local_img_path); |
| | | console.log('[Sync] 图片上传结果:', { |
| | | recordId: record.id, |
| | | type: record.type, |
| | | localImgPath: record.local_img_path, |
| | | uploadRes, |
| | | }); |
| | | if (uploadRes && uploadRes.code === 200 && uploadRes.url) { |
| | | // 根据记录类型,将云端 URL 赋值给不同的业务 payload 字段 |
| | | if (record.type === 'signout') { |
| | |
| | | try { |
| | | let submitRes; |
| | | if (item.type === 'signout') { |
| | | console.log('[Sync] 开始调用签离接口:', { |
| | | recordId: item.recordId, |
| | | payload: item.payload, |
| | | }); |
| | | submitRes = await idCardSingOut(item.payload); |
| | | |
| | | // await new Promise((resolve) => { |
| | | // uni.showModal({ |
| | | // title: '调试: 签离接口返回', |
| | | // content: `记录ID: ${item.recordId}\n结果: ${JSON.stringify(submitRes).substring(0, 500)}`, |
| | | // showCancel: false, |
| | | // success: resolve, |
| | | // fail: resolve |
| | | // }); |
| | | // }); |
| | | } else { |
| | | // type 为 appointment, online, 或它们加上 _signedout 后缀时,都作为进场记录同步 |
| | | console.log('[Sync] 开始调用登记接口:', { |
| | | recordId: item.recordId, |
| | | type: item.type, |
| | | payload: item.payload, |
| | | }); |
| | | submitRes = await addVisitorRecord(item.payload); |
| | | |
| | | // await new Promise((resolve) => { |
| | | // uni.showModal({ |
| | | // title: '调试: 登记接口返回', |
| | | // content: `类型: ${item.type}\n记录ID: ${item.recordId}\n结果: ${JSON.stringify(submitRes).substring(0, 500)}`, |
| | | // showCancel: false, |
| | | // success: resolve, |
| | | // fail: resolve |
| | | // }); |
| | | // }); |
| | | } |
| | | |
| | | console.log('[Sync] 单条业务接口返回:', { |
| | | recordId: item.recordId, |
| | | type: item.type, |
| | | submitRes, |
| | | }); |
| | | |
| | | if (submitRes && submitRes.code === 200) { |
| | | await markRecordSynced(item.recordId); |
| | |
| | | if (signinDataToPull.length > 0) { |
| | | try { |
| | | const payloads = signinDataToPull.map(item => item.payload); |
| | | console.log('[Sync] 开始调用批量补发签到接口:', { |
| | | count: payloads.length, |
| | | payloads, |
| | | }); |
| | | const pullRes = await pullSigninData(payloads); |
| | | console.log('[Sync] 批量补发签到接口结果:', pullRes); |
| | | if (pullRes && pullRes.code === 200) { |
| | | for (let item of signinDataToPull) { |
| | | await markRecordSynced(item.recordId); |
| | |
| | | if (signoutDataToPull.length > 0) { |
| | | try { |
| | | const payloads = signoutDataToPull.map(item => item.payload); |
| | | console.log('[Sync] 开始调用批量补发签离接口:', { |
| | | count: payloads.length, |
| | | payloads, |
| | | }); |
| | | const pullRes = await pullSignoutData(payloads); |
| | | console.log('[Sync] 批量补发签离接口结果:', pullRes); |
| | | if (pullRes && pullRes.code === 200) { |
| | | for (let item of signoutDataToPull) { |
| | | await markRecordSynced(item.recordId); |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 手动触发全量离线同步: |
| | | * 持续执行同步队列,直到待同步记录清空,或检测到本轮没有任何推进为止。 |
| | | */ |
| | | export const syncAllPendingRecords = async () => { |
| | | if (isSyncing) { |
| | | return { |
| | | success: false, |
| | | reason: 'syncing', |
| | | total: 0, |
| | | synced: 0, |
| | | remaining: await getPendingSyncRecordCount(), |
| | | }; |
| | | } |
| | | |
| | | const networkType = await getCurrentNetworkType(); |
| | | if (networkType === 'none') { |
| | | return { |
| | | success: false, |
| | | reason: 'offline', |
| | | total: 0, |
| | | synced: 0, |
| | | remaining: await getPendingSyncRecordCount(), |
| | | }; |
| | | } |
| | | |
| | | const totalPending = await getPendingSyncRecordCount(); |
| | | if (totalPending === 0) { |
| | | return { |
| | | success: true, |
| | | reason: 'empty', |
| | | total: 0, |
| | | synced: 0, |
| | | remaining: 0, |
| | | }; |
| | | } |
| | | |
| | | let syncedCount = 0; |
| | | let remainingCount = totalPending; |
| | | let round = 0; |
| | | const maxRounds = 20; |
| | | |
| | | while (remainingCount > 0 && round < maxRounds) { |
| | | const beforeCount = await getPendingSyncRecordCount(); |
| | | if (beforeCount <= 0) { |
| | | remainingCount = 0; |
| | | break; |
| | | } |
| | | |
| | | await startSyncQueue(); |
| | | |
| | | const afterCount = await getPendingSyncRecordCount(); |
| | | const currentRoundSynced = Math.max(beforeCount - afterCount, 0); |
| | | syncedCount += currentRoundSynced; |
| | | remainingCount = afterCount; |
| | | round += 1; |
| | | |
| | | // 本轮没有任何推进,说明存在上传失败或脏数据,避免死循环卡死按钮。 |
| | | if (afterCount >= beforeCount) { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | return { |
| | | success: remainingCount === 0, |
| | | reason: remainingCount === 0 ? 'completed' : 'partial', |
| | | total: totalPending, |
| | | synced: syncedCount, |
| | | remaining: remainingCount, |
| | | }; |
| | | }; |
| | | |
| | | /** |
| | | * 每日凌晨或空闲时清理超期废弃数据并同步最新配置字典 |
| | | */ |
| | | export const runDailyCleanup = async () => { |