# 员工端APP离线存储方案设计

**版本**: v1.0  
**创建日期**: 2026-02-26  
**适用范围**: ether-app-employee

---

## 一、现状分析

### 1.1 现有实现

当前代码 (`src/utils/offline.ts`) 已实现：

| 功能 | 实现状态 | 说明 |
|------|---------|------|
| 基础缓存 | ✅ 已实现 | `offlineStorage.set/get/remove/clear` |
| 缓存过期 | ✅ 已实现 | 支持TTL过期机制 |
| 网络状态监听 | ✅ 已实现 | 响应式网络状态 |
| 待同步队列 | ✅ 已实现 | `pendingActions` 队列 |
| 位置追踪 | ✅ 已实现 | `locationTracker` |

### 1.2 存在问题

1. **数据模型不完整**: 缺少业务数据的离线模型定义
2. **冲突解决缺失**: 没有处理数据冲突的策略
3. **存储容量管理**: 没有容量限制和清理策略
4. **数据加密**: 敏感数据未加密存储
5. **同步策略简单**: 仅支持重试，缺少增量同步

---

## 二、行业最佳实践

### 2.1 离线优先架构 (Offline-First)

```
┌─────────────────────────────────────────────────────────────┐
│                      应用层                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  工单模块   │  │  巡检模块   │  │  访客模块   │         │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘         │
│         │                │                │                 │
│         ▼                ▼                ▼                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              数据访问层 (Repository)                 │   │
│  │  - 统一数据访问接口                                  │   │
│  │  - 自动选择在线/离线数据源                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│         ┌──────────────────┼──────────────────┐            │
│         ▼                  ▼                  ▼            │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐    │
│  │  在线API    │    │  同步引擎   │    │  本地存储   │    │
│  │  (Remote)   │◄──►│  (Sync)     │◄──►│  (Local)    │    │
│  └─────────────┘    └─────────────┘    └─────────────┘    │
│         │                  │                  │            │
│         │                  ▼                  │            │
│         │         ┌─────────────┐            │            │
│         │         │  同步队列   │            │            │
│         │         │  (Queue)    │            │            │
│         │         └─────────────┘            │            │
│         │                                     │            │
│         ▼                                     ▼            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    网络层                            │   │
│  │  - 网络状态检测                                      │   │
│  │  - 请求重试                                          │   │
│  │  - 请求缓存                                          │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
```

### 2.2 数据同步策略

#### 策略1: 最后写入胜出 (LWW - Last Write Wins)
- 适用场景: 简单数据、无冲突风险
- 实现方式: 使用时间戳比较

#### 策略2: 版本向量 (Vector Clock)
- 适用场景: 需要精确冲突检测
- 实现方式: 每个节点维护版本号

#### 策略3: 业务合并 (Application-Level Merge)
- 适用场景: 复杂业务数据
- 实现方式: 业务逻辑决定合并策略

### 2.3 存储方案对比

| 方案 | 容量 | 性能 | 复杂度 | 适用场景 |
|------|------|------|--------|---------|
| uni.setStorage | 10MB | 高 | 低 | 简单数据 |
| IndexedDB | 50MB+ | 中 | 中 | 结构化数据 |
| SQLite | 无限制 | 高 | 高 | 复杂查询 |
| 本地文件 | 无限制 | 低 | 高 | 大文件存储 |

---

## 三、推荐方案

### 3.1 整体架构

采用 **分层存储 + 增量同步 + 冲突检测** 的方案：

```
┌─────────────────────────────────────────────────────────────┐
│                     离线存储架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                  数据访问层                          │   │
│  │  OfflineRepository<T>                               │   │
│  │  - get(id): Promise<T>                              │   │
│  │  - getAll(): Promise<T[]>                           │   │
│  │  - save(item: T): Promise<void>                     │   │
│  │  - delete(id: string): Promise<void>                │   │
│  │  - sync(): Promise<SyncResult>                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│         ┌──────────────────┼──────────────────┐            │
│         ▼                  ▼                  ▼            │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐    │
│  │  缓存层     │    │  持久层     │    │  同步层     │    │
│  │  (Memory)   │    │  (Storage)  │    │  (Sync)     │    │
│  └─────────────┘    └─────────────┘    └─────────────┘    │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                      存储分区                               │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │  系统数据区 (System Zone)                           │   │
│  │  - 用户信息、Token、配置                            │   │
│  │  - 容量: 1MB                                        │   │
│  │  - 策略: 永久存储                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  业务数据区 (Business Zone)                         │   │
│  │  - 工单、巡检任务、访客记录                         │   │
│  │  - 容量: 5MB                                        │   │
│  │  - 策略: LRU淘汰                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  临时数据区 (Temp Zone)                             │   │
│  │  - 表单草稿、临时照片                               │   │
│  │  - 容量: 2MB                                        │   │
│  │  - 策略: 定时清理                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  同步队列区 (Queue Zone)                            │   │
│  │  - 待同步操作                                       │   │
│  │  - 容量: 1MB                                        │   │
│  │  - 策略: 先进先出                                   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
```

