Преглед на файлове

fix(growth-main): 推荐阅读/商品改用首页组件并移到底部,恢复饮食推荐

- 推荐商品: 内联 rec-product → DimensionProducts 组件(自加载,与首页一致)
- 推荐阅读: 内联 rec-card → ArticleBookshelf 组件(合并 healthArticles+growthArticles)
- 推荐区块移至页面底部(DimensionActivities 之后)
- 恢复被 b725d6883 删掉的饮食推荐 action-card 入口卡片
- 清理: 移除旧推荐样式、productList API 调用、healthProducts/growthProducts 死代码
- 兼容: goToArticle/goToProduct 支持对象参数(组件 emit 传入)
liaoxg преди 2 месеца
родител
ревизия
5ddb89126b
променени са 1 файла, в които са добавени 47 реда и са изтрити 72 реда
  1. 47 72
      cfc-frontend/pages/growth-main/index.vue

+ 47 - 72
cfc-frontend/pages/growth-main/index.vue

@@ -171,41 +171,15 @@
         </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="getArticleKey(item)" @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="getArticleKey(item)" @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="getProductKey(item)" @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="getProductKey(item)" @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 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>
 
@@ -273,6 +247,26 @@
         </view>
       </view>
       <DimensionActivities :isLoggedIn="true" @activityClick="goActivityDetail" @moreActivities="goMoreActivities" style="margin-bottom:60rpx" />
+
+      <!-- 推荐商品(组件化,移到底部) -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">🛍️ 推荐商品</text>
+          <text class="section-more" @click="goMoreProducts">更多 ›</text>
+        </view>
+        <DimensionProducts :isLoggedIn="true" :showMore="false" @productClick="goToProduct" />
+      </view>
+
+      <!-- 推荐阅读(组件化,移到底部) -->
+      <view class="section" v-if="recommendArticles.length > 0">
+        <view class="section-header">
+          <text class="section-title">📖 推荐阅读</text>
+          <text class="section-more" @click="goMoreArticles">更多 ›</text>
+        </view>
+        <ArticleBookshelf :articles="recommendArticles" :isLoggedIn="true" @articleClick="goToArticle" />
+      </view>
+
+      <view class="bottom-spacer" style="height: 40rpx;"></view>
     </view>
   </view>
 </template>
@@ -280,21 +274,27 @@
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
-import { healthCheckinList, getHealthMealList, getSurveyStatus, getSurveyHistory, getFoodCautionList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList, productList } from '../../utils/api.js'
+import DimensionProducts from '../../components/DimensionProducts.vue'
+import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
+import { healthCheckinList, getHealthMealList, getSurveyStatus, getSurveyHistory, getFoodCautionList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 export default {
-  components: { PageBanner, LoginGuideCard, DimensionActivities },
+  components: { PageBanner, LoginGuideCard, DimensionActivities, DimensionProducts, ArticleBookshelf },
   data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], activeTab: 'diet', trackingTabs: [{ key: 'diet', label: '饮食' }, { key: 'exercise', label: '运动' }, { key: 'sleep', label: '睡眠' }, { key: 'emotion', label: '心情' }], trackingData: { diet: [], exercise: [], sleep: [], emotion: [] }, 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 },
     activeTabLabel: function() { var m = { diet: '饮食', exercise: '运动', sleep: '睡眠', emotion: '心情' }; return m[this.activeTab] || '' },
-    currentRecords: function() { return this.trackingData[this.activeTab] || [] }
+    currentRecords: function() { return this.trackingData[this.activeTab] || [] },
+    recommendArticles: function() {
+      var arr = []
+      if (this.healthArticles && this.healthArticles.length > 0) arr = arr.concat(this.healthArticles)
+      if (this.growthArticles && this.growthArticles.length > 0) arr = arr.concat(this.growthArticles)
+      return arr
+    }
   },
   onShow() {
     this.isLoggedIn = !!uni.getStorageSync('token')
@@ -310,8 +310,6 @@ export default {
     getProgressKey: function(item) { return item && item.label ? item.label : 'p-' + Math.random() },
     getRecordKey: function(rec) { return rec && (rec._id || rec.id) ? 'r' + (rec._id || rec.id) : (rec && rec._text ? rec._text + rec._content : 'r-' + Math.random()) },
     getHistoryKey: function(h) { return h && h._date ? h._date + h._content : 'h-' + Math.random() },
-    getArticleKey: function(item) { return item && item.id ? 'a' + item.id : 'a-' + Math.random() },
-    getProductKey: function(item) { return item && item.id ? 'p' + item.id : 'p-' + Math.random() },
     dismissReminder: function() { this.hasIncomplete = false },
     switchTab: function(key) { this.activeTab = key; this.loadTrackingData(key) },
     handleProgressClick: function(item) {
@@ -331,8 +329,10 @@ 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 }) },
+    goToArticle: function(article) { var id = article && article.id ? article.id : article; if (!id) return; uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id }) },
+    goToProduct: function(prod) { var id = prod && prod.id ? prod.id : prod; if (!id) return; uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id }) },
+    goMoreProducts: function() { uni.navigateTo({ url: '/pages/shop/index/index' }) },
+    goMoreArticles: function() { uni.navigateTo({ url: '/pages/article-center/index?dimension=action' }) },
     goPage: function(url) { uni.navigateTo({ url: url }) },
     loadTrackingData: function(key) { return this.trackingData[key] || [] },
     formatDateKey: function(dateVal) {
@@ -356,21 +356,9 @@ 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 loadRecommendedProducts() {
-      var self = this
-      try {
-        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) {}
-    },
     async loadProgressItems() {
       var self = this
       var items = []
@@ -475,12 +463,9 @@ export default {
         var res = await getGrowthRecommendations('health', 6)
         if (res && res.code === 200 && res.data) {
           var arts = res.data.articles || []
-          var prods = res.data.products || []
           // 按 growth_category 分组
           self.healthArticles = arts.filter(function(a){ return a.growthCategory === 'health' })
-          self.healthProducts = prods.filter(function(p){ return p.growthCategory === 'health' })
           self.growthArticles = arts.filter(function(a){ return a.growthCategory === 'growth' })
-          self.growthProducts = prods.filter(function(p){ return p.growthCategory === 'growth' })
         }
       } catch(e) {}
     },
@@ -537,20 +522,10 @@ 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}
+/* section-header 组件通用样式(推荐商品/阅读) */
+.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12rpx}
+.section-title{font-size:28rpx;font-weight:600;color:#333}
+.section-more{font-size:24rpx;color:#F97316}
 /* 登录后原有样式 */
 .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}