Răsfoiți Sursa

feat(growth): 成长页新增推荐文章+推荐商品板块

- 后端: GrowthController /api/growth/recommendations 按 growth_category 查询
- 后端: AI自动分类接口 /api/growth/articles/products/ai-classify
- 前端: growth-main 新增推荐文章和推荐商品卡片
- 前端: api.js 新增 getGrowthRecommendations()
iwt 2 luni în urmă
părinte
comite
ccb6224b55
2 a modificat fișierele cu 82 adăugiri și 2 ștergeri
  1. 78 2
      cfc-frontend/pages/growth-main/index.vue
  2. 4 0
      cfc-frontend/utils/api.js

+ 78 - 2
cfc-frontend/pages/growth-main/index.vue

@@ -179,6 +179,45 @@
           <text class="action-arrow">›</text>
         </view>
       </view>
+
+      <!-- 推荐文章 -->
+      <view class="section" v-if="healthArticles.length > 0 || growthArticles.length > 0">
+        <view class="rec-section-title">📖 推荐阅读</view>
+        <view class="rec-card" v-for="item in healthArticles" :key="'ha'+item.id" @click="goToArticle(item.id)">
+          <image class="rec-img" :src="item.coverImage" mode="aspectFill" v-if="item.coverImage"/>
+          <view class="rec-info">
+            <text class="rec-title">{{ item.title }}</text>
+            <text class="rec-desc">{{ item.summary }}</text>
+          </view>
+        </view>
+        <view class="rec-card" v-for="item in growthArticles" :key="'ga'+item.id" @click="goToArticle(item.id)">
+          <image class="rec-img" :src="item.coverImage" mode="aspectFill" v-if="item.coverImage"/>
+          <view class="rec-info">
+            <text class="rec-title">{{ item.title }}</text>
+            <text class="rec-desc">{{ item.summary }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 推荐商品 -->
+      <view class="section" v-if="healthProducts.length > 0 || growthProducts.length > 0">
+        <view class="rec-section-title">🛒 推荐商品</view>
+        <view class="rec-product" v-for="item in healthProducts" :key="'hp'+item.id" @click="goToProduct(item.id)">
+          <image class="rec-pimg" :src="item.coverImage" mode="aspectFill"/>
+          <view class="rec-pinfo">
+            <text class="rec-pname">{{ item.name }}</text>
+            <text class="rec-pdesc">{{ item.intro || item.description || '' }}</text>
+          </view>
+        </view>
+        <view class="rec-product" v-for="item in growthProducts" :key="'gp'+item.id" @click="goToProduct(item.id)">
+          <image class="rec-pimg" :src="item.coverImage" mode="aspectFill"/>
+          <view class="rec-pinfo">
+            <text class="rec-pname">{{ item.name }}</text>
+            <text class="rec-pdesc">{{ item.intro || item.description || '' }}</text>
+          </view>
+        </view>
+      </view>
+
       <view class="section">
         <view class="tracking-card">
           <view class="tracking-tabs">
@@ -250,10 +289,15 @@
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
-import { healthCheckinList, getDietDailyRecords, getSurveyStatus, getSurveyHistory, getFoodCautionList } from '../../utils/api.js'
+import { healthCheckinList, getDietDailyRecords, getSurveyStatus, getSurveyHistory, getFoodCautionList, getGrowthRecommendations } from '../../utils/api.js'
 export default {
   components: { PageBanner, LoginGuideCard, DimensionActivities },
-  data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], activeTab: 'diet', trackingTabs: [{ key: 'diet', label: '饮食' }, { key: 'exercise', label: '运动' }, { key: 'sleep', label: '睡眠' }, { key: 'emotion', label: '心情' }, { key: 'survey', label: '调研' }], trackingData: { diet: [], exercise: [], sleep: [], emotion: [], survey: [] }, streakDays: 0, activeChallenges: 0, completeness: 0, cautionCount: 0, pendingSurvey: null, allDone: false, surveyHistory: [], isLoggedIn: false } },
+  data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], activeTab: 'diet', trackingTabs: [{ key: 'diet', label: '饮食' }, { key: 'exercise', label: '运动' }, { key: 'sleep', label: '睡眠' }, { key: 'emotion', label: '心情' }, { key: 'survey', label: '调研' }], trackingData: { diet: [], exercise: [], sleep: [], emotion: [], survey: [] }, streakDays: 0, activeChallenges: 0, completeness: 0, cautionCount: 0,
+      healthArticles: [],
+      healthProducts: [],
+      growthArticles: [],
+      growthProducts: [],
+      pendingSurvey: null, allDone: false, surveyHistory: [], isLoggedIn: false } },
   computed: {
     doneCount: function() { var c = 0; for (var i = 0; i < this.progressItems.length; i++) if (this.progressItems[i] && this.progressItems[i].done) c++; return c },
     totalCount: function() { return this.progressItems.length },
@@ -293,12 +337,15 @@ export default {
     handleGuestFeatureTap: function() { uni.showToast({ title: '登录后即可体验该功能', icon: 'none' }) },
     goToCaution: function() { uni.navigateTo({ url: '/pages/growth/caution-detail' }) },
     goToFoods: function() { uni.navigateTo({ url: '/pages/growth/foods-detail' }) },
+    goToArticle: function(id) { uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id }) },
+    goToProduct: function(id) { uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id }) },
     goPage: function(url) { uni.navigateTo({ url: url }) },
     loadTrackingData: function(key) { return this.trackingData[key] || [] },
     async loadAllData() {
       var self = this
       try { await self.loadProgressItems() } catch(e) {}
       try { await self.loadCautionData() } catch(e) {}
+      try { await self.loadGrowthRecommendations() } catch(e) {}
       try { await self.loadSurveyData() } catch(e) {}
       try { await self.loadMilestones() } catch(e) {}
     },
