diff --git a/common/js/goods_detail_base.js b/common/js/goods_detail_base.js
index a29ee79..fafe255 100644
--- a/common/js/goods_detail_base.js
+++ b/common/js/goods_detail_base.js
@@ -185,9 +185,12 @@ export default {
if (this.goodsRoute != '/pages_goods/detail') this.setPublicShare();
// this.getBarrageData();
- if (this.addonIsExist.form) {
- await this.getGoodsForm();
- }
+ // 性能优化: 商品表单(留言表单)仅在商品详情打开"立即留言"弹窗时才需要,
+ // 不再阻塞首次渲染。改为在后台并发请求, 用户真正打开弹窗时数据已就绪。
+ // 注意: 不再依赖 addonIsExist.form (后端可能未开启 form 插件, 但产品被设为"留言商品"(isinformation=2)
+ // 仍需展示留言表单), 始终尝试加载, getGoodsForm 内部已对失败分支兜底空数组。
+ this._goodsFormPromise = this.getGoodsForm();
+ // 不 await, 释放主流程; 调用方需要时可 await this._goodsFormPromise
},
/**
* 刷新商品详情数据
@@ -327,14 +330,35 @@ export default {
* 给 detail.vue 的 diy-form 直接消费。所有分支都做兜底初始化,避免下游拿到 undefined。
*/
async getGoodsForm() {
- const res = await this.$api.sendRequest({
- url: "/form/api/form/goodsform",
- data: {
- goods_id: this.goodsSkuDetail.goods_id,
- format: 'object'
- },
- async: false
- });
+ // 防御: sku 切换/数据未到时 goods_id 可能为 0/空, 此时直接走空数据分支,
+ // 避免后端拿到非法 goods_id 导致 4xx/5xx 后还把脏数据塞给前端。
+ if (!this.goodsSkuDetail || !this.goodsSkuDetail.goods_id) {
+ const emptyFn = () => {
+ this.$set(this.goodsSkuDetail, 'goods_form', []);
+ this.$set(this.goodsSkuDetail, 'goods_form_config', {});
+ this.$set(this.goodsSkuDetail, 'goods_form_name', '');
+ this.$set(this.goodsSkuDetail, 'goods_form_tip', '');
+ this.$set(this.goodsSkuDetail, 'goods_form_banner', '');
+ this.$set(this.goodsSkuDetail, 'goods_form_show_banner', false);
+ this.$set(this.goodsSkuDetail, 'goods_form_show_tip', false);
+ };
+ emptyFn();
+ return;
+ }
+ let res;
+ try {
+ res = await this.$api.sendRequest({
+ url: "/form/api/form/goodsform",
+ data: {
+ goods_id: this.goodsSkuDetail.goods_id,
+ format: 'object'
+ },
+ async: false
+ });
+ } catch (e) {
+ console.warn('getGoodsForm request failed', e);
+ res = null;
+ }
const empty = () => {
this.$set(this.goodsSkuDetail, 'goods_form', []);
this.$set(this.goodsSkuDetail, 'goods_form_config', {});
diff --git a/components-diy/diy-bottom-nav.vue b/components-diy/diy-bottom-nav.vue
index d6f04ce..638ee0b 100644
--- a/components-diy/diy-bottom-nav.vue
+++ b/components-diy/diy-bottom-nav.vue
@@ -1,30 +1,35 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ cartNumber > 99 ? '99+' : cartNumber }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ cartNumber > 99 ? '99+' : cartNumber }}
+
-
@@ -38,7 +43,7 @@
-
+
diff --git a/components-diy/diy-category.vue b/components-diy/diy-category.vue
index a7186e6..245efeb 100644
--- a/components-diy/diy-category.vue
+++ b/components-diy/diy-category.vue
@@ -323,6 +323,13 @@ export default {
* 加防御性检查,避免上线后第一次进入崩溃
*/
getHeightArea(index) {
+ // 节流: 500ms 内的重复请求复用上次结果, 避免每次 onShow 都跑 selectAll 触发频闪
+ const now = Date.now();
+ if (this._lastHeightAreaTime && now - this._lastHeightAreaTime < 500 && this.heightArea && this.heightArea.length) {
+ if (index != -1 && index < this.categoryTree.length - 1 && Array.isArray(this.$refs.categoryItem) && this.$refs.categoryItem[index + 1]) this.$refs.categoryItem[index + 1].getGoodsList();
+ return;
+ }
+ this._lastHeightAreaTime = now;
let heightArea = [];
if (!query || typeof query.selectAll !== 'function') {
query = uni.createSelectorQuery && uni.createSelectorQuery().in(this);
diff --git a/components/loading-cover/loading-cover.vue b/components/loading-cover/loading-cover.vue
index e1072b6..9804158 100644
--- a/components/loading-cover/loading-cover.vue
+++ b/components/loading-cover/loading-cover.vue
@@ -42,82 +42,90 @@ export default {
};
-
diff --git a/lang/en-us/goods/detail.js b/lang/en-us/goods/detail.js
index d9967fe..8b30d7e 100644
--- a/lang/en-us/goods/detail.js
+++ b/lang/en-us/goods/detail.js
@@ -115,6 +115,7 @@ export const lang = {
// Product Message
productMessage: 'Product Message',
+ formEmptyTip: 'No message fields to fill',
// AI Customer Service
aiCustomerService: 'AI Assistant',
diff --git a/lang/zh-cn/goods/detail.js b/lang/zh-cn/goods/detail.js
index 606d3c4..2b9b0ff 100644
--- a/lang/zh-cn/goods/detail.js
+++ b/lang/zh-cn/goods/detail.js
@@ -108,6 +108,7 @@ export const lang = {
// 商品留言
productMessage: '商品留言',
+ formEmptyTip: '暂无需填写的留言字段',
// 智能客服
aiCustomerService: '智能客服',
diff --git a/pages_goods/category.vue b/pages_goods/category.vue
index dd813d7..1985b43 100644
--- a/pages_goods/category.vue
+++ b/pages_goods/category.vue
@@ -1,7 +1,8 @@
-
-
+
+
+
@@ -15,8 +16,8 @@
-
-
+
+
@@ -27,7 +28,13 @@ export default {
data() {
return {
diyData: { value: [] },
- mpShareData: null //小程序分享数据
+ mpShareData: null, //小程序分享数据
+ // 修复底部 TabBar 频闪: 用本地 loadingVisible 状态镜像 的可见性,
+ // 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;
+ });
+ });
}
});
},
diff --git a/pages_goods/detail.vue b/pages_goods/detail.vue
index 7cf20c8..b7187c4 100644
--- a/pages_goods/detail.vue
+++ b/pages_goods/detail.vue
@@ -411,7 +411,12 @@
-->
+
+
+ {{ $lang('goods.detail.formEmptyTip') }}
+
自带滚动, 不再按字段数无限加高
+ FormHeight() {
+ // 上限 65vh: 留足顶部状态栏/导航栏 + 底部 home indicator, 也不会显得"一打开就半屏遮脸"
+ // 字段多时通过内部 自然滚动
+ 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 内部 还没完成首次渲染,
+ // 直接 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(){