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

chore(hub): 合并并发工作区改动(wuxing-sandbox配色/成长主图/健康成员选择/家庭关系等)

- wuxing-sandbox: 成员切换图标配色恢复 v1.2 浅蓝徽章 #8FC5E8 系
- growth-main: 主图布局精简
- diet/recommendation / health(MemberPickerPopup,report-list) / profile(family-members,profile,index): 并发修复
- tools/xaf_agent_hub: lead_backlog 登记 CFC-EN-001 开发已提交+核验已派、tasks_backlog/.model_state 状态同步
Agnes Agent 1 неделя назад
Родитель
Сommit
59e8f76740

+ 31 - 32
cfc-frontend/components/wuxing-sandbox-helpers/render.js

@@ -30,9 +30,8 @@ var DECOR = {
   action: { r: 76,  g: 175, b: 80  }   // 行·木  #4CAF50
 }
 
-/** 暖中性(星廓 / 太极),与填充 #FCE7CE 同色系 */
+/** 星廓描边(浅蓝,深色容器上可见) */
 var SAND = { r: 143, g: 197, b: 232 }  // 浅蓝描边
-var INK  = { r: 74,  g: 55,  b: 40  }  // 暖褐墨(太极深面 / 深色数字)
 
 function rgba(c, a) {
   return 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + a + ')'
@@ -207,68 +206,68 @@ export function renderStar(ctx, w, h, ts, options) {
 export function drawTaiji(ctx, cx, cy, r, familyEnergy, personalEnergy) {
   ctx.save()
 
-  // 1. 底圆(暖白)
+  // 1. 外圆背景(透明,仅保留极淡边框)
   ctx.beginPath()
   ctx.arc(cx, cy, r, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(255,255,255,0.94)'
-  ctx.fill()
-  ctx.strokeStyle = rgba(SAND, 0.45)
-  ctx.lineWidth = 1
+  ctx.strokeStyle = 'rgba(0,0,0,0.08)'
+  ctx.lineWidth = 0.5
   ctx.stroke()
 
-  // 2. 下半深面(暖褐墨)
+  // 2. 左半边(阴,半透明)
   ctx.beginPath()
   ctx.moveTo(cx, cy)
-  ctx.arc(cx, cy, r, 0, Math.PI)
+  ctx.arc(cx, cy, r, Math.PI / 2, -Math.PI / 2)
   ctx.closePath()
-  ctx.fillStyle = rgba(INK, 0.92)
+  ctx.fillStyle = 'rgba(120,120,120,0.2)'
   ctx.fill()
 
-  // 3. 上半小鱼(深面在浅区里的"头")
+  // 3. 上半小圆左半弧(半透明浅色,在暗半圆中)
   ctx.beginPath()
-  ctx.arc(cx, cy - r / 2, r / 2, 0, Math.PI * 2)
+  ctx.moveTo(cx, cy - r)
+  ctx.arc(cx, cy - r / 2, r / 2, Math.PI / 2, -Math.PI / 2)
   ctx.closePath()
-  ctx.fillStyle = rgba(INK, 0.92)
+  ctx.fillStyle = 'rgba(255,255,255,0.35)'
   ctx.fill()
 
-  // 4. 下半小鱼(浅面在深区里的"头")
+  // 4. 下半小圆右半弧(半透明深色,在浅半圆中)
   ctx.beginPath()
-  ctx.arc(cx, cy + r / 2, r / 2, 0, Math.PI * 2)
+  ctx.moveTo(cx, cy + r)
+  ctx.arc(cx, cy + r / 2, r / 2, -Math.PI / 2, Math.PI / 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(255,255,255,0.94)'
+  ctx.fillStyle = 'rgba(120,120,120,0.2)'
   ctx.fill()
 
   // 鱼眼半径(够大,容纳数值文字)
-  var eyeR = r * 0.19
+  var eyeR = r * 0.16
 
-  // 5. 上鱼眼(深面上的亮点)— 个人能量
+  // 5. 阳鱼眼(浅区域中的暗点)
   ctx.beginPath()
-  ctx.arc(cx, cy - r / 2, eyeR, 0, Math.PI * 2)
+  ctx.arc(cx, cy + r / 2, eyeR, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(255,255,255,0.92)'
+  ctx.fillStyle = 'rgba(100,100,100,0.4)'
   ctx.fill()
 
-  // 6. 下鱼眼(浅面上的暗点)— 家庭能量
+  // 6. 阴鱼眼(暗区域中的亮点)
   ctx.beginPath()
-  ctx.arc(cx, cy + r / 2, eyeR, 0, Math.PI * 2)
+  ctx.arc(cx, cy - r / 2, eyeR, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = rgba(INK, 0.92)
+  ctx.fillStyle = 'rgba(255,255,255,0.5)'
   ctx.fill()
 
-  // 7. 上眼文字 — 个人能量(浅底深墨字)
-  var personalVal = (personalEnergy != null && !isNaN(personalEnergy)) ? Math.round(personalEnergy) : '--'
-  var eyeFontSize = Math.round(eyeR * 1.05)
+  // 7. 阳眼文字 — 家庭能量
+  var familyVal = (familyEnergy != null && !isNaN(familyEnergy)) ? Math.round(familyEnergy) : '--'
+  var eyeFontSize = Math.round(eyeR * 1.2)
   ctx.font = 'bold ' + eyeFontSize + 'px sans-serif'
   ctx.textAlign = 'center'
   ctx.textBaseline = 'middle'
-  ctx.fillStyle = rgba(INK, 0.95)
-  ctx.fillText(personalVal + '', cx, cy - r / 2)
-
-  // 8. 下眼文字 — 家庭能量(深底白字)
-  var familyVal = (familyEnergy != null && !isNaN(familyEnergy)) ? Math.round(familyEnergy) : '--'
-  ctx.fillStyle = 'rgba(255,255,255,0.96)'
+  ctx.fillStyle = 'rgba(255,255,255,0.85)'
   ctx.fillText(familyVal + '', cx, cy + r / 2)
 
+  // 8. 阴眼文字 — 个人能量
+  var personalVal = (personalEnergy != null && !isNaN(personalEnergy)) ? Math.round(personalEnergy) : '--'
+  ctx.fillStyle = 'rgba(80,80,80,0.7)'
+  ctx.fillText(personalVal + '', cx, cy - r / 2)
+
   ctx.restore()
 }

+ 9 - 8
cfc-frontend/components/wuxing-sandbox.vue

@@ -175,11 +175,12 @@ var DIM_ICON_URIS = {
   action: svgUri(ICON_PATHS.leaf,     '#4CAF50')  // 行·木  $mat-green
 }
 
-// UI 图标(品牌深橙,对白底 / 品牌浅底均 ≥3:1;$brand-primary-deep #C2410C)
+// UI 图标(成员切换 = 浅蓝,呼应原始 v1.2 成员徽章 #8FC5E8 系,深色容器上高对比可见;
+// 下拉选择器 check / 预览 pentagon 保留品牌深橙 #C2410C,与其橙色高亮态一致)
 var UI_ICON_URIS = {
-  person:   svgUri(ICON_PATHS.person,   '#C2410C'),
-  family:   svgUri(ICON_PATHS.family,   '#C2410C'),
-  swap:     svgUri(ICON_PATHS.swap,     '#C2410C'),
+  person:   svgUri(ICON_PATHS.person,   '#8FC5E8'),
+  family:   svgUri(ICON_PATHS.family,   '#8FC5E8'),
+  swap:     svgUri(ICON_PATHS.swap,     '#8FC5E8'),
   check:    svgUri(ICON_PATHS.check,    '#C2410C'),
   pentagon: svgUri(ICON_PATHS.pentagon, '#C2410C')
 }
@@ -814,7 +815,7 @@ export default {
 .sandbox-title { font-size: calc(26rpx * var(--font-scale, 1)); font-weight: bold; color: #8FC5E8; margin-left: 8rpx; }
 .sandbox-logo { width: 32rpx; height: 32rpx; flex-shrink: 0; }
 
-/* ---- 右上角切换(触控 ≥44px = 88rpx) ---- */
+/* ---- 右上角切换(触控 ≥44px = 88rpx;配色恢复原始 v1.2 浅蓝徽章 #8FC5E8 系) ---- */
 .sandbox-switch {
   display: flex;
   align-items: center;
@@ -824,9 +825,9 @@ export default {
   margin-left: auto;
   flex-shrink: 0;
   border-radius: 44rpx;
-  color: $brand-primary-deep;
-  background: $brand-primary-soft;
-  border: 1rpx solid rgba($brand-primary, 0.18);
+  color: rgba(143,197,232,0.7);
+  background: rgba(143,197,232,0.1);
+  border: 1rpx solid rgba(143,197,232,0.25);
   transition: opacity $duration-fast;
 }
 .sandbox-switch:active { opacity: 0.8; }

+ 3 - 0
cfc-frontend/pages/diet/recommendation.vue

@@ -698,6 +698,9 @@ export default {
 .content {
   flex: 1;
   padding: $uni-space-page;
+  box-sizing: border-box;
+  width: 100%;
+  overflow: hidden;
 }
 .empty-state { display: flex; flex-direction: column; align-items: center; padding: 120rpx 40rpx; }
 .empty-icon-wrap {

+ 22 - 98
cfc-frontend/pages/growth-main/index.vue

@@ -200,6 +200,14 @@
                 {{ item.done ? '✔' : (item.actionText || '去记录') }}
               </text>
             </view>
+            <!-- 更多任务:作为卡片出现在记账打卡之后,点击进入任务列表 -->
+            <view class="progress-tile progress-tile-more" @click="goToTasks">
+              <view class="progress-tile-icon-wrap">
+                <text class="progress-tile-icon">📋</text>
+              </view>
+              <text class="progress-tile-label">更多任务</text>
+              <text class="progress-tile-status status-more">去查看 ›</text>
+            </view>
           </view>
           <view class="streak-footer" v-if="streakDays > 0">
             <text class="streak-badge">🔥 连续打卡 {{ streakDays }} 天</text>
@@ -221,28 +229,6 @@
         </view>
       </view>
 
-      <!-- 任务中心(内联) -->
-      <view class="section" v-if="recentTasks.length > 0">
-        <view class="task-preview-card">
-          <view class="task-preview-header">
-            <text class="task-preview-title">📋 今日任务</text>
-            <text class="task-preview-more" @click="goToTasks">更多 ›</text>
-          </view>
-          <view class="task-preview-list">
-            <view class="task-preview-item" v-for="(task, idx) in recentTasks" :key="getTaskKey(task, idx)" @click="goToTasks">
-              <view class="task-preview-left">
-                <text class="task-preview-name">{{ task.title }}</text>
-                <view class="task-preview-meta">
-                  <text class="task-preview-points">+{{ task.points }}CF</text>
-                  <text class="task-preview-deadline" v-if="task.deadline">⏱ {{ formatTaskTime(task.deadline) }}</text>
-                </view>
-              </view>
-              <text class="task-preview-arrow">›</text>
-            </view>
-          </view>
-        </view>
-      </view>
-
       <DimensionActivities :isLoggedIn="true" @activityClick="goActivityDetail" @moreActivities="goMoreActivities" style="margin-bottom:60rpx" />
 
       <!-- 推荐阅读 -->
@@ -280,7 +266,7 @@ import shareMixin from '../../components/share-mixin.js'
 export default {
   mixins: [fontScaleMixin, shareMixin],
   components: { LoginGuideCard, DimensionActivities, DimensionProducts, ArticleFeed },
-  data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], recentTasks: [], todayTaskList: [], streakDays: 0, activeChallenges: 0, completeness: 0,
+  data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], todayTaskList: [], streakDays: 0, activeChallenges: 0, completeness: 0,
       healthArticles: [],
       growthArticles: [],
       isLoggedIn: false } },
@@ -326,14 +312,7 @@ export default {
       }
       if (url) uni.navigateTo({ url: url })
     },
-    getTaskKey: function(task, idx) { return (task && task.id) ? ('t' + task.id) : ('tk' + idx) },
-    formatTaskTime: function(dateVal) {
-      if (!dateVal) return ''
-      var d = parseDate(dateVal)
-      if (!d) return ''
-      return (d.getMonth() + 1) + '/' + d.getDate() + ' ' + this.pad(d.getHours()) + ':' + this.pad(d.getMinutes())
-    },
-    /** 当日任务统一取数入口:一次请求供"今日成长打卡联动 taskId"与"今日任务预览"共用,避免同页重复调用 /api/tasks/today */
+    /** 当日任务统一取数入口:一次请求供"今日成长打卡联动 taskId"使用,避免同页重复调用 /api/tasks/today */
     async fetchTodayTasks() {
       var self = this
       try {
@@ -351,16 +330,9 @@ export default {
       }
       return null
     },
-    async loadRecentTasks() {
-      var self = this
-      try {
-        if (!self.todayTaskList) await self.fetchTodayTasks()
-        self.recentTasks = (self.todayTaskList || []).slice(0, 3)
-      } catch(e) {}
-    },
     goActivityDetail: function(act) { uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id }) },
     goMoreActivities: function() { uni.navigateTo({ url: '/pages/activity/index' }) },
-    goToTasks: function() { uni.navigateTo({ url: '/pages/child/tasks' }) },
+    goToTasks: function() { uni.navigateTo({ url: '/pages/tasks/tasks' }) },
     handleGuestFeatureTap: function() { uni.showToast({ title: '登录后即可体验该功能', icon: 'none' }) },
     goToFoods: function() {
       var mid = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || ''
@@ -407,7 +379,6 @@ export default {
     async loadAllData() {
       var self = this
       try { await self.loadProgressItems() } catch(e) {}
-      try { await self.loadRecentTasks() } catch(e) {}
       try { await self.loadGrowthRecommendations() } catch(e) {}
       try { await self.loadMilestones() } catch(e) {}
     },
@@ -937,6 +908,17 @@ $c-danger-ink:       $status-danger-deep;
   background: $facade-fill-2;
   color: $facade-text-muted;
 }
+.progress-tile-more {
+  background: $facade-brand-tint;
+  border: 2rpx solid rgba($brand-primary, 0.28);
+}
+.progress-tile-more:active { opacity: 0.82; transform: scale(0.96); }
+.progress-tile-more .progress-tile-icon-wrap { background: rgba($brand-primary, 0.12); }
+.progress-tile-more .progress-tile-label { color: $brand-primary; }
+.status-more {
+  background: $brand-primary;
+  color: $uni-surface;
+}
 .streak-footer {
   margin-top: 18rpx;
   padding-top: 16rpx;
@@ -992,64 +974,6 @@ $c-danger-ink:       $status-danger-deep;
 }
 .action-arrow { font-size: calc(32rpx * var(--font-scale, 1)); color: $facade-text-ghost; flex-shrink: 0; margin-left: 12rpx; }
 
-// ---- 任务预览 ----
-.task-preview-card {
-  background: $app-surface;
-  border-radius: 24rpx;
-  padding: 24rpx 28rpx;
-  box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
-}
-.task-preview-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.task-preview-title {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $facade-text-strong;
-}
-.task-preview-more {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $brand-primary;
-}
-.task-preview-list { display: flex; flex-direction: column; }
-.task-preview-item {
-  display: flex;
-  align-items: center;
-  padding: 16rpx 0;
-  border-bottom: 1rpx solid $facade-fill-2;
-}
-.task-preview-item:last-child { border-bottom: none; }
-.task-preview-left { flex: 1; min-width: 0; }
-.task-preview-name {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $facade-text-strong;
-  display: block;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-.task-preview-meta {
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
-  margin-top: 6rpx;
-}
-.task-preview-points {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  font-weight: 700;
-}
-.task-preview-deadline { font-size: calc(20rpx * var(--font-scale, 1)); color: $facade-text-muted; }
-.task-preview-arrow {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $facade-text-ghost;
-  flex-shrink: 0;
-  margin-left: 12rpx;
-}
-
 // ---- 通用 section-header ----
 .section-header {
   display: flex;

+ 5 - 2
cfc-frontend/pages/health/components/MemberPickerPopup.vue

@@ -2,11 +2,14 @@
   <!--
     统一成员选择弹出控件(底部弹出面板)
 
+    位置:pages/health/components/ —— 位于 pages/health 分包内,
+    仅供该分包页面引用;主包/其他分包页面不可引用(分包组件不支持跨分包/主包引用)。
+    当前使用方:pages/health/report-list.vue(「调整报告归属」面板)。
+
     蓝本:components/wuxing-sandbox.vue 的 member-picker-mask / member-picker-panel
     (首页能量沙盘「切换视角」成员选择弹出页)。
 
-    用途:替换所有「需要弹出选择成员」的自定义底部弹出场景,
-    例如 pages/health/report-list.vue 的「调整报告归属」面板。
+    用途:替换「需要弹出选择成员」的自定义底部弹出场景。
 
     约定:
       · options 由调用方构建,每项形如 { key, label, memberId, active, member, ...ext }

+ 1 - 1
cfc-frontend/pages/health/report-list.vue

@@ -123,7 +123,7 @@
 import { BrandColors } from '../../utils/brand-colors.js'
 import { parseDate } from '../../utils/format.js'
 import MemberSelector from '@/components/MemberSelector.vue'
-import MemberPickerPopup from '@/components/MemberPickerPopup.vue'
+import MemberPickerPopup from '@/pages/health/components/MemberPickerPopup.vue'
 import { normalizeMembers, memberIdOf, resolveMemberId } from '@/utils/memberNormalize.js'
 import { getReportList, deleteReport, getFamilyMemberList, getCollectingReports, reassignReport, getReportTransferList, approveReportTransfer, denyReportTransfer } from '../../utils/api.js'
 import fontScaleMixin from '@/mixins/font-scale.js'

+ 22 - 5
cfc-frontend/pages/profile-extra/family-members.vue

@@ -205,6 +205,20 @@ import store from '../../store/index.js'
 import GenerationPicker from '@/components/GenerationPicker.vue'
 import fontScaleMixin from '@/mixins/font-scale.js'
 
+// canvas 画布色板(引用 BrandColors;画布专属浅色保留集中定义,约定见 uni.scss §14)
+var CANVAS_COLORS = {
+  gradStart:   BrandColors.body,        // #FF8C42 背景渐变起
+  gradEnd:     BrandColors.bodyLight,   // #FFB366 背景渐变终
+  cardFace:    '#FFFCF5',               // 卡片面(米白)
+  cardInk:     BrandColors.primaryDeep, // #C2410C 深橙文字
+  cardFaceAlt: '#FFF0E0',               // 卡片内底(浅橙)
+  hintText:    BrandColors.textMuted,   // #999999 占位灰
+  btnStart:    BrandColors.body,
+  btnEnd:      BrandColors.bodyLight,
+  btnInk:      BrandColors.white,
+  dotColors: [BrandColors.body, BrandColors.wisdom, BrandColors.wealth, BrandColors.action, BrandColors.mind]
+}
+
 export default {
   mixins: [fontScaleMixin],
   components: { GenerationPicker },
@@ -265,6 +279,14 @@ export default {
     detailMemberScores() {
       if (!this.detailMember) return {}
       return this.memberScores[this.detailMember.id] || {}
+    },
+    // 报告权限弹窗:当前选中的被授权成员的展示文案(必须放在 computed 中,
+    // 不能写成 methods 里的 get 访问器——Vue 选项合并会触发 getter 求值导致 this.permMembers 未定义)
+    permGranteeLabel() {
+      var self = this
+      var list = this.permMembers || []
+      var found = list.filter(function(m) { return String(m.value) === String(self.permGranteeUserId) })
+      return found.length > 0 ? found[0].label : ''
     }
   },
   onShow() {
@@ -497,11 +519,6 @@ export default {
       var opt = this.permMembers[idx]
       this.permGranteeUserId = opt ? String(opt.value) : null
     },
-    get permGranteeLabel() {
-      var self = this
-      var found = this.permMembers.filter(function(m) { return String(m.value) === String(self.permGranteeUserId) })
-      return found.length > 0 ? found[0].label : ''
-    },
     addPerm() {
       var self = this
       if (!this.permTargetMember || !this.permGranteeUserId) {

+ 8 - 4
cfc-frontend/pages/profile-main/profile.vue

@@ -361,12 +361,13 @@ export default {
       posterQrCode: '',
       posterEnergyData: null,
       referralCodeLoaded: false,
+      // 登录态:响应式字段(非 computed)。computed 依赖非响应式的 Storage 读取,
+      // 登录成功写入 token 后不会触发重算,导致页面仍显示登录前状态。
+      // 改为 data 字段并在 onShow / 登录成功回调中重新赋值,登录后页面即时刷新。
+      isLoggedIn: !!uni.getStorageSync('token'),
     }
   },
   computed: {
-    isLoggedIn() {
-      return !!uni.getStorageSync('token')
-    },
     roleLabel() {
       var map = { parent: '', child: '成员', teacher: '规划师' }
       return map[this.role] || '用户'
@@ -402,6 +403,8 @@ export default {
     }
   },
   onShow() {
+    // 重新读取登录态(登录浮层成功后 mixin 会触发 onShow),驱动页面切换到已登录视图
+    this.isLoggedIn = !!uni.getStorageSync('token')
     if (!this.isLoggedIn) {
       return
     }
@@ -700,8 +703,9 @@ export default {
     goLogin: function() {
       this.openLoginSheet()
     },
-    // 登录浮层成功回调:刷新个人资料与登录后数据
+    // 登录浮层成功回调:立即切换到已登录视图并刷新个人资料与登录后数据
     onLoginSuccess: function() {
+      this.isLoggedIn = true
       if (this.loadUserInfo) this.loadUserInfo()
     },
     goSkip: function() {

+ 13 - 5
cfc-frontend/pages/profile/index.vue

@@ -39,6 +39,10 @@ export default {
   },
   data() {
     return {
+      // 登录态:响应式字段(非 computed)。computed 依赖非响应式的 Storage 读取,
+      // 登录成功写入 token 后不会触发重算,导致页面仍显示登录前状态。
+      // 改为 data 字段并在 onShow / 登录成功回调中重新赋值,登录后页面即时刷新。
+      isLoggedIn: !!uni.getStorageSync('token'),
       shareData: null,
       fromWealth: false,
       referralCode: '',
@@ -47,9 +51,6 @@ export default {
     }
   },
   computed: {
-    isLoggedIn() {
-      return !!uni.getStorageSync('token')
-    },
     role() {
       return uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
     }
@@ -64,10 +65,16 @@ export default {
       this._fontScaleHandler = function() {
         self.fontScale = getFontScale()
       }
+      // 登录成功后 mixin 会重新调用 onLoad 刷新页面,先解绑旧监听避免重复注册
+      if (this._fontScaleHandler) {
+        uni.$off('fontScaleChanged', this._fontScaleHandler)
+      }
       uni.$on('fontScaleChanged', this._fontScaleHandler)
     },
   onShow() {
     this.fontScale = getFontScale()
+    // 重新读取登录态(登录浮层成功后 mixin 会触发 onShow),驱动页面切换到已登录视图
+    this.isLoggedIn = !!uni.getStorageSync('token')
     if (this.isLoggedIn) {
       this.loadReferralCode()
     }
@@ -122,9 +129,10 @@ export default {
     goLogin() {
       this.openLoginSheet()
     },
-    // 登录浮层成功回调:加载登录后数据(推荐码等)
+    // 登录浮层成功回调:立即切换到已登录视图并加载登录后数据(推荐码等)
     onLoginSuccess() {
-      if (this.isLoggedIn) this.loadReferralCode()
+      this.isLoggedIn = true
+      this.loadReferralCode()
     },
     goToEditInfo() {
       uni.navigateTo({

+ 3 - 3
tools/xaf_agent_hub/.model_state.json

@@ -1,6 +1,6 @@
 {
-  "opencode_blackout_until_ms": 1793318400000,
+  "opencode_blackout_until_ms": 1793577600000,
   "opencode_last_429_reason": "free usage limit reached",
-  "opencode_last_429_at": "2026-10-29 12:30:43",
-  "opencode_last_429_source": "fix_free_limit:local-45278/ses_e7a2ba054ffevMfpIkzpSBE9ij"
+  "opencode_last_429_at": "2026-11-01 11:22:17",
+  "opencode_last_429_source": "fix_free_limit:local-45278/ses_e6d8111b8ffe9Uf28e2m2Gun9C"
 }

+ 2 - 0
tools/xaf_agent_hub/lead_backlog.md

@@ -21,3 +21,5 @@
 - [ ] **T04 第一段收口**:主体已由并发会话 `1f2f9cd01` 合入 cfclub;lan-189 会话(`ses_e850327a`)验收 5 文件 + 修 TrendChart 白板 bug(`feature/t04-segment1 = 9efb65396`)。是否派独立核验后合 cfclub?第二段(report-detail.vue + L1/L2/L3 门槛)未派。
 - [ ] **CFC-EN-001 能量发放 500 修复**(2026-11-01 派发,来自接口冒烟测试结果诊断):派 lan-248 `ses_e68fcb4d0ffedxY8SIu6g3uwkA`(axonhub/deepseek-flash,429 黑窗 fallback,新建会话 20:20 确认执行中),指令 `out/en001_prompt.txt`。修复 `/api/energy/grant` 稳定 500(链路 grantEnergy→awardEnergy→awardByConfig/executeAward;候选根因 getDimByCode("body") null / parseDimensionAssign 异常,以真实栈为准)。完成后自 commit+push 到 origin/cfclub,**待独立核验**后合入。
   - 诊断报告:`docs/tdd/test/2026-11-01-fixme-diagnosis.md`(27 项 fixme 分类:2 真缺陷 / 1 部署滞后 / 15 环境阻塞 / 1 环境数据 / 4 测试夹具 / 2 夹具+授权;badges NOT_FOUND 已过时,前端 10-30 `c465bc3b3` 已收敛 MemberBadge 口径)。
+  - **开发已提交**:commit `fcc6aa876` → 分支 `feature/cfc-en-001-energy-grant-500`(基于 7baa09caa,4 文件 265+/32-)。根因确认:旧 grantEnergy 委托 `awardEnergy("task")`,task_complete 行为配置 100% 分配到 action,请求 body 维度时 balanceMap.get("body")=null → RuntimeException → GlobalExceptionHandler 映射 500。修复:显式构造 allocations 直达指定维度(绕过行为配置比例)+ RuntimeException→IllegalArgumentException/IllegalStateException(GlobalExceptionHandler 已映射 400);新增 2 单测类(Controller 3 例 + Service 2 例)+ 移除 e2e 2 处 fixme + 1 个特征复现用例。
+  - **独立核验已派**:lan-204 `ses_e68af7756ffeUGY5FdJW6C66wg`(axonhub/deepseek,新建 21:37),指令 `out/verify_en001_prompt.txt`(检出→rebase origin/cfclub→编译→单测→Q1-Q6 逻辑审查→边界风险→核验结论),核验通过后合入 cfclub。

+ 69 - 4
tools/xaf_agent_hub/tasks_backlog.json

@@ -1462,13 +1462,14 @@
     "priority": "P1",
     "module": "fullstack",
     "instance": "",
-    "status": "dispatched",
+    "status": "done",
     "spec": "docs/superpowers/specs/2026-11-01-team-system-design.md",
     "prompt": "规格 2026-11-01-team-system-design.md §3.4:新增团队「服务指派」能力——把服务性内容(售后/咨询)指派给团队成员跟进。\n数据:新增表 supply_service_assignment(id, systemId(teamId), customerRelationId, assigneeUserId, serviceType[AFTER_SALE|CONSULT|ACTIVITY_SUPPORT], refOrderId, refRequestId, status[PENDING|PROCESSING|DONE], assignedBy, assignedAt, doneAt, note);迁移走 DatabaseInitializer.runMigrations()+ensureColumn 并同步 schema.sql(幂等)。\n接口(CommissionController 同风格, /api/admin/supply-team/* 或新建 /api/team/*):POST /team/service-assign(指派)、POST /team/service-tasks(成员侧『派给我的任务』)、POST /team/customer/{id}/services(某客户服务记录)。\n对接:AfterSalesRequest 建单时可自动生成一条 PENDING 服务任务(refRequestId);ProductServicePerson 的商品服务人可作为指派候选。\n前端:vendor/team-system.vue 增加「服务指派」入口/列表;新增服务任务页(成员侧)。\n约束:@PostMapping+@Resource+@RequestAttribute(\"userId\")+Result<T>;先查 API_REFERENCE 避免重复并同步;mvn clean compile + node --check 通过;不破坏现有4套团队功能。",
     "session_id": "ses_e6b0865d0ffe810wzY1R7LlcB4",
     "dispatched_at": "2026-11-01 10:36:58",
     "dispatched_to": "lan-204",
-    "model": "axonhub/deepseek"
+    "model": "axonhub/deepseek",
+    "done_at": "2026-11-01 20:22:08"
   },
   {
     "id": "T35",
@@ -1491,13 +1492,14 @@
     "priority": "P1",
     "module": "fullstack",
     "instance": "",
-    "status": "dispatched",
+    "status": "done",
     "spec": "docs/superpowers/specs/2026-11-01-team-system-design.md",
     "prompt": "规格 2026-11-01-team-system-design.md §3.2(已合入 cfclub)。背景:T32 已于错误前提下(团队=推荐下级、按商品SKU聚合)被自动派发并完成,可能已在 cfc-backend 留下 /api/commission/team/product-performance 等实现。本任务按**正确前提**重做:客户=购买过团队产品(供应链 supplySystemId)的用户,购买明细是**客户维度**。\n后端:扩展 SupplyTeamController.listCustomers(POST /api/admin/supply-team/customers):每条客户额外返回 orderCount(该客户在体系下已完成订单数, product_orders WHERE supplySystemId=? AND (buyerId=? OR familyId=?) AND fulfillStatus='completed' 去重 orderNo)、totalAmountYuan(sum(totalAmount)/100, BigDecimal 2位 HALF_UP)、recentOrders(最近1-3笔: orderNo,productName,amountYuan,paidAt)。\n前端:cfc-frontend/pages/vendor/team-customers/team-customers.vue 客户卡片加「关联订单 N / 关联金额 ¥X」+ 下钻购买明细(复用现有 systemId/parentId 与 getVendorTeamCustomers)。\n复核:若 T32 旧实现(product-performance/SKU聚合)与团队页无关或基于 referral 模型,请将其标记为废弃(410/注释)或改造为本客户维度接口,避免两套口径并存。\n约束:@PostMapping+@Resource+@RequestAttribute(\"userId\")+Result<T>;先查 API_REFERENCE 避免重复并同步;mvn clean compile + node --check 通过。",
     "session_id": "ses_e6b084c61ffeOyd8pCON1nz31V",
     "dispatched_at": "2026-11-01 10:37:04",
     "dispatched_to": "lan-204",
-    "model": "axonhub/deepseek"
+    "model": "axonhub/deepseek",
+    "done_at": "2026-11-01 20:22:08"
   },
   {
     "id": "DOC-2026-11-01-team-product-performance-design",
@@ -1515,5 +1517,68 @@
     "dispatched_to": "lan-189",
     "model": "opencode/deepseek-v4.1-flash-free",
     "done_at": "2026-11-01 11:25:25"
+  },
+  {
+    "id": "GAP-2026-11-01-home-greet-sh",
+    "title": "[差距] 2026 11 01 home greet shortcut and my messages",
+    "priority": "P1",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "done",
+    "spec": "docs/superpowers/plans/2026-11-01-home-greet-shortcut-and-my-messages.md",
+    "entity": "cfc-backend/src/test/java/com/etotem/cfc/controller/guide/TeacherMessageControllerMyTest.java",
+    "gap_kinds": [
+      "missing_file"
+    ],
+    "prompt": "请核对需求/设计文档 docs/superpowers/plans/2026-11-01-home-greet-shortcut-and-my-messages.md 与实际实现的差距,并完成以下差距项:\n\n文档主题:2026 11 01 home greet shortcut and my messages\n所属模块:cfc-backend\n\n检测到的差距:\n  - [文档提及但尚未创建的代码文件] cfc-backend/src/test/java/com/etotem/cfc/controller/guide/TeacherMessageControllerMyTest.j\n  - [文档提及但尚未创建的代码文件] cfc-frontend/pages/profile/my-messages.vue\n  - [文档提及但尚未创建的代码文件] TeacherMessageControllerMyTest.java\n\n任务来源:sync_gap_tasks(巡检发现空闲会话且任务池为空时自动生成)。\n要求:先阅读该文档对应章节,确认差距真实存在(若已实现请如实说明并标记为无需改动),然后按文档口径实现/补齐,完成后自行提交并推送。",
+    "generated_from": "sync_gap_tasks",
+    "generated_at": "2026-11-01 20:22:02",
+    "session_id": "ses_e68f08005ffehqkea6tmi7ZKUo",
+    "dispatched_at": "2026-11-01 20:22:17",
+    "dispatched_to": "lan-204",
+    "model": "axonhub/deepseek",
+    "done_at": "2026-11-01 21:21:39"
+  },
+  {
+    "id": "GAP-2026-10-31-sku-combo-des",
+    "title": "[差距] 2026 10 31 sku combo design",
+    "priority": "P1",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "done",
+    "spec": "docs/superpowers/specs/2026-10-31-sku-combo-design.md",
+    "entity": "ProductSkuComboItem.java",
+    "gap_kinds": [
+      "missing_file"
+    ],
+    "prompt": "请核对需求/设计文档 docs/superpowers/specs/2026-10-31-sku-combo-design.md 与实际实现的差距,并完成以下差距项:\n\n文档主题:2026 10 31 sku combo design\n所属模块:cfc-backend\n\n检测到的差距:\n  - [文档提及但尚未创建的代码文件] ProductSkuComboItem.java\n  - [文档提及但尚未创建的代码文件] ProductSkuComboItemMapper.java\n\n任务来源:sync_gap_tasks(巡检发现空闲会话且任务池为空时自动生成)。\n要求:先阅读该文档对应章节,确认差距真实存在(若已实现请如实说明并标记为无需改动),然后按文档口径实现/补齐,完成后自行提交并推送。",
+    "generated_from": "sync_gap_tasks",
+    "generated_at": "2026-11-01 20:22:02",
+    "session_id": "ses_e68f091b6ffeUhHnJYtT1covDa",
+    "dispatched_at": "2026-11-01 20:22:13",
+    "dispatched_to": "lan-248",
+    "model": "axonhub/deepseek",
+    "done_at": "2026-11-01 20:42:57"
+  },
+  {
+    "id": "GAP-2026-11-01-team-system-d",
+    "title": "[差距] 2026 11 01 team system design",
+    "priority": "P2",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "done",
+    "spec": "docs/superpowers/specs/2026-11-01-team-system-design.md",
+    "entity": "| C2 | `DistributionSystem.ownerId` | 字段存在但**结算逻辑从未使用** | 「唯一负责人」语义未落地为结算对象 | 随 T37 一并接入 |",
+    "gap_kinds": [
+      "statement"
+    ],
+    "prompt": "请核对需求/设计文档 docs/superpowers/specs/2026-11-01-team-system-design.md 与实际实现的差距,并完成以下差距项:\n\n文档主题:2026 11 01 team system design\n所属模块:cfc-backend\n\n检测到的差距:\n  - [标记为「设计完成但未实现」的内容] | C2 | `DistributionSystem.ownerId` | 字段存在但**结算逻辑从未使用** | 「唯一负责人」语义未落地为结算对象 | 随 T37 一并接入 |\n\n任务来源:sync_gap_tasks(巡检发现空闲会话且任务池为空时自动生成)。\n要求:先阅读该文档对应章节,确认差距真实存在(若已实现请如实说明并标记为无需改动),然后按文档口径实现/补齐,完成后自行提交并推送。",
+    "generated_from": "sync_gap_tasks",
+    "generated_at": "2026-11-01 20:34:34",
+    "session_id": "ses_e68e4d6c4ffegNd3JduoxSXilM",
+    "dispatched_at": "2026-11-01 20:35:02",
+    "dispatched_to": "lan-204",
+    "model": "axonhub/deepseek",
+    "done_at": "2026-11-01 21:12:03"
   }
 ]