useDimensionRegistry.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import config from '@/config.js'
  2. /**
  3. * useDimensionRegistry - 统一维度注册中心(前端只读封装)
  4. *
  5. * 健康页 5 张雷达图(身体 / 认知 / 心理 / 社会性 / 财富)原前端硬编码维度清单与取分字段,
  6. * 现改为从统一维度注册中心 `/api/dimension-registry/tree` 配置驱动取数画图
  7. * (落实产品要求「健康页面的雷达图也是按这个配置取数据画图」)。
  8. *
  9. * 设计要点:
  10. * 1. 模块级缓存:首次请求成功后整棵树缓存,tab 切换不再重复请求。
  11. * 2. ready 标志:请求成功或失败后均置位,供页面安全兜底(后端未上线 / 接口异常 → 回落硬编码)。
  12. * 3. 失败不抛错:ready=false、返回空,页面降级,不阻断渲染。
  13. * 4. 兼容 uni-app 硬约束:不使用可选链 `?.`、new Date(string)、CSS Grid。
  14. *
  15. * 契约(已与后端 DimensionNode / seed 对齐):
  16. * GET /api/dimension-registry/tree → Result<List<DimensionNode>>
  17. * DimensionNode: { id, code, name, parentId, nodeKind, sourceSystem, legacyAlias, wuxingRuleRef, children }
  18. * legacyAlias 是 JSON 数组字符串,如 '["opennessScore"]',取首个元素作为分数字段名 scoreField。
  19. */
  20. // 模块级缓存:跨组件 / 跨 tab 复用,避免重复请求
  21. var _cache = {
  22. tree: null, // DimensionNode[](5 个根,各带 children)
  23. ready: false, // 首次请求是否已结束(成功或失败均置位)
  24. pending: null // 进行中的 Promise(防并发重复请求)
  25. }
  26. // tab(code) → 注册中心根 code 映射
  27. // body→body,cognitive→wisdom,mental→mind,relationship→action,wealth→wealth
  28. var TAB_TO_ROOT = {
  29. body: 'body',
  30. cognitive: 'wisdom',
  31. mental: 'mind',
  32. relationship: 'action',
  33. wealth: 'wealth'
  34. }
  35. // 财富子维度在「成员(儿童)」语境下使用更贴近儿童的文案(与改造前字面量一致)
  36. var WEALTH_CHILD_LABEL = {
  37. 'wealth.skill': '学业能力',
  38. 'wealth.income': '积分积累'
  39. }
  40. /**
  41. * 解析 legacyAlias:JSON 数组字符串,取首个元素作为分数字段名 scoreField。
  42. * 例:'["opennessScore"]' → 'opennessScore';'["driveAutonomy"]' → 'driveAutonomy'
  43. * @param {string} alias
  44. * @returns {string}
  45. */
  46. function parseLegacyAlias(alias) {
  47. if (!alias || typeof alias !== 'string') return ''
  48. var arr = null
  49. try {
  50. arr = JSON.parse(alias)
  51. } catch (e) {
  52. return ''
  53. }
  54. if (!Array.isArray(arr) || arr.length === 0) return ''
  55. return typeof arr[0] === 'string' ? arr[0] : ''
  56. }
  57. /**
  58. * 触发(或复用)注册中心树拉取。幂等:已尝试过则直接 resolve,不重复请求。
  59. * @returns {Promise<void>}
  60. */
  61. function ensureDimensionRegistry() {
  62. if (_cache.ready) {
  63. return Promise.resolve()
  64. }
  65. if (_cache.pending) {
  66. return _cache.pending
  67. }
  68. _cache.pending = new Promise(function(resolve) {
  69. var token = uni.getStorageSync('token')
  70. var base = (config && config.API_BASE_URL) ? config.API_BASE_URL : ''
  71. var url = base + '/api/dimension-registry/tree'
  72. uni.request({
  73. url: url,
  74. method: 'GET',
  75. header: {
  76. 'Content-Type': 'application/json',
  77. 'Authorization': token ? 'Bearer ' + token : ''
  78. },
  79. success: function(res) {
  80. try {
  81. var data = res && res.data
  82. // Result<List<DimensionNode>> → data.data
  83. var list = (data && data.code === 200 && data.data) ? data.data : []
  84. if (Array.isArray(list) && list.length > 0) {
  85. _cache.tree = list
  86. }
  87. } catch (e) {
  88. // 解析异常:保持 tree 为空,回落兜底
  89. _cache.tree = null
  90. }
  91. // 无论成功 / 空数据 / 解析异常,均标记尝试结束(不抛错)
  92. _cache.ready = true
  93. resolve()
  94. },
  95. fail: function() {
  96. // 网络 / 接口异常:不抛错,回落硬编码兜底
  97. _cache.ready = true
  98. resolve()
  99. }
  100. })
  101. })
  102. // 请求结束后清空进行中引用(tree / ready 仍保留)
  103. _cache.pending.then(function() { _cache.pending = null })
  104. return _cache.pending
  105. }
  106. /**
  107. * 注册中心树是否就绪(成功取到数据)。
  108. * @returns {boolean}
  109. */
  110. function isRegistryReady() {
  111. return !!_cache.tree && _cache.tree.length > 0
  112. }
  113. /**
  114. * 取某 tab 的雷达配置:[{ label, code, scoreField }]
  115. * - label:注册中心节点 name(维度中文名)
  116. * - code:节点 code(body 类为 body.growth,用于 bodyData 按 dimension 字段匹配)
  117. * - scoreField:legacyAlias 首元素(取分读取器字段名)
  118. * @param {string} tabCode
  119. * @returns {Array}
  120. */
  121. function getRadarConfig(tabCode) {
  122. var rootCode = TAB_TO_ROOT[tabCode]
  123. if (!rootCode || !_cache.tree || !Array.isArray(_cache.tree)) return []
  124. var root = null
  125. for (var i = 0; i < _cache.tree.length; i++) {
  126. var node = _cache.tree[i]
  127. if (node && node.code === rootCode) {
  128. root = node
  129. break
  130. }
  131. }
  132. if (!root || !root.children || !root.children.length) return []
  133. var config = []
  134. for (var j = 0; j < root.children.length; j++) {
  135. var child = root.children[j]
  136. if (!child) continue
  137. // P1-T3 防御:指标节点(nodeKind=indicator)是维度树的混排子节点,
  138. // 不应作为健康页雷达图的轴(雷达轴应为 sub-dim 维度节点)。
  139. // 操作者将指标挂到某维度节点下后,该节点 children 会含指标,过滤掉保持雷达 5×7/7/10/5/4 不变。
  140. if (child.nodeKind === 'indicator') continue
  141. config.push({
  142. label: child.name || '',
  143. code: child.code || '',
  144. scoreField: parseLegacyAlias(child.legacyAlias)
  145. })
  146. }
  147. return config
  148. }
  149. /**
  150. * 取注册中心 5 根节点元信息(五行归属):[{ code, name, element: wuxingRuleRef }]。
  151. *
  152. * <p>供五行能量沙盘维度名称 / 五行元素兜底(统一 code + wuxing_rule_ref),
  153. * 未就绪返回空数组(调用方回落组件内 DIM_META)。</p>
  154. *
  155. * @returns {Array<{code:string,name:string,element:string}>}
  156. */
  157. function getRegistryRootMeta() {
  158. if (!_cache.tree || !Array.isArray(_cache.tree)) return []
  159. var out = []
  160. for (var i = 0; i < _cache.tree.length; i++) {
  161. var node = _cache.tree[i]
  162. if (!node || !node.code) continue
  163. // 仅维度根节点(nodeKind=root;旧数据无 nodeKind 的按「无父节点」视为根)
  164. if (node.nodeKind && node.nodeKind !== 'root') continue
  165. out.push({
  166. code: node.code,
  167. name: node.name || '',
  168. element: node.wuxingRuleRef || ''
  169. })
  170. }
  171. return out
  172. }
  173. export {
  174. ensureDimensionRegistry,
  175. getRadarConfig,
  176. getRegistryRootMeta,
  177. isRegistryReady,
  178. WEALTH_CHILD_LABEL
  179. }