DimensionProducts.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <template>
  2. <view class="section">
  3. <view class="section-header">
  4. <text class="section-title">🛍️ 推荐商品</text>
  5. <text class="section-more" @click="$emit('moreProducts')">更多 ›</text>
  6. </view>
  7. <view class="product-list">
  8. <!-- 无数据时显示占位卡片 -->
  9. <template v-if="!displayProducts || displayProducts.length === 0">
  10. <view class="product-card" v-for="i in 2" :key="'ph-' + i">
  11. <view class="product-img-placeholder"></view>
  12. <text class="product-name line-clamp-2">暂无推荐商品</text>
  13. <text class="product-price">--</text>
  14. </view>
  15. </template>
  16. <!-- 有数据时显示商品卡片 -->
  17. <template v-else>
  18. <view class="product-card" v-for="prod in displayProducts" :key="prod.id" @click="$emit('productClick', prod)">
  19. <image class="product-img" :src="prod.coverImage || '/static/default-product.png'" mode="aspectFill" />
  20. <text class="product-name line-clamp-2">{{ prod.name }}</text>
  21. <text class="product-price" v-if="prod.priceLabel">{{ prod.priceLabel }}</text>
  22. <text class="product-price" v-else-if="prod.price">{{ formatPriceWithSymbol(prod.price) }}</text>
  23. <text class="product-price" v-else>免费</text>
  24. </view>
  25. </template>
  26. </view>
  27. </view>
  28. </template>
  29. <script>
  30. export default {
  31. props: {
  32. dimensionCode: { type: String, required: true },
  33. products: { type: Array, default: function() { return [] } },
  34. isLoggedIn: { type: Boolean, default: false }
  35. },
  36. computed: {
  37. displayProducts: function() {
  38. return this.products
  39. }
  40. },
  41. methods: {
  42. formatPriceWithSymbol: function(price) {
  43. if (price == null) return '免费'
  44. return '¥' + (Number(price) / 100).toFixed(0)
  45. }
  46. }
  47. }
  48. </script>
  49. <style scoped>
  50. .section {
  51. margin: 20rpx 20rpx;
  52. }
  53. .section-header {
  54. display: flex;
  55. justify-content: space-between;
  56. align-items: center;
  57. margin-bottom: 20rpx;
  58. }
  59. .section-title {
  60. font-size: 30rpx;
  61. font-weight: bold;
  62. color: #333;
  63. }
  64. .section-more {
  65. font-size: 24rpx;
  66. color: #999;
  67. }
  68. .product-list {
  69. display: flex;
  70. flex-direction: row;
  71. flex-wrap: wrap;
  72. gap: 16rpx;
  73. }
  74. .product-card {
  75. width: calc(50% - 8rpx);
  76. background: #fff;
  77. border-radius: 16rpx;
  78. padding: 16rpx;
  79. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  80. box-sizing: border-box;
  81. }
  82. .product-card:active {
  83. opacity: 0.8;
  84. }
  85. .product-img {
  86. width: 100%;
  87. height: 200rpx;
  88. border-radius: 12rpx;
  89. background: #f0f0f0;
  90. display: block;
  91. }
  92. .product-img-placeholder {
  93. width: 100%;
  94. height: 200rpx;
  95. border-radius: 12rpx;
  96. background: linear-gradient(135deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
  97. background-size: 200% 100%;
  98. animation: shimmer 1.5s infinite;
  99. margin-bottom: 10rpx;
  100. }
  101. @keyframes shimmer {
  102. 0% { background-position: 200% 0; }
  103. 100% { background-position: -200% 0; }
  104. }
  105. .product-name {
  106. font-size: 26rpx;
  107. color: #333;
  108. font-weight: 500;
  109. display: block;
  110. margin: 8rpx 0 6rpx;
  111. min-height: 36rpx;
  112. }
  113. .product-price {
  114. font-size: 24rpx;
  115. color: #F97316;
  116. font-weight: 600;
  117. }
  118. .line-clamp-2 {
  119. overflow: hidden;
  120. text-overflow: ellipsis;
  121. display: -webkit-box;
  122. -webkit-line-clamp: 2;
  123. -webkit-box-orient: vertical;
  124. }
  125. </style>