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