import config from '@/config.js' /** * useDimensionRegistry - 统一维度注册中心(前端只读封装) * * 健康页 5 张雷达图(身体 / 认知 / 心理 / 社会性 / 财富)原前端硬编码维度清单与取分字段, * 现改为从统一维度注册中心 `/api/dimension-registry/tree` 配置驱动取数画图 * (落实产品要求「健康页面的雷达图也是按这个配置取数据画图」)。 * * 设计要点: * 1. 模块级缓存:首次请求成功后整棵树缓存,tab 切换不再重复请求。 * 2. ready 标志:请求成功或失败后均置位,供页面安全兜底(后端未上线 / 接口异常 → 回落硬编码)。 * 3. 失败不抛错:ready=false、返回空,页面降级,不阻断渲染。 * 4. 兼容 uni-app 硬约束:不使用可选链 `?.`、new Date(string)、CSS Grid。 * * 契约(已与后端 DimensionNode / seed 对齐): * GET /api/dimension-registry/tree → Result> * DimensionNode: { id, code, name, parentId, nodeKind, sourceSystem, legacyAlias, wuxingRuleRef, children } * legacyAlias 是 JSON 数组字符串,如 '["opennessScore"]',取首个元素作为分数字段名 scoreField。 */ // 模块级缓存:跨组件 / 跨 tab 复用,避免重复请求 var _cache = { tree: null, // DimensionNode[](5 个根,各带 children) ready: false, // 首次请求是否已结束(成功或失败均置位) pending: null // 进行中的 Promise(防并发重复请求) } // tab(code) → 注册中心根 code 映射 // body→body,cognitive→wisdom,mental→mind,relationship→action,wealth→wealth var TAB_TO_ROOT = { body: 'body', cognitive: 'wisdom', mental: 'mind', relationship: 'action', wealth: 'wealth' } // 财富子维度在「成员(儿童)」语境下使用更贴近儿童的文案(与改造前字面量一致) var WEALTH_CHILD_LABEL = { 'wealth.skill': '学业能力', 'wealth.income': '积分积累' } /** * 解析 legacyAlias:JSON 数组字符串,取首个元素作为分数字段名 scoreField。 * 例:'["opennessScore"]' → 'opennessScore';'["driveAutonomy"]' → 'driveAutonomy' * @param {string} alias * @returns {string} */ function parseLegacyAlias(alias) { if (!alias || typeof alias !== 'string') return '' var arr = null try { arr = JSON.parse(alias) } catch (e) { return '' } if (!Array.isArray(arr) || arr.length === 0) return '' return typeof arr[0] === 'string' ? arr[0] : '' } /** * 触发(或复用)注册中心树拉取。幂等:已尝试过则直接 resolve,不重复请求。 * @returns {Promise} */ function ensureDimensionRegistry() { if (_cache.ready) { return Promise.resolve() } if (_cache.pending) { return _cache.pending } _cache.pending = new Promise(function(resolve) { var token = uni.getStorageSync('token') var base = (config && config.API_BASE_URL) ? config.API_BASE_URL : '' var url = base + '/api/dimension-registry/tree' uni.request({ url: url, method: 'GET', header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' }, success: function(res) { try { var data = res && res.data // Result> → data.data var list = (data && data.code === 200 && data.data) ? data.data : [] if (Array.isArray(list) && list.length > 0) { _cache.tree = list } } catch (e) { // 解析异常:保持 tree 为空,回落兜底 _cache.tree = null } // 无论成功 / 空数据 / 解析异常,均标记尝试结束(不抛错) _cache.ready = true resolve() }, fail: function() { // 网络 / 接口异常:不抛错,回落硬编码兜底 _cache.ready = true resolve() } }) }) // 请求结束后清空进行中引用(tree / ready 仍保留) _cache.pending.then(function() { _cache.pending = null }) return _cache.pending } /** * 注册中心树是否就绪(成功取到数据)。 * @returns {boolean} */ function isRegistryReady() { return !!_cache.tree && _cache.tree.length > 0 } /** * 取某 tab 的雷达配置:[{ label, code, scoreField }] * - label:注册中心节点 name(维度中文名) * - code:节点 code(body 类为 body.growth,用于 bodyData 按 dimension 字段匹配) * - scoreField:legacyAlias 首元素(取分读取器字段名) * @param {string} tabCode * @returns {Array} */ function getRadarConfig(tabCode) { var rootCode = TAB_TO_ROOT[tabCode] if (!rootCode || !_cache.tree || !Array.isArray(_cache.tree)) return [] var root = null for (var i = 0; i < _cache.tree.length; i++) { var node = _cache.tree[i] if (node && node.code === rootCode) { root = node break } } if (!root || !root.children || !root.children.length) return [] var config = [] for (var j = 0; j < root.children.length; j++) { var child = root.children[j] if (!child) continue // P1-T3 防御:指标节点(nodeKind=indicator)是维度树的混排子节点, // 不应作为健康页雷达图的轴(雷达轴应为 sub-dim 维度节点)。 // 操作者将指标挂到某维度节点下后,该节点 children 会含指标,过滤掉保持雷达 5×7/7/10/5/4 不变。 if (child.nodeKind === 'indicator') continue config.push({ label: child.name || '', code: child.code || '', scoreField: parseLegacyAlias(child.legacyAlias) }) } return config } /** * 取注册中心 5 根节点元信息(五行归属):[{ code, name, element: wuxingRuleRef }]。 * *

供五行能量沙盘维度名称 / 五行元素兜底(统一 code + wuxing_rule_ref), * 未就绪返回空数组(调用方回落组件内 DIM_META)。

* * @returns {Array<{code:string,name:string,element:string}>} */ function getRegistryRootMeta() { if (!_cache.tree || !Array.isArray(_cache.tree)) return [] var out = [] for (var i = 0; i < _cache.tree.length; i++) { var node = _cache.tree[i] if (!node || !node.code) continue // 仅维度根节点(nodeKind=root;旧数据无 nodeKind 的按「无父节点」视为根) if (node.nodeKind && node.nodeKind !== 'root') continue out.push({ code: node.code, name: node.name || '', element: node.wuxingRuleRef || '' }) } return out } export { ensureDimensionRegistry, getRadarConfig, getRegistryRootMeta, isRegistryReady, WEALTH_CHILD_LABEL }