Просмотр исходного кода

feat: add showToFamily toggle in profile/child-edit, clean up mind page

profile.vue: add '对家庭成员可见' switch for parent role, with updateMemberVisibility API integration. edit-child.vue: add showToFamily toggle in child editor. mind/index.vue: remove dualDimension/loadEnergyData, route 测评报告 to emotion-report page, remove unused tab-bar CSS. docs: add code-vs-design discrepancy fix plan.
User 4 месяцев назад
Родитель
Сommit
afecb0f994

+ 4 - 57
cfc-frontend/pages/mind/index.vue

@@ -28,8 +28,7 @@
     <FamilyEnergyBar
       v-if="isLoggedIn"
       dimensionCode="mind"
-      :sandboxData="sandboxData"
-      :dualDimension="dualDimension" />
+      :sandboxData="sandboxData" />
 
     <!-- 登录后:用户快捷入口 -->
     <UserQuickEntry
@@ -299,7 +298,7 @@ import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
-import { getVisibleSections, getFeaturedArticles, getEmiReport, getAssessmentLatestResult, getDailyTip, getReadingStats, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers } from '../../utils/api.js'
+import { getVisibleSections, getFeaturedArticles, getEmiReport, getAssessmentLatestResult, getDailyTip, getReadingStats, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers } from '../../utils/api.js'
 import config from '../../config.js'
 
 export default {
@@ -350,7 +349,7 @@ export default {
       funcList: [
         { icon: '\u{1F4D6}', label: '阅读', needLogin: false, page: '/pages/mind/articles' },
         { icon: '\u{1F3AE}', label: '小游戏', needLogin: true, page: '/pages/games/list' },
-        { icon: '\u{1F4CA}', label: '测评报告', needLogin: true, action: 'switchCognitive' },
+        { icon: '\u{1F4CA}', label: '测评报告', needLogin: true, page: '/pages/mind/emotion-report' },
         { icon: '\u{1F9EA}', label: '训练中心', needLogin: true, page: '/pages/mind/training' },
         { icon: '\u{1F4DD}', label: '成长档案', needLogin: true, page: '/pages/growth/index' }
       ],
@@ -360,7 +359,6 @@ export default {
       userName: '',
       children: [],
       sandboxData: null,
-      dualDimension: null,
       dimensionTasks: [],
       dimensionActivities: [],
       dimensionProducts: []
@@ -583,7 +581,7 @@ export default {
     },
     goCognitiveReport: function() {
       var childId = this.currentChildId || ''
-      uni.navigateTo({ url: '/pages/wisdom/cognitive-report?childId=' + childId })
+      uni.navigateTo({ url: '/pages/mind/emotion-report?childId=' + childId })
     },
     goGame: function(gameCode) {
       if (!this.isLoggedIn) {
@@ -642,30 +640,10 @@ export default {
       })
     },
     loadDimensionData: function() {
-      this.loadEnergyData()
       this.loadDimensionTasks()
       this.loadDimensionActivities()
       this.loadDimensionProducts()
     },
-    loadEnergyData: function() {
-      var self = this
-      if (!this.currentChildId) return
-      getEnergyOverview(this.currentChildId).then(function(res) {
-        if (res && res.data) {
-          var dims = res.data.dimensions
-          if (dims && dims.length > 0) {
-            for (var i = 0; i < dims.length; i++) {
-              if (dims[i].code === 'mind') {
-                self.dualDimension = dims[i]
-                break
-              }
-            }
-          }
-        }
-      }).catch(function(e) {
-        console.log('获取能量概览失败', e)
-      })
-    },
     loadSandboxData: function() {
       var self = this
       getFamilyEnergySandbox().then(function(res) {
@@ -761,37 +739,6 @@ export default {
   padding-bottom: 120rpx;
 }
 
-/* ===== 双 Tab 切换 ===== */
-.tab-bar {
-  display: flex;
-  flex-direction: row;
-  margin: 20rpx 30rpx 0;
-  background: #fff;
-  border-radius: 40rpx;
-  padding: 6rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.tab-item {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 18rpx 0;
-  border-radius: 36rpx;
-}
-.tab-item.tab-active {
-  background: linear-gradient(135deg, #FF6B35, #FFD700);
-  box-shadow: 0 4rpx 12rpx rgba(255,107,53,0.3);
-}
-.tab-text {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #666;
-}
-.tab-active .tab-text {
-  color: #fff;
-}
-
 /* ===== 心理能量概况 ===== */
 .energy-overview {
   display: flex;

+ 13 - 0
cfc-frontend/pages/profile/edit-child.vue

@@ -54,6 +54,13 @@
         <text class="hint">开启后,超时未完成任务将扣减积分</text>
       </view>
 
+      <!-- 对家庭成员可见 -->
+      <view class="form-item">
+        <text class="label">对家庭成员可见</text>
+        <switch :checked="form.showToFamily !== 0" @change="onShowToFamilyChange" color="#5B9BD5" />
+        <text class="hint">关闭后,其他家庭成员将看不到此孩子</text>
+      </view>
+
       <!-- 主题风格 -->
       <view class="form-item">
         <text class="label">主题风格</text>
@@ -86,6 +93,7 @@ export default {
         phone: '',
         idCard: '',
         penaltyEnabled: 1,
+        showToFamily: 1,
         theme: 'default'
       },
       genderOptions: [
@@ -149,6 +157,7 @@ export default {
             phone: child.phone || '',
             idCard: child.idCard || '',
             penaltyEnabled: child.penaltyEnabled !== 0 ? 1 : 0,
+            showToFamily: child.showToFamily !== 0 ? 1 : 0,
             theme: child.theme || 'default'
           }
         }
@@ -175,6 +184,9 @@ export default {
     onPenaltyChange(e) {
       this.form.penaltyEnabled = e.detail.value ? 1 : 0
     },
+    onShowToFamilyChange(e) {
+      this.form.showToFamily = e.detail.value ? 1 : 0
+    },
     onThemeChange(e) {
       this.form.theme = this.themes[e.detail.value].value
     },
@@ -203,6 +215,7 @@ export default {
           phone: this.form.phone,
           idCard: this.form.idCard,
           penaltyEnabled: this.form.penaltyEnabled,
+          showToFamily: this.form.showToFamily,
           theme: this.form.theme
         })
         uni.showToast({ title: '保存成功', icon: 'success' })

+ 37 - 2
cfc-frontend/pages/profile/profile.vue

@@ -153,6 +153,11 @@
         <text>🔐 {{ hasPassword ? '重置密码' : '设置密码' }}</text>
         <text class="arrow">›</text>
       </view>
+      <!-- 对家庭成员可见:仅家长可见 -->
+      <view class="menu-item menu-item-switch" v-if="role === 'parent'">
+        <text>👁 对家庭成员可见</text>
+        <switch :checked="showToFamily === 1" @change="onShowToFamilyChange" color="#5B9BD5" />
+      </view>
       <button class="menu-item menu-item-btn" open-type="share" @click="inviteFamily" v-if="role === 'parent'">
         <text>👨‍👩‍👧‍👦 邀请家人</text>
         <text class="arrow">›</text>
@@ -202,7 +207,7 @@
 
 <script>
 import PageBanner from '../../components/PageBanner.vue'
-import { setPassword, verifyPassword, getChildren, vendorStatus, getEnergyOverview, getUserActionStats, getReferralCode, getReferralSummary, getCommissionSummary } from '../../utils/api.js'
+import { setPassword, verifyPassword, getChildren, vendorStatus, getEnergyOverview, getUserActionStats, getReferralCode, getReferralSummary, getCommissionSummary, updateMemberVisibility, getVisibleFamilyMembers } from '../../utils/api.js'
 
 export default {
   components: { PageBanner },
@@ -219,6 +224,7 @@ export default {
       isSwitchedChild: false,
       shareData: null,
       isVendor: false,
+      showToFamily: 1,
       totalEnergy: null,
       badges: [
         { id: 1, icon: '\u{1F3C6}', name: '连续7天', unlocked: false },
@@ -274,6 +280,7 @@ export default {
     // 获取切换标记
     this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
     this.loadChildren()
+    this.loadShowToFamily()
     this.checkVendorStatus()
     this.loadEnergyData()
     this.loadBadges()
@@ -292,6 +299,19 @@ export default {
         console.error('获取孩子列表失败', e)
       }
     },
+    async loadShowToFamily() {
+      try {
+        var uid = this.$store.state.userId || uni.getStorageSync('userId')
+        var res = await getVisibleFamilyMembers()
+        var members = res.data || []
+        var self = members.find(function(m) { return m.id == uid })
+        if (self) {
+          this.showToFamily = self.showToFamily !== 0 ? 1 : 0
+        }
+      } catch (e) {
+        console.error('获取可见性设置失败', e)
+      }
+    },
     goToEditInfo() {
       uni.navigateTo({ url: '/pages/user-edit/user-edit' })
     },
@@ -304,6 +324,18 @@ export default {
     goToPointsLogs() {
       uni.navigateTo({ url: '/pages/points/points' })
     },
+    async onShowToFamilyChange(e) {
+      var val = e.detail.value ? 1 : 0
+      var uid = this.$store.state.userId || uni.getStorageSync('userId')
+      try {
+        await updateMemberVisibility(uid, 'parent', val)
+        this.showToFamily = val
+        uni.showToast({ title: val ? '已对家庭成员可见' : '已对家庭成员隐藏', icon: 'none' })
+      } catch (e) {
+        console.error('更新可见性失败', e)
+        uni.showToast({ title: '操作失败', icon: 'none' })
+      }
+    },
     switchMode() {
       if (this.role === 'child') {
         this.verifyPasswordForSwitch(() => {
@@ -812,7 +844,10 @@ export default {
   font-size: 28rpx;
   color: #333;
 }
-.menu-item-btn::after { border: none; }
+.menu-item-btn::after { border: none; }
+.menu-item-switch {
+  align-items: center;
+}
 .modal-mask {
   position: fixed;
   top: 0;

+ 717 - 0
docs/superpowers/plans/2026-06-15-code-design-discrepancy-fix.md

@@ -0,0 +1,717 @@
+# 代码 vs 设计差异修复计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 修复小程序代码与设计文档(v2.0 + 五维页面重设计)之间的所有差异,使实现完全对齐设计
+
+**架构:** 前端修复为主,涉及 7 个页面/组件和 1 个配置文件。后端补充 Activity 可见性三字段和 Product 游客价格逻辑。分批并行修改:先改 TabBar 和路由等基础设施,再改每个 Tab 页的逻辑
+
+**Tech Stack:** uni-app Vue 2, Spring Boot 2.7
+
+**优先级:** P0(阻塞) > P1(重要) > P2(一般) > P3(优化)
+
+---
+
+## 文件改动清单
+
+| 文件 | 操作 | 职责 |
+|------|------|------|
+| `cfc-frontend/pages.json` | 修改 | TabBar 第5个 Tab 改名为"富沛",路径改为 `pages/wealth/index` |
+| `cfc-frontend/pages/index/index.vue` | 修改 | 移除 teacher-index 路由分支;移除 TeacherIndex import;未登录态商品/活动/文章统一改为 `/api/product/list`;隐藏价格;弹登录提示 |
+| `cfc-frontend/pages/index/parent-index.vue` | 修改 | 移除五行沙盘;改为服务商入口+代办;增加家庭能量横条(顶部);增加维度商品/活动区块 |
+| `cfc-frontend/pages/index/child-index.vue` | 修改 | 增加家庭能量横条(顶部);增加维度商品/活动区块;`reLaunch` 改 `switchTab` |
+| `cfc-frontend/pages/teacher/index.vue` | 废弃 | 整个页面废弃,但保留文件做软删除(用注释标记废弃 + 路由不直达) |
+| `cfc-frontend/pages/body/index.vue` | 修改 | 游客可见商品/活动(无价格);`BASE_URL` 硬编码改 config |
+| `cfc-frontend/pages/mind/index.vue` | 修改 | FamilyEnergyBar 传 `dualDimension` 双维度参数实现堆叠图;游客可见商品/活动(无价格) |
+| `cfc-frontend/pages/action/index.vue` | 修改 | 游客可见商品/活动(无价格);funcList 商城路径修复 |
+| `cfc-frontend/components/FamilyEnergyBar.vue` | 修改 | 支持 `dualDimension` 属性,当传入时渲染堆叠柱(心+智叠加) |
+| `cfc-frontend/utils/api.js` | 修改 | 首页/发现页商品接口从 `DANSHOP_BASE` 改为 `/api/product/list` |
+| `cfc-backend/.../controller/product/ProductController.java` | 修改 | 游客模式:无 token 请求时 price/memberPrice 返回 null |
+| `cfc-backend/.../entity/Activity.java` | 修改 | 增加 visibility/visibleScope/visibleTo 三字段 |
+
+---
+
+### Task 1: TabBar 第5个 Tab 改为「富沛」
+
+**Files:**
+- Modify: `cfc-frontend/pages.json:589-626`
+
+- [ ] **Step 1: 修改 pages.json TabBar 配置**
+
+```json
+// 在 pages.json 中找到 tabBar.list 第5项,改为:
+{
+  "pagePath": "pages/wealth/index",
+  "text": "富沛",
+  "iconPath": "static/tab-wealth.png",
+  "selectedIconPath": "static/tab-wealth-active.png"
+}
+```
+
+同时确认 `pages/wealth/index` 已在 subPackages 中正确注册(第 560-581 行已存在 `pages/wealth` 子包)。
+
+- [ ] **Step 2: 验证**
+
+确认 `pages.json` wealth 子包已包含 `pages/wealth/index` 页面,确认 `static/tab-wealth.png` 和 `static/tab-wealth-active.png` 图标文件存在。如果不存在,创建 48x48px 的占位图标。
+
+- [ ] **Step 3: 清理旧 Tab 图片引用**
+
+从 `static/` 目录检查是否有 `tab-profile.png` / `tab-profile-active.png`,确认不再被引用。
+
+---
+
+### Task 2: 首页 index.vue — 移除教师路由 + 未登录态商品接口改造
+
+**Files:**
+- Modify: `cfc-frontend/pages/index/index.vue`
+
+- [ ] **Step 1: 移除 TeacherIndex 引入(第 108 行)**
+
+修改前:
+```javascript
+import TeacherIndex from '../teacher/index.vue'
+```
+
+修改后:
+```javascript
+// TeacherIndex 已废弃,教师功能折入 parent-index
+```
+
+同时从 `components` 对象中移除 `TeacherIndex`。
+
+- [ ] **Step 2: 移除 teacher 路由分支(第 97 行)**
+
+修改前:
+```html
+<teacher-index v-else-if="currentRole === 'teacher'" />
+```
+
+修改后:
+```html
+<!-- teacher-index 已废弃,教师功能折入 parent-index -->
+```
+
+- [ ] **Step 3: 未登录态商品列表改用 /api/product/list**
+
+修改前(第 200 行):
+```javascript
+productList(params).then(res => { ... })
+```
+
+修改后:
+```javascript
+// 调用 getProductsByDomain 或其他 api.js 中已封装的 /api/product/list
+```
+
+- [ ] **Step 4: 未登录态商品隐藏价格(第 44-48 行)**
+
+修改前:
+```html
+<view class="product-price-row">
+  <text class="product-price">¥{{ item.price }}</text>
+  <text v-if="item.memberPrice" class="product-member">会员¥{{ item.memberPrice }}</text>
+</view>
+```
+
+修改后:
+```html
+<view class="product-price-row">
+  <!-- 游客不显示价格 -->
+  <text class="product-price-login">登录查看</text>
+</view>
+```
+
+- [ ] **Step 5: 未登录态商品点击弹登录提示(第 40 行)**
+
+修改前:
+```html
+@click="goDetail(item.id)"
+```
+
+修改后:
+```html
+@click="handleLogin()"
+```
+
+并在 `handleLogin` 中:
+```javascript
+handleLogin() {
+  uni.showModal({
+    title: '提示',
+    content: '请先登录后查看详情',
+    success: function(res) {
+      if (res.confirm) uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/index/index') })
+    }
+  })
+}
+```
+
+- [ ] **Step 6: 确保 onShow 中调用 loadProducts()**
+
+确认在 `onShow` 的未登录分支中调用 `this.loadProducts()`,使游客首次进入能看到商品列表。
+
+```javascript
+onShow() {
+  if (!this.isLoggedIn()) {
+    this.currentRole = ''
+    this.loadProducts() // 游客加载商品
+    return
+  }
+  this.syncRoleAndTabBar()
+}
+```
+
+---
+
+### Task 3: parent-index.vue — 移除沙盘 + 增加服务商入口 + 家庭能量横条
+
+**Files:**
+- Modify: `cfc-frontend/pages/index/parent-index.vue`
+
+- [ ] **Step 1: 移除五行沙盘(第 74-84 行)**
+
+删除整个 `wuxing-sandbox` 组件的实例化和 import。
+
+修改前:
+```html
+<!-- 五行能量沙盘(浠艾福 — 全家庭版) -->
+<view class="animate-fade-in animate-stagger-3">
+<wuxing-sandbox mode="live"
+  :scores="energyScores"
+  :overall-score="energyOverall"
+  :members="energyMembers"
+  :dimensions="energyDimensions"
+  :total-energy="totalEnergy"
+  :total-health-index="totalHealthIndex"
+  @point-click="goToDomain" />
+</view>
+```
+
+修改后:删除。
+
+- [ ] **Step 2: 顶部增加家庭能量横条 FamilyEnergyBar**
+
+在 Banner 下方增加:
+```html
+<!-- 家庭能量横条 -->
+<FamilyEnergyBar
+  v-if="hasFamilyData"
+  dimensionCode="parent"
+  :sandboxData="sandboxData" />
+```
+
+添加对应 import:
+```javascript
+import FamilyEnergyBar from '@/components/FamilyEnergyBar.vue'
+```
+
+- [ ] **Step 3: 增加服务商快捷入口(替代废弃的 teacher-index)**
+
+在「待处理事项」区域增加服务商快捷卡片,当用户角色为 `teacher` 或 `role` 包含 `teacher` 时显示:
+
+```html
+<!-- ===== 服务商快捷入口(teacher 折入) ===== -->
+<view class="teacher-quick-section" v-if="isTeacher">
+  <view class="section-title">🔧 服务商工具</view>
+  <view class="pending-grid">
+    <view class="pending-card" @click="goToTeacherFamilies">
+      <view class="pending-icon pending-icon--blue">
+        <text class="pending-emoji">👨‍👩‍👧</text>
+      </view>
+      <view class="pending-info">
+        <text class="pending-count accent-color">{{ serviceFamilyCount }}</text>
+        <text class="pending-label">服务家庭</text>
+      </view>
+    </view>
+    <view class="pending-card" @click="goToAssignTask">
+      <view class="pending-icon pending-icon--green">
+        <text class="pending-emoji">📝</text>
+      </view>
+      <view class="pending-info">
+        <text class="pending-count">{{ pendingTaskCount }}</text>
+        <text class="pending-label">待办任务</text>
+      </view>
+    </view>
+    <view class="pending-card" @click="goToIncome">
+      <view class="pending-icon pending-icon--gold">
+        <text class="pending-emoji">💰</text>
+      </view>
+      <view class="pending-info">
+        <text class="pending-count">¥{{ todayIncome }}</text>
+        <text class="pending-label">今日收入</text>
+      </view>
+    </view>
+    <view class="pending-card" @click="goToTeam">
+      <view class="pending-icon pending-icon--purple">
+        <text class="pending-emoji">👥</text>
+      </view>
+      <view class="pending-info">
+        <text class="pending-count">{{ teamCount }}</text>
+        <text class="pending-label">团队</text>
+      </view>
+    </view>
+  </view>
+</view>
+```
+
+data 中增加字段:`isTeacher: false`, `serviceFamilyCount: 0`, `pendingTaskCount: 0`, `todayIncome: 0`, `teamCount: 0`
+
+- [ ] **Step 4: 增加维度商品/活动推荐区块**
+
+在「成长建议」后面增加:
+```html
+<!-- ===== 推荐活动 ===== -->
+<DimensionActivities
+  dimensionCode="all"
+  :activities="recommendedActivities"
+  @activityClick="goActivityDetail"
+  @moreActivities="goMoreActivities" />
+
+<!-- ===== 推荐商品 ===== -->
+<DimensionProducts
+  dimensionCode="all"
+  :products="recommendedProducts"
+  @productClick="goProductDetail"
+  @moreProducts="goMoreProducts" />
+```
+
+添加 import:`import DimensionActivities from '@/components/DimensionActivities.vue'` 和 `import DimensionProducts from '@/components/DimensionProducts.vue'`
+
+---
+
+### Task 4: child-index.vue — 增加家庭能量横条 + 维度商品/活动
+
+**Files:**
+- Modify: `cfc-frontend/pages/index/child-index.vue`
+
+- [ ] **Step 1: 在 Banner 下方增加 FamilyEnergyBar**
+
+```html
+<!-- 家庭能量横条 -->
+<FamilyEnergyBar
+  v-if="hasChildren"
+  dimensionCode="child"
+  :sandboxData="sandboxData" />
+```
+
+添加 import:`import FamilyEnergyBar from '@/components/FamilyEnergyBar.vue'`
+
+- [ ] **Step 2: 增加维度商品/活动区块**
+
+在「最近能量变动」下方增加:
+```html
+<!-- ===== 推荐活动 ===== -->
+<DimensionActivities
+  v-if="hasChildren"
+  dimensionCode="child"
+  :activities="dimensionActivities"
+  @activityClick="goActivityDetail"
+  @moreActivities="goMoreActivities" />
+
+<!-- ===== 推荐商品 ===== -->
+<DimensionProducts
+  v-if="hasChildren"
+  dimensionCode="child"
+  :products="dimensionProducts"
+  @productClick="goProductDetail"
+  @moreProducts="goMoreProducts" />
+```
+
+添加对应 import,data 中增加 `dimensionActivities: [], dimensionProducts: []`,loadData 中调用对应加载方法。
+
+- [ ] **Step 3: reLaunch 改 switchTab(第 432 行)**
+
+修改前:
+```javascript
+uni.reLaunch({ url: '/pages/index/parent-index' })
+```
+
+修改后:
+```javascript
+uni.switchTab({ url: '/pages/index/index' })
+```
+
+---
+
+### Task 5: teacher/index.vue — 软废弃
+
+**Files:**
+- Modify: `cfc-frontend/pages/teacher/index.vue`
+
+- [ ] **Step 1: 文件首部添加废弃标记**
+
+在 `<template>` 上方添加注释:
+```html
+<!--
+  ⚠️ 废弃说明(2026-06-15):
+  此页面已按照设计决策废弃。教师首页功能已折入 parent-index。
+  保留文件仅用于参考,不通过任何路由访问。
+-->
+```
+
+- [ ] **Step 2: 模板替换为提示页**
+
+将整个 `<template>` 内容替换为简洁提示:
+```html
+<template>
+  <view class="deprecated-container">
+    <text class="deprecated-icon">📋</text>
+    <text class="deprecated-title">服务商功能已整合</text>
+    <text class="deprecated-desc">服务商工具和待办已迁移至家长首页</text>
+    <view class="deprecated-btn" @click="goToHome">回到首页</view>
+  </view>
+</template>
+```
+
+保留 script 中 `goToHome` 方法(`uni.switchTab({ url: '/pages/index/index' })`)。
+
+- [ ] **Step 3: 确认 pages.json 中 teacher 子包保留**
+
+teacher 子包页面(families/team/messages/orders 等)作为功能页面仍可通过导航访问,仅首页废弃。**无需移除 subPackage 注册。**
+
+---
+
+### Task 6: body/index.vue — 游客可见商品/活动 + 修复 BASE_URL
+
+**Files:**
+- Modify: `cfc-frontend/pages/body/index.vue`
+
+- [ ] **Step 1: 维度商品/活动/任务对游客可见(不隐藏,仅无价格)**
+
+修改前(第 94-107 行):
+```html
+<DimensionActivities
+  v-if="isLoggedIn"
+  ...
+/>
+<DimensionProducts
+  v-if="isLoggedIn"
+  ...
+/>
+<DimensionTasks
+  v-if="isLoggedIn"
+  ...
+/>
+```
+
+修改后:
+```html
+<DimensionActivities
+  ...
+/>
+<DimensionProducts
+  ...
+/>
+<DimensionTasks
+  v-if="isLoggedIn"
+  ...
+/>
+```
+
+(任务仍需要登录,因为关联孩子;活动和商品游客可浏览)
+
+在 `onShow` 中,游客也需要加载活动和商品数据:
+```javascript
+this.loadDimensionActivities()
+this.loadDimensionProducts()
+```
+
+- [ ] **Step 2: 修复 BASE_URL 硬编码(第 150 行)**
+
+修改前:
+```javascript
+const BASE_URL = 'http://localhost:8080'
+```
+
+修改后:
+```javascript
+import config from '../../config.js'
+const BASE_URL = config.api('')
+```
+
+---
+
+### Task 7: mind/index.vue — 堆叠图 + 游客可见商品/活动
+
+**Files:**
+- Modify: `cfc-frontend/pages/mind/index.vue`
+- Modify: `cfc-frontend/components/FamilyEnergyBar.vue`
+
+- [ ] **Step 1: mind 页 FamilyEnergyBar 传入 dualDimension 参数(第 20-24 行)**
+
+修改前:
+```html
+<FamilyEnergyBar
+  v-if="isLoggedIn"
+  dimensionCode="mind"
+  :sandboxData="sandboxData"
+  :dualDimension="dualDimension" />
+```
+
+`dualDimension` 当前只传了 mind 维度的数据。需要改为包含 mind + wisdom 两个维度的堆叠数据对象。
+
+在 `loadEnergyData` 方法中修改(第 662-680 行):
+```javascript
+loadEnergyData: function() {
+  var self = this
+  if (!this.currentChildId) return
+  getEnergyOverview(this.currentChildId).then(function(res) {
+    if (res && res.data) {
+      var dims = res.data.dimensions
+      if (dims && dims.length > 0) {
+        var mindDim = null
+        var wisdomDim = null
+        for (var i = 0; i < dims.length; i++) {
+          if (dims[i].code === 'mind') mindDim = dims[i]
+          if (dims[i].code === 'wisdom') wisdomDim = dims[i]
+        }
+        // dualDimension 格式:{ mind: { score, label, color }, wisdom: { score, label, color } }
+        self.dualDimension = {
+          primary: mindDim || { code: 'mind', score: 0, label: '心理' },
+          secondary: wisdomDim || { code: 'wisdom', score: 0, label: '认知' }
+        }
+      }
+    }
+  }).catch(function(e) {
+    console.log('获取能量概览失败', e)
+  })
+}
+```
+
+- [ ] **Step 2: FamilyEnergyBar 支持 dualDimension 堆叠渲染**
+
+在 `cfc-frontend/components/FamilyEnergyBar.vue` 中,当 `dualDimension` 为对象且包含 `{ primary, secondary }` 时,能量柱渲染为上下堆叠:
+
+```html
+<!-- 堆叠柱渲染 -->
+<view class="stacked-bar" v-if="dualDimension && dualDimension.primary">
+  <view class="stacked-bar-segment stacked-bar--primary"
+    :style="{ height: primaryPercent + '%', background: '#FF6B35' }"
+    @click="onSegmentClick('mind')">
+    <text class="segment-label">心 {{ dualDimension.primary.score }}</text>
+  </view>
+  <view class="stacked-bar-segment stacked-bar--secondary"
+    :style="{ height: secondaryPercent + '%', background: '#8B5CF6' }"
+    @click="onSegmentClick('wisdom')">
+    <text class="segment-label">智 {{ dualDimension.secondary.score }}</text>
+  </view>
+</view>
+```
+
+computed 增加:
+```javascript
+primaryPercent() {
+  if (!this.dualDimension || !this.dualDimension.primary) return 0
+  return Math.min(100, this.dualDimension.primary.score || 0)
+},
+secondaryPercent() {
+  if (!this.dualDimension || !this.dualDimension.secondary) return 0
+  return Math.min(100, this.dualDimension.secondary.score || 0)
+}
+```
+
+- [ ] **Step 3: 游客可见维度商品/活动(同 Task 6 Step 1)**
+
+修改前(第 261-280 行):
+```html
+<DimensionTasks v-if="isLoggedIn" ... />
+<DimensionActivities v-if="isLoggedIn" ... />
+<DimensionProducts v-if="isLoggedIn" ... />
+```
+
+修改后:
+```html
+<DimensionTasks v-if="isLoggedIn" ... />
+<DimensionActivities ... />
+<DimensionProducts ... />
+```
+
+---
+
+### Task 8: action/index.vue — 游客可见商品/活动 + 修复商城路径
+
+**Files:**
+- Modify: `cfc-frontend/pages/action/index.vue`
+
+- [ ] **Step 1: 游客可见维度商品/活动**
+
+修改前(第 37-56 行):
+```html
+<DimensionTasks v-if="isLoggedIn" ... />
+<DimensionActivities v-if="isLoggedIn" ... />
+<DimensionProducts v-if="isLoggedIn" ... />
+```
+
+修改后:
+```html
+<DimensionTasks v-if="isLoggedIn" ... />
+<DimensionActivities ... />
+<DimensionProducts ... />
+```
+
+- [ ] **Step 2: 修复 funcList 商城路径(第 141 行)**
+
+修改前:
+```javascript
+{ icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/index' }
+```
+
+修改后:
+```javascript
+{ icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/detail/detail' }
+```
+
+确认实际商城页面路径(pages.json 中 shop 子包入口页为 `detail/detail`,或者改为 `'/pages/discover/index?type=product'`)。
+
+---
+
+### Task 9: DimensionProducts 组件 — 支持游客模式隐藏价格
+
+**Files:**
+- Modify: `cfc-frontend/components/DimensionProducts.vue`
+
+- [ ] **Step 1: 增加 isLoggedIn prop**
+
+```javascript
+props: {
+  // ... 现有 props
+  isLoggedIn: { type: Boolean, default: false }
+}
+```
+
+- [ ] **Step 2: 价格区域根据 isLoggedIn 切换显示**
+
+在价格渲染位置:
+```html
+<view class="product-price-row" v-if="isLoggedIn">
+  <text class="product-price">¥{{ item.price }}</text>
+  <text v-if="item.memberPrice" class="product-member">会员¥{{ item.memberPrice }}</text>
+</view>
+<view class="product-price-row" v-else>
+  <text class="product-login-tip">登录查看</text>
+</view>
+```
+
+在 body/mind/action 页使用时传入 `:isLoggedIn="isLoggedIn"`。
+
+---
+
+### Task 10: DimensionActivities 组件 — 支持游客空占位
+
+**Files:**
+- Modify: `cfc-frontend/components/DimensionActivities.vue`
+
+- [ ] **Step 1: 增加 isLoggedIn prop**
+
+```javascript
+props: {
+  // ... 现有 props
+  isLoggedIn: { type: Boolean, default: false }
+}
+```
+
+- [ ] **Step 2: 空数据时区分游客和登录态**
+
+在空数据展示处:
+```html
+<view class="activities-empty" v-if="activities.length === 0">
+  <text class="empty-tip">{{ isLoggedIn ? '暂无相关活动' : '登录后查看更多活动' }}</text>
+</view>
+```
+
+在 body/mind/action 页使用时传入 `:isLoggedIn="isLoggedIn"`。
+
+---
+
+### Task 11: 后端 — 商品游客价格返回 null
+
+**Files:**
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/product/ProductController.java`
+
+- [ ] **Step 1: 商品列表接口:无 token 时 price/memberPrice 返回 null**
+
+在 `ProductController` 的商品查询方法中,检查 `@RequestAttribute("userId")` 是否为 null。若无 userId(游客),将商品 DTO 中的 price/memberPrice 字段置为 null:
+
+```java
+// 在返回前遍历商品列表
+if (userId == null) {
+    // 游客模式:隐藏价格
+    productDTO.setPrice(null);
+    productDTO.setMemberPrice(null);
+}
+```
+
+---
+
+### Task 12: 后端 — Activity 可见性三字段
+
+**Files:**
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/entity/Activity.java`
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/sncp/ActivityController.java`
+
+- [ ] **Step 1: Activity 实体增加可见性字段**
+
+```java
+/** 可见性:public(公开) / restricted(私密) */
+private String visibility;
+
+/** 可见范围:family(家庭) / role(角色) / user(用户) */
+private String visibleScope;
+
+/** 可见对象标识:家庭ID/角色名/用户ID 的 JSON 数组,如 "["family_1","family_2"]" */
+private String visibleTo;
+```
+
+对应增加 getter/setter(可用 Lombok `@Data`)。
+
+- [ ] **Step 2: 活动列表接口增加可见性过滤**
+
+在 `ActivityController` 的列表查询中:
+```java
+// 游客:只返回 public 活动
+if (userId == null) {
+    queryWrapper.eq("visibility", "public");
+} else {
+    // 登录用户:public + 自己有权限的 restricted 活动
+    queryWrapper.and(w -> 
+        w.eq("visibility", "public")
+        .or(w2 -> w2.eq("visibility", "restricted")
+            .and(w3 -> {
+                // visibleScope = family → familyId 在 visibleTo 中
+                // visibleScope = role → role 在 visibleTo 中
+                // visibleScope = user → userId 在 visibleTo 中
+                ...
+            })
+        )
+    );
+}
+```
+
+---
+
+### Task 13: 验证清单
+
+- [ ] **Step 1: LSP 诊断**
+
+对每个修改过的前端文件运行 `lsp_diagnostics`,确保无错误。
+
+- [ ] **Step 2: 后端编译**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+预期输出:`BUILD SUCCESS`
+
+- [ ] **Step 3: 功能验收**
+
+| 验收项 | 预期结果 |
+|--------|---------|
+| TabBar 第5个 Tab 显示"富沛" | 图标和文字正确 |
+| 未登录首页商品列表显示 | 显示商品封面和名称,无价格,显示"登录查看" |
+| 未登录点击商品 | 弹登录提示 |
+| 游客进入身体/心智/行动页 | 看到活动/商品列表,无价格 |
+| 家长首页无沙盘 | 五行沙盘不渲染 |
+| 家长首页有服务商入口 | teacher 用户看到服务商工具卡片 |
+| 心智页能量柱为堆叠图 | 一根柱上下显示心+智 |
+| 教师首页废弃 | 直接跳转到 `/pages/teacher/index` 显示废弃提示 |