Files
lucky_shop/pages_goods/public/js/detail.js
ZF sun 73776841d8 fix: 修复鸿蒙元服务商品详情页空白及接口兼容问题
1. 移除view组件无效的scroll-y属性,避免鸿蒙JSVM静默不渲染导致页面空白
2. 删除冗余的商品分类页面路由配置
3. 为ref调用和接口请求添加try-catch与空值防御,避免极端场景下崩溃
4. 补充鸿蒙元服务接口调用的兼容性处理,确保请求正常发起
5. 调整loading隐藏时机和异常捕获逻辑,优化页面容错能力
2026-07-20 08:44:15 +08:00

611 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export default {
data() {
return {
goodsRoute: '/pages_goods/detail',
couponList: [], //优惠券列表
couponBtnSwitch: false, //获取优惠券防止重复提交
posterApi: '/api/goods/poster',
//满减活动
manjian: {
type: 0,
manjian_name: "",
rule_json: null
},
//组合套餐
bundlingType: false,
bundling: [{
bundling_goods: {
bl_name: '',
sku_image: ''
}
}],
membercard: null, // 会员卡信息
hackReset: true,
cardOff: false
// 留言表单数据不再在 data() 维护:detail.vue 的 computed formDiyValue
// 直接从 goodsSkuDetail.goods_form / goods_form_config / goods_form_* 实时读取并打包成
// diy-form raw 模式期望的 value 结构。
}
},
computed: {
showDiscount() {
var flag = false;
if (
this.preview == 0 &&
this.addonIsExist.discount &&
this.goodsSkuDetail.promotion_type == 1 &&
this.goodsSkuDetail.discountTimeMachine &&
(!this.goodsSkuDetail.member_price || (this.goodsSkuDetail.member_price > 0 && Number(this.goodsSkuDetail.member_price) > Number(this.goodsSkuDetail.discount_price))
)
) {
flag = true;
}
return flag;
},
memberCardDiscount() {
let discount = 0,
showPrice = this.goodsSkuDetail.member_price > 0 && Number(this.goodsSkuDetail.member_price) < Number(this.goodsSkuDetail.discount_price) ? this.goodsSkuDetail.member_price : this.goodsSkuDetail.discount_price;
if (this.membercard && this.membercard.member_price > 0 && (parseFloat(showPrice) > parseFloat(this.membercard.member_price))) {
discount = parseFloat(showPrice) - parseFloat(this.membercard.member_price);
}
return discount.toFixed(2);
},
// 弹窗高度: 受限于视口, 内部表单 <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;
}
},
onLoad(data) {
// #ifdef MP-ALIPAY
let options = my.getLaunchOptionsSync();
options.query && Object.assign(data, options.query)
// #endif
this.skuId = data.sku_id || 0;
this.goodsId = data.goods_id || 0;
// 小程序扫码进入
if (data.scene) {
var sceneParams = decodeURIComponent(data.scene);
sceneParams = sceneParams.split('&');
if (sceneParams.length) {
sceneParams.forEach(item => {
if (item.indexOf('goods_id') != -1) this.goodsId = item.split('-')[1];
});
}
}
// #ifdef MP-WEIXIN
this.getShareImg();
// #endif
},
async onShow() {
// 同步获取商品详情
// 鸿蒙元服务兼容: 包 try/catch, 防止内部任何抛错导致整个 page 显示空白
try {
await this.getGoodsSkuDetail();
} catch (e) {
console.error('getGoodsSkuDetail failed:', e);
// 即使失败也要保证 loading-cover 关闭, 避免空白屏
setTimeout(() => {
try {
const ref = this.$refs && this.$refs.loadingCover;
if (ref && typeof ref.hide === 'function') ref.hide();
} catch (e2) { /* swallow */ }
}, 0);
}
},
onHide() {
this.couponBtnSwitch = false;
},
methods: {
//拨打电话
phoneClick(mobile){
uni.makePhoneCall({
phoneNumber: mobile+'',
success(){},
fail(){}
})
},
setSkuId(val) {
if (val) {
this.skuId = val;
this.getBundling();
}
},
// 获取商品详情
async getGoodsSkuDetail() {
let res;
try {
// 修复鸿蒙元服务: 必须传 async: false !
// 当 App.vue 的 boot (lang/theme/remote-config) 未完成时,
// sendRequest 在 async !== false 模式下会直接 return undefined (不返回 promise),
// 导致 res.code undefined / res.data 抛 TypeError, 整个 onShow 失败.
res = await this.$api.sendRequest({
url: '/api/goodssku/detail',
async: false,
data: {
sku_id: this.skuId,
goods_id: this.goodsId,
}
});
// 防御: 即便 async:false 仍可能在极端情况下返回 undefined
if (!res || typeof res !== 'object') {
throw new Error('sendRequest returned non-object response (likely boot not complete)');
}
} catch (e) {
console.error('getGoodsSkuDetail sendRequest failed:', e);
throw e;
}
let data = res.data;
if (data.goods_sku_detail != null) {
if (data.goods_sku_detail.promotion_type == 'presale' && data.goods_sku_detail.presale_id) {
this.$util.redirectTo('/pages_promotion/presale/detail', {
id: data.goods_sku_detail.presale_id,
sku_id: this.skuId
}, 'reLaunch');
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;
// 分享参数、链接
this.shareQuery = 'goods_id=' + this.goodsSkuDetail.goods_id;
this.shareUrl = this.goodsRoute + '?' + this.shareQuery;
// 在线客服聊天参数
this.chatRoomParams = {
sku_id: this.goodsSkuDetail.sku_id
};
let typeId = this.goodsSkuDetail.goods_promotion[0];
if (typeId) {
// 限时折扣
if (typeId.discount_id) {
this.chatRoomParams.type = 'discount'
this.chatRoomParams.type_id = typeId.discount_id
}
}
// 海报参数
this.posterParams = {
goods_id: this.goodsId
};
// 让首屏主区域先绘制
// 兼容鸿蒙元服务: $nextTick 在某些 JSVM 实现下不可靠, 用 setTimeout 兜底
// #ifdef MP-HARMONY
await new Promise(resolve => setTimeout(resolve, 16));
// #endif
// #ifndef MP-HARMONY
await this.$nextTick();
// #endif
// 关闭 loading-cover 提前到 nextTick 后, 让用户看到首屏内容
// 用 setTimeout 0 推迟到下一个 task, 避开当前 sync 阶段 vendor 报错导致 hide 失败
setTimeout(() => {
try {
const ref = this.$refs && this.$refs.loadingCover;
if (ref && typeof ref.hide === 'function') ref.hide();
} catch (e) { /* swallow */ }
}, 0);
// 处理商品数据 (同步字段变换 + 后台并发 getGoodsForm)
await this.handleGoodsSkuData();
// 限时折扣
if (this.goodsSkuDetail.promotion_type == 1 && this.addonIsExist.discount) {
//检测倒计时
if ((this.goodsSkuDetail.end_time - res.timestamp) > 0) {
this.goodsSkuDetail.discountTimeMachine = this.$util.countDown(this.goodsSkuDetail.end_time - res.timestamp);
} else {
this.goodsSkuDetail.promotion_type = 0;
}
}
if (this.goodsSkuDetail.promotion_type == 1 && this.goodsSkuDetail.discountTimeMachine) {
if (this.goodsSkuDetail.member_price > 0 && Number(this.goodsSkuDetail.member_price) <= Number(this.goodsSkuDetail.discount_price)) {
this.goodsSkuDetail.show_price = this.goodsSkuDetail.member_price;
} else {
this.goodsSkuDetail.show_price = this.goodsSkuDetail.discount_price;
}
} else {
if (this.goodsSkuDetail.member_price > 0) {
this.goodsSkuDetail.show_price = this.goodsSkuDetail.member_price;
} else {
this.goodsSkuDetail.show_price = this.goodsSkuDetail.price;
}
}
// 满减
if (this.goodsSkuDetail.manjian) {
this.getManjian(this.goodsSkuDetail.manjian);
}
// 会员卡
if (this.goodsSkuDetail.membercard) {
this.membercard = this.goodsSkuDetail.membercard;
}
// 优惠券
if (this.goodsSkuDetail.coupon_list) {
this.couponList = this.goodsSkuDetail.coupon_list;
this.couponList.forEach(v => {
if (v.count == v.lead_count) v.useState = 2;
else if (v.max_fetch != 0 && v.member_coupon_num && v.member_coupon_num >= v.max_fetch) v.useState = 1;
else v.useState = 0;
});
this.couponList = this.couponList.sort(this.sortBy('useState'))
}
// 组合套餐
// if (this.goodsSkuDetail.bundling_list) {
// this.handleBundlingData(this.goodsSkuDetail.bundling_list);
// }
} else {
this.$util.redirectTo('/pages_tool/goods/not_exist', {}, 'redirectTo');
}
},
choiceSku() {
this.$refs.goodsSku.show("buy_now", () => {
this.$store.dispatch('getCartNumber');
});
},
//商品留言打开
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), 确保表单完整渲染
// 兼容鸿蒙元服务: $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();
}
},
//商品留言关闭
closeinformationPopup(){
this.$refs.informationPopup.close();
},
//保存商品留言
saveubfirnation(){
// ref 指向 <diy-form>:diy-form 只暴露 getFormData() 方法,无 formData 属性
if (!this.$refs.form.verify()) return;
// uni.setStorageSync('goodFormData', {
// goods_id: this.goodsSkuDetail.goods_id,
// form_data: this.$refs.form.getFormData()
// });
this.$api.sendRequest({
url: '/api/goods/information',
data: {
goods_id: this.goodsSkuDetail.goods_id,
form_data: JSON.stringify(this.$refs.form.getFormData())
},
success: res => {
var data = res.data;
if (res.code == 0) {
}else{
this.$util.showToast({
title: res.msg
});
}
},
});
},
// 加入购物车
joinCart() {
if (!this.storeToken && this.preview == 0) {
this.redirectTologin(this.shareUrl);
return;
}
if (this.goodsSkuDetail.is_virtual == 1) {
this.$refs.goodsSku.show("buy_now", () => {
this.$store.dispatch('getCartNumber');
});
} else {
this.$refs.goodsSku.show("join_cart", () => {
this.$store.dispatch('getCartNumber');
});
}
},
// 立即购买
buyNow() {
if (!this.storeToken && this.preview == 0) {
this.redirectTologin(this.shareUrl);
return;
}
this.$refs.goodsSku.show("buy_now", () => {
});
},
sortBy(field) {
//根据传过来的字段进行排序,y-x 得分从高到低,x-y 从低到高
return (y, x) => {
return y[field] - x[field]
}
},
// 打开优惠券弹出层
openCouponPopup() {
this.$refs.couponPopup.open();
},
// 关闭优惠券弹出层
closeCouponPopup() {
this.$refs.couponPopup.close();
},
// 领取优惠券
receiveCoupon(item) {
let that = this;
if (this.preview) return; // 开启预览,禁止任何操作和跳转
if (this.couponBtnSwitch) return;
this.couponBtnSwitch = true;
if (this.storeToken) {
this.$api.sendRequest({
url: '/coupon/api/coupon/receive',
data: {
coupon_type_id: item.coupon_type_id,
get_type: 2 //获取方式:1订单2.直接领取3.活动领取
},
success: res => {
var data = res.data;
let msg = '';
let list = this.couponList;
if (res.data.is_exist == 1 && res.code < 0) {
msg = this.$lang('goods.detail.couponAlreadyClaimed');
} else if (res.code == 0) {
msg = this.$lang('goods.detail.couponClaimSuccess');
} else {
msg = res.message;
}
if (res.data.is_exist == 1) {
for (let i = 0; i < list.length; i++) {
if (list[i].coupon_type_id == item.coupon_type_id) {
that.$set(that.couponList[i], 'useState', 1);
}
}
} else {
for (let i = 0; i < list.length; i++) {
if (list[i].coupon_type_id == item.coupon_type_id) {
that.$set(that.couponList[i], 'useState', 2);
}
}
}
this.$util.showToast({
title: msg
});
that.$forceUpdate()
this.hackReset = false;
this.$nextTick(() => {
this.hackReset = true;
})
this.couponBtnSwitch = false;
},
});
} else {
this.redirectTologin(this.shareUrl);
this.couponBtnSwitch = false;
}
},
//-------------------------------------满减-------------------------------------
//获取满减信息
getManjian(data) {
this.manjian = data;
let limit = data.type == 0 ? this.$lang('goods.detail.manjianUnitYuan') : this.$lang('goods.detail.manjianUnitPiece');
Object.keys(data.rule_json).forEach((key) => {
var item = data.rule_json[key];
if (item.coupon_data) {
for (var i = 0; i < item.coupon_data.length; i++) {
item.coupon_data[i].coupon_num = item.coupon_num[i]
}
}
item.limit = data.type == 0 ? parseFloat(item.limit).toFixed(2) : parseInt(item.limit);
// 满减
if (item.discount_money != undefined) {
let itemText = this.$lang('goods.detail.fullReductionItem', { limit: item.limit, unit: limit, money: item.discount_money });
if (this.manjian.manjian == undefined) {
this.manjian.manjian = itemText;
} else {
this.manjian.manjian += this.$lang('goods.detail.manjianItemSeparator') + itemText;
}
}
// 满送
if (item.point != undefined || item.coupon != undefined) {
let text = '';
if (item.point != undefined) {
text = this.$lang('goods.detail.fullGiftPoints', { point: item.point });
}
if (item.coupon != undefined && item.coupon_data != undefined) {
item.coupon_data.forEach((couponItem, couponIndex) => {
if (couponItem.type == 'discount') {
let couponText = this.$lang('goods.detail.fullGiftDiscountCoupon', { count: item.coupon_num[couponIndex], discount: parseFloat(couponItem.discount) });
if (text == '') text = couponText;
else text += this.$lang('goods.detail.fullGiftConnector') + couponText;
} else {
let couponText = this.$lang('goods.detail.fullGiftMoneyCoupon', { count: item.coupon_num[couponIndex], money: parseFloat(couponItem.money) });
if (text == '') text = couponText;
else text += this.$lang('goods.detail.fullGiftConnector') + couponText;
}
})
}
let giftItemText = this.$lang('goods.detail.fullGiftItem', { limit: item.limit, unit: limit, gift: text });
if (this.manjian.mansong == undefined) {
this.manjian.mansong = giftItemText;
} else {
this.manjian.mansong += this.$lang('goods.detail.manjianItemSeparator') + giftItemText;
}
}
// 包邮
if (item.free_shipping != undefined) {
let itemText = this.$lang('goods.detail.freeShippingItem', { limit: item.limit, unit: limit });
if (this.manjian.free_shipping == undefined) {
this.manjian.free_shipping = itemText;
} else {
this.manjian.free_shipping += this.$lang('goods.detail.manjianItemSeparator') + itemText;
}
}
});
},
openManjianPopup() {
this.$refs.manjianPopup.open();
},
closeManjianPopup() {
this.$refs.manjianPopup.close();
},
//-------------------------------------组合套餐-------------------------------------
//获取当前商品关联的组合套餐
getBundling() {
// 连锁门店没有营销活动
if (this.globalStoreConfig && this.globalStoreConfig.store_business == 'store') return;
this.$api.sendRequest({
url: "/bundling/api/bundling/lists",
data: {
sku_id: this.skuId
},
success: res => {
// this.handleBundlingData(res.data);
}
});
},
handleBundlingData(data) {
this.bundling = data;
if (this.bundling.length) {
for (var i = 0; i < this.bundling[0].bundling_goods.length; i++) {
if (this.bundling[0].bundling_goods[i].sku_id == this.skuId) {
this.bundlingType = true;
break;
} else {
this.bundlingType = false;
}
}
for (var i = 0; i < this.bundling.length; i++) {
for (var j = 0; j < this.bundling[i].bundling_goods.length; j++) {
if (this.bundling[i].bundling_goods[j].sku_id == this.skuId) {
this.bundling[i].bundling_goods.splice(j, 1);
}
}
}
}
},
// 打开组合套餐弹出层
openBundlingPopup() {
this.$refs.bundlingPopup.open();
},
// 关闭组合套餐弹出层
closeBundlingPopup() {
this.$refs.bundlingPopup.close();
},
imageError() {
this.goodsSkuDetail.sku_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
bundlingImageError(index, goods_index) {
this.bundling[index].bundling_goods[goods_index].sku_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
fenxiao() {
this.$refs.fenxiaoPopup.show()
},
toGoodsDetail(item) {
this.$util.redirectTo(this.goodsRoute, {
sku_id: item
});
},
toComoDetail(id) {
this.$util.redirectTo('/pages_promotion/bundling/detail', {
bl_id: id
});
},
/**
* 获取分享图
*/
getShareImg() {
let posterParams = {
goods_id: this.goodsId
};
this.$api.sendRequest({
url: '/api/goods/shareimg',
data: {
qrcode_param: JSON.stringify(posterParams)
},
success: res => {
if (res.code == 0) this.shareImg = res.data.path + '?no=' + parseInt((new Date()).getTime() / 1000);
}
})
},
/**
* 金额格式化输出
* @param {Object} money
*/
decimalPointFormat(money) {
if (isNaN(parseFloat(money))) return money;
let arr = money.toString().split(".");
let arr1 = arr[1].split("").reverse();
let arr2 = [];
arr1.forEach((item, index) => {
if (item > 1) {
arr2 = arr1.splice(index);
}
});
let str = arr2.length ? (arr[0] + "." + arr2.reverse().join("")) : arr[0];
return str;
}
}
}