Designer: 系统分析员 (System Analyst) Status: 设计稿第一版
Scope: 小程序端 3 个 TabBar 页(身体/心智/行动)+ 相关子页面 + 后端补充接口
根据用户要求的 6 条设计原则:
| # | 原则 | 实现方式 |
|---|---|---|
| 1 | 三个页面都包含商品、活动、任务 | 每页底部固定「推荐商品」「热门活动」「今日任务」三个通用区块,按当前维度筛选 |
| 2 | 文章都在心智里 | 只有「心智」页展示文章列表及阅读统计;身体页和行动页不出现文章板块 |
| 3 | 身体包含各种体检数据 | 利用已有 health_reports + health_indicators 展示健康报告摘要、指标解读 |
| 4 | 心智包括 DAN 成长测评+认知6个维度 | 保留原有心 · 智双 Tab 结构,融合 DAN 结果展示 + 六维雷达图 |
| 5 | 每页显示当前登录用户信息+任务/活动快捷入口 | 在能量柱状图下方统一展示用户身份 + 三个快捷按钮 |
| 6 | 每页顶部横放柱状图:家庭总能量 + 各成员能量值,可点击跳转 | 利用 /api/energy/sandbox 数据渲染,成员条可点击跳转到对应维度详情 |
┌───────────────────────────────────────────────────────────┐
│ PageBanner (dimension 主题) │
├───────────────────────────────────────────────────────────┤
│ ① 家庭能量柱状图(横放) │
│ 「全家」 ████████████████████ 85 │
│ 「妈妈」 ████████████████ 72 ← 可点击跳转 │
│ 「爸爸」 ████████████████████ 80 ← 可点击跳转 │
│ 「小宝」 ██████████████ 65 ← 可点击跳转 │
├───────────────────────────────────────────────────────────┤
│ ② 当前用户信息 + 快捷入口 │
│ 👤 当前:妈妈(家长) │
│ [📋 今日任务] [🔥 相关活动] [🛍️ 推荐商品] │
├───────────────────────────────────────────────────────────┤
│ ③ 维度专属内容 (见各页详述) │
├───────────────────────────────────────────────────────────┤
│ ④ 推荐商品(按维度筛选) ← 通用区块,3页皆有 │
│ 4个商品卡片,2×2 网格 │
├───────────────────────────────────────────────────────────┤
│ ⑤ 热门活动(按维度筛选) ← 通用区块,3页皆有 │
│ 横向滚动活动卡片 │
├───────────────────────────────────────────────────────────┤
│ ⑥ 功能入口 Grid (4个) ← 通用区块,3页各自不同 │
│ body: 运动/饮食/作息/菌群 │
│ mind: 阅读/小游戏/测评报告/训练中心 │
│ action: 任务/活动/商城/课程 │
└───────────────────────────────────────────────────────────┘
┌──────────────┐
│ Page onShow │
└──────┬───────┘
│
▼
┌──────────────────────────────────────┐
│ POST /api/energy/sandbox │ ← 家庭能量全景
│ → bodyScore/mindScore/wisdomScore/ │
│ actionScore/wealthScore │
│ → members[].{bodyScore,mindScore, │
│ wisdomScore,actionScore,wealthScore}│
└──────────────────┬───────────────────┘
│
├── 提取当前维度评分 → 渲染柱状图
├── 成员列表 → 每成员柱状图 + 跳转
│
▼ 并行加载(根据页面不同)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 健康报告数据 │ │ DAN测评数据 │ │ 任务数据 │
│ POST /api/ │ │ POST /api/ │ │ POST /api/ │
│ health/... │ │ cognitive/.. │ │ tasks/... │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼ 并行加载(3页通用)
┌──────────────┐ ┌──────────────┐
│ 商品(按维度) │ │ 活动(按维度) │
│ POST /api/ │ │ POST /api/ │
│ product/list │ │ activity/list│
└──────────────┘ └──────────────┘
商品用 energy_dimension_id 筛选(需给 Product 新增该字段,若尚无)
任务用 category 或 dimension 字段筛选
活动用新建的 dimension 字段筛选
文章用 relatedDimensions 字段筛选(已存在)
pages/body/index.vue┌─ ③ 维度专属内容 ──────────────────────────────────────┐
│ ┌─ 家庭健康概况(有数据时) ─────────────────────────┐ │
│ │ 最新体检报告: 2026-05-15 综合评分: 82/100 │ │
│ │ 最新菌群检测: 2026-04-20 菌群健康: 76/100 │ │
│ │ [查看完整报告 →] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 无报告时 ────────────────────────────────────────┐ │
│ │ 还没有健康检测报告 │ │
│ │ [上传体检报告] [菌群检测] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 健康指标解读(有报告时) ─────────────────────────┐ │
│ │ 营养状况: 🟢 正常 疾病风险: 🟢 低 │ │
│ │ 肠道屏障: 🟡 注意 神经递质: 🟢 正常 │ │
│ │ [查看全部指标 →] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 今日身体数据 ────────────────────────────────────┐ │
│ │ 🚶 6,582步 💧 1.2L 😴 8.5h │ │
│ │ (对接智能设备后自动同步) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 健康打卡 ────────────────────────────────────────┐ │
│ │ 今日: 运动✔ 喝水✔ 早睡✔ 冥想□ +⚡15能量 │ │
│ │ [去打卡 →] │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
| 入口 | 图标 | 跳转 |
|---|---|---|
| 运动 | 🏃 | pages/body/sports (已有或新增) |
| 饮食 | 🍎 | pages/body/diet |
| 作息 | 😴 | pages/body/sleep |
| 菌群 | 🔬 | pages/body/gut-flora (已有或跳转报告) |
| 数据 | 接口 | 状态 |
|---|---|---|
| 家庭能量柱状图 | POST /api/energy/sandbox → bodyScore, members[].bodyScore |
✅ 已有 |
| 最新健康报告 | POST /api/health/report/latest |
✅ 已有 |
| 报告列表 | POST /api/health/report/list |
✅ 已有 |
| 指标明细 | POST /api/health/indicator/list |
✅ 已有 |
| 身体维度详情 | POST /api/health/body/detail |
✅ 已有 |
| 健康打卡 | POST /api/checkin/today |
✨ 新增 |
| 按维度商品 | POST /api/product/list + 参数 dimensionCode |
✅ 已有改造 |
| 按维度活动 | POST /api/activity/list + 参数 dimensionCode |
✨ 新增 |
pages/mind/index.vue保留原有的 心(心理)/ 智(认知)双 Tab 结构,合并为统一心智页面:
【心】心理 Tab (emotion):
┌─ 心理能量概况 ─────────────────────────────────────────┐
│ ❤ 心能量: 82 | 💪 韧性: 78 | 😊 情绪: 85 │
└────────────────────────────────────────────────────────┘
┌─ EMI 心理四维 ─────────────────────────────────────────┐
│ 情绪商数 ████████████████░░ 82 优秀 │
│ 心理韧性 ██████████████░░░░ 76 良好 │
│ 压力应对 █████████████████░░ 80 良好 │
│ 自我认知 ██████████████████░ 85 优秀 │
│ [查看完整报告 →] 综合 EQ 分: 81 │
└────────────────────────────────────────────────────────┘
┌─ 无评估数据占位引导 ────────────────────────────────┐
│ 「完成 EMI 心理测评后查看详细分析报告」 │
│ [预约测评] │
└────────────────────────────────────────────────────────┘
【智】认知 Tab (cognitive):
┌─ 综合认知评分 ─────────────────────────────────────────┐
│ 综合得分: 88 | 上次: 2026-05-20 │
│ [查看完整报告 →] [预约测评] │
└────────────────────────────────────────────────────────┘
┌─ 认知六维雷达图 ───────────────────────────────────────┐
│ 家庭成员切换: [小宝 78] [大宝 85] [自己 92] ←横向滚动 │
│ ┌────────────────────────────────────────────────┐ │
│ │ 六维雷达图 (RadarChart) │ │
│ │ 感知 │ │
│ │ 专注 记忆 │ │
│ │ 逻辑 空间 │ │
│ │ 加工速度 │ │
│ └────────────────────────────────────────────────┘ │
│ (点击维度标签进入对应能力训练) │
└────────────────────────────────────────────────────────┘
┌─ 认知训练 ─────────────────────────────────────────────┐
│ [🎯 舒尔特方格] [🔢 猜数字] [🧩 数独] │
└────────────────────────────────────────────────────────┘
┌─ 本月阅读统计 ─────────────────────────────────────────┐
│ 已读: 12篇 / 阅读时长: 156分钟 │
│ ████████████░░░░ 60% [去阅读 →] │
└────────────────────────────────────────────────────────┘
心智页 心理 Tab 底部和 认知 Tab 底部都放推荐文章:
┌─ 推荐阅读 ─────────────────────────────────────────────┐
│ ┌────────────────────────────────────────────────┐ │
│ │ [情绪管理] 如何帮助孩子管理情绪 │ │
│ │ 掌握3个简单技巧... │ │
│ │ 阅读 2.3k │ │
│ ├────────────────────────────────────────────────┤ │
│ │ [认知发展] 提升逻辑思维的5个日常游戏 │ │
│ │ 在家就能玩的思维训练... │ │
│ │ 阅读 1.8k │ │
│ └────────────────────────────────────────────────┘ │
│ [更多文章 →] │
└────────────────────────────────────────────────────────┘
文章根据 relatedDimensions 筛选相关维度:心理 Tab 显示 relatedDimensions 包含 mind 的文章;认知 Tab 显示包含 wisdom 的文章。
| 入口 | 图标 | 跳转 |
|---|---|---|
| 阅读 | 📖 | pages/mind/articles (已有) |
| 小游戏 | 🎮 | pages/games/list (已有) |
| 测评报告 | 📊 | 心理报告/认知报告 (已有) |
| 训练中心 | 🎯 | pages/mind/training (已有) |
心智 Tab 合并了「心」和「智」两个维度。横放柱状图采用堆叠图(Stacked Bar)模式:每个成员一根柱,叠加显示心能量 + 智能量:
心智能量总览:
┌──────────────────────────────────────────────────────┐
│ 全家 ████████████████████████████████████░░ 82+88=170│
│ └── 心 82 ──┘└─── 智 88 ───┘ │
│ │
│ 妈妈 ████████████████████████████░░░░ 72+82=154 │
│ └── 心 72 ──┘└─── 智 82 ───┘ │
│ │
│ 爸爸 ██████████████████████████████████░░ 78+86=164 │
│ └── 心 78 ──┘└─── 智 86 ───┘ │
│ │
│ 小宝 ████████████████████████░░░░░░░ 65+78=143 │
│ └── 心 65 ──┘└─── 智 78 ───┘ │
│ │
│ (柱长 = 心分数 + 智分数,上限200) │
│ 点击心色段 → 心理报告 点击智色段 → 认知报告 │
└──────────────────────────────────────────────────────┘
堆叠图设计要点:
| 属性 | 值 |
|---|---|
| 柱总长 | mindScore + wisdomScore(上限 200,即 100+100) |
| 心色段 | 暖色(如 #FF6B35 橙),居左 |
| 智色段 | 冷色(如 #667eea 蓝紫),居右 |
| 刻度标签 | 显示 心分数+智分数=总分(如 72+82=154) |
| 点击心段 | 跳转到心理报告页 |
| 点击智段 | 跳转到认知报告页 |
| 无数据时 | 柱显示为浅灰色占位,点击引导预约测评 |
数据改造: FamilyEnergyBar 组件新增 stackedDimensions prop,心智页传入 ['mind', 'wisdom'],组件内部渲染两段堆叠而非单段。
| 数据 | 接口 | 状态 |
|---|---|---|
| 家庭能量 | /api/energy/sandbox → mindScore/wisdomScore + members[].mindScore/wisdomScore |
✅ 已有 |
| EMI 心理报告 | POST /api/assessment/emi/latest |
✅ 已有 |
| DAN 认知结果 | POST /api/assessment/latest |
✅ 已有 |
| 六维家族数据 | POST /api/cognitive/family |
✅ 已有 |
| 文章列表(按维度) | POST /api/article/list?dimension=body/mind/wisdom |
✅ 已有改造 |
| 阅读统计 | POST /api/reading/stats |
✅ 已有 |
| 按维度商品 | POST /api/product/list?dimension=心智 |
✨ 新增改造 |
| 按维度活动 | POST /api/activity/list?dimension=心智 |
✨ 新增 |
pages/action/index.vue┌─ ③ 维度专属内容 ──────────────────────────────────────┐
│ ┌─ 今日任务 ────────────────────────────────────────┐ │
│ │ 📋 完成晨跑 30 分钟 +20分 ⏰ 今日截止 │ │
│ │ 📋 整理书桌 +10分 ⏰ 今日截止 │ │
│ │ 📋 阅读打卡 15 分钟 +15分 ⏰ 明日截止 │ │
│ │ [查看全部任务 →] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 任务完成统计 ────────────────────────────────────┐ │
│ │ 本周完成: 12/15 ████████████░░ 80% │ │
│ │ 本月连续打卡: 8天 🔥 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 进行中的活动 ────────────────────────────────────┐ │
│ │ 🔥 21天亲子运动打卡营 │ │
│ │ 📅 5/1 ~ 5/21 | 进度: 12/21天 │ │
│ │ ██████████████░░░░░░ │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
| 入口 | 图标 | 跳转 |
|---|---|---|
| 任务 | 📋 | pages/tasks/tasks (已有 TabBar) |
| 活动 | 🔥 | pages/discover/index?type=activity (已有) |
| 商城 | 🛍️ | pages/shop/index (已有) |
| 课程 | 🎓 | pages/discover/index?type=course (已有) |
| 数据 | 接口 | 状态 |
|---|---|---|
| 家庭能量 | /api/energy/sandbox → actionScore + members[].actionScore |
✅ 已有 |
| 今日任务(按维度) | POST /api/tasks/list?dimension=action |
✅ 已有改造 |
| 任务统计 | POST /api/tasks/stats?childId=X |
✅ 已有 |
| 按维度商品 | POST /api/product/list?dimension=行动 |
✨ 新增改造 |
| 按维度活动 | POST /api/activity/list?dimension=行动 |
✨ 新增 |
| 端点 | 方法 | 说明 | 优先级 |
|---|---|---|---|
POST /api/activity/list |
新增 | 活动列表(支持按 dimension 筛选) | P1 |
POST /api/activity/detail |
新增 | 活动详情 | P1 |
POST /api/checkin/today |
新增 | 今日健康打卡状态 | P1 (身体页) |
POST /api/checkin/do |
新增 | 执行打卡 | P1 |
POST /api/checkin/list |
新增 | 打卡记录 | P2 |
POST /api/product/list |
改造 | 增加 dimension 筛选参数 | P1 |
POST /api/tasks/list |
改造 | 增加 dimension 筛选参数 | P1 |
| 页面 | 操作 | 说明 |
|---|---|---|
pages/body/index.vue |
🔄 改造 | 统一布局 + 能量柱状图 + 健康数据 + 通用区块 |
pages/mind/index.vue |
🔄 改造 | 统一布局 + 能量柱状图 + 双Tab + 通用区块 |
pages/action/index.vue |
🔄 改造 | 统一布局 + 能量柱状图 + 今日任务 + 通用区块 |
pages/body/health-report.vue |
✨ 新增 | 健康报告详情 (已有设计) |
pages/body/checkin.vue |
✨ 新增 | 健康打卡 (已有设计) |
pages/body/member-body-detail.vue |
✨ 新增 | 成员身体维度详情(柱状图点击跳转目标) |
pages/mind/member-mind-detail.vue |
✨ 新增 | 成员心/智维度详情 |
pages/action/member-action-detail.vue |
✨ 新增 | 成员行动维度详情 |
当用户点击能量柱状图中某个成员的能量条时,跳转到对应成员的该维度详情页。
每个成员维度详情页展示该成员在某维度下的详细信息:
┌─ 返回 ────────────────────────────────────────────────┐
│ │
│ 成员名称:小宝 │
│ 身体能量值:65/100 │
│ │
│ ┌─ 能量构成 ────────────────────────────────────────┐ │
│ │ 获得来源: │ │
│ │ 完成任务 +120 │ │
│ │ 健康打卡 +45 │ │
│ │ 商品购买 -30 │ │
│ │ 当前余额: 135 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ 能量流水(近7天) ───────────────────────────────┐ │
│ │ 06/07 完成晨跑 +10 ⚡ │ │
│ │ 06/07 健康打卡 +5 ⚡ │ │
│ │ 06/06 购买运动用品 -15 ⚡ │ │
│ │ [查看更多 →] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ [维度专属内容] │
│ body: 健康报告/指标/身体数据 │
│ mind: EMI心理结果/DAN认知结果 │
│ action: 任务/活动参与记录 │
└────────────────────────────────────────────────────────┘
@TableName("activities")
public class Activity {
private Long id;
private String title;
private String description;
private String coverImage;
private String dimensionCode; // body/mind/wisdom/action/wealth
private String activityType; // offline/online/campaign
private String status; // draft/published/ended
private Date startTime;
private Date endTime;
private String location;
private Integer maxParticipants;
private Integer currentParticipants;
private BigDecimal price;
private Long vendorId; // 活动方
private Date createdAt;
}
Product 实体已有 domain 字段(来源域)和 externalSource(外部来源)。可以复用现有字段或新增 energyDimensionId(与 energy_dimension 表关联)。推荐新增 energyDimensionId 字段以统一维度筛选逻辑。
文件:
cfc-frontend/components/FamilyEnergyBar.vue — 家庭能量横放柱状图组件cfc-frontend/components/UserQuickEntry.vue — 用户信息+快捷入口组件说明:
dimensionCode + sandboxData(来自 /api/energy/sandbox)@click 事件跳转到对应成员详情页文件:
cfc-frontend/components/DimensionProducts.vue — 按维度商品展示cfc-frontend/components/DimensionActivities.vue — 按维度活动展示cfc-frontend/components/DimensionTasks.vue — 按维度任务展示说明:
dimensionCode 参数文件:
cfc-frontend/pages/body/index.vue改造点:
<FamilyEnergyBar dimensionCode="body" /><UserQuickEntry /><DimensionProducts> + <DimensionActivities>文件:
cfc-frontend/pages/mind/index.vue改造点:
<FamilyEnergyBar dimensionCode="mind" dualDimension="wisdom" />(双维度)<UserQuickEntry /><DimensionProducts> + <DimensionActivities>文件:
cfc-frontend/pages/action/index.vue改造点:
<FamilyEnergyBar dimensionCode="action" /><UserQuickEntry /><DimensionProducts> + <DimensionActivities>文件:
cfc-frontend/pages/body/member-body-detail.vuecfc-frontend/pages/mind/member-mind-detail.vuecfc-frontend/pages/action/member-action-detail.vue说明:
childId + dimensionCode文件:
ActivityController.java — 活动 CRUDActivityService.javaentity/Activity.javamapper/ActivityMapper.java说明:
ProductController 增加 dimensionCode 参数TaskController 增加 dimensionCode 参数TabBar: 首页 | 身体 | 心智 | 行动 | 我的
?.(用 && 代替)项目已有预存的 LSP entity getter 缺失错误(entity 未生成 getter),这些是已有问题,不影响本次设计的内容。
┌──────────────────────────────────────────────────────────┐
│ 🏃 身体能量 ⚡ 85 │
│ │
│ 全家 ████████████████████████████████░░ 85 │
│ 妈妈 ██████████████████████░░░░░░░░░░ 72 ←点击跳转 │
│ 爸爸 ██████████████████████████░░░░░░ 80 ←点击跳转 │
│ 小宝 ████████████████████░░░░░░░░░░░░ 65 ←点击跳转 │
│ │
│ 颜色渐变: #10B981 → #34D399 (body) │
│ #8B5CF6 → #A78BFA (mind) │
│ #F97316 → #FB923C (action) │
└──────────────────────────────────────────────────────────┘
Click member bar → uni.navigateTo 带参数:
uni.navigateTo({
url: '/pages/body/member-body-detail?childId=' + member.memberId
})
┌──────────────────────────────────────────────────────────┐
│ 👤 当前用户: 妈妈(家长) 切换孩子: [小宝 ▼] │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 📋 今日 │ │ 🔥 相关 │ │ 🛍️ 推荐 │ │
│ │ 任务 │ │ 活动 │ │ 商品 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────┘
三个按钮分别滚动页面到对应区块,或跳转到对应 TabBar 页面。
| 操作 | 文件 | 说明 |
|---|---|---|
| ✨ | cfc-frontend/components/FamilyEnergyBar.vue |
能量横放柱状图组件 |
| ✨ | cfc-frontend/components/UserQuickEntry.vue |
用户信息+快捷入口组件 |
| ✨ | cfc-frontend/components/DimensionProducts.vue |
按维度商品展示组件 |
| ✨ | cfc-frontend/components/DimensionActivities.vue |
按维度活动展示组件 |
| ✨ | cfc-frontend/components/DimensionTasks.vue |
按维度任务展示组件 |
| 🔄 | cfc-frontend/pages/body/index.vue |
身体页统一布局改造 |
| 🔄 | cfc-frontend/pages/mind/index.vue |
心智页统一布局改造 |
| 🔄 | cfc-frontend/pages/action/index.vue |
行动页统一布局改造 |
| ✨ | cfc-frontend/pages/body/member-body-detail.vue |
身体成员详情页 |
| ✨ | cfc-frontend/pages/mind/member-mind-detail.vue |
心智成员详情页 |
| ✨ | cfc-frontend/pages/action/member-action-detail.vue |
行动成员详情页 |
| ✨ | cfc-backend/.../entity/Activity.java |
活动实体 |
| ✨ | cfc-backend/.../mapper/ActivityMapper.java |
活动 Mapper |
| ✨ | cfc-backend/.../controller/ActivityController.java |
活动 CRUD |
| ✨ | cfc-backend/.../service/ActivityService.java |
活动服务 |
| 🔄 | cfc-backend/.../controller/ProductController.java |
增加维度筛选参数 |
| 🔄 | cfc-backend/.../controller/TaskController.java |
增加维度筛选参数 |
| 🔄 | cfc-frontend/pages.json |
注册新增子页面 |
| 🔄 | cfc-frontend/utils/api.js |
新增 API 封装 |
| 决策 | 选项 | 选择 |
|---|---|---|
| 能量值使用 | balance值(整数) vs 评分(0-100) | 评分(0-100) — 标准化,跨成员可比 |
| 心智双维度展示 | 分两组并列 vs 堆叠图 | 堆叠图 — 一根柱同时显示心+智,节省空间且直观对比两者比例,点击对应色段可分别跳转报告 |
| 商品按维度筛选 | 新字段 vs 复用 domain |
新增 energyDimensionId — 与 energy_dimension 表统一关联 |
| Activity 实现 | 新表 vs 用 Product 扩展 | 新表 — Activity 有独立生命周期和业务逻辑 |
| 成员详情页数量 | 3个独立页 vs 1个通用页+参数 | 3个独立页 — 每个维度详情内容差异大,独立页面更清晰 |
| 登录前价格 | 隐藏 vs 显示"登录查看" | 隐藏数值,显示"登录查看价格" — 不暴露定价信息 |
| 私密内容过滤 | 前端过滤 vs 后端过滤 | 后端统一过滤 — 后端根据 auth 状态和角色返回不同数据集 |
| 私密活动可见范围 | public/member/private 三态 | visibility + visibleScope + visibleTo 三字段 — 支持 family/role/user 三层级,扩展性好 |
| 游客无数据占位 | 直接隐藏 vs 显示"登录后查看更多" | 显示「🔒 登录后查看更多」占位 — 提示用户有更多内容需登录解锁 |
| 游客点击商品/活动 | 弹登录 vs 可直接看详情页(无价格) | 弹登录提示 — 私密活动不能暴露,详情页也依赖登录态 |
| # | 原则 | 说明 |
|---|---|---|
| A | 商品/活动/文章登录前可浏览 | 三个页面的通用区块在未登录时可见 |
| B | 商品和活动不显示价格 | 价格数值隐藏,替换为"登录查看"提示 |
| C | 私密活动从列表即过滤 | 未授权用户完全看不到私密活动,不显示无权限条目 |
| D | 文章按可见性过滤 | 复用 Article.visibility + Article.visibleTo 字段 |
| E | 后端统一过滤 | 后端根据 @RequestAttribute("userId") 是否存在 + 角色决定返回什么数据 |
| F | 个性化板块变为引导 | 原本登录后的能量柱状图、健康数据、测评报告、今日任务 → 替换为登录引导卡 |
| G | 无数据时显示占位提示 | 游客模式下某维度无公开活动/商品时,显示「登录后查看更多」占位,而非直接隐藏区块 |
支持 3 种可见范围(scope),每种对应不同的 visibleTo 存储格式:
| visibility | 含义 | visibleTo 格式 | 说明 |
|---|---|---|---|
public |
所有人可见 | — | 游客和登录用户均可浏览 |
member |
登录用户可见 | — | 任意登录用户可浏览 |
restricted |
限定范围可见 | 见下方 scope 说明 | 按家庭/角色/具体人过滤 |
restricted 模式下还需 visibleScope 字段说明限定类型:
| visibleScope | 含义 | visibleTo 示例 | 匹配逻辑 |
|---|---|---|---|
family |
特定家庭 | "42" 或 "[42,55]" |
用户的 familyId 匹配 |
role |
特定角色 | "[\"parent\",\"teacher\"]" |
用户的 role 字段匹配 |
user |
特定用户 | "[\"101\",\"102\"]" |
用户的 userId 在可见列表中(JSON_CONTAINS) |
// 在 Activity 实体中新增
/** 可见性: public / member / restricted */
private String visibility;
/** 限定模式: family / role / user — 仅 visibility=restricted 时有效 */
private String visibleScope;
/** 可见范围标识: family_id(s) / role(s) / user_id(s) — JSON 格式 */
private String visibleTo;
设计决策理由: 选用
visibility+visibleScope+visibleTo三字段组合而非单一visibility枚举,是因为 scope 隔离了不同类型的可见判断逻辑(SQL 条件不同),且支持扩展新 scope 类型(如将来增加grade年级可见)而不需改枚举。
private String visibility; // public / member / private
private String visibleTo; // 可见的user_id列表(JSON数组) 或 role列表
private String status; // draft / published / archived
status + memberEligible)private String status; // approved / rejected / pending
private String memberEligible; // 是否仅会员可见(yes/no)
┌───────────────────────────────────────────────────────────┐
│ PageBanner (dimension 主题,与登录后一致) │
│ + 副标题: 登录后展示个性化数据 │
├───────────────────────────────────────────────────────────┤
│ ┌─ 登录引导横条 ──────────────────────────────────────┐ │
│ │ 🔒 登录后查看家庭能量和个性化数据 │ │
│ │ [立即登录 / 注册] │ │
│ └──────────────────────────────────────────────────────┘ │
├───────────────────────────────────────────────────────────┤
│ ③ 维度专属内容(游客版) 看下文各页详述 │
├───────────────────────────────────────────────────────────┤
│ ④ 推荐商品(按维度筛选) │
│ 有公开商品 → 商品卡片: 封面 + 名称 + [登录查看价格] │
│ 无公开商品 → 「🔒 登录后查看更多推荐商品」占位 │
├───────────────────────────────────────────────────────────┤
│ ⑤ 热门活动(按维度筛选) │
│ 有公开活动 → 活动卡片: 封面 + 名称 + 日期 + 地点 │
│ 无公开活动 → 「🔒 登录后查看更多相关活动」占位 │
│ 私密活动直接不返回(后端过滤) │
├───────────────────────────────────────────────────────────┤
│ ⑥ 功能入口 Grid │
│ 保留图标,点击部分跳转需要登录时弹登录提示 │
└───────────────────────────────────────────────────────────┘
商品/活动空占位设计:
┌─────────────────────────────────────┐
│ 🔒 登录后查看更多推荐商品 │ ← 灰色小字,不可点击
│ 或 │
│ 🔒 登录后查看更多相关活动 │
└─────────────────────────────────────┘
占位条件:游客模式下,对应维度无任何 visibility=public 的数据。点击占位无事件(仅为提示,不跳转登录)。
页面元素 | 游客(未登录) | 登录后(已有设计)
----------------------|----------------------------|----------------------------
PageBanner | 显示,tagline 不变 | 显示
家庭能量柱状图 | ❌ 隐藏 | ✅ 显示
用户快捷入口 | ❌ 隐藏 | ✅ 显示
健康报告/指标 | ❌ 隐藏 | ✅ 显示
今日身体数据 | ❌ 隐藏 | ✅ 显示
健康打卡 | ❌ 隐藏 | ✅ 显示
推荐商品 | ✅ 显示(无价格)/ 无商品→占位 | ✅ 显示(有价格)
热门活动 | ✅ 显示(无价格)/ 无活动→占位 | ✅ 显示(有价格)
登录引导卡 | ✅ 显示 | ❌ 隐藏
健康小贴士(游客版) | ✅ 从文章库取 body 类文章 | ❌ 隐藏(已有替代区块)
商品/活动空占位: 身体页 body 维度下无公开商品时显示「🔒 登录后查看更多推荐商品」,无公开活动时显示「🔒 登录后查看更多相关活动」。
登录引导卡设计:
┌──────────────────────────────────────────────────┐
│ 🔒 登录后可以 │
│ · 查看全家的身体能量数据和健康报告 │
│ · 记录每日健康打卡,获取身体能量 │
│ · 查看专属健康商品和活动价格 │
│ [立即登录] [注册账号] │
└──────────────────────────────────────────────────┘
健康小贴士(游客专属):
身体页在游客模式下展示从 Article 表筛选的 body 相关健康文章,作为「健康小贴士」板块:
┌─ 健康小贴士(从 articles 表取) ─────────────────┐
│ 📋 儿童每日运动指南 │
│ 🍎 五行饮食搭配法则 │
│ 😴 优质睡眠的5个习惯 │
│ [阅读更多 →] │
└──────────────────────────────────────────────────┘
筛选条件:visibility='public' AND status='published' AND (relatedDimensions LIKE '%body%' OR categoryId IN (身体类目))
页面元素 | 游客(未登录) | 登录后(已有设计)
----------------------|----------------------------|----------------------------
PageBanner | 显示 | 显示
家庭能量柱状图 | ❌ 隐藏 | ✅ 显示
用户快捷入口 | ❌ 隐藏 | ✅ 显示
心理 Tab(EMI) | ❌ 隐藏(无数据) | ✅ 显示
认知 Tab(DAN+雷达图) | ❌ 隐藏(无数据) | ✅ 显示
认知训练(小游戏) | ❌ 隐藏(需登录) | ✅ 显示
阅读统计 | ❌ 隐藏 | ✅ 显示
推荐阅读(文章) | ✅ 公开文章 | ✅ 全部可见文章
推荐商品 | ✅ 显示(无价格)/ 无→占位 | ✅ 显示(有价格)
热门活动 | ✅ 显示(无价格)/ 无→占位 | ✅ 显示(有价格)
登录引导卡 | ✅ 显示(专属文案) | ❌ 隐藏
心智页游客布局(双Tab依然保留,但内容不同):
┌─ 登录引导卡 ───────────────────────────────────────┐
│ 🔒 登录后查看 EMI 心理测评报告 │
│ · 情绪商数、心理韧性、压力应对、自我认知四维分析 │
│ [立即登录] │
└────────────────────────────────────────────────────┘
┌─ 推荐阅读(心理维度) ──────────────────────────────┐
│ 从文章库筛选 relatedDimensions 含 mind 的公开文章 │
└────────────────────────────────────────────────────┘
┌─ 登录引导卡 ───────────────────────────────────────┐
│ 🔒 登录后查看 DAN 认知评估报告 │
│ · 六维认知雷达图:感知/专注/记忆/逻辑/空间/加工速度 │
│ [立即登录] │
└────────────────────────────────────────────────────┘
┌─ 推荐阅读(认知维度) ──────────────────────────────┐
│ 从文章库筛选 relatedDimensions 含 wisdom 的公开文章 │
└────────────────────────────────────────────────────┘
页面元素 | 游客(未登录) | 登录后(已有设计)
----------------------|----------------------------|----------------------------
PageBanner | 显示 | 显示
家庭能量柱状图 | ❌ 隐藏 | ✅ 显示
用户快捷入口 | ❌ 隐藏 | ✅ 显示
今日任务 | ❌ 隐藏(无数据) | ✅ 显示
推荐商品 | ✅ 显示(无价格)/ 无→占位 | ✅ 显示(有价格)
热门活动 | ✅ 显示(无价格)/ 无→占位 | ✅ 显示(有价格)
登录引导卡 | ✅ 显示(专属文案) | ❌ 隐藏
功能入口 | ✅ 显示,部分需弹登录 | ✅ 全部可用
行动页游客布局:
┌─ 登录引导卡 ───────────────────────────────────────┐
│ 🔒 登录后查看任务和活动参与记录 │
│ · 查看今日任务,完成打卡获取行动能量 │
│ · 报名参加热门活动,记录成长足迹 │
│ [立即登录] │
└────────────────────────────────────────────────────┘
┌─ 热门活动(按 action 维度筛选) ──────────────────┐
│ 横向滚动卡片,全部公开活动,不显示价格 │
└────────────────────────────────────────────────────┘
┌─ 推荐商品(按 action 维度筛选) ──────────────────┐
│ 2×2 网格商品卡片,不显示价格 │
└────────────────────────────────────────────────────┘
所有列表接口根据请求中是否携带有效 JWT Token,返回不同数据集:
请求到达
│
├─ 有 JWT Token(已登录)
│ ├─ userId 存在 → 正常返回全部可见数据
│ ├─ 商品: status=approved + memberEligible 检查
│ ├─ 活动: visiblity IN (public, member, private) + visibleTo 匹配
│ ├─ 文章: visibility IN (public, member, private) + visibleTo 匹配
│ └─ 价格: 正常返回 price 字段
│
└─ 无 JWT Token(游客)
├─ userId = null
├─ 商品: status=approved AND memberEligible!='yes'(非会员专属)
├─ 活动: visibility='public'
├─ 文章: visibility='public'
└─ 价格: 返回 price=null 或 0(前端据此显示"登录查看")
@PostMapping("/list")
public Result<Page<ProductDTO>> list(@RequestBody Map<String, Object> params,
@RequestAttribute(value = "userId", required = false) Long userId) {
// ... 现有参数解析 ...
LambdaQueryWrapper<Product> query = new LambdaQueryWrapper<Product>()
.eq(Product::getStatus, "approved");
// 游客过滤:排除会员专属商品
if (userId == null) {
query.ne(Product::getMemberEligible, "yes");
}
// 维度筛选
String dimensionCode = (String) params.get("dimensionCode");
if (dimensionCode != null && !dimensionCode.isEmpty()) {
query.eq(Product::getEnergyDimensionId, getDimensionId(dimensionCode));
}
// 查询并转换DTO(游客不返回价格)
Page<Product> pageResult = productService.page(new Page<>(page, size), query);
Page<ProductDTO> dtoPage = toDTOPage(pageResult, userId == null);
return Result.success(dtoPage);
}
后端根据 visibility / visibleScope / visibleTo 三字段组合判断可见性:
@PostMapping("/list")
public Result<Page<ActivityDTO>> list(@RequestBody Map<String, Object> params,
@RequestAttribute(value = "userId", required = false) Long userId,
@RequestAttribute(value = "familyId", required = false) Long familyId,
@RequestAttribute(value = "role", required = false) String role) {
LambdaQueryWrapper<Activity> query = new LambdaQueryWrapper<Activity>()
.eq(Activity::getStatus, "published");
if (userId == null) {
// 游客:只看公开活动
query.eq(Activity::getVisibility, "public");
} else {
// 登录用户:public + member + restricted 中自己符合条件的
query.and(w -> {
w.eq(Activity::getVisibility, "public")
.or().eq(Activity::getVisibility, "member");
// restricted 模式:按 scope 类型分别匹配
if (familyId != null) {
// family 范围 → 匹配 familyId
w.or().and(w2 -> w2.eq(Activity::getVisibility, "restricted")
.eq(Activity::getVisibleScope, "family")
.apply("JSON_CONTAINS(visible_to, {0})", "\"" + familyId + "\""));
}
// role 范围 → 匹配用户 role
if (role != null) {
w.or().and(w2 -> w2.eq(Activity::getVisibility, "restricted")
.eq(Activity::getVisibleScope, "role")
.apply("JSON_CONTAINS(visible_to, {0})", "\"" + role + "\""));
}
// user 范围 → 匹配具体 userId
w.or().and(w2 -> w2.eq(Activity::getVisibility, "restricted")
.eq(Activity::getVisibleScope, "user")
.apply("JSON_CONTAINS(visible_to, {0})", "\"" + userId + "\""));
});
}
String dimensionCode = (String) params.get("dimensionCode");
if (dimensionCode != null && !dimensionCode.isEmpty()) {
query.eq(Activity::getDimensionCode, dimensionCode);
}
Page<Activity> pageResult = activityService.page(new Page<>(page, size), query);
Page<ActivityDTO> dtoPage = toDTOPage(pageResult, userId == null);
return Result.success(dtoPage);
}
若
familyId非必须(用户可能无家庭),用null安全防护避免 SQL 异常。
@PostMapping("/list")
public Result<Page<ArticleDTO>> list(@RequestBody Map<String, Object> params,
@RequestAttribute(value = "userId", required = false) Long userId) {
LambdaQueryWrapper<Article> query = new LambdaQueryWrapper<Article>()
.eq(Article::getStatus, "published");
if (userId == null) {
query.eq(Article::getVisibility, "public");
} else {
// 登录用户:public + member + 指定给自己的 private
query.and(w -> w.eq(Article::getVisibility, "public")
.or().eq(Article::getVisibility, "member")
.or().and(w2 -> w2.eq(Article::getVisibility, "private")
.apply("JSON_CONTAINS(visible_to, {0})", "\"" + userId + "\"")));
}
String dimension = (String) params.get("dimension");
if (dimension != null && !dimension.isEmpty()) {
query.like(Article::getRelatedDimensions, dimension);
}
Page<Article> pageResult = articleService.page(new Page<>(page, size), query);
return Result.success(toDTOPage(pageResult, userId == null));
}
@Data
public class ProductDTO {
private Long id;
private String name;
private String description;
private String coverImage;
private BigDecimal price; // 游客 = null
private String priceLabel; // 游客 = "登录查看价格",登录后 = "¥XXX"
private String vendorName;
private String productType;
// 其他非敏感字段
}
@Data
public class ActivityDTO {
private Long id;
private String title;
private String coverImage;
private String startTime;
private String endTime;
private String location;
private BigDecimal price; // 游客 = null
private String priceLabel; // 游客 = "登录查看",登录后 = "¥XXX"
private Integer maxParticipants;
private Integer currentParticipants;
private String vendorName;
// 其他非敏感字段
}
所有页面统一使用 isLoggedIn 变量控制渲染分支:
// 每个页面的 onShow 中
onShow() {
var token = uni.getStorageSync('token')
this.isLoggedIn = !!token
if (this.isLoggedIn) {
this.loadPersonalData() // 能量、健康、测评、任务等
}
// 无论是否登录,都加载公共数据
this.loadPublicData() // 商品、活动、文章
}
// 组件内部根据 props hidePrice 控制价格显示
// 后端在未登录时返回 price=null, priceLabel="登录查看价格"
商品区块渲染逻辑(含占位):
<template>
<view class="section">
<view class="section-header">
<text class="section-title">🛍️ 推荐商品</text>
<text class="section-more" @click="$emit('moreProducts')">更多 ›</text>
</view>
<!-- 有商品时:卡片列表 -->
<view v-if="products.length > 0" class="product-list">
<view class="product-card" v-for="prod in products" :key="prod.id" @click="onProductClick(prod)">
<image class="product-img" :src="prod.coverImage || '/static/default-product.png'" />
<text class="product-name">{{ prod.name }}</text>
<text v-if="!isLoggedIn" class="product-price-label">登录查看价格</text>
<text v-else class="product-price">¥{{ prod.price }}</text>
</view>
</view>
<!-- 游客无商品时:占位提示 -->
<view v-else-if="!isLoggedIn" class="empty-placeholder">
<text class="placeholder-text">🔒 登录后查看更多推荐商品</text>
</view>
</view>
</template>
活动区块渲染逻辑(含占位):
<template>
<view class="section">
<view class="section-header">
<text class="section-title">🔥 相关活动</text>
<text class="section-more" @click="$emit('moreActivities')">更多 ›</text>
</view>
<!-- 有活动时:卡片列表 -->
<view v-if="activities.length > 0" class="activity-list">
<view class="activity-card" v-for="act in activities" :key="act.id" @click="onActivityClick(act)">
<image class="activity-img" :src="act.coverImage || '/static/default-activity.png'" />
<view class="activity-info">
<text class="activity-name">{{ act.title }}</text>
<text class="activity-meta">{{ act.startTime }} · {{ act.location }}</text>
<text v-if="!isLoggedIn" class="activity-price-label">登录查看</text>
<text v-else class="activity-price">¥{{ act.price }}</text>
</view>
</view>
</view>
<!-- 游客无活动时:占位提示 -->
<view v-else-if="!isLoggedIn" class="empty-placeholder">
<text class="placeholder-text">🔒 登录后查看更多相关活动</text>
</view>
</view>
</template>
在原有文件变更清单基础上新增:
| 操作 | 文件 | 说明 |
|---|---|---|
| 🔄 | cfc-backend/.../entity/Activity.java |
补充 visibility + visibleTo 字段 |
| 🔄 | cfc-backend/.../dto/ProductDTO.java |
新增 priceLabel 字段 |
| ✨ | cfc-backend/.../dto/ActivityDTO.java |
新增 priceLabel 字段 |
| 🔄 | cfc-backend/.../dto/ArticleDTO.java |
若尚无,新增 DTO 类 |
| 🔄 | cfc-backend/.../controller/ProductController.java |
list 接口增加 @RequestAttribute("userId") 和游客逻辑 |
| 🔄 | cfc-backend/.../controller/ActivityController.java |
list 接口增加 userId 参数和可见性过滤 |
| 🔄 | cfc-backend/.../controller/ArticleController.java |
list 接口增加 userId 参数和可见性过滤 |
| 🔄 | cfc-frontend/pages/body/index.vue |
增加游客分支渲染 |
| 🔄 | cfc-frontend/pages/mind/index.vue |
增加游客分支渲染 |
| 🔄 | cfc-frontend/pages/action/index.vue |
增加游客分支渲染 |