/** * OmniCard AI (CRMmp) - 移动端弱网离线视觉指示器与断网重放队列 * 严格遵循 AGENTS.md 规范与 uts 强类型规范,一套代码兼顾 App 与 Web/H5 */ export interface OfflineAction { id?: string; type: string; url: string; method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; data?: any; header?: Record; timestamp?: number; retryCount?: number; } export interface OfflineQueueStatus { isOnline: boolean; networkType: string; pendingCount: number; lastSyncTime?: number; } export interface ReplayResult { total: number; success: number; failed: number; } const STORAGE_KEY = 'omni_offline_queue'; const DEFAULT_MAX_RETRIES = 3; let onlineState = true; let currentNetworkType = 'wifi'; let lastSyncTimestamp: number | undefined = undefined; /** * 获取当前网络在线状态 */ export function isOnline(): boolean { return onlineState; } /** * 手动或通过原生事件更新网络状态 */ export function setNetworkStatus(online: boolean, networkType = 'unknown'): void { onlineState = online; currentNetworkType = networkType; } /** * 获取离线队列所有条目 */ export function getOfflineQueue(): OfflineAction[] { try { const raw = uni.getStorageSync(STORAGE_KEY); if (!raw) return []; const list = JSON.parse(String(raw)); return Array.isArray(list) ? list : []; } catch (e) { return []; } } /** * 清空离线队列 */ export function clearOfflineQueue(): void { try { uni.removeStorageSync(STORAGE_KEY); } catch (e) {} } /** * 暂存一个离线操作到本地队列 (FIFO) */ export function enqueueOfflineAction(action: OfflineAction): number { const queue = getOfflineQueue(); const item: OfflineAction = { ...action, id: action.id || `offline_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`, timestamp: action.timestamp || Date.now(), retryCount: action.retryCount || 0, }; queue.push(item); try { uni.setStorageSync(STORAGE_KEY, JSON.stringify(queue)); } catch (e) {} return queue.length; } /** * 网络恢复时自动重放离线队列 */ export async function replayOfflineQueue( executor: (action: OfflineAction) => Promise, maxRetries = DEFAULT_MAX_RETRIES ): Promise { const queue = getOfflineQueue(); if (queue.length === 0) { return { total: 0, success: 0, failed: 0 }; } const remaining: OfflineAction[] = []; let successCount = 0; let failedCount = 0; for (const item of queue) { let ok = false; try { ok = await executor(item); } catch (e) { ok = false; } if (ok) { successCount++; } else { failedCount++; const nextRetries = (item.retryCount || 0) + 1; if (nextRetries < maxRetries) { remaining.push({ ...item, retryCount: nextRetries, }); } } } try { if (remaining.length > 0) { uni.setStorageSync(STORAGE_KEY, JSON.stringify(remaining)); } else { uni.removeStorageSync(STORAGE_KEY); } } catch (e) {} lastSyncTimestamp = Date.now(); return { total: queue.length, success: successCount, failed: failedCount, }; } /** * 获取离线队列综合状态 */ export function getOfflineQueueStatus(): OfflineQueueStatus { const queue = getOfflineQueue(); return { isOnline: onlineState, networkType: currentNetworkType, pendingCount: queue.length, lastSyncTime: lastSyncTimestamp, }; } /** * 格式化多语言横幅提示文案 */ export function formatOfflineBannerText(lang: string = 'en', pendingCount = 0): string { const normalized = (lang || 'en').toLowerCase().split('-')[0]; switch (normalized) { case 'zh': return pendingCount > 0 ? `网络已断开 (${pendingCount} 条暂存待同步)` : '网络已断开,正在尝试重连...'; case 'ar': return pendingCount > 0 ? `غير متصل بالشبكة (${pendingCount} في الانتظار)` : 'غير متصل بالشبكة، جاري إعادة الاتصال...'; case 'es': return pendingCount > 0 ? `Sin conexión (${pendingCount} pendientes)` : 'Sin conexión, reintentando...'; case 'fr': return pendingCount > 0 ? `Hors ligne (${pendingCount} en attente)` : 'Hors ligne, reconnexion en cours...'; case 'ja': return pendingCount > 0 ? `オフライン (${pendingCount} 件の未送信保留)` : 'オフラインです。再接続中...'; case 'en': default: return pendingCount > 0 ? `Offline mode (${pendingCount} pending)` : 'Offline, reconnecting...'; } }