### 3.2 数据模型设计

```typescript
// 离线数据基础模型
interface OfflineEntity {
  id: string
  _offline?: {
    version: number        // 本地版本号
    serverVersion: number  // 服务器版本号
    lastModified: number   // 最后修改时间
    syncStatus: SyncStatus // 同步状态
    localChanges: boolean  // 是否有本地修改
  }
}

// 同步状态枚举
enum SyncStatus {
  SYNCED = 'SYNCED',           // 已同步
  PENDING = 'PENDING',         // 待同步
  CONFLICT = 'CONFLICT',       // 冲突
  DELETED = 'DELETED'          // 已删除
}

// 同步操作类型
enum SyncOperation {
  CREATE = 'CREATE',
  UPDATE = 'UPDATE',
  DELETE = 'DELETE'
}

// 同步队列项
interface SyncQueueItem {
  id: string
  entityType: string      // 实体类型: WorkOrder, Inspection, Visitor
  entityId: string        // 实体ID
  operation: SyncOperation
  payload: any            // 操作数据
  timestamp: number
  retryCount: number
  lastError?: string
}
```

### 3.3 核心接口设计

```typescript
// 离线存储接口
interface OfflineStorage<T extends OfflineEntity> {
  // 数据操作
  get(id: string): Promise<T | null>
  getAll(): Promise<T[]>
  save(entity: T): Promise<void>
  delete(id: string): Promise<void>
  
  // 查询
  query(filter: QueryFilter): Promise<T[]>
  
  // 同步
  sync(): Promise<SyncResult>
  getPendingChanges(): Promise<SyncQueueItem[]>
  
  // 状态
  isOffline(): boolean
  getLastSyncTime(): number
}

// 同步引擎接口
interface SyncEngine {
  // 同步操作
  push(): Promise<SyncResult>      // 推送本地变更
  pull(): Promise<SyncResult>      // 拉取远程变更
  fullSync(): Promise<SyncResult>  // 完整同步
  
  // 冲突处理
  resolveConflict(item: SyncQueueItem, resolution: ConflictResolution): Promise<void>
  
  // 事件
  onSyncStart: Event<void>
  onSyncComplete: Event<SyncResult>
  onConflict: Event<ConflictInfo>
}

// 冲突解决策略
enum ConflictResolution {
  LOCAL_WINS = 'LOCAL_WINS',    // 本地优先
  SERVER_WINS = 'SERVER_WINS',  // 服务器优先
  MERGE = 'MERGE',              // 合并
  MANUAL = 'MANUAL'             // 手动解决
}
```

### 3.4 业务数据离线策略

#### 3.4.1 工单模块

| 数据 | 离线读取 | 离线写入 | 同步策略 |
|------|---------|---------|---------|
| 工单列表 | ✅ 缓存 | ❌ | 下拉刷新时同步 |
| 工单详情 | ✅ 缓存 | ❌ | 查看时同步 |
| 接单操作 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 开始处理 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 完成工单 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 上传照片 | ❌ | ✅ 队列 | 网络恢复后同步 |

#### 3.4.2 巡检模块

| 数据 | 离线读取 | 离线写入 | 同步策略 |
|------|---------|---------|---------|
| 巡检任务列表 | ✅ 缓存 | ❌ | 每日首次打开同步 |
| 巡检任务详情 | ✅ 缓存 | ❌ | 查看时同步 |
| 签到记录 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 巡检结果 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 异常上报 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 巡检照片 | ❌ | ✅ 队列 | 网络恢复后同步 |

#### 3.4.3 访客模块

| 数据 | 离线读取 | 离线写入 | 同步策略 |
|------|---------|---------|---------|
| 访客记录 | ✅ 缓存 | ❌ | 每次进入页面同步 |
| 访客登记 | ❌ | ✅ 队列 | 网络恢复后同步 |
| 凭证验证 | ❌ | ❌ | 必须在线 |
| 通行确认 | ❌ | ✅ 队列 | 网络恢复后同步 |

---

## 四、实现计划

### 4.1 阶段一：基础增强 (1-2天)

1. **完善数据模型**
   - 定义 `OfflineEntity` 基类
   - 为业务实体添加离线元数据

2. **增强存储层**
   - 实现存储分区管理
   - 添加容量限制和LRU淘汰
   - 实现数据加密

