dictMixin.js 16 KB

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