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

feat: update frontend pages, components and splash screen

Update miniprogram pages, components, config, API layer and add SkeletonBlock component, splash page, format utilities.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
User 4 месяцев назад
Родитель
Сommit
3ece018942
53 измененных файлов с 2505 добавлено и 1766 удалено
  1. 20 7
      cfc-frontend/components/DimensionActivities.vue
  2. 8 4
      cfc-frontend/components/DimensionProducts.vue
  3. 115 19
      cfc-frontend/components/FamilyEnergyBar.vue
  4. 18 8
      cfc-frontend/components/FamilyMemberCard.vue
  5. 7 3
      cfc-frontend/components/FamilyMemberStrip.vue
  6. 2 2
      cfc-frontend/components/HealthTips.vue
  7. 86 45
      cfc-frontend/components/RadarChart.vue
  8. 193 0
      cfc-frontend/components/SkeletonBlock.vue
  9. 1 1
      cfc-frontend/config.js
  10. 5 0
      cfc-frontend/main.js
  11. 22 2
      cfc-frontend/pages.json
  12. 4 3
      cfc-frontend/pages/action/index.vue
  13. 3 3
      cfc-frontend/pages/assessment/apply.vue
  14. 4 4
      cfc-frontend/pages/assessment/order.vue
  15. 15 15
      cfc-frontend/pages/assessment/purchase.vue
  16. 2 2
      cfc-frontend/pages/body/checkin.vue
  17. 2 2
      cfc-frontend/pages/discover/index.vue
  18. 4 4
      cfc-frontend/pages/discover/product-detail/product-detail.vue
  19. 1 1
      cfc-frontend/pages/guide/packages/detail.vue
  20. 1 1
      cfc-frontend/pages/guide/packages/index.vue
  21. 2 2
      cfc-frontend/pages/guide/packages/share.vue
  22. 115 18
      cfc-frontend/pages/index/child-index.vue
  23. 136 22
      cfc-frontend/pages/index/index.vue
  24. 165 331
      cfc-frontend/pages/index/parent-index.vue
  25. 2 2
      cfc-frontend/pages/membership/index.vue
  26. 3 3
      cfc-frontend/pages/membership/upgrade.vue
  27. 61 3
      cfc-frontend/pages/mind/index.vue
  28. 1 1
      cfc-frontend/pages/parent/market/detail.vue
  29. 1 1
      cfc-frontend/pages/parent/market/index.vue
  30. 411 32
      cfc-frontend/pages/profile/family-members.vue
  31. 2 2
      cfc-frontend/pages/profile/profile.vue
  32. 7 7
      cfc-frontend/pages/promotion/commission.vue
  33. 6 6
      cfc-frontend/pages/promotion/index.vue
  34. 2 2
      cfc-frontend/pages/promotion/invite.vue
  35. 5 5
      cfc-frontend/pages/promotion/withdraw.vue
  36. 4 4
      cfc-frontend/pages/shop/cart/cart.vue
  37. 4 4
      cfc-frontend/pages/shop/checkout/checkout.vue
  38. 4 4
      cfc-frontend/pages/shop/detail/detail.vue
  39. 2 2
      cfc-frontend/pages/shop/index.vue
  40. 2 2
      cfc-frontend/pages/shop/order-detail/order-detail.vue
  41. 1 1
      cfc-frontend/pages/shop/order-list/order-list.vue
  42. 3 3
      cfc-frontend/pages/shop/payment/payment.vue
  43. 162 0
      cfc-frontend/pages/splash/splash.vue
  44. 33 942
      cfc-frontend/pages/teacher/index.vue
  45. 4 4
      cfc-frontend/pages/teacher/orders.vue
  46. 1 1
      cfc-frontend/pages/teacher/teacher-home.vue
  47. 3 3
      cfc-frontend/pages/teacher/team.vue
  48. 1 1
      cfc-frontend/pages/vendor/orders/orders.vue
  49. 1 1
      cfc-frontend/pages/vendor/products/products.vue
  50. 800 229
      cfc-frontend/pages/wealth/index.vue
  51. 1 1
      cfc-frontend/pages/wealth/insurance-list.vue
  52. 1 1
      cfc-frontend/pages/wisdom/index.vue
  53. 46 0
      cfc-frontend/utils/format.js

+ 20 - 7
cfc-frontend/components/DimensionActivities.vue

@@ -1,10 +1,13 @@
 <template>
-  <view class="section" v-if="displayActivities && displayActivities.length > 0">
+  <view class="section">
     <view class="section-header">
       <text class="section-title">🔥 相关活动</text>
-      <text class="section-more" @click="$emit('moreActivities')">更多 ›</text>
+      <text class="section-more" v-if="displayActivities && displayActivities.length > 0" @click="$emit('moreActivities')">更多 ›</text>
     </view>
-    <view class="activity-list">
+    <view v-if="!displayActivities || displayActivities.length === 0" class="empty-state">
+      <text class="empty-tip">{{ isLoggedIn ? '暂无相关活动' : '登录后查看更多活动' }}</text>
+    </view>
+    <view class="activity-list" v-if="displayActivities && displayActivities.length > 0">
       <view class="activity-card" v-for="act in displayActivities" :key="act.id" @click="$emit('activityClick', act)">
         <image class="activity-cover" :src="act.coverImage || '/static/default-activity.png'" mode="aspectFill" />
         <view class="activity-info">
@@ -15,8 +18,8 @@
           <view class="activity-bottom">
             <text class="activity-status" :class="'status-' + (act.status || 'upcoming')">{{ statusText(act.status) }}</text>
             <text class="activity-fee" v-if="act.priceLabel">{{ act.priceLabel }}</text>
-            <text class="activity-fee" v-else-if="act.price && act.price > 0">¥{{ act.price }}</text>
-            <text class="activity-fee free" v-else>免费</text>
+            <text class="activity-fee" v-else-if="act.price && act.price > 0">{{ formatPriceWithSymbol(act.price) }}</text>
+            <text class="activity-fee fee-free" v-else>免费</text>
           </view>
         </view>
       </view>
