Explorar o código

merge: 首页/成长页推荐样式统一(含 ArticleBookshelf 死引用清理)

Agnes Agent hai 1 semana
pai
achega
e78d2b9b75

+ 162 - 0
cfc-frontend/components/ArticleFeed.vue

@@ -0,0 +1,162 @@
+<template>
+  <view class="af-section">
+    <!-- 空态 -->
+    <view v-if="(!articles || articles.length === 0) && !loading" class="af-empty">
+      <text class="af-empty-text">暂无推荐文章</text>
+    </view>
+
+    <!-- 加载态骨架:3 条左图右文卡片 -->
+    <view v-else-if="loading && (!articles || articles.length === 0)" class="af-list">
+      <view v-for="n in 3" :key="n" class="af-item">
+        <view class="af-cover af-skeleton-block"></view>
+        <view class="af-body">
+          <view class="af-skeleton-line af-skeleton-line-title"></view>
+          <view class="af-skeleton-line"></view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 左图右文列表(与文章详情「相关文章」视觉一致) -->
+    <view v-else class="af-list">
+      <view
+        v-for="item in articles"
+        :key="item.id"
+        class="af-item"
+        hover-class="af-item-pressed"
+        @click="onArticleClick(item)"
+      >
+        <image class="af-cover" :src="articleCover(item)" mode="aspectFill" lazy-load />
+        <view class="af-body">
+          <text class="af-title">{{ item.title }}</text>
+          <text class="af-summary" v-if="item.summary">{{ item.summary }}</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import config from '@/config.js'
+
+/**
+ * ArticleFeed — 文章「关联文章」同款左图右文列表,用于首页/成长页「推荐阅读」统一视觉。
+ * 纯展示组件:数据由父页面传入,点击通过 articleClick 事件上抛文章对象。
+ */
+export default {
+  name: 'ArticleFeed',
+  props: {
+    articles: { type: Array, default: function() { return [] } },
+    loading: { type: Boolean, default: false }
+  },
+  methods: {
+    onArticleClick: function(item) {
+      this.$emit('articleClick', item)
+    },
+    // 封面图 URL 解析:空 / null / undefined / '/null' → 默认图(与 RelatedContent 同口径,增补 '/null' 兜底)
+    articleCover: function(item) {
+      if (!item || !item.coverImage) return '/static/default-article.png'
+      return this.resolveUrl(item.coverImage) || '/static/default-article.png'
+    },
+    resolveUrl: function(path) {
+      var s = String(path || '').trim()
+      if (!s || s === 'null' || s === 'undefined' || s === '/null') return ''
+      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0 || s.indexOf('/static/') === 0) return s
+      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.af-section {
+  background: $app-surface;
+  border-radius: 16rpx;
+  padding: 24rpx;
+}
+
+/* ---- 空态 ---- */
+.af-empty {
+  display: flex;
+  justify-content: center;
+  padding: 40rpx 0;
+}
+.af-empty-text {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-gray-400;
+}
+
+/* ---- 列表 ---- */
+.af-list {
+  display: flex;
+  flex-direction: column;
+}
+.af-item {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+  padding: 14rpx 0;
+  border-radius: 16rpx;
+}
+.af-item-pressed {
+  background: $facade-fill-gray-100;
+}
+.af-cover {
+  width: 160rpx;
+  height: 120rpx;
+  border-radius: 16rpx;
+  background: $facade-fill-gray-100;
+  flex-shrink: 0;
+}
+.af-body {
+  flex: 1;
+  min-width: 0;
+  margin-left: 16rpx;
+  display: flex;
+  flex-direction: column;
+}
+.af-title {
+  font-size: calc(27rpx * var(--font-scale, 1));
+  font-weight: 500;
+  color: $facade-text-gray-800;
+  line-height: 1.4;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+.af-summary {
+  font-size: calc(23rpx * var(--font-scale, 1));
+  color: $facade-text-gray-400;
+  line-height: 1.4;
+  margin-top: 6rpx;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+
+/* ---- 骨架屏(3 条左图右文占位) ---- */
+.af-skeleton-block {
+  background: linear-gradient(180deg, $facade-fill-4 0%, $facade-fill 50%, $facade-fill-4 100%);
+  background-size: 200% 100%;
+  animation: af-skeleton-wave 1.4s ease-in-out infinite;
+}
+.af-skeleton-line {
+  width: 100%;
+  height: 28rpx;
+  border-radius: 8rpx;
+  margin-top: 10rpx;
+  background: linear-gradient(180deg, $facade-fill-4 0%, $facade-fill 50%, $facade-fill-4 100%);
+  background-size: 200% 100%;
+  animation: af-skeleton-wave 1.4s ease-in-out infinite;
+}
+.af-skeleton-line-title {
+  width: 72%;
+  height: 32rpx;
+  margin-top: 0;
+}
+@keyframes af-skeleton-wave {
+  0% { background-position: 200% 0; }
+  100% { background-position: -200% 0; }
+}
+</style>

+ 4 - 4
cfc-frontend/pages/growth-main/index.vue

@@ -251,7 +251,7 @@
           <text class="section-title">📖 推荐阅读</text>
           <text class="section-more" @click="goMoreArticles">更多 ›</text>
         </view>
-        <ArticleBookshelf :articles="recommendArticles" :isLoggedIn="true" @articleClick="goToArticle" />
+        <ArticleFeed :articles="recommendArticles" @articleClick="goToArticle" />
       </view>
 
       <!-- 推荐商品(放最底部) -->
@@ -260,7 +260,7 @@
           <text class="section-title">🛍️ 推荐商品</text>
           <text class="section-more" @click="goMoreProducts">更多 ›</text>
         </view>
-        <DimensionProducts :isLoggedIn="true" :showMore="false" @productClick="goToProduct" />
+        <DimensionProducts :isLoggedIn="true" :showMore="false" layout="mall" @productClick="goToProduct" />
       </view>
 
       <view class="bottom-spacer" style="height: 40rpx;"></view>
@@ -272,14 +272,14 @@
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
-import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
+import ArticleFeed from '../../components/ArticleFeed.vue'
 import { healthCheckinList, getHealthMealList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList, getCheckinList, getTodayTasks, reportAppEnter } from '../../utils/api.js'
 import { parseDate, recordDateOf } from '../../utils/format.js'
 import fontScaleMixin from '@/mixins/font-scale.js'
 import shareMixin from '../../components/share-mixin.js'
 export default {
   mixins: [fontScaleMixin, shareMixin],
-  components: { LoginGuideCard, DimensionActivities, DimensionProducts, ArticleBookshelf },
+  components: { LoginGuideCard, DimensionActivities, DimensionProducts, ArticleFeed },
   data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], recentTasks: [], todayTaskList: [], streakDays: 0, activeChallenges: 0, completeness: 0,
       healthArticles: [],
       growthArticles: [],

+ 3 - 4
cfc-frontend/pages/index-home/index.vue

@@ -241,9 +241,8 @@
         <text class="section-title">📖 推荐阅读</text>
         <text class="section-more" @click="goMoreArticles">更多 ›</text>
       </view>
-      <ArticleBookshelf
+      <ArticleFeed
         :articles="actionArticles"
-        :isLoggedIn="true"
         @articleClick="goArticleDetail" />
     </view>
 
@@ -461,7 +460,7 @@ import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import GateLock from '../../components/gate-lock.vue'
 import FamilyFeed from '../../components/FamilyFeed.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
-import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
+import ArticleFeed from '../../components/ArticleFeed.vue'
 import FamilyChallengeCard from '../../components/FamilyChallengeCard.vue'
 import DailyTaskCard from '../../components/daily-task-card.vue'
 import ReportUploadCard from '../../components/report-upload-card.vue'
@@ -483,7 +482,7 @@ components: {
     GateLock,
     FamilyFeed,
     DimensionProducts,
-    ArticleBookshelf,
+    ArticleFeed,
     FamilyChallengeCard,
     DailyTaskCard,
     ReportUploadCard,