# 能量沙盘组件全面重构设计 **日期**: 2026-06-08 **状态**: 待实施 **范围**: cfc-frontend/components/wuxing-sandbox.vue 及 3 个消费页面 ## 1. 背景与目标 当前 `wuxing-sandbox.vue` 是一个 812 行的单文件组件,混合了 5 种职责(Canvas 渲染、填充动画、点击检测、视图模式切换、UI 编排),并包含 5 处死代码。`sandbox` 对象 prop 耦合后端 DTO 结构,导致消费页面需要 `safeSandbox` hack 规避 WXML 编译问题。 **目标**: 1. 将 812 行单文件拆分为 1 个薄编排组件 + 3 个纯 JS 工具模块 2. 重新设计 props/events/slots 接口,扁平化、显式化 3. 清理死代码(约 50 行) 4. 保持 3 个消费页面的导入路径不变,仅适配接口 ## 2. 架构方案:单组件 + 提取工具模块 ### 2.1 文件结构 ``` components/ ├── wuxing-sandbox.vue # 主组件(orchestrator,~250行) └── wuxing-sandbox-helpers/ ├── render.js # Canvas 渲染函数(~180行) ├── animation.js # 动画控制器类(~80行) └── hitdetect.js # 点击检测工具(~50行) ``` ### 2.2 为什么不用子组件拆分? uni-app 微信小程序中,`` 与 HTML 元素(star-point 标签)在同一组件层级才能正确叠放。拆成子组件会引入: - canvas `createSelectorQuery().in(this)` 作用域问题 - 跨组件 canvas 坐标对齐复杂性 - HTML 覆盖层 z-index 在子组件间不可靠 提取为纯 JS 工具模块是 uni-app Vue 2 下最安全的方式,无 mixin 命名冲突风险,函数可独立测试。 ## 3. 接口设计 ### 3.1 Props(扁平化替代 sandbox 对象) | Prop | Type | Default | 说明 | |------|------|---------|------| | `mode` | String | `'preview'` | `'preview'` 随机数据 / `'live'` 真实数据 | | `scores` | Object | `{body:0,mind:0,wisdom:0,action:0,wealth:0}` | 五维分值(0-100) | | `overallScore` | Number | `0` | 综合分值 | | `members` | Array | `[]` | 家庭成员数组(MemberEnergyDTO 格式) | | `badgeText` | String | `''` | 顶部徽章文字,空则自动计算 | | `showDimensionBar` | Boolean | `true` | 是否显示底部维度条 | | `starHeight` | String | `'480rpx'` | 星图区域高度 | **关键变更**:删除 `sandbox` 对象 prop,用 `scores` + `overallScore` + `members` 替代。组件内部处理 null/undefined,消费页面无需 `safeSandbox` hack。 ### 3.2 Events | Event | Payload | 说明 | |-------|---------|------| | `@point-click` | `{domain, viewMode, member}` | 点击星臂维度(保留原有) | | `@center-click` | `{viewMode, selectedMemberIndex}` | 点击中心太极区域 | | `@mode-change` | `{viewMode, selectedMemberIndex}` | 家庭↔个人模式切换后触发 | ### 3.3 Slots | Slot | 说明 | |------|------| | `header` | 替换默认标题+徽章区域 | | `dimension-bar` | 替换默认维度详情条 | ### 3.4 内部状态(非 prop) - `viewMode`: `'family'` | `'member'` — 当前视图 - `selectedMemberIndex`: Number — 当前选中成员下标 - `previewScores`: Array — 预览模式随机值 - `canvasReady`, `canvasWidth`, `canvasHeight`, `ctx`, `dpr` — Canvas 状态 - `animBody/Mind/Wisdom/Action/Wealth` — 动画插值 ## 4. 工具模块详细设计 ### 4.1 render.js — Canvas 渲染(纯函数,无状态) ```js // 导出 export function calcStarVertices(cx, cy, outerR) → { outer: [{x,y}], inner: [{x,y}] } export function renderStar(ctx, w, h, ts, options) → void export function drawTaiji(ctx, cx, cy, r) → void // ts = [body, mind, wisdom, action, wealth] 归一化值 (0~1) // options = { colors, outerR, showTaiji, taijiLabel } ``` `renderStar` 内部调用 `calcStarVertices`、`drawTaiji`,一次调用完成整个星图渲染。 ### 4.2 animation.js — 动画控制器 ```js export class FillAnimator { constructor(getCurrentTs, onFrame, onComplete) start(targetTs, duration = 600) → void // ease-out cubic cancel() → void // 清理 timer } ``` - `getCurrentTs()` 返回当前 5 维插值数组 - `onFrame(interpolatedTs)` 每帧回调 - `onComplete()` 动画完成回调 - 使用 `setTimeout(fn, 16)` 模拟 requestAnimationFrame(uni-app 兼容) ### 4.3 hitdetect.js — 点击检测 ```js export function pointInPolygon(x, y, polygon) → boolean export function detectHitArm(relX, relY, cx, cy, outerR) → number // 0~4 或 -1 ``` `detectHitArm` 封装了星臂多边形计算 + 遍历检测,返回命中星臂索引。 ## 5. 主组件 wuxing-sandbox.vue 结构(~250 行) ```