|
|
@@ -0,0 +1,262 @@
|
|
|
+<!--
|
|
|
+ 首页模块「更多」商品列表页
|
|
|
+ 从秒杀/团购/热门推荐/今日上新/下拉商品的「更多」进入;
|
|
|
+ 样式参照 goods/list.vue,本次不包含搜索与筛选功能。
|
|
|
+ 秒杀/团购保留活动价、原价划线与团购人数展示。
|
|
|
+-->
|
|
|
+<template>
|
|
|
+ <view class="goods-list-page">
|
|
|
+ <view v-if="!$util.isEmpty(list.data)" class="card-list">
|
|
|
+ <view
|
|
|
+ v-for="item in list.data"
|
|
|
+ :key="item.id"
|
|
|
+ class="goods-card"
|
|
|
+ @click="goDetail(item)"
|
|
|
+ >
|
|
|
+ <view class="cover-wrap">
|
|
|
+ <image class="cover" :src="item.coverUrl || item.smallCover || item.cover" mode="aspectFill" />
|
|
|
+ <view v-if="isGroupBuy && item.groupSize" class="badge">{{ item.groupSize }}人团</view>
|
|
|
+ <view v-else-if="isSeckill" class="badge">秒杀</view>
|
|
|
+ </view>
|
|
|
+ <view class="card-body">
|
|
|
+ <text class="goods-name">{{ item.name }}</text>
|
|
|
+ <view class="bottom-row">
|
|
|
+ <view class="price-wrap">
|
|
|
+ <text class="price">
|
|
|
+ <text class="price-symbol">¥</text>
|
|
|
+ {{ formatPrice(item.price) }}
|
|
|
+ </text>
|
|
|
+ <text v-if="isActivity && item.originPrice" class="origin-price">
|
|
|
+ ¥{{ formatPrice(item.originPrice) }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <text v-if="!isActivity" class="sold">已售{{ formatSold(item.sold || item.totalSold) }}件</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="list.finished" class="list-end">已经到底了</view>
|
|
|
+ </view>
|
|
|
+ <block v-else-if="!list.loading">
|
|
|
+ <app-wrapper-empty title="暂无商品" :is-empty="true" />
|
|
|
+ </block>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import AppWrapperEmpty from '@/components/app-wrapper-empty'
|
|
|
+import { getSectionGoods } from '@/api/home-page-config'
|
|
|
+import { list } from '@/mixins'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'sectionGoodsList',
|
|
|
+ components: {
|
|
|
+ AppWrapperEmpty
|
|
|
+ },
|
|
|
+ mixins: [list],
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ moduleKey: '',
|
|
|
+ pageTitle: '商品列表'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ isSeckill() {
|
|
|
+ return this.moduleKey === 'seckill'
|
|
|
+ },
|
|
|
+ isGroupBuy() {
|
|
|
+ return this.moduleKey === 'groupBuy'
|
|
|
+ },
|
|
|
+ isActivity() {
|
|
|
+ return this.isSeckill || this.isGroupBuy
|
|
|
+ },
|
|
|
+ account() {
|
|
|
+ return (this.option && this.option.account) || uni.getStorageSync('account') || ''
|
|
|
+ },
|
|
|
+ hdId() {
|
|
|
+ return (this.option && this.option.hdId) || uni.getStorageSync('hdId') || ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ init() {
|
|
|
+ this.moduleKey = (this.option && this.option.moduleKey) || ''
|
|
|
+ let title = (this.option && this.option.title) || ''
|
|
|
+ try {
|
|
|
+ title = title ? decodeURIComponent(title) : ''
|
|
|
+ } catch (e) {
|
|
|
+ // 标题解码失败时保留原始标题,避免异常阻断列表加载
|
|
|
+ }
|
|
|
+ this.pageTitle = title || '商品列表'
|
|
|
+ uni.setNavigationBarTitle({ title: this.pageTitle })
|
|
|
+ this.loadGoods()
|
|
|
+ },
|
|
|
+ buildQuery() {
|
|
|
+ return {
|
|
|
+ moduleKey: this.moduleKey,
|
|
|
+ page: this.list.page,
|
|
|
+ pageSize: 10
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadGoods() {
|
|
|
+ this.list.loading = true
|
|
|
+ return getSectionGoods(this.buildQuery())
|
|
|
+ .then((res) => {
|
|
|
+ this.completes(res)
|
|
|
+ // 后端返回的 title 优先用于导航栏(路由未带 title 时兜底)
|
|
|
+ if (res.code === 1 && res.data && res.data.title && !((this.option && this.option.title))) {
|
|
|
+ this.pageTitle = res.data.title
|
|
|
+ uni.setNavigationBarTitle({ title: this.pageTitle })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ this.list.loading = false
|
|
|
+ this.list.finished = true
|
|
|
+ })
|
|
|
+ },
|
|
|
+ formatPrice(val) {
|
|
|
+ const n = parseFloat(val)
|
|
|
+ if (isNaN(n)) return '0'
|
|
|
+ return n % 1 === 0 ? String(n) : n.toFixed(2)
|
|
|
+ },
|
|
|
+ formatSold(val) {
|
|
|
+ const n = parseInt(val, 10) || 0
|
|
|
+ if (n >= 10000) return (n / 10000).toFixed(1) + '万+'
|
|
|
+ return String(n)
|
|
|
+ },
|
|
|
+ goDetail(item) {
|
|
|
+ // 防御性判断:v-for 的 :key 曾用过 item.id || item.goodsId 这类表达式,
|
|
|
+ // 在非 H5 端(小程序/App)不受支持,会导致列表刷新/翻页时点击命中的
|
|
|
+ // item 与实际数据错位甚至取到 undefined,这里兜底避免报错崩溃。
|
|
|
+ if (!item) return
|
|
|
+ const id = item.id || item.goodsId
|
|
|
+ if (!id) return
|
|
|
+ this.pageTo({
|
|
|
+ url: `/pages/goods/detail?id=${id}&account=${this.account}&hdId=${this.hdId}`
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async onPullDownRefresh() {
|
|
|
+ this.resetList()
|
|
|
+ await this.loadGoods()
|
|
|
+ uni.stopPullDownRefresh()
|
|
|
+ },
|
|
|
+ async onReachBottom() {
|
|
|
+ if (!this.list.finished) {
|
|
|
+ await this.loadGoods()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.goods-list-page {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #f5f5f5;
|
|
|
+ padding-bottom: 40upx;
|
|
|
+}
|
|
|
+
|
|
|
+.card-list {
|
|
|
+ padding: 20upx;
|
|
|
+}
|
|
|
+
|
|
|
+.goods-card {
|
|
|
+ display: flex;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16upx;
|
|
|
+ padding: 20upx;
|
|
|
+ margin-bottom: 20upx;
|
|
|
+ box-shadow: 0 4upx 16upx rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.cover-wrap {
|
|
|
+ position: relative;
|
|
|
+ width: 200upx;
|
|
|
+ height: 200upx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-radius: 12upx;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.cover {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.badge {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ padding: 4upx 12upx;
|
|
|
+ font-size: 20upx;
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 0 0 12upx 0;
|
|
|
+ background: #FF4D6D;
|
|
|
+}
|
|
|
+
|
|
|
+.card-body {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ margin-left: 20upx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.goods-name {
|
|
|
+ font-size: 30upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #333;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-top: auto;
|
|
|
+ padding-top: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.price-wrap {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ margin-right: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.price {
|
|
|
+ font-size: 34upx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #FF4D6D;
|
|
|
+}
|
|
|
+
|
|
|
+.price-symbol {
|
|
|
+ font-size: 24upx;
|
|
|
+ margin-right: 2upx;
|
|
|
+}
|
|
|
+
|
|
|
+.origin-price {
|
|
|
+ margin-left: 12upx;
|
|
|
+ font-size: 22upx;
|
|
|
+ color: #bbb;
|
|
|
+ text-decoration: line-through;
|
|
|
+}
|
|
|
+
|
|
|
+.sold {
|
|
|
+ font-size: 22upx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.list-end {
|
|
|
+ padding: 30upx 0 20upx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
+</style>
|