Parcourir la source

Merge remote-tracking branch 'origin/cfclub' into cfclub

# Conflicts:
#	cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
jiapu il y a 2 mois
Parent
commit
0807877e3a

+ 57 - 13
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -8492,16 +8492,11 @@ public class DatabaseInitializer implements CommandLineRunner {
         ensureColumn("health_reports", "proxy_uploader_id", "BIGINT COMMENT '代上传人ID(规划师/营养师)'");
         ensureColumn("health_reports", "proxy_upload_note", "TEXT COMMENT '代上传备注'");
         ensureColumn("health_reports", "ai_analysis", "TEXT COMMENT 'AI解读结果JSON'");
+        // 迁移180b: health_reports 添加报告来源格式(beijing/guhe,两种菌群报告数据结构不兼容,需区分)
+        ensureColumn("health_reports", "report_format", "VARCHAR(32) COMMENT '报告来源格式: beijing(北京菌群)/guhe(谷禾)'");
 
         // 迁移181: health_report_drafts 添加备注字段(代上传说明文字)
         ensureColumn("health_report_drafts", "remark", "VARCHAR(500) DEFAULT NULL COMMENT '备注(如代上传时的说明文字)'");
-        // 迁移181b: 异步采集状态列(实体已映射,必须在查询前存在;放在草稿表迁移旁避免后面 ensureColumn 抛错导致漏执行)
-        ensureColumn("health_report_drafts", "parse_status",
-                "VARCHAR(20) DEFAULT NULL COMMENT '异步解析状态: collecting/completed/failed(NULL=未触发异步采集)'");
-        ensureColumn("health_report_drafts", "parse_method",
-                "VARCHAR(32) DEFAULT NULL COMMENT '解析方式: fingerprint_script/opencode/langgraph'");
-        ensureColumn("health_report_drafts", "parse_error",
-                "VARCHAR(2000) DEFAULT NULL COMMENT '异步解析失败原因'");
 
         // 迁移182: 创建 family_challenge_participant 表(挑战参与成员,任意成员发起/可选参与)
         try {
@@ -10272,13 +10267,62 @@ public class DatabaseInitializer implements CommandLineRunner {
                 "VARCHAR(2000) DEFAULT NULL COMMENT '异步解析失败原因'");
         log.info("已添加 health_report_drafts 异步采集状态列 (parse_status/parse_method/parse_error)");
 
-        // 迁移289: health_report_drafts.expire_at 去掉 ON UPDATE CURRENT_TIMESTAMP
-        // 解析更新 payload 时会把过期时间重置为当前时刻,导致确认入库被误判过期
-        try {
-            jdbcTemplate.execute("ALTER TABLE health_report_drafts MODIFY COLUMN expire_at DATETIME COMMENT '草稿过期时间'");
-            log.info("已修正 health_report_drafts.expire_at 列定义");
+        // 迁移289: health_reports 补 report_format 列(HealthReport 实体已含字段,但从未迁移至 DB,全字段 SELECT 报 Unknown column)
+        ensureColumn("health_reports", "report_format",
+                "VARCHAR(32) DEFAULT NULL COMMENT '报告格式: pdf/image'");
+
+        // 迁移290: 批量补齐实体已含但 DB 从未迁移的真实缺列(全字段 SELECT 防 Unknown column)
+        // 审计核实:以下列不在任何历史 ADD COLUMN/ensureColumn 中,属真实缺口
+        // 涉及 12 表 33 列(food_nutrients 命名问题单列迁移291;articles/health_reports 等解析后无缺口)
+        ensureColumn("activities", "visibility", "VARCHAR(20) DEFAULT NULL COMMENT 'visibility'");
+        ensureColumn("activities", "visible_to", "TEXT DEFAULT NULL COMMENT 'visibleTo'");
+        ensureColumn("blood_type_pairing", "blood_type1", "VARCHAR(255) DEFAULT NULL COMMENT 'bloodType1'");
+        ensureColumn("blood_type_pairing", "blood_type2", "VARCHAR(255) DEFAULT NULL COMMENT 'bloodType2'");
+        ensureColumn("dan_knowledge_base", "file_url", "VARCHAR(500) DEFAULT NULL COMMENT 'fileUrl'");
+        ensureColumn("dan_knowledge_base", "file_name", "VARCHAR(255) DEFAULT NULL COMMENT 'fileName'");
+        ensureColumn("dan_knowledge_base", "source_url", "VARCHAR(500) DEFAULT NULL COMMENT 'sourceUrl'");
+        ensureColumn("dan_knowledge_base", "source_type", "VARCHAR(20) DEFAULT NULL COMMENT 'sourceType'");
+        ensureColumn("distribution_relations", "invite_code", "VARCHAR(64) DEFAULT NULL COMMENT 'inviteCode'");
+        ensureColumn("family_members", "birth_hour", "VARCHAR(20) DEFAULT NULL COMMENT 'birthHour'");
+        ensureColumn("family_members", "birth_weight", "INT DEFAULT NULL COMMENT 'birthWeight'");
+        ensureColumn("family_members", "birth_place", "VARCHAR(255) DEFAULT NULL COMMENT 'birthPlace'");
+        ensureColumn("family_members", "is_cesarean", "INT DEFAULT NULL COMMENT 'isCesarean'");
+        ensureColumn("food_recommend_idx", "index_score", "INT DEFAULT NULL COMMENT 'indexScore'");
+        ensureColumn("health_indicators", "symptoms", "VARCHAR(2000) DEFAULT NULL COMMENT 'symptoms'");
+        ensureColumn("numsoul_config", "keywords", "VARCHAR(2000) DEFAULT NULL COMMENT 'keywords'");
+        ensureColumn("numsoul_detail_config", "keywords", "VARCHAR(2000) DEFAULT NULL COMMENT 'keywords'");
+        ensureColumn("product_orders", "tracking_number", "VARCHAR(100) DEFAULT NULL COMMENT 'trackingNumber'");
+        ensureColumn("product_orders", "express_company", "VARCHAR(100) DEFAULT NULL COMMENT 'expressCompany'");
+        ensureColumn("product_orders", "delivery_method", "INT DEFAULT NULL COMMENT 'deliveryMethod'");
+        ensureColumn("product_orders", "pickup_code", "VARCHAR(64) DEFAULT NULL COMMENT 'pickupCode'");
+        ensureColumn("product_orders", "confirm_time", "DATETIME DEFAULT NULL COMMENT 'confirmTime'");
+        ensureColumn("product_orders", "refund_status", "INT DEFAULT NULL COMMENT 'refundStatus'");
+        ensureColumn("product_orders", "refund_reason", "VARCHAR(255) DEFAULT NULL COMMENT 'refundReason'");
+        ensureColumn("product_orders", "refund_amount", "INT DEFAULT NULL COMMENT 'refundAmount'");
+        ensureColumn("product_orders", "refund_time", "DATETIME DEFAULT NULL COMMENT 'refundTime'");
+        ensureColumn("promotion_tier_config", "min_team_size", "INT DEFAULT NULL COMMENT 'minTeamSize'");
+        ensureColumn("promotion_tier_config", "min_referral_earnings", "INT DEFAULT NULL COMMENT 'minReferralEarnings'");
+        ensureColumn("promotion_tier_config", "min_share_earnings", "INT DEFAULT NULL COMMENT 'minShareEarnings'");
+        ensureColumn("promotion_tier_config", "profit_share_percent", "INT DEFAULT NULL COMMENT 'profitSharePercent'");
+        ensureColumn("promotion_tier_config", "enabled", "INT DEFAULT NULL COMMENT 'enabled'");
+        ensureColumn("zodiac_pairing", "zodiac_code1", "VARCHAR(255) DEFAULT NULL COMMENT 'zodiacCode1'");
+        ensureColumn("zodiac_pairing", "zodiac_code2", "VARCHAR(255) DEFAULT NULL COMMENT 'zodiacCode2'");
+        log.info("已补齐真实缺列 (迁移290, 12表33列)");
+
+        // 迁移291: food_nutrients 列名对齐(amount_per_100g -> amount_per100g)
+        // FoodNutrient 实体字段 amountPer100g 经 MyBatis-Plus camel->snake 映射为 amount_per100g,
+        // 但建表 SQL 误用 amount_per_100g,导致实体全字段 SELECT 报 Unknown column 'amount_per100g'。
+        // 当前 FoodNutrientMapper 无 Service 调用,属潜在隐患;此处统一列名为 amount_per100g。
+        try {
+            Integer cnt = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() " +
+                    "AND TABLE_NAME = 'food_nutrients' AND COLUMN_NAME = 'amount_per_100g'", Integer.class);
+            if (cnt != null && cnt > 0) {
+                jdbcTemplate.execute("ALTER TABLE food_nutrients CHANGE COLUMN amount_per_100g amount_per100g DECIMAL(10,4) NOT NULL COMMENT '每100g含量'");
+                log.info("已重命名 food_nutrients.amount_per_100g -> amount_per100g");
+            }
         } catch (Exception e) {
-            log.warn("修正 health_report_drafts.expire_at 失败: {}", e.getMessage());
+            // 表不存在或列不存在,忽略
         }
     }
 }

