首页底部
Test Runner / hello (push) Successful in 4s

This commit is contained in:
2026-08-25 18:12:16 +08:00
parent 69734f36b4
commit 4bee0101e7
22 changed files with 378 additions and 183 deletions
+103 -53
View File
@@ -47,30 +47,39 @@
</view> </view>
</view> </view>
<!-- 底部CTA区域 - 橙色背景+图片叠加+二维码+文字 --> <!-- 底部CTA区域 - 图片底+橙色叠加+内容层 -->
<view class="cta-footer"> <view class="cta-footer">
<!-- 橙色背景层 --> <!-- 图片底层 - image_26原图 -->
<image class="cta-overlay-img" src="/static/images/home/image_26_1496_1761.png" mode="widthFix"></image>
<!-- 橙色叠加层 - 在图片上方, 高不透明度让橙色更亮更突出 -->
<view class="cta-orange-bg"></view> <view class="cta-orange-bg"></view>
<!-- 图片叠加 --> <!-- 内容 -->
<image <view class="cta-content">
class="cta-overlay-img" <!-- 长按识别 196×20px 377×38rpx, 14px Medium white -->
src="/static/images/home/image_26_1496_1761.png" <text class="cta-tip">长按识别二维码添加经理微信</text>
mode="widthFix" <!-- 装饰箭头图标 19×10px 37×19rpx -->
></image> <image class="cta-arrow" src="/static/icons/home/icon_arrow_down.png" mode="aspectFit"></image>
<!-- 二维码 --> <!-- 二维码 104×104px 200×200rpx, 圆角10px 19rpx -->
<view class="qr-wrap"> <view class="qr-wrap">
<image
class="qr-img"
src="/static/images/home/Rectangle_187_1470_1653.png"
mode="aspectFit"
></image>
</view>
<!-- 电话栏 200×45px 385×87rpx, #000 0.1透明, 圆角10px 19rpx -->
<view class="phone-pill">
<image class="phone-icon" src="/static/icons/home/icon_phone.svg" mode="aspectFit"></image>
<text class="phone-text">15999928161</text>
<image class="phone-icon" src="/static/icons/home/icon_wechat.svg" mode="aspectFit"></image>
</view>
<!-- 联系我们 - 用户提供的image.png -->
<image <image
class="qr-img" class="cta-text-img"
src="/static/images/home/Rectangle_187_1470_1653.png" src="/static/images/home/contact_us.png"
mode="aspectFit" mode="widthFix"
></image> ></image>
</view> </view>
<!-- 联系我们文字图 -->
<image
class="cta-text-img"
src="/static/images/home/image_28_1498_1767.png"
mode="widthFix"
></image>
</view> </view>
</view> </view>
</template> </template>
@@ -238,61 +247,102 @@ export default {
line-height: 1.45; line-height: 1.45;
} }
/* 底部CTA - 橙色背景+图片叠加+二维码+文字 */ /* 底部CTA - 橙色背景+图片叠加层+内容层 */
.cta-footer { .cta-footer {
position: relative; position: relative;
width: 100%; width: 750rpx;
display: flex; overflow: hidden;
flex-direction: column;
align-items: center;
padding: 0;
box-sizing: border-box;
} }
/* 橙色背景层 - 391x200 */ /* 图片底层 - image_26原图 */
.cta-overlay-img {
position: relative;
width: 100%;
z-index: 0;
display: block;
}
/* 橙色叠加层 - 在图片上方, 0.8高不透明度让亮橙色突出 */
.cta-orange-bg { .cta-orange-bg {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 400rpx; height: 100%;
background: #ff6800; background: #ff6800;
z-index: 0; opacity: 0.85;
}
/* 图片叠加层 - 390x219 */
.cta-overlay-img {
position: relative;
width: 100%;
z-index: 1; z-index: 1;
display: block;
} }
/* 二维码 - 104x104 居中 圆角10px 白底 */ /* 内容层 - absolute覆盖图片和橙色层, flex column 居中 */
.qr-wrap { .cta-content {
position: absolute; position: absolute;
top: 54rpx; top: 0;
left: 50%; left: 0;
transform: translateX(-50%); width: 100%;
width: 208rpx; height: 100%;
height: 208rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 20rpx;
box-sizing: border-box;
z-index: 2; z-index: 2;
display: flex; display: flex;
flex-direction: column;
align-items: center;
padding-top: 33rpx; /* 17px → 33rpx */
box-sizing: border-box;
}
/* 长按识别 14px Medium white → 27rpx */
.cta-tip {
font-size: 27rpx;
font-weight: 500;
color: #ffffff;
line-height: 38rpx;
}
/* 装饰箭头图标 19×10px → 37×19rpx */
.cta-arrow {
width: 37rpx;
height: 19rpx;
margin-top: 8rpx; /* 4px → 8rpx */
}
/* 二维码 104×104px → 200×200rpx, 圆角10px → 19rpx */
.qr-wrap {
margin-top: 13rpx; /* 7px → 13rpx */
width: 200rpx;
height: 200rpx;
background: #ffffff;
border-radius: 19rpx;
padding: 0;
box-sizing: border-box;
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
} }
.qr-img { .qr-img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
/* 联系我们文字图 - 236x78 居中 */ /* 电话栏 200×45px → 385×87rpx, #000 0.1透明, 圆角10px → 19rpx */
.phone-pill {
margin-top: 23rpx; /* 12px → 23rpx */
width: 385rpx;
height: 87rpx;
background: rgba(0, 0, 0, 0.1);
border-radius: 19rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
box-sizing: border-box;
}
.phone-icon {
width: 35rpx; /* 18px → 35rpx */
height: 35rpx;
flex-shrink: 0;
}
.phone-text {
font-size: 23rpx; /* 12px → 23rpx */
font-weight: 700;
color: #ffffff;
line-height: 32rpx;
}
/* 联系我们 - 用户提供的image.png */
.cta-text-img { .cta-text-img {
position: absolute; margin-top: 19rpx; /* 10px → 19rpx */
top: 256rpx; width: 569rpx;
left: 50%;
transform: translateX(-50%);
width: 472rpx;
z-index: 3;
} }
</style> </style>
@@ -18,7 +18,33 @@ export default {
default: () => [] default: () => []
} }
}, },
mounted() {
// 首次测量(可能为 0,因为 v-if 可能为 false
this.$nextTick(() => this.emitHeight())
},
watch: {
// 筛选标签增减 → 高度变化 → 重新测量
'list.length'() {
this.$nextTick(() => this.emitHeight())
}
},
methods: { methods: {
emitHeight() {
// v-if 为 false 时 #activeFilters 不存在,返回 0
if (!this.list || this.list.length === 0) {
this.$emit('height-change', 0)
return
}
const query = uni.createSelectorQuery().in(this)
query.select('#activeFilters').boundingClientRect()
query.exec((res) => {
if (res && res[0]) {
this.$emit('height-change', res[0].height)
} else {
this.$emit('height-change', 0)
}
})
},
onRemove(key) { onRemove(key) {
this.$emit('remove', key) this.$emit('remove', key)
}, },
+19
View File
@@ -30,7 +30,26 @@ export default {
default: '' default: ''
} }
}, },
mounted() {
// 首次测量
this.$nextTick(() => this.emitHeight())
},
watch: {
// 国家列表变化 → 换行行数可能变化 → 重新测量
'list.length'() {
this.$nextTick(() => this.emitHeight())
}
},
methods: { methods: {
emitHeight() {
const query = uni.createSelectorQuery().in(this)
query.select('#countryBar').boundingClientRect()
query.exec((res) => {
if (res && res[0]) {
this.$emit('height-change', res[0].height)
}
})
},
selectItem(item) { selectItem(item) {
this.$emit('select', item.id) this.$emit('select', item.id)
} }
+26
View File
@@ -81,7 +81,33 @@ export default {
// 商品滚动区固定高度(px),由父组件动态计算传入 // 商品滚动区固定高度(px),由父组件动态计算传入
scrollHeight: { type: Number, default: 400 } scrollHeight: { type: Number, default: 400 }
}, },
mounted() {
// 首次测量子分类高度
this.$nextTick(() => this.emitSubHeight())
},
watch: {
// 子分类列表变化 → 重新测量
'subCategories.length'() {
this.$nextTick(() => this.emitSubHeight())
}
},
methods: { 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) }, selectSub(id) { this.$emit('sub-select', id) },
onTapProduct(item) { this.$emit('product-click', item) }, onTapProduct(item) { this.$emit('product-click', item) },
onScrollToLower() { this.$emit('scroll-to-lower') } onScrollToLower() { this.$emit('scroll-to-lower') }
+13
View File
@@ -41,7 +41,20 @@ export default {
this.statusBarHeight = 20 this.statusBarHeight = 20
} }
}, },
mounted() {
// 测量自身高度并 emit 给父组件(小程序端无法跨组件查询 DOM,必须自测量)
this.$nextTick(() => this.emitHeight())
},
methods: { methods: {
emitHeight() {
const query = uni.createSelectorQuery().in(this)
query.select('#searchBar').boundingClientRect()
query.exec((res) => {
if (res && res[0]) {
this.$emit('height-change', res[0].height)
}
})
},
onInput(e) { onInput(e) {
this.$emit('input', e.detail.value) this.$emit('input', e.detail.value)
}, },
+69 -41
View File
@@ -7,6 +7,7 @@
@input="onSearchInput" @input="onSearchInput"
@blur="onSearchBlur" @blur="onSearchBlur"
@filter="toggleFilter" @filter="toggleFilter"
@height-change="onSearchBarHeight"
/> />
<!-- 国家选择条 --> <!-- 国家选择条 -->
@@ -14,6 +15,7 @@
:list="countries" :list="countries"
:selected-id="selectedCountry" :selected-id="selectedCountry"
@select="selectCountry" @select="selectCountry"
@height-change="onCountryBarHeight"
/> />
<!-- 主体内容左侧分类侧边栏(固定宽度) + 右侧内容区(自动剩余宽度) --> <!-- 主体内容左侧分类侧边栏(固定宽度) + 右侧内容区(自动剩余宽度) -->
@@ -32,6 +34,7 @@
:list="activeFilters" :list="activeFilters"
@remove="removeFilter" @remove="removeFilter"
@clear-all="clearAllFilters" @clear-all="clearAllFilters"
@height-change="onActiveFiltersHeight"
/> />
<!-- 产品列表(内含子分类条件+滚动商品) --> <!-- 产品列表(内含子分类条件+滚动商品) -->
@@ -46,6 +49,7 @@
@sub-select="selectSubCategory" @sub-select="selectSubCategory"
@product-click="goDetail" @product-click="goDetail"
@scroll-to-lower="onScrollToLower" @scroll-to-lower="onScrollToLower"
@sub-height-change="onSubCategoriesHeight"
/> />
</view> </view>
</view> </view>
@@ -83,6 +87,16 @@ export default {
ProductGrid, ProductGrid,
FilterDrawer FilterDrawer
}, },
watch: {
// 筛选标签增减 → 子组件会自测量并 emit,父组件这里不需要 watch
// 保留一个兜底:activeFilters.length 变化时主动触发一次重算
'activeFilters.length'() {
this.recomputeScrollHeight()
},
'subCategories.length'() {
this.recomputeScrollHeight()
}
},
data() { data() {
return { return {
searchKeyword: '', searchKeyword: '',
@@ -110,25 +124,33 @@ export default {
products: [], products: [],
windowHeight: 667, windowHeight: 667,
scrollTopVal: 0, scrollTopVal: 0,
productScrollHeight: 400 // 商品滚动区高度(px),动态计算 productScrollHeight: 400, // 商品滚动区高度(px),由子组件测量后动态计算
// 各子组件上报的高度(px),默认 0
_searchBarH: 0,
_countryBarH: 0,
_activeFiltersH: 0,
_subCategoriesH: 0
} }
}, },
async onLoad() { async onLoad() {
try { try {
const sysInfo = uni.getSystemInfoSync() const sysInfo = uni.getSystemInfoSync()
// windowHeight 已自动扣除 uni-app 默认导航栏 & TabBar 高度 // tabBar 页 + navigationStyle:custom 时,windowHeight 在不同平台/版本可能不扣 tabBar
// 当前页是 navigationStyle:custom + TabBar 页,正好匹配可视区 // 改用 screenHeight - tabBarHeight 计算,确保可视区高度正确
this.windowHeight = sysInfo.windowHeight || 667 const tabBarHeight = sysInfo.tabBarHeight || 50
this.windowHeight = (sysInfo.screenHeight || 750) - tabBarHeight
console.log('[onLoad] screenHeight=' + sysInfo.screenHeight +
', tabBarHeight=' + tabBarHeight +
' → windowHeight=' + this.windowHeight)
} catch (e) { } catch (e) {
this.windowHeight = 667 this.windowHeight = 667
} }
await this.initData() await this.initData()
this.loadSubCategories('all') this.loadSubCategories('all')
await this.loadGoods(1) await this.loadGoods(1)
// 首屏数据渲染完成后计算滚动区高度 // 首屏数据渲染完成后,子组件会通过 mounted 自行测量并 emit 高度
this.$nextTick(() => { // 这里兜底触发一次重算(防止某些端 mounted 比父组件 onLoad 晚)
this.calcScrollHeight() this.recomputeScrollHeight()
})
}, },
methods: { methods: {
async initData() { async initData() {
@@ -259,10 +281,8 @@ export default {
this.hasMore = this.products.length < total this.hasMore = this.products.length < total
this.loadingMore = false this.loadingMore = false
// 数据变更后重算滚动区高度(筛选标签/子分类数量变化会影响上方占位高度 // 数据变更后兜底重算滚动区高度(子组件会自行 emit,这里兜底
this.$nextTick(() => { this.recomputeScrollHeight()
this.calcScrollHeight()
})
console.log( console.log(
'[loadGoods] total=' + total + '[loadGoods] total=' + total +
@@ -364,38 +384,46 @@ export default {
}, },
/** /**
* 动态计算商品滚动区高度(px)。 * 子组件高度上报回调 —— 每个子组件在 mounted / 内容变化时自行测量 DOM 高度,
* 用 createSelectorQuery 测量 SearchBar / CountryBar / ActiveFilters / subCategories 的实际高度 * 通过 @height-change 事件上报给父组件。父组件汇总后重算滚动区高度
* 用 windowHeight 依次减去,得到 scroll-view 可用的固定高度。 *
* 这种方式解决了 uni-app 小程序端 createSelectorQuery 无法跨组件查询 DOM 的问题:
* H5 端 DOM 全局可查,但小程序每个组件有独立节点树,页面级 select 选不到子组件内部的节点。
*/
onSearchBarHeight(h) {
this._searchBarH = h
this.recomputeScrollHeight()
},
onCountryBarHeight(h) {
this._countryBarH = h
this.recomputeScrollHeight()
},
onActiveFiltersHeight(h) {
this._activeFiltersH = h
this.recomputeScrollHeight()
},
onSubCategoriesHeight(h) {
this._subCategoriesH = h
this.recomputeScrollHeight()
},
/**
* 根据 4 个子组件上报的高度,计算 scroll-view 可用的固定高度。
* —— 完全符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求。 * —— 完全符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求。
*/ */
calcScrollHeight() { recomputeScrollHeight() {
const query = uni.createSelectorQuery().in(this) const totalUsed = this._searchBarH + this._countryBarH +
// 分别测量 4 个元素的 boundingClientRect this._activeFiltersH + this._subCategoriesH
query.select('#searchBar').boundingClientRect() let h = this.windowHeight - totalUsed
query.select('#countryBar').boundingClientRect() // 最小值保护:至少 200px,避免极端情况
query.select('#activeFilters').boundingClientRect() if (h < 200) h = 200
query.select('#subCategories').boundingClientRect() if (h > this.windowHeight) h = this.windowHeight
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 this.productScrollHeight = Math.round(h)
let h = this.windowHeight - totalUsed console.log('[recomputeScrollHeight] windowHeight=' + this.windowHeight +
// 最小值保护:至少 200px,避免极端情况 ', searchBar=' + this._searchBarH + ', countryBar=' + this._countryBarH +
if (h < 200) h = 200 ', activeFilters=' + this._activeFiltersH + ', subCategories=' + this._subCategoriesH +
if (h > this.windowHeight) h = this.windowHeight ' → scrollHeight=' + this.productScrollHeight)
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) { async selectCountry(id) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.898 16.875H12.8215C11.0215 16.7805 8.59034 14.9985 6.31559 12.1117L5.47634 11.052C3.20159 8.16525 2.01021 5.34487 2.29034 3.51113C2.48609 2.21738 4.31871 1.125 5.36609 1.125C5.88134 1.125 6.06921 1.39388 6.13671 1.55588C6.72509 2.664 7.45859 4.58663 7.46421 5.27625V5.32463L7.44734 5.37075C7.31571 5.733 7.02434 5.90625 6.76559 6.06038C6.42471 6.26288 6.23234 6.3945 6.20759 6.77587C6.20196 6.89062 6.29646 7.41825 7.79608 9.35775L8.43734 10.1711C9.94258 12.0364 10.4151 12.2366 10.5242 12.2524C10.8876 12.3075 11.0608 12.1444 11.3207 11.8418C11.5187 11.6089 11.7437 11.3512 12.1138 11.295L12.1633 11.286L12.2083 11.3017C12.8653 11.4424 14.5371 12.5764 15.4933 13.4201C15.6283 13.5146 15.868 13.8004 15.679 14.427C15.382 15.4114 14.0848 16.875 12.898 16.875Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 890 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.898 16.875H12.8215C11.0215 16.7805 8.59034 14.9985 6.31559 12.1117L5.47634 11.052C3.20159 8.16525 2.01021 5.34487 2.29034 3.51113C2.48609 2.21738 4.31871 1.125 5.36609 1.125C5.88134 1.125 6.06921 1.39388 6.13671 1.55588C6.72509 2.664 7.45859 4.58663 7.46421 5.27625V5.32463L7.44734 5.37075C7.31571 5.733 7.02434 5.90625 6.76559 6.06038C6.42471 6.26288 6.23234 6.3945 6.20759 6.77587C6.20196 6.89062 6.29646 7.41825 7.79608 9.35775L8.43734 10.1711C9.94258 12.0364 10.4151 12.2366 10.5242 12.2524C10.8876 12.3075 11.0608 12.1444 11.3207 11.8418C11.5187 11.6089 11.7437 11.3512 12.1138 11.295L12.1633 11.286L12.2083 11.3017C12.8653 11.4424 14.5371 12.5764 15.4933 13.4201C15.6283 13.5146 15.868 13.8004 15.679 14.427C15.382 15.4114 14.0848 16.875 12.898 16.875Z" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 892 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.24985 4.5V2.25C5.24985 1.83579 5.58564 1.5 5.99985 1.5H14.9998C15.4141 1.5 15.7498 1.83579 15.7498 2.25V12.75C15.7498 13.1642 15.4141 13.5 14.9998 13.5H12.7498V15.7493C12.7498 16.1639 12.4124 16.5 11.9948 16.5H3.00499C2.58794 16.5 2.25 16.1665 2.25 15.7493L2.25195 5.25065C2.25202 4.83608 2.58948 4.5 3.00706 4.5H5.24985ZM3.75181 6L3.75014 15H11.2498V6H3.75181ZM6.74985 4.5H12.7498V12H14.2498V3H6.74985V4.5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 538 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.24985 4.5V2.25C5.24985 1.83579 5.58564 1.5 5.99985 1.5H14.9998C15.4141 1.5 15.7498 1.83579 15.7498 2.25V12.75C15.7498 13.1642 15.4141 13.5 14.9998 13.5H12.7498V15.7493C12.7498 16.1639 12.4124 16.5 11.9948 16.5H3.00499C2.58794 16.5 2.25 16.1665 2.25 15.7493L2.25195 5.25065C2.25202 4.83608 2.58948 4.5 3.00706 4.5H5.24985ZM3.75181 6L3.75014 15H11.2498V6H3.75181ZM6.74985 4.5H12.7498V12H14.2498V3H6.74985V4.5Z" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 KiB

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 616 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+105 -84
View File
@@ -188,6 +188,16 @@ var _default = {
ProductGrid: ProductGrid, ProductGrid: ProductGrid,
FilterDrawer: FilterDrawer FilterDrawer: FilterDrawer
}, },
watch: {
// 筛选标签增减 → 子组件会自测量并 emit,父组件这里不需要 watch
// 保留一个兜底:activeFilters.length 变化时主动触发一次重算
'activeFilters.length': function activeFiltersLength() {
this.recomputeScrollHeight();
},
'subCategories.length': function subCategoriesLength() {
this.recomputeScrollHeight();
}
},
data: function data() { data: function data() {
return { return {
searchKeyword: '', searchKeyword: '',
@@ -213,21 +223,29 @@ var _default = {
products: [], products: [],
windowHeight: 667, windowHeight: 667,
scrollTopVal: 0, scrollTopVal: 0,
productScrollHeight: 400 // 商品滚动区高度(px),动态计算 productScrollHeight: 400,
// 商品滚动区高度(px),由子组件测量后动态计算
// 各子组件上报的高度(px),默认 0
_searchBarH: 0,
_countryBarH: 0,
_activeFiltersH: 0,
_subCategoriesH: 0
}; };
}, },
onLoad: function onLoad() { onLoad: function onLoad() {
var _this = this; var _this = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
var sysInfo; var sysInfo, tabBarHeight;
return _regenerator.default.wrap(function _callee$(_context) { return _regenerator.default.wrap(function _callee$(_context) {
while (1) { while (1) {
switch (_context.prev = _context.next) { switch (_context.prev = _context.next) {
case 0: case 0:
try { try {
sysInfo = uni.getSystemInfoSync(); // windowHeight 已自动扣除 uni-app 默认导航栏 & TabBar 高度 sysInfo = uni.getSystemInfoSync(); // tabBar 页 + navigationStyle:custom 时,windowHeight 在不同平台/版本可能不扣 tabBar
// 当前页是 navigationStyle:custom + TabBar 页,正好匹配可视区 // 改用 screenHeight - tabBarHeight 计算,确保可视区高度正确
_this.windowHeight = sysInfo.windowHeight || 667; tabBarHeight = sysInfo.tabBarHeight || 50;
_this.windowHeight = (sysInfo.screenHeight || 750) - tabBarHeight;
console.log('[onLoad] screenHeight=' + sysInfo.screenHeight + ', tabBarHeight=' + tabBarHeight + ' → windowHeight=' + _this.windowHeight);
} catch (e) { } catch (e) {
_this.windowHeight = 667; _this.windowHeight = 667;
} }
@@ -238,10 +256,9 @@ var _default = {
_context.next = 6; _context.next = 6;
return _this.loadGoods(1); return _this.loadGoods(1);
case 6: case 6:
// 首屏数据渲染完成后计算滚动区高度 // 首屏数据渲染完成后,子组件会通过 mounted 自行测量并 emit 高度
_this.$nextTick(function () { // 这里兜底触发一次重算(防止某些端 mounted 比父组件 onLoad 晚)
_this.calcScrollHeight(); _this.recomputeScrollHeight();
});
case 7: case 7:
case "end": case "end":
return _context.stop(); return _context.stop();
@@ -432,10 +449,8 @@ var _default = {
_this4.hasMore = _this4.products.length < total; _this4.hasMore = _this4.products.length < total;
_this4.loadingMore = false; _this4.loadingMore = false;
// 数据变更后重算滚动区高度(筛选标签/子分类数量变化会影响上方占位高度 // 数据变更后兜底重算滚动区高度(子组件会自行 emit,这里兜底
_this4.$nextTick(function () { _this4.recomputeScrollHeight();
_this4.calcScrollHeight();
});
console.log('[loadGoods] total=' + total + ', page=' + currentPage + ', items=' + products.length + ', loaded=' + _this4.products.length + ', hasMore=' + _this4.hasMore); console.log('[loadGoods] total=' + total + ', page=' + currentPage + ', items=' + products.length + ', loaded=' + _this4.products.length + ', hasMore=' + _this4.hasMore);
_context4.next = 25; _context4.next = 25;
break; break;
@@ -542,64 +557,70 @@ var _default = {
}); });
}, },
/** /**
* 动态计算商品滚动区高度(px)。 * 子组件高度上报回调 —— 每个子组件在 mounted / 内容变化时自行测量 DOM 高度,
* 用 createSelectorQuery 测量 SearchBar / CountryBar / ActiveFilters / subCategories 的实际高度 * 通过 @height-change 事件上报给父组件。父组件汇总后重算滚动区高度
* 用 windowHeight 依次减去,得到 scroll-view 可用的固定高度。 *
* 这种方式解决了 uni-app 小程序端 createSelectorQuery 无法跨组件查询 DOM 的问题:
* H5 端 DOM 全局可查,但小程序每个组件有独立节点树,页面级 select 选不到子组件内部的节点。
*/
onSearchBarHeight: function onSearchBarHeight(h) {
this._searchBarH = h;
this.recomputeScrollHeight();
},
onCountryBarHeight: function onCountryBarHeight(h) {
this._countryBarH = h;
this.recomputeScrollHeight();
},
onActiveFiltersHeight: function onActiveFiltersHeight(h) {
this._activeFiltersH = h;
this.recomputeScrollHeight();
},
onSubCategoriesHeight: function onSubCategoriesHeight(h) {
this._subCategoriesH = h;
this.recomputeScrollHeight();
},
/**
* 根据 4 个子组件上报的高度,计算 scroll-view 可用的固定高度。
* —— 完全符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求。 * —— 完全符合 uni-app / 微信小程序官方文档「scroll-y 需给固定 height」的要求。
*/ */
calcScrollHeight: function calcScrollHeight() { recomputeScrollHeight: function recomputeScrollHeight() {
var _this6 = this; var totalUsed = this._searchBarH + this._countryBarH + this._activeFiltersH + this._subCategoriesH;
var query = uni.createSelectorQuery().in(this); var h = this.windowHeight - totalUsed;
// 分别测量 4 个元素的 boundingClientRect // 最小值保护:至少 200px,避免极端情况
query.select('#searchBar').boundingClientRect(); if (h < 200) h = 200;
query.select('#countryBar').boundingClientRect(); if (h > this.windowHeight) h = this.windowHeight;
query.select('#activeFilters').boundingClientRect(); this.productScrollHeight = Math.round(h);
query.select('#subCategories').boundingClientRect(); console.log('[recomputeScrollHeight] windowHeight=' + this.windowHeight + ', searchBar=' + this._searchBarH + ', countryBar=' + this._countryBarH + ', activeFilters=' + this._activeFiltersH + ', subCategories=' + this._subCategoriesH + ' → scrollHeight=' + this.productScrollHeight);
query.exec(function (res) {
// res[0]=searchBar, res[1]=countryBar, res[2]=activeFilters, res[3]=subCategories
// 注意:v-if 为 false 时 res[i] 可能为 null
var searchBarH = res[0] ? res[0].height : 0;
var countryBarH = res[1] ? res[1].height : 0;
var activeFiltersH = res[2] ? res[2].height : 0;
var subCategoriesH = res[3] ? res[3].height : 0;
var totalUsed = searchBarH + countryBarH + activeFiltersH + subCategoriesH;
var h = _this6.windowHeight - totalUsed;
// 最小值保护:至少 200px,避免极端情况
if (h < 200) h = 200;
if (h > _this6.windowHeight) h = _this6.windowHeight;
_this6.productScrollHeight = Math.round(h);
console.log('[calcScrollHeight] windowHeight=' + _this6.windowHeight + ', searchBar=' + searchBarH + ', countryBar=' + countryBarH + ', activeFilters=' + activeFiltersH + ', subCategories=' + subCategoriesH + ' → scrollHeight=' + _this6.productScrollHeight);
});
}, },
selectCountry: function selectCountry(id) { selectCountry: function selectCountry(id) {
var _this7 = this; var _this6 = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() {
var country, filters; var country, filters;
return _regenerator.default.wrap(function _callee5$(_context5) { return _regenerator.default.wrap(function _callee5$(_context5) {
while (1) { while (1) {
switch (_context5.prev = _context5.next) { switch (_context5.prev = _context5.next) {
case 0: case 0:
if (!(_this7.selectedCountry === id)) { if (!(_this6.selectedCountry === id)) {
_context5.next = 8; _context5.next = 8;
break; break;
} }
_this7.selectedCountry = ''; _this6.selectedCountry = '';
_this7.activeFilters = _this7.activeFilters.filter(function (f) { _this6.activeFilters = _this6.activeFilters.filter(function (f) {
return f.key !== 'country'; return f.key !== 'country';
}); });
_context5.next = 5; _context5.next = 5;
return _this7.loadCategoryData(''); return _this6.loadCategoryData('');
case 5: case 5:
_context5.next = 7; _context5.next = 7;
return _this7.loadGoods(1); return _this6.loadGoods(1);
case 7: case 7:
return _context5.abrupt("return"); return _context5.abrupt("return");
case 8: case 8:
// 选中新国家 // 选中新国家
country = _this7.countries.find(function (c) { country = _this6.countries.find(function (c) {
return c.id === id; return c.id === id;
}); });
filters = _this7.activeFilters.filter(function (f) { filters = _this6.activeFilters.filter(function (f) {
return f.key !== 'country'; return f.key !== 'country';
}); });
if (country) { if (country) {
@@ -608,13 +629,13 @@ var _default = {
label: country.name label: country.name
}); });
} }
_this7.selectedCountry = id; _this6.selectedCountry = id;
_this7.activeFilters = filters; _this6.activeFilters = filters;
_context5.next = 15; _context5.next = 15;
return _this7.loadCategoryData(id); return _this6.loadCategoryData(id);
case 15: case 15:
_context5.next = 17; _context5.next = 17;
return _this7.loadGoods(1); return _this6.loadGoods(1);
case 17: case 17:
case "end": case "end":
return _context5.stop(); return _context5.stop();
@@ -624,16 +645,16 @@ var _default = {
}))(); }))();
}, },
selectCategory: function selectCategory(id) { selectCategory: function selectCategory(id) {
var _this8 = this; var _this7 = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee6() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee6() {
return _regenerator.default.wrap(function _callee6$(_context6) { return _regenerator.default.wrap(function _callee6$(_context6) {
while (1) { while (1) {
switch (_context6.prev = _context6.next) { switch (_context6.prev = _context6.next) {
case 0: case 0:
_this8.activeCategory = id; _this7.activeCategory = id;
_this8.loadSubCategories(id); _this7.loadSubCategories(id);
_context6.next = 4; _context6.next = 4;
return _this8.loadGoods(1); return _this7.loadGoods(1);
case 4: case 4:
case "end": case "end":
return _context6.stop(); return _context6.stop();
@@ -643,15 +664,15 @@ var _default = {
}))(); }))();
}, },
selectSubCategory: function selectSubCategory(id) { selectSubCategory: function selectSubCategory(id) {
var _this9 = this; var _this8 = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee7() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee7() {
return _regenerator.default.wrap(function _callee7$(_context7) { return _regenerator.default.wrap(function _callee7$(_context7) {
while (1) { while (1) {
switch (_context7.prev = _context7.next) { switch (_context7.prev = _context7.next) {
case 0: case 0:
_this9.activeSubCategory = id; _this8.activeSubCategory = id;
_context7.next = 3; _context7.next = 3;
return _this9.loadGoods(1); return _this8.loadGoods(1);
case 3: case 3:
case "end": case "end":
return _context7.stop(); return _context7.stop();
@@ -683,27 +704,27 @@ var _default = {
this.filterGroups = groups; this.filterGroups = groups;
}, },
removeFilter: function removeFilter(key) { removeFilter: function removeFilter(key) {
var _this10 = this; var _this9 = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee8() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee8() {
var filters, groupId, groups; var filters, groupId, groups;
return _regenerator.default.wrap(function _callee8$(_context8) { return _regenerator.default.wrap(function _callee8$(_context8) {
while (1) { while (1) {
switch (_context8.prev = _context8.next) { switch (_context8.prev = _context8.next) {
case 0: case 0:
filters = _this10.activeFilters.filter(function (f) { filters = _this9.activeFilters.filter(function (f) {
return f.key !== key; return f.key !== key;
}); });
if (!(key === 'country')) { if (!(key === 'country')) {
_context8.next = 9; _context8.next = 9;
break; break;
} }
_this10.selectedCountry = ''; _this9.selectedCountry = '';
_this10.activeFilters = filters; _this9.activeFilters = filters;
_context8.next = 6; _context8.next = 6;
return _this10.loadCategoryData(''); return _this9.loadCategoryData('');
case 6: case 6:
_context8.next = 8; _context8.next = 8;
return _this10.loadGoods(1); return _this9.loadGoods(1);
case 8: case 8:
return _context8.abrupt("return"); return _context8.abrupt("return");
case 9: case 9:
@@ -712,7 +733,7 @@ var _default = {
break; break;
} }
groupId = key.replace('tag_', ''); groupId = key.replace('tag_', '');
groups = _this10.filterGroups.map(function (g) { groups = _this9.filterGroups.map(function (g) {
if (String(g.id) === String(groupId)) { if (String(g.id) === String(groupId)) {
return Object.assign({}, g, { return Object.assign({}, g, {
selected: [] selected: []
@@ -720,14 +741,14 @@ var _default = {
} }
return g; return g;
}); });
_this10.activeFilters = filters; _this9.activeFilters = filters;
_this10.filterGroups = groups; _this9.filterGroups = groups;
_context8.next = 16; _context8.next = 16;
return _this10.loadGoods(1); return _this9.loadGoods(1);
case 16: case 16:
return _context8.abrupt("return"); return _context8.abrupt("return");
case 17: case 17:
_this10.activeFilters = filters; _this9.activeFilters = filters;
case 18: case 18:
case "end": case "end":
return _context8.stop(); return _context8.stop();
@@ -737,33 +758,33 @@ var _default = {
}))(); }))();
}, },
clearAllFilters: function clearAllFilters() { clearAllFilters: function clearAllFilters() {
var _this11 = this; var _this10 = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee9() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee9() {
var hasCountry, groups; var hasCountry, groups;
return _regenerator.default.wrap(function _callee9$(_context9) { return _regenerator.default.wrap(function _callee9$(_context9) {
while (1) { while (1) {
switch (_context9.prev = _context9.next) { switch (_context9.prev = _context9.next) {
case 0: case 0:
hasCountry = _this11.activeFilters.some(function (f) { hasCountry = _this10.activeFilters.some(function (f) {
return f.key === 'country'; return f.key === 'country';
}); });
_this11.activeFilters = []; _this10.activeFilters = [];
if (!(hasCountry || _this11.selectedCountry)) { if (!(hasCountry || _this10.selectedCountry)) {
_context9.next = 6; _context9.next = 6;
break; break;
} }
_this11.selectedCountry = ''; _this10.selectedCountry = '';
_context9.next = 6; _context9.next = 6;
return _this11.loadCategoryData(''); return _this10.loadCategoryData('');
case 6: case 6:
groups = _this11.filterGroups.map(function (g) { groups = _this10.filterGroups.map(function (g) {
return Object.assign({}, g, { return Object.assign({}, g, {
selected: [] selected: []
}); });
}); });
_this11.filterGroups = groups; _this10.filterGroups = groups;
_context9.next = 10; _context9.next = 10;
return _this11.loadGoods(1); return _this10.loadGoods(1);
case 10: case 10:
case "end": case "end":
return _context9.stop(); return _context9.stop();
@@ -781,7 +802,7 @@ var _default = {
this.filterGroups = groups; this.filterGroups = groups;
}, },
confirmFilters: function confirmFilters() { confirmFilters: function confirmFilters() {
var _this12 = this; var _this11 = this;
return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee10() { return (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee10() {
var filters, countryFilter; var filters, countryFilter;
return _regenerator.default.wrap(function _callee10$(_context10) { return _regenerator.default.wrap(function _callee10$(_context10) {
@@ -789,11 +810,11 @@ var _default = {
switch (_context10.prev = _context10.next) { switch (_context10.prev = _context10.next) {
case 0: case 0:
filters = []; filters = [];
countryFilter = _this12.activeFilters.find(function (f) { countryFilter = _this11.activeFilters.find(function (f) {
return f.key === 'country'; return f.key === 'country';
}); });
if (countryFilter) filters.push(countryFilter); if (countryFilter) filters.push(countryFilter);
_this12.filterGroups.forEach(function (group) { _this11.filterGroups.forEach(function (group) {
if (group.selected.length > 0) { if (group.selected.length > 0) {
var labels = group.tags.filter(function (t) { var labels = group.tags.filter(function (t) {
return group.selected.indexOf(t.id) > -1; return group.selected.indexOf(t.id) > -1;
@@ -808,10 +829,10 @@ var _default = {
} }
} }
}); });
_this12.activeFilters = filters; _this11.activeFilters = filters;
_this12.showFilter = false; _this11.showFilter = false;
_context10.next = 8; _context10.next = 8;
return _this12.loadGoods(1); return _this11.loadGoods(1);
case 8: case 8:
case "end": case "end":
return _context10.stop(); return _context10.stop();
+1 -1
View File
@@ -1 +1 @@
<view class="products-page" style="{{'height:'+(windowHeight+'px')+';'}}"><search-bar vue-id="08007ac0-1" keyword="{{searchKeyword}}" data-event-opts="{{[['^input',[['onSearchInput']]],['^blur',[['onSearchBlur']]],['^filter',[['toggleFilter']]]]}}" bind:input="__e" bind:blur="__e" bind:filter="__e" bind:__l="__l"></search-bar><country-bar vue-id="08007ac0-2" list="{{countries}}" selected-id="{{selectedCountry}}" data-event-opts="{{[['^select',[['selectCountry']]]]}}" bind:select="__e" bind:__l="__l"></country-bar><view class="content-body"><category-sidebar vue-id="08007ac0-3" list="{{categories}}" active-id="{{activeCategory}}" data-event-opts="{{[['^select',[['selectCategory']]]]}}" bind:select="__e" bind:__l="__l"></category-sidebar><view class="right-wrap"><active-filters vue-id="08007ac0-4" list="{{activeFilters}}" data-event-opts="{{[['^remove',[['removeFilter']]],['^clearAll',[['clearAllFilters']]]]}}" bind:remove="__e" bind:clearAll="__e" bind:__l="__l"></active-filters><product-grid vue-id="08007ac0-5" list="{{products}}" sub-categories="{{subCategories}}" active-sub-id="{{activeSubCategory}}" has-more="{{hasMore}}" loading="{{loadingMore}}" scroll-top-val="{{scrollTopVal}}" scroll-height="{{productScrollHeight}}" data-event-opts="{{[['^subSelect',[['selectSubCategory']]],['^productClick',[['goDetail']]],['^scrollToLower',[['onScrollToLower']]]]}}" bind:subSelect="__e" bind:productClick="__e" bind:scrollToLower="__e" bind:__l="__l"></product-grid></view></view><filter-drawer vue-id="08007ac0-6" visible="{{showFilter}}" groups="{{filterGroups}}" data-event-opts="{{[['^close',[['closeFilter']]],['^toggleTag',[['toggleFilterTag']]],['^reset',[['resetFilters']]],['^confirm',[['confirmFilters']]]]}}" bind:close="__e" bind:toggleTag="__e" bind:reset="__e" bind:confirm="__e" bind:__l="__l"></filter-drawer></view> <view class="products-page" style="{{'height:'+(windowHeight+'px')+';'}}"><search-bar vue-id="08007ac0-1" keyword="{{searchKeyword}}" data-event-opts="{{[['^input',[['onSearchInput']]],['^blur',[['onSearchBlur']]],['^filter',[['toggleFilter']]],['^heightChange',[['onSearchBarHeight']]]]}}" bind:input="__e" bind:blur="__e" bind:filter="__e" bind:heightChange="__e" bind:__l="__l"></search-bar><country-bar vue-id="08007ac0-2" list="{{countries}}" selected-id="{{selectedCountry}}" data-event-opts="{{[['^select',[['selectCountry']]],['^heightChange',[['onCountryBarHeight']]]]}}" bind:select="__e" bind:heightChange="__e" bind:__l="__l"></country-bar><view class="content-body"><category-sidebar vue-id="08007ac0-3" list="{{categories}}" active-id="{{activeCategory}}" data-event-opts="{{[['^select',[['selectCategory']]]]}}" bind:select="__e" bind:__l="__l"></category-sidebar><view class="right-wrap"><active-filters vue-id="08007ac0-4" list="{{activeFilters}}" data-event-opts="{{[['^remove',[['removeFilter']]],['^clearAll',[['clearAllFilters']]],['^heightChange',[['onActiveFiltersHeight']]]]}}" bind:remove="__e" bind:clearAll="__e" bind:heightChange="__e" bind:__l="__l"></active-filters><product-grid vue-id="08007ac0-5" list="{{products}}" sub-categories="{{subCategories}}" active-sub-id="{{activeSubCategory}}" has-more="{{hasMore}}" loading="{{loadingMore}}" scroll-top-val="{{scrollTopVal}}" scroll-height="{{productScrollHeight}}" data-event-opts="{{[['^subSelect',[['selectSubCategory']]],['^productClick',[['goDetail']]],['^scrollToLower',[['onScrollToLower']]],['^subHeightChange',[['onSubCategoriesHeight']]]]}}" bind:subSelect="__e" bind:productClick="__e" bind:scrollToLower="__e" bind:subHeightChange="__e" bind:__l="__l"></product-grid></view></view><filter-drawer vue-id="08007ac0-6" visible="{{showFilter}}" groups="{{filterGroups}}" data-event-opts="{{[['^close',[['closeFilter']]],['^toggleTag',[['toggleFilterTag']]],['^reset',[['resetFilters']]],['^confirm',[['confirmFilters']]]]}}" bind:close="__e" bind:toggleTag="__e" bind:reset="__e" bind:confirm="__e" bind:__l="__l"></filter-drawer></view>