修复商品列表滚动错误
Test Runner / hello (push) Failing after 32s

This commit is contained in:
2026-08-25 14:25:43 +08:00
parent 65356953ed
commit 69734f36b4
25 changed files with 642 additions and 246 deletions
+20 -51
View File
@@ -4,7 +4,7 @@
<text class="section-title">用强大的功能开启盈利快车道</text>
<!-- 功能卡片横向滚动
:scroll-left 绑定 featScrollLeftactiveFeat 变化时尽量把选中项滚到左侧 -->
受控模式active 由父组件统一管理scroll-left 跟随 active -->
<scroll-view
scroll-x
class="feat-scroll"
@@ -15,7 +15,7 @@
<view class="feat-list">
<view
class="feat-card"
:class="{ active: activeFeat === index }"
:class="{ active: active === index }"
v-for="(item, index) in features"
:key="index"
@tap="onFeatTap(index)"
@@ -30,7 +30,7 @@
</scroll-view>
<!-- 集成平台大图 - 370x257 8px圆角
图片来源features[activeFeat].img4张卡片分别对应一张大图占位时 4 张同图设计师供图后换 mock.js 即可 -->
跟随 active 联动切换 -->
<view class="integration-wrap">
<image
class="integration-img"
@@ -42,14 +42,10 @@
</template>
<script>
// 功能卡片尺寸(与 CSS 保持一致,用于计算「选中卡片滚到左边」的 scrollLeft
// 卡片尺寸常量,用于计算 scroll-left(与 CSS 保持一致
// CSS: .feat-card { width:332rpx }, .feat-list gap:16rpx
// 1rpx = 0.5px,所以 1 张卡片+gap = (332 + 16) * 0.5 = 174px
const CARD_WIDTH_PX = 332 * 0.5 // 166
const CARD_GAP_PX = 16 * 0.5 // 8
const CARD_STEP_PX = CARD_WIDTH_PX + CARD_GAP_PX // 174
const AUTO_PLAY_INTERVAL = 3000 // 3 秒自动轮播一次
// 1rpx = 0.5px → 单卡步长 = (332 + 16) * 0.5 = 174px
const CARD_STEP_PX = (332 + 16) * 0.5 // 174
export default {
name: 'PlatformFeatures',
@@ -57,53 +53,44 @@ export default {
features: {
type: Array,
default: () => []
},
// 受控 active:由父组件(index.vue 的统一轮播定时器)传入
active: {
type: Number,
default: 0
}
},
data() {
return {
activeFeat: 0, // 默认从第一张开始轮播
featScrollLeft: 0, // scroll-view 的 scroll-leftpx
_timer: null
featScrollLeft: 0
}
},
computed: {
integrationImg() {
const f = this.features[this.activeFeat]
const f = this.features[this.active]
return (f && f.img) || ''
}
},
watch: {
activeFeat(idx) {
// 选中项变化时:把选中卡片尽量滚到左边
// index * step = 选中卡片起始 x
active(idx) {
// active 变化(无论是自动轮播还是手动点击)都把选中卡片尽量滚到左边
this.scrollToCard(idx)
}
},
mounted() {
this.startAutoPlay()
},
beforeDestroy() {
this.stopAutoPlay()
},
methods: {
onFeatTap(index) {
// 用户手动点击:切换 + 先停轮播,再隔 3 秒重启(避免刚点完又被立即切走)
this.stopAutoPlay()
this.activeFeat = index
// 子组件不再自管 state,只 emit 给父组件
this.$emit('change', index)
this.startAutoPlay()
},
/**
* 让横滚容器把第 idx 张卡片「尽量」滚到左边贴边。
* scroll-view 的 scroll-left 有最大上限(内容总长 - 视口宽度)
* 当 idx 是最后几张时,scroll-left 超过最大值,scroll-view 会自动截断到可滚的最大值,
* 因此卡片虽无法真的贴左,但已经是「滚到最接近左侧」的位置了。
* 把第 idx 张卡片「尽量」滚到左边贴边。
* scroll-view 的 scroll-left 超过最大可滚值时会自动截断
* 因此最后几张卡片虽无法真正贴左,但已经是「最接近左侧」的位置。
*/
scrollToCard(idx) {
const target = Math.max(0, idx) * CARD_STEP_PX
// 先赋值一个不同的值,保证 uni-app scroll-view 每次都能识别到属性变化
// (否则从 0 → 0 这种无变化赋值不触发滚动)
// 处理 0→0 无变化不触发滚动的情况
if (this.featScrollLeft === target) {
this.featScrollLeft = target + 0.001
this.$nextTick(() => {
@@ -112,24 +99,6 @@ export default {
} else {
this.featScrollLeft = target
}
},
startAutoPlay() {
this.stopAutoPlay()
if (!this.features || this.features.length <= 1) return
this._timer = setInterval(() => {
const total = this.features.length
const next = (this.activeFeat + 1) % total
this.activeFeat = next
this.$emit('change', next)
}, AUTO_PLAY_INTERVAL)
},
stopAutoPlay() {
if (this._timer) {
clearInterval(this._timer)
this._timer = null
}
}
}
}
+56 -8
View File
@@ -3,12 +3,19 @@
<view class="steps-section">
<text class="section-title">4步开始你的按需定制生意</text>
<!-- 步骤卡片横向滚动 -->
<scroll-view scroll-x class="steps-scroll" show-scrollbar="false">
<!-- 步骤卡片横向滚动
受控模式active 由父组件管理scroll-left 跟随 active 把选中卡片尽量滚到左边 -->
<scroll-view
scroll-x
class="steps-scroll"
show-scrollbar="false"
:scroll-left="stepsScrollLeft"
:scroll-with-animation="true"
>
<view class="steps-list">
<view
class="step-card"
:class="{ active: activeStep === index }"
:class="{ active: active === index }"
v-for="(item, index) in steps"
:key="index"
@tap="onStepTap(index)"
@@ -22,11 +29,12 @@
</view>
</scroll-view>
<!-- 预览大图 - image 25 370x370 6px圆角 -->
<!-- 预览大图 - image 25 370x370 6px圆角
跟随 active 联动切换4 步分别对应一张图占位时同图设计师供图后改 mock.js -->
<view class="preview-wrap">
<image
class="preview-img"
src="/static/images/home/image_25_1484_1689.png"
:src="previewImg"
mode="widthFix"
></image>
</view>
@@ -34,23 +42,63 @@
</template>
<script>
// 卡片尺寸常量,用于计算 scroll-left(与 CSS 保持一致)
// CSS: .step-card { width:332rpx }, .steps-list gap:16rpx
// 1rpx = 0.5px → 单卡步长 = (332 + 16) * 0.5 = 174px
const CARD_STEP_PX = (332 + 16) * 0.5 // 174
export default {
name: 'StepsFlow',
props: {
steps: {
type: Array,
default: () => []
},
// 受控 active:由父组件(index.vue 的统一轮播定时器)传入
active: {
type: Number,
default: 0
}
},
data() {
return {
activeStep: 0
stepsScrollLeft: 0
}
},
computed: {
previewImg() {
const s = this.steps[this.active]
return (s && s.img) || ''
}
},
watch: {
active(idx) {
// active 变化(无论是自动轮播还是手动点击)都把选中卡片尽量滚到左边
this.scrollToCard(idx)
}
},
methods: {
onStepTap(index) {
this.activeStep = index
// 子组件不再自管 state,只 emit 给父组件
this.$emit('change', index)
},
/**
* 把第 idx 张卡片「尽量」滚到左边贴边。
* scroll-view 的 scroll-left 超出最大可滚值时会自动截断,
* 因此最后几张卡片虽然无法真正贴左,但已经是「最接近左侧」的位置。
*/
scrollToCard(idx) {
const target = Math.max(0, idx) * CARD_STEP_PX
// 处理 0→0 无变化不触发滚动的情况
if (this.stepsScrollLeft === target) {
this.stepsScrollLeft = target + 0.001
this.$nextTick(() => {
this.stepsScrollLeft = target
})
} else {
this.stepsScrollLeft = target
}
}
}
}
@@ -86,7 +134,7 @@ export default {
display: inline-flex;
gap: 16rpx;
}
/* 卡片 - 166x80 圆角6px */
/* 卡片 - 166x80 圆角6px (332rpx × 160rpx) */
.step-card {
flex-shrink: 0;
width: 332rpx;
+84 -5
View File
@@ -3,8 +3,12 @@
<!-- Hero 首屏 -->
<HeroBanner :stats="trustStats" />
<!-- 4步流程 -->
<StepsFlow :steps="stepList" />
<!-- 4步流程active 由页面统一管理跟下面 PlatformFeatures 共用同一个定时器 -->
<StepsFlow
:steps="stepList"
:active="currentStep"
@change="onStepChange"
/>
<!-- 全球POD货盘 -->
<PodCatalog
@@ -15,8 +19,12 @@
@productTap="onGoDetail"
/>
<!-- 平台功能 + 集成平台 -->
<PlatformFeatures :features="featureList" />
<!-- 平台功能 + 集成平台active 由页面统一管理跟上面 StepsFlow 共用同一个定时器 -->
<PlatformFeatures
:features="featureList"
:active="currentFeat"
@change="onFeatChange"
/>
<!-- 为什么选择 -->
<WhyChoose :reasons="reasonList" />
@@ -45,6 +53,9 @@ import {
companyFeatures
} from './mock.js'
// 统一轮播间隔:3 秒推进一次,同时驱动 StepsFlow + PlatformFeatures
const AUTO_PLAY_INTERVAL = 3000
export default {
components: {
HeroBanner,
@@ -62,13 +73,28 @@ export default {
reasonList,
companyFeatures,
podCountries: [],
podProducts: []
podProducts: [],
// 两个区块共用一个定时器,但各自独立轮播(互不干扰)
currentStep: 0,
currentFeat: 0,
_autoPlayTimer: null // 唯一一个定时器
}
},
async onLoad() {
await this.loadPodCountries()
await this.loadPodProducts('')
},
onShow() {
// 页面显示 → 启动轮播
this.startAutoPlay()
},
onHide() {
// 页面隐藏 → 回收定时器(切 Tab、跳详情、退后台等)
this.stopAutoPlay()
},
onUnload() {
this.stopAutoPlay()
},
methods: {
async loadPodCountries() {
try {
@@ -117,6 +143,59 @@ export default {
uni.navigateTo({
url: '/pages/product-detail/product-detail?id=' + item.id
})
},
/* ============================
* 统一轮播控制(单一入口)
* ============================ */
/**
* 启动统一轮播:单一 setInterval,3 秒一次,
* 同时推进 StepsFlow 的 currentStep 和 PlatformFeatures 的 currentFeat。
* 两个组件的 active 各自 mod 自己的 length,互不干扰。
*/
startAutoPlay() {
this.stopAutoPlay()
this._autoPlayTimer = setInterval(() => {
const stepTotal = (this.stepList || []).length
const featTotal = (this.featureList || []).length
if (stepTotal > 1) {
this.currentStep = (this.currentStep + 1) % stepTotal
}
if (featTotal > 1) {
this.currentFeat = (this.currentFeat + 1) % featTotal
}
}, AUTO_PLAY_INTERVAL)
},
/**
* 回收定时器:onHide / onUnload 时调用,避免后台运行浪费资源
*/
stopAutoPlay() {
if (this._autoPlayTimer) {
clearInterval(this._autoPlayTimer)
this._autoPlayTimer = null
}
},
/**
* 用户点击 StepsFlow 卡片:
* 先停定时器 → 更新 currentStep → 重启 3s 计数
* (同时 currentFeat 的计数被重置也无妨,体验上完全可接受)
*/
onStepChange(idx) {
this.stopAutoPlay()
this.currentStep = idx
this.startAutoPlay()
},
/**
* 用户点击 PlatformFeatures 卡片:同上逻辑,函数复用
*/
onFeatChange(idx) {
this.stopAutoPlay()
this.currentFeat = idx
this.startAutoPlay()
}
}
}
+10 -4
View File
@@ -10,26 +10,32 @@ export const trustStats = [
]
// 4步流程
// 每一步对应一张预览大图(img 字段,目前暂用同一张占位图,设计师供图后替换即可)
const STEP_IMG = '/static/images/home/image_25_1484_1689.png'
export const stepList = [
{
step: '01',
title: '选择产品',
desc: '海量精选产品,满足多元定制需求'
desc: '海量精选产品,满足多元定制需求',
img: STEP_IMG
},
{
step: '02',
title: '上传设计',
desc: '可视化编辑器,轻松实现创意落地'
desc: '可视化编辑器,轻松实现创意落地',
img: STEP_IMG
},
{
step: '03',
title: '在线销售',
desc: '一键开通店铺,多渠道触达目标客群'
desc: '一键开通店铺,多渠道触达目标客群',
img: STEP_IMG
},
{
step: '04',
title: '生产配送',
desc: '智能化柔性生产,快速响应全球配送'
desc: '智能化柔性生产,快速响应全球配送',
img: STEP_IMG
}
]