Browse Source

字典缓存

zouzs 2 tuần trước cách đây
mục cha
commit
82f4ba5972
2 tập tin đã thay đổi với 77 bổ sung32 xóa
  1. 69 14
      src/store/modules/dict.ts
  2. 8 18
      src/utils/dict.ts

+ 69 - 14
src/store/modules/dict.ts

@@ -1,12 +1,17 @@
 import { defineStore } from "pinia";
+import { localForage } from "@/utils/localforage";
+import { getDictDataByType } from "@/api/system/dict";
+
+const DICT_PREFIX = "dict:";
+const DEFAULT_TTL_MINUTES = 60;
 
 export const useDictStore = defineStore("dict", {
   state: () => ({
-    dict: []
+    dict: [] as Array<{ key: string; value: any[] }>
   }),
 
   actions: {
-    // 获取指定字典类型
+    // 内存读取
     getDict(key: string) {
       try {
         const item = this.dict.find(item => item.key === key);
@@ -17,10 +22,9 @@ export const useDictStore = defineStore("dict", {
       }
     },
 
-    // 设置字典数据
-    setDict(key: string, value: any) {
+    // 内存写入
+    setDict(key: string, value: any[]) {
       if (!key || typeof key !== "string") return;
-      // 避免重复添加
       const index = this.dict.findIndex(item => item.key === key);
       if (index !== -1) {
         this.dict[index].value = value;
@@ -29,24 +33,75 @@ export const useDictStore = defineStore("dict", {
       }
     },
 
-    // 移除字典数据
-    removeDict(key: string) {
+    // 从持久层加载(未过期则返回)
+    async loadFromStorage(key: string) {
+      const cacheKey = `${DICT_PREFIX}${key}`;
+      const cached = await localForage().getItem<any[]>(cacheKey);
+      if (cached && cached.length) {
+        this.setDict(key, cached);
+        return cached;
+      }
+      return null;
+    },
+
+    // 写入持久层(带 TTL,单位分钟)
+    async persistDict(
+      key: string,
+      value: any[],
+      ttlMinutes = DEFAULT_TTL_MINUTES
+    ) {
+      const cacheKey = `${DICT_PREFIX}${key}`;
+      await localForage().setItem(cacheKey, value, ttlMinutes);
+    },
+
+    // 统一拉取入口(内存→持久层→后端)
+    async fetchDictByType(
+      key: string,
+      opts?: { forceRefresh?: boolean; ttlMinutes?: number }
+    ) {
+      // 1) 内存命中
+      const inMemory = this.getDict(key);
+      if (inMemory && !opts?.forceRefresh) return inMemory;
+
+      // 2) 持久层命中
+      const fromStorage = await this.loadFromStorage(key);
+      if (fromStorage && !opts?.forceRefresh) return fromStorage;
+
+      // 3) 后端拉取
+      const resp = await getDictDataByType(key);
+      const dictData = resp.data.map((p: any) => ({
+        label: p.dictLabel,
+        value: p.dictValue,
+        class: p.listClass
+      }));
+      this.setDict(key, dictData);
+      await this.persistDict(
+        key,
+        dictData,
+        opts?.ttlMinutes ?? DEFAULT_TTL_MINUTES
+      );
+      return dictData;
+    },
+
+    // 删除:内存 + 持久层
+    async removeDict(key: string) {
       try {
         const index = this.dict.findIndex(item => item.key === key);
-        if (index !== -1) {
-          this.dict.splice(index, 1);
-          return true;
-        }
-        return false;
+        if (index !== -1) this.dict.splice(index, 1);
+        await localForage().removeItem(`${DICT_PREFIX}${key}`);
+        return true;
       } catch (e) {
         console.error("移除字典失败:", e);
         return false;
       }
     },
 
-    // 清空所有字典数据
-    clearAll() {
+    // 清空:内存 + 持久层(仅清理字典前缀)
+    async clearAll() {
       this.dict = [];
+      const keys = await localForage().keys();
+      const dictKeys = keys.filter(k => k.startsWith(DICT_PREFIX));
+      await Promise.all(dictKeys.map(k => localForage().removeItem(k)));
     }
   }
 });

+ 8 - 18
src/utils/dict.ts

@@ -1,28 +1,22 @@
 import { ref, toRefs } from "vue";
 import { useDictStore } from "@/store/modules/dict";
-import { getDictDataByType } from "@/api/system/dict"; // 根据key获取字典接口
 import { isString } from "@pureadmin/utils";
 
 export function useDict(...args: string[]) {
-  const res = ref({});
+  const res = ref<Record<string, any[]>>({});
   const dictStore = useDictStore();
 
   args.forEach(dictType => {
     res.value[dictType] = [];
     const dicts = dictStore.getDict(dictType);
 
-    if (dicts) {
+    if (dicts && dicts.length) {
       res.value[dictType] = dicts;
     } else {
-      getDictDataByType(dictType)
-        .then(resp => {
-          const dictData = resp.data.map(p => ({
-            label: p.dictLabel,
-            value: p.dictValue,
-            class: p.listClass
-          }));
+      dictStore
+        .fetchDictByType(dictType)
+        .then(dictData => {
           res.value[dictType] = dictData;
-          dictStore.setDict(dictType, dictData);
         })
         .catch(error => {
           console.error(`获取字典${dictType}失败:`, error);
@@ -34,9 +28,7 @@ export function useDict(...args: string[]) {
 }
 
 export function useDictValue<T>(dictType: T, searchValue: string) {
-  if (!isString(searchValue)) {
-    searchValue = String(searchValue);
-  }
+  if (!isString(searchValue)) searchValue = String(searchValue);
   const dictStore = useDictStore();
   const dicts = dictStore.getDict(dictType as string) || [];
   const item = dicts.find((d: any) => d.value === searchValue);
@@ -44,11 +36,9 @@ export function useDictValue<T>(dictType: T, searchValue: string) {
 }
 
 export function useDictClass<T>(dictType: T, searchValue: string) {
-  if (!isString(searchValue)) {
-    searchValue = String(searchValue);
-  }
+  if (!isString(searchValue)) searchValue = String(searchValue);
   const dictStore = useDictStore();
   const dicts = dictStore.getDict(dictType as string) || [];
   const item = dicts.find((d: any) => d.value === searchValue);
   return item ? item.class : null;
-}
+}