ch-tgr-h5/src/views/Goods/GoodsDetail.vue
2026-05-29 10:27:56 +08:00

388 lines
13 KiB
Vue
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.

<template>
<BasePage :title="data.name || '产品详情'">
<div class="goodsdetails">
<div class="t">
<img :src="$file(data.img)" alt="">
</div>
<div class="container">
<div class="goodsshow">
<div class="t">
<div class="tit">
{{ data.name }}
</div>
<div class="desc" v-if="data.description">
{{ data.description }}
</div>
</div>
<div class="price_box">
<div class="price" v-if="data.mallstate !== 5">
¥{{ data.saleprice?.toFixed(2) }}
</div>
<div class="oprice" v-if="data.mallstate !== 5">
¥{{ data.originalprice?.toFixed(2) }}
</div>
<div class="allpoint" v-if="data.mallstate === 5">
<b>{{ data.saleprice?.toFixed(2) || '0.00' }}</b>积分
</div>
<div class="sales r">
已售{{ data.salenums }}
</div>
</div>
<div class="deduct_box">
<div class="point" v-if="data.deductjifen > 0">
<img src="/img/de-i1.png" alt="">
积分抵{{ data.deductjifen?.toFixed(2) || '0.00' }}
</div>
<div class="vip" v-if="data.deducthuiyuanka > 0">
<img src="/img/de-i2.png" alt="">
会员卡额度抵{{ data.deducthuiyuanka?.toFixed(2) || '0.00' }}
</div>
</div>
<hr>
<div class="gt">
<div>
<img src="/img/gt-i1.png" alt="">
正品保障
</div>
<div>
<img src="/img/gt-i1.png" alt="">
快速发货
</div>
<div>
<img src="/img/gt-i1.png" alt="">
售后无忧
</div>
</div>
</div>
<div class="spec_box" @click="showArea = true">
<img style="height: 3.47vw;" src="/img/nocart.png" alt="">
<span>不支持发货区域</span>
<span
style="color: #999999;max-width: 43vw;width: 43vw;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
{{ formattedForbidArea }}
</span>
<van-icon class="r" name="arrow"></van-icon>
</div>
<div class="spec_box" @click="showSpecs = true">
<img src="/img/spec.png" alt="">
<span class="overhide">{{ selectedSku }}</span>
<van-icon class="r" name="arrow"></van-icon>
</div>
<div class="prodetail">
<div class="tit">
<img src="/img/pro.png" alt="">
<span>产品详情</span>
</div>
<div class="vhtml" v-html="data.contents"></div>
</div>
</div>
<van-action-bar safe-area-inset-bottom placeholder>
<van-action-bar-icon icon="/img/vb-home.png" text="首页" @click="$navigate('Home')" />
<van-action-bar-icon icon="/img/vb-service.png" text="客服" @click="$goService()" />
<van-action-bar-icon icon="/img/vb-share.png" text="分享" @click="beforeShare" />
<van-action-bar-button color="#ca2904" type="danger" text="立即购买" @click="showSpecs = true" />
</van-action-bar>
<van-share-sheet v-model:show="showShare" title="立即分享给好友" :options="options" @select="onSelect" />
<van-popup v-model:show="showSpecs" position="bottom" closeable round>
<div class="showspec">
<div class="goods">
<img :src="$file(tempSku.img || data.img)" alt="">
<div>
<div class="s_price">¥{{ (tempSku.saleprice * tempSku.Qty).toFixed(2) }}</div>
<div class="s_stock">剩余{{ tempSku.saleleft }}</div>
<div class="s_spec">已选{{ tempSku.skuname }}</div>
</div>
</div>
<div class="spec_box" v-for="spec in specSelect">
<span class="title">
{{ spec.name }}
</span>
<div class="speccc">
<div v-for="child in spec.children" class="spec_de"
:class="selectedSpecs[spec.name] === child.name ? 'active' : 'inactive'"
@click="selectSpec(spec.name, child.name)">
{{ child.name }}
</div>
</div>
</div>
<div class="buynums box">
<span class="title">
购买数量
</span>
<van-stepper class="r" integer v-model="tempSku.Qty" min="1" max="99" theme="round" button-size="22"
disable-input />
</div>
<van-action-bar safe-area-inset-bottom placeholder>
<van-action-bar-button color="#ca2904" type="danger" text="立即购买" @click="submit" />
</van-action-bar>
</div>
</van-popup>
<van-popup v-model:show="show" class="shareimgbox" style="max-width: 80vw;">
<div class="share_box" id="bsCard">
<div class="share_userinfo">
<img :src="$file($userInfo.userimg)" alt="">
<div class="name"><span>{{ $userInfo.nickname }}友情推荐</span></div>
</div>
<div class="goods_info">
<img class="goods_img" :src="$file(data.img)" alt="">
<div class="goods_name">{{ data.name }}</div>
</div>
<div class="price">
<div class="allpointp" v-if="data.mallstate === 5">
<span>{{ data.saleprice?.toFixed(2) || '0.00' }}</span>积分
</div>
<div class="goods_price" v-else>¥{{ data.saleprice?.toFixed(2) || '0.00' }}</div>
<div class="o_price" v-if="data.mallstate !== 5">¥{{ data.originalprice?.toFixed(2) || '0.00' }}</div>
</div>
<div class="bottom_box">
<div class="qrcode">
<vue-qr :margin="0" :text="shareLink" backgroundColor="#ffffff00" colorLight="rgb(255,255,255)"></vue-qr>
长按扫码购买
</div>
<img class="logo" src="/img/logo.png" alt="">
</div>
</div>
<div class="share_result" v-if="ShareImg">
<img :src="ShareImg" alt="分享海报">
</div>
</van-popup>
<van-popup v-model:show="showArea" safe-area-inset-bottom
style=" width: 86.67vw; background-color: #fff7f7; border-radius: 2.67vw;overflow: visible;" class="area_popup"
@click.self="showArea = false">
<div class="area_box" style="position: relative;height: 80vw;">
<img style="width:31.73vw;position: absolute;top: -16vw;z-index: 1;left: 50%;transform: translateX(-50%);"
src="/img/no.png" alt="">
<div
style="height: 55vw;top:12vw;z-index: 2;position: absolute;overflow-y: scroll;font-size: 3.73vw;color: #333333;padding: 0 4vw 3.33vw;border-bottom: 1px solid #f5f5f5;">
<span style="">
不支持发货区域
<span style="font-size: 3.73vw;;color: #ea3e23;">
{{ formattedForbidArea }}
</span>
</span>
</div>
<button @click="showArea = false"
style="position: absolute;bottom: 0;left: 0;height: 16vw;width:100%;border:none;background-color: transparent; font-size: 3.73vw;font-weight: bold;color:#333;">
好的知道了
</button>
</div>
</van-popup>
</div>
</BasePage>
</template>
<script>
import { toDataURL } from '@/utils/html2image.js'
export default {
name: 'GoodsDetail',
emits: ['updateShare'],
mounted() {
this.init();
},
data() {
return {
data: {},
specSelect: [],
hide: true,
showArea: false,
show: false,
specCombinations: [],
tempSku: {},
showSpecs: false,
selectedSku: '请选择规格分类',
selectedSpecs: {},
showShare: false,
options: [
{ name: '分享海报', icon: '/img/share.png' },
],
shareLink: '',
ShareImg: '',
loading: false,
transparent: 'rgba(0,0,0,0)',
shareBoxHidden: false
}
},
computed: {
formattedForbidArea() {
if (!this.data.forbidbuyarea) return '暂无'
try {
const str = this.data.forbidbuyarea.replace(/\\"/g, '"')
const arr = JSON.parse(str)
return arr.join('、')
} catch {
return this.data.forbidbuyarea.replace(/\\"/g, '"')
}
}
},
methods: {
init() {
this.$get(`/v1/client/EProsClient/${this.$route.query.id}`).then(res => {
this.data = res.data;
this.specSelect = this.data.specSelect || [];
this.specCombinations = this.data.specCombinations || [];
const recommend = localStorage.getItem('cellphone') || '';
this.shareLink = `${location.origin}/#/GoodsDetail?id=${this.data.id}${recommend ? '&RecommendCode=' + recommend : ''}`;
this.$emit('updateShare', this.data.name, this.data.description, this.data.img);
this.initDefaultSku();
});
},
initDefaultSku() {
const mainSku = this.specCombinations.find(s => s.ismain) || this.specCombinations[0];
if (mainSku) {
this.tempSku = { ...mainSku, Qty: 1 };
this.selectedSku = "已选:" + mainSku.skuname;
// 根据skuname设置默认选中规格skuname用逗号分隔
const skuParts = mainSku.skuname.split(',').filter(Boolean);
this.specSelect.forEach((spec) => {
const specIndex = this.specSelect.findIndex(s => s.name === spec.name);
// 优先从skuname匹配匹配不到则默认选中第一个子选项
if (skuParts[specIndex]) {
const child = spec.children?.find(c => skuParts[specIndex].includes(c.name));
if (child) {
this.selectedSpecs[spec.name] = child.name;
} else if (spec.children?.length > 0) {
this.selectedSpecs[spec.name] = spec.children[0].name;
}
} else if (spec.children?.length > 0) {
// skuname没有对应分类时默认选中第一个
this.selectedSpecs[spec.name] = spec.children[0].name;
}
});
// 调用refreshSku确保tempSku包含正确的skuid等信息
this.refreshSku();
}
},
selectSpec(specName, specValue) {
this.selectedSpecs[specName] = specValue;
this.refreshSku();
},
refreshSku() {
const specKeys = Object.keys(this.selectedSpecs);
if (specKeys.length === 0) return;
let matchedSku = this.specCombinations.find(combo => {
const skuParts = combo.skuname.split(',').filter(Boolean);
return specKeys.every((key) => {
const selectedVal = this.selectedSpecs[key];
const specIndex = this.specSelect.findIndex(s => s.name === key);
return skuParts[specIndex]?.includes(selectedVal);
});
});
if (matchedSku) {
this.tempSku = { ...matchedSku, Qty: this.tempSku.Qty || 1 };
this.selectedSku = "已选:" + matchedSku.skuname;
}
},
back() {
window.history.back();
},
onSelect(option) {
this.showShare = false;
if (option.name === "分享海报") {
this.show = true;
// 如果图片已生成且元素已隐藏,直接显示,不重复生成
if (this.ShareImg && this.shareBoxHidden) {
this.loading = false;
return;
}
// 如果图片已生成但元素未隐藏,先隐藏元素再显示
if (this.ShareImg) {
this.hideShareBox();
this.loading = false;
return;
}
this.loading = true;
// 等待 DOM 渲染后再生成
setTimeout(() => {
this.generateShareImg();
}, 500);
} else if (option.name == "复制链接") {
this.$copyText(location.href);
this.$showToast({ type: "success", message: "复制成功" });
} else if (option.name == "分享好友") {
this.showTip = true;
}
},
submit() {
if (this.tempSku.saleleft < 1) {
this.$showFailToast("该规格库存不足,无法购买");
return;
}
this.$navigate(`/TradeConfirm/?id=${this.data.id}&skuname=${encodeURIComponent(this.tempSku.skuname)}&buynums=${this.tempSku.Qty}`);
},
beforeShare() {
this.showShare = true;
},
async generateShareImg() {
var shareContent = document.querySelector('#bsCard');
if (!shareContent) return;
try {
// Wait for all images inside shareContent to load
const images = shareContent.querySelectorAll('img');
for (const img of images) {
if (!img.complete) {
await new Promise((resolve) => {
img.onload = resolve;
img.onerror = resolve;
});
}
}
// Wait for vue-qr to render
await new Promise(resolve => setTimeout(resolve, 500));
this.ShareImg = await toDataURL(shareContent, {
pixelRatio: 2,
useCORS: true,
format: 'png'
});
this.hideShareBox();
this.loading = false;
} catch (e) {
console.error(e);
this.loading = false;
}
},
hideShareBox() {
const shareBox = document.querySelector('.share_box');
if (shareBox) {
shareBox.style.display = 'none';
this.shareBoxHidden = true;
}
}
}
}
</script>