3. **优化同步队列**
   - 支持操作优先级
   - 添加依赖关系处理
   - 实现批量同步

### 4.2 阶段二：同步引擎 (2-3天)

1. **增量同步**
   - 实现版本号比较
   - 只同步变更数据

2. **冲突检测**
   - 检测版本冲突
   - 记录冲突信息

3. **冲突解决**
   - 实现自动解决策略
   - 提供手动解决接口

### 4.3 阶段三：业务集成 (2-3天)

1. **工单模块集成**
   - 实现 `WorkOrderRepository`
   - 集成离线操作

2. **巡检模块集成**
   - 实现 `InspectionRepository`
   - 集成离线操作

3. **访客模块集成**
   - 实现 `VisitorRepository`
   - 集成离线操作

### 4.4 阶段四：测试优化 (1-2天)

1. **单元测试**
   - 存储层测试
   - 同步引擎测试

2. **集成测试**
   - 离线场景测试
   - 网络恢复测试

3. **性能优化**
   - 存储性能优化
   - 同步性能优化

---

## 五、关键代码示例

### 5.1 离线存储基类

```typescript
// src/stores/offline/base-repository.ts
export abstract class BaseRepository<T extends OfflineEntity> {
  protected cacheKey: string
  protected syncEndpoint: string
  
  constructor(cacheKey: string, syncEndpoint: string) {
    this.cacheKey = cacheKey
    this.syncEndpoint = syncEndpoint
  }
  
  async get(id: string): Promise<T | null> {
    // 先从内存缓存读取
    const cached = memoryCache.get(`${this.cacheKey}:${id}`)
    if (cached) return cached
    
    // 再从本地存储读取
    const stored = offlineStorage.get<T>(`${this.cacheKey}:${id}`)
    if (stored) {
      memoryCache.set(`${this.cacheKey}:${id}`, stored)
      return stored
    }
    
    // 最后从服务器获取
    if (networkStatus.value.isConnected) {
      const remote = await this.fetchFromServer(id)
      if (remote) {
        await this.saveToCache(remote)
        return remote
      }
    }
    
    return null
  }
  
  async save(entity: T): Promise<void> {
    // 更新离线元数据
    entity._offline = {
      version: (entity._offline?.version || 0) + 1,
      serverVersion: entity._offline?.serverVersion || 0,
      lastModified: Date.now(),
      syncStatus: SyncStatus.PENDING,
      localChanges: true
    }
    
    // 保存到本地
    await this.saveToCache(entity)
    
    // 添加到同步队列
    await syncQueue.add({
      entityType: this.cacheKey,
      entityId: entity.id,
      operation: entity._offline.serverVersion === 0 ? SyncOperation.CREATE : SyncOperation.UPDATE,
      payload: entity,
      timestamp: Date.now(),
      retryCount: 0
    })
  }
  
  async sync(): Promise<SyncResult> {
    if (!networkStatus.value.isConnected) {
      return { success: false, reason: 'offline' }
    }
    
    // 推送本地变更
    const pushResult = await this.pushChanges()
    
    // 拉取远程变更
    const pullResult = await this.pullChanges()
    
    return {
      success: pushResult.success && pullResult.success,
      pushed: pushResult.count,
      pulled: pullResult.count,
      conflicts: [...pushResult.conflicts, ...pullResult.conflicts]
    }
  }
}
```

### 5.2 同步队列增强

```typescript
// src/stores/offline/sync-queue.ts
export class SyncQueue {
  private queue: SyncQueueItem[] = []
  private processing = false
  
  // 添加操作到队列
  async add(item: Omit<SyncQueueItem, 'id'>): Promise<void> {
    const queueItem: SyncQueueItem = {
      ...item,
      id: `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    }
    
    this.queue.push(queueItem)
    await this.persist()
    
    // 如果在线，立即尝试同步
    if (networkStatus.value.isConnected && !this.processing) {
      this.process()
    }
  }
  
  // 处理队列
  async process(): Promise<void> {
    if (this.processing || !networkStatus.value.isConnected) return
    this.processing = true
    
    try {
      // 按优先级排序
      this.sortByPriority()
      
      // 批量处理
      const batch = this.queue.slice(0, 10)
      
      for (const item of batch) {
        try {
          const success = await this.processItem(item)
          if (success) {
            this.queue = this.queue.filter(q => q.id !== item.id)
          } else {
            item.retryCount++
            if (item.retryCount >= 3) {
              // 超过重试次数，标记为失败
              item.lastError = 'Max retry exceeded'
            }
          }
        } catch (error) {
          item.lastError = String(error)
          item.retryCount++
        }
      }
      
      await this.persist()
    } finally {
      this.processing = false
    }
  }
  
  // 处理单个操作
  private async processItem(item: SyncQueueItem): Promise<boolean> {
    const handler = this.handlers.get(item.entityType)
    if (!handler) return false
    
    return await handler(item)
  }
}
```

### 5.3 冲突检测与解决

```typescript
// src/stores/offline/conflict-resolver.ts
export class ConflictResolver {
  // 检测冲突
  detectConflict(local: OfflineEntity, remote: OfflineEntity): boolean {
    return local._offline.serverVersion !== remote._offline.version
  }
  
