Files
inkreach-uni/pages/products/components/ProductGrid.vue
T
2026-08-31 13:35:50 +08:00

245 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 右侧商品列表区 - 上方子分类 + 下方滚动商品网格 -->
<view class="right-content">
<!-- 子分类(顶部条件) - Figma 1519:250 -->
<view class="sub-categories" id="subCategories" v-if="subCategories.length > 0">
<view
class="sub-cat-item"
:class="{ active: activeSubId === item.id }"
v-for="(item, index) in subCategories"
:key="index"
@tap="selectSub(item.id)"
>{{ item.name }}</view>
</view>
<!-- 商品网格滚动区 - 固定高度由父组件动态计算传入),符合官方文档要求 -->
<scroll-view
scroll-y
class="product-scroll"
:lower-threshold="100"
:scroll-top="scrollTopVal"
:scroll-with-animation="false"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="onScrollToLower"
>
<view class="product-grid" v-if="list.length > 0">
<view
class="product-card"
v-for="(item, index) in list"
:key="index"
@tap="onTapProduct(item)"
>
<!-- 图片 135x120 (Figma) -->
<view class="product-img">
<image
class="product-image"
:src="item.image"
mode="aspectFill"
lazy-load
v-if="item.image"
></image>
</view>
<!-- 商品信息 -->
<view class="product-info">
<text class="product-name">{{ item.name }}</text>
<!-- 价格 + "起" -->
<view class="price-row">
<text class="product-price">{{ item.price }}</text>
<text class="price-unit"></text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="product-empty" v-else>
<text class="empty-icon">📦</text>
<text class="empty-text">没有找到相关商品</text>
</view>
<!-- 底部加载提示 -->
<view class="load-tip" v-if="list.length > 0">
<text v-if="loading">加载中...</text>
<text v-else-if="!hasMore">没有更多了</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
name: 'ProductGrid',
props: {
list: { type: Array, default: () => [] },
subCategories: { type: Array, default: () => [] },
activeSubId: { type: [String, Number], default: '' },
hasMore: { type: Boolean, default: true },
loading: { type: Boolean, default: false },
scrollTopVal: { type: Number, default: 0 },
// 商品滚动区固定高度(px),由父组件动态计算传入
scrollHeight: { type: Number, default: 400 }
},
mounted() {
// 首次测量子分类高度
this.$nextTick(() => this.emitSubHeight())
},
watch: {
// 子分类列表变化 → 重新测量
'subCategories.length'() {
this.$nextTick(() => this.emitSubHeight())
}
},
methods: {
emitSubHeight() {
// v-if 为 false 时 #subCategories 不存在,返回 0
if (!this.subCategories || this.subCategories.length === 0) {
this.$emit('sub-height-change', 0)
return
}
const query = uni.createSelectorQuery().in(this)
query.select('#subCategories').boundingClientRect()
query.exec((res) => {
if (res && res[0]) {
this.$emit('sub-height-change', res[0].height)
} else {
this.$emit('sub-height-change', 0)
}
})
},
selectSub(id) { this.$emit('sub-select', id) },
onTapProduct(item) { this.$emit('product-click', item) },
onScrollToLower() { this.$emit('scroll-to-lower') }
}
}
</script>
<style scoped>
/* 右侧内容区 - 上下分:顶部条件 + 下方滚动
不再依赖 flex:1 撑高度,scroll-view 用父组件传入的固定高度 */
.right-content {
display: flex;
flex-direction: column;
background: #f2f2f2;
}
/* 顶部子分类条件 - padding 6px 10px 圆角 60px */
.sub-categories {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 20rpx 20rpx;
background: #ffffff;
border-bottom: 1rpx solid #eeeeee;
}
.sub-cat-item {
padding: 12rpx 20rpx;
background: #ffffff;
border-radius: 60rpx;
font-size: 24rpx;
font-weight: 500;
color: #000000;
line-height: 1.2;
}
/* 选中态 - 白底+橙色边框0.5px+橙色文字 */
.sub-cat-item.active {
border: 1rpx solid #ff6800;
color: #ff6800;
}
/* 滚动商品区 - 高度由父组件通过 :style 传入(px 固定值),
符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求 */
.product-scroll {
width: 100%;
}
/* 商品网格 - 固定2列 grid,不会因商品个数变化而拉伸占行 */
.product-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
padding: 20rpx;
}
.product-card {
min-width: 0;
background: #ffffff;
border-radius: 12rpx;
overflow: hidden;
box-sizing: border-box;
}
/* 图片 135x120 - 用 padding-bottom 撑比例 */
.product-img {
position: relative;
width: 100%;
padding-bottom: 88.89%; /* 120/135 */
background: #f7f7f7;
overflow: hidden;
}
.product-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* 商品信息 - 内边距 10px */
.product-info {
padding: 20rpx;
}
/* 商品名 13px Medium #000000 */
.product-name {
display: block;
font-size: 26rpx;
font-weight: 500;
color: #000000;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 价格行 - 价格 + "起"字横向排列 */
.price-row {
display: flex;
align-items: baseline;
margin-top: 16rpx;
}
/* 价格 15px Heavy #FF0000 红色 */
.product-price {
font-size: 30rpx;
font-weight: 900;
color: #ff0000;
line-height: 1.2;
}
/* "起"字 - 比价格字体小,其他样式相同 */
.price-unit {
font-size: 22rpx;
font-weight: 900;
color: #ff0000;
line-height: 1.2;
margin-left: 4rpx;
}
/* 底部加载提示 */
.load-tip {
padding: 32rpx;
text-align: center;
font-size: 24rpx;
color: #999999;
}
/* 空状态 */
.product-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-icon {
font-size: 96rpx;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 26rpx;
color: #999999;
}
</style>