浏览代码

feat(frontend): DimensionProducts组件支持分页加载+商城样式

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 1 周之前
父节点
当前提交
e68de1dc02
共有 1 个文件被更改,包括 107 次插入 和 21 次删除
  1. 107 21
      cfc-frontend/components/DimensionProducts.vue

+ 107 - 21
cfc-frontend/components/DimensionProducts.vue

@@ -3,21 +3,27 @@
     <view v-if="!loading && displayProducts.length === 0" class="empty-state">
       <text class="empty-tip">{{ emptyText }}</text>
     </view>
-    <view v-else class="product-list">
-      <view class="product-card" v-for="prod in displayProducts" :key="prod.id" @click="onProductClick(prod)">
-        <image class="product-img" :src="getImageUrl(prod.coverImage) || '/static/default-product.png'" mode="aspectFill" />
-        <view class="product-body">
-          <text class="product-name">{{ prod.name }}</text>
+    <view v-else :class="['product-list', layout === 'mall' ? 'product-list-mall' : '']">
+      <view :class="['product-card', layout === 'mall' ? 'product-card-mall' : '']" v-for="prod in displayProducts" :key="prod.id" @click="onProductClick(prod)">
+        <image class="product-img" :class="layout === 'mall' ? 'product-img-mall' : ''" :src="getImageUrl(prod.coverImage) || '/static/default-product.png'" mode="aspectFill" />
+        <view class="product-body" :class="layout === 'mall' ? 'product-body-mall' : ''">
+          <text class="product-name" :class="layout === 'mall' ? 'product-name-mall' : ''">{{ prod.name }}</text>
           <view class="price-row">
-            <text class="product-price" v-if="prod.priceLabel">{{ prod.priceLabel }}</text>
-            <text class="product-price" v-else-if="prod.price">{{ formatPriceWithSymbol(prod.price) }}</text>
-            <text class="product-price" v-else>免费</text>
+            <text class="product-price" :class="layout === 'mall' ? 'product-price-mall' : ''" v-if="prod.priceLabel">{{ prod.priceLabel }}</text>
+            <text class="product-price" :class="layout === 'mall' ? 'product-price-mall' : ''" v-else-if="prod.price">{{ formatPriceWithSymbol(prod.price) }}</text>
+            <text class="product-price" :class="layout === 'mall' ? 'product-price-mall' : ''" v-else>免费</text>
           </view>
         </view>
       </view>
-    </view>
-    <view class="loading-state" v-if="loading">
-      <text class="loading-text">加载中...</text>
+      <view v-if="loading" class="loading-state">
+        <text class="loading-text">加载中...</text>
+      </view>
+      <view v-if="loadingMore" class="loading-more">
+        <text class="loading-text">加载中...</text>
+      </view>
+      <view v-if="noMore && displayProducts.length > 0" class="no-more">
+        <text class="no-more-text">— 没有更多了 —</text>
+      </view>
     </view>
   </view>
 </template>
@@ -35,17 +41,24 @@ export default {
     isLoggedIn: { type: Boolean, default: false },
     dimensionCode: { type: String, default: '' },
     title: { type: String, default: '推荐商品' },
-    showMore: { type: Boolean, default: true }
+    showMore: { type: Boolean, default: true },
+    // 'compact' 保持原始 4 列小卡片(member-detail 等页面);'mall' 两列大卡片(首页)
+    layout: { type: String, default: 'compact' }
   },
   data: function() {
     return {
       selfProducts: [],
-      loading: false
+      loading: false,
+      loadingMore: false,
+      page: 1,
+      size: 4,
+      noMore: false
     }
   },
   computed: {
     displayProducts: function() {
-      return (this.products && this.products.length > 0 ? this.products : this.selfProducts).slice(0, 4)
+      // 兼容模式:父组件传入 products 时原样展示(member-detail 等页面已自行截断)
+      return (this.products && this.products.length > 0 ? this.products : this.selfProducts)
     },
     emptyText: function() {
       // 有 familyId(已登录且选中孩子)但推荐接口无数据
@@ -79,21 +92,49 @@ export default {
   methods: {
     loadDimensionProducts: function() {
       var self = this
+      self.page = 1
+      self.noMore = false
       self.loading = true
+      self.loadDimensionProductsByPage(self.page).then(function(list) {
+        self.loading = false
+        self.selfProducts = list
+        if (list.length < self.size) self.noMore = true
+      }).catch(function() {
+        self.loading = false
+        self.selfProducts = []
+      })
+    },
+    // 供父页面 onReachBottom / scrolltolower 调用的加载更多
+    loadMore: function() {
+      var self = this
+      if (self.loading || self.loadingMore || self.noMore) return
+      // 兼容模式(父组件传入 products)不自加载
+      if (self.products && self.products.length > 0) return
+      self.loadingMore = true
+      var nextPage = self.page + 1
+      self.loadDimensionProductsByPage(nextPage).then(function(list) {
+        self.loadingMore = false
+        self.page = nextPage
+        self.selfProducts = self.selfProducts.concat(list)
+        if (list.length < self.size) self.noMore = true
+      }).catch(function() {
+        self.loadingMore = false
+      })
+    },
+    loadDimensionProductsByPage: function(page) {
+      var self = this
       var params = {
-        limit: 4
+        page: page,
+        size: self.size
       }
       if (self.dimensionCode) {
         params.dimensionCode = self.dimensionCode
       }
-      getDimensionProducts(params).then(function(res) {
-        self.loading = false
+      return getDimensionProducts(params).then(function(res) {
         if (res.code === 200 && res.data) {
-          self.selfProducts = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          return Array.isArray(res.data) ? res.data : (res.data.records || [])
         }
-      }).catch(function() {
-        self.loading = false
-        self.selfProducts = []
+        return []
       })
     },
     onProductClick: function(prod) {
@@ -163,6 +204,34 @@ export default {
   color: #F97316;
   font-weight: bold;
 }
+/* ===== 商城样式(首页 layout="mall")===== */
+.product-list-mall {
+  justify-content: space-between;
+}
+.product-card-mall {
+  width: 49%;
+  border-radius: 16rpx;
+  margin-right: 0;
+}
+.product-img-mall {
+  height: 320rpx;
+}
+.product-body-mall {
+  padding: 16rpx 16rpx 14rpx;
+}
+.product-name-mall {
+  display: -webkit-box;
+  font-size: 26rpx;
+  line-height: 1.4;
+  margin-bottom: 8rpx;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  white-space: normal;
+}
+.product-price-mall {
+  font-size: 28rpx;
+}
 .empty-state {
   display: flex;
   justify-content: center;
@@ -176,9 +245,26 @@ export default {
   display: flex;
   justify-content: center;
   padding: 40rpx 0;
+  width: 100%;
+}
+.loading-more {
+  display: flex;
+  justify-content: center;
+  padding: 20rpx 0;
+  width: 100%;
 }
 .loading-text {
   font-size: 24rpx;
   color: #999;
 }
+.no-more {
+  display: flex;
+  justify-content: center;
+  padding: 20rpx 0;
+  width: 100%;
+}
+.no-more-text {
+  font-size: 22rpx;
+  color: #ccc;
+}
 </style>