user-shop-h5/src/views/subDir/shopDetail.vue
2026-08-10 16:50:33 +08:00

1461 lines
45 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>
<div v-if="loading" class="ui-shopDetail">
<van-swipe :autoplay="3000" lazy-render>
<van-swipe-item v-for="image in banner" :key="image" class="ui-relative">
<img class="ui-mall-pic" :src="image" alt="" />
</van-swipe-item>
<template #indicator="{ active, total }">
<div class="custom-indicator font_26 colorF">{{ active + 1 }}/{{ total }}</div>
</template>
</van-swipe>
<div class="ui-shop-detail-container">
<div class="ui-shop-data-box">
<div class="f-fcl">
<span class="font_28 ui-price-symbol bold">¥</span>
<div class="font_44 ui-show-price bold"><span v-html="htmlPrice"></span></div>
<!-- <div class="font_28 ui-original-price color6">¥{{ originPrice }}</div>-->
</div>
<div class="font_32 color3 bold ui-pt-10">{{ title }}</div>
<!-- <div class="font_24 color9 ui-pt-20">通过无抗生素产品认证,个个可...</div>-->
</div>
<div class="ui-ExpressDelivery-data-box">
<div>
<div class="f-fcl font_28">
<span class="color9 ui-pr-24">配送</span>
<span class="ui-pr-24">广东深圳</span>
<!-- <span v-if="freightPrice != '0.00' || freightPrice != 0" class="ui-pl-24 ui-relative ui-distribution-address">运费:{{ freightPrice }}</span>-->
</div>
<!-- <div class="font_28 ui-pt-24">-->
<!-- <span class="color9 ui-pr-24">服务</span>-->
<!-- <span class="color3">支持7天无理由退货</span>-->
<!-- </div>-->
</div>
</div>
</div>
<div v-if="describe" class="ui-relative ui-pt-40">
<img class="ui-line-icon" src="https://image.fulllinkai.com/202405/15/266a22038e0122c70fc282f00c78dbc7.png" alt="" />
<div class="font_26 color3 ui-line-text">商品详情</div>
</div>
<div v-if="describe" class="font_28 color3 ui-detail-introduce">
<richTextParsing :rich-text="describe"></richTextParsing>
</div>
<div class="ui-bottom-operation f-fbc">
<div class="ui-operation-icon-box" @click="showShare = true">
<img class="ui-operation-icon" src="https://image.fulllinkai.com/202405/15/b18fc23a9956849ddbead6882fa60bb9.png" alt="" />
<div class="font_20 color6 text-center">分享</div>
</div>
<div class="ui-buy-btn f-fcc font_30 colorF" @click="showBug = true">立即购买</div>
</div>
<sharePosterV2 ref="openSharePoster">
<template #sharePoster>
<!--生成海报html容器-->
<div id="capture" class="ui-poster-container">
<div class="ui-user-avatar-box f-fcc">
<div class="ui-user-avatar backCover" :style="{ background: 'url(' + canvasData.avatar + ')' }"></div>
<div class="color3 font_22 ui-ml-8 ui-user-name-box f-fcc">
<div v-if="shareName">{{ shareName }}</div>
<div>推荐一个好物给你</div>
</div>
</div>
<div class="ui-poster-pic-box">
<img class="ui-poster-pic" :src="canvasData.pic" alt="" />
</div>
<div class="ui-poster-data-box">
<div class="ui-mt-20 ui-ml-20">
<div class="f-fcl">
<span class="font_24 ui-poster-price-symbol">¥</span>
<div class="font_40 ui-poster-price"><span v-html="htmlSharePrice"></span></div>
</div>
<div class="font_24 ellipsis_2 ui-poster-title color3">{{ title }}</div>
</div>
<div class="ui-poster-qrcode-box">
<img class="ui-poster-qrcode" :src="canvasData.qrcode" alt="" />
<div class="font_20 color6 text-center">长按保存图片至相册</div>
</div>
</div>
</div>
</template>
</sharePosterV2>
<van-popup v-model:show="showBug" round position="bottom" :duration="0.5">
<div class="ui-bug-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="showBug = false" />
<div class="ui-buy-detail-box">
<img class="ui-buy-pic" :src="icon" alt="" />
<div>
<div class="ellipsis_1 font_32 color333 bold ui-buy-title">{{ title }}</div>
<div>
<div class="f-fbc ui-total-price-box">
<div class="f-fcl">
<div v-if="calculating" class="font_24 color6 bold">计算中...</div>
<!-- <div v-else-if="stock == 0 || stock < minNum" class="font_24 color6">{{ stock == 0 ? '库存不足' : stock < minNum ? '库存不足,小于最低购买量' : '' }}</div>-->
<div v-else class="f-fcl bold ui-total-price">
<span class="font_28 ui-buy-price-symbol">¥</span>
<div class="font_44"><span v-html="htmlTotalPrice"></span></div>
</div>
</div>
</div>
<div class="f-fcl ui-buy-price color3 font_24 ui-mt-6"> 已选:{{ getSelectedSpecNames() }} </div>
</div>
<!-- <div class="f-fcl ui-buy-price color3 font_24"> 已选:{{ spuData.spec[spuIndex].name }} {{ spuSubIndex !== null ? spuSubData.spec[spuSubIndex].name : '' }} </div>-->
</div>
</div>
<div v-if="spuData.name" class="ui-sku-box">
<div class="ui-sku-list">
<!-- <div class="font_28 color3 bold">{{ spuData.name }}</div>-->
<!-- <div class="f-fcl f-wrap">-->
<!-- <div v-for="(item, index) in spuData.spec" :key="index" class="font_26 f-fbc color3 ui-pt-16" @click="selectSku(index)">-->
<!-- <div class="ui-sku-item" :class="spuIndex == index ? 'ui-sku-item-active colorF' : 'color3'">{{ item.name }}</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-if="spuSubData.name" class="ui-pt-36">-->
<!-- <div class="font_28 color3 bold">{{ spuSubData.name }}</div>-->
<!-- <div class="f-fcl f-wrap">-->
<!-- <div v-for="(item, index) in spuSubData.spec" :key="index" class="font_26 f-fbc color3 ui-pt-16" @click="selectSubSku(index)">-->
<!-- <div class="ui-sku-item" :class="spuSubIndex == index ? 'ui-sku-item-active colorF' : 'color3'">{{ item.name }}</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class="ui-sku-list ui-sku-list-v2">
<!-- 使用动态递归规格选择器 -->
<dynamic-sku-selector :specs="[spuData]" :selected-path="selectedSpecPath" :disabled="isRouteSelected" :show-only-selected="showOnlySelected" @select="handleSpecSelect" />
</div>
<div class="ui-num-box f-fbc ui-pt-28">
<div class="font_24 color6"><span class="font_28 color3 ui-mr-16">数量</span></div>
<div class="f-fcr ui-num-input-box">
<div class="ui-relative f-fcl">
<div class="font_40 color6 ui-num-input-minus" @click="numMinus()">
<div class="ui-num-minus">-</div>
</div>
<van-field v-model="num" type="digit" class="ui-input f-fcc font_28 color3" input-align="center" placeholder="购买数量" @input="numChange" />
<!-- <div class="ui-select-input font_28">{{ num }}</div>-->
<div class="font_40 color6 ui-num-input-add" @click="numAdd()">
<div class="ui-num-minus-v2">+</div>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="f-fbc ui-total-price-box">-->
<!-- <div class="f-fcl">-->
<!-- <div class="font_24 color6">合计金额:</div>-->
<!-- <div v-if="calculating" class="font_26 color6 bold">计算中...</div>-->
<!-- <div v-else-if="num == 0" class="font_26 color6 bold">库存不足</div>-->
<!-- <div v-else class="f-fcl bold ui-total-price">-->
<!-- <span class="font_28 ui-buy-price-symbol">¥</span>-->
<!-- <div class="font_44"><span v-html="htmlTotalPrice"></span></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
<div v-if="policyTitle" class="ui-confirm-bottom-box">
<div class="f-fcc">
<img v-show="!isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202405/16/fae039c670338fe6743b7f6a8803ec43.png" alt="" @click="isAgreement = !isAgreement" />
<img v-show="isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202405/16/c27533426d24451aceba94e4fe316e88.png" alt="" @click="isAgreement = !isAgreement" />
<div class="font_24 color6" @click="isAgreement = !isAgreement">
已阅读并同意<span class="ui-agreement-text" @click.stop="jumpAgreement">{{ policyTitle }}</span>
</div>
</div>
</div>
<div class="ui-confirm-btn font_32 f-fcc colorF bold" @click="payment">立即支付</div>
</div>
</van-popup>
<van-share-sheet v-model:show="showShare" title="立即分享给好友" :options="options" @select="onSelect" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, onDeactivated, onActivated, computed } from 'vue';
import qrcode from 'qrcode';
import { showToast } from 'vant';
import clipboard3 from 'vue-clipboard3';
import router from '@/router';
import requestGo from '@/utils/requestGo';
import { weXinShare } from '@/plugins/wxShare';
import { examineRegister, imageConversion } from '@/plugins/public';
import { useUserStore } from '@/store/modules/user';
import dynamicSkuSelector from '@/components/dynamicSkuSelector.vue';
defineOptions({ name: 'ShopDetail' });
const { toClipboard } = clipboard3();
const userStore = useUserStore() as any;
const loading = ref(false);
const showBug = ref(false);
const timer = ref<any>(null);
const isAgreement = ref(false); // 是否同意协议
const isRouteSelected = ref(false); // 标记是否通过路由参数选择
const currentSkuId = ref(0); // 当前选中的SKU ID
const id = ref<any>('');
const title = ref<any>('');
const icon = ref<any>('');
const policyId = ref<any>('');
const policyTitle = ref<any>('');
const banner = ref<any[]>([]); // 轮播图
const describe = ref<any>(); // 详情富文本
const num = ref<any>(0); // 购买数量
const minNum = ref<any>(0); // 购买最小数量
const skuIds = ref<any>([]);
const skuIdDataList = ref<any[]>([]);
const stock = ref<any>(0); // 选中的sku剩余库存
const calculating = ref<any>(false); // 是否显示价格计算中
const shareUrl = ref('');
const spuData = ref<any>({}); // spu一级数据
const spuSubData = ref<any>({}); // spu二级数据
const spuIndex = ref(0); // spu一级选中下标
const spuSubIndex = ref<any>(null); // spu二级选中下标
const defaultSkuId = ref<any>(0); // 默认sku_id
const querySkuId = ref<any>(0); // 路径sku_id
const skuShip = ref(3); // sku对应的物流设置
const htmlPrice = ref<any>(''); // 详情展示价格
const htmlSharePrice = ref<any>(''); // 分享展示价格
const shareName = ref<any>('');
const htmlTotalPrice = ref<any>('0'); // 总价展示价格
const originPrice = ref<any>(0); // 原价
const freightPrice = ref<any>(0); // 邮费
const canvasData = ref<any>({ background: '', avatar: '', pic: '', qrcode: '' }); // 分享所需要的图片数据
const openSharePoster = ref<any>(); // 分享按钮组件事件
const spuBanner = ref<any>([]);
const spuDesc = ref<any>('');
const showShare = ref(false);
const options = [
{ name: '复制链接', icon: 'link' },
{ name: '分享海报', icon: 'poster' },
];
const onSelect = (option) => {
if (option.name == '复制链接') {
onCopy(shareUrl.value);
} else {
isShowPoster();
}
showShare.value = false;
};
const onCopy = (e) => {
try {
toClipboard(e);
showToast('复制成功');
} catch (err) {
console.error(err);
showToast('复制失败,请手动复制');
}
};
// 获取商品详情
const getDetail = () => {
spuBanner.value = [];
spuDesc.value = '';
requestGo({ url: `/h5/v2/shop/common/spu/detail/${id.value}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
banner.value = [];
banner.value = [result.sku_default.icon];
result.spu.banner = JSON.parse(result.spu.banner);
console.log(spuBanner.value, banner.value, 'spuBanner.value');
if (banner.value.length == 0) {
banner.value = spuBanner.value;
}
describe.value = result.spu.description;
spuDesc.value = result.spu.description;
title.value = result.spu.title;
icon.value = result.sku_default.icon;
policyId.value = result.spu.policy_id;
policyTitle.value = result.spu.policy_title;
result.spu.spec = JSON.parse(result.spu.spec);
spuData.value = result.spu.spec;
// spuSubData.value = {};
// // 默认选中第一个sku
// if (spuData.value.name && spuData.value.spec[spuIndex.value].sub_spec) {
// spuSubData.value = spuData.value.spec[spuIndex.value].sub_spec;
// spuSubIndex.value = 0;
// }
selectedSpecPath.value = findFirstSkuPath([spuData.value]);
// 收集所有skuid
// const allSkuIds = collectAllSkuIds(spuData.value.spec);
// console.log(allSkuIds, 'allSkuIds');
// skuIds.value = allSkuIds.join(',');
// 默认的sku_id
defaultSkuId.value = result.sku_default?.id || 0;
minNum.value = result.sku_default?.maxNum || 10;
// 循环出所有的skuid
spuData.value.spec.forEach((item) => {
if (item.skuid) {
skuIds.value.push(item.skuid);
}
if (item.sub_spec && item.sub_spec.spec) {
recursion(item.sub_spec.spec, skuIds.value);
}
});
console.log(spuData.value.spec, '=====');
if (`${result.sku_default.price}`.includes('.')) {
let splitPrice = result.sku_default.price.split('.');
htmlSharePrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlSharePrice.value = result.sku_default.price;
}
console.log(htmlSharePrice.value, 'htmlSharePrice.value');
shareName.value = userStore.agentName ? (userStore.agentName.length > 4 ? `${userStore.agentName.slice(0, 4)}...` : userStore.agentName) : '';
// 根据循环出所有的skuid获取每一个skuid的库存
// 获取库存数据后设置默认选中
getSkuIdStock();
let shareTitle = ''
let shareIcon = ''
let shareDesc = '';
if (id.value == 118 || id.value == 332) {
shareDesc = '核心方案+专业服务团队与 AI 技术全程支持';
} else if (id.value == 178) {
shareDesc = '双证报考,自我提升 + 就业双赢';
} else if (id.value == 489) {
shareTitle = '友福同门•体重管理'
shareIcon = 'https://images.health.ufutx.com/202608/10/114e3a0d4d38049b3a0b628bb0db8dbb.jpg'
shareDesc = '轻松减 不反弹';
} else {
shareDesc = '🌟 友福同享严选好物 →';
}
weXinShare(`${shareIcon || result.spu.icon}`, `https://health.ufutx.cn/go_html/store_common#/shopDetail/${id.value}?from_user_id=${userStore.userID}&from_type=rt_${id.value}`, `${shareTitle || result.spu.title}`, `${shareDesc}`);
let text = `https://health.ufutx.cn/go_html/store_common#/shopDetail/${id.value}?from_user_id=${userStore.userID}&from_type=rt_${id.value}`;
shareUrl.value = text;
qrcode.toDataURL(text, (err, url) => {
if (err) {
console.log(err);
} else {
// 分享二维码图片转base64
canvasData.value.qrcode = url;
}
});
// 分享背景图片转base64
imageConversion('https://image.fulllinkai.com/202111/02/f23994d8fd3088c833a8bbf2bfdf6de5.png').then((res) => {
canvasData.value.background = res;
});
// 分享用户头像图片转base64
imageConversion(`${userStore.agentAvatar}`).then((res) => {
canvasData.value.avatar = res;
});
// 分享活动图片转base64
imageConversion(`${result.spu.icon}`).then((res) => {
canvasData.value.pic = res;
});
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
// 获取所有skuid库存
const getSkuIdStock = () => {
console.log(skuIds.value, 'skuIds.value');
requestGo({ url: `/h5/v2/shop/common/sku/${skuIds.value}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
console.log(result, '77777');
skuIdDataList.value = result;
//
// if (skuIdDataList.value && skuIdDataList.value.length > 0) {
// // 判断路径 sku_id是否存在sku列表中
// skuIdDataList.value.map((item) => {
// if (item.id == querySkuId.value) {
// defaultSkuId.value = querySkuId.value;
// }
// });
//
// spuData.value.spec.map((item, index) => {
// if (item.sub_spec) {
// item.sub_spec.spec.map((itemV2, indexV2) => {
// if (defaultSkuId.value == itemV2.skuid) {
// spuIndex.value = index;
// spuSubIndex.value = indexV2;
// spuSubData.value = item.sub_spec;
// }
// });
// } else {
// if (defaultSkuId.value == item.skuid) {
// spuIndex.value = index;
// }
// }
// });
// gainSelectStock();
// console.log('3444');
// num.value = stock.value > minNum.value ? minNum.value : 0;
// } else {
// console.log('999');
// num.value = 0;
// }
// console.log(skuIdDataList.value, 'vvv-');
const firstPath = findFirstSkuPath([spuData.value]);
console.log(firstPath, '===');
if (firstPath.length > 0) {
selectedSpecPath.value = firstPath;
gainSelectStock();
}
console.log(router.currentRoute.value.query.selectPath, 'router.currentRoute.value.query.selectPath');
if (router.currentRoute.value.query.selectPath) {
console.log('111');
handleRouteSelectPath();
} else {
console.log('222');
prepay('init');
}
})
.catch((err) => {
console.log(err);
});
};
// 替换原有的固定两级规格变量
const selectedSpecPath = ref([]); // 存储选中的规格路径 [0, 0, 1, 2]
const specHierarchy = ref([]); // 存储规格层级结构
const showOnlySelected = ref(false);
// // 新增递归获取所有skuid的通用方法
// // 修正后的collectAllSkuIds函数
// const collectAllSkuIds = (specData) => {
// const ids = [];
//
// const traverse = (specs) => {
// if (!specs || !Array.isArray(specs)) return;
//
// specs.forEach((item) => {
// if (item.skuid) {
// ids.push(item.skuid);
// }
// if (item.sub_spec && item.sub_spec.spec) {
// traverse(item.sub_spec.spec);
// }
// });
// };
//
// traverse(specData);
// return ids;
// };
// 修正findFirstSkuPath支持多级递归
const findFirstSkuPath = (specData) => {
const traverse = (specs, basePath = []) => {
if (!specs || !Array.isArray(specs)) return [];
for (let specIndex = 0; specIndex < specs.length; specIndex++) {
const specGroup = specs[specIndex];
if (!specGroup || !specGroup.spec) continue;
for (let itemIndex = 0; itemIndex < specGroup.spec.length; itemIndex++) {
const item = specGroup.spec[itemIndex];
const currentPath = [...basePath, itemIndex];
if (item.skuid) {
return currentPath;
}
if (item.sub_spec && item.sub_spec.spec && item.sub_spec.spec.length > 0) {
const subPath = traverse([item.sub_spec], currentPath);
if (subPath.length > 0) {
return subPath;
}
}
}
}
return [];
};
return traverse(specData);
};
// 修正后的getSkuByPath函数
const getSkuByPath = (path, specData) => {
if (!path || !path.length || !specData || !Array.isArray(specData)) return null;
// 移除偶数长度检查
let currentSpecs = specData;
let result = null;
for (let i = 0; i < path.length; i++) {
const index = path[i];
// 边界检查
if (!currentSpecs[0] || !currentSpecs[0].spec || index < 0 || index >= currentSpecs[0].spec.length) {
return null;
}
const item = currentSpecs[0].spec[index];
if (!item) {
return null;
}
result = item;
// 如果还有下一级,继续深入
if (i + 1 < path.length && result.sub_spec) {
currentSpecs = [result.sub_spec];
} else if (i + 1 < path.length && !result.sub_spec) {
return null; // 路径超出层级
}
}
return result;
};
const getSelectedSpecNames = () => {
if (!selectedSpecPath.value.length) return '';
const names = [];
let current = spuData.value;
for (let i = 0; i < selectedSpecPath.value.length; i++) {
const index = selectedSpecPath.value[i];
// 边界检查
if (!current || !current.spec || !Array.isArray(current.spec) || index < 0 || index >= current.spec.length) {
break;
}
const item = current.spec[index];
if (!item) {
break;
}
names.push(item.name);
// 继续深入下一层
if (i < selectedSpecPath.value.length - 1) {
if (!item.sub_spec) {
break;
}
current = item.sub_spec;
}
}
return names.join(' ');
};
// // 修正后的collectAllSkuIds函数
// const collectAllSkuIds = (specs) => {
// const ids = [];
// console.log(specs, 'specs=');
// const traverse = (items) => {
// console.log(items, 'i==sdf');
// if (!Array.isArray(items)) return;
//
// items.forEach((specGroup) => {
// if (specGroup && specGroup.spec && Array.isArray(specGroup.spec)) {
// specGroup.spec.forEach((item) => {
// if (item.skuid) {
// ids.push(item.skuid);
// }
// if (item.sub_spec && item.sub_spec.spec && Array.isArray(item.sub_spec.spec)) {
// traverse([item.sub_spec]); // 保持数组格式
// }
// });
// }
// });
// };
//
// traverse(specs || []);
// return ids;
// };
// 处理规格选择事件
const handleSpecSelect = (fullPath: number[]) => {
console.log('handleSpecSelect收到路径:', fullPath);
if (!fullPath || !Array.isArray(fullPath) || fullPath.length === 0) {
console.warn('无效的路径:', fullPath);
return;
}
selectedSpecPath.value = [...fullPath];
const currentItem = getItemByPath(fullPath);
if (!currentItem) {
console.warn('路径对应的规格项不存在:', fullPath);
return;
}
// 自动选择下一层级规格(使用新的路径格式)
const autoSelectNextLevel = (currentPath: number[]) => {
const item = getItemByPath(currentPath);
if (!item) {
console.warn('autoSelectNextLevel - 规格项不存在:', currentPath);
return;
}
if (item.sub_spec && item.sub_spec.spec && Array.isArray(item.sub_spec.spec) && item.sub_spec.spec.length > 0) {
// 使用新的路径格式:[...currentPath, 0]
const nextPath = [...currentPath, 0];
const nextItem = getItemByPath(nextPath);
if (nextItem) {
selectedSpecPath.value = nextPath;
autoSelectNextLevel(nextPath);
} else {
console.warn('下一层级路径无效:', nextPath);
}
}
};
autoSelectNextLevel([...fullPath]);
gainSelectStock();
};
// 通过路径获取规格项
const getItemByPath = (path: number[]) => {
if (!path || path.length === 0 || !spuData.value) return null;
// 移除偶数长度检查
let current = spuData.value;
for (let i = 0; i < path.length; i++) {
const index = path[i];
// 边界检查
if (!current || !current.spec || !Array.isArray(current.spec) || index < 0 || index >= current.spec.length) {
return null;
}
const item = current.spec[index];
if (!item) {
return null;
}
if (i === path.length - 1) {
return item;
}
// 继续深入下一层
if (!item.sub_spec) {
return null;
}
current = item.sub_spec;
}
return null;
};
// // 修正后的findItemByPath函数 - 与getItemByPath保持一致
// const findItemByPath = (path: number[]) => {
// if (!path || path.length === 0 || !spuData.value) return null;
// // 移除偶数长度检查
//
// let current = spuData.value;
//
// for (let i = 0; i < path.length; i++) {
// const index = path[i];
//
// if (!current || !current.spec || !Array.isArray(current.spec) || index < 0 || index >= current.spec.length) {
// return null;
// }
//
// const item = current.spec[index];
// if (!item) {
// return null;
// }
//
// if (i === path.length - 1) {
// return item;
// }
//
// if (!item.sub_spec) {
// return null;
// }
//
// current = item.sub_spec;
// }
//
// return null;
// };
//
// const getNextLevelPath = (currentPath) => {
// console.log(currentPath, 'currentPath');
// let current = spuData.value;
//
// // 遍历到当前路径
// for (let i = 0; i < currentPath.length; i += 2) {
// const specIndex = currentPath[i];
// const itemIndex = currentPath[i + 1];
// if (!current || !current.spec || !current.spec[specIndex] || !current.spec[specIndex].spec[itemIndex]) {
// return null;
// }
//
// const item = current.spec[specIndex].spec[itemIndex];
// console.log(item, 'item====');
//
// // 检查是否有子规格
// if (item.sub_spec && item.sub_spec.spec && item.sub_spec.spec.length > 0) {
// // 返回下一层级的第一项
// return [0, 0];
// }
//
// current = item.sub_spec;
// console.log(current, 'current');
// }
//
// return null;
// };
// 获取选中sku的库存信息
const gainSelectStock = () => {
console.log('当前选中路径:', selectedSpecPath.value);
console.log('当前选中路径2:', [spuData.value]);
const selectedSku = getSkuByPath(selectedSpecPath.value, [spuData.value]);
console.log('选中的sku:', selectedSku);
if (selectedSku && selectedSku.skuid) {
const skuInfo = skuIdDataList.value.find((item) => item.id === selectedSku.skuid);
console.log(skuInfo, 'skuInfo');
if (skuInfo) {
stock.value = skuInfo.stock;
minNum.value = skuInfo.min_num;
num.value = minNum.value;
originPrice.value = skuInfo.price;
banner.value = [skuInfo.icon];
icon.value = skuInfo.icon;
describe.value = skuInfo.description || spuDesc.value;
}
} else {
// 如果没有找到sku重置状态
// stock.value = 0;
num.value = 0;
}
prepay('init');
console.log();
};
// 重构递归收集所有skuid
const recursion = (items, ids = []) => {
items.forEach((item) => {
if (item.skuid) {
ids.push(item.skuid);
}
if (item.sub_spec && item.sub_spec.spec) {
recursion(item.sub_spec.spec, ids);
}
});
return ids;
};
// 获取预计算价格
const prepay = (e) => {
// let id = '';
// if (spuSubIndex.value !== null) {
// id = spuSubData.value.spec[spuSubIndex.value].skuid;
// } else {
// id = spuData.value.spec[spuIndex.value].skuid;
// }
const selectedSku = getSkuByPath(selectedSpecPath.value, [spuData.value]);
if (!selectedSku || !selectedSku.skuid) return;
const id = selectedSku.skuid;
console.log(id, 'id===');
clearTimeout(timer.value);
timer.value = setTimeout(() => {
let data = {
sku_unit_list: [{ sku_id: id, num: num.value - 0 }],
};
requestGo({ url: `/h5/v2/shop/common/sku/prepay_amount/1`, data, method: 'post' })
.then((res) => {
const result = res.data.prepay_amount_list[0];
// originPrice.value = result.price_origin;
freightPrice.value = result.freight;
loading.value = true;
if (e == 'init') {
if (`${result.price}`.includes('.')) {
let splitPrice = result.price.split('.');
htmlPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlPrice.value = result.price;
}
// if (!stock.value) {
// num.value = 0;
// }
}
minNum.value = result.min_num;
skuShip.value = result.ship;
title.value = result.title;
console.log(skuShip.value, 'sss');
if (`${result.price}`.includes('.')) {
let splitPrice = result.price.split('.');
htmlTotalPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
htmlPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlTotalPrice.value = result.price;
htmlPrice.value = result.price;
}
calculating.value = false;
})
.catch((err) => {
console.log(err);
});
}, 300);
};
// Sku 数量减少
const numMinus = () => {
if (num.value <= 1) {
return;
}
if (num.value <= minNum.value) {
showToast('不能小于基础购买量');
return;
}
num.value--;
calculating.value = true;
prepay('calculate');
};
// Sku 数量增加
const numAdd = () => {
// if (num.value >= stock.value) {
// showToast('超出限购或商品库存上限');
// return;
// }
// if (stock.value < minNum.value) {
// showToast('库存不足,小于最低购买量');
// return;
// }
num.value++;
calculating.value = true;
prepay('calculate');
};
// 过滤输入的数字
const numChange = () => {
if (num.value < minNum.value) {
num.value;
}
// if (num.value >= stock.value) {
// num.value = stock.value;
// }
if (num.value < minNum.value) {
num.value = minNum.value;
}
// if (stock.value < minNum.value) {
// num.value = 0;
// }
if (num.value) {
prepay('calculate');
} else {
calculating.value = true;
}
};
// 支付
const payment = () => {
const selectedSku = getSkuByPath(selectedSpecPath.value, [spuData.value]);
if (!selectedSku || !selectedSku.skuid) {
showToast('请先选择规格');
return;
}
const data = {
id: id.value,
skuId: selectedSku.skuid,
spuNum: num.value,
spuName: getSelectedSpecNames(),
skuShip: skuShip.value,
};
if (num.value <= 0) {
showToast('请先选择购买数量');
return;
}
if (policyTitle.value && !isAgreement.value) {
showToast('请先阅读并同意协议');
return;
}
// if (import.meta.env.MODE === 'development') {
// router.push({
// name: 'shopOrderConfirmV2',
// query: { orderData: JSON.stringify(data) },
// });
// return;
// }
// 没有资料先去注册,注册后回到分享的指定页面
console.log(userStore, 'userStore.agentMobile');
if (!userStore.agentMobile && id.value != '118' && id.value != '332' && id.value != '489') {
examineRegister(router.currentRoute.value);
return;
}
router.push({
name: 'shopOrderConfirm',
query: { orderData: JSON.stringify(data) },
});
};
const selectSku = (index) => {
if (spuIndex.value == index) {
return;
}
htmlTotalPrice.value = '0';
spuIndex.value = index;
if (spuData.value.spec[index].sub_spec) {
spuSubData.value = spuData.value.spec[index].sub_spec;
spuSubIndex.value = 0;
} else {
spuSubData.value = {};
spuSubIndex.value = null;
}
num.value = 0;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
// if (stock.value) {
num.value = minNum.value;
calculating.value = true;
prepay('calculate');
// }
};
const selectSubSku = (index) => {
if (spuSubIndex.value == index) {
return;
}
htmlTotalPrice.value = '0';
spuSubData.value = spuData.value.spec[spuIndex.value].sub_spec;
spuSubIndex.value = index;
num.value = 0;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
if (stock.value && stock.value >= minNum.value) {
num.value = stock.value > minNum.value ? minNum.value : 0;
calculating.value = true;
prepay('calculate');
}
};
const isShowPoster = () => {
if (canvasData.value.background && canvasData.value.avatar && canvasData.value.pic && canvasData.value.qrcode) {
openSharePoster.value.isShow();
}
};
const jumpAgreement = () => {
router.push({
name: 'shopAgreement',
params: { id: policyId.value },
query: { title: policyTitle.value },
});
};
// 添加处理路由参数的函数
const handleRouteSelectPath = () => {
const selectPathParam = router.currentRoute.value.query.selectPath;
if (selectPathParam) {
try {
// 解析路径参数
const pathStr = String(selectPathParam);
const pathArray = pathStr
.split(',')
.map(Number)
.filter((n) => !isNaN(n) && n >= 0);
if (pathArray.length > 0) {
console.log('从路由参数获取选择路径:', pathArray);
// 验证路径有效性
const isValid = validatePath(pathArray);
if (isValid) {
selectedSpecPath.value = [...pathArray];
isRouteSelected.value = true;
// 获取选中SKU信息
const selectedItem = getItemByPath(pathArray);
console.log(selectedItem, 'selectedItem=');
if (selectedItem && selectedItem.skuid) {
currentSkuId.value = selectedItem.skuid;
console.log(currentSkuId.value, 'currentSkuId.value');
// // 查找对应的SKU数据
// const skuData = skuIdDataList.value.find((item) => item.id === selectedItem.skuid);
// if (skuData) {
// updateSkuInfo(skuData);
// }
// 自动展开下一层级
const autoSelectNextLevel = (currentPath: number[]) => {
const item = getItemByPath(currentPath);
if (item && item.sub_spec && item.sub_spec.spec && item.sub_spec.spec.length > 0) {
const nextPath = [...currentPath, 0];
const nextItem = getItemByPath(nextPath);
if (nextItem) {
selectedSpecPath.value = nextPath;
autoSelectNextLevel(nextPath);
}
}
};
autoSelectNextLevel([...pathArray]);
console.log('gainSelectStock');
gainSelectStock();
}
} else {
console.warn('无效的路由路径:', pathArray);
}
}
} catch (error) {
console.error('解析路由参数失败:', error);
}
}
};
// 添加路径验证函数
const validatePath = (path: number[]): boolean => {
if (!path || path.length === 0) return false;
let current = spuData.value;
for (let i = 0; i < path.length; i++) {
const index = path[i];
if (!current || !current.spec || !Array.isArray(current.spec) || index < 0 || index >= current.spec.length) {
return false;
}
const item = current.spec[index];
if (!item) return false;
if (i < path.length - 1) {
if (!item.sub_spec) return false;
current = item.sub_spec;
}
}
return true;
};
// 离开时清除生成的海报
onDeactivated(() => {
openSharePoster.value.remove();
});
onActivated(() => {});
onMounted(() => {
let route = router.currentRoute.value.params;
let query = router.currentRoute.value.query;
// if (id.value != route.id) {
id.value = route.id;
loading.value = false;
showBug.value = false;
spuIndex.value = 0;
spuSubIndex.value = null;
querySkuId.value = query?.sku_id || 0;
skuIds.value = [];
getDetail();
// } else {
// // 分享用户头像图片转base64
// imageConversion(`${userStore.agentAvatar}`).then((res) => {
// canvasData.value.avatar = res;
// });
// }
});
</script>
<style lang="scss" scoped>
.ui-shopDetail {
background: #f4f4f4;
overflow-y: auto;
min-height: 100vh;
}
.ui-mall-pic {
width: 100vw;
height: px2rem(750);
display: block;
object-fit: cover;
object-position: center;
}
:deep(.van-cell) {
padding: 0 px2rem(2);
}
.ui-input {
width: px2rem(88);
}
.custom-indicator {
position: absolute;
right: px2rem(20);
top: px2rem(20);
border-radius: px2rem(8);
padding: px2rem(8) px2rem(14);
letter-spacing: px2rem(4);
background: #aaaaaa;
}
.ui-shop-detail-container {
padding: px2rem(24);
.ui-shop-data-box,
.ui-ExpressDelivery-data-box {
padding: px2rem(30) px2rem(24);
background: #ffffff;
border-radius: px2rem(16);
margin-bottom: px2rem(24);
.ui-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(2);
}
.ui-show-price,
.ui-price-symbol {
color: #cc352f;
}
.ui-original-price {
text-decoration: line-through;
margin-left: px2rem(16);
}
}
.ui-ExpressDelivery-data-box {
.ui-distribution-address:after {
content: '';
width: px2rem(2);
height: px2rem(22);
background: #d8d8d8;
position: absolute;
left: 0;
top: px2rem(10);
}
}
}
.ui-line-icon {
width: px2rem(310);
height: px2rem(12);
display: block;
margin: 0 auto;
}
.ui-line-text {
position: absolute;
left: 50%;
top: px2rem(28);
transform: translateX(-50%);
}
.ui-detail-introduce {
padding: px2rem(20) 0 px2rem(120) 0;
background: #ffffff;
margin-top: px2rem(30);
}
.ui-bottom-operation {
width: 100%;
background: #ffffff;
padding: px2rem(18) px2rem(24) px2rem(42) 0;
box-sizing: border-box;
position: fixed;
bottom: 0;
left: 0;
border-top: px2rem(2) solid #f5f5f5;
.ui-operation-icon-box {
flex: 1;
.ui-operation-icon {
width: px2rem(44);
height: px2rem(44);
display: block;
margin: 0 auto px2rem(2) auto;
}
}
.ui-buy-btn {
width: px2rem(480);
height: px2rem(88);
background: #2a63b2;
border-radius: px2rem(24);
}
}
.ui-poster-container {
position: relative;
position: fixed;
top: -100000;
left: 0;
z-index: -99999;
border-radius: px2rem(16);
width: px2rem(482);
height: px2rem(806);
padding: 0 px2rem(30) px2rem(30) px2rem(30);
background: #f7f7f7;
box-sizing: border-box;
background-size: cover !important;
background-position: center !important;
background-repeat: no-repeat !important;
.ui-user-avatar-box {
width: px2rem(440);
height: px2rem(48);
background: #ffffff;
border-radius: px2rem(40);
margin-top: px2rem(8);
position: absolute;
left: 50%;
transform: translateX(-50%);
top: px2rem(10);
z-index: 10;
.ui-user-avatar {
width: px2rem(28);
height: px2rem(28);
border-radius: 50%;
margin-left: px2rem(18);
}
.ui-user-name-box {
margin-top: px2rem(-6);
}
}
.ui-poster-pic-box,
.ui-poster-data-box {
width: px2rem(450);
height: px2rem(450);
border-radius: px2rem(16);
background: #ffffff;
position: absolute;
top: px2rem(80);
left: px2rem(16);
display: flex;
justify-content: space-between;
.ui-poster-pic {
width: 100%;
height: 100%;
border-radius: px2rem(16);
object-fit: cover;
object-position: center;
display: block;
vertical-align: top;
}
}
.ui-poster-data-box {
top: px2rem(546);
height: px2rem(244);
.ui-poster-title {
width: px2rem(226);
line-height: px2rem(36);
max-width: px2rem(226);
margin-top: px2rem(4);
}
.ui-poster-price,
.ui-poster-price-symbol {
color: #cc352f;
}
.ui-poster-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(6);
}
.ui-poster-qrcode-box {
.ui-poster-qrcode {
width: px2rem(200);
height: px2rem(200);
border-radius: px2rem(16);
display: block;
object-fit: cover;
object-position: center;
margin-bottom: px2rem(-8);
}
}
}
}
.ui-bug-box {
position: relative;
background: #ffffff;
padding-bottom: px2rem(60);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-buy-detail-box {
display: flex;
justify-content: left;
//height: px2rem(160);
position: relative;
padding: px2rem(30) px2rem(30) 0 px2rem(30);
.ui-buy-pic {
width: px2rem(160);
min-width: px2rem(160);
height: px2rem(160);
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
margin-right: px2rem(16);
border-radius: px2rem(8);
border: px2rem(2) solid #f8f8f8;
}
.ui-buy-title {
max-width: px2rem(440);
line-height: px2rem(48);
}
.ui-buy-price {
//position: absolute;
//bottom: px2rem(-12);
}
}
.ui-buy-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(2);
}
.ui-total-price-box {
padding-top: px2rem(40);
.ui-total-price {
color: #cc352f;
}
}
.ui-user-input {
width: px2rem(532);
box-sizing: border-box;
border: px2rem(2) solid #f5f5f5;
border-radius: px2rem(16);
padding: px2rem(16) px2rem(30);
}
::v-deep(input::-webkit-input-placeholder) {
font-weight: initial;
color: #999999;
}
// 去除默认下划线
::v-deep(.van-cell:after) {
position: relative;
}
.ui-sku-box {
padding: px2rem(40) px2rem(30) px2rem(24) px2rem(30);
.ui-sku-list-v2 {
height: px2rem(500);
overflow-y: auto;
}
.ui-sku-list {
.ui-sku-item,
.ui-sku-item-active {
padding: px2rem(12) px2rem(24);
background: #f4f4f4;
border-radius: px2rem(8);
margin-right: px2rem(20);
}
.ui-sku-item-active {
background: #2a63b2;
}
.ui-active-sku {
background: #e8fff7;
}
}
}
.ui-num-box {
.ui-num-input-box {
border-radius: px2rem(8);
border: px2rem(2) solid #ececec;
.ui-num-input-minus,
.ui-num-input-add {
width: px2rem(56);
height: px2rem(56);
text-align: center;
position: relative;
.ui-num-minus,
.ui-num-minus-v2 {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.ui-num-minus {
top: 43%;
}
}
}
.ui-num-input-minus {
border-right: px2rem(2) solid #ececec;
}
.ui-num-input-add {
border-left: px2rem(2) solid #ececec;
}
.ui-select-input {
width: px2rem(44);
border: none;
text-align: center;
padding: px2rem(4) px2rem(20);
background: initial;
line-height: px2rem(40);
}
}
.ui-confirm-btn {
width: px2rem(638);
height: px2rem(88);
background: #2a63b2;
border-radius: px2rem(24);
margin: px2rem(28) auto 0 auto;
}
.ui-confirm-bottom-box {
padding-top: px2rem(28);
border-top: px2rem(2) solid #eaeaea;
.ui-agreement-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
vertical-align: top;
margin-right: px2rem(8);
}
.ui-agreement-text {
color: #2a63b2;
}
}
}
</style>