dictMixin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import {
  2. getDictData,
  3. getMultipleDictData
  4. } from '@/api/services/dict';
  5. import storage from '@/utils/storage';
  6. import staticDict from '@/utils/staticDict';
  7. // 全局字典缓存对象,用于存储已加载的字典数据
  8. const dictCache = {
  9. // 缓存的字典数据,格式为 { dictType: [{label, value, ...}, ...] }
  10. data: {},
  11. // 缓存过期时间,单位为毫秒
  12. expireTime: 1000 * 60 * 60, // 1小时
  13. // 缓存最后更新时间
  14. lastUpdateTime: {},
  15. // 正在加载的字典类型,用于防止重复请求
  16. loading: {}
  17. };
  18. /**
  19. * 字典数据加载Mixin
  20. * 使用方式:
  21. * 1. 在组件中导入 import dictMixin from '@/utils/mixins/dictMixin';
  22. * 2. 在组件的mixins选项中注册 mixins: [dictMixin]
  23. * 3. 在组件的data中定义需要的字典类型 dictTypeList: ['sys_user_sex', 'sys_normal_disable', ...]
  24. * 4. 在组件的methods中调用 getDictLabel 等方法使用字典数据
  25. * 5. 在模板中直接使用 dictData 对象获取字典项 v-for="item in dictData.sys_user_sex"
  26. */
  27. export default {
  28. data() {
  29. return {
  30. // 组件中的字典数据
  31. dictData: {},
  32. // 定义组件需要加载的字典类型
  33. dictTypeList: [],
  34. // 字典加载状态
  35. dictLoading: false
  36. };
  37. },
  38. created() {
  39. // 组件创建时,如果有定义dictTypeList,则自动加载字典数据
  40. if (this.dictTypeList && this.dictTypeList.length > 0) {
  41. console.log(`[DictMixin] Component created, loading dictionaries: ${this.dictTypeList.join(', ')}`);
  42. this.loadDict();
  43. }
  44. },
  45. methods: {
  46. /**
  47. * 加载字典数据
  48. * @param {Array} dictTypes - 字典类型数组,如果不传则使用组件中定义的dictTypeList
  49. * @returns {Promise} - 返回字典加载的Promise对象
  50. */
  51. loadDict(dictTypes) {
  52. const types = dictTypes || this.dictTypeList;
  53. if (!types || types.length === 0) {
  54. return Promise.resolve({});
  55. }
  56. // 标记加载中
  57. this.dictLoading = true;
  58. // 需要从服务器获取的字典类型
  59. const needFetch = [];
  60. // 检查是否有静态字典或缓存
  61. types.forEach(type => {
  62. // 先检查是否有静态字典
  63. if (staticDict[type]) {
  64. // 使用静态字典数据
  65. console.log(`[DictMixin] Using static dictionary for ${type}`);
  66. this.$set(this.dictData, type, staticDict[type]);
  67. } else {
  68. // 检查缓存
  69. const cachedDict = this.getDictFromCache(type);
  70. if (cachedDict) {
  71. // 已有缓存,直接使用
  72. console.log(`[DictMixin] Using cached dictionary for ${type}`);
  73. this.$set(this.dictData, type, cachedDict);
  74. } else if (!dictCache.loading[type]) {
  75. // 需要从服务器获取,并且当前没有其他组件正在加载
  76. console.log(`[DictMixin] Need to fetch dictionary ${type} from server`);
  77. needFetch.push(type);
  78. } else {
  79. console.log(
  80. `[DictMixin] Dictionary ${type} is already being loaded by another component, waiting...`
  81. );
  82. }
  83. }
  84. });
  85. // 如果所有字典都已缓存或使用静态数据,直接返回
  86. if (needFetch.length === 0) {
  87. this.dictLoading = false;
  88. // 检查是否有正在加载的字典,如果有,等待它们完成
  89. const loadingTypes = types.filter(type => dictCache.loading[type]);
  90. if (loadingTypes.length > 0) {
  91. return this.waitForDictLoading(loadingTypes);
  92. }
  93. return Promise.resolve(this.dictData);
  94. }
  95. // 从服务器获取字典数据
  96. return this.fetchDictData(needFetch).then(res => {
  97. this.dictLoading = false;
  98. console.log("this.dictData", this.dictData);
  99. return this.dictData;
  100. }).catch(err => {
  101. this.dictLoading = false;
  102. console.error('加载字典数据失败:', err);
  103. return Promise.reject(err);
  104. });
  105. },
  106. /**
  107. * 等待指定类型的字典加载完成
  108. * @param {Array} types - 字典类型数组
  109. * @returns {Promise} - 返回等待的Promise对象
  110. */
  111. waitForDictLoading(types) {
  112. return new Promise(resolve => {
  113. const checkInterval = setInterval(() => {
  114. const stillLoading = types.some(type => dictCache.loading[type]);
  115. if (!stillLoading) {
  116. clearInterval(checkInterval);
  117. // 加载完成后,从缓存中获取数据
  118. types.forEach(type => {
  119. const cachedDict = this.getDictFromCache(type);
  120. if (cachedDict) {
  121. this.$set(this.dictData, type, cachedDict);
  122. }
  123. });
  124. resolve(this.dictData);
  125. }
  126. }, 50);
  127. });
  128. },
  129. /**
  130. * 从服务器获取字典数据
  131. * - 单个字典类型 → 单次请求
  132. * - 多个字典类型 → 并发请求
  133. * - 支持部分失败:失败的字典会忽略,成功的字典会返回
  134. *
  135. * @param {Array} dictTypes - 字典类型数组
  136. * @returns {Promise<Object|Array>}
  137. * - 单个字典时返回 dictList 数组
  138. * - 多个字典时返回 { dictType: dictList } 的 Map
  139. */
  140. fetchDictData(dictTypes) {
  141. // 标记这些字典类型正在加载
  142. dictTypes.forEach(type => {
  143. dictCache.loading[type] = true;
  144. });
  145. // --- 单个字典类型 ---
  146. if (dictTypes.length === 1) {
  147. const dictType = dictTypes[0];
  148. console.log(`[DictMixin] Fetching single dictionary: ${dictType}`);
  149. return getDictData(dictType).then(res => {
  150. if (res.data.code === 200) {
  151. let dictList = [];
  152. if (dictType === 'mall_product_category') {
  153. dictList.push({
  154. dictLabel: '推荐',
  155. dictValue: '-1'
  156. });
  157. dictList.push(...res.data.data);
  158. } else {
  159. dictList = res.data.data;
  160. }
  161. // 更新组件数据和缓存
  162. this.$set(this.dictData, dictType, dictList);
  163. this.updateDictCache(dictType, dictList);
  164. delete dictCache.loading[dictType];
  165. return dictList;
  166. } else {
  167. console.error(`获取字典[${dictType}]数据失败:`, res.data.msg);
  168. delete dictCache.loading[dictType];
  169. return Promise.reject(res.data.msg);
  170. }
  171. }).catch(err => {
  172. delete dictCache.loading[dictType];
  173. throw err;
  174. });
  175. }
  176. // --- 多个字典类型,并发请求 ---
  177. console.log(`[DictMixin] Concurrently fetching ${dictTypes.length} dictionaries: ${dictTypes.join(', ')}`);
  178. const requests = dictTypes.map(dictType => {
  179. return getDictData(dictType).then(res => {
  180. if (res.data.code === 200) {
  181. let dictList = [];
  182. if (dictType === 'mall_product_category') {
  183. dictList.push({
  184. dictLabel: '推荐',
  185. dictValue: '-1'
  186. });
  187. dictList.push(...res.data.data);
  188. } else {
  189. dictList = res.data.data;
  190. }
  191. this.$set(this.dictData, dictType, dictList);
  192. this.updateDictCache(dictType, dictList);
  193. delete dictCache.loading[dictType];
  194. return {
  195. dictType,
  196. dictList,
  197. success: true
  198. };
  199. } else {
  200. console.error(`获取字典[${dictType}]数据失败:`, res.data.msg);
  201. delete dictCache.loading[dictType];
  202. return {
  203. dictType,
  204. dictList: [],
  205. success: false,
  206. msg: res.data.msg
  207. };
  208. }
  209. }).catch(err => {
  210. console.error(`获取字典[${dictType}]异常:`, err);
  211. delete dictCache.loading[dictType];
  212. return {
  213. dictType,
  214. dictList: [],
  215. success: false,
  216. msg: err
  217. };
  218. });
  219. });
  220. // 等所有请求完成
  221. return Promise.allSettled(requests).then(results => {
  222. const dictMap = {};
  223. const failed = [];
  224. results.forEach(r => {
  225. if (r.status === 'fulfilled') {
  226. const {
  227. dictType,
  228. dictList,
  229. success,
  230. msg
  231. } = r.value;
  232. if (success) {
  233. dictMap[dictType] = dictList;
  234. } else {
  235. failed.push({
  236. dictType,
  237. msg
  238. });
  239. }
  240. } else {
  241. // 理论上不会走这里,因为 catch 已经返回对象了
  242. console.error(`字典请求失败:`, r.reason);
  243. }
  244. });
  245. console.log(
  246. `[DictMixin] Loaded ${Object.keys(dictMap).length} dictionaries, failed ${failed.length}`
  247. );
  248. if (failed.length > 0) {
  249. console.warn('以下字典加载失败:', failed);
  250. }
  251. return dictMap;
  252. });
  253. },
  254. /**
  255. * 从服务器获取字典数据
  256. * @param {Array} dictTypes - 字典类型数组
  257. * @returns {Promise} - 返回字典获取的Promise对象
  258. */
  259. // fetchDictData(dictTypes) {
  260. // // 标记这些字典类型正在加载
  261. // dictTypes.forEach(type => {
  262. // dictCache.loading[type] = true;
  263. // });
  264. // if (dictTypes.length === 1) {
  265. // // 单个字典类型,直接获取
  266. // console.log(`[DictMixin] Fetching single dictionary: ${dictTypes[0]}`);
  267. // return getDictData(dictTypes[0]).then(res => {
  268. // if (res.data.code === 200) {
  269. // const dictType = dictTypes[0];
  270. // var dictList = []
  271. // if(dictType === 'mall_product_category'){
  272. // dictList.push({
  273. // dictLabel:'推荐',
  274. // dictValue:'-1'
  275. // })
  276. // // 展开数组追加
  277. // dictList.push(...res.data.data);
  278. // }else{
  279. // dictList = res.data.data
  280. // }
  281. // // 更新组件字典数据
  282. // this.$set(this.dictData, dictType, dictList);
  283. // // 更新缓存
  284. // this.updateDictCache(dictType, dictList);
  285. // // 取消加载标记
  286. // delete dictCache.loading[dictType];
  287. // return dictList;
  288. // } else {
  289. // console.error(`获取字典[${dictTypes[0]}]数据失败:`, res.data.msg);
  290. // // 取消加载标记
  291. // delete dictCache.loading[dictTypes[0]];
  292. // return Promise.reject(res.data.msg);
  293. // }
  294. // }).catch(err => {
  295. // // 发生错误时取消加载标记
  296. // delete dictCache.loading[dictTypes[0]];
  297. // throw err;
  298. // });
  299. // } else {
  300. // // 多个字典类型,批量获取
  301. // console.log(`[DictMixin] Batch fetching ${dictTypes.length} dictionaries: ${dictTypes.join(', ')}`);
  302. // return getMultipleDictData(dictTypes).then(res => {
  303. // if (res.data.code === 200) {
  304. // const dictMap = res.data.data || {};
  305. // // 更新组件字典数据和缓存
  306. // Object.keys(dictMap).forEach(dictType => {
  307. // const dictList = dictMap[dictType] || [];
  308. // // 更新组件字典数据
  309. // this.$set(this.dictData, dictType, dictList);
  310. // // 更新缓存
  311. // this.updateDictCache(dictType, dictList);
  312. // // 取消加载标记
  313. // delete dictCache.loading[dictType];
  314. // });
  315. // console.log(`[DictMixin] Successfully loaded ${Object.keys(dictMap).length} dictionaries`);
  316. // return dictMap;
  317. // } else {
  318. // console.error(`获取字典数据失败:`, res.data.msg);
  319. // // 取消所有加载标记
  320. // dictTypes.forEach(type => {
  321. // delete dictCache.loading[type];
  322. // });
  323. // return Promise.reject(res.data.msg);
  324. // }
  325. // }).catch(err => {
  326. // // 发生错误时取消所有加载标记
  327. // dictTypes.forEach(type => {
  328. // delete dictCache.loading[type];
  329. // });
  330. // throw err;
  331. // });
  332. // }
  333. // },
  334. /**
  335. * 从缓存中获取字典数据
  336. * @param {String} dictType - 字典类型
  337. * @returns {Array|null} - 返回字典数据,不存在或已过期则返回null
  338. */
  339. getDictFromCache(dictType) {
  340. // 判断是否有缓存
  341. if (!dictCache.data[dictType]) {
  342. return null;
  343. }
  344. // 判断缓存是否过期
  345. const lastUpdateTime = dictCache.lastUpdateTime[dictType] || 0;
  346. const now = Date.now();
  347. if (now - lastUpdateTime > dictCache.expireTime) {
  348. // 缓存已过期,删除缓存
  349. delete dictCache.data[dictType];
  350. delete dictCache.lastUpdateTime[dictType];
  351. return null;
  352. }
  353. // 返回缓存的字典数据
  354. return dictCache.data[dictType];
  355. },
  356. /**
  357. * 更新字典缓存
  358. * @param {String} dictType - 字典类型
  359. * @param {Array} dictList - 字典数据列表
  360. */
  361. updateDictCache(dictType, dictList) {
  362. dictCache.data[dictType] = dictList;
  363. dictCache.lastUpdateTime[dictType] = Date.now();
  364. // 更新本地存储
  365. try {
  366. // 只存储最后更新时间,具体数据保存在内存中
  367. storage.setDict(`dict_time_${dictType}`, dictCache.lastUpdateTime[dictType]);
  368. } catch (e) {
  369. console.error('更新字典缓存失败:', e);
  370. }
  371. },
  372. /**
  373. * 清除字典缓存
  374. * @param {String} dictType - 字典类型,不传则清除所有缓存
  375. */
  376. clearDictCache(dictType) {
  377. if (dictType) {
  378. delete dictCache.data[dictType];
  379. delete dictCache.lastUpdateTime[dictType];
  380. storage.removeDict(`dict_time_${dictType}`);
  381. } else {
  382. dictCache.data = {};
  383. dictCache.lastUpdateTime = {};
  384. // 清除所有字典相关的本地存储
  385. const keys = Object.keys(localStorage);
  386. keys.forEach(key => {
  387. if (key.startsWith('dict_time_')) {
  388. storage.removeDict(key);
  389. }
  390. });
  391. }
  392. },
  393. /**
  394. * 根据字典值获取对应的字典标签
  395. * @param {String} dictType - 字典类型
  396. * @param {String|Number} value - 字典值
  397. * @param {String} defaultLabel - 默认标签
  398. * @returns {String} - 字典标签
  399. */
  400. getDictLabel(dictType, value, defaultLabel = '') {
  401. // 首先检查组件数据
  402. const dictList = this.dictData[dictType];
  403. if (dictList) {
  404. const item = dictList.find(dict => dict.dictValue === value);
  405. if (item) return item.dictLabel;
  406. }
  407. // 再检查静态字典
  408. // const staticDictList = staticDict[dictType];
  409. // if (staticDictList) {
  410. // const item = staticDictList.find(dict => dict.value === value);
  411. // if (item) return item.label;
  412. // }
  413. // 都没找到,返回默认值
  414. return defaultLabel;
  415. },
  416. /**
  417. * 根据字典标签获取对应的字典值
  418. * @param {String} dictType - 字典类型
  419. * @param {String} label - 字典标签
  420. * @param {String|Number} defaultValue - 默认值
  421. * @returns {String|Number} - 字典值
  422. */
  423. getDictValue(dictType, label, defaultValue = '') {
  424. // 首先检查组件数据
  425. const dictList = this.dictData[dictType];
  426. if (dictList) {
  427. const item = dictList.find(dict => dict.label === label);
  428. if (item) return item.value;
  429. }
  430. // 再检查静态字典
  431. const staticDictList = staticDict[dictType];
  432. if (staticDictList) {
  433. const item = staticDictList.find(dict => dict.label === label);
  434. if (item) return item.value;
  435. }
  436. // 都没找到,返回默认值
  437. return defaultValue;
  438. },
  439. /**
  440. * 获取字典列表
  441. * @param {String} dictType - 字典类型
  442. * @returns {Array} - 字典列表
  443. */
  444. getDictList(dictType) {
  445. // 首先检查组件数据
  446. const dictList = this.dictData[dictType];
  447. if (dictList) return dictList;
  448. // 再检查静态字典
  449. return staticDict[dictType] || [];
  450. },
  451. /**
  452. * 获取字典类型对应的样式类
  453. * @param {String} dictType - 字典类型
  454. * @param {String|Number} value - 字典值
  455. * @param {String} defaultClass - 默认样式类
  456. * @returns {String} - 字典项的样式类
  457. */
  458. getDictClass(dictType, value, defaultClass = '') {
  459. // 首先检查组件数据
  460. const dictList = this.dictData[dictType];
  461. if (dictList) {
  462. const item = dictList.find(dict => dict.dictValue === value);
  463. if (item && item.listClass) return item.listClass;
  464. }
  465. // 再检查静态字典
  466. // const staticDictList = staticDict[dictType];
  467. // if (staticDictList) {
  468. // const item = staticDictList.find(dict => dict.value === value);
  469. // if (item && item.listClass) return item.listClass;
  470. // }
  471. // 都没找到,返回默认值
  472. return defaultClass;
  473. }
  474. }
  475. };