useDict.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /**
  2. * 字典数据加载 Composable (Vue 3 Composition API)
  3. *
  4. * 使用方式:
  5. * 1. 在组件中导入 import { useDict } from '@/utils/composables/useDict';
  6. * 2. 在 setup 中调用 const { dictData, dictLoading, loadDict, getDictLabel, ... } = useDict(['sys_user_sex', 'sys_normal_disable']);
  7. * 3. 在模板中直接使用 dictData 对象获取字典项 v-for="item in dictData.sys_user_sex"
  8. */
  9. import { ref, reactive, onMounted } from 'vue';
  10. import { getDictData } from '@/api/services/dict';
  11. import storage from '@/utils/storage';
  12. import staticDict from '@/utils/staticDict';
  13. // 全局字典缓存对象,用于存储已加载的字典数据
  14. const dictCache = {
  15. // 缓存的字典数据,格式为 { dictType: [{label, value, ...}, ...] }
  16. data: {},
  17. // 缓存过期时间,单位为毫秒
  18. expireTime: 1000 * 60 * 60, // 1小时
  19. // 缓存最后更新时间
  20. lastUpdateTime: {},
  21. // 正在加载的字典类型,用于防止重复请求
  22. loading: {}
  23. };
  24. /**
  25. * 字典数据加载 Composable
  26. * @param {Array} initialDictTypes - 初始需要加载的字典类型数组
  27. * @param {Object} options - 配置选项
  28. * @param {Boolean} options.autoLoad - 是否自动加载,默认为 true
  29. * @returns {Object} - 返回字典相关的响应式数据和方法
  30. */
  31. export function useDict(initialDictTypes = [], options = {}) {
  32. const { autoLoad = true } = options;
  33. // 组件中的字典数据
  34. const dictData = reactive({});
  35. // 字典加载状态
  36. const dictLoading = ref(false);
  37. // 定义组件需要加载的字典类型
  38. const dictTypeList = ref(initialDictTypes);
  39. /**
  40. * 从缓存中获取字典数据
  41. * @param {String} dictType - 字典类型
  42. * @returns {Array|null} - 返回字典数据,不存在或已过期则返回null
  43. */
  44. const getDictFromCache = (dictType) => {
  45. // 判断是否有缓存
  46. if (!dictCache.data[dictType]) {
  47. return null;
  48. }
  49. // 判断缓存是否过期
  50. const lastUpdateTime = dictCache.lastUpdateTime[dictType] || 0;
  51. const now = Date.now();
  52. if (now - lastUpdateTime > dictCache.expireTime) {
  53. // 缓存已过期,删除缓存
  54. delete dictCache.data[dictType];
  55. delete dictCache.lastUpdateTime[dictType];
  56. return null;
  57. }
  58. // 返回缓存的字典数据
  59. return dictCache.data[dictType];
  60. };
  61. /**
  62. * 更新字典缓存
  63. * @param {String} dictType - 字典类型
  64. * @param {Array} dictList - 字典数据列表
  65. */
  66. const updateDictCache = (dictType, dictList) => {
  67. dictCache.data[dictType] = dictList;
  68. dictCache.lastUpdateTime[dictType] = Date.now();
  69. // 更新本地存储
  70. try {
  71. // 只存储最后更新时间,具体数据保存在内存中
  72. storage.setDict(`dict_time_${dictType}`, dictCache.lastUpdateTime[dictType]);
  73. } catch (e) {
  74. console.error('更新字典缓存失败:', e);
  75. }
  76. };
  77. /**
  78. * 等待指定类型的字典加载完成
  79. * @param {Array} types - 字典类型数组
  80. * @returns {Promise} - 返回等待的Promise对象
  81. */
  82. const waitForDictLoading = (types) => {
  83. return new Promise(resolve => {
  84. const checkInterval = setInterval(() => {
  85. const stillLoading = types.some(type => dictCache.loading[type]);
  86. if (!stillLoading) {
  87. clearInterval(checkInterval);
  88. // 加载完成后,从缓存中获取数据
  89. types.forEach(type => {
  90. const cachedDict = getDictFromCache(type);
  91. if (cachedDict) {
  92. dictData[type] = cachedDict;
  93. }
  94. });
  95. resolve(dictData);
  96. }
  97. }, 50);
  98. });
  99. };
  100. /**
  101. * 从服务器获取字典数据
  102. * @param {Array} dictTypes - 字典类型数组
  103. * @returns {Promise} - 返回字典获取的Promise对象
  104. */
  105. const fetchDictData = (dictTypes) => {
  106. // 标记这些字典类型正在加载
  107. dictTypes.forEach(type => {
  108. dictCache.loading[type] = true;
  109. });
  110. // 单个字典类型
  111. if (dictTypes.length === 1) {
  112. const dictType = dictTypes[0];
  113. console.log(`[useDict] Fetching single dictionary: ${dictType}`);
  114. return getDictData(dictType).then(res => {
  115. if (res.data.code === 200) {
  116. let dictList = [];
  117. if (dictType === 'mall_product_category') {
  118. dictList.push({
  119. dictLabel: '推荐',
  120. dictValue: '-1'
  121. });
  122. dictList.push(...res.data.data);
  123. } else {
  124. dictList = res.data.data;
  125. }
  126. // 更新组件数据和缓存
  127. dictData[dictType] = dictList;
  128. updateDictCache(dictType, dictList);
  129. delete dictCache.loading[dictType];
  130. return dictList;
  131. } else {
  132. console.error(`获取字典[${dictType}]数据失败:`, res.data.msg);
  133. delete dictCache.loading[dictType];
  134. return Promise.reject(res.data.msg);
  135. }
  136. }).catch(err => {
  137. delete dictCache.loading[dictType];
  138. throw err;
  139. });
  140. }
  141. // 多个字典类型,并发请求
  142. console.log(`[useDict] Concurrently fetching ${dictTypes.length} dictionaries: ${dictTypes.join(', ')}`);
  143. const requests = dictTypes.map(dictType => {
  144. return getDictData(dictType).then(res => {
  145. if (res.data.code === 200) {
  146. let dictList = [];
  147. if (dictType === 'mall_product_category') {
  148. dictList.push({
  149. dictLabel: '推荐',
  150. dictValue: '-1'
  151. });
  152. dictList.push(...res.data.data);
  153. } else {
  154. dictList = res.data.data;
  155. }
  156. dictData[dictType] = dictList;
  157. updateDictCache(dictType, dictList);
  158. delete dictCache.loading[dictType];
  159. return {
  160. dictType,
  161. dictList,
  162. success: true
  163. };
  164. } else {
  165. console.error(`获取字典[${dictType}]数据失败:`, res.data.msg);
  166. delete dictCache.loading[dictType];
  167. return {
  168. dictType,
  169. dictList: [],
  170. success: false,
  171. msg: res.data.msg
  172. };
  173. }
  174. }).catch(err => {
  175. console.error(`获取字典[${dictType}]异常:`, err);
  176. delete dictCache.loading[dictType];
  177. return {
  178. dictType,
  179. dictList: [],
  180. success: false,
  181. msg: err
  182. };
  183. });
  184. });
  185. // 等所有请求完成
  186. return Promise.allSettled(requests).then(results => {
  187. const dictMap = {};
  188. const failed = [];
  189. results.forEach(r => {
  190. if (r.status === 'fulfilled') {
  191. const { dictType, dictList, success, msg } = r.value;
  192. if (success) {
  193. dictMap[dictType] = dictList;
  194. } else {
  195. failed.push({ dictType, msg });
  196. }
  197. } else {
  198. console.error(`字典请求失败:`, r.reason);
  199. }
  200. });
  201. console.log(`[useDict] Loaded ${Object.keys(dictMap).length} dictionaries, failed ${failed.length}`);
  202. if (failed.length > 0) {
  203. console.warn('以下字典加载失败:', failed);
  204. }
  205. return dictMap;
  206. });
  207. };
  208. /**
  209. * 加载字典数据
  210. * @param {Array} dictTypes - 字典类型数组,如果不传则使用初始化时的dictTypeList
  211. * @returns {Promise} - 返回字典加载的Promise对象
  212. */
  213. const loadDict = (dictTypes) => {
  214. const types = dictTypes || dictTypeList.value;
  215. if (!types || types.length === 0) {
  216. return Promise.resolve({});
  217. }
  218. // 标记加载中
  219. dictLoading.value = true;
  220. // 需要从服务器获取的字典类型
  221. const needFetch = [];
  222. // 检查是否有静态字典或缓存
  223. types.forEach(type => {
  224. // 先检查是否有静态字典
  225. if (staticDict[type]) {
  226. // 使用静态字典数据
  227. console.log(`[useDict] Using static dictionary for ${type}`);
  228. dictData[type] = staticDict[type];
  229. } else {
  230. // 检查缓存
  231. const cachedDict = getDictFromCache(type);
  232. if (cachedDict) {
  233. // 已有缓存,直接使用
  234. console.log(`[useDict] Using cached dictionary for ${type}`);
  235. dictData[type] = cachedDict;
  236. } else if (!dictCache.loading[type]) {
  237. // 需要从服务器获取,并且当前没有其他组件正在加载
  238. console.log(`[useDict] Need to fetch dictionary ${type} from server`);
  239. needFetch.push(type);
  240. } else {
  241. console.log(`[useDict] Dictionary ${type} is already being loaded by another component, waiting...`);
  242. }
  243. }
  244. });
  245. // 如果所有字典都已缓存或使用静态数据,直接返回
  246. if (needFetch.length === 0) {
  247. dictLoading.value = false;
  248. // 检查是否有正在加载的字典,如果有,等待它们完成
  249. const loadingTypes = types.filter(type => dictCache.loading[type]);
  250. if (loadingTypes.length > 0) {
  251. return waitForDictLoading(loadingTypes);
  252. }
  253. return Promise.resolve(dictData);
  254. }
  255. // 从服务器获取字典数据
  256. return fetchDictData(needFetch).then(res => {
  257. dictLoading.value = false;
  258. console.log("dictData", dictData);
  259. return dictData;
  260. }).catch(err => {
  261. dictLoading.value = false;
  262. console.error('加载字典数据失败:', err);
  263. return Promise.reject(err);
  264. });
  265. };
  266. /**
  267. * 清除字典缓存
  268. * @param {String} dictType - 字典类型,不传则清除所有缓存
  269. */
  270. const clearDictCache = (dictType) => {
  271. if (dictType) {
  272. delete dictCache.data[dictType];
  273. delete dictCache.lastUpdateTime[dictType];
  274. storage.removeDict(`dict_time_${dictType}`);
  275. } else {
  276. dictCache.data = {};
  277. dictCache.lastUpdateTime = {};
  278. // 清除所有字典相关的本地存储
  279. // TODO: 需要根据平台使用不同的方式获取所有存储的key
  280. // 原因: uni-app 没有直接获取所有key的API,需要手动管理字典key列表
  281. // 推荐: 维护一个字典key列表,在添加字典时记录key,清除时遍历列表删除
  282. }
  283. };
  284. /**
  285. * 根据字典值获取对应的字典标签
  286. * @param {String} dictType - 字典类型
  287. * @param {String|Number} value - 字典值
  288. * @param {String} defaultLabel - 默认标签
  289. * @returns {String} - 字典标签
  290. */
  291. const getDictLabel = (dictType, value, defaultLabel = '') => {
  292. // 首先检查组件数据
  293. const dictList = dictData[dictType];
  294. if (dictList) {
  295. const item = dictList.find(dict => dict.dictValue === value);
  296. if (item) return item.dictLabel;
  297. }
  298. // 都没找到,返回默认值
  299. return defaultLabel;
  300. };
  301. /**
  302. * 根据字典标签获取对应的字典值
  303. * @param {String} dictType - 字典类型
  304. * @param {String} label - 字典标签
  305. * @param {String|Number} defaultValue - 默认值
  306. * @returns {String|Number} - 字典值
  307. */
  308. const getDictValue = (dictType, label, defaultValue = '') => {
  309. // 首先检查组件数据
  310. const dictList = dictData[dictType];
  311. if (dictList) {
  312. const item = dictList.find(dict => dict.label === label);
  313. if (item) return item.value;
  314. }
  315. // 再检查静态字典
  316. const staticDictList = staticDict[dictType];
  317. if (staticDictList) {
  318. const item = staticDictList.find(dict => dict.label === label);
  319. if (item) return item.value;
  320. }
  321. // 都没找到,返回默认值
  322. return defaultValue;
  323. };
  324. /**
  325. * 获取字典列表
  326. * @param {String} dictType - 字典类型
  327. * @returns {Array} - 字典列表
  328. */
  329. const getDictList = (dictType) => {
  330. // 首先检查组件数据
  331. const dictList = dictData[dictType];
  332. if (dictList) return dictList;
  333. // 再检查静态字典
  334. return staticDict[dictType] || [];
  335. };
  336. /**
  337. * 获取字典类型对应的样式类
  338. * @param {String} dictType - 字典类型
  339. * @param {String|Number} value - 字典值
  340. * @param {String} defaultClass - 默认样式类
  341. * @returns {String} - 字典项的样式类
  342. */
  343. const getDictClass = (dictType, value, defaultClass = '') => {
  344. // 首先检查组件数据
  345. const dictList = dictData[dictType];
  346. if (dictList) {
  347. const item = dictList.find(dict => dict.dictValue === value);
  348. if (item && item.listClass) return item.listClass;
  349. }
  350. // 都没找到,返回默认值
  351. return defaultClass;
  352. };
  353. // 自动加载字典
  354. if (autoLoad && initialDictTypes.length > 0) {
  355. onMounted(() => {
  356. console.log(`[useDict] Auto-loading dictionaries: ${initialDictTypes.join(', ')}`);
  357. loadDict();
  358. });
  359. }
  360. return {
  361. dictData,
  362. dictLoading,
  363. dictTypeList,
  364. loadDict,
  365. clearDictCache,
  366. getDictLabel,
  367. getDictValue,
  368. getDictList,
  369. getDictClass
  370. };
  371. }
  372. export default useDict;