|
@@ -3,21 +3,27 @@
|
|
|
<view v-if="!loading && displayProducts.length === 0" class="empty-state">
|
|
<view v-if="!loading && displayProducts.length === 0" class="empty-state">
|
|
|
<text class="empty-tip">{{ emptyText }}</text>
|
|
<text class="empty-tip">{{ emptyText }}</text>
|
|
|
</view>
|
|
</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">
|
|
<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>
|
|
</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>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
@@ -35,17 +41,24 @@ export default {
|
|
|
isLoggedIn: { type: Boolean, default: false },
|
|
isLoggedIn: { type: Boolean, default: false },
|
|
|
dimensionCode: { type: String, default: '' },
|
|
dimensionCode: { type: String, default: '' },
|
|
|
title: { 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() {
|
|
data: function() {
|
|
|
return {
|
|
return {
|
|
|
selfProducts: [],
|
|
selfProducts: [],
|
|
|
- loading: false
|
|
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ loadingMore: false,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ size: 4,
|
|
|
|
|
+ noMore: false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
computed: {
|
|
computed: {
|
|
|
displayProducts: function() {
|
|
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() {
|
|
emptyText: function() {
|
|
|
// 有 familyId(已登录且选中孩子)但推荐接口无数据
|
|
// 有 familyId(已登录且选中孩子)但推荐接口无数据
|
|
@@ -79,21 +92,49 @@ export default {
|
|
|
methods: {
|
|
methods: {
|
|
|
loadDimensionProducts: function() {
|
|
loadDimensionProducts: function() {
|
|
|
var self = this
|
|
var self = this
|
|
|
|
|
+ self.page = 1
|
|
|
|
|
+ self.noMore = false
|
|
|
self.loading = true
|
|
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 = {
|
|
var params = {
|
|
|
- limit: 4
|
|
|
|
|
|
|
+ page: page,
|
|
|
|
|
+ size: self.size
|
|
|
}
|
|
}
|
|
|
if (self.dimensionCode) {
|
|
if (self.dimensionCode) {
|
|
|
params.dimensionCode = 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) {
|
|
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) {
|
|
onProductClick: function(prod) {
|
|
@@ -163,6 +204,34 @@ export default {
|
|
|
color: #F97316;
|
|
color: #F97316;
|
|
|
font-weight: bold;
|
|
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 {
|
|
.empty-state {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
@@ -176,9 +245,26 @@ export default {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
padding: 40rpx 0;
|
|
padding: 40rpx 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.loading-more {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 20rpx 0;
|
|
|
|
|
+ width: 100%;
|
|
|
}
|
|
}
|
|
|
.loading-text {
|
|
.loading-text {
|
|
|
font-size: 24rpx;
|
|
font-size: 24rpx;
|
|
|
color: #999;
|
|
color: #999;
|
|
|
}
|
|
}
|
|
|
|
|
+.no-more {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 20rpx 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.no-more-text {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: #ccc;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|