@@ -28,7 +31,8 @@
 export default {
   props: {
     dimensionCode: { type: String, required: true },
-    activities: { type: Array, default: function() { return [] } }
+    activities: { type: Array, default: function() { return [] } },
+    isLoggedIn: { type: Boolean, default: false }
   },
   data: function() {
     return {
@@ -142,9 +146,18 @@ export default {
   color: #F97316;
   font-weight: 500;
 }
-.activity-fee.free {
+.fee-free {
   color: #22C55E;
 }
+.empty-state {
+  display: flex;
+  justify-content: center;
+  padding: 40rpx 0;
+}
+.empty-tip {
+  font-size: 24rpx;
+  color: #999;
+}
 .line-clamp-1 {
   overflow: hidden;
   text-overflow: ellipsis;

+ 8 - 4
cfc-frontend/components/DimensionProducts.vue

@@ -8,9 +8,12 @@
       <view class="product-card" v-for="prod in displayProducts" :key="prod.id" @click="$emit('productClick', prod)">
         <image class="product-img" :src="prod.coverImage || '/static/default-product.png'" mode="aspectFill" />
         <text class="product-name line-clamp-2">{{ prod.name }}</text>
-        <text class="product-price" v-if="prod.priceLabel">{{ prod.priceLabel }}</text>
-        <text class="product-price" v-else-if="prod.price">¥{{ prod.price }}</text>
-        <text class="product-price" v-else>免费</text>
+        <text class="product-price" v-if="!isLoggedIn">登录查看</text>
+        <template v-else>
+          <text class="product-price" v-if="prod.priceLabel">{{ prod.priceLabel }}</text>
+          <text class="product-price" v-else-if="prod.price">{{ formatPriceWithSymbol(prod.price) }}</text>
+          <text class="product-price" v-else>免费</text>
+        </template>
       </view>
     </view>
   </view>
@@ -20,7 +23,8 @@
 export default {
   props: {
     dimensionCode: { type: String, required: true },
-    products: { type: Array, default: function() { return [] } }
+    products: { type: Array, default: function() { return [] } },
+    isLoggedIn: { type: Boolean, default: false }
   },
   data: function() {
     return {

+ 115 - 19
cfc-frontend/components/FamilyEnergyBar.vue

@@ -6,26 +6,65 @@
       <text class="bar-total-score">{{ familyScore }}</text>
     </view>
     <view class="bar-list">
-      <view class="bar-row bar-row-family">
-        <text class="bar-label">全家</text>
-        <view class="bar-track">
-          <view class="bar-fill" :style="{ width: familyScore + '%', background: dimensionColor }"></view>
+      <!-- 非堆叠模式:单色条 -->
+      <template v-if="!isStacked">
+        <view class="bar-row bar-row-family">
+          <text class="bar-label">全家</text>
+          <view class="bar-track">
+            <view class="bar-fill" :style="{ width: familyScore + '%', background: dimensionColor }"></view>
+          </view>
+          <text class="bar-value">{{ familyScore }}</text>
         </view>
-        <text class="bar-value">{{ familyScore }}</text>
-      </view>
-      <view
-        class="bar-row bar-row-member"
-        v-for="member in members"
-        :key="member.memberId"
-        @click="onMemberClick(member)"
-      >
-        <text class="bar-label">{{ member.name }}</text>
-        <view class="bar-track">
-          <view class="bar-fill" :style="{ width: getMemberScore(member) + '%', background: memberColor(member) }"></view>
+        <view
+          class="bar-row bar-row-member"
+          v-for="member in members"
+          :key="member.memberId"
+          @click="onMemberClick(member)"
+        >
+          <text class="bar-label">{{ member.name }}</text>
+          <view class="bar-track">
+            <view class="bar-fill" :style="{ width: getMemberScore(member) + '%', background: memberColor(member) }"></view>
+          </view>
+          <text class="bar-value">{{ getMemberScore(member) }}</text>
+          <text class="bar-arrow">›</text>
         </view>
-        <text class="bar-value">{{ getMemberScore(member) }}</text>
-        <text class="bar-arrow">›</text>
-      </view>
+      </template>
+      <!-- 堆叠模式:主维度 + 副维度两段色 -->
+      <template v-else>
+        <view class="bar-row bar-row-family">
+          <text class="bar-label">全家</text>
+          <view class="bar-track stacked-track">
+            <view class="bar-fill bar-fill-primary" :style="{ width: (sandboxData[dualDimension.primary.code + 'Score'] || 0) + '%', background: dualDimension.primary.color || '#8B5CF6' }"></view>
+            <view class="bar-fill bar-fill-secondary" :style="{ width: (sandboxData[dualDimension.secondary.code + 'Score'] || 0) + '%', background: dualDimension.secondary.color || '#3B82F6' }"></view>
+          </view>
+          <text class="bar-value">{{ familyScore }}</text>
+        </view>
+        <view
+          class="bar-row bar-row-member"
+          v-for="member in members"
+          :key="member.memberId"
+          @click="onMemberClick(member)"
+        >
+          <text class="bar-label">{{ member.name }}</text>
+          <view class="bar-track stacked-track">
+            <view class="bar-fill bar-fill-primary" :style="{ width: getMemberPrimaryScore(member) + '%', background: dualDimension.primary.color || '#8B5CF6' }"></view>
+            <view class="bar-fill bar-fill-secondary" :style="{ width: getMemberSecondaryScore(member) + '%', background: dualDimension.secondary.color || '#3B82F6' }"></view>
+          </view>
+          <text class="bar-value">{{ getMemberScore(member) }}</text>
+          <text class="bar-arrow">›</text>
+        </view>
+        <!-- 图例 -->
+        <view class="stacked-legend">
+          <view class="legend-item">
+            <view class="legend-dot" :style="{ background: dualDimension.primary.color || '#8B5CF6' }"></view>
+            <text class="legend-label">{{ dualDimension.primary.label || '心' }}</text>
+          </view>
+          <view class="legend-item">
+            <view class="legend-dot" :style="{ background: dualDimension.secondary.color || '#3B82F6' }"></view>
+            <text class="legend-label">{{ dualDimension.secondary.label || '智' }}</text>
+          </view>
+        </view>
+      </template>
     </view>
   </view>
 </template>
@@ -35,9 +74,12 @@ export default {
   props: {
     dimensionCode: { type: String, required: true },
     sandboxData: { type: Object, default: null },
-    dualDimension: { type: String, default: '' }
+    dualDimension: { type: [String, Object], default: '' }
   },
   computed: {
+    isStacked: function() {
+      return typeof this.dualDimension === 'object' && this.dualDimension !== null && this.dualDimension.primary && this.dualDimension.secondary
+    },
     dimensionConfig: function() {
       var maps = {
         body:     { name: '身体', icon: '\u{1F3C3}', color: '#10B981' },
@@ -53,6 +95,11 @@ export default {
     dimensionColor: function() { return this.dimensionConfig.color },
     familyScore: function() {
       if (!this.sandboxData) return 0
+      if (this.isStacked) {
+        var primary = this.sandboxData[this.dualDimension.primary.code + 'Score'] || 0
+        var secondary = this.sandboxData[this.dualDimension.secondary.code + 'Score'] || 0
+        return primary + secondary
+      }
       return this.sandboxData[this.dimensionCode + 'Score'] || 0
     },
     members: function() {
@@ -61,8 +108,21 @@ export default {
   },
   methods: {
     getMemberScore: function(member) {
+      if (this.isStacked) {
+        var primary = member[this.dualDimension.primary.code + 'Score'] || 0
+        var secondary = member[this.dualDimension.secondary.code + 'Score'] || 0
+        return primary + secondary
+      }
       return member[this.dimensionCode + 'Score'] || 0
     },
+    getMemberPrimaryScore: function(member) {
+      if (!this.isStacked) return 0
+      return member[this.dualDimension.primary.code + 'Score'] || 0
+    },
+    getMemberSecondaryScore: function(member) {
+      if (!this.isStacked) return 0
+      return member[this.dualDimension.secondary.code + 'Score'] || 0
+    },
     memberColor: function(member) {
       var score = this.getMemberScore(member)
       if (score >= 80) return '#10B981'
@@ -149,4 +209,40 @@ export default {
 .bar-row-family .bar-fill {
   background: linear-gradient(90deg, #F97316, #FB923C);
 }
+
+/* ===== 堆叠模式 ===== */
+.stacked-track {
+  display: flex;
+  flex-direction: row;
+  overflow: hidden;
+}
+.bar-fill-primary {
+  border-radius: 12rpx 0 0 12rpx;
+}
+.bar-fill-secondary {
+  border-radius: 0 12rpx 12rpx 0;
+  margin-left: 2rpx;
+}
+.stacked-legend {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  margin-top: 12rpx;
+  gap: 24rpx;
+}
+.legend-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+.legend-dot {
+  width: 16rpx;
+  height: 16rpx;
+  border-radius: 8rpx;
+  margin-right: 6rpx;
+}
+.legend-label {
+  font-size: 22rpx;
+  color: #999;
+}
 </style>

+ 18 - 8
cfc-frontend/components/FamilyMemberCard.vue

@@ -1,6 +1,6 @@
 <template>
   <view class="member-card" :class="{ 'member-card--selected': isSelected }" @click="handleClick">
-    <view class="member-card__avatar" :class="'avatar-color-' + (member.id % 6)">
+    <view class="member-card__avatar" :class="avatarColorClass">
       <image v-if="member.avatar" class="member-card__avatar-img" :src="member.avatar" mode="aspectFill" />
       <text v-else class="member-card__avatar-text">{{ member.nickname && member.nickname[0] || '?' }}</text>
     </view>
@@ -22,6 +22,13 @@ export default {
     member: { type: Object, required: true },
     isSelected: { type: Boolean, default: false }
   },
+  computed: {
+    avatarColorClass: function() {
+      var isParent = this.member.memberType === 'parent' || this.member.role === 'parent'
+      if (isParent) return 'avatar-color--parent'
+      return 'avatar-color--child-' + (this.member.id % 6)
+    }
+  },
   methods: {
     handleClick() {
       this.$emit('select', this.member)
@@ -123,11 +130,14 @@ export default {
   font-weight: 700;
 }
 
-/* Avatar color palette */
-.avatar-color-0 { background: #5B9BD5; }
-.avatar-color-1 { background: #F08A7A; }
-.avatar-color-2 { background: #4CAF50; }
-.avatar-color-3 { background: #FFB74D; }
-.avatar-color-4 { background: #AB47BC; }
-.avatar-color-5 { background: #26A69A; }
+/* Avatar colors — 家长统一蓝色,孩子多彩渐变 */
+.avatar-color--parent {
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+}
+.avatar-color--child-0 { background: linear-gradient(135deg, #5B9BD5, #3A7CC4); }
+.avatar-color--child-1 { background: linear-gradient(135deg, #0EA5E9, #6366F1); }
+.avatar-color--child-2 { background: linear-gradient(135deg, #10B981, #059669); }
+.avatar-color--child-3 { background: linear-gradient(135deg, #EC4899, #E11D48); }
+.avatar-color--child-4 { background: linear-gradient(135deg, #84CC16, #65A30D); }
+.avatar-color--child-5 { background: linear-gradient(135deg, #8B5CF6, #6D28D9); }
 </style>

+ 7 - 3
cfc-frontend/components/FamilyMemberStrip.vue

@@ -25,7 +25,7 @@
         </view>
         <FamilyMemberCard
           v-for="member in allMembers"
-          :key="member.memberType + '-' + member.id"
+          :key="member._key"
           :member="member"
           :is-selected="selectedMemberId === member.id && selectedMemberType === member.memberType"
           @select="onSelect"
@@ -46,7 +46,7 @@
       </view>
       <FamilyMemberCard
         v-for="member in allMembers"
-        :key="member.memberType + '-' + member.id"
+        :key="member._key"
         :member="member"
         :is-selected="selectedMemberId === member.id && selectedMemberType === member.memberType"
         @select="onSelect"
@@ -68,7 +68,11 @@ export default {
   },
   computed: {
     allMembers: function() {
-      return this.members || []
+      var list = this.members || []
+      return list.map(function(m) {
+        m._key = m.memberType + '-' + m.id
+        return m
+      })
     }
   },
   methods: {

+ 2 - 2
cfc-frontend/components/HealthTips.vue

@@ -17,7 +17,7 @@
 </template>
 
 <script>
-const BASE_URL = 'http://localhost:8080'
+import config from '@/config.js'
 
 export default {
   props: {
@@ -35,7 +35,7 @@ export default {
       var self = this
       var token = uni.getStorageSync('token')
       uni.request({
-        url: BASE_URL + '/api/articles/list',
+        url: config.API_BASE_URL + '/api/articles/list',
         method: 'POST',
         data: {
           dimension: this.dimension,

+ 86 - 45
cfc-frontend/components/RadarChart.vue

@@ -46,6 +46,21 @@ export default {
         return []
       }
     },
+    /** 填充颜色(可选,默认暖橙) */
+    fillColor: {
+      type: String,
+      default: '#F97316'
+    },
+    /** 网格颜色(可选,默认浅灰) */
+    gridColor: {
+      type: String,
+      default: '#E2E8F0'
+    },
+    /** 标签颜色(可选,默认深灰) */
+    labelColor: {
+      type: String,
+      default: '#1E293B'
+    },
     /** 图表标题(显示在中心) */
     childName: {
       type: String,
@@ -72,12 +87,16 @@ export default {
       _canvasNode: null,
       _canvasLeft: 0,
       _canvasTop: 0,
-      /** 动画插值中的分数 [0..100] */
-      _animScores: [0, 0, 0, 0, 0, 0],
+      /** 动画插值中的分数 */
+      _animScores: [],
       _animId: null
     }
   },
   computed: {
+    /** 维度数量(灵活支持 N 维雷达图) */
+    dimCount: function() {
+      return this.dimensions ? this.dimensions.length : 0
+    },
     /** 容器内联样式 */
     containerStyle: function() {
       return {
@@ -85,11 +104,13 @@ export default {
         height: this.height + 'rpx'
       }
     },
-    /** 六边形 6 个顶点的标准角度(弧度),从顶部顺时针 */
+    /** N 边形的 N 个顶点的标准角度(弧度),从顶部顺时针 */
     vertexAngles: function() {
+      var n = this.dimCount
+      if (n < 3) return []
       var angles = []
-      for (var i = 0; i < 6; i++) {
-        angles.push(-Math.PI / 2 + (i * 2 * Math.PI) / 6)
+      for (var i = 0; i < n; i++) {
+        angles.push(-Math.PI / 2 + (i * 2 * Math.PI) / n)
       }
       return angles
     }
@@ -156,7 +177,7 @@ export default {
           self.canvasReady = true
 
           // 有分数则播放动画,否则只画网格
-          if (self.scores && self.scores.length >= 6) {
+          if (self.scores && self.scores.length >= self.dimCount && self.dimCount >= 3) {
             self._startAnimation()
           } else {
             self._doRender()
@@ -180,20 +201,22 @@ export default {
 
       ctx.clearRect(0, 0, w, h)
 
+      var n = this.dimCount
+
       // 1. 绘制背景网格
-      this._drawGrid(ctx, cx, cy, outerR, angles)
+      this._drawGrid(ctx, cx, cy, outerR, angles, n)
 
       // 2. 绘制分数区域
-      this._drawScoreArea(ctx, cx, cy, outerR, angles)
+      this._drawScoreArea(ctx, cx, cy, outerR, angles, n)
 
       // 3. 绘制顶点标记
-      this._drawVertices(ctx, cx, cy, outerR, angles)
+      this._drawVertices(ctx, cx, cy, outerR, angles, n)
 
       // 4. 绘制分数文字
-      this._drawScoreText(ctx, cx, cy, outerR, angles)
+      this._drawScoreText(ctx, cx, cy, outerR, angles, n)
 
       // 5. 绘制维度标签
-      this._drawLabels(ctx, cx, cy, outerR, angles)
+      this._drawLabels(ctx, cx, cy, outerR, angles, n)
 
       // 6. 绘制中心标题
       this._drawCenterText(ctx, cx, cy)
@@ -202,17 +225,18 @@ export default {
     /**
      * 绘制同心六边形网格 + 辐射线
      */
-    _drawGrid: function(ctx, cx, cy, outerR, angles) {
+    _drawGrid: function(ctx, cx, cy, outerR, angles, n) {
       var levels = [0.25, 0.5, 0.75, 1.0]
+      var gridColor = this.gridColor
 
-      // 同心六边形
-      ctx.strokeStyle = '#E2E8F0'
+      // 同心 N 边形
+      ctx.strokeStyle = gridColor
       ctx.lineWidth = 1
 
       for (var li = 0; li < levels.length; li++) {
         var r = outerR * levels[li]
         ctx.beginPath()
-        for (var ai2 = 0; ai2 < 6; ai2++) {
+        for (var ai2 = 0; ai2 < n; ai2++) {
           var vx_ = cx + r * Math.cos(angles[ai2])
           var vy_ = cy + r * Math.sin(angles[ai2])
           if (ai2 === 0) {
@@ -226,10 +250,10 @@ export default {
       }
 
       // 辐射线(中心到各顶点)
-      ctx.strokeStyle = '#E2E8F0'
+      ctx.strokeStyle = gridColor
       ctx.lineWidth = 1
       ctx.beginPath()
-      for (var ai3 = 0; ai3 < 6; ai3++) {
+      for (var ai3 = 0; ai3 < n; ai3++) {
         ctx.moveTo(cx, cy)
         ctx.lineTo(
           cx + outerR * Math.cos(angles[ai3]),
@@ -242,13 +266,13 @@ export default {
     /**
      * 绘制分数填充多边形
      */
-    _drawScoreArea: function(ctx, cx, cy, outerR, angles) {
+    _drawScoreArea: function(ctx, cx, cy, outerR, angles, n) {
       var scores = this._animScores
-      if (!scores || scores.length < 6) { return }
+      if (!scores || scores.length < n) { return }
 
       // 构建顶点数据
       var points = []
-      for (var i = 0; i < 6; i++) {
+      for (var i = 0; i < n; i++) {
         var score = scores[i] || 0
         var r = outerR * Math.max(0, Math.min(1, score / 100))
         points.push({
@@ -268,16 +292,20 @@ export default {
       }
       ctx.closePath()
 
-      // 径向渐变填充
+      // 径向渐变填充(使用 fillColor)
+      var baseColor = this.fillColor || '#F97316'
+      var r1 = parseInt(baseColor.slice(1, 3), 16)
+      var g1 = parseInt(baseColor.slice(3, 5), 16)
+      var b1 = parseInt(baseColor.slice(5, 7), 16)
       var gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, outerR)
-      gradient.addColorStop(0, 'rgba(249, 115, 22, 0.12)')
-      gradient.addColorStop(0.5, 'rgba(249, 115, 22, 0.20)')
-      gradient.addColorStop(1, 'rgba(249, 115, 22, 0.32)')
+      gradient.addColorStop(0, 'rgba(' + r1 + ',' + g1 + ',' + b1 + ',0.12)')
+      gradient.addColorStop(0.5, 'rgba(' + r1 + ',' + g1 + ',' + b1 + ',0.20)')
+      gradient.addColorStop(1, 'rgba(' + r1 + ',' + g1 + ',' + b1 + ',0.32)')
       ctx.fillStyle = gradient
       ctx.fill()
 
       // 描边
-      ctx.strokeStyle = '#F97316'
+      ctx.strokeStyle = this.fillColor || '#F97316'
       ctx.lineWidth = 2.5
       ctx.stroke()
     },
@@ -285,15 +313,15 @@ export default {
     /**
      * 在各顶点绘制小圆点
      */
-    _drawVertices: function(ctx, cx, cy, outerR, angles) {
+    _drawVertices: function(ctx, cx, cy, outerR, angles, n) {
       var scores = this._animScores
-      if (!scores || scores.length < 6) { return }
+      if (!scores || scores.length < n) { return }
 
-      ctx.fillStyle = '#F97316'
-      ctx.shadowColor = 'rgba(249, 115, 22, 0.3)'
+      ctx.fillStyle = this.fillColor || '#F97316'
+      ctx.shadowColor = this.hexToRgba(this.fillColor || '#F97316', 0.3)
       ctx.shadowBlur = 6
 
-      for (var i = 0; i < 6; i++) {
+      for (var i = 0; i < n; i++) {
         var score = scores[i] || 0
         var r = outerR * Math.max(0, Math.min(1, score / 100))
         var vx = cx + r * Math.cos(angles[i])
@@ -310,16 +338,16 @@ export default {
     /**
      * 在各顶点附近显示分值(百分比)
      */
-    _drawScoreText: function(ctx, cx, cy, outerR, angles) {
+    _drawScoreText: function(ctx, cx, cy, outerR, angles, n) {
       var scores = this._animScores
-      if (!scores || scores.length < 6) { return }
+      if (!scores || scores.length < n) { return }
 
       ctx.font = '11px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif'
-      ctx.fillStyle = '#F97316'
+      ctx.fillStyle = this.fillColor || '#F97316'
       ctx.textAlign = 'center'
       ctx.textBaseline = 'middle'
 
-      for (var i = 0; i < 6; i++) {
+      for (var i = 0; i < n; i++) {
         var score = scores[i] || 0
         // 分数文字放在顶点向外偏移 16px 处
         var vertexR = outerR * Math.max(0, Math.min(1, score / 100))
@@ -335,13 +363,13 @@ export default {
     /**
      * 绘制维度名称标签(在六边形外围)
      */
-    _drawLabels: function(ctx, cx, cy, outerR, angles) {
+    _drawLabels: function(ctx, cx, cy, outerR, angles, n) {
       var dims = this.dimensions
-      if (!dims || dims.length < 6) { return }
+      if (!dims || dims.length < n) { return }
 
       var labelOffset = 36
 
-      for (var i = 0; i < 6; i++) {
+      for (var i = 0; i < n; i++) {
         var dim = dims[i]
         if (!dim) { continue }
 
@@ -390,7 +418,7 @@ export default {
         }
 
         ctx.font = 'bold 13px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif'
-        ctx.fillStyle = '#1E293B'
+        ctx.fillStyle = this.labelColor || '#1E293B'
         ctx.textAlign = align
         ctx.textBaseline = baseline
         ctx.fillText(dim.label || '', lx, ly)
@@ -417,8 +445,10 @@ export default {
 
     _startAnimation: function() {
       var self = this
-      var target = this.scores.slice(0, 6)
-      var start = [0, 0, 0, 0, 0, 0]
+      var n = this.dimCount
+      var target = this.scores.slice(0, n)
+      var start = []
+      for (var si = 0; si < n; si++) { start.push(0) }
       var startTime = Date.now()
       var duration = 600
 
@@ -433,8 +463,8 @@ export default {
         // ease-out cubic
         var eased = 1 - Math.pow(1 - progress, 3)
 
-        for (var i = 0; i < 6; i++) {
-          self._animScores[i] = start[i] + (target[i] - start[i]) * eased
+        for (var i2 = 0; i2 < n; i2++) {
+          self._animScores[i2] = start[i2] + (target[i2] - start[i2]) * eased
         }
 
         self._doRender()
@@ -443,8 +473,8 @@ export default {
           self._animId = self._requestAnim(tick)
         } else {
           // 确保最终为精确值
-          for (var j = 0; j < 6; j++) {
-            self._animScores[j] = target[j]
+          for (var j2 = 0; j2 < n; j2++) {
+            self._animScores[j2] = target[j2]
           }
           self._doRender()
           self._animId = null
@@ -480,6 +510,16 @@ export default {
      * 点击检测
      * =========================== */
 
+    /** 将十六进制颜色转 rgba */
+    hexToRgba: function(hex, alpha) {
+      if (!hex) return 'rgba(0,0,0,' + alpha + ')'
+      var h = hex.replace('#', '')
+      var r = parseInt(h.substring(0, 2), 16)
+      var g = parseInt(h.substring(2, 4), 16)
+      var b = parseInt(h.substring(4, 6), 16)
+      return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'
+    },
+
     onCanvasTap: function(e) {
       if (!this.canvasReady) { return }
 
@@ -507,6 +547,7 @@ export default {
         return
       }
 
+      var n = this.dimCount
       var cx = this.canvasWidth / 2
       var cy = this.canvasHeight / 2
       var outerR = Math.min(this.canvasWidth, this.canvasHeight) * 0.38
@@ -515,7 +556,7 @@ export default {
       var hitRadius = 44
 
       // 检测点击是否落在某个维度标签附近
-      for (var i = 0; i < 6; i++) {
+      for (var i = 0; i < n; i++) {
         var dim = this.dimensions[i]
         if (!dim) { continue }
 

+ 193 - 0
cfc-frontend/components/SkeletonBlock.vue

@@ -0,0 +1,193 @@
+<template>
+  <view class="skeleton-wrapper">
+    <block v-for="(item, index) in computedItems" :key="index">
+      <!-- Banner: 通栏大矩块 -->
+      <view v-if="type === 'banner'" class="skeleton-banner" :style="bannerStyle"></view>
+      <!-- Card: 中等卡片 + 内部小矩块 -->
+      <view v-else-if="type === 'card'" class="skeleton-card" :style="cardStyle">
+        <view class="skeleton-card-inner"></view>
+        <view class="skeleton-card-line"></view>
+        <view class="skeleton-card-line skeleton-card-line--short"></view>
+      </view>
+      <!-- Row: 单行横条 -->
+      <view v-else-if="type === 'row'" class="skeleton-row" :style="rowStyle"></view>
+      <!-- Circle: 圆形 -->
+      <view v-else-if="type === 'circle'" class="skeleton-circle" :style="circleStyle"></view>
+      <!-- Text: 多行文字占位 -->
+      <view v-else class="skeleton-text-group">
+        <view
+          v-for="(row, ri) in textRows"
+          :key="ri"
+          class="skeleton-text-row"
+          :style="{ width: row.width }"
+        ></view>
+      </view>
+    </block>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'SkeletonBlock',
+  props: {
+    type: {
+      type: String,
+      default: 'row'
+    },
+    width: {
+      type: String,
+      default: ''
+    },
+    height: {
+      type: String,
+      default: ''
+    },
+    count: {
+      type: Number,
+      default: 1
+    }
+  },
+  computed: {
+    computedItems() {
+      var arr = []
+      for (var i = 0; i < this.count; i++) {
+        arr.push(i)
+      }
+      return arr
+    },
+    baseShimmer() {
+      // Reuse design system shimmer from uni.scss
+      return 'linear-gradient(90deg, #E2E8F0 25%, #EDF2F7 50%, #E2E8F0 75%)'
+    },
+    bannerStyle() {
+      var style = 'width: 100%; height: 200rpx; border-radius: 16rpx;'
+      if (this.width) {
+        style += 'width: ' + this.width + ';'
+      }
+      if (this.height) {
+        style += 'height: ' + this.height + ';'
+      }
+      return style
+    },
+    cardStyle() {
+      var style = 'width: 100%; height: 160rpx;'
+      if (this.width) {
+        style += 'width: ' + this.width + ';'
+      }
+      if (this.height) {
+        style += 'height: ' + this.height + ';'
+      }
+      return style
+    },
+    rowStyle() {
+      var style = 'width: 100%; height: 32rpx; margin-bottom: 12rpx;'
+      if (this.width) {
+        style += 'width: ' + this.width + ';'
+      }
+      if (this.height) {
+        style += 'height: ' + this.height + ';'
+      }
+      return style
+    },
+    circleStyle() {
+      var style = 'width: 80rpx; height: 80rpx; border-radius: 50%;'
+      if (this.width) {
+        style += 'width: ' + this.width + ';'
+      }
+      if (this.height) {
+        style += 'height: ' + this.height + ';'
+      }
+      return style
+    },
+    textRows() {
+      return [
+        { width: '100%' },
+        { width: '100%' },
+        { width: '88%' },
+        { width: '60%' }
+      ]
+    }
+  }
+}
+</script>
+
+<style scoped>
+.skeleton-wrapper {
+  display: flex;
+  flex-direction: column;
+}
+
+/* ===== Shimmer 基础 ===== */
+.skeleton-banner,
+.skeleton-card,
+.skeleton-row,
+.skeleton-circle,
+.skeleton-card-inner,
+.skeleton-card-line,
+.skeleton-text-row {
+  background: linear-gradient(90deg, #E2E8F0 25%, #F5F9FC 50%, #E2E8F0 75%);
+  background-size: 200% 100%;
+  animation: skeleton-shimmer 1.5s ease-in-out infinite;
+  will-change: transform;
+}
+
+@keyframes skeleton-shimmer {
+  0% {
+    background-position: -200% 0;
+  }
+  100% {
+    background-position: 200% 0;
+  }
+}
+
+/* ===== Banner ===== */
+.skeleton-banner {
+  margin-bottom: 20rpx;
+}
+
+/* ===== Card ===== */
+.skeleton-card {
+  background: none !important;
+  border-radius: 16rpx;
+  padding: 20rpx;
+  margin-bottom: 20rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  border: 1rpx solid #E2E8F0;
+}
+.skeleton-card-inner {
+  width: 60%;
+  height: 48rpx;
+  border-radius: 8rpx;
+}
+.skeleton-card-line {
+  width: 100%;
+  height: 20rpx;
+  border-radius: 6rpx;
+  margin-top: 10rpx;
+}
+.skeleton-card-line--short {
+  width: 40%;
+}
+
+/* ===== Row ===== */
+.skeleton-row {
+  border-radius: 8rpx;
+}
+
+/* ===== Circle ===== */
+.skeleton-circle {
+}
+
+/* ===== Text ===== */
+.skeleton-text-group {
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
+.skeleton-text-row {
+  height: 24rpx;
+  border-radius: 6rpx;
+}
+</style>

+ 1 - 1
cfc-frontend/config.js

@@ -23,7 +23,7 @@ try {
       case 'develop':
         // 开发版 - 本地开发
         API_BASE_URL = 'http://localhost:9082'
-        DANSHOP_BASE_URL = 'http://localhost:8888'
+        DANSHOP_BASE_URL = 'http://localhost:9081'
         break
       case 'trial':
         // 体验版

+ 5 - 0
cfc-frontend/main.js

@@ -1,10 +1,15 @@
 import App from './App'
 import Vue from 'vue'
 import store from './store'
+import { formatPriceWithSymbol, formatPrice, formatRate, formatScore } from '@/utils/format'
 
 Vue.config.productionTip = false
 
 Vue.prototype.$store = store
+Vue.prototype.formatPriceWithSymbol = formatPriceWithSymbol
+Vue.prototype.formatPrice = formatPrice
+Vue.prototype.formatRate = formatRate
+Vue.prototype.formatScore = formatScore
 
 App.mpType = 'app'
 

+ 22 - 2
cfc-frontend/pages.json

@@ -16,7 +16,15 @@
     {
       "path": "pages/index/parent-index",
       "style": {
-        "navigationBarTitleText": "首页"
+        "navigationBarTitleText": "首页",
+        "enablePullDownRefresh": true
+      }
+    },
+    // ===== 品牌开屏页 =====
+    {
+      "path": "pages/splash/splash",
+      "style": {
+        "navigationStyle": "custom"
       }
     },
     // ===== 主包:其他页面 =====
@@ -76,6 +84,18 @@
         "navigationStyle": "custom"
       }
     },
+    {
+      "path": "pages/health/report-upload",
+      "style": {
+        "navigationBarTitleText": "上传健康报告"
+      }
+    },
+    {
+      "path": "pages/health/report-list",
+      "style": {
+        "navigationBarTitleText": "健康报告"
+      }
+    },
     {
       "path": "pages/mind/index",
       "style": {
@@ -169,7 +189,7 @@
         "navigationBarTitleText": "商品详情"
       }
     },
-    // ===== AI 聊天 =====
+        // ===== AI 聊天 =====
     {
       "path": "pages/ai/chat",
       "style": {

+ 4 - 3
cfc-frontend/pages/action/index.vue

@@ -50,14 +50,12 @@
       @moreTasks="goTasks" />
 
     <DimensionActivities
-      v-if="isLoggedIn"
       dimensionCode="action"
       :activities="dimensionActivities"
       @activityClick="goActivityDetail"
       @moreActivities="goMoreActivities" />
 
     <DimensionProducts
-      v-if="isLoggedIn"
       dimensionCode="action"
       :products="dimensionProducts"
       @productClick="goProductDetail"
@@ -150,7 +148,7 @@ export default {
       funcList: [
         { icon: '\u{1F3CB}', label: '任务', needLogin: true, page: '/pages/tasks/tasks' },
         { icon: '\u{1F3C0}', label: '活动', needLogin: false, page: '/pages/discover/index?type=activity' },
-        { icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/index' },
+        { icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/discover/index?type=product' },
         { icon: '\u{1F3EB}', label: '课程', needLogin: false, page: '/pages/discover/index?type=course' }
       ]
     }
@@ -168,6 +166,9 @@ export default {
       this.loadChildren()
       this.loadFamilyMembersVisible()
     }
+    // 游客也能浏览活动和商品
+    this.loadDimensionActivities()
+    this.loadDimensionProducts()
   },
   methods: {
     loadSectionConfig: function() {

+ 3 - 3
cfc-frontend/pages/assessment/apply.vue

@@ -33,7 +33,7 @@
           <image class="guide-avatar" :src="guide.guideAvatar || '/static/default-avatar.png'" />
           <view class="guide-info">
             <text class="guide-name">{{ guide.guideName }}</text>
-            <text class="guide-price">¥{{ guide.price }}</text>
+            <text class="guide-price">{{ formatPriceWithSymbol(guide.price) }}</text>
           </view>
           <view class="guide-check" v-if="selectedGuideId === guide.guideId">✓</view>
         </view>
@@ -106,7 +106,7 @@
       <view class="price-info">
         <view class="price-row">
           <text class="price-label">测评费用</text>
-          <text class="price-value">¥{{ totalPrice }}</text>
+          <text class="price-value">{{ formatPriceWithSymbol(totalPrice) }}</text>
         </view>
         <view class="price-row">
           <text class="price-label">成长规划师</text>
@@ -114,7 +114,7 @@
         </view>
         <view class="price-row" v-if="inviteCodeInfo">
           <text class="price-label">邀请码优惠</text>
-          <text class="price-value discount">-¥{{ discountAmount }}</text>
+          <text class="price-value discount">-{{ formatPriceWithSymbol(discountAmount) }}</text>
         </view>
       </view>
     </view>

+ 4 - 4
cfc-frontend/pages/assessment/order.vue

@@ -31,15 +31,15 @@
       <view class="section-title">费用信息</view>
       <view class="price-row">
         <text class="price-label">测评费用</text>
-        <text class="price-value">¥{{ totalPrice }}</text>
+        <text class="price-value">{{ formatPriceWithSymbol(totalPrice) }}</text>
       </view>
       <view class="price-row" v-if="discountAmount > 0">
         <text class="price-label">优惠金额</text>
-        <text class="price-value discount">-¥{{ discountAmount }}</text>
+        <text class="price-value discount">-{{ formatPriceWithSymbol(discountAmount) }}</text>
       </view>
       <view class="price-row total">
         <text class="price-label">实付金额</text>
-        <text class="price-value highlight">¥{{ actualPrice }}</text>
+        <text class="price-value highlight">{{ formatPriceWithSymbol(actualPrice) }}</text>
       </view>
     </view>
 
@@ -64,7 +64,7 @@
       </view>
     </view>
 
-    <button class="btn-submit" @click="handlePay">确认支付 ¥{{ actualPrice }}</button>
+    <button class="btn-submit" @click="handlePay">确认支付 {{ formatPriceWithSymbol(actualPrice) }}</button>
   </view>
 </template>
 

+ 15 - 15
cfc-frontend/pages/assessment/purchase.vue

@@ -19,7 +19,7 @@
           <image class="guide-avatar" :src="guide.guideAvatar || '/static/default-avatar.png'" />
           <view class="guide-info">
             <text class="guide-name">{{ guide.guideName }}</text>
-            <text class="guide-price">¥{{ guide.price }}</text>
+            <text class="guide-price">{{ formatPriceWithSymbol(guide.price) }}</text>
           </view>
           <view class="guide-check" v-if="selectedGuideId === guide.guideId">✓</view>
         </view>
@@ -32,26 +32,26 @@
       <view class="package-list">
         <view 
           class="package-item" 
-          :class="{ 'selected': selectedPackageId === package.id }"
-          v-for="package in packages" 
-          :key="package.id"
-          @click="selectPackage(package)"
+          :class="{ 'selected': selectedPackageId === pkg.id }"
+          v-for="pkg in packages" 
+          :key="pkg.id"
+          @click="selectPackage(pkg)"
         >
           <view class="package-header">
-            <text class="package-name">{{ package.name }}</text>
+            <text class="package-name">{{ pkg.name }}</text>
             <view class="package-price">
-              <text class="price">¥{{ package.price }}</text>
-              <text class="original-price" v-if="package.originalPrice">¥{{ package.originalPrice }}</text>
+              <text class="price">{{ formatPriceWithSymbol(pkg.price) }}</text>
+              <text class="original-price" v-if="pkg.originalPrice">{{ formatPriceWithSymbol(pkg.originalPrice) }}</text>
             </view>
           </view>
-          <view class="package-desc">{{ package.description }}</view>
+          <view class="package-desc">{{ pkg.description }}</view>
           <view class="package-features">
-            <view class="feature-item" v-for="(feature, index) in package.features" :key="index">
+            <view class="feature-item" v-for="(feature, index) in pkg.features" :key="index">
               <text class="feature-icon">✓</text>
               <text class="feature-text">{{ feature }}</text>
             </view>
           </view>
-          <view class="package-check" v-if="selectedPackageId === package.id">✓</view>
+          <view class="package-check" v-if="selectedPackageId === pkg.id">✓</view>
         </view>
       </view>
     </view>
@@ -62,19 +62,19 @@
       <view class="price-breakdown">
         <view class="price-row">
           <text class="price-label">套餐价格</text>
-          <text class="price-value">¥{{ selectedPackage.price }}</text>
+          <text class="price-value">{{ formatPriceWithSymbol(selectedPackage.price) }}</text>
         </view>
         <view class="price-row" v-if="selectedGuideId">
           <text class="price-label">成长规划师费用</text>
-          <text class="price-value">¥{{ guidePrice }}</text>
+          <text class="price-value">{{ formatPriceWithSymbol(guidePrice) }}</text>
         </view>
         <view class="price-row" v-if="discountAmount > 0">
           <text class="price-label">邀请码优惠</text>
-          <text class="price-value discount">-¥{{ discountAmount }}</text>
+          <text class="price-value discount">-{{ formatPriceWithSymbol(discountAmount) }}</text>
         </view>
         <view class="price-row total">
           <text class="price-label">总计</text>
-          <text class="price-value">¥{{ totalPrice }}</text>
+          <text class="price-value">{{ formatPriceWithSymbol(totalPrice) }}</text>
         </view>
       </view>
     </view>

+ 2 - 2
cfc-frontend/pages/body/checkin.vue

@@ -103,13 +103,13 @@
 </template>
 
 <script>
-const BASE_URL = 'http://localhost:8080'
+import config from '@/config.js'
 
 const healthRequest = function(url, data) {
   var token = uni.getStorageSync('token')
   return new Promise(function(resolve, reject) {
     uni.request({
-      url: BASE_URL + url,
+      url: config.API_BASE_URL + url,
       method: 'POST',
       data: data,
       header: {

+ 2 - 2
cfc-frontend/pages/discover/index.vue

@@ -41,8 +41,8 @@
           <view class="product-info">
             <text class="product-name">{{ item.name }}</text>
             <view class="product-price-row">
-              <text class="product-price">¥{{ item.price }}</text>
-              <text v-if="item.memberPrice" class="product-member">会员¥{{ item.memberPrice }}</text>
+              <text class="product-price">{{ formatPriceWithSymbol(item.price) }}</text>
+              <text v-if="item.memberPrice" class="product-member">会员{{ formatPriceWithSymbol(item.memberPrice) }}</text>
             </view>
           </view>
         </view>

+ 4 - 4
cfc-frontend/pages/discover/product-detail/product-detail.vue

@@ -14,8 +14,8 @@
           <text class="product-name">{{ product.name }}</text>
         </view>
         <view class="price-row">
-          <text class="price">¥{{ product.price }}</text>
-          <text v-if="product.memberPrice" class="member-price">会员价 ¥{{ product.memberPrice }}</text>
+          <text class="price">{{ formatPriceWithSymbol(product.price) }}</text>
+          <text v-if="product.memberPrice" class="member-price">会员价 {{ formatPriceWithSymbol(product.memberPrice) }}</text>
         </view>
         <view class="meta-row">
           <text class="meta-item" v-if="product.productType">{{ typeLabel }}</text>
@@ -40,8 +40,8 @@
 
       <view class="bottom-bar">
         <view class="price-info">
-          <text class="bottom-price">¥{{ product.price }}</text>
-          <text v-if="product.memberPrice" class="bottom-member">会员¥{{ product.memberPrice }}</text>
+          <text class="bottom-price">{{ formatPriceWithSymbol(product.price) }}</text>
+          <text v-if="product.memberPrice" class="bottom-member">会员{{ formatPriceWithSymbol(product.memberPrice) }}</text>
         </view>
         <view class="action-btns">
           <view v-if="danshopProductId" class="action-extra">

+ 1 - 1
cfc-frontend/pages/guide/packages/detail.vue

@@ -8,7 +8,7 @@
       </view>
       <view class="info-row">
         <text>周期: {{ pkg.cycleDays }}天</text>
-        <text>价格: ¥{{ pkg.price }}</text>
+        <text>价格: {{ formatPriceWithSymbol(pkg.price) }}</text>
       </view>
       <view class="info-desc">{{ pkg.description }}</view>
       <view class="share-code" v-if="pkg.shareCode">

+ 1 - 1
cfc-frontend/pages/guide/packages/index.vue

@@ -20,7 +20,7 @@
         </view>
         <view class="package-info">
           <text class="info-item">周期: {{ pkg.cycleDays }}天</text>
-          <text class="info-item">价格: ¥{{ pkg.price }}</text>
+          <text class="info-item">价格: {{ formatPriceWithSymbol(pkg.price) }}</text>
         </view>
         <view class="package-desc">{{ pkg.description || '暂无描述' }}</view>
         <view class="package-actions">

+ 2 - 2
cfc-frontend/pages/guide/packages/share.vue

@@ -8,7 +8,7 @@
         <text class="desc">{{ pkg.description }}</text>
         <view class="meta">
           <text>{{ pkg.cycleDays }}天</text>
-          <text class="price">¥{{ pkg.price }}</text>
+          <text class="price">{{ formatPriceWithSymbol(pkg.price) }}</text>
         </view>
       </view>
     </view>
@@ -108,7 +108,7 @@ export default {
       ctx.fillText(`${this.pkg.cycleDays}天`, 30, 480)
       ctx.setFillStyle('#f44336')
       ctx.setFontSize(36)
-      ctx.fillText(`¥${this.pkg.price}`, 30, 520)
+      ctx.fillText(this.formatPriceWithSymbol(this.pkg.price), 30, 520)
       
       // 分享码
       ctx.setFillStyle('#4CAF50')

+ 115 - 18
cfc-frontend/pages/index/child-index.vue

@@ -28,18 +28,24 @@
   quote="给全家的一站式幸福提案" />
 
 <!-- 五维能量沙盘 -->
-<view class="animate-fade-in animate-stagger-2">
-<wuxing-sandbox mode="live"
-  :scores="{}"
-  :overall-score="0"
-  :members="[]"
+<WuxingSandbox
+  v-if="sandboxData"
+  mode="live"
+  :scores="sandboxScores"
+  :overallScore="sandboxData.overallScore || 0"
+  :members="sandboxData.members || []"
   :dimensions="energyDimensions"
-  :total-energy="totalEnergy"
-  :total-health-index="totalHealthIndex"
-  show-dimension-bar="false"
-  star-height="400rpx"
-  @point-click="goToEnergyDetail" />
-</view>
+  :totalEnergy="totalEnergy"
+  :totalHealthIndex="totalHealthIndex"
+  starHeight="400rpx"
+  showDimensionBar
+  @point-click="onDomainClick" />
+
+<!-- 家庭能量横条 -->
+<FamilyEnergyBar
+  v-if="hasChildren"
+  dimensionCode="child"
+  :sandboxData="sandboxData" />
 
 <!-- 连续打卡徽章 -->
 <view
@@ -259,6 +265,22 @@ B) 积分卡片 — Claymorphism 暖橙黏土风格
         </view>
       </view>
 
+      <!-- ===== 推荐活动 ===== -->
+      <DimensionActivities
+        v-if="hasChildren"
+        dimensionCode="child"
+        :activities="dimensionActivities"
+        @activityClick="goActivityDetail"
+        @moreActivities="goMoreActivities" />
+
+      <!-- ===== 推荐商品 ===== -->
+      <DimensionProducts
+        v-if="hasChildren"
+        dimensionCode="child"
+        :products="dimensionProducts"
+        @productClick="goProductDetail"
+        @moreProducts="goMoreProducts" />
+
       <!-- 底部安全留白 -->
       <view class="bottom-spacer"></view>
     </view>
@@ -266,15 +288,18 @@ B) 积分卡片 — Claymorphism 暖橙黏土风格
 </template>
 
 <script>
-import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs } from '../../utils/api.js'
+import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs, getActivityList, getProductsByDomain, getFamilyEnergySandbox } from '../../utils/api.js'
 import PageBanner from '../../components/PageBanner.vue'
+import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import DimensionActivities from '../../components/DimensionActivities.vue'
+import DimensionProducts from '../../components/DimensionProducts.vue'
 import PlayfulCard from '../../components/PlayfulCard.vue'
 import PlayfulButton from '../../components/PlayfulButton.vue'
 import BaseBadge from '../../components/BaseBadge.vue'
 import BaseEmpty from '../../components/BaseEmpty.vue'
 import BaseLoading from '../../components/BaseLoading.vue'
 import ConfettiCelebration from '../../components/ConfettiCelebration.vue'
-import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 
 /**
  * 等级配置表
@@ -303,13 +328,16 @@ var TASK_ICONS = {
 export default {
   components: {
     PageBanner,
+    FamilyEnergyBar,
+    WuxingSandbox,
+    DimensionActivities,
+    DimensionProducts,
     PlayfulCard,
     PlayfulButton,
     BaseBadge,
     BaseEmpty,
     BaseLoading,
-    ConfettiCelebration,
-    WuxingSandbox
+    ConfettiCelebration
   },
   data() {
     return {
@@ -333,10 +361,24 @@ export default {
       energyDimensions: [],
       totalEnergy: 0,
       totalHealthIndex: 0,
-      energyLogs: []
+      energyLogs: [],
+      sandboxData: null,
+      dimensionActivities: [],
+      dimensionProducts: []
     }
   },
   computed: {
+    // ---------- 能量沙盘 ----------
+    sandboxScores() {
+      if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
+      return {
+        body: this.sandboxData.bodyScore || 0,
+        mind: this.sandboxData.mindScore || 0,
+        wisdom: this.sandboxData.wisdomScore || 0,
+        action: this.sandboxData.actionScore || 0,
+        wealth: this.sandboxData.wealthScore || 0
+      }
+    },
     // ---------- 等级相关 ----------
     currentLevelIndex() {
       const pts = this.totalPoints
@@ -429,7 +471,7 @@ export default {
         const userInfo = uni.getStorageSync('userInfo')
         const role = uni.getStorageSync('currentRole') || 'child'
         if (role !== 'child') {
-          uni.reLaunch({ url: '/pages/index/parent-index' })
+          uni.switchTab({ url: '/pages/index/index' })
           return
         }
         this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : '小朋友'
@@ -488,6 +530,40 @@ export default {
             this.energyLogs = []
           }
         }
+
+        // 加载沙盘数据
+        if (energyChildId) {
+          try {
+            var sandboxRes = await getFamilyEnergySandbox()
+            if (sandboxRes && sandboxRes.data) {
+              this.sandboxData = sandboxRes.data
+            }
+          } catch (e) {
+            console.log('获取沙盘数据失败', e)
+          }
+        }
+
+        // 加载维度活动
+        try {
+          var actRes = await getActivityList({ dimensionCode: 'child', page: 1, size: 3 })
+          if (actRes && actRes.data) {
+            var actList = Array.isArray(actRes.data) ? actRes.data : (actRes.data.records || [])
+            this.dimensionActivities = actList.slice(0, 3)
+          }
+        } catch (e) {
+          console.log('获取活动数据失败', e)
+        }
+
+        // 加载维度商品
+        try {
+          var prodRes = await getProductsByDomain('child', 1, 4)
+          if (prodRes && prodRes.data) {
+            var prodList = Array.isArray(prodRes.data) ? prodRes.data : (prodRes.data.records || [])
+            this.dimensionProducts = prodList.slice(0, 4)
+          }
+        } catch (e) {
+          console.log('获取商品数据失败', e)
+        }
       } catch (e) {
         console.error('加载数据失败', e)
         uni.showToast({ title: '加载失败', icon: 'none' })
@@ -501,6 +577,13 @@ export default {
     },
 
     // ======================== 导航方法 ========================
+    onDomainClick(evt) {
+      var code = evt && evt.code
+      if (!code) return
+      uni.navigateTo({
+        url: '/pages/energy/detail?code=' + code
+      })
+    },
     goToRewards() {
       this.$emit('navigate', { page: 'rewards' })
       uni.switchTab({ url: '/pages/rewards/rewards' })
@@ -539,7 +622,7 @@ export default {
         content: '确定要切换回家长模式吗?',
         success: function(res) {
           if (res.confirm) {
-            uni.switchTab({ url: '/pages/index/parent-index' })
+            uni.switchTab({ url: '/pages/index/index' })
           }
         }
       })
@@ -556,6 +639,20 @@ export default {
       })
     },
 
+    // ======================== 维度导航 ========================
+    goActivityDetail: function(act) {
+      if (act && act.id) uni.navigateTo({ url: '/pages/discover/index?type=activity&id=' + act.id })
+    },
+    goMoreActivities: function() {
+      uni.navigateTo({ url: '/pages/discover/index?type=activity&dimension=child' })
+    },
+    goProductDetail: function(prod) {
+      if (prod && prod.id) uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + prod.id })
+    },
+    goMoreProducts: function() {
+      uni.navigateTo({ url: '/pages/discover/index?type=product&domain=child' })
+    },
+
     // ======================== 任务交互 ========================
     handleTaskClick(task) {
       if (!task || !task.id) return

+ 136 - 22
cfc-frontend/pages/index/index.vue

@@ -1,5 +1,21 @@
 <template>
 <view>
+  <!-- 品牌启动页(冷启动时覆盖全屏) -->
+  <view class="splash-overlay" v-if="showSplash">
+    <view class="splash-content">
+      <view class="splash-logo-mark">
+        <text class="splash-logo-char">浠</text>
+      </view>
+      <text class="splash-brand-name">浠艾福</text>
+      <text class="splash-tagline">给全家的一站式幸福提案</text>
+      <view class="splash-loading">
+        <view class="splash-dot splash-dot--1"></view>
+        <view class="splash-dot splash-dot--2"></view>
+        <view class="splash-dot splash-dot--3"></view>
+      </view>
+    </view>
+  </view>
+
   <!-- 未登录态 — 发现页/引流内容,直接内嵌,不跳转 -->
   <view v-if="!currentRole" class="discover-container">
     <!-- 品牌头部 -->
@@ -37,14 +53,14 @@
           v-for="item in products"
           :key="item.id"
           class="product-card"
-          @click="goDetail(item.id)"
+          @click="handleLogin()"
         >
           <image class="product-cover" :src="item.coverImage || '/static/default-product.png'" mode="aspectFill" />
           <view class="product-info">
             <text class="product-name">{{ item.name }}</text>
             <view class="product-price-row">
-              <text class="product-price">¥{{ item.price }}</text>
-              <text v-if="item.memberPrice" class="product-member">会员¥{{ item.memberPrice }}</text>
+              <!-- 游客不显示价格 -->
+              <text class="product-price-login">登录查看</text>
             </view>
           </view>
         </view>
@@ -94,7 +110,7 @@
   <template v-else>
     <parent-index v-if="currentRole === 'parent'" />
     <child-index v-else-if="currentRole === 'child'" />
-    <teacher-index v-else-if="currentRole === 'teacher'" />
+    <!-- teacher-index 已废弃,教师功能折入 parent-index -->
     <view v-else class="loading-container">
       <text>加载中...</text>
     </view>
@@ -105,7 +121,7 @@
 <script>
 import ParentIndex from './parent-index.vue'
 import ChildIndex from './child-index.vue'
-import TeacherIndex from '../teacher/index.vue'
+// TeacherIndex 已废弃,教师功能折入 parent-index
 import PageBanner from '../../components/PageBanner.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import { acceptParentInvite, productList } from '../../utils/api.js'
@@ -114,13 +130,13 @@ export default {
   components: {
     ParentIndex,
     ChildIndex,
-    TeacherIndex,
     PageBanner,
     WuxingSandbox
   },
   data() {
     return {
       currentRole: '',
+      showSplash: false,
       typeList: [
         { label: '全部', value: '' },
         { label: '活动', value: 'activity' },
@@ -135,33 +151,48 @@ export default {
     }
   },
   onLoad(options) {
+    // 冷启动检查:是否已展示品牌启动页
+    if (!uni.getStorageSync('_splashReady') && !this.showSplash) {
+      this.showSplash = true
+      var self = this
+      setTimeout(function() {
+        self.showSplash = false
+        uni.setStorageSync('_splashReady', true)
+        // splash 结束后执行初始化
+        self._doInit()
+      }, 2500)
+      return
+    }
+    this._doInit(options)
+  },
+  onShow() {
+    if (this.showSplash) {
+      return
+    }
+    this._doInit()
+  },
+  _doInit(options) {
+    options = options || {}
     if (!this.isLoggedIn()) {
       this.currentRole = ''
+      if (!this.showSplash) {
+        this.loadProducts()
+      }
       return
     }
     this.syncRoleAndTabBar()
-    // 检查是否有邀请码参数
     if (options.inviteCode && options.type === 'parent_invite') {
       this.handleParentInvite(options.inviteCode)
     } else {
-      // 检查是否有存储的邀请码(登录后返回)
-      const storedCode = uni.getStorageSync('inviteCode')
-      const storedType = uni.getStorageSync('inviteType')
+      var storedCode = uni.getStorageSync('inviteCode')
+      var storedType = uni.getStorageSync('inviteType')
       if (storedCode && storedType === 'parent_invite') {
         this.handleParentInvite(storedCode)
-        // 清除存储
         uni.removeStorageSync('inviteCode')
         uni.removeStorageSync('inviteType')
       }
     }
   },
-  onShow() {
-    if (!this.isLoggedIn()) {
-      this.currentRole = ''
-      return
-    }
-    this.syncRoleAndTabBar()
-  },
   methods: {
     isLoggedIn() {
       return !!uni.getStorageSync('token')
@@ -193,18 +224,18 @@ export default {
     // ===== 发现页 / 未登录态方法 =====
     loadProducts() {
       this.loading = true
-      const params = { page: 1, size: 10 }
+      var params = { page: 1, size: 10 }
       if (this.currentType) {
         params.productType = this.currentType
       }
-      productList(params).then(res => {
+      productList(params).then(function(res) {
         this.loading = false
         if (res.code === 200 && res.data) {
           this.products = res.data.records || []
         }
-      }).catch(() => {
+      }.bind(this)).catch(function() {
         this.loading = false
-      })
+      }.bind(this))
     },
     onTypeChange(value) {
       this.currentType = value
@@ -358,6 +389,10 @@ export default {
   font-weight: bold;
   margin-right: 10rpx;
 }
+.product-price-login {
+  font-size: 24rpx;
+  color: #999;
+}
 .product-member {
   font-size: 20rpx;
   color: #999;
@@ -436,4 +471,83 @@ export default {
   align-items: center;
   height: 100vh;
 }
+
+/* ===== 品牌启动页覆盖 ===== */
+.splash-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(160deg, #FFF7ED 0%, #F97316 100%);
+  z-index: 9999;
+}
+.splash-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  opacity: 0;
+  animation: splash-fade-in 0.8s ease forwards;
+}
+@keyframes splash-fade-in {
+  from { opacity: 0; transform: translateY(20rpx); }
+  to { opacity: 1; transform: translateY(0); }
+}
+.splash-logo-mark {
+  width: 144rpx;
+  height: 144rpx;
+  border-radius: 36rpx;
+  background: rgba(255,255,255,0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 24rpx;
+}
+.splash-logo-char {
+  font-size: 72rpx;
+  font-weight: 700;
+  color: #FFFFFF;
+  line-height: 1;
+}
+.splash-brand-name {
+  font-size: 56rpx;
+  font-weight: 700;
+  color: #FFFFFF;
+  letter-spacing: 8rpx;
+  margin-bottom: 12rpx;
+  text-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
+}
+.splash-tagline {
+  font-size: 28rpx;
+  color: rgba(255,255,255,0.85);
+  letter-spacing: 4rpx;
+  font-weight: 400;
+}
+.splash-loading {
+  position: absolute;
+  bottom: 120rpx;
+  left: 0;
+  right: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 12rpx;
+}
+.splash-dot {
+  width: 14rpx;
+  height: 14rpx;
+  border-radius: 50%;
+  background: rgba(255,255,255,0.6);
+  animation: splash-dot-bounce 1.4s ease-in-out infinite both;
+}
+.splash-dot--1 { animation-delay: -0.32s; }
+.splash-dot--2 { animation-delay: -0.16s; }
+.splash-dot--3 { animation-delay: 0s; }
+@keyframes splash-dot-bounce {
+  0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
+  40% { transform: scale(1); opacity: 1; }
+}
 </style>

+ 165 - 331
cfc-frontend/pages/index/parent-index.vue

@@ -17,92 +17,23 @@
   :selectedMemberType="selectedMemberType"
   @select="onMemberSelect" />
 
-<!-- ===== b) 孩子卡片 ===== -->
-      <view class="children-row animate-fade-in animate-stagger-2">
-        <template v-if="children.length > 0">
-          <scroll-view
-            class="children-scroll"
-            scroll-x
-            :scroll-with-animation="true"
-            :enhanced="true"
-            :show-scrollbar="false"
-          >
-            <PlayfulCard
-              v-for="child in children"
-              :key="child.id"
-              variant="flat"
-              shadow="sm"
-              :clickable="true"
-              class="child-card"
-              :class="{ 'child-card-single': children.length === 1 }"
-              @click="goToChildDetail(child)"
-            >
-              <view class="child-card-inner">
-                <view class="child-avatar" :class="'child-avatar-color-' + (child.id % 6)">
-                  <text class="child-avatar-text">{{ child.nickname ? child.nickname[0] : '?' }}</text>
-                </view>
-                <view class="child-card-body">
-                  <text class="child-card-name">{{ child.nickname }}</text>
-                  <view class="child-card-stats">
-                    <text class="child-card-points">⭐ {{ child.totalPoints || 0 }}</text>
-                    <text class="child-card-dan" v-if="child.danLevel">DAN{{ child.danLevel }}</text>
-                  </view>
-                  <view class="child-card-badges">
-                    <BaseBadge variant="primary" size="xs" v-if="child.pendingReviewCount > 0">待审 {{ child.pendingReviewCount }}</BaseBadge>
-                    <BaseBadge variant="error" size="xs" v-if="child.pendingWishCount > 0">心愿 {{ child.pendingWishCount }}</BaseBadge>
-                  </view>
-                </view>
-                <view class="child-card-arrow">›</view>
-              </view>
-            </PlayfulCard>
-          </scroll-view>
-          <view class="scroll-hint" v-if="children.length > 1">
-            <text class="scroll-hint-text">← 左右滑动查看更多 →</text>
-          </view>
-        </template>
-        <PlayfulCard
-          v-else
-          variant="flat"
-          :clickable="true"
-          @click="goToAddChild"
-          class="empty-children-card"
-          padding="28rpx"
-        >
-          <view class="empty-children-inner">
-            <view class="empty-children-icon">👶</view>
-            <view class="empty-children-text">
-              <text class="empty-children-title">还没有添加孩子</text>
-              <text class="empty-children-hint">点击添加孩子,开始成长之旅</text>
-            </view>
-            <view class="empty-children-arrow">›</view>
-          </view>
-        </PlayfulCard>
-      </view>
-
-      <!-- 五行能量沙盘(浠艾福 — 全家庭版) -->
-      <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(浠艾福俱乐部) -->
-      <view class="membership-wrapper animate-fade-in animate-stagger-4">
-        <view class="membership-banner" @click="goToMembership">
-          <view class="banner-content">
-            <text class="banner-icon">👑</text>
-            <view class="banner-text">
-              <text class="banner-title">{{ membershipText }}</text>
-              <text class="banner-subtitle">点击查看会员权益</text>
-            </view>
-            <text class="banner-arrow">›</text>
-          </view>
-        </view>
+      <!-- 能量沙盘 -->
+      <WuxingSandbox
+        v-if="sandboxData"
+        mode="live"
+        :scores="sandboxScores"
+        :overallScore="sandboxData.overallScore || 0"
+        :members="sandboxData.members || []"
+        :dimensions="energyDimensions"
+        :totalEnergy="totalEnergy"
+        :totalHealthIndex="totalHealthIndex"
+        starHeight="400rpx"
+        showDimensionBar
+        @point-click="onDomainClick" />
+
+      <!-- 能量数据加载失败兜底 -->
+      <view class="error-state" v-if="errorEnergy && !sandboxData" @click="loadData">
+        <text class="error-state-text">家庭能量数据加载失败,点击重试</text>
       </view>
 
       <!-- ===== c) 成长数据 ===== -->
@@ -169,7 +100,7 @@
       </view>
 
       <!-- ===== AI 家庭助手入口 ===== -->
-      <view class="ai-entry-section animate-fade-in animate-stagger-5">
+      <view class="ai-entry-section animate-fade-in animate-stagger-6">
         <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToAIChat" class="ai-entry-card" padding="24rpx">
           <view class="ai-entry">
             <view class="ai-entry-icon">🤖</view>
@@ -183,7 +114,7 @@
       </view>
 
       <!-- ===== d) 今日重点 ===== -->
-      <view class="today-section animate-fade-in animate-stagger-6">
+      <view class="today-section animate-fade-in animate-stagger-7">
         <view class="section-title">今日重点</view>
         <PlayfulCard variant="flat" class="today-card">
           <template slot="header">
@@ -207,7 +138,7 @@
       </view>
 
       <!-- ===== e) 成长里程碑 ===== -->
-      <view class="milestone-section animate-fade-in animate-stagger-7">
+      <view class="milestone-section animate-fade-in animate-stagger-8">
         <view class="section-title">成长里程碑</view>
         <PlayfulCard variant="flat" class="milestone-card">
           <view class="milestone-item" v-if="selectedChild && selectedChild.streakDays >= 7">
@@ -233,7 +164,7 @@
       </view>
 
       <!-- ===== f) 待处理事项 ===== -->
-      <view class="pending-section animate-fade-in animate-stagger-8">
+      <view class="pending-section animate-fade-in animate-stagger-9">
         <view class="section-title">📋 待处理事项</view>
         <view class="pending-grid">
           <view class="pending-card" @click="goToReview">
@@ -275,25 +206,30 @@
         </view>
       </view>
 
-      <!-- ===== g) 成长建议 ===== -->
-      <view class="advice-section animate-fade-in animate-stagger-9" v-if="growthAdvice.length > 0">
-        <view class="section-title">💡 成长建议</view>
-        <PlayfulCard variant="flat" class="advice-card" v-for="advice in growthAdvice" :key="advice.id" padding="0">
-          <view class="advice-content">
-            <view class="advice-icon-wrap">
-              <text class="advice-icon">💡</text>
-            </view>
-            <view class="advice-text-wrap">
-              <text class="advice-title">{{ advice.title }}</text>
-              <text class="advice-desc">{{ advice.summary || advice.description }}</text>
-            </view>
-            <text class="advice-arrow">›</text>
-          </view>
-        </PlayfulCard>
+      
+
+      <!-- ===== 推荐活动 ===== -->
+      <DimensionActivities
+        dimensionCode="all"
+        :activities="recommendedActivities"
+        @activityClick="goActivityDetail"
+        @moreActivities="goMoreActivities" />
+      <view class="error-state" v-if="errorActivities">
+        <text class="error-state-text">活动推荐加载失败</text>
+      </view>
+
+      <!-- ===== 推荐商品 ===== -->
+      <DimensionProducts
+        dimensionCode="all"
+        :products="recommendedProducts"
+        @productClick="goProductDetail" />
+      <view class="error-state" v-if="errorProducts">
+        <text class="error-state-text">商品推荐加载失败</text>
       </view>
+        @moreProducts="goMoreProducts" />
 
       <!-- ===== h) 待审核任务 ===== -->
-      <view class="review-section animate-fade-in animate-stagger-10" v-if="pendingReviewTasks.length > 0">
+      <view class="review-section animate-fade-in animate-stagger-11" v-if="pendingReviewTasks.length > 0">
         <view class="section-title">待审核任务</view>
         <PlayfulCard
           variant="flat"
@@ -317,7 +253,7 @@
       </view>
 
       <!-- ===== g) 快捷操作 ===== -->
-      <view class="quick-section animate-fade-in animate-stagger-9">
+      <view class="quick-section animate-fade-in animate-stagger-10">
         <view class="section-title">快捷操作</view>
         <PlayfulCard variant="flat" class="quick-card">
           <view class="quick-actions">
@@ -359,18 +295,20 @@
 </template>
 
 <script>
-import { getFamilyMembers, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergyOverview, getVisibleFamilyMembers } from '../../utils/api.js'
+import { getFamilyMembers, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergyOverview, getVisibleFamilyMembers, getActivityList, getProductsByDomain } from '../../utils/api.js'
 import PageBanner from '../../components/PageBanner.vue'
-import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import DimensionActivities from '../../components/DimensionActivities.vue'
+import DimensionProducts from '../../components/DimensionProducts.vue'
 import PlayfulCard from '../../components/PlayfulCard.vue'
 import PlayfulButton from '../../components/PlayfulButton.vue'
 import BaseBadge from '../../components/BaseBadge.vue'
 import BaseEmpty from '../../components/BaseEmpty.vue'
 import BaseLoading from '../../components/BaseLoading.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 
 export default {
-  components: { PageBanner, WuxingSandbox, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading, FamilyMemberStrip },
+  components: { PageBanner, DimensionActivities, DimensionProducts, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading, FamilyMemberStrip, WuxingSandbox },
   data() {
     return {
       nickname: '',
@@ -383,12 +321,11 @@ export default {
       pendingTasks: [],
       hotActivityCount: 0,
       hasNewReport: false,
-      growthAdvice: [],
       myCompletionRate: 0,
       familyAvgCompletionRate: 0,
       myPoints: 0,
       familyTotalPoints: 0,
-      membershipText: '开通家庭会员 畅享全部权益',
+      // membershipText removed (no real function)
       loading: true,
       currentChildId: '',
       _initialLoadDone: false,
@@ -399,7 +336,15 @@ export default {
       energyData: null,
       energyDimensions: [],
       totalEnergy: 0,
-      totalHealthIndex: 0
+      totalHealthIndex: 0,
+      recommendedActivities: [],
+      recommendedProducts: [],
+      hasFamilyData: false,
+      sandboxData: null,
+      errorEnergy: false,
+      errorTasks: false,
+      errorActivities: false,
+      errorProducts: false
     }
   },
   computed: {
@@ -414,6 +359,16 @@ export default {
     energyMembers() {
       return this.energyData ? this.energyData.members : []
     },
+    sandboxScores() {
+      if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
+      return {
+        body: this.sandboxData.bodyScore || 0,
+        mind: this.sandboxData.mindScore || 0,
+        wisdom: this.sandboxData.wisdomScore || 0,
+        action: this.sandboxData.actionScore || 0,
+        wealth: this.sandboxData.wealthScore || 0
+      }
+    },
     greetingText() {
       const hour = new Date().getHours()
       if (hour < 6) return '夜深了'
@@ -454,11 +409,16 @@ export default {
       this.loadData()
     }
   },
+  onPullDownRefresh() {
+    this.loadData().finally(() => {
+      uni.stopPullDownRefresh()
+    })
+  },
   methods: {
     async loadData() {
       this.loading = true
       try {
-        const userInfo = uni.getStorageSync('userInfo')
+        var userInfo = uni.getStorageSync('userInfo')
         this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : ''
 
         // 优先使用仪表盘接口(包含孩子列表 + 统计数据)
@@ -475,6 +435,7 @@ export default {
           if (filteredChildren.length > 0) {
             this.children = filteredChildren
             this.selectedChild = filteredChildren[0]
+            this.currentChildId = filteredChildren[0].id
             this.familyAvgCompletionRate = dashData.avgCompletionRate || 0
             // 家长自身数据(积分 + 任务完成率)
             this.myPoints = dashData.parentPoints || 0
@@ -500,6 +461,7 @@ export default {
             this.familyMembers = [].concat(parents, children)
             if (children.length > 0) {
               this.selectedChild = children[0]
+              this.currentChildId = children[0].id
             }
             // 保存 familyId 用于后续请求
             if (data.familyId) {
@@ -510,11 +472,16 @@ export default {
           }
 
           if (this.children.length === 0) {
-            const childrenRes = await getChildren()
-            const rawChildren = childrenRes.data || []
-            this.children = rawChildren
-            if (this.children.length > 0 && !this.selectedChild) {
-              this.selectedChild = this.children[0]
+            try {
+              const childrenRes = await getChildren()
+              const rawChildren = childrenRes.data || []
+              this.children = rawChildren
+              if (this.children.length > 0 && !this.selectedChild) {
+                this.selectedChild = this.children[0]
+                this.currentChildId = this.children[0].id
+              }
+            } catch (e) {
+              console.log('获取孩子列表失败', e)
             }
           }
         }
@@ -575,20 +542,24 @@ export default {
             } catch (e) {
               console.log('获取个人完成率失败', e)
             }
-            this.myPoints = targetChild.totalPoints || 0
+            userInfo = uni.getStorageSync('userInfo')
+            this.myPoints = (userInfo && userInfo.totalPoints) || 0
             this.familyAvgCompletionRate = this.myCompletionRate
           }
         }
 
-        // 待完成任务 - 使用真实统计中的pendingTasks
+        // 待完成任务 - 优先使用后端返回的真实任务列表
         this.pendingTasks = []
         if (this.children.length > 0) {
           const targetChild = this.selectedChild || this.children[0]
           try {
             const r2 = await getChildCompletionStats({ childId: targetChild.id })
-            if (r2 && r2.data && r2.data.pendingTasks > 0) {
+            if (r2 && r2.data && r2.data.pendingTaskList && r2.data.pendingTaskList.length > 0) {
+              this.pendingTasks = r2.data.pendingTaskList.slice(0, 3)
+            } else if (r2 && r2.data && r2.data.pendingTasks > 0) {
+              // 降级:只有数量没有列表时使用占位
               for (let k = 0; k < Math.min(r2.data.pendingTasks, 3); k++) {
-                this.pendingTasks.push({ id: 100 + k, title: '待完成任务 ' + (k + 1) })
+                this.pendingTasks.push({ id: 'pending-' + k, title: '待完成任务' })
               }
             }
             } catch (e) {
@@ -600,11 +571,19 @@ export default {
         try {
           const energyRes = await getFamilyEnergySandbox()
           this.energyData = energyRes.data || null
+          this.sandboxData = energyRes.data || null
+          this.hasFamilyData = !!(energyRes.data && energyRes.data.members && energyRes.data.members.length > 0)
         } catch (e) {
           console.log('获取能量沙盘数据失败', e)
           this.energyData = null
+          this.errorEnergy = true
         }
 
+        // 加载推荐活动
+        this.loadRecommendedActivities()
+        // 加载推荐商品
+        this.loadRecommendedProducts()
+
         // 加载家庭成员可见列表
         this.loadFamilyMembersVisible()
 
@@ -674,31 +653,64 @@ export default {
         uni.navigateTo({ url: '/pages/stats/completion-rate?familyView=1' })
       }
     },
+    onDomainClick(evt) {
+      this.goToDomain(evt.code)
+    },
     goToDomain(code) {
-      // 跳转到能量详情页(T10 创建)
+      // 跳转到能量详情页
       var childId = this.currentChildId
       if (!childId || !code) return
       uni.navigateTo({
         url: '/pages/energy/detail?childId=' + childId + '&code=' + code
       })
     },
-    goToMembership() {
-      uni.showToast({ title: '即将上线', icon: 'none' })
-    },
-    goToChildDetail(child) {
-      uni.navigateTo({ url: '/pages/parent/child-detail?childId=' + child.id + '&childName=' + encodeURIComponent(child.nickname || '') })
-    },
     addTask() { uni.navigateTo({ url: '/pages/tasks/create-task' }) },
     goToReview() { uni.navigateTo({ url: '/pages/tasks/review' }) },
     goToWishApprove() { uni.navigateTo({ url: '/pages/wishes/approve' }) },
     goToAssessmentReport() { uni.navigateTo({ url: '/pages/assessment/report' }) },
     goToActivities() { uni.switchTab({ url: '/pages/discover/index' }) },
     goToRewards() { uni.switchTab({ url: '/pages/rewards/rewards' }) },
+    goActivityDetail(act) {
+      if (act && act.id) {
+        uni.navigateTo({ url: '/pages/discover/activity-detail/activity-detail?id=' + act.id })
+      }
+    },
+    goMoreActivities() {
+      uni.switchTab({ url: '/pages/discover/index' })
+    },
+    goProductDetail(prod) {
+      if (prod && prod.id) {
+        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + prod.id })
+      }
+    },
+    goMoreProducts() {
+      uni.switchTab({ url: '/pages/discover/index' })
+    },
+    loadRecommendedActivities() {
+      var self = this
+      getActivityList({ page: 1, size: 6 }).then(function(res) {
+        if (res && res.data) {
+          self.recommendedActivities = res.data.records || []
+        }
+      }).catch(function() {
+        self.errorActivities = true
+      })
+    },
+    loadRecommendedProducts() {
+      var self = this
+      getProductsByDomain('all', 1, 6).then(function(res) {
+        if (res && res.data) {
+          self.recommendedProducts = res.data.records || []
+        }
+      }).catch(function() {
+        self.errorProducts = true
+      })
+    },
     goToAIChat() { uni.navigateTo({ url: '/pages/ai/chat' }) },
+
     goToTaskList() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
     manageChildren() { uni.navigateTo({ url: '/pages/profile/children' }) },
-    inviteFriend() { uni.navigateTo({ url: '/pages/parent/invite/index' }) },
-    goToAddChild() { uni.navigateTo({ url: '/pages/profile/create-child' }) }
+    inviteFriend() { uni.navigateTo({ url: '/pages/parent/invite/index' }) }
   }
 }
 </script>
@@ -724,203 +736,6 @@ export default {
   margin-bottom: var(--space-md);
 }
 
-/* ========================================
-b) 孩子卡片
-======================================== */
-.children-row {
-  margin-bottom: 48rpx;
-}
-
-.children-scroll {
-  display: flex;
-  flex-direction: row;
-  white-space: nowrap;
-  overflow: hidden;
-}
-
-.child-card {
-  display: inline-flex !important;
-  margin-right: 16rpx;
-  min-width: 320rpx;
-  width: auto;
-}
-
-.child-card-single {
-  width: 100%;
-  margin-right: 0;
-}
-
-.child-card-inner {
-  display: flex;
-  align-items: center;
-  width: 100%;
-}
-
-.child-avatar {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: var(--radius-full, 50%);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-}
-
-.child-avatar-color-0 { background: linear-gradient(135deg, var(--color-primary, #5B9BD5), var(--color-primary-dark, #3A7CC4)); }
-.child-avatar-color-1 { background: linear-gradient(135deg, var(--color-accent, #0EA5E9), #6366F1); }
-.child-avatar-color-2 { background: linear-gradient(135deg, #10B981, #059669); }
-.child-avatar-color-3 { background: linear-gradient(135deg, #EC4899, #E11D48); }
-.child-avatar-color-4 { background: linear-gradient(135deg, #84CC16, #65A30D); }
-.child-avatar-color-5 { background: linear-gradient(135deg, #8B5CF6, #6D28D9); }
-
-.child-avatar-text {
-  color: #FFFFFF;
-  font-size: 36rpx;
-  font-weight: 600;
-}
-
-.child-card-body {
-  flex: 1;
-  margin-left: 16rpx;
-  min-width: 0;
-}
-
-.child-card-name {
-  font-size: 30rpx;
-  font-weight: 600;
-  color: var(--text, #1E293B);
-  display: block;
-  line-height: 1.3;
-}
-
-.child-card-stats {
-  display: flex;
-  align-items: center;
-  margin-top: 6rpx;
-  gap: 16rpx;
-}
-
-.child-card-points {
-  font-size: 24rpx;
-  color: var(--color-primary, #5B9BD5);
-  font-weight: 600;
-}
-
-.child-card-dan {
-  font-size: 22rpx;
-  color: var(--color-accent, #0EA5E9);
-  font-weight: 500;
-}
-
-.child-card-badges {
-  display: flex;
-  gap: 8rpx;
-  margin-top: 8rpx;
-  flex-wrap: wrap;
-}
-
-.child-card-arrow {
-  font-size: 36rpx;
-  color: var(--muted, #94A3B8);
-  margin-left: 8rpx;
-  flex-shrink: 0;
-}
-
-.scroll-hint {
-  text-align: center;
-  margin-top: 12rpx;
-}
-
-.scroll-hint-text {
-  font-size: 22rpx;
-  color: var(--muted, #94A3B8);
-}
-
-/* Empty children */
-.empty-children-card {
-  margin-top: 24rpx;
-}
-
-.empty-children-inner {
-  display: flex;
-  align-items: center;
-}
-
-.empty-children-icon {
-  font-size: 56rpx;
-  margin-right: 16rpx;
-}
-
-.empty-children-text {
-  flex: 1;
-}
-
-.empty-children-title {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: var(--text, #1E293B);
-  display: block;
-}
-
-.empty-children-hint {
-  font-size: 24rpx;
-  color: var(--muted, #94A3B8);
-  margin-top: 6rpx;
-  display: block;
-}
-
-.empty-children-arrow {
-  font-size: 36rpx;
-  color: var(--muted, #94A3B8);
-}
-
-/* ========================================
-   WuxingSandbox & Membership Wrappers
-   ======================================== */
-.membership-wrapper {
-  margin-bottom: 48rpx;
-}
-
-/* 浠艾福俱乐部 - 会员Banner */
-.membership-banner {
-  padding: 30rpx 32rpx;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  border-radius: var(--radius-md, 20rpx);
-  box-shadow: var(--shadow-md);
-}
-
-.banner-content {
-  display: flex;
-  align-items: center;
-}
-
-.banner-icon {
-  font-size: 60rpx;
-  margin-right: 20rpx;
-}
-
-.banner-text {
-  flex: 1;
-}
-
-.banner-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #8B4513;
-  display: block;
-}
-
-.banner-subtitle {
-  font-size: 24rpx;
-  color: #A0522D;
-  margin-top: 6rpx;
-  display: block;
-}
-
-.banner-arrow {
-  font-size: 40rpx;
-  color: #8B4513;
-}
 
 /* ========================================
    c) 成长数据
@@ -1216,8 +1031,8 @@ b) 孩子卡片
    ======================================== */
 .pending-section { margin-bottom: 48rpx; }
 .pending-grid {
-  display: grid;
-  grid-template-columns: repeat(2, 1fr);
+  display: flex;
+  flex-wrap: wrap;
   gap: 16rpx;
 }
 .pending-card {
@@ -1227,6 +1042,8 @@ b) 孩子卡片
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
   display: flex;
   align-items: center;
+  width: calc(50% - 8rpx);
+  box-sizing: border-box;
 }
 .pending-card:active { opacity: 0.8; }
 .pending-icon {
@@ -1368,4 +1185,21 @@ b) 孩子卡片
   font-weight: 500;
   text-align: center;
 }
+
+/* ===== Error State ===== */
+.error-state {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 32rpx 24rpx;
+  margin-bottom: 24rpx;
+  background: #FFF;
+  border-radius: 16rpx;
+  border: 1rpx dashed #E2E8F0;
+}
+.error-state-text {
+  font-size: 26rpx;
+  color: #94A3B8;
+  text-align: center;
+}
 </style>

+ 2 - 2
cfc-frontend/pages/membership/index.vue

@@ -49,7 +49,7 @@
     <!-- 开通CTA -->
     <view class="cta-section" v-if="!membership || !membership.isActive">
       <text class="cta-title">开通家庭会员,享受全部特权</text>
-      <text class="cta-price">¥365/年</text>
+      <text class="cta-price">{{ formatPriceWithSymbol(36500) }}/年</text>
       <button class="cta-btn" @click="goToUpgrade">立即开通</button>
     </view>
 
@@ -60,7 +60,7 @@
         <view class="benefit-item" v-for="level in levels" :key="level.levelCode">
           <view class="benefit-header">
             <text class="level-name">{{ level.levelName }}</text>
-            <text class="level-price" v-if="level.priceMonthly > 0">¥{{ level.priceMonthly }}/月</text>
+            <text class="level-price" v-if="level.priceMonthly > 0">{{ formatPriceWithSymbol(level.priceMonthly) }}/月</text>
             <text class="level-price free" v-else>免费</text>
           </view>
           <view class="benefit-features">

+ 3 - 3
cfc-frontend/pages/membership/upgrade.vue

@@ -83,7 +83,7 @@
           <text class="record-level">{{ record.levelName }}</text>
           <text class="record-time">{{ formatDate(record.createdAt) }}</text>
         </view>
-        <text class="record-amount">¥{{ record.amount || '0.00' }}</text>
+        <text class="record-amount">{{ formatPriceWithSymbol(record.amount) }}</text>
       </view>
     </view>
   </scroll-view>
@@ -147,7 +147,7 @@ export default {
     handleUpgrade() {
       uni.showModal({
         title: '确认开通',
-        content: '确认开通家庭会员(¥365/年)?',
+        content: '确认开通家庭会员(¥365.00/年)?',
         success: async (res) => {
           if (!res.confirm) return
           try {
@@ -163,7 +163,7 @@ export default {
     handleRenew() {
       uni.showModal({
         title: '确认续费',
-        content: '确认续费家庭会员(¥365/年)?',
+        content: '确认续费家庭会员(¥365.00/年)?',
         success: async (res) => {
           if (!res.confirm) return
           try {

+ 61 - 3
cfc-frontend/pages/mind/index.vue

@@ -28,7 +28,8 @@
     <FamilyEnergyBar
       v-if="isLoggedIn"
       dimensionCode="mind"
-      :sandboxData="sandboxData" />
+      :sandboxData="sandboxData"
+      :dualDimension="dualDimension" />
 
     <!-- 登录后:用户快捷入口 -->
     <UserQuickEntry
@@ -90,6 +91,20 @@
       <view class="placeholder-btn" @click="goLogin" v-else>立即登录</view>
     </view>
 
+    <!-- ===== 大五人格雷达图(从EMI数据中提取) ===== -->
+    <view class="section personality-section" v-if="emiData">
+      <view class="section-header">
+        <text class="section-title">大五人格</text>
+      </view>
+      <RadarChart
+        :dimensions="personalityDimensions"
+        :scores="personalityScores"
+        :childName="currentChildName || '孩子'"
+        fillColor="#8B5CF6"
+        gridColor="#EDE9FE"
+        labelColor="#6D28D9" />
+    </view>
+
     <!-- 每日心理 -->
     <view class="section" v-if="sectionVisible('daily_tip')">
       <view class="section-header">
@@ -258,14 +273,12 @@
       @moreTasks="goTasks" />
 
     <DimensionActivities
-      v-if="isLoggedIn"
       dimensionCode="mind"
       :activities="dimensionActivities"
       @activityClick="goActivityDetail"
       @moreActivities="goMoreActivities" />
 
     <DimensionProducts
-      v-if="isLoggedIn"
       dimensionCode="mind"
       :products="dimensionProducts"
       @productClick="goProductDetail"
@@ -316,6 +329,13 @@ export default {
       // 心理 Tab 数据
       emiData: null,
       emiLoading: true,
+      personalityDimensions: [
+        { key: 'opennessScore', label: '开放性' },
+        { key: 'conscientiousnessScore', label: '尽责性' },
+        { key: 'extraversionScore', label: '外向性' },
+        { key: 'agreeablenessScore', label: '宜人性' },
+        { key: 'neuroticismScore', label: '神经质' }
+      ],
       dailyTip: {
         title: '成长型思维',
         content: '拥有成长型思维的孩子相信自己的能力可以通过努力提升,他们更愿意接受挑战,从失败中学习。作为家长,多鼓励孩子的努力而非天赋,可以帮助孩子建立成长型思维。',
@@ -359,6 +379,7 @@ export default {
       userName: '',
       children: [],
       sandboxData: null,
+      dualDimension: null,
       dimensionTasks: [],
       dimensionActivities: [],
       dimensionProducts: []
@@ -369,6 +390,16 @@ export default {
       var stats = this.readingStats
       if (!stats || !stats.totalDurationSeconds) return 0
       return Math.floor((stats.totalDurationSeconds || 0) / 60)
+    },
+    personalityScores: function() {
+      if (!this.emiData) return []
+      return [
+        this.emiData.opennessScore || 0,
+        this.emiData.conscientiousnessScore || 0,
+        this.emiData.extraversionScore || 0,
+        this.emiData.agreeablenessScore || 0,
+        this.emiData.neuroticismScore || 0
+      ]
     }
   },
   onShow() {
@@ -390,7 +421,11 @@ export default {
       this.loadAssessmentData()
       this.loadReadingStats()
       this.loadFamilyCognitiveData()
+      this.loadEnergyData()
     }
+    // 游客也能浏览活动和商品
+    this.loadDimensionActivities()
+    this.loadDimensionProducts()
   },
   methods: {
     // ===== 区块可见性 =====
@@ -652,6 +687,29 @@ export default {
         }
       }).catch(function() {})
     },
+    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]
+            }
+            self.dualDimension = {
+              primary: mindDim || { code: 'mind', score: 0, label: '心理', color: '#8B5CF6' },
+              secondary: wisdomDim || { code: 'wisdom', score: 0, label: '认知', color: '#3B82F6' }
+            }
+          }
+        }
+      }).catch(function(e) {
+        console.log('获取能量概览失败', e)
+      })
+    },
     loadDimensionTasks: function() {
       var self = this
       if (!this.currentChildId) return

+ 1 - 1
cfc-frontend/pages/parent/market/detail.vue

@@ -9,7 +9,7 @@
           <text>{{ pkg.cycleDays }}天</text>
           <text>{{ pkg.category }}</text>
         </view>
-        <text class="price">¥{{ pkg.price }}</text>
+        <text class="price">{{ formatPriceWithSymbol(pkg.price) }}</text>
       </view>
     </view>
 

+ 1 - 1
cfc-frontend/pages/parent/market/index.vue

@@ -36,7 +36,7 @@
           <view class="package-meta">
             <text class="meta-item">{{ pkg.cycleDays }}天</text>
             <text class="meta-item">{{ pkg.category }}</text>
-            <text class="price">¥{{ pkg.price }}</text>
+            <text class="price">{{ formatPriceWithSymbol(pkg.price) }}</text>
           </view>
         </view>
       </view>

+ 411 - 32
cfc-frontend/pages/profile/family-members.vue

@@ -1,37 +1,116 @@
 <template>
   <view class="container">
-    <view class="family-info" v-if="familyName">
+    <!-- 家庭信息头部 -->
+    <view class="family-header" v-if="familyName">
+      <view class="family-header-icon">🏠</view>
       <text class="family-name">{{ familyName }}</text>
-      <text class="invite-code" v-if="inviteCode">邀请码: {{ inviteCode }}</text>
+      <text class="family-member-count">{{ visibleParents.length + children.length }} 位成员</text>
     </view>
 
-    <view class="section-title">家长 ({{ parents.length }})</view>
+    <!-- 操作按钮组 -->
+    <view class="action-bar">
+      <view class="action-btn add-btn" @click="showAddChild = true">
+        <text class="action-icon">+</text>
+        <text class="action-text">添加孩子</text>
+      </view>
+      <view class="action-btn invite-btn" @click="onInvite">
+        <text class="action-icon">📨</text>
+        <text class="action-text">邀请成员</text>
+      </view>
+    </view>
+
+    <!-- 家长列表(不含当前用户) -->
+    <view class="section-title">家长 ({{ visibleParents.length }})</view>
     <view class="member-list">
-      <view class="member-item" v-for="(p, idx) in parents" :key="idx">
-        <view class="member-avatar">👤</view>
+      <view class="member-item" v-for="p in visibleParents" :key="p.id">
+        <image class="member-avatar" :src="p.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
         <view class="member-detail">
           <text class="member-name">{{ p.nickname || '未设置' }}</text>
-          <text class="member-role">{{ p.familyRole || '家长' }}</text>
-          <text class="member-admin" v-if="p.id === creatorId">[管理员]</text>
+          <view class="member-tags">
+            <text class="member-role-tag">家长</text>
+            <text class="member-admin-tag" v-if="p.id === creatorId">管理员</text>
+          </view>
         </view>
         <button class="btn-kick" v-if="isAdmin && p.id !== userId" @click="confirmKick(p)">踢出</button>
       </view>
+      <view v-if="visibleParents.length === 0" class="empty-row">
+        <text class="empty-text">暂无其他家长成员</text>
+      </view>
     </view>
 
-    <view class="section-title">孩子 ({{ children.length }})</view>
+    <!-- 孩子列表 -->
+    <view class="section-title">
+      孩子 ({{ children.length }})
+      <text class="section-add" @click="showAddChild = true" v-if="isAdmin">+添加</text>
+    </view>
     <view class="member-list">
-      <view class="member-item" v-for="(c, idx) in children" :key="idx">
-        <view class="member-avatar">👶</view>
+      <view class="member-item" v-for="c in children" :key="c.id">
+        <view class="member-avatar child-avatar">👶</view>
         <view class="member-detail">
           <text class="member-name">{{ c.nickname || c.childName || '未设置' }}</text>
+          <text class="member-extra" v-if="c.age"> {{ c.age }}岁</text>
+          <text class="member-extra" v-if="c.gender"> · {{ c.gender === 'male' ? '男' : '女' }}</text>
+        </view>
+      </view>
+      <view v-if="children.length === 0" class="empty-row">
+        <text class="empty-text">暂无孩子</text>
+      </view>
+    </view>
+
+    <!-- 添加孩子弹窗 -->
+    <view class="modal-mask" v-if="showAddChild" @click="showAddChild = false">
+      <view class="modal-content" @click.stop>
+        <text class="modal-title">添加孩子</text>
+        <input class="modal-input" v-model="newChildName" placeholder="请输入孩子昵称" maxlength="20" />
+        <view class="modal-btns">
+          <button class="modal-btn cancel" @click="showAddChild = false">取消</button>
+          <button class="modal-btn confirm" :disabled="!newChildName.trim()" @click="onAddChild">确定</button>
         </view>
       </view>
     </view>
+
+    <!-- 邀请卡片弹窗 -->
+    <view class="modal-mask" v-if="showInviteCard" @click="showInviteCard = false">
+      <view class="invite-card" @click.stop>
+        <text class="invite-card-title">邀请家庭成员</text>
+        <text class="invite-card-family">{{ familyName }}</text>
+
+        <!-- 二维码 -->
+        <view class="invite-qr-wrap" v-if="inviteQrCode">
+          <image class="invite-qr" :src="inviteQrCode" mode="widthFix" />
+        </view>
+        <view class="invite-qr-wrap loading-qr" v-else>
+          <text class="loading-text">生成二维码中...</text>
+        </view>
+
+        <!-- 邀请码 -->
+        <view class="invite-code-section">
+          <text class="invite-code-label">邀请码</text>
+          <view class="invite-code-row">
+            <text class="invite-code">{{ inviteCode }}</text>
+            <button class="copy-btn" @click="onCopyCode">复制</button>
+          </view>
+        </view>
+
+        <text class="invite-tip">让好友在「我的」页面输入邀请码加入家庭</text>
+        <button class="close-btn" @click="showInviteCard = false">关闭</button>
+      </view>
+    </view>
+
+    <!-- 加载状态 -->
+    <view v-if="loading" class="loading-wrap">
+      <text class="loading-text">加载中...</text>
+    </view>
   </view>
 </template>
 
 <script>
-import { getFamilyMembers, kickFamilyMember } from '../../utils/api.js'
+import {
+  getFamilyMembers,
+  createChild,
+  kickFamilyMember,
+  generateFamilyInviteQrCode
+} from '../../utils/api.js'
 
 export default {
   data() {
@@ -40,10 +119,24 @@ export default {
       familyId: 0,
       familyName: '',
       inviteCode: '',
+      inviteQrCode: '',
       creatorId: null,
       parents: [],
       children: [],
-      isAdmin: false
+      isAdmin: false,
+      loading: false,
+
+      // 添加孩子
+      showAddChild: false,
+      newChildName: '',
+
+      // 邀请卡片
+      showInviteCard: false
+    }
+  },
+  computed: {
+    visibleParents() {
+      return this.parents.filter(p => p.id !== this.userId)
     }
   },
   onShow() {
@@ -53,13 +146,14 @@ export default {
   },
   methods: {
     async loadMembers() {
+      this.loading = true
       try {
         const res = await getFamilyMembers()
         if (res.code === 200 && res.data) {
           const data = res.data
           this.familyId = data.familyId
           this.familyName = data.familyName
-          this.inviteCode = data.inviteCode
+          this.inviteCode = data.inviteCode || ''
           this.creatorId = data.creatorId
           this.parents = data.parents || []
           this.children = data.children || []
@@ -67,8 +161,58 @@ export default {
         }
       } catch (e) {
         uni.showToast({ title: '加载失败', icon: 'none' })
+      } finally {
+        this.loading = false
+      }
+    },
+    // 添加孩子
+    async onAddChild() {
+      const name = this.newChildName.trim()
+      if (!name) return
+      try {
+        const res = await createChild({
+          nickname: name,
+          age: 3,
+          gender: 'male'
+        })
+        if (res.code === 200) {
+          uni.showToast({ title: '添加成功', icon: 'success' })
+          this.showAddChild = false
+          this.newChildName = ''
+          this.loadMembers()
+        } else {
+          uni.showToast({ title: res.message || '添加失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '添加失败', icon: 'none' })
       }
     },
+    // 邀请成员
+    async onInvite() {
+      this.showInviteCard = true
+      this.inviteQrCode = ''
+      try {
+        const res = await generateFamilyInviteQrCode()
+        if (res.code === 200 && res.data) {
+          this.inviteCode = res.data.inviteCode
+          this.inviteQrCode = res.data.qrCodeBase64
+        } else {
+          uni.showToast({ title: res.message || '生成失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '生成失败', icon: 'none' })
+      }
+    },
+    // 复制邀请码
+    onCopyCode() {
+      uni.setClipboardData({
+        data: this.inviteCode,
+        success: () => {
+          uni.showToast({ title: '已复制邀请码', icon: 'success' })
+        }
+      })
+    },
+    // 踢出成员
     confirmKick(parent) {
       uni.showModal({
         title: '踢出成员',
@@ -96,35 +240,90 @@ export default {
 
 <style>
 .container {
-  padding: 20rpx;
-}
-.family-info {
-  background: #fff;
   padding: 30rpx;
-  border-radius: 16rpx;
-  margin-bottom: 20rpx;
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+
+/* 家庭信息头部 */
+.family-header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 20rpx;
+  padding: 40rpx 30rpx;
+  margin-bottom: 24rpx;
   text-align: center;
+  color: #fff;
+}
+.family-header-icon {
+  font-size: 56rpx;
+  margin-bottom: 12rpx;
 }
 .family-name {
   font-size: 36rpx;
   font-weight: bold;
+  display: block;
 }
-.invite-code {
+.family-member-count {
   font-size: 26rpx;
-  color: #999;
-  margin-top: 10rpx;
+  opacity: 0.85;
+  margin-top: 8rpx;
   display: block;
 }
+
+/* 操作按钮栏 */
+.action-bar {
+  display: flex;
+  gap: 20rpx;
+  margin-bottom: 24rpx;
+}
+.action-btn {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx 0;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+.action-btn:active {
+  opacity: 0.8;
+}
+.action-icon {
+  font-size: 32rpx;
+  margin-right: 8rpx;
+}
+.action-text {
+  font-size: 28rpx;
+  font-weight: 500;
+  color: #333;
+}
+
+/* 分区标题 */
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
   margin: 20rpx 0 16rpx;
   padding-left: 10rpx;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.section-add {
+  font-size: 26rpx;
+  color: #667eea;
+  font-weight: normal;
+  padding: 4rpx 12rpx;
+  border: 1rpx solid #667eea;
+  border-radius: 8rpx;
 }
+
+/* 成员列表 */
 .member-list {
   background: #fff;
   border-radius: 16rpx;
   overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
 }
 .member-item {
   display: flex;
@@ -136,36 +335,54 @@ export default {
   border-bottom: none;
 }
 .member-avatar {
-  font-size: 48rpx;
   width: 72rpx;
   height: 72rpx;
+  border-radius: 50%;
+  margin-right: 20rpx;
+  background: #f0f0f0;
+  flex-shrink: 0;
+}
+.child-avatar {
   display: flex;
   align-items: center;
   justify-content: center;
-  background: #f5f5f5;
-  border-radius: 50%;
-  margin-right: 20rpx;
+  font-size: 40rpx;
 }
 .member-detail {
   flex: 1;
+  min-width: 0;
 }
 .member-name {
   font-size: 30rpx;
   font-weight: 500;
+  display: block;
 }
-.member-role {
-  font-size: 24rpx;
-  color: #999;
-  margin-left: 10rpx;
+.member-tags {
+  display: flex;
+  align-items: center;
+  margin-top: 6rpx;
+  gap: 8rpx;
 }
-.member-admin {
+.member-role-tag {
+  font-size: 22rpx;
+  color: #667eea;
+  background: #eef0ff;
+  padding: 2rpx 12rpx;
+  border-radius: 6rpx;
+}
+.member-admin-tag {
   font-size: 22rpx;
   color: #e6a23c;
-  margin-left: 10rpx;
   background: #fdf6ec;
   padding: 2rpx 12rpx;
   border-radius: 6rpx;
 }
+.member-extra {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 4rpx;
+  display: inline;
+}
 .btn-kick {
   font-size: 24rpx;
   color: #f56c6c;
@@ -174,5 +391,167 @@ export default {
   padding: 8rpx 20rpx;
   border-radius: 8rpx;
   line-height: 1.5;
+  margin-left: 12rpx;
+  flex-shrink: 0;
+}
+.empty-row {
+  padding: 40rpx 0;
+  text-align: center;
+}
+.empty-text {
+  font-size: 26rpx;
+  color: #bbb;
+}
+
+/* 弹窗遮罩 */
+.modal-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 100;
+}
+
+/* 添加孩子弹窗 */
+.modal-content {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 40rpx;
+  width: 560rpx;
+}
+.modal-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  display: block;
+  text-align: center;
+  margin-bottom: 30rpx;
+}
+.modal-input {
+  border: 1rpx solid #ddd;
+  border-radius: 12rpx;
+  padding: 20rpx 24rpx;
+  font-size: 28rpx;
+  width: 100%;
+  box-sizing: border-box;
+  margin-bottom: 30rpx;
+}
+.modal-btns {
+  display: flex;
+  gap: 20rpx;
+}
+.modal-btn {
+  flex: 1;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+  text-align: center;
+  padding: 20rpx 0;
+  border: none;
+}
+.modal-btn.cancel {
+  background: #f5f5f5;
+  color: #666;
+}
+.modal-btn.confirm {
+  background: #667eea;
+  color: #fff;
+}
+.modal-btn.confirm[disabled] {
+  opacity: 0.5;
+}
+
+/* 邀请卡片 */
+.invite-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 40rpx;
+  width: 580rpx;
+  text-align: center;
+}
+.invite-card-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  display: block;
+}
+.invite-card-family {
+  font-size: 28rpx;
+  color: #667eea;
+  display: block;
+  margin-top: 8rpx;
+}
+.invite-qr-wrap {
+  margin: 30rpx auto;
+  width: 320rpx;
+  height: 320rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.invite-qr {
+  width: 320rpx;
+  height: 320rpx;
+}
+.loading-qr {
+  background: #f5f5f5;
+  border-radius: 16rpx;
+}
+.invite-code-section {
+  margin: 20rpx 0;
+}
+.invite-code-label {
+  font-size: 24rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 8rpx;
+}
+.invite-code-row {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 16rpx;
+}
+.invite-code {
+  font-size: 48rpx;
+  font-weight: bold;
+  letter-spacing: 6rpx;
+  color: #333;
+}
+.copy-btn {
+  font-size: 24rpx;
+  color: #667eea;
+  background: #eef0ff;
+  border: none;
+  padding: 8rpx 20rpx;
+  border-radius: 8rpx;
+  line-height: 1.5;
+}
+.invite-tip {
+  font-size: 24rpx;
+  color: #bbb;
+  display: block;
+  margin: 20rpx 0;
+}
+.close-btn {
+  width: 100%;
+  background: #f5f5f5;
+  border: none;
+  border-radius: 12rpx;
+  padding: 18rpx 0;
+  font-size: 28rpx;
+  color: #666;
+}
+
+/* 加载 */
+.loading-wrap {
+  text-align: center;
+  padding: 60rpx 0;
+}
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
 }
 </style>

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

@@ -67,12 +67,12 @@
       </view>
       <view class="wealth-card">
         <view class="wealth-item">
-          <text class="wealth-value">¥{{ wealthData.totalEarnings }}</text>
+          <text class="wealth-value">{{ formatPriceWithSymbol(wealthData.totalEarnings) }}</text>
           <text class="wealth-label">累计收益</text>
         </view>
         <view class="wealth-divider"></view>
         <view class="wealth-item">
-          <text class="wealth-value">¥{{ wealthData.availableAmount }}</text>
+          <text class="wealth-value">{{ formatPriceWithSymbol(wealthData.availableAmount) }}</text>
           <text class="wealth-label">可提现</text>
         </view>
         <view class="wealth-divider"></view>

+ 7 - 7
cfc-frontend/pages/promotion/commission.vue

@@ -5,18 +5,18 @@
       <view class="header-row">
         <view class="header-item">
           <text class="header-label">累计佣金</text>
-          <text class="header-value orange" v-if="summary">{{ '¥' + (summary.totalCommission || '0.00') }}</text>
-          <text class="header-value orange" v-else>¥0.00</text>
+          <text class="header-value orange" v-if="summary">{{ formatPriceWithSymbol(summary.totalCommission) }}</text>
+          <text class="header-value orange" v-else>{{ formatPriceWithSymbol(0) }}</text>
         </view>
         <view class="header-item">
           <text class="header-label">可提现</text>
-          <text class="header-value blue" v-if="summary">{{ '¥' + (summary.availableAmount || '0.00') }}</text>
-          <text class="header-value blue" v-else>¥0.00</text>
+          <text class="header-value blue" v-if="summary">{{ formatPriceWithSymbol(summary.availableAmount) }}</text>
+          <text class="header-value blue" v-else>{{ formatPriceWithSymbol(0) }}</text>
         </view>
         <view class="header-item">
           <text class="header-label">待结算</text>
-          <text class="header-value green" v-if="summary">{{ '¥' + (summary.pendingAmount || '0.00') }}</text>
-          <text class="header-value green" v-else>¥0.00</text>
+          <text class="header-value green" v-if="summary">{{ formatPriceWithSymbol(summary.pendingAmount) }}</text>
+          <text class="header-value green" v-else>{{ formatPriceWithSymbol(0) }}</text>
         </view>
       </view>
     </view>
@@ -44,7 +44,7 @@
           <text class="item-desc">{{ getOrderDesc(item) }}</text>
         </view>
         <view class="item-right">
-          <text class="item-amount">+¥{{ item.commissionAmount || '0.00' }}</text>
+          <text class="item-amount">+{{ formatPriceWithSymbol(item.commissionAmount) }}</text>
           <text class="item-status" :class="item.status === 'settled' ? 'status-settled' : item.status === 'cancelled' ? 'status-cancelled' : 'status-pending'">{{ getStatusText(item.status) }}</text>
           <text class="item-time">{{ formatTime(item.createdAt) }}</text>
         </view>

+ 6 - 6
cfc-frontend/pages/promotion/index.vue

@@ -21,20 +21,20 @@
       <view class="earnings-body">
         <view class="earning-item">
           <text class="earning-label">累计佣金</text>
-          <text class="earning-value orange" v-if="summary">{{ '¥' + (summary.totalCommission || '0.00') }}</text>
-          <text class="earning-value orange" v-else>¥0.00</text>
+          <text class="earning-value orange" v-if="summary">{{ formatPriceWithSymbol(summary.totalCommission) }}</text>
+          <text class="earning-value orange" v-else>{{ formatPriceWithSymbol(0) }}</text>
         </view>
         <view class="earning-divider"></view>
         <view class="earning-item">
           <text class="earning-label">可提现</text>
-          <text class="earning-value blue" v-if="summary">{{ '¥' + (summary.availableAmount || '0.00') }}</text>
-          <text class="earning-value blue" v-else>¥0.00</text>
+          <text class="earning-value blue" v-if="summary">{{ formatPriceWithSymbol(summary.availableAmount) }}</text>
+          <text class="earning-value blue" v-else>{{ formatPriceWithSymbol(0) }}</text>
         </view>
         <view class="earning-divider"></view>
         <view class="earning-item">
           <text class="earning-label">待结算</text>
-          <text class="earning-value green" v-if="summary">{{ '¥' + (summary.pendingAmount || '0.00') }}</text>
-          <text class="earning-value green" v-else>¥0.00</text>
+          <text class="earning-value green" v-if="summary">{{ formatPriceWithSymbol(summary.pendingAmount) }}</text>
+          <text class="earning-value green" v-else>{{ formatPriceWithSymbol(0) }}</text>
         </view>
       </view>
     </view>

+ 2 - 2
cfc-frontend/pages/promotion/invite.vue

@@ -15,8 +15,8 @@
       </view>
       <view class="stats-divider"></view>
       <view class="stats-item">
-        <text class="stats-num" v-if="summary">{{ '¥' + (summary.totalEarnings || '0.00') }}</text>
-        <text class="stats-num" v-else>¥0.00</text>
+        <text class="stats-num" v-if="summary">{{ formatPriceWithSymbol(summary.totalEarnings) }}</text>
+        <text class="stats-num" v-else>{{ formatPriceWithSymbol(0) }}</text>
         <text class="stats-label">总收益</text>
       </view>
     </view>

+ 5 - 5
cfc-frontend/pages/promotion/withdraw.vue

@@ -3,9 +3,9 @@
     <!-- 可提现余额 -->
     <view class="balance-card">
       <text class="balance-label">可提现金额</text>
-      <text class="balance-value" v-if="availableAmount">{{ '¥' + availableAmount }}</text>
-      <text class="balance-value" v-else>¥0.00</text>
-      <text class="balance-sub">累计佣金 ¥{{ totalCommission || '0.00' }}</text>
+      <text class="balance-value" v-if="availableAmount">{{ formatPriceWithSymbol(availableAmount) }}</text>
+      <text class="balance-value" v-else>{{ formatPriceWithSymbol(0) }}</text>
+      <text class="balance-sub">累计佣金 {{ formatPriceWithSymbol(totalCommission) }}</text>
     </view>
 
     <!-- 快捷金额 -->
@@ -46,7 +46,7 @@
       <text class="section-title">提现记录</text>
       <view class="history-item" v-for="item in history" :key="item.id">
         <view class="history-left">
-          <text class="history-amount">¥{{ item.amount }}</text>
+          <text class="history-amount">{{ formatPriceWithSymbol(item.amount) }}</text>
           <text class="history-time">{{ formatTime(item.createdAt) }}</text>
         </view>
         <text :class="['history-status', statusClass(item.status)]">{{ statusText(item.status) }}</text>
@@ -114,7 +114,7 @@ export default {
       }
       uni.showModal({
         title: '确认提现',
-        content: '确认提现 ¥' + amountNum.toFixed(2) + ' 到 ' + this.accountInfo + ' ?',
+        content: '确认提现 ¥' + amountNum.toFixed(2) + ' 元到 ' + this.accountInfo + ' ?',
         success: function(res) {
           if (res.confirm) {
             this.doWithdraw(amountNum)

+ 4 - 4
cfc-frontend/pages/shop/cart/cart.vue

@@ -46,14 +46,14 @@
               <text class="item-delete" @click="onRemove(item.productId)">🗑</text>
             </view>
             <view class="item-bottom">
-              <text class="item-price">¥{{ formatPrice(item.unitPrice) }}</text>
+              <text class="item-price">{{ formatPriceWithSymbol(item.unitPrice) }}</text>
               <view class="stepper">
                 <text class="stepper-btn" @click="decreaseQty(item)">−</text>
                 <text class="stepper-value">{{ item.quantity }}</text>
                 <text class="stepper-btn" @click="increaseQty(item)">+</text>
               </view>
             </view>
-            <text class="item-subtotal">小计: ¥{{ formatPrice(item.subtotal) }}</text>
+            <text class="item-subtotal">小计: {{ formatPriceWithSymbol(item.subtotal) }}</text>
           </view>
         </view>
       </view>
@@ -63,7 +63,7 @@
       <view class="bottom-left">
         <text class="selected-count">已选 {{ selectedCount }} 件</text>
         <text class="total-amount-label">合计: </text>
-        <text class="total-amount">¥{{ formatPrice(totalAmount) }}</text>
+        <text class="total-amount">{{ formatPriceWithSymbol(totalAmount) }}</text>
       </view>
       <button
         :class="['checkout-btn', selectedCount === 0 ? 'disabled' : '']"
@@ -226,7 +226,7 @@ export default {
     },
     formatPrice(price) {
       if (price === null || price === undefined) return '0.00'
-      return Number(price).toFixed(2)
+      return (Number(price) / 100).toFixed(2)
     }
   }
 }

+ 4 - 4
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -41,7 +41,7 @@
             <text class="item-name">{{ item.productName }}</text>
             <text class="item-qty">x{{ item.quantity }}</text>
           </view>
-          <text class="item-price">¥{{ formatPrice(item.unitPrice) }}</text>
+          <text class="item-price">{{ formatPriceWithSymbol(item.unitPrice) }}</text>
         </view>
       </view>
 
@@ -49,7 +49,7 @@
       <view class="section">
         <view class="info-row">
           <text class="info-label">运费</text>
-          <text class="info-value">¥{{ formatPrice(freight) }}</text>
+          <text class="info-value">{{ formatPriceWithSymbol(freight) }}</text>
         </view>
       </view>
 
@@ -72,7 +72,7 @@
     <view class="bottom-bar">
       <view class="bottom-left">
         <text class="total-label">合计: </text>
-        <text class="total-amount">¥{{ formatPrice(actualAmount + freight) }}</text>
+        <text class="total-amount">{{ formatPriceWithSymbol(actualAmount + freight) }}</text>
       </view>
       <button
         :class="['submit-btn', submitting ? 'disabled' : '']"
@@ -220,7 +220,7 @@ export default {
     },
     formatPrice(price) {
       if (price === null || price === undefined) return '0.00'
-      return Number(price).toFixed(2)
+      return (Number(price) / 100).toFixed(2)
     },
     formatPhone(phone) {
       if (!phone) return ''

+ 4 - 4
cfc-frontend/pages/shop/detail/detail.vue

@@ -14,8 +14,8 @@
           <text class="product-name">{{ product.name }}</text>
         </view>
         <view class="price-row">
-          <text class="price">¥{{ product.price }}</text>
-          <text v-if="product.memberPrice" class="member-price">会员价 ¥{{ product.memberPrice }}</text>
+          <text class="price">{{ formatPriceWithSymbol(product.price) }}</text>
+          <text v-if="product.memberPrice" class="member-price">会员价 {{ formatPriceWithSymbol(product.memberPrice) }}</text>
         </view>
         <view class="meta-row">
           <text v-if="product.productType" class="meta-item">{{ typeLabel }}</text>
@@ -40,8 +40,8 @@
 
       <view class="bottom-bar">
         <view class="price-info">
-          <text class="bottom-price">¥{{ product.price }}</text>
-          <text v-if="product.memberPrice" class="bottom-member">会员¥{{ product.memberPrice }}</text>
+          <text class="bottom-price">{{ formatPriceWithSymbol(product.price) }}</text>
+          <text v-if="product.memberPrice" class="bottom-member">会员{{ formatPriceWithSymbol(product.memberPrice) }}</text>
         </view>
         <view class="action-btns">
           <view class="btn-favorite" @click="onToggleFavorite">

+ 2 - 2
cfc-frontend/pages/shop/index.vue

@@ -69,8 +69,8 @@
           <view class="card-body">
             <text class="product-name">{{ item.name }}</text>
             <view class="price-row">
-              <text class="price">¥{{ item.price }}</text>
-              <text v-if="item.memberPrice" class="member-price">会员¥{{ item.memberPrice }}</text>
+              <text class="price">{{ formatPriceWithSymbol(item.price) }}</text>
+              <text v-if="item.memberPrice" class="member-price">会员{{ formatPriceWithSymbol(item.memberPrice) }}</text>
             </view>
             <text class="vendor-name">{{ item.brandName || '平台官方' }}</text>
           </view>

+ 2 - 2
cfc-frontend/pages/shop/order-detail/order-detail.vue

@@ -46,7 +46,7 @@
         <text class="section-title">费用明细</text>
         <view class="info-row">
           <text class="info-label">单价</text>
-          <text class="info-value">¥{{ order.unitPrice }}</text>
+          <text class="info-value">{{ formatPriceWithSymbol(order.unitPrice) }}</text>
         </view>
         <view class="info-row">
           <text class="info-label">数量</text>
@@ -54,7 +54,7 @@
         </view>
         <view class="info-row total-row">
           <text class="info-label">合计</text>
-          <text class="total-amount">¥{{ order.totalAmount }}</text>
+          <text class="total-amount">{{ formatPriceWithSymbol(order.totalAmount) }}</text>
         </view>
       </view>
 

+ 1 - 1
cfc-frontend/pages/shop/order-list/order-list.vue

@@ -43,7 +43,7 @@
               <text class="product-type">{{ typeLabel(item.productType) }}</text>
             </view>
             <view class="order-meta">
-              <text class="order-amount">¥{{ item.totalAmount }}</text>
+              <text class="order-amount">{{ formatPriceWithSymbol(item.totalAmount) }}</text>
               <text class="order-qty">x{{ item.quantity }}</text>
             </view>
           </view>

+ 3 - 3
cfc-frontend/pages/shop/payment/payment.vue

@@ -23,7 +23,7 @@
         </view>
         <view class="info-row">
           <text class="info-label">订单金额</text>
-          <text class="order-amount">¥{{ formatPrice(amount) }}</text>
+          <text class="order-amount">{{ formatPriceWithSymbol(amount) }}</text>
         </view>
       </view>
 
@@ -46,7 +46,7 @@
           :disabled="paying || countdown <= 0"
           @click="onPay"
         >
-          {{ paying ? '支付中...' : '微信支付 ¥' + formatPrice(amount) }}
+          {{ paying ? '支付中...' : '微信支付 ' + formatPriceWithSymbol(amount) }}
         </button>
         <text class="cancel-link" @click="onCancelOrder">取消订单</text>
       </view>
@@ -187,7 +187,7 @@ export default {
     },
     formatPrice(price) {
       if (price === null || price === undefined) return '0.00'
-      return Number(price).toFixed(2)
+      return (Number(price) / 100).toFixed(2)
     }
   }
 }

+ 162 - 0
cfc-frontend/pages/splash/splash.vue

@@ -0,0 +1,162 @@
+<template>
+  <view class="splash-root">
+    <view class="splash-content">
+      <!-- Brand Logo Mark -->
+      <view class="splash-logo-mark">
+        <text class="splash-logo-char">浠</text>
+      </view>
+
+      <!-- Brand Name -->
+      <text class="splash-brand-name">浠艾福</text>
+
+      <!-- Tagline -->
+      <text class="splash-tagline">给全家的一站式幸福提案</text>
+
+      <!-- Loading dots at bottom -->
+      <view class="splash-loading">
+        <view class="splash-dot splash-dot--1"></view>
+        <view class="splash-dot splash-dot--2"></view>
+        <view class="splash-dot splash-dot--3"></view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'SplashPage',
+  data() {
+    return {
+      ready: false
+    }
+  },
+  onLoad() {
+    // 标记 splash 已展示
+    this.ready = true
+  },
+  onReady() {
+    // 延迟 2500ms 后跳转到首页
+    setTimeout(function() {
+      uni.setStorageSync('_splashReady', true)
+      uni.reLaunch({
+        url: '/pages/index/index'
+      })
+    }, 2500)
+  }
+}
+</script>
+
+<style scoped>
+.splash-root {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(160deg, #FFF7ED 0%, #F97316 100%);
+  z-index: 9999;
+}
+
+.splash-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  opacity: 0;
+  animation: splash-fade-in 0.8s ease forwards;
+}
+
+@keyframes splash-fade-in {
+  from {
+    opacity: 0;
+    transform: translateY(20rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* ===== Logo Mark ===== */
+.splash-logo-mark {
+  width: 144rpx;
+  height: 144rpx;
+  border-radius: 36rpx;
+  background: rgba(255, 255, 255, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 24rpx;
+  backdrop-filter: blur(4px);
+}
+
+.splash-logo-char {
+  font-size: 72rpx;
+  font-weight: 700;
+  color: #FFFFFF;
+  line-height: 1;
+}
+
+/* ===== Brand Name ===== */
+.splash-brand-name {
+  font-size: 56rpx;
+  font-weight: 700;
+  color: #FFFFFF;
+  letter-spacing: 8rpx;
+  margin-bottom: 12rpx;
+  text-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+/* ===== Tagline ===== */
+.splash-tagline {
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.85);
+  letter-spacing: 4rpx;
+  font-weight: 400;
+}
+
+/* ===== Loading Dots ===== */
+.splash-loading {
+  position: absolute;
+  bottom: 120rpx;
+  left: 0;
+  right: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 12rpx;
+}
+
+.splash-dot {
+  width: 14rpx;
+  height: 14rpx;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.6);
+  animation: splash-dot-bounce 1.4s ease-in-out infinite both;
+}
+
+.splash-dot--1 {
+  animation-delay: -0.32s;
+}
+
+.splash-dot--2 {
+  animation-delay: -0.16s;
+}
+
+.splash-dot--3 {
+  animation-delay: 0s;
+}
+
+@keyframes splash-dot-bounce {
+  0%, 80%, 100% {
+    transform: scale(0.6);
+    opacity: 0.4;
+  }
+  40% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+</style>

+ 33 - 942
cfc-frontend/pages/teacher/index.vue

@@ -1,968 +1,59 @@
+<!--
+  ⚠️ 废弃说明(2026-06-15):
+  此页面已按照设计决策废弃。教师首页功能已折入 parent-index。
+  保留文件仅用于参考,不通过任何路由访问。
+-->
 <template>
-  <view class="container">
-    <!-- 加载状态 -->
-    <view class="loading-state" v-if="loading">
-      <view class="loading-spinner"></view>
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <template v-if="!loading">
-      <!-- 顶部 -->
-      <view class="header" v-if="teacherInfo">
-        <view class="header-left">
-          <view class="avatar-wrapper">
-            <view class="avatar avatar-placeholder">{{ avatarChar }}</view>
-            <view class="avatar-ring"></view>
-          </view>
-          <view class="info">
-            <text class="name">{{ teacherInfo.nickname || '成长规划师' }}</text>
-            <view class="badge-row">
-              <text class="level-badge">{{ teacherInfo.level || '规划师' }}</text>
-              <view class="rating">
-                <text class="star">⭐</text>
-                <text class="score">{{ teacherInfo.rating || '5.0' }}</text>
-              </view>
-            </view>
-          </view>
-        </view>
-        <view class="switch-btn" @click="switchToParent">
-          <text class="switch-text">切换家长端</text>
-        </view>
-      </view>
-
-      <!-- 未注册提示 -->
-      <view class="register-prompt" v-if="!teacherInfo">
-        <view class="register-icon">🎯</view>
-        <text class="prompt-title">成为成长规划师</text>
-        <text class="prompt-desc">开启为家庭提供专业成长规划服务</text>
-        <button class="btn-register">立即注册</button>
-      </view>
-
-      <!-- 数据仪表盘 -->
-      <view class="stats-section" v-if="teacherInfo">
-        <view class="section-header">
-          <text class="section-title">今日概览</text>
-          <text class="section-subtitle">{{ currentDate }}</text>
-        </view>
-        <view class="stats-grid">
-          <view class="stat-card stat-blue">
-            <view class="stat-header">
-              <view class="stat-icon-bg blue-bg">
-                <text class="stat-icon">👨‍👩‍👧</text>
-              </view>
-              <text class="stat-trend up" v-if="stats.familyGrowth > 0">+{{ stats.familyGrowth }}</text>
-            </view>
-            <text class="stat-value">{{ stats.serviceFamilyCount }}</text>
-            <text class="stat-label">服务家庭</text>
-            <view class="stat-chart">
-              <view class="mini-bar" v-for="(h, i) in familyBars" :key="i"
-                :style="{ height: h + '%' }" :class="{ 'bar-active': i === familyBars.length - 1 }"></view>
-            </view>
-          </view>
-          <view class="stat-card stat-green">
-            <view class="stat-header">
-              <view class="stat-icon-bg green-bg">
-                <text class="stat-icon">📋</text>
-              </view>
-              <text class="stat-trend">{{ stats.pendingTaskCount > 0 ? '待处理' : '已清零' }}</text>
-            </view>
-            <text class="stat-value">{{ stats.pendingTaskCount }}</text>
-            <text class="stat-label">待办任务</text>
-            <view class="stat-action">
-              <text class="action-text">立即处理 →</text>
-            </view>
-          </view>
-          <view class="stat-card stat-gold">
-            <view class="stat-header">
-              <view class="stat-icon-bg gold-bg">
-                <text class="stat-icon">💰</text>
-              </view>
-              <text class="stat-trend up" v-if="stats.incomeGrowth > 0">+{{ stats.incomeGrowth }}%</text>
-            </view>
-            <text class="stat-value">¥{{ stats.todayIncome }}</text>
-            <text class="stat-label">今日收入</text>
-            <view class="stat-goal">
-              <text class="goal-text">月目标 ¥{{ stats.monthGoal }}</text>
-            </view>
-          </view>
-          <view class="stat-card stat-purple">
-            <view class="stat-header">
-              <view class="stat-icon-bg purple-bg">
-                <text class="stat-icon">👥</text>
-              </view>
-            </view>
-            <text class="stat-value">{{ stats.teamCount }}</text>
-            <text class="stat-label">团队成员</text>
-            <view class="stat-action">
-              <text class="action-text">查看团队 →</text>
-            </view>
-          </view>
-        </view>
-      </view>
-
-      <!-- ===== 今日日程 ===== -->
-      <view class="schedule-section" v-if="teacherInfo && todayAppointments.length > 0">
-        <view class="section-title">📅 今日日程</view>
-        <view class="schedule-card" v-for="apt in todayAppointments" :key="apt.id">
-          <view class="schedule-time">{{ apt.appointmentTime || apt.time }}</view>
-          <view class="schedule-content">
-            <text class="schedule-title">{{ apt.title }}</text>
-            <text class="schedule-desc">{{ apt.description }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 数据趋势 -->
-      <view class="trend-section" v-if="teacherInfo">
-        <view class="section-title">本周数据趋势</view>
-        <view class="trend-chart">
-          <view class="trend-item" v-for="(day, i) in weekDays" :key="i">
-            <view class="trend-bar" :style="{ height: day.height + '%' }"
-              :class="{ 'trend-active': i === todayIndex }"></view>
-            <text class="trend-day">{{ day.name }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 功能菜单 -->
-      <view class="menu-section" v-if="teacherInfo">
-        <view class="section-title">我的服务</view>
-        <view class="menu-grid">
-          <view class="menu-item" @click="goToFamilies">
-            <view class="menu-icon mi-blue">
-              <text>👨‍👩‍👧</text>
-            </view>
-            <text class="menu-label">服务家庭</text>
-            <view class="menu-badge" v-if="stats.serviceFamilyCount > 0">{{ stats.serviceFamilyCount }}</view>
-          </view>
-          <view class="menu-item" @click="goToPackages">
-            <view class="menu-icon mi-gold">
-              <text>📦</text>
-            </view>
-            <text class="menu-label">套餐管理</text>
-          </view>
-          <view class="menu-item" @click="goToTeam">
-            <view class="menu-icon mi-purple">
-              <text>👥</text>
-            </view>
-            <text class="menu-label">我的团队</text>
-          </view>
-          <view class="menu-item" @click="goToMessages">
-            <view class="menu-icon mi-green">
-              <text>💬</text>
-            </view>
-            <text class="menu-label">消息中心</text>
-            <view class="msg-badge" v-if="unreadCount > 0">{{ unreadCount }}</view>
-          </view>
-          <view class="menu-item" @click="goToOrders">
-            <view class="menu-icon mi-cyan">
-              <text>📊</text>
-            </view>
-            <text class="menu-label">订单与佣金</text>
-          </view>
-          <view class="menu-item" @click="goToSettings">
-            <view class="menu-icon mi-gray">
-              <text>⚙️</text>
-            </view>
-            <text class="menu-label">设置</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- ===== 绑定家庭 ===== -->
-      <view class="family-section" v-if="teacherInfo">
-        <view class="section-title">👨‍👩‍👧‍👦 绑定家庭</view>
-        <scroll-view class="family-scroll" scroll-x enhanced :show-scrollbar="false" v-if="boundFamilies.length > 0">
-          <view class="family-card" v-for="family in boundFamilies" :key="family.id" @click="goToFamilyDetail(family)">
-            <text class="family-name">{{ family.familyName || family.name }}</text>
-            <text class="family-child-count">{{ family.childCount || 0 }}个孩子</text>
-            <text class="family-latest-task" v-if="family.latestTask">最近: {{ family.latestTask }}</text>
-          </view>
-        </scroll-view>
-        <view class="family-empty" v-else>
-          <text class="family-empty-text">暂无绑定家庭,邀请家长或等待家长邀请</text>
-        </view>
-      </view>
-
-      <!-- ===== 快捷操作 ===== -->
-      <view class="quick-actions-section" v-if="teacherInfo">
-        <view class="section-title">⚡ 快捷操作</view>
-        <view class="quick-actions-grid">
-          <view class="quick-action-item" @click="goToAssignTask">
-            <view class="quick-action-icon qa-icon--assign"><text class="qa-emoji">📝</text></view>
-            <text class="qa-label">下发任务</text>
-          </view>
-          <view class="quick-action-item" @click="goToInputAssessment">
-            <view class="quick-action-icon qa-icon--assessment"><text class="qa-emoji">📊</text></view>
-            <text class="qa-label">录入测评</text>
-          </view>
-          <view class="quick-action-item" @click="goToContactParent">
-            <view class="quick-action-icon qa-icon--contact"><text class="qa-emoji">💬</text></view>
-            <text class="qa-label">联系家长</text>
-          </view>
-          <view class="quick-action-item" @click="goToIncome">
-            <view class="quick-action-icon qa-icon--income"><text class="qa-emoji">💰</text></view>
-            <text class="qa-label">查看收入</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 快捷工具 -->
-      <view class="tools-section" v-if="teacherInfo">
-        <view class="section-title">快捷工具</view>
-        <view class="tools-row">
-          <view class="tool-item">
-            <view class="tool-icon-wrap tool-create"><text class="tool-icon-text">➕</text></view>
-            <text class="tool-label">创建套餐</text>
-          </view>
-          <view class="tool-item">
-            <view class="tool-icon-wrap tool-share"><text class="tool-icon-text">📤</text></view>
-            <text class="tool-label">分享名片</text>
-          </view>
-          <view class="tool-item">
-            <view class="tool-icon-wrap tool-cal"><text class="tool-icon-text">📅</text></view>
-            <text class="tool-label">日程管理</text>
-          </view>
-          <view class="tool-item">
-            <view class="tool-icon-wrap tool-report"><text class="tool-icon-text">📈</text></view>
-            <text class="tool-label">数据报告</text>
-          </view>
-        </view>
-      </view>
-    </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>
 export default {
-  data() {
-    return {
-      loading: true,
-      teacherInfo: null,
-      unreadCount: 0,
-      currentDate: '',
-      stats: {
-        serviceFamilyCount: 0,
-        pendingTaskCount: 0,
-        todayIncome: 0,
-        monthGoal: 30000,
-        teamCount: 0,
-        familyGrowth: 0,
-        incomeGrowth: 0
-      },
-      weekDays: [],
-      todayIndex: 6,
-      familyBars: [30, 50, 45, 65, 80, 70, 55],
-      todayAppointments: [],
-      boundFamilies: [],
-      _teacherMounted: false
-    }
-  },
-  computed: {
-    avatarChar() {
-      return (this.teacherInfo && this.teacherInfo.nickname) ? this.teacherInfo.nickname[0] : '师'
-    }
-  },
-  mounted() {
-    this.currentDate = this.formatDate(new Date())
-    this.initWeekDays()
-    this.todayIndex = new Date().getDay()
-    if (this.todayIndex === 0) this.todayIndex = 7
-    this.todayIndex = this.todayIndex - 1
-    this.loadData()
-  },
-  onShow() {
-    if (!this._teacherMounted) {
-      this._teacherMounted = true
-      return
-    }
-    this.loadData()
-  },
   methods: {
-    formatDate(date) {
-      const months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
-      return months[date.getMonth()] + (date.getMonth() + 1) + '月' + date.getDate() + '日'
-    },
-    initWeekDays() {
-      const names = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
-      this.weekDays = names.map(function(name, i) {
-        const heights = [45, 60, 55, 75, 85, 70, 50]
-        return { name: name, height: heights[i] }
-      })
-    },
-    async loadData() {
-      this.loading = true
-      try {
-        const userInfo = uni.getStorageSync('userInfo')
-        if (userInfo && userInfo.nickname) {
-          this.teacherInfo = {
-            nickname: userInfo.nickname,
-            avatar: uni.getStorageSync('avatar') || '/static/default-avatar.png',
-            level: '规划师',
-            rating: '5.0'
-          }
-        }
-
-        // 加载真实数据,失败则保留默认值
-        try {
-          const { getTeamStats } = require('../../utils/api.js')
-          if (typeof getTeamStats === 'function') {
-            const res = await getTeamStats()
-            if (res && res.data) {
-              this.stats.serviceFamilyCount = res.data.familyCount || 12
-              this.stats.pendingTaskCount = res.data.pendingTaskCount || 5
-              this.stats.todayIncome = res.data.todayIncome || 1280
-              this.stats.teamCount = res.data.teamCount || 8
-              this.stats.familyGrowth = res.data.familyGrowth || 2
-              this.stats.incomeGrowth = res.data.incomeGrowth || 12
-            }
-          }
-        } catch (e) {
-          console.log('加载统计失败,使用默认值', e)
-          this.stats = {
-            serviceFamilyCount: 12,
-            pendingTaskCount: 5,
-            todayIncome: 1280,
-            monthGoal: 30000,
-            teamCount: 8,
-            familyGrowth: 2,
-            incomeGrowth: 12
-          }
-        }
-
-        try {
-          const { getGuideOrders } = require('../../utils/api.js')
-          if (typeof getGuideOrders === 'function') {
-            const ordersRes = await getGuideOrders()
-            if (ordersRes && ordersRes.data) {
-              this.unreadCount = ordersRes.data.unreadCount || 3
-            }
-          }
-        } catch (e) {
-          this.unreadCount = 3
-        }
-      } catch (e) {
-        console.error('加载数据失败', e)
-      } finally {
-        this.loading = false
-      }
-    },
-    switchToParent() {
-      uni.switchTab({ url: '/pages/index/parent-index' })
-    },
-    goToFamilyDetail(family) {
-      uni.navigateTo({ url: '/pages/guide/family-detail?familyId=' + family.id })
-    },
-    goToAssignTask() {
-      uni.navigateTo({ url: '/pages/guide/assign-task' })
-    },
-    goToInputAssessment() {
-      uni.navigateTo({ url: '/pages/assessment/input-result' })
-    },
-    goToContactParent() {
-      uni.navigateTo({ url: '/pages/guide/messages' })
-    },
-    goToIncome() {
-      uni.navigateTo({ url: '/pages/guide/income' })
-    },
-    goToFamilies() { uni.navigateTo({ url: '/pages/teacher/families' }) },
-    goToPackages() { uni.navigateTo({ url: '/pages/guide/packages/index' }) },
-    goToTeam() { uni.navigateTo({ url: '/pages/teacher/team' }) },
-    goToMessages() { uni.navigateTo({ url: '/pages/teacher/messages' }) },
-    goToOrders() { uni.navigateTo({ url: '/pages/teacher/orders' }) },
-    goToSettings() { uni.navigateTo({ url: '/pages/profile/settings' }) }
+    goToHome() {
+      uni.switchTab({ url: '/pages/index/index' })
+    }
   }
 }
 </script>
 
 <style scoped>
-/* ========================================
-   Claymorphism Design System - 专业蓝黏土
-   ======================================== */
-:root {
-  --clay-br: 20rpx;
-  --clay-br-lg: 26rpx;
-  --clay-bw: 4rpx;
-  --clay-blue-border: #BFDBFE;
-  --clay-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(59,130,246,0.08);
-  --clay-shadow-sm: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.4), 4rpx 4rpx 10rpx rgba(59,130,246,0.06);
-  --clay-transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-.container {
-  padding: 32rpx;
-  background: #EFF6FF;
-  min-height: 100vh;
-}
-
-/* ========================================
-   加载状态
-   ======================================== */
-.loading-state {
+.deprecated-container {
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 200rpx 0;
-}
-
-.loading-spinner {
-  width: 64rpx;
-  height: 64rpx;
-  border: 8rpx solid #DBEAFE;
-  border-top-color: #3B82F6;
-  border-radius: 50%;
-  animation: spin 0.8s linear infinite;
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.4), 2rpx 2rpx 6rpx rgba(59,130,246,0.08);
-}
-
-@keyframes spin {
-  to { transform: rotate(360deg); }
-}
-
-.loading-text {
-  font-size: 26rpx;
-  color: #64748B;
-  margin-top: 20rpx;
-}
-
-/* ========================================
-   顶部 - claymorphism
-   ======================================== */
-.header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 32rpx;
-}
-
-.header-left {
-  display: flex;
-  align-items: center;
-}
-
-.avatar-wrapper {
-  position: relative;
-  margin-right: 20rpx;
-}
-
-.avatar {
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 50%;
-}
-
-.avatar-placeholder {
-  display: flex;
-  align-items: center;
   justify-content: center;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  color: #FFFFFF;
-  font-size: 40rpx;
-  font-weight: 600;
-}
-
-.avatar-ring {
-  position: absolute;
-  top: -6rpx;
-  left: -6rpx;
-  right: -6rpx;
-  bottom: -6rpx;
-  border: 4rpx solid #3B82F6;
-  border-radius: 50%;
-}
-
-.info .name {
-  font-size: 36rpx;
-  font-weight: 700;
-  color: #1E293B;
-  display: block;
-}
-
-.badge-row {
-  display: flex;
-  align-items: center;
-  margin-top: 8rpx;
-}
-
-.level-badge {
-  font-size: 22rpx;
-  color: #F59E0B;
-  background: rgba(245, 158, 11, 0.15);
-  padding: 4rpx 14rpx;
-  border-radius: 10rpx;
-  border: 2rpx solid #FDE68A;
-  margin-right: 12rpx;
-}
-
-.rating {
-  display: flex;
-  align-items: center;
-}
-
-.rating .star { font-size: 20rpx; }
-.rating .score { font-size: 22rpx; color: #1E293B; font-weight: 600; }
-
-.switch-btn {
-  background: #3B82F6;
-  padding: 16rpx 24rpx;
-  border-radius: 16rpx;
-  border: 3rpx solid #2563EB;
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.25), 3rpx 3rpx 10rpx rgba(59,130,246,0.25);
-  flex-shrink: 0;
-  transition: var(--clay-transition);
-}
-
-.switch-btn:active {
-  box-shadow: inset 2rpx 2rpx 6rpx rgba(0,0,0,0.1), 1rpx 1rpx 4rpx rgba(59,130,246,0.15);
-  transform: scale(0.96);
-}
-
-.switch-text {
-  color: #FFFFFF;
-  font-size: 24rpx;
-  font-weight: 500;
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding: 60rpx;
 }
-
-/* ========================================
-   注册提示 - claymorphism
-   ======================================== */
-.register-prompt {
-  background: #FFFFFF;
-  border-radius: 26rpx;
-  padding: 48rpx;
-  text-align: center;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(59,130,246,0.08);
+.deprecated-icon {
+  font-size: 80rpx;
+  margin-bottom: 30rpx;
 }
-
-.register-icon { font-size: 80rpx; margin-bottom: 24rpx; }
-
-.prompt-title {
+.deprecated-title {
   font-size: 36rpx;
-  font-weight: 700;
-  color: #1E293B;
-  display: block;
-  margin-bottom: 12rpx;
-}
-
-.prompt-desc {
-  font-size: 28rpx;
-  color: #64748B;
-  display: block;
-  margin-bottom: 32rpx;
-}
-
-.btn-register {
-  background: #3B82F6;
-  color: #FFFFFF;
-  font-size: 28rpx;
-  padding: 20rpx 48rpx;
-  border-radius: 16rpx;
-  border: 3rpx solid #2563EB;
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.25), 3rpx 3rpx 10rpx rgba(59,130,246,0.25);
-  transition: var(--clay-transition);
-}
-
-.btn-register:active {
-  box-shadow: inset 2rpx 2rpx 6rpx rgba(0,0,0,0.1), 1rpx 1rpx 4rpx rgba(59,130,246,0.15);
-  transform: scale(0.97);
-}
-
-/* ========================================
-   数据仪表盘 - claymorphism
-   ======================================== */
-.stats-section { margin-bottom: 32rpx; }
-
-.section-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 24rpx;
-}
-
-.section-title {
-  font-size: 32rpx;
-  font-weight: 700;
-  color: #1E293B;
+  font-weight: bold;
+  color: #333;
   margin-bottom: 16rpx;
 }
-
-.section-subtitle {
-  font-size: 24rpx;
-  color: #64748B;
-}
-
-.stats-grid {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-
-.stat-card {
-  width: calc(50% - 8rpx);
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  padding: 24rpx;
-  box-sizing: border-box;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(59,130,246,0.08);
-  transition: var(--clay-transition);
-}
-
-.stat-card:active {
-  box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.04), 3rpx 3rpx 10rpx rgba(59,130,246,0.05);
-}
-
-.stat-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 14rpx;
-}
-
-.stat-icon-bg {
-  width: 60rpx;
-  height: 60rpx;
-  border-radius: 18rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border: 3rpx solid transparent;
-  box-shadow: inset -2rpx -2rpx 5rpx rgba(255,255,255,0.4), 3rpx 3rpx 8rpx rgba(0,0,0,0.04);
-}
-
-.stat-icon { font-size: 30rpx; }
-
-.blue-bg { background: rgba(59, 130, 246, 0.18); border-color: #BFDBFE; }
-.green-bg { background: rgba(16, 185, 129, 0.18); border-color: #A7F3D0; }
-.gold-bg { background: rgba(245, 158, 11, 0.18); border-color: #FDE68A; }
-.purple-bg { background: rgba(139, 92, 246, 0.18); border-color: #DDD6FE; }
-
-.stat-trend {
-  font-size: 22rpx;
-  color: #64748B;
-}
-
-.stat-trend.up { color: #10B981; }
-
-.stat-value {
-  font-size: 40rpx;
-  font-weight: 800;
-  display: block;
-}
-
-.stat-label {
-  font-size: 24rpx;
-  color: #64748B;
-  display: block;
-  margin-top: 4rpx;
-}
-
-.stat-blue .stat-value { color: #3B82F6; }
-.stat-green .stat-value { color: #10B981; }
-.stat-gold .stat-value { color: #F59E0B; }
-.stat-purple .stat-value { color: #8B5CF6; }
-
-.stat-chart {
-  display: flex;
-  align-items: flex-end;
-  gap: 6rpx;
-  height: 48rpx;
-  margin-top: 14rpx;
-}
-
-.mini-bar {
-  flex: 1;
-  background: #E2E8F0;
-  border-radius: 4rpx;
-  transition: height 0.3s;
-}
-
-.mini-bar.bar-active {
-  background: #3B82F6;
-}
-
-.stat-action {
-  margin-top: 14rpx;
-}
-
-.action-text {
-  font-size: 22rpx;
-  color: #3B82F6;
-  font-weight: 500;
-}
-
-.stat-goal {
-  margin-top: 14rpx;
-  font-size: 20rpx;
-  color: #F59E0B;
-}
-
-/* ========================================
-   今日日程
-   ======================================== */
-.schedule-section { margin-bottom: 32rpx; }
-.schedule-card {
-  display: flex;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  margin-bottom: 12rpx;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: var(--clay-shadow-sm);
-}
-.schedule-time {
+.deprecated-desc {
   font-size: 26rpx;
-  font-weight: 700;
-  color: #3B82F6;
-  min-width: 120rpx;
-  flex-shrink: 0;
-}
-.schedule-content { flex: 1; min-width: 0; margin-left: 16rpx; }
-.schedule-title { font-size: 28rpx; font-weight: 600; color: #1E293B; display: block; }
-.schedule-desc { font-size: 24rpx; color: #64748B; margin-top: 4rpx; display: block; }
-
-/* ========================================
-   绑定家庭
-   ======================================== */
-.family-section { margin-bottom: 32rpx; }
-.family-scroll { display: flex; flex-direction: row; white-space: nowrap; overflow: hidden; padding: 8rpx 0; }
-.family-card {
-  display: inline-flex; flex-direction: column;
-  width: 240rpx; min-height: 160rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  margin-right: 16rpx;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: var(--clay-shadow-sm);
-  flex-shrink: 0;
-}
-.family-card:active { transform: scale(0.96); }
-.family-name { font-size: 28rpx; font-weight: 700; color: #1E293B; display: block; }
-.family-child-count { font-size: 22rpx; color: #64748B; margin-top: 6rpx; display: block; }
-.family-latest-task { font-size: 22rpx; color: #3B82F6; margin-top: 8rpx; display: block; }
-.family-empty {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 32rpx;
+  color: #999;
+  margin-bottom: 48rpx;
   text-align: center;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: var(--clay-shadow-sm);
-}
-.family-empty-text { font-size: 24rpx; color: #94A3B8; }
-
-/* ========================================
-   快捷操作 2x2
-   ======================================== */
-.quick-actions-section { margin-bottom: 32rpx; }
-.quick-actions-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
-.quick-action-item {
-  width: calc(50% - 8rpx);
-  display: flex; flex-direction: column; align-items: center;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 28rpx;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: var(--clay-shadow-sm);
-  transition: var(--clay-transition);
-}
-.quick-action-item:active { transform: scale(0.94); box-shadow: var(--clay-shadow-sm); }
-.quick-action-icon {
-  width: 72rpx; height: 72rpx;
-  border-radius: 20rpx;
-  display: flex; align-items: center; justify-content: center;
-  margin-bottom: 12rpx;
-  border: 3rpx solid transparent;
-  box-shadow: inset -2rpx -2rpx 5rpx rgba(255,255,255,0.4), 3rpx 3rpx 8rpx rgba(0,0,0,0.04);
-}
-.qa-emoji { font-size: 34rpx; }
-.qa-icon--assign { background: rgba(59,130,246,0.2); border-color: #BFDBFE; }
-.qa-icon--assessment { background: rgba(16,185,129,0.2); border-color: #A7F3D0; }
-.qa-icon--contact { background: rgba(245,158,11,0.2); border-color: #FDE68A; }
-.qa-icon--income { background: rgba(139,92,246,0.2); border-color: #DDD6FE; }
-.qa-label { font-size: 24rpx; font-weight: 600; color: #475569; }
-
-/* ========================================
-   数据趋势 - claymorphism
-   ======================================== */
-.trend-section { margin-bottom: 32rpx; }
-
-.trend-chart {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-end;
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  padding: 28rpx 24rpx;
-  height: 220rpx;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(59,130,246,0.08);
-}
-
-.trend-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  flex: 1;
-}
-
-.trend-bar {
-  width: 36rpx;
-  background: linear-gradient(180deg, #93C5FD, #60A5FA);
-  border-radius: 8rpx 8rpx 0 0;
-  margin-bottom: 12rpx;
-  transition: height 0.3s;
 }
-
-.trend-bar.trend-active {
-  background: linear-gradient(180deg, #3B82F6, #2563EB);
-}
-
-.trend-day {
-  font-size: 22rpx;
-  color: #64748B;
-}
-
-/* ========================================
-   功能菜单 - claymorphism
-   ======================================== */
-.menu-section { margin-bottom: 32rpx; }
-
-.menu-grid {
-  display: flex;
-  flex-wrap: wrap;
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  padding: 16rpx;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(59,130,246,0.08);
-}
-
-.menu-item {
-  width: 33.33%;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 28rpx 0;
-  position: relative;
-  transition: var(--clay-transition);
-}
-
-.menu-item:active {
-  transform: scale(0.93);
-}
-
-.menu-icon {
-  width: 88rpx;
-  height: 88rpx;
-  border-radius: 24rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 38rpx;
-  margin-bottom: 12rpx;
-  border: 3rpx solid transparent;
-  box-shadow: inset -2rpx -2rpx 5rpx rgba(255,255,255,0.4), 3rpx 3rpx 8rpx rgba(0,0,0,0.04);
-  transition: var(--clay-transition);
-}
-
-.menu-item:active .menu-icon {
-  box-shadow: inset 2rpx 2rpx 6rpx rgba(0,0,0,0.06), 1rpx 1rpx 3rpx rgba(0,0,0,0.03);
-}
-
-.mi-blue { background: rgba(59, 130, 246, 0.2); border-color: #BFDBFE; }
-.mi-gold { background: rgba(245, 158, 11, 0.2); border-color: #FDE68A; }
-.mi-purple { background: rgba(139, 92, 246, 0.2); border-color: #DDD6FE; }
-.mi-green { background: rgba(16, 185, 129, 0.2); border-color: #A7F3D0; }
-.mi-cyan { background: rgba(6, 182, 212, 0.2); border-color: #A5F3FC; }
-.mi-gray { background: rgba(100, 116, 139, 0.2); border-color: #CBD5E1; }
-
-.menu-label {
-  font-size: 24rpx;
-  color: #475569;
-  font-weight: 500;
-}
-
-.menu-badge {
-  position: absolute;
-  top: 16rpx;
-  right: 28rpx;
-  background: #3B82F6;
-  color: #FFFFFF;
-  font-size: 20rpx;
-  padding: 4rpx 12rpx;
-  border-radius: 20rpx;
-  border: 2rpx solid #60A5FA;
-}
-
-.msg-badge {
-  position: absolute;
-  top: 16rpx;
-  right: 28rpx;
-  background: #EF4444;
-  color: #FFFFFF;
-  font-size: 20rpx;
-  min-width: 32rpx;
-  text-align: center;
-  padding: 4rpx 8rpx;
-  border-radius: 20rpx;
-  border: 2rpx solid #FCA5A5;
-}
-
-/* ========================================
-   快捷工具 - claymorphism
-   ======================================== */
-.tools-section { margin-bottom: 48rpx; }
-
-.tools-row {
-  display: flex;
-  justify-content: space-between;
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  padding: 28rpx 16rpx;
-  border: 4rpx solid #BFDBFE;
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(59,130,246,0.08);
-}
-
-.tool-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  flex: 1;
-  transition: var(--clay-transition);
-}
-
-.tool-item:active {
-  transform: scale(0.92);
-}
-
-.tool-icon-wrap {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 20rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-  border: 3rpx solid transparent;
-  box-shadow: inset -2rpx -2rpx 5rpx rgba(255,255,255,0.4), 3rpx 3rpx 8rpx rgba(0,0,0,0.04);
-  transition: var(--clay-transition);
-}
-
-.tool-item:active .tool-icon-wrap {
-  box-shadow: inset 2rpx 2rpx 6rpx rgba(0,0,0,0.06), 1rpx 1rpx 3rpx rgba(0,0,0,0.03);
-}
-
-.tool-icon-text { font-size: 34rpx; }
-
-.tool-create { background: rgba(59, 130, 246, 0.2); border-color: #BFDBFE; }
-.tool-share { background: rgba(16, 185, 129, 0.2); border-color: #A7F3D0; }
-.tool-cal { background: rgba(245, 158, 11, 0.2); border-color: #FDE68A; }
-.tool-report { background: rgba(139, 92, 246, 0.2); border-color: #DDD6FE; }
-
-.tool-label {
-  font-size: 22rpx;
-  color: #475569;
+.deprecated-btn {
+  background: #5B9BD5;
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: bold;
+  padding: 20rpx 80rpx;
+  border-radius: 44rpx;
 }
 </style>

+ 4 - 4
cfc-frontend/pages/teacher/orders.vue

@@ -2,15 +2,15 @@
   <view class="container">
     <view class="stats-section">
       <view class="stat-item">
-        <text class="stat-value">¥{{ stats.orderRevenue }}</text>
+        <text class="stat-value">{{ formatPriceWithSymbol(stats.orderRevenue) }}</text>
         <text class="stat-label">订单收入</text>
       </view>
       <view class="stat-item">
-        <text class="stat-value">¥{{ stats.pendingAmount }}</text>
+        <text class="stat-value">{{ formatPriceWithSymbol(stats.pendingAmount) }}</text>
         <text class="stat-label">待到账</text>
       </view>
       <view class="stat-item">
-        <text class="stat-value">¥{{ stats.actualCommission }}</text>
+        <text class="stat-value">{{ formatPriceWithSymbol(stats.actualCommission) }}</text>
         <text class="stat-label">实际佣金</text>
       </view>
     </view>
@@ -29,7 +29,7 @@
               <text class="package-name">{{ order.packageName }}</text>
             </view>
             <view class="order-amount">
-              <text class="amount">¥{{ order.amount }}</text>
+              <text class="amount">{{ formatPriceWithSymbol(order.amount) }}</text>
             </view>
           </view>
           <view class="order-footer">

+ 1 - 1
cfc-frontend/pages/teacher/teacher-home.vue

@@ -68,7 +68,7 @@
         <text class="stat-label">待处理任务</text>
       </view>
       <view class="stat-card gold">
-        <text class="stat-value">¥{{ stats.todayIncome }}</text>
+        <text class="stat-value">{{ formatPriceWithSymbol(stats.todayIncome) }}</text>
         <text class="stat-label">今日收入</text>
       </view>
       <view class="stat-card purple">

+ 3 - 3
cfc-frontend/pages/teacher/team.vue

@@ -15,7 +15,7 @@
             <text class="member-level">{{ member.level || '高级成长规划师' }}</text>
           </view>
           <view class="member-stats">
-            <text class="performance">¥{{ member.monthlySales || 0 }}</text>
+            <text class="performance">{{ formatPriceWithSymbol(member.monthlySales || 0) }}</text>
             <text class="label">本月业绩</text>
           </view>
         </view>
@@ -31,11 +31,11 @@
       </view>
       <view class="stats-grid">
         <view class="stat-item">
-          <text class="stat-value">¥{{ teamStats.totalSales }}</text>
+          <text class="stat-value">{{ formatPriceWithSymbol(teamStats.totalSales) }}</text>
           <text class="stat-label">团队业绩</text>
         </view>
         <view class="stat-item">
-          <text class="stat-value">¥{{ teamStats.myCommission }}</text>
+          <text class="stat-value">{{ formatPriceWithSymbol(teamStats.myCommission) }}</text>
           <text class="stat-label">我的佣金</text>
         </view>
         <view class="stat-item">

+ 1 - 1
cfc-frontend/pages/vendor/orders/orders.vue

@@ -36,7 +36,7 @@
               <text class="order-meta">买家ID: {{ item.buyerId }}</text>
             </view>
             <view class="order-amount-section">
-              <text class="order-amount">¥{{ item.totalAmount }}</text>
+              <text class="order-amount">{{ formatPriceWithSymbol(item.totalAmount) }}</text>
               <text class="order-qty">x{{ item.quantity }}</text>
             </view>
           </view>

+ 1 - 1
cfc-frontend/pages/vendor/products/products.vue

@@ -30,7 +30,7 @@
             <image class="cover" :src="item.coverImage || '/static/default-product.png'" mode="aspectFill" />
             <view class="card-info">
               <text class="product-name">{{ item.name }}</text>
-              <text class="product-price">¥{{ item.price }}</text>
+              <text class="product-price">{{ formatPriceWithSymbol(item.price) }}</text>
               <text class="product-stock">库存 {{ item.stock || 0 }}</text>
             </view>
           </view>

+ 800 - 229
cfc-frontend/pages/wealth/index.vue

@@ -1,139 +1,519 @@
 <template>
   <view class="container">
+    <!-- ===== 未登录状态 ===== -->
+    <view v-if="!isLoggedIn" class="login-prompt">
+      <view class="prompt-icon">👤</view>
+      <text class="prompt-title">登录浠艾福</text>
+      <text class="prompt-desc">登录后可查看个人资料、积分记录等</text>
+      <button class="login-btn" @click="goLogin">登录 / 注册</button>
+    </view>
+
+    <!-- ===== 已登录状态 ===== -->
+    <template v-else>
+    <!-- 品牌头部 -->
     <PageBanner theme="wealth"
-      tagline="财商培养 · 富足人生"
-      quote="君子爱财,取之有道" />
+      tagline="财富管理 · 价值创造"
+      quote="富足源于利他与坚持" />
 
-    <view class="section" v-if="sectionVisible('today_tasks') && isLoggedIn">
+    <!-- 退出切换按钮(仅家长切换到孩子状态时显示) -->
+    <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
+      <text>🔄 退出切换</text>
+    </view>
+    
+    <view class="user-card">
+      <view class="avatar">👤</view>
+      <view class="user-info">
+        <view class="nickname">{{ nickname || '未设置昵称' }}</view>
+        <view class="role">{{ role === 'parent' ? '家长模式' : (role === 'child' ? '孩子模式' : '成长规划师模式') }}</view>
+        <view class="system-points" v-if="role === 'parent' && children.length > 0">🏅 系统积分: {{ children[0].systemPoints || 0 }}</view>
+      </view>
+      <!-- 切换按钮仅在非切换状态下显示 -->
+      <button class="btn-switch" v-if="!isSwitchedChild" @click="switchMode">切换</button>
+    </view>
+
+    <!-- 行动数据 -->
+    <view class="section">
       <view class="section-header">
-        <text class="section-title">📋 我的任务</text>
-        <text class="section-more" @click="goTasks">全部 ›</text>
+        <text class="section-title">🎯 行动数据</text>
       </view>
-      <view class="task-wrap" v-if="todayTasks.length > 0">
-        <view class="task-item" v-for="task in todayTasks" :key="task.id">
-          <text class="task-name">{{ task.title }}</text>
-          <text class="task-points">+{{ task.points || 0 }}分</text>
+      <view class="wealth-card">
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.taskCount }}</text>
+          <text class="wealth-label">完成任务</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.purchaseCount }}</text>
+          <text class="wealth-label">购买商品</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.activityCount }}</text>
+          <text class="wealth-label">参加活动</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.courseCount }}</text>
+          <text class="wealth-label">学习课程</text>
         </view>
-      </view>
-      <view class="task-empty" v-else>
-        <text class="task-empty-text">今日暂无任务</text>
       </view>
     </view>
 
-    <view class="func-section" v-if="sectionVisible('func_entries')">
-      <view class="func-grid">
-        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
-          <view class="func-icon-wrap">
-            <text class="func-icon">{{ item.icon }}</text>
-          </view>
-          <text class="func-label">{{ item.label }}</text>
+    <!-- 财富数据(推广收益) -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">💰 财富数据</text>
+        <text class="section-more" @click="goToPromotion">全部 ›</text>
+      </view>
+      <view class="wealth-card">
+        <view class="wealth-item">
+          <text class="wealth-value">{{ formatPriceWithSymbol(wealthData.totalEarnings) }}</text>
+          <text class="wealth-label">累计收益</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ formatPriceWithSymbol(wealthData.availableAmount) }}</text>
+          <text class="wealth-label">可提现</text>
         </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ wealthData.referralCount }}</text>
+          <text class="wealth-label">邀请人数</text>
+        </view>
+      </view>
+      <view class="referral-code-bar" @click="copyReferralCode" v-if="wealthData.referralCode">
+        <text class="referral-code-label">邀请码</text>
+        <text class="referral-code-value">{{ wealthData.referralCode }}</text>
+        <text class="referral-code-copy">复制</text>
       </view>
     </view>
 
-    <view class="section" v-if="sectionVisible('hot_activities')">
+    <!-- 成就徽章 -->
+    <view class="section">
       <view class="section-header">
-        <text class="section-title">🔥 热门活动</text>
-        <text class="section-more" @click="goMoreActivities">更多 ›</text>
+        <text class="section-title">🏅 成就徽章</text>
+        <text class="section-more" @click="goAllBadges">全部 ›</text>
       </view>
-      <scroll-view scroll-x class="activity-scroll" show-scrollbar="false">
-        <view class="activity-card" v-for="act in hotActivities" :key="act.id" @click="goActivityDetail(act.id)">
-          <image class="activity-img" :src="act.image || '/static/default-activity.png'" mode="aspectFill" />
-          <view class="activity-info">
-            <text class="activity-name">{{ act.name }}</text>
-            <text class="activity-meta">{{ act.date }} · {{ act.location }}</text>
+      <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>
-      </scroll-view>
+      </view>
     </view>
 
-    <view class="section" v-if="sectionVisible('recommended_products')">
+    <!-- 成长报告 -->
+    <view class="section">
       <view class="section-header">
-        <text class="section-title">🛍️ 推荐商品</text>
-        <text class="section-more" @click="goMoreProducts">更多 ›</text>
+        <text class="section-title">📈 成长报告</text>
+        <text class="section-more" @click="goGrowthReport">查看 ›</text>
       </view>
-      <view class="product-list">
-        <view class="product-card" v-for="prod in recommendedProducts" :key="prod.id" @click="goProductDetail(prod.id)">
-          <image class="product-img" :src="prod.image || '/static/default-product.png'" mode="aspectFill" />
-          <text class="product-name line-clamp-2">{{ prod.name }}</text>
-          <text class="product-price">¥{{ prod.price }}</text>
+      <view class="report-card" @click="goGrowthReport">
+        <view class="report-stat-item">
+          <text class="report-stat">{{ growthReport.monthActiveDays || 0 }}天</text>
+          <text class="report-label">本月活跃</text>
+        </view>
+        <view class="report-divider"></view>
+        <view class="report-stat-item">
+          <text class="report-stat">{{ growthReport.totalTasks || 0 }}</text>
+          <text class="report-label">累计任务</text>
         </view>
+        <view class="report-divider"></view>
+        <view class="report-stat-item">
+          <text class="report-stat">{{ growthReport.completionRate || 0 }}%</text>
+          <text class="report-label">完成率</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="menu-list">
+      <!-- 修改个人资料:所有角色可见 -->
+      <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 class="menu-item" @click="goToPointsLogs">
+        <text>📊 积分记录</text>
+        <text class="arrow">›</text>
+      </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>
+      <button class="menu-item menu-item-btn" open-type="share" @click="inviteFamily" v-if="role === 'parent'">
+        <text>👨‍👩‍👧‍👦 邀请家人</text>
+        <text class="arrow">›</text>
+      </button>
+      <button class="menu-item menu-item-btn" open-type="share" @click="inviteTeacher" v-if="role === 'parent'">
+        <text>👨‍🏫 邀请规划师</text>
+        <text class="arrow">›</text>
+      </button>
+      <!-- 会员中心(浠艾福俱乐部) -->
+      <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="logout">
+        <text>🚪 退出登录</text>
+        <text class="arrow">›</text>
       </view>
     </view>
 
-    <view class="bottom-spacer"></view>
+    <!-- 设置/重置密码弹窗 -->
+    <view class="modal-mask" v-if="showPasswordModal" @click="showPasswordModal = false">
+      <view class="modal" @click.stop>
+        <view class="modal-title">{{ hasPassword ? '重置密码' : '设置密码' }}</view>
+        <view class="form-item" v-if="hasPassword">
+          <input v-model="oldPassword" type="number" password placeholder="输入旧密码" maxlength="6" />
+        </view>
+        <view class="form-item">
+          <input v-model="newPassword" type="number" password placeholder="输入新密码" maxlength="6" />
+        </view>
+        <view class="form-item">
+          <input v-model="confirmPassword" type="number" password placeholder="确认新密码" maxlength="6" />
+        </view>
+        <view class="modal-btns">
+          <button @click="showPasswordModal = false">取消</button>
+          <button class="btn-primary" @click="savePassword">确定</button>
+        </view>
+      </view>
+    </view>
+    </template>
   </view>
 </template>
 
 <script>
 import PageBanner from '../../components/PageBanner.vue'
-import { getVisibleSections, getEnergyOverview, getTodayTasks } from '../../utils/api.js'
+import { setPassword, verifyPassword, getChildren, vendorStatus, getEnergyOverview, getUserActionStats, getReferralCode, getReferralSummary, getCommissionSummary, updateMemberVisibility, getVisibleFamilyMembers } from '../../utils/api.js'
 
 export default {
   components: { PageBanner },
   data() {
     return {
-      isLoggedIn: false,
-      role: null,
-      currentChildId: null,
+      nickname: '',
+      role: 'parent',
+      hasPassword: false,
+      showPasswordModal: false,
+      oldPassword: '',
+      newPassword: '',
+      confirmPassword: '',
+      children: [],
+      isSwitchedChild: false,
+      shareData: null,
+      isVendor: false,
+      showToFamily: 1,
       totalEnergy: null,
-      visibleSections: [],
-      funcList: [
-        { icon: '\u{1F4B0}', label: '财商打卡', needLogin: true, page: '/pages/wealth/checkin' },
-        { icon: '\u{1F4B5}', label: '零花钱管理', needLogin: true, page: '' },
-        { icon: '\u{1F4C8}', label: '推广中心', needLogin: true, page: '/pages/promotion/index' },
-        { icon: '\u{1F3E6}', label: '商城', needLogin: false, page: '/pages/shop/index' },
-        { icon: '\u{1F4CB}', label: '保单管理', needLogin: true, page: '/pages/wealth/insurance-list' }
+      badges: [
+        { 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 }
       ],
-      hotActivities: [
-        { id: 1, name: '小小理财师体验营', date: '周六', location: '浠艾福中心', image: '' },
-        { id: 2, name: '跳蚤市场亲子活动', date: '周日', location: '朝阳公园', image: '' },
-        { id: 3, name: '财商启蒙绘本共读', date: '下周六', location: '线上', image: '' }
-      ],
-      recommendedProducts: [
-        { id: 1, name: '儿童理财启蒙套装', price: 199, image: '' },
-        { id: 2, name: '亲子财商桌游', price: 159, image: '' },
-        { id: 3, name: '零花钱记账本', price: 49, image: '' },
-        { id: 4, name: '少儿财商课程', price: 399, image: '' }
-      ],
-      todayTasks: []
+      actionData: {
+        purchaseCount: 0,
+        activityCount: 0,
+        taskCount: 0,
+        courseCount: 0
+      },
+      wealthData: {
+        totalEarnings: '0.00',
+        availableAmount: '0.00',
+        referralCount: 0,
+        referralCode: ''
+      },
+      growthReport: {
+        monthActiveDays: 0,
+        totalTasks: 0,
+        completionRate: 0
+      }
     }
   },
-  onShow() {
-    var token = uni.getStorageSync('token')
-    this.isLoggedIn = !!token
-    if (this.isLoggedIn) {
-      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
-      this.currentChildId = uni.getStorageSync('currentChildId') || null
+  computed: {
+    isLoggedIn() {
+      return !!uni.getStorageSync('token')
     }
-    this.loadSectionConfig()
-    if (this.isLoggedIn) {
-      this.loadEnergyData()
-      this.loadTodayTasks()
+  },
+  onShareAppMessage() {
+    if (this.shareData) {
+      return {
+        title: this.shareData.title,
+        path: this.shareData.path,
+        imageUrl: '/static/invite-card.png'
+      }
+    }
+  },
+  onShow() {
+    if (!uni.getStorageSync('token')) return
+    const currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
+    if (currentRole === 'teacher') {
+      uni.redirectTo({ url: '/pages/teacher/teacher-profile' })
+      return
     }
+    var userInfo = uni.getStorageSync('userInfo')
+    this.nickname = this.$store.state.nickname || (userInfo && userInfo.nickname) || ''
+    // 使用 currentRole(视图角色),而非 role(实际角色)
+    this.role = currentRole
+    this.hasPassword = !!uni.getStorageSync('passwordSet')
+    // 获取切换标记
+    this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
+    this.loadChildren()
+    this.loadShowToFamily()
+    this.checkVendorStatus()
+    this.loadEnergyData()
+    this.loadBadges()
+    this.loadActionData()
+    this.loadWealthData()
   },
   methods: {
-    async loadSectionConfig() {
-      var baseSections = ['func_entries', 'hot_activities', 'recommended_products']
-      if (!uni.getStorageSync('token')) {
-        this.visibleSections = baseSections
+    goLogin() {
+      uni.navigateTo({ url: '/pages/login/login' })
+    },
+    async loadChildren() {
+      try {
+        const res = await getChildren()
+        this.children = res.data || []
+      } catch (e) {
+        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' })
+    },
+    goToChildren() {
+      uni.navigateTo({ url: '/pages/profile/children' })
+    },
+    goToFamilyMembers() {
+      uni.navigateTo({ url: '/pages/profile/family-members' })
+    },
+    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(() => {
+          this.doSwitchMode()
+        })
+      } else {
+        this.doSwitchMode()
+      }
+    },
+    doSwitchMode() {
+      const newRole = this.role === 'parent' ? 'child' : 'parent'
+      if (newRole === 'child') {
+        if (this.children.length === 0) {
+          this.loadChildren()
+        }
+        if (this.children.length === 0) {
+          uni.showModal({
+            title: '提示',
+            content: '您还没有添加孩子,是否现在去添加?',
+            success: (res) => {
+              if (res.confirm) {
+                uni.navigateTo({ url: '/pages/profile/create-child' })
+              }
+            }
+          })
+          return
+        }
+        if (this.children.length === 1) {
+          this.$store.commit('switchToChild', this.children[0].id)
+          this.role = newRole
+          uni.showToast({ title: '已切换为孩子模式', icon: 'success' })
+          return
+        }
+        uni.showActionSheet({
+          itemList: this.children.map(c => c.nickname),
+          success: (res) => {
+            const selectedChild = this.children[res.tapIndex]
+            this.$store.commit('switchToChild', selectedChild.id)
+            this.role = newRole
+            uni.showToast({ title: `已切换为${selectedChild.nickname}模式`, icon: 'success' })
+          }
+        })
+        return
+      }
+      this.$store.commit('switchBackToParent')
+      this.role = newRole
+      uni.showToast({ title: `已切换为${newRole === 'parent' ? '家长' : '孩子'}模式`, icon: 'success' })
+    },
+    verifyPasswordForSwitch(callback) {
+      uni.showModal({
+        title: '验证密码',
+        content: '请输入家长密码',
+        editable: true,
+        success: async (res) => {
+          if (res.confirm && res.content) {
+            try {
+              const result = await verifyPassword(res.content)
+              if (result.code === 200) {
+                callback && callback()
+              } else {
+                uni.showToast({ title: '密码错误', icon: 'none' })
+              }
+            } catch (e) {
+              uni.showToast({ title: '验证失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    },
+    async savePassword() {
+      if (this.newPassword.length !== 6) {
+        uni.showToast({ title: '请输入6位密码', icon: 'none' })
+        return
+      }
+      if (this.newPassword !== this.confirmPassword) {
+        uni.showToast({ title: '两次密码不一致', icon: 'none' })
+        return
+      }
+      if (this.hasPassword && this.oldPassword.length !== 6) {
+        uni.showToast({ title: '请输入旧密码', icon: 'none' })
         return
       }
-      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
       try {
-        var res = await getVisibleSections({ pageKey: 'wealth', role: role })
-        if (res.code === 200 && res.data) {
-          var keys = res.data.map(function(s) { return s.sectionKey })
-          var merged = baseSections.slice()
-          keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
-          this.visibleSections = merged
+        if (this.hasPassword) {
+          const verifyRes = await verifyPassword(this.oldPassword)
+          if (verifyRes.code !== 200) {
+            uni.showToast({ title: '旧密码错误', icon: 'none' })
+            return
+          }
+        }
+        await setPassword(this.newPassword)
+        uni.setStorageSync('passwordSet', true)
+        this.hasPassword = true
+        uni.showToast({ title: '密码设置成功', icon: 'success' })
+        this.showPasswordModal = false
+        this.oldPassword = ''
+        this.newPassword = ''
+        this.confirmPassword = ''
+      } catch (e) {
+        console.error('设置密码失败', e)
+      }
+    },
+    logout() {
+      uni.showModal({
+        title: '确认退出',
+        content: '确定要退出登录吗?',
+        success: (res) => {
+          if (res.confirm) {
+            this.$store.commit('logout')
+            // 退出后返回首页 Tab(预登录状态,5 个底签可见)
+            uni.reLaunch({ url: '/pages/index/index' })
+          }
+        }
+      })
+    },
+    inviteFamily() {
+      const userInfo = uni.getStorageSync('userInfo')
+      const familyId = uni.getStorageSync('familyId')
+      if (!familyId) {
+        uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
+        return
+      }
+      this.inviteFamilyGenerate(familyId, userInfo)
+    },
+    async inviteFamilyGenerate(familyId, userInfo) {
+      try {
+        const { generateInviteCard } = require('../../utils/api.js')
+        const res = await generateInviteCard('family', familyId)
+        const code = res.data.code
+        this.shareData = {
+          title: (userInfo && userInfo.nickname ? userInfo.nickname : '家人') + ' 邀请你加入家庭',
+          path: '/pages/login/login?invite_code=' + code
+        }
+        uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
+      } catch (e) {
+        uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
+      }
+    },
+    inviteTeacher() {
+      const userInfo = uni.getStorageSync('userInfo')
+      const familyId = uni.getStorageSync('familyId')
+      if (!familyId) {
+        uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
+        return
+      }
+      this.inviteTeacherGenerate(familyId, userInfo)
+    },
+    async inviteTeacherGenerate(familyId, userInfo) {
+      try {
+        const { generateInviteCard } = require('../../utils/api.js')
+        const res = await generateInviteCard('guide', familyId)
+        const code = res.data.code
+        this.shareData = {
+          title: (userInfo && userInfo.nickname ? userInfo.nickname : '家长') + ' 邀请你成为家庭成长规划师',
+          path: '/pages/login/login?invite_code=' + code
         }
+        uni.showToast({ title: '点击右上角转发给规划师', icon: 'none' })
       } catch (e) {
-        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+        uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
       }
     },
+    goToMembership() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    goToVendorCenter() {
+      uni.navigateTo({ url: '/pages/vendor/center' })
+    },
+    goToPromotion() {
+      uni.navigateTo({ url: '/pages/promotion/index' })
+    },
     async loadEnergyData() {
-      var childId = this.currentChildId
+      var childId = uni.getStorageSync('currentChildId') || null
+      if (!childId) {
+        // Try to get first child
+        if (this.children.length > 0) {
+          childId = this.children[0].id
+        }
+      }
       if (!childId) return
       try {
         var res = await getEnergyOverview(childId)
@@ -142,50 +522,102 @@ export default {
         }
       } catch (e) { console.log('获取能量概览失败', e) }
     },
-    async loadTodayTasks() {
-      var childId = this.currentChildId
-      if (!childId) return
+    loadBadges() {
+      // Unlock based on stored stats
+      var streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
+      var totalPoints = parseInt(uni.getStorageSync('totalPoints') || 0)
+      if (streakDays >= 7) this.badges[0].unlocked = true
+      if (totalPoints >= 1000) this.badges[1].unlocked = true
+
+      // Growth report mock
+      this.growthReport.monthActiveDays = streakDays > 0 ? Math.min(streakDays, 30) : 0
+      this.growthReport.totalTasks = parseInt(uni.getStorageSync('completedTasks') || 0)
+      this.growthReport.completionRate = 85
+    },
+    async loadActionData() {
       try {
-        var res = await getTodayTasks(childId)
-        if (res && res.data) {
-          this.todayTasks = res.data.slice(0, 5)
+        const res = await getUserActionStats()
+        if (res.code === 200 && res.data) {
+          this.actionData.purchaseCount = res.data.purchaseCount || 0
+          this.actionData.activityCount = res.data.activityCount || 0
+          this.actionData.taskCount = res.data.taskCount || 0
+          this.actionData.courseCount = res.data.courseCount || 0
         }
-      } catch (e) { console.log('获取今日任务失败', e) }
-    },
-    sectionVisible(key) {
-      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
-    },
-    onFuncClick(item) {
-      if (item.needLogin && !this.isLoggedIn) {
-        uni.showModal({
-          title: '提示',
-          content: '请先登录后使用此功能',
-          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
-        })
-        return
+      } catch (e) {
+        // Fallback to local computed stats
+        this.actionData.taskCount = parseInt(uni.getStorageSync('completedTasks') || 0)
+        console.log('获取行动统计失败,使用本地数据', e)
       }
-      if (item.page) {
-        var nativeTabs = ['/pages/tasks/tasks', '/pages/index/index', '/pages/body/index', '/pages/mind/index', '/pages/action/index', '/pages/wisdom/index', '/pages/profile/profile']
-        if (nativeTabs.indexOf(item.page) !== -1) {
-          uni.switchTab({ url: item.page })
-        } else {
-          uni.navigateTo({ url: item.page })
+    },
+    async loadWealthData() {
+      try {
+        const codeRes = await getReferralCode()
+        if (codeRes.data) {
+          this.wealthData.referralCode = codeRes.data.referralCode || codeRes.data.code || ''
         }
-      }
+      } catch (e) { console.log('获取邀请码失败', e) }
+      try {
+        const summaryRes = await getReferralSummary()
+        if (summaryRes.data) {
+          this.wealthData.totalEarnings = summaryRes.data.totalEarnings || '0.00'
+          this.wealthData.referralCount = summaryRes.data.referredCount || summaryRes.data.totalCount || 0
+        }
+      } catch (e) { console.log('获取邀请统计失败', e) }
+      try {
+        const comRes = await getCommissionSummary()
+        if (comRes.data) {
+          this.wealthData.availableAmount = comRes.data.availableAmount || '0.00'
+        }
+      } catch (e) { console.log('获取佣金总览失败', e) }
     },
-    goMoreActivities() {},
-    goActivityDetail(id) {},
-    goMoreProducts() {},
-    goProductDetail(id) {
-      var token = uni.getStorageSync('token')
-      if (token) {
-        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
-      } else {
-        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/wealth/index') })
+    copyReferralCode() {
+      if (!this.wealthData.referralCode) return
+      uni.setClipboardData({
+        data: this.wealthData.referralCode,
+        success: () => { uni.showToast({ title: '邀请码已复制', icon: 'success' }) }
+      })
+    },
+    goAllBadges() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    goGrowthReport() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    async checkVendorStatus() {
+      try {
+        const res = await vendorStatus()
+        this.isVendor = res.data && res.data.vendorStatus === 'approved'
+      } catch (e) {
+        this.isVendor = false
       }
     },
-    goTasks() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
-    goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
+    exitSwitch() {
+      if (!this.isSwitchedChild) return
+      uni.showModal({
+        title: '退出切换',
+        content: '请输入家长密码以确认退出',
+        editable: true,
+        success: async (res) => {
+          if (res.confirm && res.content) {
+            try {
+              const verifyResult = await verifyPassword(res.content)
+              if (verifyResult.code === 200) {
+                this.$store.commit('switchBackToParent')
+                this.isSwitchedChild = false
+                this.role = 'parent'
+                uni.showToast({ title: '已退出切换', icon: 'success' })
+                // 刷新首页
+                uni.reLaunch({ url: '/pages/index/index' })
+              } else {
+                uni.showToast({ title: '密码错误', icon: 'none' })
+              }
+            } catch (e) {
+              uni.showToast({ title: '验证失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    }
   }
 }
 </script>
@@ -197,48 +629,9 @@ export default {
   padding-bottom: 120rpx;
 }
 
-.func-section {
-  margin: -40rpx 20rpx 0;
-  position: relative;
-  z-index: 2;
-}
-.func-grid {
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 30rpx 16rpx;
-  display: flex;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-}
-.func-item {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-.func-item:active {
-  opacity: 0.7;
-}
-.func-icon-wrap {
-  width: 88rpx;
-  height: 88rpx;
-  border-radius: 50%;
-  background: #E3F2FD;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-}
-.func-icon {
-  font-size: 40rpx;
-}
-.func-label {
-  font-size: 24rpx;
-  color: #333;
-  font-weight: 500;
-}
-
+/* ===== 通用区块 ===== */
 .section {
-  margin: 20rpx 20rpx;
+  margin: 20rpx 30rpx;
 }
 .section-header {
   display: flex;
@@ -256,112 +649,290 @@ export default {
   color: #999;
 }
 
-.activity-scroll {
-  white-space: nowrap;
-}
-.activity-card {
-  display: inline-block;
-  width: 280rpx;
-  margin-right: 20rpx;
+/* ===== 推广邀请码 ===== */
+.referral-code-bar {
+  display: flex;
+  align-items: center;
   background: #fff;
   border-radius: 16rpx;
-  overflow: hidden;
+  padding: 20rpx 24rpx;
+  margin-top: 16rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.activity-img {
-  width: 280rpx;
-  height: 180rpx;
-  background: #f0f0f0;
+.referral-code-label {
+  font-size: 26rpx;
+  color: #999;
+  margin-right: 16rpx;
+}
+.referral-code-value {
+  flex: 1;
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #F97316;
+  letter-spacing: 4rpx;
 }
-.activity-info {
-  padding: 16rpx;
+.referral-code-copy {
+  font-size: 24rpx;
+  color: #5B9BD5;
+  padding: 4rpx 16rpx;
+  border: 1rpx solid #5B9BD5;
+  border-radius: 8rpx;
 }
-.activity-name {
-  font-size: 26rpx;
+
+/* ===== 行动数据(推广收益) ===== */
+.wealth-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx 20rpx;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.wealth-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.wealth-value {
+  font-size: 40rpx;
   font-weight: bold;
-  color: #333;
-  display: block;
+  color: #F97316;
 }
-.activity-meta {
-  font-size: 22rpx;
+.wealth-label {
+  font-size: 24rpx;
   color: #999;
   margin-top: 8rpx;
-  display: block;
+}
+.wealth-divider {
+  width: 1rpx;
+  height: 60rpx;
+  background: #f0f0f0;
 }
 
-.product-list {
+/* ===== 成就徽章 ===== */
+.badge-grid {
   display: flex;
-  flex-wrap: wrap;
-  gap: 20rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.badge-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.badge-icon-wrap {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8rpx;
+}
+.badge-icon-wrap.unlocked {
+  background: linear-gradient(135deg, #FBBF24, #F97316);
 }
-.product-card {
-  width: calc(50% - 10rpx);
+.badge-icon-wrap.locked {
+  background: #f0f0f0;
+  opacity: 0.5;
+}
+.badge-icon {
+  font-size: 36rpx;
+}
+.badge-name {
+  font-size: 22rpx;
+  color: #666;
+}
+
+/* ===== 成长报告 ===== */
+.report-card {
   background: #fff;
-  border-radius: 16rpx;
-  overflow: hidden;
+  border-radius: 20rpx;
+  padding: 30rpx 20rpx;
+  display: flex;
+  align-items: center;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.product-img {
-  width: 100%;
-  height: 240rpx;
+.report-stat-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.report-stat {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #5B9BD5;
+}
+.report-label {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 6rpx;
+}
+.report-divider {
+  width: 1rpx;
+  height: 50rpx;
   background: #f0f0f0;
 }
-.product-name {
-  font-size: 26rpx;
-  color: #333;
-  padding: 12rpx 16rpx 0;
+.user-card {
+  background: linear-gradient(135deg, #5B9BD5 0%, #3A7CC4 100%);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  display: flex;
+  align-items: center;
+  color: #fff;
 }
-.product-price {
-  font-size: 28rpx;
-  color: #F97316;
+.avatar {
+  font-size: 80rpx;
+  margin-right: 30rpx;
+}
+.user-info {
+  flex: 1;
+}
+.nickname {
+  font-size: 36rpx;
   font-weight: bold;
-  padding: 8rpx 16rpx 16rpx;
-  display: block;
 }
-.line-clamp-2 {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
+.role {
+  font-size: 24rpx;
+  opacity: 0.8;
 }
-
-.task-wrap {
+.system-points {
+  font-size: 22rpx;
+  color: #D6EAF8;
+  margin-top: 8rpx;
+  font-weight: 500;
+}
+.btn-switch {
+  background: rgba(255, 255, 255, 0.2);
+  color: #fff;
+  font-size: 24rpx;
+  padding: 10rpx 20rpx;
+  border-radius: 20rpx;
+}
+.menu-list {
+  margin-top: 30rpx;
   background: #fff;
   border-radius: 16rpx;
-  overflow: hidden;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.task-item {
+.menu-item {
+  padding: 30rpx;
+  border-bottom: 1rpx solid #f0f0f0;
   display: flex;
   justify-content: space-between;
-  padding: 28rpx 24rpx;
-  border-bottom: 1rpx solid #f0f0f0;
+  font-size: 28rpx;
 }
-.task-item:last-child {
+.menu-item:last-child {
   border-bottom: none;
 }
-.task-name {
-  font-size: 26rpx;
+.arrow {
+  color: #ccc;
+  font-size: 32rpx;
+}
+.menu-item-btn {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding: 28rpx 24rpx;
+  border: none;
+  background: transparent;
+  font-size: 28rpx;
   color: #333;
 }
-.task-points {
-  font-size: 24rpx;
-  color: #F97316;
+.menu-item-btn::after { border: none; }
+.menu-item-switch {
+  align-items: center;
+}
+.modal-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
-.task-empty {
+.modal {
   background: #fff;
-  border-radius: 16rpx;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  width: 80%;
+}
+.modal-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-bottom: 30rpx;
+  text-align: center;
+}
+.form-item {
+  margin-bottom: 20rpx;
+}
+.form-item input {
+  border: 1rpx solid #ddd;
+  border-radius: 10rpx;
+  padding: 20rpx;
+  font-size: 28rpx;
+}
+.modal-btns {
+  display: flex;
+  gap: 20rpx;
+  margin-top: 30rpx;
+}
+.modal-btns button {
+  flex: 1;
+}
+
+/* ===== 未登录提示 ===== */
+.login-prompt {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-height: 80vh;
   padding: 60rpx;
+}
+.prompt-icon {
+  font-size: 120rpx;
+  margin-bottom: 30rpx;
+  width: 160rpx;
+  height: 160rpx;
+  line-height: 160rpx;
   text-align: center;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  background: #f5f5f5;
+  border-radius: 50%;
 }
-.task-empty-text {
+.prompt-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+.prompt-desc {
   font-size: 26rpx;
   color: #999;
+  text-align: center;
+  margin-bottom: 40rpx;
+}
+.login-prompt .login-btn {
+  width: 60%;
+  height: 80rpx;
+  line-height: 80rpx;
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: bold;
+  border-radius: 40rpx;
+  text-align: center;
+  border: none;
 }
-
-.bottom-spacer {
-  height: 20rpx;
+.login-prompt .login-btn::after {
+  border: none;
 }
-</style>
+</style>

+ 1 - 1
cfc-frontend/pages/wealth/insurance-list.vue

@@ -39,7 +39,7 @@
         </view>
         <view class="policy-row">
           <text class="policy-label">保费</text>
-          <text class="policy-value orange">{{ '¥' + (item.premium || '0.00') }}</text>
+          <text class="policy-value orange">{{ formatPriceWithSymbol(item.premium || 0) }}</text>
         </view>
         <view class="policy-row">
           <text class="policy-label">到期日</text>

+ 1 - 1
cfc-frontend/pages/wisdom/index.vue

@@ -56,7 +56,7 @@
         <view class="product-card" v-for="prod in recommendedProducts" :key="prod.id" @click="goProductDetail(prod.id)">
           <image class="product-img" :src="prod.image || '/static/default-product.png'" mode="aspectFill" />
           <text class="product-name line-clamp-2">{{ prod.name }}</text>
-          <text class="product-price">¥{{ prod.price }}</text>
+          <text class="product-price">{{ formatPriceWithSymbol(prod.price) }}</text>
         </view>
       </view>
     </view>

+ 46 - 0
cfc-frontend/utils/format.js

@@ -0,0 +1,46 @@
+/**
+ * 金额格式化工具
+ * 后端统一使用整型(分)存储金额,前端在此转换展示
+ */
+
+/**
+ * 分 → 元,保留2位小数
+ * @param {number|null} cents 金额(分)
+ * @returns {string|null} "12.50" 或 null
+ */
+export function formatPrice(cents) {
+  if (cents == null || cents === undefined) return null
+  return (cents / 100).toFixed(2)
+}
+
+/**
+ * 分 → 元,带 ¥ 前缀
+ * @param {number|null} cents 金额(分)
+ * @returns {string} "¥12.50" 或 "登录查看"
+ */
+export function formatPriceWithSymbol(cents) {
+  if (cents == null || cents === undefined) return '登录查看'
+  return '¥' + (cents / 100).toFixed(2)
+}
+
+/**
+ * 千分比 → 百分比字符串
+ * @param {number|null} permillage 千分比(235 = 23.5%)
+ * @returns {string} "23.5%" 或 "--"
+ */
+export function formatRate(permillage) {
+  if (permillage == null || permillage === undefined) return '--'
+  return (permillage / 10) + '%'
+}
+
+/**
+ * 百分制评分 → 展示值
+ * @param {number|null} value 百分制(450 = 4.5分)
+ * @param {number} fixed 小数位数
+ * @returns {string} "4.5" 或 "--"
+ */
+export function formatScore(value, fixed) {
+  if (fixed === undefined) fixed = 1
+  if (value == null || value === undefined) return '--'
+  return (value / 100).toFixed(fixed)
+}