@@ -346,6 +393,21 @@ export default {
         }
       } catch(e) {}
     },
+    async loadGrowthRecommendations() {
+      var self = this
+      try {
+        var res = await getGrowthRecommendations('health', 3)
+        if (res && res.code === 200 && res.data) {
+          self.healthArticles = res.data.articles || []
+          self.healthProducts = res.data.products || []
+        }
+        var res2 = await getGrowthRecommendations('growth', 3)
+        if (res2 && res2.code === 200 && res2.data) {
+          self.growthArticles = res2.data.articles || []
+          self.growthProducts = res2.data.products || []
+        }
+      } catch(e) {}
+    },
     async loadCautionData() {
       var self = this
       try {
@@ -399,6 +461,20 @@ export default {
 .action-title{font-size:28rpx;font-weight:600;color:#333;display:block}
 .action-desc{font-size:22rpx;color:#888;margin-top:4rpx;display:block}
 .action-arrow{font-size:32rpx;color:#ccc}
+/* 成长推荐卡片 */
+.rec-section-title{font-size:28rpx;font-weight:600;color:#333;padding:20rpx 28rpx 8rpx}
+.rec-card{display:flex;background:#fff;border-radius:16rpx;margin:0 24rpx 12rpx;padding:16rpx;box-shadow:0 2rpx 8rpx rgba(0,0,0,0.06)}
+.rec-card:active{opacity:0.85}
+.rec-img{width:160rpx;height:110rpx;border-radius:12rpx;flex-shrink:0;margin-right:16rpx;background:#f5f5f5}
+.rec-info{flex:1;min-width:0}
+.rec-title{font-size:26rpx;font-weight:600;color:#333;display:block;margin-bottom:6rpx;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+.rec-desc{font-size:22rpx;color:#888;display:block;line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
+.rec-product{display:flex;background:#fff;border-radius:16rpx;margin:0 24rpx 12rpx;padding:16rpx;box-shadow:0 2rpx 8rpx rgba(0,0,0,0.06)}
+.rec-product:active{opacity:0.85}
+.rec-pimg{width:140rpx;height:140rpx;border-radius:12rpx;flex-shrink:0;margin-right:16rpx;background:#f5f5f5}
+.rec-pinfo{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}
+.rec-pname{font-size:26rpx;font-weight:600;color:#333;display:block;margin-bottom:6rpx;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+.rec-pdesc{font-size:22rpx;color:#888;display:block;line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
 /* 登录后原有样式 */
 .reminder-bar{margin:12rpx 24rpx;background:#FFF3E0;border:1rpx solid #FFB74D;border-radius:12rpx;padding:14rpx 20rpx;display:flex;align-items:center;justify-content:space-between}
 .reminder-text{font-size:24rpx;color:#E65100}

+ 4 - 0
cfc-frontend/utils/api.js

@@ -2628,5 +2628,9 @@ export const saveMealConfig = (data) => request('/api/diet/meals/config/save', '
 // ===== 北京菌群报告 — 菌群功能分析 =====
 export const getGutFloraAnalysis = (reportId) => request('/api/nutrition/beijing/analysis', 'POST', { reportId })
 
+// ===== 成长板块推荐 =====
+export const getGrowthRecommendations = (category = 'health', limit = 3) =>
+  request('/api/growth/recommendations', 'POST', { category, limit })
+
 // ===== 食材戒备匹配 =====
 export const getFoodCautionList = () => request('/api/food/recommendation/caution', 'POST', {})