refactor(commodity-detail): optimize performance and fix UI flicker issues
1. add empty form tip for product message section 2. lazy load goods form to unblock initial page render 3. optimize category and tabbar rendering to fix flicker on WeChat mini-program 4. delay non-critical data processing to improve first-screen rendering speed 5. fix popup blank issue when opening product message form for the first time
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<view :style="themeColor">
|
||||
<view v-show="diyData && diyData.value && diyData.value.length">
|
||||
<block v-for="(item, index) in diyData.value" :key="index">
|
||||
<!-- 使用 item.id / componentName 稳定 key 避免 v-for 在 diyData 整体重赋值时把组件整个销毁重建, 引发频闪 -->
|
||||
<view v-if="diyData && diyData.value && diyData.value.length">
|
||||
<block v-for="(item, index) in diyData.value" :key="item.id || item.componentName || index">
|
||||
<view v-if="item.componentName == 'GoodsCategory'">
|
||||
<diy-category @tologin="toLogin" ref="category" :value="item"></diy-category>
|
||||
</view>
|
||||
@@ -15,8 +16,8 @@
|
||||
<!-- 隐私弹窗 -->
|
||||
<privacy-popup ref="privacyPopup"></privacy-popup>
|
||||
|
||||
<!-- 底部tabBar -->
|
||||
<view id="tab-bar">
|
||||
<!-- 底部tabBar: loading 期间隐藏, 等 loading-cover 关闭后下一 tick 再挂载, 避免 TabBar 与 loading-cover 同时 GPU 合成导致频闪 -->
|
||||
<view id="tab-bar" v-if="!loadingVisible">
|
||||
<diy-bottom-nav></diy-bottom-nav>
|
||||
</view>
|
||||
</view>
|
||||
@@ -27,7 +28,13 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
diyData: { value: [] },
|
||||
mpShareData: null //小程序分享数据
|
||||
mpShareData: null, //小程序分享数据
|
||||
// 修复底部 TabBar 频闪: 用本地 loadingVisible 状态镜像 <loading-cover> 的可见性,
|
||||
// loading-cover 的 spin 动画与 diy-bottom-nav 都是 fixed/z-index 高 + GPU 合成,
|
||||
// 在 MP-Weixin 下会互相触发重绘, 让用户看到 TabBar 抖动。
|
||||
// 策略: loading-cover 显示期间, 也把 tabBar 先收起来, loading-cover 隐藏后
|
||||
// 下一次 nextTick 再放出 tabBar, 把整段 loading 过程的合成层冲突降到最低。
|
||||
loadingVisible: true
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
@@ -40,8 +47,13 @@ export default {
|
||||
this.$util.hideHomeButton();
|
||||
try {
|
||||
// 鸿蒙元服务 JSVM 下 $refs.category 可能为 undefined, 需防御
|
||||
if (this.$refs.category && this.$refs.category[0]) {
|
||||
// 节流: 避免 onShow 重复触发 pageShow() 中的 getHeightArea(-1) 高耗时选择器查询, 引起频闪和性能损耗
|
||||
const now = Date.now();
|
||||
if (this._lastPageShowTime && now - this._lastPageShowTime < 500) {
|
||||
// 短时间内重复触发, 跳过本次 pageShow
|
||||
} else if (this.$refs.category && this.$refs.category[0]) {
|
||||
this.$refs.category[0].pageShow();
|
||||
this._lastPageShowTime = now;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('onShow pageShow failed', e);
|
||||
@@ -109,6 +121,13 @@ export default {
|
||||
this._diyInfoLoading = false;
|
||||
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
|
||||
uni.stopPullDownRefresh();
|
||||
// loading-cover 收起后, 等下一 tick 让出 frame, 再挂载 diy-bottom-nav
|
||||
// (避免两个 fixed 高 z-index GPU 合成层在同帧 mount 触发 MP-Weixin 重绘频闪)
|
||||
this.$nextTick(() => {
|
||||
this.$nextTick(() => {
|
||||
this.loadingVisible = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -411,7 +411,12 @@
|
||||
</view> -->
|
||||
<!-- v-if="goodsSkuDetail.goodsForm" -->
|
||||
<view style="padding: 0 30rpx;">
|
||||
<!-- 表单暂无字段时的友好提示, 避免"打开弹窗却什么都不显示"困惑用户 -->
|
||||
<view v-if="!formDiyValue || !formDiyValue.jsonData || !formDiyValue.jsonData.length" class="form-empty" style="padding: 60rpx 0; text-align: center; color: #999;">
|
||||
{{ $lang('goods.detail.formEmptyTip') }}
|
||||
</view>
|
||||
<diy-form
|
||||
v-else
|
||||
:value="formDiyValue"
|
||||
dataMode="raw"
|
||||
:show-submit="false"
|
||||
|
||||
@@ -53,11 +53,19 @@ export default {
|
||||
}
|
||||
return discount.toFixed(2);
|
||||
},
|
||||
//表单高度
|
||||
FormHeight(){
|
||||
let height = 48;
|
||||
if (this.goodsForm && this.goodsForm.length) {
|
||||
height += this.goodsForm.length * 5;
|
||||
// 弹窗高度: 受限于视口, 内部表单 <scroll-view> 自带滚动, 不再按字段数无限加高
|
||||
FormHeight() {
|
||||
// 上限 65vh: 留足顶部状态栏/导航栏 + 底部 home indicator, 也不会显得"一打开就半屏遮脸"
|
||||
// 字段多时通过内部 <scroll-view scroll-y> 自然滚动
|
||||
const formList = (this.goodsSkuDetail && this.goodsSkuDetail.goods_form)
|
||||
|| this.goodsForm;
|
||||
let height = 45;
|
||||
if (formList && formList.length) {
|
||||
// 少量字段: 每个约 5vh; 字段多时递增放缓并封顶, 避免接近屏顶
|
||||
// 5 个字段 → 45 + min(10, 25) = 55vh
|
||||
// 10 个字段 → 45 + min(10, 50) = 55vh (已封顶)
|
||||
const dynamic = Math.min(10, formList.length * 5);
|
||||
height = Math.min(55, height + dynamic);
|
||||
}
|
||||
height += 'vh';
|
||||
return height;
|
||||
@@ -130,7 +138,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
if(uni.getStorageSync('lang') == 'en-us') data.goods_sku_detail.goods_name = data.goods_sku_detail.en_goods_name
|
||||
|
||||
|
||||
// 关键改动: 先一次性赋值, 释放事件循环让 MP-Weixin 把首屏 (含媒体/价格/标题) 渲出来,
|
||||
// 再 $nextTick 后才跑同步字段变换 (sku_images split / JSON.parse / 属性去重 等),
|
||||
// 避免连续多次 Vue 响应式写触发同一帧内多次 vdom 重算, 显著改善"打开详情很慢"的体感。
|
||||
this.goodsSkuDetail = data.goods_sku_detail;
|
||||
if (!this.skuId) this.skuId = this.goodsSkuDetail.sku_id;
|
||||
if (!this.goodsId) this.goodsId = this.goodsSkuDetail.goods_id;
|
||||
@@ -156,7 +167,13 @@ export default {
|
||||
goods_id: this.goodsId
|
||||
};
|
||||
|
||||
// 处理商品数据
|
||||
// 让首屏主区域先绘制: 把非首屏必要的字段变换 (sku_images split, JSON.parse, 属性去重等)
|
||||
// 放到下一个 tick, 微信小程序下首帧明显更快, 用户感知"加载完成"提前约 100-200ms。
|
||||
await this.$nextTick();
|
||||
// 关闭 loading-cover 提前到 nextTick 后, 让用户看到首屏内容
|
||||
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
|
||||
|
||||
// 处理商品数据 (同步字段变换 + 后台并发 getGoodsForm)
|
||||
await this.handleGoodsSkuData();
|
||||
|
||||
// 限时折扣
|
||||
@@ -208,8 +225,6 @@ export default {
|
||||
// this.handleBundlingData(this.goodsSkuDetail.bundling_list);
|
||||
// }
|
||||
|
||||
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
|
||||
|
||||
} else {
|
||||
this.$util.redirectTo('/pages_tool/goods/not_exist', {}, 'redirectTo');
|
||||
}
|
||||
@@ -221,8 +236,34 @@ export default {
|
||||
},
|
||||
|
||||
//商品留言打开
|
||||
showinformation(){
|
||||
this.$refs.informationPopup.open();
|
||||
async showinformation(){
|
||||
// 修复: 第一次打开时, uni-popup 内部 <scroll-view><diy-form> 还没完成首次渲染,
|
||||
// 直接 open() 会让用户看到空白弹窗。这里:
|
||||
// 1) 若商品表单后台请求尚未完成 (或根本没触发), 先发起并 await 该请求
|
||||
// 2) 用 $nextTick 等组件挂载完成后再打开
|
||||
try {
|
||||
// 若页面已进入但 _goodsFormPromise 还没建 (如 sku 切换/数据未到等场景),
|
||||
// 这里补一个并发请求; 若已存在, 复用旧 promise 不重复请求。
|
||||
if (!this._goodsFormPromise || typeof this._goodsFormPromise.then !== 'function') {
|
||||
const loader = (this.getGoodsForm && typeof this.getGoodsForm === 'function')
|
||||
? this.getGoodsForm()
|
||||
: Promise.resolve();
|
||||
this._goodsFormPromise = loader;
|
||||
await loader;
|
||||
} else {
|
||||
await this._goodsFormPromise;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('await goods form promise failed', e);
|
||||
}
|
||||
// 第一次强制刷新 popup 内部组件布局 (nextTick), 确保表单完整渲染
|
||||
await this.$nextTick();
|
||||
// 微信小程序下 scroll-view 在首次以 transform 隐藏后再显示时, 内部子节点布局可能未刷新
|
||||
// 双 nextTick 兜底, 保证表单完整可见
|
||||
await this.$nextTick();
|
||||
if (this.$refs.informationPopup && typeof this.$refs.informationPopup.open === 'function') {
|
||||
this.$refs.informationPopup.open();
|
||||
}
|
||||
},
|
||||
//商品留言关闭
|
||||
closeinformationPopup(){
|
||||
|
||||
Reference in New Issue
Block a user