|
|
@@ -0,0 +1,177 @@
|
|
|
+import { reactive } from 'vue'
|
|
|
+import { getSimCardMonthlyData } from '@/service/simCardManage'
|
|
|
+import { simCardCacheDB } from '@/service/simCardManage/simCardCache'
|
|
|
+import type { SimCardMonthlyData, SimCardInfo } from '@/service/simCardManage/interface'
|
|
|
+
|
|
|
+/** 响应式内存缓存 key: `${giccid}_${month}` */
|
|
|
+const memoryCache = reactive(new Map<string, SimCardMonthlyData>())
|
|
|
+
|
|
|
+/** 正在请求中的 key 集合(响应式,驱动表格 loading 状态) */
|
|
|
+const loadingSet = reactive(new Set<string>())
|
|
|
+
|
|
|
+// ─── 队列控制 ───
|
|
|
+let fetchQueue: Array<{ giccid: string; month: string }> = []
|
|
|
+let currentVersion = 0
|
|
|
+let isProcessing = false
|
|
|
+
|
|
|
+// ─── 工具函数 ───
|
|
|
+
|
|
|
+export function cacheKey(giccid: string, month: string): string {
|
|
|
+ return `${giccid}_${month}`
|
|
|
+}
|
|
|
+
|
|
|
+/** 获取上月 month 字符串 (输入 "2026-08" → 输出 "2026-07") */
|
|
|
+function getPrevMonth(month: string): string {
|
|
|
+ const [year, mon] = month.split('-').map(Number)
|
|
|
+ const date = new Date(year, mon - 2, 1)
|
|
|
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 核心:请求单条数据并写入双层缓存 ───
|
|
|
+
|
|
|
+async function fetchAndCache(giccid: string, month: string): Promise<SimCardMonthlyData> {
|
|
|
+ const key = cacheKey(giccid, month)
|
|
|
+
|
|
|
+ // 1. 内存缓存命中
|
|
|
+ const memHit = memoryCache.get(key)
|
|
|
+ if (memHit) return memHit
|
|
|
+
|
|
|
+ // 2. IndexedDB 缓存命中(带惰性过期)
|
|
|
+ const dbHit = await simCardCacheDB.get(giccid, month)
|
|
|
+ if (dbHit) {
|
|
|
+ memoryCache.set(key, dbHit)
|
|
|
+ return dbHit
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 网络请求
|
|
|
+ loadingSet.add(key)
|
|
|
+ try {
|
|
|
+ const data = await getSimCardMonthlyData(giccid, month)
|
|
|
+ memoryCache.set(key, data)
|
|
|
+ await simCardCacheDB.set(giccid, month, data)
|
|
|
+ return data
|
|
|
+ } finally {
|
|
|
+ loadingSet.delete(key)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 队列处理循环 ───
|
|
|
+
|
|
|
+async function processQueue(version: number) {
|
|
|
+ if (isProcessing) return
|
|
|
+ isProcessing = true
|
|
|
+
|
|
|
+ while (fetchQueue.length > 0) {
|
|
|
+ // 版本号不匹配 → 当前请求完成后停止
|
|
|
+ if (currentVersion !== version) break
|
|
|
+
|
|
|
+ const item = fetchQueue.shift()!
|
|
|
+ const key = cacheKey(item.giccid, item.month)
|
|
|
+
|
|
|
+ // 已在内存中则跳过
|
|
|
+ if (memoryCache.has(key)) continue
|
|
|
+
|
|
|
+ await fetchAndCache(item.giccid, item.month)
|
|
|
+
|
|
|
+ // 检查版本号是否仍有效(fetchAndCache 是异步的)
|
|
|
+ if (currentVersion !== version) break
|
|
|
+
|
|
|
+ // 如果获取到的数据最新日是1号,追加上月数据到队列
|
|
|
+ const data = memoryCache.get(key)
|
|
|
+ if (data) {
|
|
|
+ const days = Object.keys(data.dailyUsage).map(Number)
|
|
|
+ if (days.length > 0) {
|
|
|
+ const latestDay = Math.max(...days)
|
|
|
+ if (latestDay === 1) {
|
|
|
+ const prevMonth = getPrevMonth(item.month)
|
|
|
+ const prevKey = cacheKey(item.giccid, prevMonth)
|
|
|
+ if (!memoryCache.has(prevKey)) {
|
|
|
+ fetchQueue.push({ giccid: item.giccid, month: prevMonth })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ isProcessing = false
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 计算日增量 ───
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算某 SIM 卡当月的日增量
|
|
|
+ * - 取最新一天的用量 - 前一天的用量
|
|
|
+ * - 1号时需要上月最后一天数据
|
|
|
+ * - 前一日数据不存在返回 null(展示 "-")
|
|
|
+ */
|
|
|
+function computeDailyIncrement(giccid: string, month: string): number | null {
|
|
|
+ const key = cacheKey(giccid, month)
|
|
|
+ const data = memoryCache.get(key)
|
|
|
+ if (!data) return null
|
|
|
+
|
|
|
+ const days = Object.keys(data.dailyUsage).map(Number).sort((a, b) => a - b)
|
|
|
+ if (days.length === 0) return null
|
|
|
+
|
|
|
+ const latestDay = days[days.length - 1]
|
|
|
+ const latestUsage = data.dailyUsage[latestDay].dataUsage
|
|
|
+
|
|
|
+ if (latestDay === 1) {
|
|
|
+ // 1日:读取上月最后一天
|
|
|
+ const prevMonth = getPrevMonth(month)
|
|
|
+ const prevKey = cacheKey(giccid, prevMonth)
|
|
|
+ const prevData = memoryCache.get(prevKey)
|
|
|
+ if (!prevData) return null
|
|
|
+
|
|
|
+ const prevDays = Object.keys(prevData.dailyUsage).map(Number).sort((a, b) => a - b)
|
|
|
+ if (prevDays.length === 0) return null
|
|
|
+
|
|
|
+ const lastDayOfPrevMonth = prevDays[prevDays.length - 1]
|
|
|
+ return latestUsage - prevData.dailyUsage[lastDayOfPrevMonth].dataUsage
|
|
|
+ } else {
|
|
|
+ // 非1日:取前一天
|
|
|
+ const prevDayData = data.dailyUsage[latestDay - 1]
|
|
|
+ if (!prevDayData) return null
|
|
|
+ return latestUsage - prevDayData.dataUsage
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ─── 对外暴露的方法 ───
|
|
|
+
|
|
|
+/**
|
|
|
+ * 翻页 / 搜索后调用:重建预加载队列
|
|
|
+ * 已缓存的数据会被跳过,只补充未缓存的
|
|
|
+ */
|
|
|
+function onPageChange(simCards: SimCardInfo[]) {
|
|
|
+ currentVersion++
|
|
|
+ const version = currentVersion
|
|
|
+
|
|
|
+ // 重建队列
|
|
|
+ fetchQueue = []
|
|
|
+ for (const sim of simCards) {
|
|
|
+ const key = cacheKey(sim.giccid, sim.month)
|
|
|
+ if (!memoryCache.has(key) && !loadingSet.has(key)) {
|
|
|
+ fetchQueue.push({ giccid: sim.giccid, month: sim.month })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ processQueue(version)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 点击查看月数据时调用(优先级最高)
|
|
|
+ * 直接发请求,不等待队列
|
|
|
+ */
|
|
|
+async function getWithPriority(giccid: string, month: string): Promise<SimCardMonthlyData> {
|
|
|
+ return await fetchAndCache(giccid, month)
|
|
|
+}
|
|
|
+
|
|
|
+export function useSimCardMonthlyCache() {
|
|
|
+ return {
|
|
|
+ memoryCache,
|
|
|
+ loadingSet,
|
|
|
+ cacheKey,
|
|
|
+ computeDailyIncrement,
|
|
|
+ onPageChange,
|
|
|
+ getWithPriority,
|
|
|
+ }
|
|
|
+}
|