export default { data() { return { goodsRoute: '/pages_goods/detail', couponList: [], //优惠券列表 couponBtnSwitch: false, //获取优惠券防止重复提交 // 商品详情骨架屏:true 显示骨架,false 显示真实内容 // 首屏进入即为 true,商品详情数据首屏渲染完成后(与 loading-cover 同时机)置 false loading: true, 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); }, // 弹窗高度: 受限于视口, 内部表单 自带滚动, 不再按字段数无限加高 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; } }, 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); // 失败时不关闭骨架屏, 由视图层 hasRealData(goods_id>0) 自动保留占位, // 避免错误地把页面切到"空详情"状态 uni.showToast({ title: this.$lang ? this.$lang('common.networkError') : '网络异常,请重试', icon: 'none' }); } }, 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 }; // 鸿蒙兼容: 后端在部分场景下可能不返回 goods_promotion, 缺字段直接 .[0] 会抛 TypeError, // 整个 onShow 走 catch 路径无法关闭骨架屏, 表现为"数据已回来但永远显示骨架屏" const promotionList = this.goodsSkuDetail?.goods_promotion || []; let typeId = promotionList.length > 0 ? promotionList[0] : undefined; 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)); // 修复鸿蒙元服务: 不依赖 setTimeout 关闭骨架屏. // 鸿蒙 JSVM 在 onShow 早期窗口的 setTimeout 回调有时被推迟/丢弃, 表现为"数据已回来但永远显示骨架屏". // 数据已赋值 + 首屏让步, 同步直接关闭骨架屏, 由 hasRealData 兜底避免空详情. this.loading = false; // #endif // #ifndef MP-HARMONY await this.$nextTick(); // 关闭商品详情骨架屏: 在真实数据首屏渲染完成后再隐藏骨架屏, // 用户会看到骨架屏 → 真实详情 的清晰过渡 // 最短展示 300ms: 数据请求很快时也保留足够感知时间, 否则一闪而过用户根本看不到骨架屏 setTimeout(() => { this.loading = false; }, 300); // #endif // 处理商品数据 (同步字段变换 + 后台并发 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 (Array.isArray(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 内部 还没完成首次渲染, // 直接 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 只暴露 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; } } }