389 lines
13 KiB
Vue
389 lines
13 KiB
Vue
<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;">
|
||
<img style="width:31.73vw;position: absolute;top: -16vw;z-index: 1;left: 50%;transform: translateX(-50%);"
|
||
src="/img/no.png" alt="">
|
||
<div
|
||
style="font-size: 3.73vw;color: #333333;padding:18vw 3.33vw 3.33vw;display: flex;flex-direction: column;align-items: center;justify-content:center;border-bottom: 1px solid #f5f5f5;">
|
||
<span style="">
|
||
不支持发货区域:
|
||
<span style="font-size: 3.73vw;;color: #ea3e23;">
|
||
{{ formattedForbidArea }}
|
||
</span>
|
||
</span>
|
||
|
||
</div>
|
||
<button @click="showArea = false"
|
||
style="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.map(s => s.split('-')[1] || s).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', { title: this.data.name, imageUrl: this.data.img, link: this.shareLink });
|
||
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> |