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 三字段 |
Files:
Modify: cfc-frontend/pages.json:589-626
[ ] Step 1: 修改 pages.json TabBar 配置
// 在 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 子包)。
确认 pages.json wealth 子包已包含 pages/wealth/index 页面,确认 static/tab-wealth.png 和 static/tab-wealth-active.png 图标文件存在。如果不存在,创建 48x48px 的占位图标。
从 static/ 目录检查是否有 tab-profile.png / tab-profile-active.png,确认不再被引用。
Files:
Modify: cfc-frontend/pages/index/index.vue
[ ] Step 1: 移除 TeacherIndex 引入(第 108 行)
修改前:
import TeacherIndex from '../teacher/index.vue'
修改后:
// TeacherIndex 已废弃,教师功能折入 parent-index
同时从 components 对象中移除 TeacherIndex。
修改前:
<teacher-index v-else-if="currentRole === 'teacher'" />
修改后:
<!-- teacher-index 已废弃,教师功能折入 parent-index -->
修改前(第 200 行):
productList(params).then(res => { ... })
修改后:
// 调用 getProductsByDomain 或其他 api.js 中已封装的 /api/product/list
修改前:
<view class="product-price-row">
<text class="product-price">¥{{ item.price }}</text>
<text v-if="item.memberPrice" class="product-member">会员¥{{ item.memberPrice }}</text>
</view>
修改后:
<view class="product-price-row">
<!-- 游客不显示价格 -->
<text class="product-price-login">登录查看</text>
</view>
修改前:
@click="goDetail(item.id)"
修改后:
@click="handleLogin()"
并在 handleLogin 中:
handleLogin() {
uni.showModal({
title: '提示',
content: '请先登录后查看详情',
success: function(res) {
if (res.confirm) uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/index/index') })
}
})
}
确认在 onShow 的未登录分支中调用 this.loadProducts(),使游客首次进入能看到商品列表。
onShow() {
if (!this.isLoggedIn()) {
this.currentRole = ''
this.loadProducts() // 游客加载商品
return
}
this.syncRoleAndTabBar()
}
Files:
Modify: cfc-frontend/pages/index/parent-index.vue
[ ] Step 1: 移除五行沙盘(第 74-84 行)
删除整个 wuxing-sandbox 组件的实例化和 import。
修改前:
<!-- 五行能量沙盘(浠艾福 — 全家庭版) -->
<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>
修改后:删除。
在 Banner 下方增加:
<!-- 家庭能量横条 -->
<FamilyEnergyBar
v-if="hasFamilyData"
dimensionCode="parent"
:sandboxData="sandboxData" />
添加对应 import:
import FamilyEnergyBar from '@/components/FamilyEnergyBar.vue'
在「待处理事项」区域增加服务商快捷卡片,当用户角色为 teacher 或 role 包含 teacher 时显示:
<!-- ===== 服务商快捷入口(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
在「成长建议」后面增加:
<!-- ===== 推荐活动 ===== -->
<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'
Files:
Modify: cfc-frontend/pages/index/child-index.vue
[ ] Step 1: 在 Banner 下方增加 FamilyEnergyBar
<!-- 家庭能量横条 -->
<FamilyEnergyBar
v-if="hasChildren"
dimensionCode="child"
:sandboxData="sandboxData" />
添加 import:import FamilyEnergyBar from '@/components/FamilyEnergyBar.vue'
在「最近能量变动」下方增加:
<!-- ===== 推荐活动 ===== -->
<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 中调用对应加载方法。
修改前:
uni.reLaunch({ url: '/pages/index/parent-index' })
修改后:
uni.switchTab({ url: '/pages/index/index' })
Files:
Modify: cfc-frontend/pages/teacher/index.vue
[ ] Step 1: 文件首部添加废弃标记
在 <template> 上方添加注释:
<!--
⚠️ 废弃说明(2026-06-15):
此页面已按照设计决策废弃。教师首页功能已折入 parent-index。
保留文件仅用于参考,不通过任何路由访问。
-->
将整个 <template> 内容替换为简洁提示:
<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' }))。
teacher 子包页面(families/team/messages/orders 等)作为功能页面仍可通过导航访问,仅首页废弃。无需移除 subPackage 注册。
Files:
Modify: cfc-frontend/pages/body/index.vue
[ ] Step 1: 维度商品/活动/任务对游客可见(不隐藏,仅无价格)
修改前(第 94-107 行):
<DimensionActivities
v-if="isLoggedIn"
...
/>
<DimensionProducts
v-if="isLoggedIn"
...
/>
<DimensionTasks
v-if="isLoggedIn"
...
/>
修改后:
<DimensionActivities
...
/>
<DimensionProducts
...
/>
<DimensionTasks
v-if="isLoggedIn"
...
/>
(任务仍需要登录,因为关联孩子;活动和商品游客可浏览)
在 onShow 中,游客也需要加载活动和商品数据:
this.loadDimensionActivities()
this.loadDimensionProducts()
修改前:
const BASE_URL = 'http://localhost:8080'
修改后:
import config from '../../config.js'
const BASE_URL = config.api('')
Files:
cfc-frontend/pages/mind/index.vueModify: cfc-frontend/components/FamilyEnergyBar.vue
[ ] Step 1: mind 页 FamilyEnergyBar 传入 dualDimension 参数(第 20-24 行)
修改前:
<FamilyEnergyBar
v-if="isLoggedIn"
dimensionCode="mind"
:sandboxData="sandboxData"
:dualDimension="dualDimension" />
dualDimension 当前只传了 mind 维度的数据。需要改为包含 mind + wisdom 两个维度的堆叠数据对象。
在 loadEnergyData 方法中修改(第 662-680 行):
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)
})
}
在 cfc-frontend/components/FamilyEnergyBar.vue 中,当 dualDimension 为对象且包含 { primary, secondary } 时,能量柱渲染为上下堆叠:
<!-- 堆叠柱渲染 -->
<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 增加:
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)
}
修改前(第 261-280 行):
<DimensionTasks v-if="isLoggedIn" ... />
<DimensionActivities v-if="isLoggedIn" ... />
<DimensionProducts v-if="isLoggedIn" ... />
修改后:
<DimensionTasks v-if="isLoggedIn" ... />
<DimensionActivities ... />
<DimensionProducts ... />
Files:
Modify: cfc-frontend/pages/action/index.vue
[ ] Step 1: 游客可见维度商品/活动
修改前(第 37-56 行):
<DimensionTasks v-if="isLoggedIn" ... />
<DimensionActivities v-if="isLoggedIn" ... />
<DimensionProducts v-if="isLoggedIn" ... />
修改后:
<DimensionTasks v-if="isLoggedIn" ... />
<DimensionActivities ... />
<DimensionProducts ... />
修改前:
{ icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/index' }
修改后:
{ icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/detail/detail' }
确认实际商城页面路径(pages.json 中 shop 子包入口页为 detail/detail,或者改为 '/pages/discover/index?type=product')。
Files:
Modify: cfc-frontend/components/DimensionProducts.vue
[ ] Step 1: 增加 isLoggedIn prop
props: {
// ... 现有 props
isLoggedIn: { type: Boolean, default: false }
}
[ ] Step 2: 价格区域根据 isLoggedIn 切换显示
在价格渲染位置:
<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"。
Files:
Modify: cfc-frontend/components/DimensionActivities.vue
[ ] Step 1: 增加 isLoggedIn prop
props: {
// ... 现有 props
isLoggedIn: { type: Boolean, default: false }
}
[ ] Step 2: 空数据时区分游客和登录态
在空数据展示处:
<view class="activities-empty" v-if="activities.length === 0">
<text class="empty-tip">{{ isLoggedIn ? '暂无相关活动' : '登录后查看更多活动' }}</text>
</view>
在 body/mind/action 页使用时传入 :isLoggedIn="isLoggedIn"。
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:
// 在返回前遍历商品列表
if (userId == null) {
// 游客模式:隐藏价格
productDTO.setPrice(null);
productDTO.setMemberPrice(null);
}
Files:
cfc-backend/src/main/java/com/etotem/cfc/entity/Activity.javaModify: cfc-backend/src/main/java/com/etotem/cfc/controller/sncp/ActivityController.java
[ ] Step 1: Activity 实体增加可见性字段
/** 可见性: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)。
在 ActivityController 的列表查询中:
// 游客:只返回 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 中
...
})
)
);
}
对每个修改过的前端文件运行 lsp_diagnostics,确保无错误。
[ ] Step 2: 后端编译
cd cfc-backend && mvn clean compile -q
预期输出:BUILD SUCCESS
| 验收项 | 预期结果 |
|---|---|
| TabBar 第5个 Tab 显示"富沛" | 图标和文字正确 |
| 未登录首页商品列表显示 | 显示商品封面和名称,无价格,显示"登录查看" |
| 未登录点击商品 | 弹登录提示 |
| 游客进入身体/心智/行动页 | 看到活动/商品列表,无价格 |
| 家长首页无沙盘 | 五行沙盘不渲染 |
| 家长首页有服务商入口 | teacher 用户看到服务商工具卡片 |
| 心智页能量柱为堆叠图 | 一根柱上下显示心+智 |
| 教师首页废弃 | 直接跳转到 /pages/teacher/index 显示废弃提示 |