@@ -4,7 +4,7 @@
|
||||
<text class="section-title">用强大的功能,开启盈利快车道</text>
|
||||
|
||||
<!-- 功能卡片横向滚动
|
||||
:scroll-left 绑定 featScrollLeft,activeFeat 变化时尽量把选中项滚到左侧 -->
|
||||
受控模式: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].img(4张卡片分别对应一张大图;占位时 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-left(px)
|
||||
_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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 已选筛选标签 - Figma 1519:250 圆角 60px -->
|
||||
<view class="active-filters" v-if="list.length > 0">
|
||||
<view class="active-filters" id="activeFilters" v-if="list.length > 0">
|
||||
<view class="active-filter-tag" v-for="(item, index) in list" :key="index">
|
||||
<text class="filter-text">{{ item.label }}</text>
|
||||
<text class="filter-remove" @tap="onRemove(item.key)">×</text>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 国家选择条 - 换行布局(对应 Figma 1511:195) -->
|
||||
<view class="country-bar">
|
||||
<view class="country-bar" id="countryBar">
|
||||
<view class="country-inner">
|
||||
<view
|
||||
class="country-item"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!-- 右侧商品列表区 - 上方子分类 + 下方滚动商品网格 -->
|
||||
<view class="right-content">
|
||||
<!-- 子分类(顶部条件) - Figma 1519:250 -->
|
||||
<view class="sub-categories" v-if="subCategories.length > 0">
|
||||
<view class="sub-categories" id="subCategories" v-if="subCategories.length > 0">
|
||||
<view
|
||||
class="sub-cat-item"
|
||||
:class="{ active: activeSubId === item.id }"
|
||||
@@ -12,8 +12,16 @@
|
||||
>{{ item.name }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品网格滚动区 -->
|
||||
<scroll-view scroll-y class="product-scroll" :lower-threshold="100" :scroll-top="scrollTopVal" :scroll-with-animation="false" @scrolltolower="onScrollToLower">
|
||||
<!-- 商品网格滚动区 - 固定高度(由父组件动态计算传入),符合官方文档要求 -->
|
||||
<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"
|
||||
@@ -69,7 +77,9 @@ export default {
|
||||
activeSubId: { type: [String, Number], default: '' },
|
||||
hasMore: { type: Boolean, default: true },
|
||||
loading: { type: Boolean, default: false },
|
||||
scrollTopVal: { type: Number, default: 0 }
|
||||
scrollTopVal: { type: Number, default: 0 },
|
||||
// 商品滚动区固定高度(px),由父组件动态计算传入
|
||||
scrollHeight: { type: Number, default: 400 }
|
||||
},
|
||||
methods: {
|
||||
selectSub(id) { this.$emit('sub-select', id) },
|
||||
@@ -81,15 +91,11 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
/* 右侧内容区 - 上下分:顶部条件 + 下方滚动
|
||||
flex:1 与 height:100% 冲突,二选一(保留 flex:1) */
|
||||
不再依赖 flex:1 撑高度,scroll-view 用父组件传入的固定高度 */
|
||||
.right-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f2f2f2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部子分类条件 - padding 6px 10px 圆角 60px */
|
||||
@@ -116,13 +122,10 @@ export default {
|
||||
color: #ff6800;
|
||||
}
|
||||
|
||||
/* 滚动商品区 - uni-app scroll-view 在 flex 布局里必须同时有 flex:1 + height:0
|
||||
仅 min-height:0 无法让部分端的 scroll-view 正确计算内部 viewport 高度 */
|
||||
/* 滚动商品区 - 高度由父组件通过 :style 传入(px 固定值),
|
||||
符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求 */
|
||||
.product-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 商品网格 - 固定2列 grid,不会因商品个数变化而拉伸占行 */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="search-bar" id="searchBar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="search-input-wrap">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
:has-more="hasMore"
|
||||
:loading="loadingMore"
|
||||
:scroll-top-val="scrollTopVal"
|
||||
:scroll-height="productScrollHeight"
|
||||
@sub-select="selectSubCategory"
|
||||
@product-click="goDetail"
|
||||
@scroll-to-lower="onScrollToLower"
|
||||
@@ -108,7 +109,8 @@ export default {
|
||||
loadingMore: false,
|
||||
products: [],
|
||||
windowHeight: 667,
|
||||
scrollTopVal: 0
|
||||
scrollTopVal: 0,
|
||||
productScrollHeight: 400 // 商品滚动区高度(px),动态计算
|
||||
}
|
||||
},
|
||||
async onLoad() {
|
||||
@@ -123,6 +125,10 @@ export default {
|
||||
await this.initData()
|
||||
this.loadSubCategories('all')
|
||||
await this.loadGoods(1)
|
||||
// 首屏数据渲染完成后计算滚动区高度
|
||||
this.$nextTick(() => {
|
||||
this.calcScrollHeight()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async initData() {
|
||||
@@ -253,6 +259,11 @@ export default {
|
||||
this.hasMore = this.products.length < total
|
||||
this.loadingMore = false
|
||||
|
||||
// 数据变更后重算滚动区高度(筛选标签/子分类数量变化会影响上方占位高度)
|
||||
this.$nextTick(() => {
|
||||
this.calcScrollHeight()
|
||||
})
|
||||
|
||||
console.log(
|
||||
'[loadGoods] total=' + total +
|
||||
', page=' + currentPage +
|
||||
@@ -352,6 +363,41 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 动态计算商品滚动区高度(px)。
|
||||
* 用 createSelectorQuery 测量 SearchBar / CountryBar / ActiveFilters / subCategories 的实际高度,
|
||||
* 用 windowHeight 依次减去,得到 scroll-view 可用的固定高度。
|
||||
* —— 完全符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求。
|
||||
*/
|
||||
calcScrollHeight() {
|
||||
const query = uni.createSelectorQuery().in(this)
|
||||
// 分别测量 4 个元素的 boundingClientRect
|
||||
query.select('#searchBar').boundingClientRect()
|
||||
query.select('#countryBar').boundingClientRect()
|
||||
query.select('#activeFilters').boundingClientRect()
|
||||
query.select('#subCategories').boundingClientRect()
|
||||
query.exec((res) => {
|
||||
// res[0]=searchBar, res[1]=countryBar, res[2]=activeFilters, res[3]=subCategories
|
||||
// 注意:v-if 为 false 时 res[i] 可能为 null
|
||||
const searchBarH = res[0] ? res[0].height : 0
|
||||
const countryBarH = res[1] ? res[1].height : 0
|
||||
const activeFiltersH = res[2] ? res[2].height : 0
|
||||
const subCategoriesH = res[3] ? res[3].height : 0
|
||||
|
||||
const totalUsed = searchBarH + countryBarH + activeFiltersH + subCategoriesH
|
||||
let h = this.windowHeight - totalUsed
|
||||
// 最小值保护:至少 200px,避免极端情况
|
||||
if (h < 200) h = 200
|
||||
if (h > this.windowHeight) h = this.windowHeight
|
||||
|
||||
this.productScrollHeight = Math.round(h)
|
||||
console.log('[calcScrollHeight] windowHeight=' + this.windowHeight +
|
||||
', searchBar=' + searchBarH + ', countryBar=' + countryBarH +
|
||||
', activeFilters=' + activeFiltersH + ', subCategories=' + subCategoriesH +
|
||||
' → scrollHeight=' + this.productScrollHeight)
|
||||
})
|
||||
},
|
||||
|
||||
async selectCountry(id) {
|
||||
// 如果点击的是已选中的国家 → 取消选中
|
||||
if (this.selectedCountry === id) {
|
||||
|
||||
Reference in New Issue
Block a user