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: 改造"我的"Tab页为品牌统一的富沛页面,补全品牌头/能量展示/菜单分组/勋章墙/成长报告/邀请合并等设计规范要求
Architecture: 后端勋章API和统计API已存在,前端改造 profile/profile.vue 一个主文件(938行)+ mind/index.vue 一个文件 + utils/api.js 新增两个方法
Tech Stack: uni-app Vue 2, 微信小程序
Design Spec Ref: docs/superpowers/specs/2026-06-19-profile-page-redesign.md
All existing issues in backend Java files are pre-existing and unrelated to these changes.
cfc-frontend/components/、cfc-frontend/pages/、cfc-frontend/utils/ 下搜索。禁止搜索 node_modules/、unpackage/、dist/?.(微信小程序不支持)git commit 或 git push,只修改文件function 关键字(Vue 2 Options API)v-for 和 v-if 不要在同一元素上(用 <template> 包裹)profile/profile.vue、mind/index.vue、utils/api.jsFiles:
Modify: cfc-frontend/utils/api.js(在文件末尾追加)
[ ] Step 1: 在 api.js 末尾新增方法
// ===================== 勋章系统 =====================
export const getUserBadges = (childId) => {
return request('/api/badges/child/list', 'POST', { childId })
}
// ===================== 成长报告 =====================
export const getGrowthReport = (childId) => {
return request('/api/stats/child/overview', 'POST', { childId })
}
Files:
Modify: cfc-frontend/pages/profile/profile.vue
[ ] Step 1: data() 新增字段
在 data() 中 growthReport 对象之后添加:
wealthEnergy: 0,
energyTrend: 0,
badgesLoaded: false,
loadEnergyData() 方法替换整个 loadEnergyData 方法(当前第509-523行)为提取 wealth 维度:
loadEnergyData: function() {
var self = this
var childId = uni.getStorageSync('currentChildId') || null
if (!childId && self.children.length > 0) {
childId = self.children[0].id
}
if (!childId) return
try {
getEnergyOverview(childId).then(function(res) {
if (res && res.data) {
self.totalEnergy = res.data.totalEnergy || 0
var dims = res.data.dimensions
if (dims && dims.length > 0) {
for (var i = 0; i < dims.length; i++) {
if (dims[i].code === 'wealth') {
self.wealthEnergy = dims[i].score || 0
break
}
}
}
}
})
} catch (e) { console.log('获取能量概览失败', e) }
}
注意:需要在文件顶部 import 中添加 getEnergyOverview 引用(确认已有)。
loadBadges() 方法替换当前 loadBadges 方法(第525-536行):
loadBadges: function() {
var self = this
var childId = uni.getStorageSync('currentChildId') || null
if (!childId && self.children.length > 0) {
childId = self.children[0].id
}
// 默认9个勋章(API失败时的fallback)
var defaultBadges = [
{ id: 1, icon: '\u{1F3C6}', name: '连续7天', unlocked: false },
{ id: 2, icon: '\u{2B50}', name: '积分达人', unlocked: false },
{ id: 3, icon: '\u{1F525}', name: '执行之星', unlocked: false },
{ id: 4, icon: '\u{1F31F}', name: '学习先锋', unlocked: false },
{ id: 5, icon: '\u{1F4AA}', name: '体能达人', unlocked: false },
{ id: 6, icon: '\u{1F9E0}', name: '心智成长', unlocked: false },
{ id: 7, icon: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', name: '家庭之星', unlocked: false },
{ id: 8, icon: '\u{1F4DA}', name: '阅读达人', unlocked: false },
{ id: 9, icon: '\u{1F3AF}', name: '全能冠军', unlocked: false }
]
// 先尝试API
if (childId) {
try {
getUserBadges(childId).then(function(res) {
if (res.code === 200 && res.data && res.data.length > 0) {
// API返回的数据结构: [{ childBadge: {...}, badge: { id, name, icon, ... } }]
var list = []
for (var i = 0; i < res.data.length; i++) {
var item = res.data[i]
var badge = item.badge || {}
list.push({
id: badge.id || i + 1,
icon: badge.icon || '\u{2B50}',
name: badge.name || '未知勋章',
unlocked: true
})
}
// 补全到9个
while (list.length < 9) {
var idx = list.length
list.push({
id: defaultBadges[idx].id,
icon: defaultBadges[idx].icon,
name: defaultBadges[idx].name,
unlocked: false
})
}
self.badges = list.slice(0, 9)
self.badgesLoaded = true
return
}
// API返回无数据,fallback到本地逻辑
self.loadBadgesLocal(defaultBadges)
}).catch(function() {
self.loadBadgesLocal(defaultBadges)
})
} catch (e) {
self.loadBadgesLocal(defaultBadges)
}
} else {
self.loadBadgesLocal(defaultBadges)
}
},
loadBadgesLocal: function(defaultBadges) {
var streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
var totalPoints = parseInt(uni.getStorageSync('totalPoints') || 0)
var completedTasks = parseInt(uni.getStorageSync('completedTasks') || 0)
// 更新解锁状态
var badges = JSON.parse(JSON.stringify(defaultBadges))
if (streakDays >= 7) badges[0].unlocked = true
if (totalPoints >= 1000) badges[1].unlocked = true
if (completedTasks >= 50) badges[2].unlocked = true
if (totalPoints >= 500) badges[3].unlocked = true
if (streakDays >= 30) badges[4].unlocked = true
if (completedTasks >= 100) badges[8].unlocked = true
this.badges = badges
this.badgesLoaded = true
}
loadGrowthReport() 方法在 methods 中 loadBadges 之后、loadActionData 之前添加:
loadGrowthReport: function() {
var self = this
var childId = uni.getStorageSync('currentChildId') || null
if (!childId && self.children.length > 0) {
childId = self.children[0].id
}
if (!childId) {
// 无childId时使用本地mock
self.loadGrowthReportLocal()
return
}
try {
getGrowthReport(childId).then(function(res) {
if (res.code === 200 && res.data) {
var data = res.data
self.growthReport.monthActiveDays = data.monthActiveDays || data.activeDays || 0
self.growthReport.totalTasks = (data.taskStats && data.taskStats.total) || data.totalTasks || 0
self.growthReport.completionRate = (data.taskStats && data.taskStats.completionRate) || data.completionRate || 0
} else {
self.loadGrowthReportLocal()
}
}).catch(function() {
self.loadGrowthReportLocal()
})
} catch (e) {
self.loadGrowthReportLocal()
}
},
loadGrowthReportLocal: function() {
var streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
var completedTasks = parseInt(uni.getStorageSync('completedTasks') || 0)
this.growthReport.monthActiveDays = streakDays > 0 ? Math.min(streakDays, 30) : 0
this.growthReport.totalTasks = completedTasks || 0
this.growthReport.completionRate = 85
}
onShow 中调用 loadGrowthReport()在 onShow 方法的 this.loadBadges() 之后添加:
this.loadGrowthReport()
在 api.js 的 import 行中,确认已有 getEnergyOverview;添加 getUserBadges 和 getGrowthReport:
import { setPassword, verifyPassword, getChildren, vendorStatus, getEnergyOverview, getUserActionStats, getReferralCode, getReferralSummary, getCommissionSummary, updateMemberVisibility, getVisibleFamilyMembers, getUserBadges, getGrowthReport } from '../../utils/api.js'
修改模板第14-16行:
<PageBanner theme="wealth"
tagline="丰盈内心,富足生活,厚德载物"
quote="" />
在第18行(exit-switch之前)插入:
<!-- 富沛能量值 -->
<view class="wealth-energy-header" v-if="isLoggedIn && wealthEnergy > 0">
<text class="energy-header-icon">💧</text>
<text class="energy-header-value">{{ wealthEnergy }}</text>
<text class="energy-header-label">富沛能量</text>
<text class="energy-header-trend" v-if="energyTrend > 0">↑较上周+{{ energyTrend }}</text>
</view>
在 <style scoped> 中 .user-card 之前添加:
/* ===== 富沛能量值条 ===== */
.wealth-energy-header {
background: linear-gradient(135deg, #42A5F5, #64B5F6);
padding: 16rpx 30rpx;
display: flex;
flex-direction: row;
align-items: center;
}
.energy-header-icon {
font-size: 36rpx;
margin-right: 12rpx;
}
.energy-header-value {
font-size: 40rpx;
font-weight: bold;
color: #fff;
margin-right: 12rpx;
}
.energy-header-label {
font-size: 26rpx;
color: #D6EAF8;
margin-right: auto;
}
.energy-header-trend {
font-size: 24rpx;
color: #4ADE80;
font-weight: 500;
}
在 system-points 行(第28行)之后插入:
<view class="wealth-energy-row" v-if="wealthEnergy > 0">
<text class="wealth-energy-icon">💧</text>
<text class="wealth-energy-value">富沛能量: {{ wealthEnergy }}</text>
<text class="wealth-energy-trend" v-if="energyTrend > 0"> ↑较上周+{{ energyTrend }}</text>
</view>
在 .system-points 样式之后添加:
.wealth-energy-row {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 8rpx;
}
.wealth-energy-icon {
font-size: 24rpx;
margin-right: 8rpx;
}
.wealth-energy-value {
font-size: 24rpx;
color: #D6EAF8;
font-weight: 500;
}
.wealth-energy-trend {
font-size: 22rpx;
color: #4ADE80;
margin-left: 8rpx;
}
将当前的 menu-list(第131-183行)替换为分组结构:
<view class="menu-list">
<!-- ===== 个人 ===== -->
<view class="menu-group">
<view class="menu-group-title">── 个人 ──</view>
<view class="menu-item" @click="goToEditInfo">
<text>📝 修改个人资料</text>
<text class="arrow">›</text>
</view>
<view class="menu-item" v-if="role === 'parent'" @click="goToChildren">
<text>👶 我的孩子</text>
<text class="arrow">›</text>
</view>
<view class="menu-item" v-if="role === 'parent'" @click="goToFamilyMembers">
<text>👨👩👧👦 家庭成员</text>
<text class="arrow">›</text>
</view>
</view>
<!-- ===== 财富 ===== -->
<view class="menu-group">
<view class="menu-group-title">── 财富 ──</view>
<view class="menu-item" @click="goToPromotion">
<text>📢 推广中心</text>
<text class="arrow">›</text>
</view>
<view class="menu-item" @click="goToPointsLogs">
<text>📊 积分记录</text>
<text class="arrow">›</text>
</view>
</view>
<!-- ===== 服务 ===== -->
<view class="menu-group">
<view class="menu-group-title">── 服务 ──</view>
<view class="menu-item" @click="goToMembership">
<text>👑 会员中心</text>
<text class="arrow">›</text>
</view>
<view class="menu-item" v-if="isVendor" @click="goToVendorCenter">
<text>🏪 服务商中心</text>
<text class="arrow">›</text>
</view>
<view class="menu-item" @click="goToOrders">
<text>🛒 订单管理</text>
<text class="arrow">›</text>
</view>
</view>
<!-- ===== 设置 ===== -->
<view class="menu-group">
<view class="menu-group-title">── 设置 ──</view>
<view class="menu-item" v-if="role === 'parent'" @click="showPasswordModal = true">
<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>
<view class="menu-item" @click="showInviteActionSheet">
<text>📨 邀请加入</text>
<text class="arrow">›</text>
</view>
<view class="menu-item" @click="logout">
<text>🚪 退出登录</text>
<text class="arrow">›</text>
</view>
</view>
</view>
移除现有的 inviteFamily/inviteTeacher 两个 button 元素(当前第161-168行)。
goToOrders 方法在 methods 中 goToVendorCenter 之后添加:
goToOrders: function() {
uni.showToast({ title: '即将上线', icon: 'none' })
}
showInviteActionSheet 方法在 methods 中 logout 方法之前添加:
showInviteActionSheet: function() {
var self = this
var familyId = uni.getStorageSync('familyId')
var userInfo = uni.getStorageSync('userInfo')
if (!familyId) {
uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
return
}
var items = ['👨👩👧👦 邀请家人', '👨🏫 邀请成长规划师']
var types = ['family', 'guide']
uni.showActionSheet({
itemList: items,
success: function(res) {
var type = types[res.tapIndex]
self.inviteGenerate(type, familyId, userInfo)
},
fail: function() {}
})
},
inviteGenerate: function(type, familyId, userInfo) {
var self = this
try {
var inviteTypes = {
family: { title: (userInfo && userInfo.nickname ? userInfo.nickname : '家人') + ' 邀请你加入家庭', path: '/pages/login/login?invite_type=family' },
guide: { title: (userInfo && userInfo.nickname ? userInfo.nickname : '家长') + ' 邀请你成为家庭成长规划师', path: '/pages/login/login?invite_type=guide' }
}
var config = inviteTypes[type] || inviteTypes.family
self.shareData = {
title: config.title,
path: config.path + '&invite_code=',
imageUrl: '/static/invite-card.png'
}
uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
} catch (e) {
uni.showToast({ title: e.message || '邀请失败', icon: 'none' })
}
}
删除当前第454-498行的 inviteFamily、inviteFamilyGenerate、inviteTeacher、inviteTeacherGenerate 四个方法。
在 .menu-list 样式之前添加:
/* ===== 功能菜单分组 ===== */
.menu-group {
margin-top: 20rpx;
}
.menu-group-title {
font-size: 24rpx;
color: #ccc;
text-align: center;
padding: 12rpx 0;
letter-spacing: 4rpx;
}
替换当前勋章网格(第97-105行)为动态3×3网格:
<view class="badge-grid">
<view class="badge-item" v-for="badge in badges" :key="badge.id">
<view :class="['badge-icon-wrap', badge.unlocked ? 'unlocked' : 'locked']">
<text class="badge-icon">{{ badge.icon }}</text>
</view>
<text class="badge-name">{{ badge.name }}</text>
</view>
</view>
(当前模板结构已基本一致,只需确认v-for写法 — 当前已是 v-for="badge in badges",保持不变)
修改 .badge-grid 样式(第714-720行):
.badge-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
background: #fff;
border-radius: 20rpx;
padding: 24rpx 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
justify-content: flex-start;
}
.badge-item {
display: flex;
flex-direction: column;
align-items: center;
width: 33.33%;
margin-bottom: 20rpx;
}
goGrowthReport 方法替换当前方法(第583-585行):
goGrowthReport: function() {
var childId = uni.getStorageSync('currentChildId') || ''
uni.navigateTo({ url: '/pages/growth/report?childId=' + childId })
}
Files:
Modify: cfc-frontend/pages/mind/index.vue
[ ] Step 1: 在心理Tab内容底部添加心愿单区块
在心理Tab内容内、<!-- ===== 推荐阅读 ===== --> 区块之后、</template>(心理Tab闭合)之前插入:
<!-- 心愿单入口(心理Tab) -->
<view class="section wishlist-section" v-if="isLoggedIn">
<view class="wishlist-card" @click="goWishlist">
<view class="wishlist-left">
<text class="wishlist-icon">💝</text>
</view>
<view class="wishlist-body">
<text class="wishlist-title">心愿单</text>
<text class="wishlist-desc">孩子可在此创建、兑换心愿</text>
</view>
<text class="wishlist-arrow">›</text>
</view>
</view>
goWishlist 方法在 methods 中 goAssessment 方法之前添加:
goWishlist: function() {
uni.navigateTo({ url: '/pages/rewards/rewards' })
}
在 <style scoped> 末尾添加:
/* ===== 心愿单入口 ===== */
.wishlist-card {
background: #fff;
border-radius: 20rpx;
padding: 28rpx 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
display: flex;
flex-direction: row;
align-items: center;
}
.wishlist-left {
width: 72rpx;
height: 72rpx;
background: linear-gradient(135deg, #FF6B35, #FFD700);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
}
.wishlist-icon {
font-size: 36rpx;
}
.wishlist-body {
flex: 1;
}
.wishlist-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 4rpx;
}
.wishlist-desc {
font-size: 22rpx;
color: #999;
}
.wishlist-arrow {
font-size: 36rpx;
color: #ccc;
margin-left: 12rpx;
}
使用 lsp_diagnostics 检查 cfc-frontend/pages/profile/profile.vue、cfc-frontend/pages/mind/index.vue、cfc-frontend/utils/api.js