DimensionProductList.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <view class="dimension-product-list">
  3. <view class="section-header">
  4. <text class="section-title">{{ title }}</text>
  5. </view>
  6. <view class="product-grid" v-if="products.length > 0">
  7. <view class="product-card" v-for="product in products" :key="product.id" @click="goProduct(product)">
  8. <image class="product-cover" :src="product.coverImage || '/static/default-product.png'" mode="aspectFill"></image>
  9. <view class="product-info">
  10. <text class="product-name">{{ product.name }}</text>
  11. <view class="product-price-row">
  12. <text class="product-price">¥{{ (product.price / 100).toFixed(2) }}</text>
  13. <text class="product-reason" v-if="product.reason">{{ product.reason }}</text>
  14. </view>
  15. <view class="match-badge" v-if="product.matchScore">
  16. <text class="match-score">{{ product.matchScore }}分</text>
  17. </view>
  18. </view>
  19. </view>
  20. </view>
  21. <view class="empty-state" v-else-if="!loading">
  22. <text class="empty-text">{{ familyId ? '暂无推荐商品' : '登录后查看推荐商品' }}</text>
  23. </view>
  24. <view class="loading-state" v-if="loading">
  25. <text class="loading-text">加载中...</text>
  26. </view>
  27. </view>
  28. </template>
  29. <script>
  30. import { getDimensionProducts, clickRepurchaseReminder } from '../utils/api.js'
  31. export default {
  32. props: {
  33. dimensionCode: { type: String, required: true },
  34. familyId: { type: [Number, String], default: null },
  35. memberScores: { type: Object, default: function() { return {} } },
  36. title: { type: String, default: '为你推荐' }
  37. },
  38. data: function() {
  39. return {
  40. products: [],
  41. loading: false
  42. }
  43. },
  44. mounted: function() {
  45. // 无 familyId(未登录/未选择孩子)时不请求推荐接口,展示引导文案
  46. if (!this.familyId) {
  47. this.loading = false
  48. return
  49. }
  50. this.loadProducts()
  51. },
  52. methods: {
  53. loadProducts: function() {
  54. var self = this
  55. self.loading = true
  56. var params = {
  57. dimensionCode: self.dimensionCode,
  58. familyId: self.familyId,
  59. limit: 6
  60. }
  61. if (self.memberScores && Object.keys(self.memberScores).length > 0) {
  62. params.memberScores = self.memberScores
  63. }
  64. getDimensionProducts(params).then(function(res) {
  65. self.loading = false
  66. if (res.code === 200 && res.data) {
  67. self.products = Array.isArray(res.data) ? res.data : (res.data.records || [])
  68. }
  69. }).catch(function() {
  70. self.loading = false
  71. })
  72. },
  73. goProduct: function(product) {
  74. if (!product || !product.id) return
  75. if (product.source === 'repurchase' && product.id) {
  76. clickRepurchaseReminder(product.id).catch(function() {})
  77. }
  78. uni.navigateTo({
  79. url: '/pages/discover-detail/product-detail/product-detail?id=' + product.productId + '&from=dimension'
  80. })
  81. }
  82. }
  83. }
  84. </script>
  85. <style scoped>
  86. .dimension-product-list {
  87. margin: 20rpx 20rpx;
  88. }
  89. .section-header {
  90. display: flex;
  91. justify-content: space-between;
  92. align-items: center;
  93. margin-bottom: 20rpx;
  94. }
  95. .section-title {
  96. font-size: 30rpx;
  97. font-weight: bold;
  98. color: #333;
  99. }
  100. .product-grid {
  101. display: flex;
  102. flex-wrap: wrap;
  103. gap: 20rpx;
  104. }
  105. .product-card {
  106. width: calc(50% - 10rpx);
  107. background: #fff;
  108. border-radius: 16rpx;
  109. overflow: hidden;
  110. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  111. }
  112. .product-cover {
  113. width: 100%;
  114. height: 240rpx;
  115. background: #f0f0f0;
  116. }
  117. .product-info {
  118. padding: 16rpx;
  119. }
  120. .product-name {
  121. font-size: 26rpx;
  122. color: #333;
  123. display: block;
  124. margin-bottom: 12rpx;
  125. overflow: hidden;
  126. text-overflow: ellipsis;
  127. display: -webkit-box;
  128. -webkit-line-clamp: 2;
  129. -webkit-box-orient: vertical;
  130. }
  131. .product-price-row {
  132. display: flex;
  133. align-items: center;
  134. justify-content: space-between;
  135. margin-bottom: 8rpx;
  136. }
  137. .product-price {
  138. font-size: 28rpx;
  139. color: #F97316;
  140. font-weight: bold;
  141. }
  142. .product-reason {
  143. font-size: 22rpx;
  144. color: #999;
  145. max-width: 60%;
  146. overflow: hidden;
  147. text-overflow: ellipsis;
  148. white-space: nowrap;
  149. }
  150. .match-badge {
  151. display: inline-block;
  152. background: linear-gradient(135deg, #6366F1, #818CF8);
  153. padding: 4rpx 16rpx;
  154. border-radius: 20rpx;
  155. margin-top: 8rpx;
  156. }
  157. .match-score {
  158. font-size: 22rpx;
  159. color: #fff;
  160. font-weight: 500;
  161. }
  162. .empty-state {
  163. display: flex;
  164. justify-content: center;
  165. padding: 60rpx 0;
  166. }
  167. .empty-text {
  168. font-size: 26rpx;
  169. color: #999;
  170. }
  171. .loading-state {
  172. display: flex;
  173. justify-content: center;
  174. padding: 40rpx 0;
  175. }
  176. .loading-text {
  177. font-size: 24rpx;
  178. color: #999;
  179. }
  180. </style>