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

Merge origin/cfclub (6 commits): 整合 gates.vue/growth-main 修复与 changelog

冲突解决:远端对 DatabaseInitializer.java/schema.sql 的改动均为 admin_menu 维护
(种子parent_id重编号 + 迁移241补充admin_menu菜单),按既定决策保留本地 sys_menu
体系、继续排除 admin_menu;其余文件(gates.vue/growth-main/CHANGELOG)自动合并
asus 1 месяц назад
Родитель
Сommit
fa0f6012a3

+ 79 - 0
AGENTS.md

@@ -184,6 +184,85 @@ python C:\code\cfc\publish_v11_stable.py
 - ❌ 暴露客户隐私(姓名/学校/家庭住址)
 - ❌ 缺少具体转变过程的描述
 
+## 公众号数据驱动优化指南(基于后台分析)
+
+> 数据来源:`https://mp.weixin.qq.com/misc/appmsganalysis?action=report&type=daily_v2` 内容分析页面
+
+### 流量构成(近30天)
+
+| 渠道 | 占比 | 含义 |
+|------|:----:|------|
+| 朋友圈 | ~33% | 文章有转发价值,主动传播能力 |
+| 搜一搜 | ~30% | 标题关键词SEO匹配度 |
+| 公众号消息 | ~16% | 粉丝主动打开率 |
+| 聊天会话 | ~11% | 点对点分享 |
+| 公众号主页 | ~10% | 关注后/查看主页 |
+| 推荐 | ~4% | 微信推荐系统曝光 |
+| 其它 | ~2% | |
+
+**关键判断**:公众号流量严重依赖**朋友圈分享**和**搜一搜SEO**,粉丝主动打开率低(16%),推荐系统几乎不推荐。
+
+### 标题公式(基于高阅读文章验证)
+
+#### ✅ 高阅读标题特征(30天TOP5)
+
+| 文章 | 阅读 | 特征 |
+|------|:---:|------|
+| 几十亿人看他跳舞,但他23年没刷过一道题 | 91 | 数字+冲突+反差 |
+| 妈妈,我不想上学:38.6%的孩子在替家长喊疼 | 50 | 数字+痛点+数据 |
+| 61.7%孩子用AI,20.5%正在变笨 | 40 | 双数字+颠覆认知 |
+| AI时代,还要让孩子背诵吗? | 36 | 争议性反问 |
+| 当孩子在说「没意思」——四无时代 | 31 | 痛点+概念包装 |
+
+#### 标题模板
+
+```
+[数字] + [冲突/反差] + [悬念/痛点]   →  "61.7%孩子用AI,20.5%正在变笨"
+[场景] + [反转] + [结论]              →  "几十亿人看他跳舞,但他23年没刷过一道题"
+[数据] + [痛点] + [解决方案暗示]       →  "38.6%的孩子在替家长喊疼"
+```
+
+#### ❌ 低阅读标题教训
+
+- 标题过长(>25字):#53「女儿超常发挥50分,妈妈却偷改了她的志愿」32字,仅18阅读
+- 标题偏平无冲突:「好无聊」「溺水是无声的」— 缺乏转发动力
+- 无关键词:标题不含"孩子""家长""教育"等高频搜索词,搜一搜匹配差
+
+#### 标题检查清单
+
+- [ ] 标题 ≤25字符(建议20字以内)
+- [ ] 含数字(百分比/次数/分数等)
+- [ ] 含冲突/反差/反转("你以为...其实...")
+- [ ] 前12字有吸引力(微信推荐展示前12字)
+- [ ] 含高频搜索词(孩子/家长/教育/青春期/暑假等)
+- [ ] 不含"愿每个X"式收束
+
+### 内容类型策略
+
+| 类型 | 建议占比 | 最佳案例 | 特点 |
+|------|:--------:|----------|------|
+| 案例故事型 | 40% | 身体如何塑造心智(91阅读) | 人物+反差,朋友圈传播力最强 |
+| 数据热点型 | 30% | 61.7%孩子用AI(40阅读) | 数据+热点话题,搜一搜匹配高 |
+| 干货实操型 | 20% | 是什么决定学习力(16阅读) | 稳定基本盘,粉丝粘性 |
+| 产品推广型 | 10% | 公益讲座/测评推广 | 控制在≤15% |
+
+### 写作前必查(补充)
+
+除现有自检清单外,额外检查:
+
+- [ ] 标题是否含数字?不含 → 加数字再发布
+- [ ] 标题是否含"孩子""家长"关键词?不含 → 调整标题
+- [ ] 前12字是否足够吸引人?太平淡 → 前移冲突点
+- [ ] 文章是否有"转发价值"?(读了想分享给朋友/家人?)
+
+### 发布后追踪
+
+- 发布后7天,登录 `数据分析 → 内容分析` 查看阅读量
+- 阅读<30 → 标题/选题有问题,下次避免同类
+- 阅读30-60 → 正常水平
+- 阅读>60 → 好标题/好选题,总结可复用的点
+- 关注"推荐"渠道占比:若持续<5%,说明标题缺乏爆点
+
 ## COMMANDS
 
 ```bash

+ 37 - 33
cfc-frontend/pages/growth-main/index.vue

@@ -144,14 +144,15 @@
       <view class="section">
         <view class="progress-card">
           <view class="progress-header">
-            <text class="progress-title">今日干预进度</text>
+            <text class="progress-title">今日成长</text>
             <text class="progress-date">{{ todayDate }}</text>
           </view>
-          <view class="progress-item" v-for="(item, idx) in progressItems" :key="getProgressKey(item)">
-            <text class="progress-icon">{{ item.icon }}</text>
-            <text class="progress-label">{{ item.label }}</text>
-            <text class="progress-status" :class="item.done ? 'status-done' : 'status-pending'">{{ item.done ? '✔ 已完成' : '' }}</text>
-            <text class="progress-btn" v-if="!item.done" @click="handleProgressClick(item)">{{ item.actionText || '补录' }}</text>
+          <view class="progress-grid" v-if="progressItems.length > 0">
+            <view class="progress-grid-item" v-for="(item, idx) in progressItems" :key="getProgressKey(item)" @click="handleProgressClick(item)">
+              <text class="progress-grid-icon">{{ item.icon }}</text>
+              <text class="progress-grid-label">{{ item.label }}</text>
+              <text class="progress-grid-status" :class="item.done ? 'status-done' : 'status-pending'">{{ item.done ? '✔' : (item.actionText || '去记录') }}</text>
+            </view>
           </view>
           <view class="progress-footer">
             <text class="footer-text">今日完成 {{ doneCount }}/{{ totalCount }}</text>
@@ -169,16 +170,6 @@
           <text class="action-arrow">›</text>
         </view>
       </view>
-      <view class="section">
-        <view class="action-card" @click="goToFoods">
-          <text class="action-icon">🍽️</text>
-          <view class="action-info">
-            <text class="action-title">饮食推荐</text>
-            <text class="action-desc">基于菌群报告的个性化食材建议</text>
-          </view>
-          <text class="action-arrow">›</text>
-        </view>
-      </view>
 
       <!-- 推荐文章 -->
       <view class="section" v-if="healthArticles.length > 0 || growthArticles.length > 0">
@@ -289,7 +280,7 @@
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
-import { healthCheckinList, getDietDailyRecords, getSurveyStatus, getSurveyHistory, getFoodCautionList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList } from '../../utils/api.js'
+import { healthCheckinList, getHealthMealList, getSurveyStatus, getSurveyHistory, getFoodCautionList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList, productList } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 export default {
   components: { PageBanner, LoginGuideCard, DimensionActivities },
@@ -365,32 +356,39 @@ export default {
       try { await self.loadProgressItems() } catch(e) {}
       try { await self.loadCautionData() } catch(e) {}
       try { await self.loadGrowthRecommendations() } catch(e) {}
+      try { await self.loadRecommendedProducts() } catch(e) {}
       try { await self.loadSurveyData() } catch(e) {}
       try { await self.loadMilestones() } catch(e) {}
     },
-    async loadProgressItems() {
+    async loadRecommendedProducts() {
       var self = this
-      var items = []
-      var checkinDone = false
       try {
-        var ciRes = await healthCheckinList({ yearMonth: self.todayDate.substring(0, 7) })
-        if (ciRes.code === 200 && ciRes.data) {
-          for (var ci = 0; ci < ciRes.data.length; ci++) { if (ciRes.data[ci] && self.formatDateKey(ciRes.data[ci].checkinDate) === self.todayDate) { checkinDone = true; break } }
+        var res = await productList({ page: 1, size: 6 })
+        if (res && res.code === 200 && res.data) {
+          var list = (res.data.list || res.data.records || res.data).slice(0, 6)
+          self.healthProducts = list
+          self.growthProducts = []
         }
       } catch(e) {}
-      items.push({ key: 'checkin', icon: '✅', label: '今日打卡', done: checkinDone, actionText: '打卡' })
-      // 饮食记录:daily 接口返回 {date, records, ...},records 才是数组
+    },
+    async loadProgressItems() {
+      var self = this
+      var items = []
+      // 饮食记录:health/meal/list 返回当日记录数组
       var dietDone = false
       try {
-        var dRes = await getDietDailyRecords({ date: self.todayDate })
-        var dRecords = (dRes.code === 200 && dRes.data && dRes.data.records) ? dRes.data.records : []
-        if (dRecords.length > 0) dietDone = true
+        var dParams = self.memberId ? { memberId: self.memberId } : {}
+        var dRes = await getHealthMealList(dParams)
+        var dList = (dRes.code === 200 && dRes.data) ? dRes.data : []
         self.trackingData.diet = []
-        for (var di = 0; di < dRecords.length; di++) {
-          var dr = dRecords[di]
+        for (var di = 0; di < dList.length; di++) {
+          var dr = dList[di]
           if (!dr) continue
-          var foods = dr.userConfirmedFoods || dr.aiRecognizedFoods || dr.notes || ''
-          self.trackingData.diet.push({ _id: dr.id, _date: self.mealTypeLabel(dr.mealType), _content: foods || '已记录' })
+          if (self.formatDateKey(dr.createdAt || dr.recordDate) === self.todayDate) dietDone = true
+          if (di < 5) {
+            var foods = dr.foodNames || dr.mealContent || dr.notes || ''
+            self.trackingData.diet.push({ _id: dr.id, _date: self.mealTypeLabel(dr.mealType), _content: foods || '已记录' })
+          }
         }
       } catch(e) {}
       items.push({ key: 'diet', icon: '🍽️', label: '饮食记录', done: dietDone, actionText: '补录' })
@@ -447,7 +445,6 @@ export default {
         }
       } catch(e) {}
       items.push({ key: 'emotion', icon: '😊', label: '心情记录', done: emotionDone, actionText: '记录' })
-      items.push({ key: 'survey', icon: '📋', label: '本周调研', done: self.allDone, actionText: self.allDone ? '' : '开始' })
       self.progressItems = items.filter(function(it) { return it && it.key })
       self.incompleteCount = 0
       for (var pi = 0; pi < items.length; pi++) if (items[pi] && !items[pi].done) self.incompleteCount++
@@ -567,6 +564,13 @@ export default {
 .progress-icon{font-size:28rpx;margin-right:12rpx;flex-shrink:0}
 .progress-label{font-size:26rpx;color:#333;flex:1}
 .progress-status{font-size:22rpx;margin-right:8rpx;flex-shrink:0}
+.progress-grid{display:flex;justify-content:space-between}
+.progress-grid-item{flex:1;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20rpx 8rpx;background:#FFF7ED;border-radius:16rpx;margin-right:12rpx}
+.progress-grid-item:last-child{margin-right:0}
+.progress-grid-item:active{opacity:0.85;background:#FFEDD5}
+.progress-grid-icon{font-size:40rpx;margin-bottom:8rpx}
+.progress-grid-label{font-size:24rpx;color:#333;margin-bottom:8rpx;text-align:center}
+.progress-grid-status{font-size:20rpx;flex-shrink:0}
 .status-done{color:#10B981}
 .status-pending{color:#ccc}
 .progress-btn{font-size:22rpx;color:#F97316;padding:4rpx 16rpx;border:1rpx solid #F97316;border-radius:20rpx;flex-shrink:0}

+ 10 - 8
cfc-frontend/pages/profile-extra/gates.vue

@@ -3,7 +3,7 @@
   <view class="page-section" v-if="mainGates.length > 0">
     <text class="section-title">主线关卡</text>
     <view class="gate-list">
-      <view class="gate-card" v-for="(g, i) in mainGates" :key="getGateKey(g, i)"
+      <view class="gate-card" v-for="(g, i) in mainGates" :key="getMainGateKey(g, i)"
         :class="'gate-card--' + (g.status === 'UNLOCKED' ? 'done' : (g.status === 'CURRENT' ? 'current' : 'locked'))">
         <view class="gate-status-icon">
           <text v-if="g.status === 'UNLOCKED'">✅</text>
@@ -15,7 +15,7 @@
           <text class="gate-desc">{{ g.description }}</text>
           <view class="gate-reward" v-if="g.rewardPoints > 0 || g.rewardEnergy > 0">
             <text class="reward-label">奖励:</text>
-            <text class="reward-points" v-if="g.rewardPoints > 0">+{{ g.rewardPoints }} CF值</text>
+            <text class="reward-points" v-if="g.rewardPoints > 0">+{{ g.rewardPoints }}积分</text>
             <text class="reward-energy" v-if="g.rewardEnergy > 0">+{{ g.rewardEnergy }}能量</text>
           </view>
         </view>
@@ -32,7 +32,7 @@
     <text class="section-title">推荐挑战</text>
     <view class="gate-list">
       <view class="gate-card" :class="'gate-card--' + (g.status === 'UNLOCKED' ? 'done' : 'current')"
-        v-for="(g, i) in softGates" :key="getGateKey(g, i)">
+        v-for="(g, i) in softGates" :key="getSoftGateKey(g, i)">
         <view class="gate-status-icon">
           <text v-if="g.status === 'UNLOCKED'">✅</text>
           <text v-else>🎯</text>
@@ -42,7 +42,7 @@
           <text class="gate-desc">{{ g.description }}</text>
           <view class="gate-reward" v-if="g.rewardPoints > 0 || g.rewardEnergy > 0">
             <text class="reward-label">奖励:</text>
-            <text class="reward-points" v-if="g.rewardPoints > 0">+{{ g.rewardPoints }} CF值</text>
+            <text class="reward-points" v-if="g.rewardPoints > 0">+{{ g.rewardPoints }}积分</text>
             <text class="reward-energy" v-if="g.rewardEnergy > 0">+{{ g.rewardEnergy }}能量</text>
           </view>
         </view>
@@ -57,7 +57,7 @@
   <view class="page-section" v-if="valueGates.length > 0">
     <text class="section-title">增值关卡</text>
     <view class="gate-list">
-      <view class="gate-card gate-card--value" v-for="(g, i) in valueGates" :key="getGateKey(g, i)">
+      <view class="gate-card gate-card--value" v-for="(g, i) in valueGates" :key="getValueGateKey(g, i)">
         <view class="gate-status-icon">
           <text v-if="g.status === 'UNLOCKED'">✅</text>
           <text v-else>💎</text>
@@ -67,7 +67,7 @@
           <text class="gate-desc">{{ g.description }}</text>
           <view class="gate-reward" v-if="g.rewardPoints > 0 || g.rewardEnergy > 0">
             <text class="reward-label">奖励:</text>
-            <text class="reward-points" v-if="g.rewardPoints > 0">+{{ g.rewardPoints }} CF值</text>
+            <text class="reward-points" v-if="g.rewardPoints > 0">+{{ g.rewardPoints }}积分</text>
             <text class="reward-energy" v-if="g.rewardEnergy > 0">+{{ g.rewardEnergy }}能量</text>
           </view>
         </view>
@@ -110,7 +110,6 @@ export default {
     this.loadData()
   },
   methods: {
-    getGateKey: function(g, i) { return (g && g.id) ? g.id : i },
     loadData: function() {
       var self = this
       self.loading = true
@@ -123,6 +122,9 @@ export default {
         self.loading = false
       })
     },
+    getMainGateKey: function(g, i) { return g.id || i },
+    getSoftGateKey: function(g, i) { return 'soft' + (g.id || i) },
+    getValueGateKey: function(g, i) { return 'val' + (g.id || i) },
     goGateAction: function(g) {
       var type = g.gateType
       var url = ''
@@ -131,7 +133,7 @@ export default {
           url = '/pages/family/self-check'
           break
         case 'INVITE_FAMILY':
-          url = '/pages/profile-extra/family-members'
+          url = '/pages/family/address-edit'
           break
         case 'CREATE_FAMILY_MEMBER':
           url = '/pages/profile-extra/family-members'

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-133cb9fd4fce11976757af441e8a9bdc860b7138
+0563c7f6d8618367a5633a88874bcb98ab57d094

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

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1051",
+  "version": "1.0.1054",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1051",
+      "version": "1.0.1054",
       "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.1052",
+  "version": "1.0.1054",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

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

@@ -4,6 +4,18 @@
 
 ---
 
+## v1.0.1054 (2026-08-14)
+
+### Bug 修复
+- 成长页功能修复(基于干净基线重建) - 标题 今日干预进度→今日成长 - 移除 今日打卡/本周调研 items、饮食推荐卡片 - 饮食检查改用 getHealthMealList(/api/health/meal/list) - 推荐商品改用 productList({page:1,size:6}) 修复空列表 - 快捷按钮 4 列 flex 布局(progress-grid) - 保留 tracking-tabs 记录区与成长记录卡
+
+
+## v1.0.1053 (2026-08-14)
+
+### 文档
+- 新增公众号数据驱动优化指南(基于后台分析)
+
+
 ## v1.0.1052 (2026-08-14)
 
 ### Bug 修复

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1052
+> 当前版本: v1.0.1054
 
 ## 历史版本
 
@@ -8,6 +8,18 @@
 
 ---
 
+## v1.0.1054 (2026-08-14)
+
+### Bug 修复
+- 成长页功能修复(基于干净基线重建) - 标题 今日干预进度→今日成长 - 移除 今日打卡/本周调研 items、饮食推荐卡片 - 饮食检查改用 getHealthMealList(/api/health/meal/list) - 推荐商品改用 productList({page:1,size:6}) 修复空列表 - 快捷按钮 4 列 flex 布局(progress-grid) - 保留 tracking-tabs 记录区与成长记录卡
+
+
+## v1.0.1053 (2026-08-14)
+
+### 文档
+- 新增公众号数据驱动优化指南(基于后台分析)
+
+
 ## v1.0.1052 (2026-08-14)
 
 ### Bug 修复