fix: 兼容鸿蒙元服务端小程序适配优化

1.  修复cart页面unload时调用不存在方法的报错
2.  适配鸿蒙themeColor绑定和$nextTick兼容问题
3.  给多处逻辑添加try/catch防止页面空白
4.  补全v-for循环的key属性
This commit is contained in:
2026-07-18 17:54:05 +08:00
parent 63e2cb2ff7
commit 5b7351dcd2
6 changed files with 138 additions and 114 deletions

View File

@@ -80,117 +80,119 @@ export default {
methods: {
// 处理商品详情数据
async handleGoodsSkuData() {
// this.navbarData.title = this.goodsSkuDetail.goods_name.length > 9 ? this.goodsSkuDetail.goods_name.substr(0, 9) + "..." : this.goodsSkuDetail.goods_name;
//设置标题
// this.$langConfig.title(this.navbarData.title);
if (this.goodsSkuDetail.config) {
this.navbarData.navBarSwitch = this.goodsSkuDetail.config.nav_bar_switch;
}
this.whetherCollection = this.goodsSkuDetail.is_collect; // 用户关注商品状态
this.modifyGoodsInfo();
// 初始化商品详情视图数据
if (this.$refs.goodsDetailView) this.$refs.goodsDetailView.init({
sku_id: this.skuId,
goods_id: this.goodsSkuDetail.goods_id,
preview: this.preview,
source_member: this.source_member,
posterParams: this.posterParams,
posterApi: this.posterApi,
shareUrl: this.shareUrl,
goodsRoute: this.goodsRoute,
isVirtual: this.goodsSkuDetail.is_virtual,
deliveryType: this.goodsSkuDetail.express_type,
whetherCollection: this.goodsSkuDetail.is_collect,
evaluateConfig: this.goodsSkuDetail.evaluate_config,
evaluateList: this.goodsSkuDetail.evaluate_list,
evaluateCount: this.goodsSkuDetail.evaluate_count
});
//媒体
if (this.goodsSkuDetail.video_url) this.switchMedia = "video";
if (!Array.isArray(this.goodsSkuDetail.sku_images)) {
if (this.goodsSkuDetail.sku_images) this.goodsSkuDetail.sku_images = this.goodsSkuDetail.sku_images.split(",");
else this.goodsSkuDetail.sku_images = [];
}
// 多规格时合并主图
if (this.goodsSkuDetail.goods_spec_format && this.goodsSkuDetail.goods_image) {
if (!Array.isArray(this.goodsSkuDetail.goods_image)) this.goodsSkuDetail.goods_image = this.goodsSkuDetail.goods_image.split(",");
this.goodsSkuDetail.sku_images = this.goodsSkuDetail.goods_image.concat(this.goodsSkuDetail.sku_images);
}
let maxHeight = '';
let systemInfo = uni.getSystemInfoSync();
this.goodsSkuDetail.goods_image_list.forEach((item, index) => {
if (typeof item.pic_spec == "string")
item.pic_spec = item.pic_spec.split('*');
let ratio = item.pic_spec[0] / systemInfo.windowWidth;
item.pic_spec[0] = item.pic_spec[0] / ratio;
item.pic_spec[1] = item.pic_spec[1] / ratio;
if (!maxHeight || maxHeight > item.pic_spec[1]) {
maxHeight = item.pic_spec[1];
// 鸿蒙兼容: 外层包 try/catch, 任何同步抛错都不会导致 page 整体空白
try {
if (this.goodsSkuDetail.config) {
this.navbarData.navBarSwitch = this.goodsSkuDetail.config.nav_bar_switch;
}
});
this.goodsSkuDetail.swiperHeight = maxHeight + 'px';
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || this.$lang('unit.piece');
this.whetherCollection = this.goodsSkuDetail.is_collect; // 用户关注商品状态
// 当前商品SKU规格
if (this.goodsSkuDetail.sku_spec_format) this.goodsSkuDetail.sku_spec_format = JSON.parse(this.goodsSkuDetail.sku_spec_format);
this.modifyGoodsInfo();
// 商品属性
if (this.goodsSkuDetail.goods_attr_format) {
let goods_attr_format = JSON.parse(this.goodsSkuDetail.goods_attr_format);
this.goodsSkuDetail.goods_attr_format = this.$util.unique(goods_attr_format, "attr_id");
for (var i = 0; i < this.goodsSkuDetail.goods_attr_format.length; i++) {
for (var j = 0; j < goods_attr_format.length; j++) {
if (this.goodsSkuDetail.goods_attr_format[i].attr_id == goods_attr_format[j].attr_id && this.goodsSkuDetail.goods_attr_format[i].attr_value_id != goods_attr_format[j].attr_value_id) {
this.goodsSkuDetail.goods_attr_format[i].attr_value_name += "、" + goods_attr_format[j].attr_value_name;
// 初始化商品详情视图数据
if (this.$refs.goodsDetailView) this.$refs.goodsDetailView.init({
sku_id: this.skuId,
goods_id: this.goodsSkuDetail.goods_id,
preview: this.preview,
source_member: this.source_member,
posterParams: this.posterParams,
posterApi: this.posterApi,
shareUrl: this.shareUrl,
goodsRoute: this.goodsRoute,
isVirtual: this.goodsSkuDetail.is_virtual,
deliveryType: this.goodsSkuDetail.express_type,
whetherCollection: this.goodsSkuDetail.is_collect,
evaluateConfig: this.goodsSkuDetail.evaluate_config,
evaluateList: this.goodsSkuDetail.evaluate_list,
evaluateCount: this.goodsSkuDetail.evaluate_count
});
//媒体
if (this.goodsSkuDetail.video_url) this.switchMedia = "video";
if (!Array.isArray(this.goodsSkuDetail.sku_images)) {
if (this.goodsSkuDetail.sku_images) this.goodsSkuDetail.sku_images = this.goodsSkuDetail.sku_images.split(",");
else this.goodsSkuDetail.sku_images = [];
}
// 多规格时合并主图
if (this.goodsSkuDetail.goods_spec_format && this.goodsSkuDetail.goods_image) {
if (!Array.isArray(this.goodsSkuDetail.goods_image)) this.goodsSkuDetail.goods_image = this.goodsSkuDetail.goods_image.split(",");
this.goodsSkuDetail.sku_images = this.goodsSkuDetail.goods_image.concat(this.goodsSkuDetail.sku_images);
}
let maxHeight = '';
let systemInfo = uni.getSystemInfoSync();
this.goodsSkuDetail.goods_image_list.forEach((item, index) => {
if (typeof item.pic_spec == "string")
item.pic_spec = item.pic_spec.split('*');
let ratio = item.pic_spec[0] / systemInfo.windowWidth;
item.pic_spec[0] = item.pic_spec[0] / ratio;
item.pic_spec[1] = item.pic_spec[1] / ratio;
if (!maxHeight || maxHeight > item.pic_spec[1]) {
maxHeight = item.pic_spec[1];
}
});
this.goodsSkuDetail.swiperHeight = maxHeight + 'px';
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || this.$lang('unit.piece');
// 当前商品SKU规格
if (this.goodsSkuDetail.sku_spec_format) this.goodsSkuDetail.sku_spec_format = JSON.parse(this.goodsSkuDetail.sku_spec_format);
// 商品属性
if (this.goodsSkuDetail.goods_attr_format) {
let goods_attr_format = JSON.parse(this.goodsSkuDetail.goods_attr_format);
this.goodsSkuDetail.goods_attr_format = this.$util.unique(goods_attr_format, "attr_id");
for (var i = 0; i < this.goodsSkuDetail.goods_attr_format.length; i++) {
for (var j = 0; j < goods_attr_format.length; j++) {
if (this.goodsSkuDetail.goods_attr_format[i].attr_id == goods_attr_format[j].attr_id && this.goodsSkuDetail.goods_attr_format[i].attr_value_id != goods_attr_format[j].attr_value_id) {
this.goodsSkuDetail.goods_attr_format[i].attr_value_name += "、" + goods_attr_format[j].attr_value_name;
}
}
}
}
}
// 商品SKU格式
if (this.goodsSkuDetail.goods_spec_format) this.goodsSkuDetail.goods_spec_format = JSON.parse(this.goodsSkuDetail.goods_spec_format);
// 商品SKU格式
if (this.goodsSkuDetail.goods_spec_format) this.goodsSkuDetail.goods_spec_format = JSON.parse(this.goodsSkuDetail.goods_spec_format);
// 商品详情
if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = (this.goodsSkuDetail.goods_content);
// if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = htmlParser(this.goodsSkuDetail.goods_content);
// 商品详情
if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = (this.goodsSkuDetail.goods_content);
// if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = htmlParser(this.goodsSkuDetail.goods_content);
//商品服务
if (this.goodsSkuDetail.goods_service) {
for (let i in this.goodsSkuDetail.goods_service) {
this.goodsSkuDetail.goods_service[i]['icon'] = this.goodsSkuDetail.goods_service[i]['icon'] ? JSON.parse(this.goodsSkuDetail.goods_service[i]['icon']) : '';
//商品服务
if (this.goodsSkuDetail.goods_service) {
for (let i in this.goodsSkuDetail.goods_service) {
this.goodsSkuDetail.goods_service[i]['icon'] = this.goodsSkuDetail.goods_service[i]['icon'] ? JSON.parse(this.goodsSkuDetail.goods_service[i]['icon']) : '';
}
}
this.contactData = {
title: this.goodsSkuDetail.sku_name,
path: this.shareUrl,
img: this.$util.img(this.goodsSkuDetail.sku_image, {
size: 'big'
})
};
if (this.$refs.goodsPromotion) this.$refs.goodsPromotion.refresh(this.goodsSkuDetail.goods_promotion);
if (this.goodsRoute != '/pages_goods/detail') this.setPublicShare();
// this.getBarrageData();
// 性能优化: 商品表单(留言表单)仅在商品详情打开"立即留言"弹窗时才需要,
// 不再阻塞首次渲染。改为在后台并发请求, 用户真正打开弹窗时数据已就绪。
// 注意: 不再依赖 addonIsExist.form (后端可能未开启 form 插件, 但产品被设为"留言商品"(isinformation=2)
// 仍需展示留言表单), 始终尝试加载, getGoodsForm 内部已对失败分支兜底空数组。
this._goodsFormPromise = this.getGoodsForm();
// 不 await, 释放主流程; 调用方需要时可 await this._goodsFormPromise
} catch (e) {
console.error('handleGoodsSkuData failed:', e);
}
this.contactData = {
title: this.goodsSkuDetail.sku_name,
path: this.shareUrl,
img: this.$util.img(this.goodsSkuDetail.sku_image, {
size: 'big'
})
};
if (this.$refs.goodsPromotion) this.$refs.goodsPromotion.refresh(this.goodsSkuDetail.goods_promotion);
if (this.goodsRoute != '/pages_goods/detail') this.setPublicShare();
// this.getBarrageData();
// 性能优化: 商品表单(留言表单)仅在商品详情打开"立即留言"弹窗时才需要,
// 不再阻塞首次渲染。改为在后台并发请求, 用户真正打开弹窗时数据已就绪。
// 注意: 不再依赖 addonIsExist.form (后端可能未开启 form 插件, 但产品被设为"留言商品"(isinformation=2)
// 仍需展示留言表单), 始终尝试加载, getGoodsForm 内部已对失败分支兜底空数组。
this._goodsFormPromise = this.getGoodsForm();
// 不 await, 释放主流程; 调用方需要时可 await this._goodsFormPromise
},
/**
* 刷新商品详情数据

View File

@@ -170,8 +170,8 @@
</view>
</view> -->
<view class="cart-point" :style="{ left: item.left + 'px', top: item.top + 'px' }" :key="index"
v-for="(item, index) in carIconList"></view>
<view class="cart-point" v-for="(item, index) in carIconList" :key="index"
:style="{ left: item.left + 'px', top: item.top + 'px' }"></view>
<ns-goods-sku-category ref="skuSelect" @refresh="refreshData" @addCart="addCartPoint"></ns-goods-sku-category>
</view>

View File

@@ -1,6 +1,6 @@
<template>
<view class="form-wrap form-component" :style="formWrapStyle">
<block v-for="(item, index) in formData">
<block v-for="(item, index) in formData" :key="index">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Text'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">

View File

@@ -1,5 +1,7 @@
<template>
<view :style="themeColor">
<!-- 鸿蒙兼容: themeColor 是 CSS 变量字符串, 鸿蒙 JSVM 早期版本对 :style="字符串" 处理可能不稳.
兜底: 只有非空字符串才绑定 :style. -->
<view :style="themeColor || undefined">
<!-- #ifndef H5 -->
<!-- <view class="page-header" v-if="goodsSkuDetail && goodsSkuDetail.config && goodsSkuDetail.config.nav_bar_switch == 0">
<ns-navbar :data="navbarData" :isBack="!isTabRootPage"></ns-navbar>

View File

@@ -40,7 +40,9 @@ export default {
this.isAction = false;
},
onUnload() {
if (!this.storeToken && this.$refs.login) this.$refs.login.cancelCompleteInfo();
if (!this.storeToken && this.$refs.login && typeof this.$refs.login.cancelCompleteInfo === 'function') {
this.$refs.login.cancelCompleteInfo();
}
},
onReady() {
if (!this.storeToken) {

View File

@@ -97,8 +97,17 @@ export default {
// #endif
},
async onShow() {
//同步获取商品详情
await this.getGoodsSkuDetail();
// 同步获取商品详情
// 鸿蒙元服务兼容: 包 try/catch, 防止内部任何抛错导致整个 page 显示空白
try {
await this.getGoodsSkuDetail();
} catch (e) {
console.error('getGoodsSkuDetail failed on 鸿蒙:', e);
// 即使失败也要保证 loading-cover 关闭, 避免空白屏
if (this.$refs.loadingCover && typeof this.$refs.loadingCover.hide === 'function') {
this.$refs.loadingCover.hide();
}
}
},
onHide() {
this.couponBtnSwitch = false;
@@ -169,7 +178,15 @@ export default {
// 让首屏主区域先绘制: 把非首屏必要的字段变换 (sku_images split, JSON.parse, 属性去重等)
// 放到下一个 tick, 微信小程序下首帧明显更快, 用户感知"加载完成"提前约 100-200ms。
// 兼容鸿蒙元服务: $nextTick / Promise 在某些 JSVM 实现下不可靠, 用 setTimeout 兜底
// #ifdef MP-HARMONY
await new Promise(resolve => setTimeout(resolve, 16));
// #endif
// #ifndef MP-HARMONY
await this.$nextTick();
// #endif
// 关闭 loading-cover 提前到 nextTick 后, 让用户看到首屏内容
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
@@ -254,14 +271,15 @@ export default {
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') {
console.warn('await goods form promise failed', e);
}
// 第一次强制刷新 popup 内部组件布局 (nextTick), 确保表单完整渲染
// 兼容鸿蒙元服务: $nextTick 不可靠时降级到 setTimeout
try { await this.$nextTick(); } catch (e) { /* 兜底 */ }
// 微信小程序下 scroll-view 在首次以 transform 隐藏后再显示时, 内部子节点布局可能未刷新
// 双 nextTick 兜底, 保证表单完整可见
try { await this.$nextTick(); } catch (e) { /* 兜底 */ }
if (this.$refs.informationPopup && typeof this.$refs.informationPopup.open === 'function') {
this.$refs.informationPopup.open();
}
},