日期: 2026-06-22
状态: 待审核
范围: cfc-frontend/components/FamilyRelationGraph.vue 完整重构
现有 FamilyRelationGraph 使用 Canvas 2D 极坐标布局绘制家庭成员关系图。存在以下问题:
用 View + Canvas 混合架构替换纯 Canvas 实现,实现:
| 决策项 | 选择 | 理由 |
|---|---|---|
| 与 FamilyRelationGraph 关系 | 完全替换(同名同接口) | 避免上层调用方改动 |
| 渲染架构 | View(布局+交互)+ Canvas(只画线) | 方案A,flex 布局天然适配多人数规则 |
| 每个成员展示 | 头像 + 昵称 | 能量值用节点大小体现 |
| 能量可视化方式 | 节点大小(不用环形进度条) | 用户明确要求 |
| 连线 | 保留,Canvas 绘制 | 信任度→颜色,沟通度→粗细 |
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
dimensionCode |
String | 必填 | 维度编码:body/mind/action/home,决定主题色 |
selfId |
Number|String | null | 当前用户 ID |
members |
Array | [] |
成员列表 [{id, nickname, memberType}] |
energyMap |
Object | {} |
能量数据 {memberId: {bodyScore, mindScore, actionScore}} |
intimacyMap |
Object | {} |
亲密度 {memberId: {closeness, communication, trust}} |
interactive |
Boolean | true | 是否可点击 |
| 事件 | 参数 | 触发条件 |
|---|---|---|
memberTap |
{memberId, memberType, nickname} |
interactive=true 时点击非 self 成员 |
members.length === 0:渲染空状态(文字"暂无家庭成员")members.length === 1:不渲染(组件返回空 v-if,非 v-show 隐藏)members.length >= 2:按规则渲染2 人:
容器: flex, flex-direction: row, justify-content: space-around, align-items: center
[我] [对方]
self 在左,对方在右。单行横排。
3 人:
容器: flex, flex-direction: row, justify-content: space-between, align-items: center
数据重排: [其他, self, 其他]
[对方] [我] [对方]
self 在中间,其余两人分列左右。
4+ 人:
两行结构:
第一行: flex, justify-content: center — 只有 [我]
第二行+: flex, flex-wrap: wrap, justify-content: center — 其他成员
[我]
[爸] [妈] [大宝] [二宝]
核心原则:用宽度,省高度。其他成员尽量排在一行,排不下才换行。
members 中分离出 self 和 otherslayoutMode 排列:
layoutMode = 'row' (2-3人): self 和 others 在同一行,按 [other1, self, other2, ...] 排列layoutMode = 'multi-row' (4+人): self 占第一行居中,others 在第二行 flex-wrap 排列能量值映射到头像直径:
//能量值来自当前维度的 score(如 dimensionCode='body' 则取 bodyScore)
//energy = energyMap[member.id][dimensionCode + 'Score'] || 0
//范围 0-100
size = 64 + (Math.max(0, Math.min(100, energy)) / 100) * 56 // 单位: rpx, 范围 64-120,clamped to 0-100
| memberType | 形状 | CSS |
|---|---|---|
| parent | 圆角方形 | border-radius: 16rpx |
| child | 圆形 | border-radius: 50% |
| partner/spouse | 圆形 + 底部心形装饰 | 圆形 + ::after 心形 |
| 数据维度 | 映射目标 | 映射规则 |
|---|---|---|
| 信任度 trust | 线颜色 | 0 → hsl(0°,75%,45%) 红色; 100 → hsl(120°,75%,45%) 绿色 |
| 沟通度 communication | 线粗细 | 0 → 2px; 100 → 10px; 公式: 2 + (comm/100)*8 |
| 全局 | 透明度 | α = 0.6 |
mounted / watch(members, energyMap, intimacyMap):
1. Vue 渲染 View 节点(flex 自动布局完成)
2. $nextTick →
a. 遍历每个节点,通过 getBoundingClientRect 读取屏幕坐标
b. 计算 self 中心坐标和每个 other 中心坐标
c. 初始化 Canvas(type=2d),设置 dpr
d. 清除 Canvas → drawLines() 画所有 self→other 连线
interactive=true(默认):View 节点绑定 @tap → $emit('memberTap', {memberId, memberType, nickname})interactive=false:不绑定 tap 事件pointer-events: none,所有交互由 View 处理| 文件 | 改动类型 | 说明 |
|---|---|---|
components/FamilyRelationGraph.vue |
重写 | View+Canvas 替换纯 Canvas |
| 所有引用 FamilyRelationGraph 的页面 | 无改动 | Props 接口不变,drop-in 替换 |
引用方列表(无需改动):
pages/index/parent-index.vuepages/body/index.vuepages/mind/index.vuepages/action/index.vuepages/body/member-body-detail.vuepages/mind/member-mind-detail.vuepages/action/member-action-detail.vuepages/index/member-home-detail.vuepages/wealth/index.vuepages/profile/profile.vue