+ 11 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/ParsedReportResult.java

@@ -77,6 +77,9 @@ public class ParsedReportResult {
     /** 食材适宜度 */
     private List<ParsedFoodSuitability> foodSuitability;
 
+    /** 报告格式: beijing(北京菌群), guhe(谷禾/募极生物) */
+    private String reportFormat;
+
     // ==================== getters/setters ====================
 
     public Integer getOverallScore() {
@@ -254,4 +257,12 @@ public class ParsedReportResult {
     public void setFoodSuitability(List<ParsedFoodSuitability> foodSuitability) {
         this.foodSuitability = foodSuitability;
     }
+
+    public String getReportFormat() {
+        return reportFormat;
+    }
+
+    public void setReportFormat(String reportFormat) {
+        this.reportFormat = reportFormat;
+    }
 }

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/HealthReport.java

@@ -32,6 +32,9 @@ public class HealthReport implements Serializable {
     /** 报告类型: physical_exam(体检报告), gut_flora(菌群检测报告) */
     private String reportType;
 
+    /** 报告格式: beijing(北京菌群), guhe(谷禾/募极生物), null(未知/体检报告) */
+    private String reportFormat;
+
     /** 报告编号 */
     private String reportNumber;
 

+ 4 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/PdfParseService.java

@@ -143,7 +143,9 @@ public ParsedReportResult parseText(String fullText) {
         // 2. 检测是否为北京菌群报告格式
         if (isBeijingFormat(normalized)) {
             log.debug("PDF 检测格式: beijing");
-            return parseBeijingReport(normalized);
+            ParsedReportResult beijingResult = parseBeijingReport(normalized);
+            beijingResult.setReportFormat("beijing");
+            return beijingResult;
         }
 
         // 3. 按行分割
@@ -170,6 +172,7 @@ public ParsedReportResult parseText(String fullText) {
         } else {
             parseTripletReport(lineList, result);
         }
+        result.setReportFormat("guhe");
 
         // 6. 解析食材推荐(格式无关)
         List<ParsedFoodSuitability> foodSuitability = parseFoodSuitability(normalized);

+ 2 - 0
cfc-backend/src/main/resources/schema.sql

@@ -1738,6 +1738,7 @@ CREATE TABLE IF NOT EXISTS health_reports (
     subject_id BIGINT COMMENT '报告主体用户ID(关联users表,为null表示未绑定)',
     family_id BIGINT COMMENT '家庭ID',
     report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam(体检)/gut_flora(菌群检测)',
+    report_format VARCHAR(32) COMMENT '报告来源格式: beijing(北京菌群)/guhe(谷禾)',
     report_date DATE COMMENT '报告日期',
     overall_score INT COMMENT '健康总分(0-100)',
     gut_health_score INT COMMENT '菌群健康分',
@@ -1746,6 +1747,7 @@ CREATE TABLE IF NOT EXISTS health_reports (
     gut_age VARCHAR(32) COMMENT '肠道预测年龄',
     gut_type VARCHAR(50) COMMENT '肠型',
     file_url VARCHAR(500) COMMENT '报告文件URL',
+    report_format VARCHAR(32) DEFAULT NULL COMMENT '报告格式: pdf/image',
     status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/archived',
     remark TEXT COMMENT '备注',
     proxy_uploader_id BIGINT COMMENT '代上传人ID(规划师/营养师)',

+ 50 - 13
cfc-frontend/components/wuxing-sandbox.vue

@@ -5,22 +5,16 @@
     <view class="sandbox-header">
       <image class="sandbox-logo" src="/static/logo.png" mode="aspectFit"></image>
       <text class="sandbox-title">浠艾福能量沙盘</text>
-      <text class="sandbox-badge">{{ displayBadgeText }}</text>
+      <!-- 右上角:live 模式显示个人/全家切换,preview 模式显示徽章文案 -->
+      <view class="sandbox-switch" v-if="mode !== 'preview'" @click="onToggle(currentView === 'family' ? 'personal' : 'family')">
+        <text class="sandbox-switch-icon">{{ currentView === 'family' ? '👨‍👩‍👧‍👦' : '👤' }}</text>
+        <text class="sandbox-switch-label">{{ currentView === 'family' ? '全家' : (currentMember ? currentMember.name : '个人') }}</text>
+        <text class="sandbox-switch-arrow">⇄</text>
+      </view>
+      <text class="sandbox-badge" v-else>{{ displayBadgeText }}</text>
     </view>
   </slot>
 
-  <!-- 个人/全家切换开关(有 2+ 成员时显示) -->
-  <view class="sandbox-toggle" v-if="hasMultipleMembers && mode !== 'preview'">
-    <view class="toggle-item" :class="activeClassName" @click="onToggle('personal')">
-      <text class="toggle-icon">👤</text>
-      <text class="toggle-text">个人</text>
-    </view>
-    <view class="toggle-item toggle-item--family" :class="activeFamilyClassName" @click="onToggle('family')">
-      <text class="toggle-icon">👨‍👩‍👧‍👦</text>
-      <text class="toggle-text">全家</text>
-    </view>
-  </view>
-
   <view class="sandbox-star" :style="{height: starHeight}">
     <view class="star-glow-bg"></view>
 
@@ -165,6 +159,11 @@ export default {
       type: Boolean,
       default: false
     },
+    /** 家庭真实成员数(来自 /api/family/member/list,用于全家切换判断;缺省时用 members 长度) */
+    memberCount: {
+      type: Number,
+      default: 0
+    },
     /** 当前视角模式(由父组件控制) */
     currentView: {
       type: String,
@@ -427,6 +426,23 @@ export default {
 
     /** 个人/全家切换(由父组件控制数据源) */
     onToggle: function(view) {
+      if (view === 'family') {
+        var total = this.memberCount > 0 ? this.memberCount : (this.members ? this.members.length : 0)
+        if (total < 2) {
+          uni.showModal({
+            title: '需要更多家庭成员',
+            content: '至少需要 2 位家庭成员才能查看全家能量,快去添加吧!',
+            confirmText: '去添加',
+            cancelText: '取消',
+            success: function(res) {
+              if (res.confirm) {
+                uni.navigateTo({ url: '/pages/profile-extra/family-members' })
+              }
+            }
+          })
+          return
+        }
+      }
       this.viewMode = view
       if (view === this.currentView) return
       this.$emit('view-change', view)
@@ -630,6 +646,27 @@ export default {
   border-radius: 20rpx;
   margin-left: auto;
 }
+.sandbox-switch {
+  display: flex;
+  align-items: center;
+  gap: 6rpx;
+  font-size: 22rpx;
+  color: #fff;
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  padding: 10rpx 20rpx;
+  border-radius: 28rpx;
+  margin-left: auto;
+  flex-shrink: 0;
+  box-shadow: 0 4rpx 12rpx rgba(102,126,234,0.45);
+  border: 1rpx solid rgba(255,255,255,0.25);
+}
+.sandbox-switch:active {
+  opacity: 0.85;
+  box-shadow: none;
+}
+.sandbox-switch-icon { font-size: 24rpx; line-height: 1; }
+.sandbox-switch-label { font-size: 22rpx; font-weight: 700; max-width: 140rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
+.sandbox-switch-arrow { font-size: 20rpx; opacity: 0.8; }
 
 /* ---- 五角星区域 ---- */
 .sandbox-star {

+ 2 - 4
cfc-frontend/pages/energy/index.vue

@@ -349,10 +349,8 @@ export default {
         this.switchView('self')
         return
       }
-      // 进入全家态前检查成员数
-      var members = this.familySandboxData && this.familySandboxData.members
-        ? this.familySandboxData.members
-        : this.familyMembers
+      // 进入全家态前检查成员数(以 /api/family/member/list 为准,sandbox 接口 members 不全)
+      var members = this.familyMembers
       if (!members || members.length < 2) {
         uni.showModal({
           title: '需要更多家庭成员',

+ 31 - 13
cfc-frontend/pages/health/report-detail.vue

@@ -414,6 +414,7 @@ export default {
       confirming: false,
       payload: {
         type: 'gut_flora',
+        reportFormat: '',
         summary: {},
         indicators: [],
         gutFlora: [],
@@ -595,6 +596,10 @@ export default {
           if (res.data.payload) {
             self.payload = Object.assign({}, self.defaultPayload(), res.data.payload)
           }
+          // 报告来源格式优先取 payload,兜底取 report 实体字段
+          if (!self.payload.reportFormat && report.reportFormat) {
+            self.payload.reportFormat = report.reportFormat
+          }
           // 后端无 blocks 时从 payload 自动组装
           var cleanBlocks = self.stripFoodBlocks(backendBlocks)
           if (cleanBlocks.length > 0) {
@@ -620,6 +625,7 @@ export default {
     defaultPayload() {
       return {
         type: 'gut_flora',
+        reportFormat: '',
         summary: {},
         indicators: [],
         gutFlora: [],
@@ -1072,25 +1078,37 @@ export default {
       var p = this.payload
       if (!p) return blocks
 
-      // 1. 评分圆环块
+      // 1. 评分圆环块(北京/谷禾报告评分体系不同:北京用 GMHI 指数,谷禾用健康总分)
       var scoreItems = []
+      var isBeijing = p.reportFormat === 'beijing'
       if (p.summary) {
-        this._addScoreItem(scoreItems, '综合', p.summary.overallScore)
-        this._addScoreItem(scoreItems, '菌群健康', p.summary.gutHealthScore)
-        this._addScoreItem(scoreItems, '慢病控制', p.summary.chronicDiseaseScore)
-        // 子项
-        if (p.summary.nutritionScore != null) { this._addSubScore(scoreItems, '综合', '营养均衡', p.summary.nutritionScore) }
-        if (p.summary.gutBalanceScore != null) { this._addSubScore(scoreItems, '菌群健康', '平衡', p.summary.gutBalanceScore) }
-        if (p.summary.gutDiversityScore != null) { this._addSubScore(scoreItems, '菌群健康', '多样性', p.summary.gutDiversityScore) }
-        if (p.summary.beneficialBacteriaScore != null) { this._addSubScore(scoreItems, '菌群健康', '有益菌', p.summary.beneficialBacteriaScore) }
-        if (p.summary.harmfulBacteriaScore != null) { this._addSubScore(scoreItems, '菌群健康', '有害菌', p.summary.harmfulBacteriaScore) }
-        if (p.summary.coreSpeciesScore != null) { this._addSubScore(scoreItems, '菌群健康', '核心菌属', p.summary.coreSpeciesScore) }
-        // 肠道年龄/肠型
+        if (isBeijing) {
+          // 北京菌群报告:GMHI 肠道微生物健康指数为主评分
+          this._addScoreItem(scoreItems, 'GMHI 肠道微生物健康指数', p.summary.overallScore != null ? p.summary.overallScore : p.summary.gmhiIndex)
+          this._addScoreItem(scoreItems, '菌群健康', p.summary.gutHealthScore)
+          if (p.summary.gutBalanceScore != null) { this._addSubScore(scoreItems, '菌群健康', '平衡', p.summary.gutBalanceScore) }
+          if (p.summary.gutDiversityScore != null) { this._addSubScore(scoreItems, '菌群健康', '多样性', p.summary.gutDiversityScore) }
+          if (p.summary.beneficialBacteriaScore != null) { this._addSubScore(scoreItems, '菌群健康', '有益菌', p.summary.beneficialBacteriaScore) }
+          if (p.summary.coreSpeciesScore != null) { this._addSubScore(scoreItems, '菌群健康', '核心菌属', p.summary.coreSpeciesScore) }
+        } else {
+          // 谷禾/其它报告:健康总分 + 细分维度
+          this._addScoreItem(scoreItems, '综合', p.summary.overallScore)
+          this._addScoreItem(scoreItems, '菌群健康', p.summary.gutHealthScore)
+          this._addScoreItem(scoreItems, '慢病控制', p.summary.chronicDiseaseScore)
+          // 子项
+          if (p.summary.nutritionScore != null) { this._addSubScore(scoreItems, '综合', '营养均衡', p.summary.nutritionScore) }
+          if (p.summary.gutBalanceScore != null) { this._addSubScore(scoreItems, '菌群健康', '平衡', p.summary.gutBalanceScore) }
+          if (p.summary.gutDiversityScore != null) { this._addSubScore(scoreItems, '菌群健康', '多样性', p.summary.gutDiversityScore) }
+          if (p.summary.beneficialBacteriaScore != null) { this._addSubScore(scoreItems, '菌群健康', '有益菌', p.summary.beneficialBacteriaScore) }
+          if (p.summary.harmfulBacteriaScore != null) { this._addSubScore(scoreItems, '菌群健康', '有害菌', p.summary.harmfulBacteriaScore) }
+          if (p.summary.coreSpeciesScore != null) { this._addSubScore(scoreItems, '菌群健康', '核心菌属', p.summary.coreSpeciesScore) }
+        }
+        // 肠道年龄/肠型(两种格式均有或部分有)
         if (p.summary.gutAge) { this._addScoreItem(scoreItems, '肠道年龄', p.summary.gutAge) }
         if (p.summary.gutType) { this._addScoreItem(scoreItems, '肠型', p.summary.gutType) }
       }
       if (scoreItems.length > 0) {
-        blocks.push({ type: 'score', title: '健康评分', items: scoreItems })
+        blocks.push({ type: 'score', title: isBeijing ? '肠道微生物健康指数' : '健康评分', items: scoreItems })
       }
 
       // 2. 疾病风险分组块

+ 1 - 0
cfc-frontend/pages/home-pages/child-index.vue

@@ -46,6 +46,7 @@
   :totalHealthIndex="totalHealthIndex"
   :familyUnlocked="familyUnlocked"
   :currentView="currentSandboxView"
+  :memberCount="familyMembersVisible.length"
   starHeight="400rpx"
   showDimensionBar
   @view-change="onSandboxViewChange"

+ 1 - 0
cfc-frontend/pages/home-pages/parent-index.vue

@@ -155,6 +155,7 @@
         :totalHealthIndex="totalHealthIndex"
         :familyUnlocked="familyUnlocked"
         :currentView="currentSandboxView"
+        :memberCount="familyMembersVisible.length"
         starHeight="400rpx"
         showDimensionBar
         @view-change="onSandboxViewChange"

+ 149 - 39
cfc-frontend/pages/index-home/index.vue

@@ -74,6 +74,7 @@
       :totalHealthIndex="totalHealthIndex"
       :currentView="currentSandboxView"
       :familyUnlocked="familyUnlocked"
+      :memberCount="familyMembersVisible.length"
       @view-change="onSandboxViewChange"
       showDimensionBar
       starHeight="480rpx" />
@@ -225,34 +226,84 @@
     <view class="hero-section">
       <image class="hero-logo" src="/static/logo.png" mode="aspectFit" />
       <text class="hero-name">浠艾福</text>
-      <text class="hero-slogan">给全家的一站式幸福提案</text>
+      <text class="hero-slogan">给全家的一站式主动健康方案</text>
       <text class="hero-powered">亿图腾科技出品</text>
 
       <view class="hero-dims">
-        <view class="hero-dim"><text class="hero-dim-emoji">🌏</text><text class="hero-dim-label">身·健康</text></view>
-        <view class="hero-dim"><text class="hero-dim-emoji">🔥</text><text class="hero-dim-label">心·情绪</text></view>
+        <view class="hero-dim"><text class="hero-dim-emoji">🌏</text><text class="hero-dim-label">身·根基</text></view>
         <view class="hero-dim"><text class="hero-dim-emoji">⚡</text><text class="hero-dim-label">智·赋能</text></view>
-        <view class="hero-dim"><text class="hero-dim-emoji">🌿</text><text class="hero-dim-label">行·习惯</text></view>
-        <view class="hero-dim"><text class="hero-dim-emoji">💧</text><text class="hero-dim-label">富·财商</text></view>
+        <view class="hero-dim"><text class="hero-dim-emoji">💧</text><text class="hero-dim-label">富·创富</text></view>
+        <view class="hero-dim"><text class="hero-dim-emoji">🌿</text><text class="hero-dim-label">行·和谐</text></view>
+        <view class="hero-dim"><text class="hero-dim-emoji">🔥</text><text class="hero-dim-label">心·大爱</text></view>
       </view>
     </view>
 
-    <!-- 价值说明 -->
-    <view class="value-cards">
-      <view class="value-card">
-        <text class="value-card-emoji">📊</text>
-        <text class="value-card-title">五维健康自检</text>
-        <text class="value-card-desc">15 题快速评估全家身·心·智·行·富状态</text>
+    <!-- 主动健康闭环 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">🔄 主动健康闭环</text>
       </view>
-      <view class="value-card">
-        <text class="value-card-emoji">👨‍👩‍👧</text>
-        <text class="value-card-title">家庭成长计划</text>
-        <text class="value-card-desc">为孩子定制每日任务,家长随时跟踪进度</text>
+      <view class="loop-cards">
+        <view class="loop-card">
+          <view class="loop-card-icon loop-icon-indigo"><text>🔍</text></view>
+          <text class="loop-card-name">提前识别</text>
+          <text class="loop-card-desc">AI 多维评估,绘制健康基线</text>
+        </view>
+        <view class="loop-card">
+          <view class="loop-card-icon loop-icon-green"><text>🛡️</text></view>
+          <text class="loop-card-name">风险管控</text>
+          <text class="loop-card-desc">分级管控,不让隐患成问题</text>
+        </view>
+        <view class="loop-card">
+          <view class="loop-card-icon loop-icon-amber"><text>⚡</text></view>
+          <text class="loop-card-name">主动干预</text>
+          <text class="loop-card-desc">任务与能量追踪,融入日常</text>
+        </view>
+        <view class="loop-card">
+          <view class="loop-card-icon loop-icon-red"><text>📊</text></view>
+          <text class="loop-card-name">效果评估</text>
+          <text class="loop-card-desc">档案记录轨迹,数据持续优化</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 五维能量 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">🌐 五维能量 · 五行相生</text>
       </view>
-      <view class="value-card">
-        <text class="value-card-emoji">🧠</text>
-        <text class="value-card-title">专业心智测评</text>
-        <text class="value-card-desc">预约成长规划师,获取个性化成长建议</text>
+      <view class="dims-cards">
+        <view class="dims-card-row">
+          <view class="dims-card-item">
+            <text class="dims-card-emoji">🌏</text>
+            <text class="dims-card-name">身 · 根基永固</text>
+            <text class="dims-card-desc">精准营养 · 健康报告 · 体质调理</text>
+          </view>
+          <view class="dims-card-item">
+            <text class="dims-card-emoji">⚡</text>
+            <text class="dims-card-name">智 · 赋能未来</text>
+            <text class="dims-card-desc">心智测评 · 素质培养 · 成长档案</text>
+          </view>
+        </view>
+        <view class="dims-card-row">
+          <view class="dims-card-item">
+            <text class="dims-card-emoji">💧</text>
+            <text class="dims-card-name">富 · 利他创富</text>
+            <text class="dims-card-desc">创客生态 · 积分奖励</text>
+          </view>
+          <view class="dims-card-item">
+            <text class="dims-card-emoji">🌿</text>
+            <text class="dims-card-name">行 · 人际和谐</text>
+            <text class="dims-card-desc">亲子关系 · 夫妻情感 · 社交人脉</text>
+          </view>
+        </view>
+        <view class="dims-card-row">
+          <view class="dims-card-item dims-card-item-full">
+            <text class="dims-card-emoji">🔥</text>
+            <text class="dims-card-name">心 · 大爱相随</text>
+            <text class="dims-card-desc">情绪疏导 · 关系心理学</text>
+          </view>
+        </view>
       </view>
     </view>
 
@@ -972,7 +1023,7 @@ membershipDays: 0,
       uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
     },
     goMoreArticles: function() {
-      uni.navigateTo({ url: '/pages/article-center/index?dimension=action' })
+      uni.navigateTo({ url: '/pages/article-center/index' })
     },
     goToUploadReport: function() {
       var id = this.currentChildId || ''
@@ -1049,11 +1100,10 @@ membershipDays: 0,
       this._watchPageReady()
     },
     _refreshOnShow() {
-      // 切 Tab 返回时刷新易变数据(仅会员状态、今日任务、挑战列表),避免重复加载
+      // 切 Tab 返回:刷新家庭成员 + 沙盘 + 会员状态(loadFamilyData 内含任务/挑战刷新)
       if (!this.isLoggedIn()) return
       this.checkMemberStatus()
-      this.loadTodayTasks()
-      this.loadChallenges()
+      this.loadFamilyData()
     },
     _watchPageReady() {
       if (this._watchPageReadyDone) return
@@ -1365,42 +1415,102 @@ membershipDays: 0,
   white-space: nowrap;
 }
 
-/* ===== 价值说明卡片 ===== */
-.value-cards {
+/* ===== 主动健康闭环 ===== */
+.loop-cards {
   display: flex;
-  flex-direction: column;
+  flex-wrap: wrap;
+  justify-content: center;
   gap: 16rpx;
-  margin: 24rpx 30rpx;
+  margin: 20rpx 30rpx;
 }
 
-.value-card {
+.loop-card {
+  width: 47%;
   background: #fff;
   border-radius: 20rpx;
-  padding: 28rpx 28rpx;
+  padding: 24rpx 20rpx;
+  text-align: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  border: 1rpx solid #F3F4F6;
+}
+
+.loop-card-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 34rpx;
+  margin-bottom: 12rpx;
+}
+
+.loop-icon-indigo { background: rgba(99,102,241,0.15); }
+.loop-icon-green { background: rgba(16,185,129,0.15); }
+.loop-icon-amber { background: rgba(245,158,11,0.15); }
+.loop-icon-red { background: rgba(239,68,68,0.15); }
+
+.loop-card-name {
+  display: block;
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #1F2937;
+  margin-bottom: 6rpx;
+}
+
+.loop-card-desc {
+  display: block;
+  font-size: 22rpx;
+  color: #6B7280;
+  line-height: 1.5;
+}
+
+/* ===== 五维能量 ===== */
+.dims-cards {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+  margin: 20rpx 30rpx;
+}
+
+.dims-card-row {
   display: flex;
-  align-items: flex-start;
-  gap: 20rpx;
+  flex-wrap: wrap;
+  gap: 16rpx;
+}
+
+.dims-card-item {
+  flex: 1;
+  min-width: 280rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx 20rpx;
+  text-align: center;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-  border: 1rpx solid rgba(249,115,22,0.08);
+  border: 1rpx solid #F3F4F6;
 }
 
-.value-card-emoji {
+.dims-card-item-full {
+  flex-basis: 100%;
+}
+
+.dims-card-emoji {
+  display: block;
   font-size: 44rpx;
-  flex-shrink: 0;
-  margin-top: 2rpx;
+  margin-bottom: 10rpx;
 }
 
-.value-card-title {
+.dims-card-name {
   display: block;
-  font-size: 30rpx;
+  font-size: 28rpx;
   font-weight: bold;
   color: #1F2937;
   margin-bottom: 6rpx;
 }
 
-.value-card-desc {
+.dims-card-desc {
   display: block;
-  font-size: 24rpx;
+  font-size: 22rpx;
   color: #6B7280;
   line-height: 1.5;
 }

+ 179 - 159
cfc-frontend/pages/login/login.vue

@@ -4,53 +4,54 @@
     <view class="logo-section">
       <image class="logo" src="/static/logo.png" mode="aspectFit" @error="handleImageError"></image>
       <text class="app-name">浠艾福</text>
-      <text class="slogan">给全家的一站式幸福提案</text>
+      <text class="slogan">给全家的一站式主动健康方案</text>
       <text class="powered">亿图腾科技出品</text>
     </view>
 
-    <!-- 角色价值说明 -->
-    <view class="value-section">
-      <view class="value-title">你能得到什么</view>
-
-      <!-- 家长端 -->
-      <view class="role-card role-parent">
-        <view class="role-badge">
-          <text class="role-badge-icon">👨‍👩‍👧</text>
-          <text class="role-badge-text">家长</text>
-        </view>
-        <view class="role-items">
-          <view class="role-item"><text class="role-item-icon">📋</text><text class="role-item-text">一键创建家庭成长计划</text></view>
-          <view class="role-item"><text class="role-item-icon">📊</text><text class="role-item-text">跟踪孩子任务完成情况</text></view>
-          <view class="role-item"><text class="role-item-icon">🧠</text><text class="role-item-text">预约专业心智测评</text></view>
-          <view class="role-item"><text class="role-item-icon">🩺</text><text class="role-item-text">健康报告 AI 智能解析</text></view>
-        </view>
+    <!-- 主动健康理念 -->
+    <view class="concept-card">
+      <view class="concept-title">什么是主动健康</view>
+      <view class="concept-desc">
+        不等问题发生,先一步<span class="concept-highlight">识别风险</span>、<span class="concept-highlight">主动干预</span>、<span class="concept-highlight">持续评估</span>——把全家健康管理融入每一天。
       </view>
+    </view>
 
-      <!-- 孩子端 -->
-      <view class="role-card role-child">
-        <view class="role-badge role-badge-child">
-          <text class="role-badge-icon">👶</text>
-          <text class="role-badge-text">孩子</text>
-        </view>
-        <view class="role-items">
-          <view class="role-item"><text class="role-item-icon">🎯</text><text class="role-item-text">每日趣味任务挑战</text></view>
-          <view class="role-item"><text class="role-item-icon">🏆</text><text class="role-item-text">积分兑换心愿奖励</text></view>
-          <view class="role-item"><text class="role-item-icon">🧩</text><text class="role-item-text">专注力小游戏训练</text></view>
+    <!-- 主动健康闭环 -->
+    <view class="section-block">
+      <view class="section-label">主动健康闭环</view>
+      <view
+        v-for="(step, i) in loopSteps"
+        :key="step.name"
+        class="loop-step-wrap"
+      >
+        <view class="loop-step" :class="step.color">
+          <view class="loop-step-icon"><text>{{ step.icon }}</text></view>
+          <view class="loop-step-text">
+            <text class="loop-step-name">{{ step.name }}</text>
+            <text class="loop-step-desc">{{ step.desc }}</text>
+          </view>
         </view>
+        <view v-if="i < loopSteps.length - 1" class="loop-arrow"><text>↓</text></view>
       </view>
+      <view class="loop-cycle"><text>🔄 持续优化 · 循环往复,主动健康永不止步</text></view>
     </view>
 
     <!-- 五维能量 -->
-    <view class="wuxing-section">
-      <view class="wuxing-title">五维成长体系</view>
-      <view class="wuxing-tags">
-        <view class="wuxing-tag wuxing-body"><text class="wuxing-emoji">🌏</text>身·健康</view>
-        <view class="wuxing-tag wuxing-mind"><text class="wuxing-emoji">🔥</text>心·情绪</view>
-        <view class="wuxing-tag wuxing-wisdom"><text class="wuxing-emoji">⚡</text>智·赋能</view>
-        <view class="wuxing-tag wuxing-action"><text class="wuxing-emoji">🌿</text>行·习惯</view>
-        <view class="wuxing-tag wuxing-wealth"><text class="wuxing-emoji">💧</text>富·财商</view>
+    <view class="section-block">
+      <view class="section-label">五维能量 · 五行相生</view>
+      <view class="dims-card">
+        <view
+          v-for="dim in dimensions"
+          :key="dim.name"
+          class="dim-item"
+        >
+          <view class="dim-item-icon" :class="dim.color"><text>{{ dim.emoji }}</text></view>
+          <view class="dim-item-text">
+            <text class="dim-item-name">{{ dim.name }} · {{ dim.tag }}</text>
+            <text class="dim-item-desc">{{ dim.desc }}</text>
+          </view>
+        </view>
       </view>
-      <text class="wuxing-desc">五行平衡 · 全面评估全家健康状态</text>
     </view>
 
     <!-- 登录表单 -->
@@ -87,7 +88,7 @@
 </template>
 
 <script>
-import { wechatPhoneLogin, checkTeamInviteStatus, joinTeamByInvite, bindReferral, bindFamilyInviter } from '../../utils/api.js'
+import { wechatPhoneLogin, checkTeamInviteStatus, joinTeamByInvite, bindReferral } from '../../utils/api.js'
 
 export default {
   data() {
@@ -96,24 +97,28 @@ export default {
       agreed: false,
       nickname: '',
       avatar: '',
-      redirectUrl: ''
+      redirectUrl: '',
+      loopSteps: [
+        { icon: '🔍', name: '提前识别', desc: 'AI 多维评估,绘制健康基线', color: 'loop-indigo' },
+        { icon: '🛡️', name: '风险管控', desc: '分级管控,不让隐患成问题', color: 'loop-green' },
+        { icon: '⚡', name: '主动干预', desc: '任务与能量追踪,融入日常', color: 'loop-amber' },
+        { icon: '📊', name: '效果评估', desc: '档案记录轨迹,数据持续优化', color: 'loop-red' }
+      ],
+      dimensions: [
+        { emoji: '🌏', name: '身', tag: '根基永固', desc: '精准营养 · 健康报告 · 体质调理', color: 'dim-body' },
+        { emoji: '⚡', name: '智', tag: '赋能未来', desc: '心智测评 · 素质培养 · 成长档案', color: 'dim-wisdom' },
+        { emoji: '💧', name: '富', tag: '利他创富', desc: '创客生态 · 积分奖励', color: 'dim-wealth' },
+        { emoji: '🌿', name: '行', tag: '人际和谐', desc: '亲子关系 · 夫妻情感 · 社交人脉', color: 'dim-action' },
+        { emoji: '🔥', name: '心', tag: '大爱相随', desc: '情绪疏导 · 关系心理学', color: 'dim-mind' }
+      ]
     }
   },
   onLoad(options) {
-      options = options || {}
-      // 处理家庭邀请码(新流程:仅首次注册时绑定邀请人)
-      if (options.familyInviteCode) {
-        uni.setStorageSync('pendingFamilyInviteCode', options.familyInviteCode)
-      }
-      // 兼容旧邀请码参数
+      // 处理邀请码
       if (options.inviteCode) {
         uni.setStorageSync('inviteCode', options.inviteCode)
         uni.setStorageSync('inviteType', options.inviteType || 'family')
       }
-      // 推广码
-      if (options.refCode) {
-        uni.setStorageSync('pendingRefCode', options.refCode)
-      }
       // 存储登录后跳转地址
       if (options.redirect) {
         this.redirectUrl = decodeURIComponent(options.redirect)
@@ -193,50 +198,12 @@ export default {
       self.$store.commit('setFamilyId', data.familyId)
       uni.showToast({ title: '登录成功', icon: 'success' })
       self.loading = false
-
-      // 判断是否首次注册(后端返回 needsRoleSelect 或无 familyId)
-      var isNewUser = data.needsRoleSelect === true || !data.familyId
-
-      // 处理家庭邀请码(仅首次注册时绑定邀请人)
-      var familyInviteCode = uni.getStorageSync('pendingFamilyInviteCode')
-      if (familyInviteCode && isNewUser) {
-        // 新用户注册:绑定家庭邀请人
-        self.bindFamilyInviter(familyInviteCode).then(function() {
-          uni.removeStorageSync('pendingFamilyInviteCode')
-          self.handlePostLoginRouting()
-        }).catch(function() {
-          uni.removeStorageSync('pendingFamilyInviteCode')
-          self.handlePostLoginRouting()
-        })
-        return
-      }
-
-      // 非新用户或无家庭邀请码,清理存储并走原有流程
-      if (familyInviteCode) {
-        uni.removeStorageSync('pendingFamilyInviteCode')
-      }
-      
       if (data.needsRoleSelect) {
         uni.redirectTo({ url: '/pages/user-edit/user-edit?token=' + data.token + '&userId=' + data.userId + '&autoParent=true' })
       } else {
         self.handlePostLoginRouting()
       }
     },
-
-    // 绑定家庭邀请人(首次注册调用)
-    bindFamilyInviter: function(code) {
-      var self = this
-      return bindFamilyInviter(code).then(function(res) {
-        if (res && res.code === 200) {
-          console.log('首次注册绑定家庭邀请人成功', code)
-          uni.showToast({ title: '已关联邀请家庭', icon: 'success' })
-        } else if (res && res.code === 400) {
-          console.log('已有邀请人,跳过绑定')
-        }
-      }).catch(function(e) {
-        console.log('绑定家庭邀请人失败', e)
-      })
-    },
     handlePostLoginRouting() {
       const inviteCode = uni.getStorageSync('inviteCode')
       const inviteType = uni.getStorageSync('inviteType')
@@ -343,7 +310,7 @@ export default {
 <style scoped>
 .login-container {
   min-height: 100vh;
-  background: linear-gradient(160deg, #FFF7ED 0%, #FED7AA 60%, #F97316 100%);
+  background: linear-gradient(160deg, #FFF7ED 0%, #FED7AA 55%, #FB923C 100%);
   padding: 48rpx 40rpx 40rpx;
 }
 
@@ -373,7 +340,7 @@ export default {
 .slogan {
   display: block;
   font-size: 26rpx;
-  color: rgba(255, 255, 255, 0.85);
+  color: rgba(255, 255, 255, 0.88);
   margin-top: 8rpx;
 }
 
@@ -384,12 +351,41 @@ export default {
   margin-top: 12rpx;
 }
 
-/* ===== 角色价值说明 ===== */
-.value-section {
-  margin-bottom: 32rpx;
+/* ===== 主动健康理念 ===== */
+.concept-card {
+  background: rgba(255, 255, 255, 0.22);
+  border: 1rpx solid rgba(255, 255, 255, 0.35);
+  border-radius: 20rpx;
+  padding: 28rpx 32rpx;
+  margin-bottom: 36rpx;
+}
+
+.concept-title {
+  display: block;
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 12rpx;
+}
+
+.concept-desc {
+  font-size: 25rpx;
+  color: rgba(255, 255, 255, 0.92);
+  line-height: 1.7;
 }
 
-.value-title {
+.concept-highlight {
+  color: #FFD700;
+  font-weight: 600;
+}
+
+/* ===== 章节块 ===== */
+.section-block {
+  margin-bottom: 36rpx;
+}
+
+.section-label {
+  display: block;
   font-size: 28rpx;
   font-weight: 600;
   color: #fff;
@@ -397,107 +393,131 @@ export default {
   padding-left: 4rpx;
 }
 
-.role-card {
-  background: rgba(255, 255, 255, 0.92);
-  border-radius: 20rpx;
-  padding: 24rpx 28rpx;
-  margin-bottom: 16rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
+/* ===== 主动健康闭环 ===== */
+.loop-step-wrap {
+  margin-bottom: 6rpx;
 }
 
-.role-badge {
-  display: inline-flex;
+.loop-step {
+  display: flex;
   align-items: center;
-  gap: 8rpx;
-  background: linear-gradient(135deg, #FF8C42, #FF6B2D);
-  border-radius: 30rpx;
-  padding: 8rpx 20rpx;
-  margin-bottom: 20rpx;
+  gap: 20rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 22rpx 26rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
+  border-left: 6rpx solid #6366F1;
 }
 
-.role-badge-child {
-  background: linear-gradient(135deg, #FF6B9D, #FF3D7F);
-}
+.loop-indigo { border-left-color: #6366F1; }
+.loop-green { border-left-color: #10B981; }
+.loop-amber { border-left-color: #F59E0B; }
+.loop-red { border-left-color: #EF4444; }
 
-.role-badge-icon {
-  font-size: 28rpx;
+.loop-step-icon {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32rpx;
+  flex-shrink: 0;
 }
 
-.role-badge-text {
-  font-size: 26rpx;
-  font-weight: 600;
-  color: #fff;
-}
+.loop-indigo .loop-step-icon { background: rgba(99,102,241,0.12); }
+.loop-green .loop-step-icon { background: rgba(16,185,129,0.12); }
+.loop-amber .loop-step-icon { background: rgba(245,158,11,0.12); }
+.loop-red .loop-step-icon { background: rgba(239,68,68,0.12); }
 
-.role-items {
+.loop-step-text {
+  flex: 1;
   display: flex;
   flex-direction: column;
-  gap: 14rpx;
+  min-width: 0;
 }
 
-.role-item {
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
+.loop-step-name {
+  font-size: 29rpx;
+  font-weight: bold;
+  color: #1F2937;
+  margin-bottom: 4rpx;
 }
 
-.role-item-icon {
+.loop-step-desc {
+  font-size: 23rpx;
+  color: #6B7280;
+  line-height: 1.5;
+}
+
+.loop-arrow {
+  text-align: center;
+  padding: 8rpx 0;
   font-size: 28rpx;
-  flex-shrink: 0;
+  color: rgba(255,255,255,0.75);
 }
 
-.role-item-text {
-  font-size: 26rpx;
-  color: #374151;
-  line-height: 1.5;
+.loop-cycle {
+  text-align: center;
+  margin-top: 18rpx;
+  font-size: 22rpx;
+  color: rgba(255,255,255,0.85);
 }
 
 /* ===== 五维能量 ===== */
-.wuxing-section {
-  background: rgba(255, 255, 255, 0.92);
+.dims-card {
+  background: #fff;
   border-radius: 20rpx;
   padding: 28rpx;
-  margin-bottom: 32rpx;
-  text-align: center;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
 }
 
-.wuxing-title {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #1F2937;
-  margin-bottom: 20rpx;
+.dim-item {
+  display: flex;
+  align-items: center;
+  gap: 20rpx;
+  padding: 12rpx 0;
+}
+
+.dim-item + .dim-item {
+  border-top: 1rpx solid #F3F4F6;
 }
 
-.wuxing-tags {
+.dim-item-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 16rpx;
   display: flex;
-  flex-wrap: wrap;
+  align-items: center;
   justify-content: center;
-  gap: 12rpx;
-  margin-bottom: 16rpx;
+  font-size: 34rpx;
+  flex-shrink: 0;
 }
 
-.wuxing-tag {
-  font-size: 24rpx;
-  padding: 10rpx 20rpx;
-  border-radius: 30rpx;
-  color: #fff;
-  font-weight: 500;
-}
+.dim-body .dim-item-icon { background: rgba(255,140,66,0.15); }
+.dim-wisdom .dim-item-icon { background: rgba(99,102,241,0.15); }
+.dim-wealth .dim-item-icon { background: rgba(245,158,11,0.15); }
+.dim-action .dim-item-icon { background: rgba(16,185,129,0.15); }
+.dim-mind .dim-item-icon { background: rgba(255,107,157,0.15); }
 
-.wuxing-body { background: rgba(255, 140, 66, 0.85); }
-.wuxing-mind { background: rgba(255, 107, 157, 0.85); }
-.wuxing-wisdom { background: rgba(99, 102, 241, 0.85); }
-.wuxing-action { background: rgba(16, 185, 129, 0.85); }
-.wuxing-wealth { background: rgba(245, 158, 11, 0.85); }
+.dim-item-text {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+}
 
-.wuxing-emoji {
-  margin-right: 4rpx;
+.dim-item-name {
+  font-size: 29rpx;
+  font-weight: bold;
+  color: #1F2937;
+  margin-bottom: 4rpx;
 }
 
-.wuxing-desc {
-  font-size: 22rpx;
-  color: #9CA3AF;
+.dim-item-desc {
+  font-size: 23rpx;
+  color: #6B7280;
+  line-height: 1.5;
 }
 
 /* ===== 登录表单 ===== */
@@ -570,4 +590,4 @@ export default {
   color: #5B9BD5;
   text-decoration: underline;
 }
-</style>
+</style>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-05e781be4865c075bd60079469f2c2f46e9f0a11
+c3e3a946750ed2fd407841d91ef46e34d9654257

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1283",
+  "version": "1.0.1288",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1283",
+      "version": "1.0.1288",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1284",
+  "version": "1.0.1289",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 117 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,123 @@
 
 ---
 
+## v1.0.1288 (2026-09-04)
+
+### 样式
+- 右上角个人/全家切换按钮更醒目(渐变胶囊+阴影+放大)
+
+
+## v1.0.1287 (2026-09-04)
+
+### 其他
+- redesign: 重构登录前首页与登录页,引入主动健康闭环×五维框架
+
+
+## v1.0.1286 (2026-09-03)
+
+### 新功能
+- 个人/全家切换移到控件右上角,替换原徽章文案
+
+### 文档
+- 添加 log-to-human 团队级技能文档
+
+
+## v1.0.1285 (2026-09-03)
+
+### 新功能
+- 首页能量沙盘个人/全家切换始终显示,成员<2时点全家弹提示引导添加
+
+
+## v1.0.1284 (2026-09-03)
+
+### Bug 修复
+- health_reports 补 report_format 列(迁移289)+ auto bump
+
+
+## v1.0.1284 (2026-09-03)
+
+### Bug 修复
+- health_reports 补 report_format 列(迁移289)+ auto bump
+
+
+## v1.0.1284 (2026-09-03)
+
+### 新功能
+- 健康报告收集功能 - ReportCollectService + draft 草稿 + 小程序上报页面
+- 能量沙盘个人/全家切换移到右上角,单成员时弹提示引导添加
+- extend search to title+summary+tags; fix compile errors in FoodRecommendService and HealthReportController
+- 家庭运营增加优惠券列表和使用记录(后端/family-coupons + /family-coupon-usage,前端弹窗展示)
+- 管理端商品编辑页新增可选赠品配置区块
+- 优化谷禾菌群报告详情页展示与饮食推荐
+- 小程序商品详情赠品选择区 + checkout 透传 giftItemIds
+- createMultiItem 单商品路径支持赠品(抽取 processGifts 共享方法)
+- handlePaymentSuccess 追加赠品发放逻辑
+- create 流程注入赠品校验与 product_order_gifts 记录
+- 新增管理端赠品配置接口(/api/admin/product-gift/save + /list)
+- 新增 ProductGiftService(配置保存/查询逻辑)
+- 新增三张赠品表实体类与Mapper接口
+- 建三张赠品相关表迁移(product_gift_configs/items/order_gifts)
+
+### 其他
+- 知识中心-知识分类,修复视频上传异常问题
+- 知识中心-知识分类,修复视频上传异常问题
+- 知识中心-知识分类,修复视频上传异常问题
+- 修复视频上传异常问题
+- 优化修复
+- - FoodRecommendService: add missing import java.math.BigDecimal
+- - HealthReportController: remove undefined 'parsed' reference in buildPayloadFromLgData
+- 
+- 切换开关仅依赖 hasMultipleMembers(有 2+ 成员时显示),
+- familyUnlocked 仍作为 prop 保留,不影响 GateLock 门禁展示。
+- 
+- - child-index: loadData 中 getChildren() 改为 getFamilyMemberList({visibleOnly:true}) 一次调用,同步设置 familyMembersVisible,跳过后续 loadFamilyMembersVisible()
+- - parent-index: dashboard 成功路径同步设置 familyMembersVisible + _membersVisibleSet,降级路径合并 getFamilyMemberList+getChildren 为单次调用并派生 children
+- 
+- - 首页 index-home: checkMemberStatus 移入 loggedInInitDone 守卫,onLoad/onShow 用 _onLoadFired 防首次双发
+- - 首页 index-home: loadFamilyFeed 循环前过滤孩子成员,避免对家长发起 getTaskHistory
+- - 全页面: energy/hierarchy/share/survey-history/parent-index/join/notification/onboarding/address/cart/checkout/shop-index/daily-tasks/service-tasks/teacher-home/wealth-cf 统一 _onLoadFired 模式
+- - parent-index: dashboard 降级路径同步设置 familyMembersVisible,避免重复请求 getFamilyMemberList
+- - utils/api.js: 全局请求去重(同 URL+method+body 3 秒窗口复用 Promise),401 重放跳过去重
+- - utils/api.js: getChildren/getUserBadges/getGrowthReport/getTaskHistoryByCategory 等别名统一委托规范函数
+- - 新增 scripts/audit-duplicate-api-calls.js CI 审计脚本(0 error)
+- - AGENTS.md: 新增规则 1-4 + 4a/4b/4c(生命周期双发、同接口单封装、N+1 过滤、getChildren 废弃)
+- 
+- - 疾病风险内联展示并颜色区分(注意/低风险),修复详情页与风险页统计不一致
+- - 疾病风险页知识库入口改为 ? 图标放疾病名之前
+- - 菌群详情页修复分类 tab 过滤(中文 category 映射英文 key)
+- - 菌群风险分组(肥胖/抑郁/过敏等)改手风琴展示
+- - 饮食推荐页:去重标题、按推荐值分级背景色(<-30红/>30绿/每10一档)、去蛋白能量固定信息、增加食材搜索、复用知识库弹窗
+- - 成长页饮食推荐复用食材页,不传 reportId 取最新建议
+- 
+- feat(product-gift): 商品详情附带当前用户等级赠品配置
+- - 涵盖:数据库迁移、实体/Mapper、ProductGiftService、管理端接口、
+-   下单校验+写记录、支付成功发放、商品详情装配、小程序前端、管理端前端
+- 
+-   - 3张新表:product_gift_configs / product_gift_items / product_order_gifts
+-   - 与现有 product_gift_rules(赠送会员)完全独立
+-   - 赠品类型:商品 或 CF值,按会员等级配置,用户可多选
+-   - 下单时写入订单赠品记录(fulfilled=0),支付成功后发放
+- - 更新 PROJECT-OVERVIEW.md §4.2 电商与供应体系,记录新设计稿
+- 
+
+### Bug 修复
+- 补全 reportFormat 字段到 Payload DTO 和 HealthReport 实体,修复编译失败
+- self-check-result :key 移除字符串拼接表达式 'ma'+i,避免非 h5 平台编译警告
+- 移除能量沙盘切换开关的 familyUnlocked 门禁条件
+- 根治 family/member/list 与 tasks/history 重复调用——index-home loadFamilyFeed 改为只查第一个孩子、parent-index/child-index 合并 getChildren+loadFamilyMembersVisible 为单次 getFamilyMemberList
+- 根治重复API调用——首页合并 family/member/list 与 membership/my、全页面 onLoad/onShow 防双发 + 请求层 3 秒去重 + getChildren 废弃委托
+- processGifts 下单时复核赠品商品库存
+- 修复 toggleGift 后多余逗号语法错误
+- class 改用 _selected 状态字段,移除方法调用
+
+### 重构
+- 清理遗留项(未用注入/静默catch/Set去重/UUID导入/参数校验/Javadoc)
+
+### 文档
+- 商品可选赠品功能实施计划
+- 商品可选赠品功能设计稿(product-gift-selection-design)
+
+
 ## v1.0.1283 (2026-09-02)
 
 ### 新功能

+ 118 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1283
+> 当前版本: v1.0.1288
 
 ## 历史版本
 
@@ -8,6 +8,123 @@
 
 ---
 
+## v1.0.1288 (2026-09-04)
+
+### 样式
+- 右上角个人/全家切换按钮更醒目(渐变胶囊+阴影+放大)
+
+
+## v1.0.1287 (2026-09-04)
+
+### 其他
+- redesign: 重构登录前首页与登录页,引入主动健康闭环×五维框架
+
+
+## v1.0.1286 (2026-09-03)
+
+### 新功能
+- 个人/全家切换移到控件右上角,替换原徽章文案
+
+### 文档
+- 添加 log-to-human 团队级技能文档
+
+
+## v1.0.1285 (2026-09-03)
+
+### 新功能
+- 首页能量沙盘个人/全家切换始终显示,成员<2时点全家弹提示引导添加
+
+
+## v1.0.1284 (2026-09-03)
+
+### Bug 修复
+- health_reports 补 report_format 列(迁移289)+ auto bump
+
+
+## v1.0.1284 (2026-09-03)
+
+### Bug 修复
+- health_reports 补 report_format 列(迁移289)+ auto bump
+
+
+## v1.0.1284 (2026-09-03)
+
+### 新功能
+- 健康报告收集功能 - ReportCollectService + draft 草稿 + 小程序上报页面
+- 能量沙盘个人/全家切换移到右上角,单成员时弹提示引导添加
+- extend search to title+summary+tags; fix compile errors in FoodRecommendService and HealthReportController
+- 家庭运营增加优惠券列表和使用记录(后端/family-coupons + /family-coupon-usage,前端弹窗展示)
+- 管理端商品编辑页新增可选赠品配置区块
+- 优化谷禾菌群报告详情页展示与饮食推荐
+- 小程序商品详情赠品选择区 + checkout 透传 giftItemIds
+- createMultiItem 单商品路径支持赠品(抽取 processGifts 共享方法)
+- handlePaymentSuccess 追加赠品发放逻辑
+- create 流程注入赠品校验与 product_order_gifts 记录
+- 新增管理端赠品配置接口(/api/admin/product-gift/save + /list)
+- 新增 ProductGiftService(配置保存/查询逻辑)
+- 新增三张赠品表实体类与Mapper接口
+- 建三张赠品相关表迁移(product_gift_configs/items/order_gifts)
+
+### 其他
+- 知识中心-知识分类,修复视频上传异常问题
+- 知识中心-知识分类,修复视频上传异常问题
+- 知识中心-知识分类,修复视频上传异常问题
+- 修复视频上传异常问题
+- 优化修复
+- - FoodRecommendService: add missing import java.math.BigDecimal
+- - HealthReportController: remove undefined 'parsed' reference in buildPayloadFromLgData
+- 
+- 切换开关仅依赖 hasMultipleMembers(有 2+ 成员时显示),
+- familyUnlocked 仍作为 prop 保留,不影响 GateLock 门禁展示。
+- 
+- - child-index: loadData 中 getChildren() 改为 getFamilyMemberList({visibleOnly:true}) 一次调用,同步设置 familyMembersVisible,跳过后续 loadFamilyMembersVisible()
+- - parent-index: dashboard 成功路径同步设置 familyMembersVisible + _membersVisibleSet,降级路径合并 getFamilyMemberList+getChildren 为单次调用并派生 children
+- 
+- - 首页 index-home: checkMemberStatus 移入 loggedInInitDone 守卫,onLoad/onShow 用 _onLoadFired 防首次双发
+- - 首页 index-home: loadFamilyFeed 循环前过滤孩子成员,避免对家长发起 getTaskHistory
+- - 全页面: energy/hierarchy/share/survey-history/parent-index/join/notification/onboarding/address/cart/checkout/shop-index/daily-tasks/service-tasks/teacher-home/wealth-cf 统一 _onLoadFired 模式
+- - parent-index: dashboard 降级路径同步设置 familyMembersVisible,避免重复请求 getFamilyMemberList
+- - utils/api.js: 全局请求去重(同 URL+method+body 3 秒窗口复用 Promise),401 重放跳过去重
+- - utils/api.js: getChildren/getUserBadges/getGrowthReport/getTaskHistoryByCategory 等别名统一委托规范函数
+- - 新增 scripts/audit-duplicate-api-calls.js CI 审计脚本(0 error)
+- - AGENTS.md: 新增规则 1-4 + 4a/4b/4c(生命周期双发、同接口单封装、N+1 过滤、getChildren 废弃)
+- 
+- - 疾病风险内联展示并颜色区分(注意/低风险),修复详情页与风险页统计不一致
+- - 疾病风险页知识库入口改为 ? 图标放疾病名之前
+- - 菌群详情页修复分类 tab 过滤(中文 category 映射英文 key)
+- - 菌群风险分组(肥胖/抑郁/过敏等)改手风琴展示
+- - 饮食推荐页:去重标题、按推荐值分级背景色(<-30红/>30绿/每10一档)、去蛋白能量固定信息、增加食材搜索、复用知识库弹窗
+- - 成长页饮食推荐复用食材页,不传 reportId 取最新建议
+- 
+- feat(product-gift): 商品详情附带当前用户等级赠品配置
+- - 涵盖:数据库迁移、实体/Mapper、ProductGiftService、管理端接口、
+-   下单校验+写记录、支付成功发放、商品详情装配、小程序前端、管理端前端
+- 
+-   - 3张新表:product_gift_configs / product_gift_items / product_order_gifts
+-   - 与现有 product_gift_rules(赠送会员)完全独立
+-   - 赠品类型:商品 或 CF值,按会员等级配置,用户可多选
+-   - 下单时写入订单赠品记录(fulfilled=0),支付成功后发放
+- - 更新 PROJECT-OVERVIEW.md §4.2 电商与供应体系,记录新设计稿
+- 
+
+### Bug 修复
+- 补全 reportFormat 字段到 Payload DTO 和 HealthReport 实体,修复编译失败
+- self-check-result :key 移除字符串拼接表达式 'ma'+i,避免非 h5 平台编译警告
+- 移除能量沙盘切换开关的 familyUnlocked 门禁条件
+- 根治 family/member/list 与 tasks/history 重复调用——index-home loadFamilyFeed 改为只查第一个孩子、parent-index/child-index 合并 getChildren+loadFamilyMembersVisible 为单次 getFamilyMemberList
+- 根治重复API调用——首页合并 family/member/list 与 membership/my、全页面 onLoad/onShow 防双发 + 请求层 3 秒去重 + getChildren 废弃委托
+- processGifts 下单时复核赠品商品库存
+- 修复 toggleGift 后多余逗号语法错误
+- class 改用 _selected 状态字段,移除方法调用
+
+### 重构
+- 清理遗留项(未用注入/静默catch/Set去重/UUID导入/参数校验/Javadoc)
+
+### 文档
+- 商品可选赠品功能实施计划
+- 商品可选赠品功能设计稿(product-gift-selection-design)
+
+
 ## v1.0.1283 (2026-09-02)
 
 ### 新功能

+ 5 - 4
cfc-web/src/views/Families.vue

@@ -150,7 +150,7 @@
     <!-- 发放优惠券弹窗 -->
     <el-dialog
       title="发放优惠券"
-      :visible.sync="couponDialogVisible"
+      :visible.sync="issueDialogVisible"
       width="500px"
     >
       <el-form :model="couponForm" label-width="100px">
@@ -172,7 +172,7 @@
         </el-form-item>
       </el-form>
       <div slot="footer">
-        <el-button @click="couponDialogVisible = false">取消</el-button>
+        <el-button @click="issueDialogVisible = false">取消</el-button>
         <el-button type="primary" @click="confirmIssueCoupon" :loading="couponSubmitting">确认发放</el-button>
       </div>
     </el-dialog>
@@ -314,6 +314,7 @@ export default {
         children: []
       },
       couponDialogVisible: false,
+      issueDialogVisible: false,
       couponForm: {
         familyId: null,
         couponId: null,
@@ -455,7 +456,7 @@ export default {
         couponId: null,
         quantity: 1
       }
-      this.couponDialogVisible = true
+      this.issueDialogVisible = true
       try {
         const res = await getCouponList({ page: 1, size: 999 })
         this.couponOptions = res.data.records || res.data.list || res.data || []
@@ -482,7 +483,7 @@ export default {
           quantity: this.couponForm.quantity
         })
         this.$message.success('发放成功')
-        this.couponDialogVisible = false
+        this.issueDialogVisible = false
       } catch (error) {
         this.$message.error(error.message || '发放失败')
       } finally {

+ 114 - 0
docs/skills/log-to-human/SKILL.md

@@ -0,0 +1,114 @@
+---
+name: log-to-human
+description: Use when reporting internal execution progress to users — transform raw tool-call logs ([工具] read, [思考], ⏳ 进展) into plain-language status updates that anyone can understand without technical context.
+---
+
+# 内部日志 → 人话转换规则
+
+## 核心原则
+
+**用户不需要知道你在用什么工具。他们只需要知道你在做什么、结果如何。**
+
+把技术执行记号翻译成业务语言,去掉噪音,保留结论。
+
+## 转换规则
+
+### 工具调用记号([工具] xxx)
+
+| 原始记号 | 转换后 | 说明 |
+|---------|--------|------|
+| `[工具] read` | 读取了 `文件路径` | 只报告读了什么,不报告读了几次 |
+| `[工具] bash` | 执行了 `命令摘要` | 命令太长时只报意图(如"检查 git 状态") |
+| `[工具] grep` | 搜索了 `关键词` | 报告找到了什么,不报告搜索过程 |
+| `[工具] edit` | 修改了 `文件:行号` | 报告改了什么,不报告编辑过程 |
+| `[工具] write` | 新建了 `文件` | |
+| `[工具] skill` | 调用了 `技能名` 技能 | 通常不需要报告 |
+
+### 思考记号([思考])
+
+把思考内容压缩成**结论句**,去掉推理过程:
+
+```
+原始:[思考] I need to check the git status first. Let me run git status to see if there are any uncommitted changes. Then I'll check the remote branches...
+转换:先确认本地状态,再检查远程差异。
+```
+
+### 进展记号(⏳ 进展)
+
+合并同类操作,按"阶段"分组汇报:
+
+```
+原始:⏳ 进展:[工具] read | [工具] bash | [工具] bash | [思考] Now I can see...
+转换:正在确认项目状态(已完成读取文件 + 执行 git 命令)。
+```
+
+## 汇报格式模板
+
+### 一句话进展
+```
+【进展】xxx —— 正在进行 yyy,预计 zzz。
+```
+
+### 阶段性进展
+```
+【进展】
+1. 已确认:xxx(根因/现状)
+2. 正在做:yyy(具体动作)
+3. 下一步:zzz
+```
+
+### 完成汇报
+```
+【完成】xxx 已处理完毕。
+变更:y 个文件,z 行改动。
+验证:编译通过 / 测试通过。
+```
+
+## 禁止事项
+
+❌ **不要报告**:
+- 工具调用次数("调用了 3 次 read")
+- 具体的命令输出内容(除非是错误)
+- 工具名本身(用户不关心你用 grep 还是 find)
+- 冗余的思考过程
+
+❌ **不要说**:
+- "正在读取文件..."(除非在等待)
+- "执行命令中..."(除非耗时超过 5 秒)
+- "思考中..."(思考过程不需要报告)
+
+## 示例
+
+### 示例 1:单次进展
+```
+原始:⏳ 进展:[工具] read | [工具] bash | [工具] bash
+转换:已确认项目状态(读取了关键文件 + 执行了 git 命令)。
+```
+
+### 示例 2:问题解决
+```
+原始:⏳ 进展:[工具] read | [工具] bash | [思考] I found the root cause...
+转换:找到根因 —— `xxx` 方法缺少空值判断,正在修复。
+```
+
+### 示例 3:多步排查
+```
+原始:⏳ 进展:[工具] read | [工具] read | [工具] grep | [工具] bash | [工具] bash
+转换:已定位问题到 `service/xxx.java:78`,正在修复。
+```
+
+### 示例 4:完成汇报
+```
+原始:所有工具调用完成,提交成功
+转换:【完成】已修复并推送。3 个文件,+45/-12 行。
+```
+
+## 快速参考
+
+| 场景 | 正确说法 | 错误说法 |
+|------|---------|---------|
+| 读取文件 | "确认了 xxx 的逻辑" | "[工具] read 读取了 xxx" |
+| 执行命令 | "检查了 git 状态" | "[工具] bash 执行了 git status" |
+| 搜索结果 | "确认了 yyy 不存在" | "[工具] grep 搜索了 yyy,找到 3 处" |
+| 修改代码 | "修复了 xxx" | "[工具] edit 修改了 xxx:78" |
+| 思考过程 | (直接报告结论) | "[思考] 我需要考虑..." |