  // 自动解决冲突
  async resolve(local: T, remote: T, strategy: ConflictResolution): Promise<T> {
    switch (strategy) {
      case ConflictResolution.LOCAL_WINS:
        return this.mergeWithLocalWins(local, remote)
      
      case ConflictResolution.SERVER_WINS:
        return this.mergeWithServerWins(local, remote)
      
      case ConflictResolution.MERGE:
        return this.merge(local, remote)
      
      case ConflictResolution.MANUAL:
        throw new ConflictError('Manual resolution required', local, remote)
    }
  }
  
  // 合并策略：本地优先
  private mergeWithLocalWins(local: T, remote: T): T {
    return {
      ...remote,
      ...local,
      _offline: {
        ...local._offline,
        serverVersion: remote._offline.version,
        syncStatus: SyncStatus.PENDING
      }
    }
  }
  
  // 合并策略：服务器优先
  private mergeWithServerWins(local: T, remote: T): T {
    return {
      ...local,
      ...remote,
      _offline: {
        version: remote._offline.version,
        serverVersion: remote._offline.version,
        lastModified: Date.now(),
        syncStatus: SyncStatus.SYNCED,
        localChanges: false
      }
    }
  }
  
  // 合并策略：字段级合并
  private merge(local: T, remote: T): T {
    const merged = { ...remote }
    
    for (const key of Object.keys(local)) {
      if (key === '_offline') continue
      
      const localValue = local[key]
      const remoteValue = remote[key]
      
      // 如果本地修改了该字段，使用本地值
      if (localValue !== remoteValue && local._offline.localChanges) {
        merged[key] = localValue
      }
    }
    
    merged._offline = {
      version: Math.max(local._offline.version, remote._offline.version),
      serverVersion: remote._offline.version,
      lastModified: Date.now(),
      syncStatus: SyncStatus.PENDING,
      localChanges: true
    }
    
    return merged
  }
}
```

---

## 六、监控与调试

### 6.1 存储监控

```typescript
// 存储监控接口
interface StorageMonitor {
  // 容量统计
  getUsage(): {
    total: number
    used: number
    zones: Record<StorageZone, number>
  }
  
  // 同步统计
  getSyncStats(): {
    pending: number
    synced: number
    failed: number
    lastSyncTime: number
  }
  
  // 清理建议
  getCleanupRecommendations(): CleanupRecommendation[]
}
```

### 6.2 调试工具

```typescript
// 开发环境调试工具
if (process.env.NODE_ENV === 'development') {
  window.__offlineDebug__ = {
    // 查看存储内容
    inspect: () => offlineStorage.getAll(),
    
    // 查看同步队列
    queue: () => syncQueue.getAll(),
    
    // 模拟离线
    goOffline: () => networkStatus.value.isConnected = false,
    
    // 模拟在线
    goOnline: () => networkStatus.value.isConnected = true,
    
    // 强制同步
    forceSync: () => syncEngine.fullSync(),
    
    // 清空存储
    clear: () => offlineStorage.clear()
  }
}
```

---

## 七、风险与对策

| 风险 | 影响 | 对策 |
|------|------|------|
| 存储容量超限 | 数据丢失 | LRU淘汰 + 容量监控 |
| 数据冲突 | 数据不一致 | 版本控制 + 冲突检测 |
| 同步失败 | 数据丢失 | 重试机制 + 本地备份 |
| 性能下降 | 用户体验差 | 增量同步 + 后台处理 |
| 安全风险 | 数据泄露 | 敏感数据加密 |

---

## 八、总结

本方案采用 **离线优先** 架构，通过以下措施确保数据一致性和用户体验：

1. **分层存储**: 系统数据、业务数据、临时数据分区管理
2. **增量同步**: 基于版本号的增量同步，减少数据传输
3. **冲突检测**: 自动检测和解决数据冲突
4. **容量管理**: LRU淘汰策略，防止存储溢出
5. **安全加密**: 敏感数据加密存储

**预计开发周期**: 6-10天  
**技术复杂度**: 中等  
**维护成本**: 低
