195 行
4.6 KiB
Plaintext
195 行
4.6 KiB
Plaintext
/**
|
|
* 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<string, any>;
|
|
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<boolean>,
|
|
maxRetries = DEFAULT_MAX_RETRIES
|
|
): Promise<ReplayResult> {
|
|
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...';
|
|
}
|
|
}
|