<template> <view @touchmove.prevent.stop> <u-overlay :show="goodsSkuPop" @click="closeFn" zIndex="490"> <u-popup class="popup-type" :show="goodsSkuPop" @close="closeFn" mode="bottom" :overlay="false" zIndex="500"> <view class="py-[32rpx] relative" v-if="goodsDetail.detail" @touchmove.prevent.stop> <view class="flex px-[32rpx] mb-[40rpx]"> <view class="rounded-[var(--goods-rounded-big)] overflow-hidden w-[180rpx] h-[180rpx]"> <u--image width="180rpx" height="180rpx" :radius="'var(--goods-rounded-big)'" :src="img(goodsDetail.detail.sku_image)" @click="imgListPreview(goodsDetail.detail.sku_image)" mode="aspectFill"> <template #error> <image class="w-[180rpx] h-[180rpx]" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image> </template> </u--image> </view> <view class="flex flex-1 flex-col ml-[24rpx] py-[10rpx]"> <view class="w-[100%]"> <view class="text-[var(--price-text-color)] flex items-baseline"> <text class="text-[32rpx] font-bold price-font">¥</text> <text class="text-[48rpx] price-font">{{ parseFloat(goodsPrice).toFixed(2).split('.')[0] }}</text> <text class="text-[32rpx] mr-[6rpx] price-font">.{{ parseFloat(goodsPrice).toFixed(2).split('.')[1] }}</text> <image class="h-[24rpx] ml-[6rpx] max-w-[44rpx]" v-if="priceType() == 'member_price'" :src="img('addon/mall/VIP.png')" mode="heightFix" /> <image class="h-[24rpx] ml-[6rpx] max-w-[72rpx]" v-if="priceType() == 'discount_price'" :src="img('addon/mall/discount.png')" mode="heightFix" /> </view> <view class="text-[26rpx] leading-[32rpx] text-[var(--text-color-light6)] mt-[20rpx]">库存{{goodsDetail.detail.stock}}{{ goodsDetail.goods.unit }}</view> </view> <view class="text-[26rpx] leading-[30rpx] text-[var(--text-color-light6)] w-[100%] max-h-[60rpx] multi-hidden" v-if="goodsDetail.goodsSpec && goodsDetail.goodsSpec.length">已选规格:{{goodsDetail.detail.sku_spec_format}}</view> </view> </view> <scroll-view class="h-[500rpx] px-[32rpx] box-border mb-[60rpx]" scroll-y="true"> <view :class="{'mt-[20rpx]': 0 != index }" v-for="(item,index) in goodsDetail.goodsSpec" :key="index"> <view class="text-[28rpx] leading-[36rpx] mb-[24rpx]">{{item.spec_name}}</view> <view class="flex flex-wrap"> <view class="box-border bg-[var(--temp-bg)] text-[24rpx] px-[44rpx] text-center h-[56rpx] flex-center mr-[20rpx] mb-[20rpx] border-1 border-solid rounded-[50rpx] border-[var(--temp-bg)]" :class="{'!border-[var(--primary-color)] text-[var(--primary-color)] !bg-[var(--primary-color-light)]': subItem.selected}" v-for="(subItem,subIndex) in item.values" :key="subIndex" @click="change(subItem, index)">{{subItem.name}}</view> </view> </view> <view class="flex justify-between items-center my-[20rpx]"> <view class="text-[28rpx]">购买数量</view> <text v-if="maxBuyShow > 0 && minBuyShow > 1" class="ml-[20rpx] mr-[auto] text-[24rpx] text-[var(--primary-color)]">({{ minBuyShow }}{{ goodsDetail.goods.unit }}起售,限购{{ maxBuyShow }}{{ goodsDetail.goods.unit }})</text> <text v-else-if="maxBuyShow > 0" class="ml-[20rpx] mr-[auto] text-[24rpx] text-[var(--primary-color)]">(限购{{ maxBuyShow }}{{ goodsDetail.goods.unit }})</text> <text v-else-if="minBuyShow > 1" class="ml-[20rpx] mr-[auto] text-[24rpx] text-[var(--primary-color)]">({{ minBuyShow }}{{ goodsDetail.goods.unit }}起售)</text> <u-number-box :min="minBuy" :max="maxBuy" integer :step="1" input-width="68rpx" v-model="buyNum" input-height="52rpx"> <template #minus> <view class="relative w-[30rpx] h-[30rpx]" @click="reduceNumChange"> <text class="text-[30rpx] nc-iconfont nc-icon-jianV6xx font-500 absolute flex items-center justify-center -left-[8rpx] -bottom-[8rpx] -right-[8rpx] -top-[8rpx]" :class="{ '!text-[var(--text-color-light9)]': buyNum <= minBuy }"></text> </view> </template> <template #input> <input class="text-[#303133] text-[28rpx] mx-[10rpx] w-[80rpx] h-[44rpx] bg-[var(--temp-bg)] leading-[44rpx] text-center rounded-[6rpx]" type="number" @input="goodsSkuInputFn" @blur="goodsSkuBlurFn" v-model="buyNum" /> </template> <template #plus> <view class="relative w-[30rpx] h-[30rpx]" @click="addNumChange"> <text class="text-[30rpx] nc-iconfont nc-icon-jiahaoV6xx font-500 absolute flex items-center justify-center -left-[8rpx] -bottom-[8rpx] -right-[8rpx] -top-[8rpx]" :class="{ '!text-[var(--text-color-light9)]': buyNum >= maxBuy }"></text> </view> </template> </u-number-box> </view> </scroll-view> <view class="px-[20rpx]"> <!-- #ifdef H5 --> <button v-if="goodsDetail.detail.stock > 0" hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" @click="confirm">确定</button> <!-- #endif --> <!-- #ifdef MP-WEIXIN --> <template v-if="goodsDetail.detail.stock > 0"> <!-- <button v-if="isBindMobile && userInfo && !userInfo.mobile" hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" open-type="getPhoneNumber" @getphonenumber="memberStore.bindMobile">确定</button> <button v-else hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" @click="confirm">确定</button> --> <button hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" @click="confirm">确定</button> </template> <!-- #endif --> <button hover-class="none" v-else class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 text-[#fff] bg-[#ccc] rounded-[50rpx]">已售罄</button> </view> </view> </u-popup> </u-overlay> <!-- 强制绑定手机号 --> <bind-mobile ref="bindMobileRef" /> </view> </template> <script setup lang="ts"> import { ref, computed, toRaw } from 'vue'; import { img, redirect, getToken } from '@/utils/common' import useCartStore from '@/addon/mall/stores/cart' import { useLogin } from '@/hooks/useLogin' import useMemberStore from '@/stores/member' import bindMobile from '@/components/bind-mobile/bind-mobile.vue'; import { cloneDeep } from 'lodash-es' import { t } from '@/locale' const props = defineProps(['goodsDetail']); const goodsSkuPop = ref(false); const callback:any = ref(null); const currSpec = ref({ skuId: "", name: [] }) const openType = ref(""); const buyNum = ref(1) const maxBuy = ref(0); // 限购 const minBuy = ref(0); // 起售 const maxBuyShow = ref(0); // 限购 const minBuyShow = ref(0); // 起售 // 商品价格 const goodsPrice = computed(() =>{ let price = "0.00"; if(Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.is_discount && goodsDetail.value.sale_price != goodsDetail.value.price){ price = goodsDetail.value.sale_price // 折扣价 }else if(Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.member_discount && getToken() && goodsDetail.value.member_price != goodsDetail.value.price){ price = goodsDetail.value.member_price // 会员价 }else{ price = goodsDetail.value.price } return price; }) // 价格类型 const priceType = () => { let type = ""; if (Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.is_discount && goodsDetail.value.sale_price != goodsDetail.value.price) { type = 'discount_price'// 折扣 } else if (Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.member_discount && getToken() && goodsDetail.value.member_price != goodsDetail.value.price) { type = 'member_price' // 会员价 } return type; } // 会员信息 const memberStore = useMemberStore() const userInfo = computed(() => memberStore.info) // 购物车数量 const cartStore = useCartStore(); cartStore.getList(); const cartList = computed(() => cartStore.cartList) const open = (type="",fn = "")=>{ openType.value = type; goodsSkuPop.value = true; callback.value = fn; } const goodsSkuInputFn = () => { setTimeout(() => { if (!buyNum.value || buyNum.value <= minBuy.value) { buyNum.value = minBuy.value || 1; } if (buyNum.value >= maxBuy.value) { buyNum.value = maxBuy.value; } // 起售大于库存,初始值也应该是零 if (minBuy.value > goodsDetail.value.detail.stock) { buyNum.value = 0; } }, 0) } const goodsSkuBlurFn = () => { setTimeout(() => { if (!buyNum.value || buyNum.value <= minBuy.value) { buyNum.value = minBuy.value || 1; } if (buyNum.value >= maxBuy.value) { buyNum.value = maxBuy.value; } // 起售大于库存,初始值也应该是零 if (minBuy.value > goodsDetail.value.detail.stock) { buyNum.value = 0; uni.showToast({ title: '商品库存小于起购数量', icon: 'none' }); } }, 0) } const closeFn = ()=>{ goodsSkuPop.value = false } const goodsDetail = computed(() => { let data = cloneDeep(props.goodsDetail); // 重组数据结构 if(Object.keys(data).length){ if(!Object.keys(currSpec.value.name).length) currSpec.value.name = data.sku_spec_format.split(","); data.goodsSpec.forEach((item: any, index: any)=>{ let specName = item.spec_values.split(","); item.values = []; specName.forEach((specItem: any, specIndex: any)=>{ item.values[specIndex] = {}; item.values[specIndex].name = specItem; item.values[specIndex].selected = false; item.values[specIndex].disabled = false; // 选中规格 currSpec.value.name.forEach((currSpecItem, currSpecIndex)=>{ if(currSpecIndex == index && currSpecItem == specItem){ item.values[specIndex].selected = true; } }) }) }) getSkuId(); // 当前详情内容 if(data.skuList && Object.keys(data.skuList).length){ data.skuList.forEach((idItem: any, idIndex: any)=>{ if(idItem.sku_id == currSpec.value.skuId){ data.detail = idItem; } }) } } // 限购 - 是否开启限购 if (data.goods.is_limit) { if (data.goods.max_buy) { let max_buy = 0; if (data.goods.limit_type == 1) { //单次限购 max_buy = data.goods.max_buy; } else { // 单人限购 let buyVal = data.goods.max_buy - (data.goods.has_buy || 0); max_buy = buyVal > 0 ? buyVal : 0; } if (max_buy > data.detail.stock) { maxBuy.value = data.detail.stock } else if (max_buy <= data.detail.stock) { maxBuy.value = max_buy; } // 限购开启且最大购买变为零时,初始值也应该是零 if (maxBuy.value == 0) { buyNum.value = 0; } } // 仅用于展示 maxBuyShow.value = data.goods.max_buy; // 限购 } else { maxBuy.value = data.detail.stock; } // 起售 minBuy.value = data.goods.min_buy > 0 ? data.goods.min_buy : 1; // 起售大于库存,初始值也应该是零 if (minBuy.value > data.detail.stock) { buyNum.value = 0; } else { buyNum.value = minBuy.value; } // 仅用于展示 minBuyShow.value = data.goods.min_buy; return data; }) const change = (data: any, index: any)=>{ currSpec.value.name[index] = data.name; getSkuId(); // 刷新当前规格信息 } const emits = defineEmits(['change']) const getSkuId = ()=>{ props.goodsDetail.skuList.forEach((skuItem: any, skuIndex: any)=>{ if(skuItem.sku_spec_format == currSpec.value.name.toString()){ currSpec.value.skuId = skuItem.sku_id emits('change',skuItem.sku_id) } }) } const addNumChange = () => { if (minBuy.value && minBuy.value > goodsDetail.value.detail.stock) { uni.showToast({ title: '商品库存小于起购数量', icon: 'none' }) return; } if (goodsDetail.value.goods.is_limit) { let tips = `该商品单次限购${ goodsDetail.value.goods.max_buy }件`; if (goodsDetail.value.goods.limit_type != 1) { //单次限购 tips = `该商品每人限购${ goodsDetail.value.goods.max_buy }件`; if (goodsDetail.value.goods.max_buy - maxBuy.value) { tips += `,已购${ goodsDetail.value.goods.max_buy - maxBuy.value }件`; } } if (buyNum.value >= maxBuy.value) { uni.showToast({ title: tips, icon: 'none' }) } } } const reduceNumChange = () => { if (minBuy.value > 1) { let tips = `该商品起购${ minBuy.value }件`; if (buyNum.value <= minBuy.value) { uni.showToast({ title: tips, icon: 'none' }) } } } //强制绑定手机号 const bindMobileRef: any = ref(null) const isBindMobile = ref(uni.getStorageSync('isbindmobile')) //提交 const confirm = ()=>{ if (buyNum.value < 1) return; // #ifdef H5 // 绑定手机号 if (!userInfo.value && uni.getStorageSync('isBindMobile')) { uni.setStorage({ key: 'loginBack', data: { url: '/addon/mall/pages/goods/detail', param: { sku_id: goodsDetail.value.sku_id, type: goodsDetail.value.type } } }) bindMobileRef.value.open() return false } // #endif // 检测是否登录 if (!userInfo.value) { useLogin().setLoginBack({ url: '/addon/mall/pages/goods/detail', param: { sku_id: goodsDetail.value.sku_id, type: goodsDetail.value.type } }) return false } // 加入购物车 if(openType.value == 'join_cart'){ let num = 0; let limitNum = 0; let cartId = ""; if(cartList.value['goods_'+goodsDetail.value.goods_id]&&cartList.value['goods_'+goodsDetail.value.goods_id]['sku_'+goodsDetail.value.sku_id]){ num = toRaw(cartList.value['goods_'+goodsDetail.value.goods_id]['sku_'+goodsDetail.value.sku_id].num); cartId = toRaw(cartList.value['goods_'+goodsDetail.value.goods_id]['sku_'+goodsDetail.value.sku_id].id) } if (cartList.value['goods_' + goodsDetail.value.goods_id] && cartList.value['goods_' + goodsDetail.value.goods_id]) { limitNum = toRaw(cartList.value['goods_' + goodsDetail.value.goods_id].totalNum); } num += buyNum.value; limitNum += Number(buyNum.value); /************** 限购-start **************/ if (goodsDetail.value.goods.is_limit) { let tips = `该商品单次限购${ goodsDetail.value.goods.max_buy }件`; if (goodsDetail.value.goods.limit_type != 1) { //单次限购 tips = `该商品每人限购${ goodsDetail.value.goods.max_buy }件`; if (goodsDetail.value.goods.max_buy - maxBuy.value) { tips += `,已购${ goodsDetail.value.goods.max_buy - maxBuy.value }件`; } } if (limitNum > maxBuy.value) { uni.showToast({ title: tips, icon: 'none' }) return false; } } /************** 限购-end **************/ cartStore.increase({ id: cartId||'', goods_id: goodsDetail.value.goods_id, sku_id: goodsDetail.value.sku_id, stock: goodsDetail.value.stock, sale_price: goodsDetail.value.sale_price, site_id: goodsDetail.value.site_id, num: num },0,()=>{ uni.showToast({ title: '加入购物车成功', icon: 'none' }); }); }else if(openType.value == 'buy_now') { // 立即购买 var data = { sku_id: goodsDetail.value.sku_id, num: buyNum.value }; let key = goodsDetail.value.shop_info.site_id uni.setStorage({ key: 'orderCreateData', data: { body: { [key]: { sku_data: [data] } } }, success: () => { redirect({ url: '/addon/mall/pages/order/payment' }) } }); } closeFn(); } //预览图片 const imgListPreview = (item: any) => { if (item === '') return false var urlList = [] urlList.push(img(item)) //push中的参数为 :src="item.img_url" 中的图片地址 uni.previewImage({ indicator: "number", loop: true, urls: urlList }) } defineExpose({ open }) </script> <style lang="scss" scoped> ::v-deep .u-number-box .u-number-box__slot{ display: flex; align-items: center; } </style>