useDict.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /**
  2. * 字典数据管理 Composable
  3. * 用于加载和缓存字典数据
  4. *
  5. * 使用示例:
  6. * import { useDict } from '@/composables/useDict'
  7. *
  8. * // 在 setup 中使用
  9. * const { dict } = useDict('case_status', 'matter_type')
  10. *
  11. * // 访问字典数据
  12. * dict.case_status // [{ label: '待处理', value: '1', ... }, ...]
  13. */
  14. import { onMounted, ref } from 'vue'
  15. import { request } from '@/utils'
  16. // 全局字典缓存
  17. const dictCache = new Map()
  18. const dictPendingCache = new Map()
  19. /**
  20. * 加载字典数据
  21. * @param {string} dictType - 字典类型
  22. * @returns {Promise<Array>} 字典数据列表
  23. */
  24. async function loadDictData(dictType) {
  25. try {
  26. const res = await request.get('/system/dict/data/list', {
  27. params: { dictType },
  28. needTip: false,
  29. })
  30. if (res.code === 200) {
  31. // 转换为标准格式:{ label, value, ... }
  32. const dictList = (res.data || []).map(item => ({
  33. label: item.dictLabel,
  34. value: item.dictValue,
  35. dictCode: item.dictCode,
  36. dictSort: item.dictSort,
  37. parentDictCode: item.parentDictCode,
  38. linkedDictType: item.linkedDictType,
  39. linkedDictValue: item.linkedDictValue,
  40. cssClass: item.cssClass,
  41. listClass: item.listClass || 'default', // 默认为 default
  42. isDefault: item.isDefault,
  43. status: item.dictStatus !== undefined ? item.dictStatus : item.status, // 兼容不同字段名
  44. remark: item.remark,
  45. raw: item, // 保留原始数据
  46. }))
  47. // 按排序字段排序
  48. dictList.sort((a, b) => (a.dictSort || 0) - (b.dictSort || 0))
  49. return dictList
  50. }
  51. throw new Error(res?.message || `字典 ${dictType} 响应无效`)
  52. }
  53. catch (error) {
  54. console.error(`加载字典 ${dictType} 失败:`, error)
  55. throw error
  56. }
  57. }
  58. /**
  59. * 获取原始字典请求。失败时保持 rejected,供 useDict 逐项处理;
  60. * 只有成功响应(包括合法空字典)才会进入全局缓存。
  61. * @param {string} dictType - 字典类型
  62. * @param {boolean} forceReload - 是否强制重新加载
  63. * @returns {Promise<Array>} 字典数据列表
  64. */
  65. function getDictRequest(dictType, forceReload = false) {
  66. if (!forceReload && dictCache.has(dictType))
  67. return Promise.resolve(dictCache.get(dictType))
  68. // 强制刷新也复用正在进行的请求,避免同一字典并发重复加载。
  69. if (dictPendingCache.has(dictType))
  70. return dictPendingCache.get(dictType)
  71. const pending = loadDictData(dictType)
  72. .then((data) => {
  73. dictCache.set(dictType, data)
  74. return data
  75. })
  76. .finally(() => {
  77. if (dictPendingCache.get(dictType) === pending)
  78. dictPendingCache.delete(dictType)
  79. })
  80. dictPendingCache.set(dictType, pending)
  81. return pending
  82. }
  83. /**
  84. * 获取字典数据(带缓存)
  85. * @param {string} dictType - 字典类型
  86. * @param {boolean} forceReload - 是否强制重新加载
  87. * @returns {Promise<Array>} 字典数据列表
  88. */
  89. async function getDictData(dictType, forceReload = false) {
  90. try {
  91. return await getDictRequest(dictType, forceReload)
  92. }
  93. catch {
  94. // 保持公共函数原有的安全返回契约,但失败结果不会污染全局缓存。
  95. return []
  96. }
  97. }
  98. /**
  99. * 清除字典缓存
  100. * @param {string} dictType - 字典类型,不传则清除所有
  101. */
  102. function clearDictCache(dictType) {
  103. if (dictType) {
  104. dictCache.delete(dictType)
  105. dictPendingCache.delete(dictType)
  106. }
  107. else {
  108. dictCache.clear()
  109. dictPendingCache.clear()
  110. }
  111. }
  112. /**
  113. * 字典 Composable
  114. * @param {...string} dictTypes - 字典类型列表
  115. * @returns {object} { dict, loading, errors, reload }
  116. */
  117. export function useDict(...dictTypes) {
  118. const dict = ref({})
  119. const loading = ref(false)
  120. const errors = ref({})
  121. function applySettledResults(types, results) {
  122. const nextErrors = { ...errors.value }
  123. const failedTypes = []
  124. types.forEach((type, index) => {
  125. const result = results[index]
  126. if (result.status === 'fulfilled') {
  127. dict.value[type] = result.value
  128. delete nextErrors[type]
  129. return
  130. }
  131. failedTypes.push(type)
  132. nextErrors[type] = result.reason?.message || `字典 ${type} 加载失败`
  133. })
  134. errors.value = nextErrors
  135. return failedTypes
  136. }
  137. async function loadDictTypes(types, forceReload = false, retryOnce = false) {
  138. const results = await Promise.allSettled(
  139. types.map(type => getDictRequest(type, forceReload)),
  140. )
  141. const failedTypes = applySettledResults(types, results)
  142. if (retryOnce && failedTypes.length > 0) {
  143. const retryResults = await Promise.allSettled(
  144. failedTypes.map(type => getDictRequest(type, true)),
  145. )
  146. applySettledResults(failedTypes, retryResults)
  147. }
  148. }
  149. /**
  150. * 加载所有字典
  151. */
  152. async function loadAllDicts() {
  153. if (dictTypes.length === 0)
  154. return
  155. loading.value = true
  156. try {
  157. await loadDictTypes(dictTypes, false, true)
  158. }
  159. catch (error) {
  160. console.error('加载字典失败:', error)
  161. }
  162. finally {
  163. loading.value = false
  164. }
  165. }
  166. /**
  167. * 重新加载字典
  168. * @param {...string} types - 要重新加载的字典类型,不传则重新加载所有
  169. */
  170. async function reload(...types) {
  171. const typesToReload = types.length > 0 ? types : dictTypes
  172. loading.value = true
  173. try {
  174. await loadDictTypes(typesToReload, true)
  175. }
  176. catch (error) {
  177. console.error('重新加载字典失败:', error)
  178. }
  179. finally {
  180. loading.value = false
  181. }
  182. }
  183. /**
  184. * 根据字典值获取标签
  185. * @param {string} dictType - 字典类型
  186. * @param {string | number} value - 字典值
  187. * @returns {string} 字典标签
  188. */
  189. function getLabel(dictType, value) {
  190. const dictList = dict.value[dictType] || []
  191. const item = dictList.find(d => String(d.value) === String(value))
  192. return item ? item.label : value
  193. }
  194. /**
  195. * 根据字典值获取字典项
  196. * @param {string} dictType - 字典类型
  197. * @param {string | number} value - 字典值
  198. * @returns {object | null} 字典项
  199. */
  200. function getDict(dictType, value) {
  201. const dictList = dict.value[dictType] || []
  202. return dictList.find(d => String(d.value) === String(value)) || null
  203. }
  204. // 组件挂载时加载字典
  205. onMounted(() => {
  206. loadAllDicts()
  207. })
  208. return {
  209. dict,
  210. loading,
  211. errors,
  212. reload,
  213. getLabel,
  214. getDict,
  215. }
  216. }
  217. /**
  218. * 导出工具函数
  219. */
  220. export {
  221. clearDictCache,
  222. getDictData,
  223. }