Files

564 lines
14 KiB
Vue
Raw Normal View History

2023-10-11 11:27:47 +08:00
<template>
2023-11-14 17:21:03 +08:00
<Popup
ref="popupRef"
:showCloseable="false"
@close="emit('close')"
2023-10-11 11:27:47 +08:00
>
<view
2023-11-14 17:21:03 +08:00
class="goodAttrSelect"
2023-10-11 11:27:47 +08:00
>
2023-11-14 17:21:03 +08:00
<view
class="goodAttrSelect-goods"
v-if="curAttr"
>
<uv-image
class="attr-image"
:src="curAttr.image || storeInfo.image"
2023-11-14 17:21:03 +08:00
width="150rpx"
height="150rpx"
></uv-image>
<view class="attr-info">
<view class="name">
{{ storeInfo.storeName }}
</view>
<view class="attr-info-bottom">
<!-- 普通SKU -->
<template v-if="actionType==='singleBuy' || !curAttr.campaignType">
<view class="price">¥{{ curAttr.price }}</view>
<view class="stock">库存{{ curAttr.stock }}</view>
</template>
<!-- 活动SKU -->
<template v-else>
<view class="flex flex-ai__end">
<view
class="price-name"
v-if="curAttr.campaignState===1">{{ activityName }}
</view>
<view class="price primary-color">¥{{ curAttr.campaignPrice }}</view>
<view class="old-price">{{ curAttr.price }}</view>
</view>
<view class="stock">库存{{ curAttr.campaignStock }}</view>
</template>
</view>
</view>
</view>
<!-- 没有选中sku -->
<view
class="goodAttrSelect-goods"
v-else
>
<uv-image
class="attr-image"
:src="storeInfo.image"
width="150rpx"
height="150rpx"
></uv-image>
<view class="attr-info">
<view class="name">
{{ storeInfo.storeName }}
</view>
<view class="attr-info-bottom">
<view class="price">¥{{ storeInfo.price }}</view>
<view class="stock">库存{{ storeInfo.stock }}</view>
2023-11-14 17:21:03 +08:00
</view>
</view>
2023-10-11 11:27:47 +08:00
</view>
<div class="line"></div>
2023-11-14 17:21:03 +08:00
<view
class="goodAttrSelect-attr row"
v-if="curAttr"
>
2023-10-11 11:27:47 +08:00
<view class="goodAttrSelect-attr-title">
数量
</view>
<view class="goodAttrSelect-attr-content">
2023-11-14 17:21:03 +08:00
<!-- cart number -->
<view
class="cart-num flex flex-ai__center flex-jc__sb"
@click.stop=""
>
<view
class="button"
2023-11-17 20:55:32 +08:00
:class="storeNum <= 1 && 'disabled'"
2023-11-14 17:21:03 +08:00
@click="handleCartNumberChange(curAttr,'minus')"
>
<uv-icon
name="minus"
color="#333"
2023-11-17 20:55:32 +08:00
size="24rpx"
2023-11-14 17:21:03 +08:00
></uv-icon>
</view>
<view class="input">
<input
type="number"
inputmode="numeric"
v-model="storeNum"
@blur="(e)=>handleCartNumberInputChange(e,curAttr)"
@input="(e)=>cartNumberInput(e,curAttr)"
2023-11-14 17:21:03 +08:00
>
</view>
<view
class="button"
2023-11-17 20:55:32 +08:00
:class="storeNum >= curAttr.stock && 'disabled'"
2023-11-14 17:21:03 +08:00
@click="handleCartNumberChange(curAttr,'plus')"
>
<uv-icon
name="plus"
color="#333"
2023-11-17 20:55:32 +08:00
size="24rpx"
2023-11-14 17:21:03 +08:00
></uv-icon>
</view>
</view>
2023-10-11 11:27:47 +08:00
</view>
</view>
<div class="line"></div>
<view
2023-11-14 17:21:03 +08:00
class="goodAttrSelect-attr"
v-for="(item, index) in productAttr"
:key="index"
2023-10-11 11:27:47 +08:00
>
<view class="goodAttrSelect-attr-title">
{{ item.attrName }}
</view>
<view class="goodAttrSelect-attr-content">
<space
wrap="warp"
gap>
2023-10-11 11:27:47 +08:00
<view
:class="{ attr: true, check: selectedAttr[index] === attr,disabled:checkAttrDisable(index,attr)}"
2023-11-14 17:21:03 +08:00
v-for="(attr, attrIndex) in item.attrValueArr"
:key="attrIndex"
@tap="handleSelectAttr(index, attr)"
>{{ attr }}
</view>
2023-10-11 11:27:47 +08:00
</space>
</view>
</view>
<view class="goodAttrSelect-action">
<uv-button
:disabled="!curAttr || curAttr&&curAttr.stock===0 || storeNum===0"
2023-11-14 17:21:03 +08:00
round
block
type="primary"
@tap="handleSubmit"
2023-10-11 11:27:47 +08:00
>
确定
</uv-button>
</view>
</view>
2023-11-14 17:21:03 +08:00
</Popup>
2023-10-11 11:27:47 +08:00
</template>
<script setup>
import {computed, nextTick, ref, toRefs} from 'vue';
2023-11-14 17:21:03 +08:00
import { useInterface } from "@/hooks/useInterface";
import Popup from '@/components/Popup/index.vue';
// ====================== hooks ============================
const {toast} = useInterface()
2023-10-11 11:27:47 +08:00
// ====================== 组件相关 ============================
const emit = defineEmits(['select', 'close'])
/**
* @property goodsDetail 商品详情
* @property skuId sku打开时默认sku
*/
const props = defineProps({
goodsDetail: {
type: Object,
},
skuId: {
type: [String, Number],
}
})
const {goodsDetail, skuId} = toRefs(props)
// ======================= 数据相关 ==============================
const storeInfo = computed(() => goodsDetail.value.storeInfo) // 商品信息
const productAttr = computed(() => goodsDetail.value.productAttr) // 商品sku attr细腻些
const productValue = computed(() => goodsDetail.value.productValue) // 商品sku value信息
const activityName = computed(() => {
if (!curAttr.value) return ''
return ['拼团价', '秒杀价', '折扣价'][curAttr.value.campaignType - 1]
})
2023-10-11 11:27:47 +08:00
// ======================= sku 操作相关 ==========================
const selectedAttr = ref([]) // 当前选中sku的attr key信息
const curAttr = ref(null) // 当前选中的sku
2023-10-11 11:27:47 +08:00
/**
* 检查是否为disable
* @param attrRowIndex
* @param attr
* @return {boolean}
*/
function checkAttrDisable(attrRowIndex, attr) {
// 模拟当前值选中
const selectedAttrMock = JSON.parse(JSON.stringify(selectedAttr.value))
selectedAttrMock[attrRowIndex] = attr
const filterAttrMock = selectedAttrMock.filter(i => i);
// 如果不是最后一次选择属性构不成sku比较直接return false
if (filterAttrMock.length < productAttr.value.length) return false
const mockNameStr = filterAttrMock.filter(i => i)
for (const skuName in productValue.value) {
if (skuName.includes(mockNameStr)) {
const sku = productValue.value[skuName]
const trueStock = sku.campaignState === 1 ? sku.campaignStock : sku.stock // 真实库存 (区分活动和普通)
// 从有库存的开始查不要从没库存的直接break
// 只要有一条includes且库存大于0就直接break
if (trueStock > 0) return false
}
}
return true
2023-11-17 20:55:32 +08:00
}
2023-10-11 11:27:47 +08:00
/**
* 设置默认sku选中
*/
function handleSetDefaultSkuSelect() {
const skuList = productValue.value
// 遍历sku的对象数组匹配到id相同的项
if (skuId.value) {
for (const skuName in skuList) {
const sku = skuList[skuName]
const trueStock = sku.campaignState === 1 ? sku.campaignStock : sku.stock // 真实库存 (区分活动和普通)
if (sku.id === skuId.value && trueStock > 0) {
// 设置默认选中
selectedAttr.value = skuName.split(',')
curAttr.value = sku
break;
}
2023-11-14 17:21:03 +08:00
}
}
// 如果没有找到那就默认选中sku的第一个、
if (selectedAttr.value.filter(i => i).length <= 0) {
const skuNameList = Reflect.ownKeys(skuList)
if (skuNameList.length > 0) {
let defaultSku, defaultSkuName
for (const skuName of skuNameList) {
const sku = skuList[skuName];
const trueStock = sku.campaignState === 1 ? sku.campaignStock : sku.stock // 真实库存 (区分活动和普通)
if (trueStock > 0) {
defaultSku = sku
defaultSkuName = skuName
break;
}
}
if (defaultSku) {
selectedAttr.value = defaultSkuName.split(',')
curAttr.value = defaultSku
}
2023-11-17 20:55:32 +08:00
}
2023-10-11 11:27:47 +08:00
}
handleSetStoreNum()
2023-10-11 11:27:47 +08:00
}
/**
* sku选中
* @param attrRowIndex sku属性row索引
* @param attrItemName sku选中的item的名称object的key
*/
const handleSelectAttr = (attrRowIndex, attrItemName) => {
if (checkAttrDisable(attrRowIndex, attrItemName)) return toast({title: "商品抢完啦~看看其他的吧"})
curAttr.value = undefined
if (selectedAttr.value[attrRowIndex] && selectedAttr.value[attrRowIndex] === attrItemName) {
selectedAttr.value[attrRowIndex] = ''
return;
2023-11-17 20:55:32 +08:00
}
selectedAttr.value[attrRowIndex] = attrItemName
if (selectedAttr.value.filter(i => i).length === productAttr.value.length) {
curAttr.value = productValue.value[selectedAttr.value.join(',')]
// 设置库存
handleSetStoreNum()
2023-11-17 20:55:32 +08:00
}
2023-10-11 11:27:47 +08:00
}
/**
* 提交
*/
2023-10-11 11:27:47 +08:00
const handleSubmit = () => {
if (!curAttr.value) return;
if (curAttr.value.stock === 0 && storeNum.value === 0) return
emit('select', {
store: curAttr.value,
num: storeNum.value
})
close()
2023-10-11 11:27:47 +08:00
}
// ========================== 数量相关 ===========================
const storeNum = ref(1) // 数量
2023-11-14 17:21:03 +08:00
/**
* 用户手动输入改变数量
* @param e
* @param item
* @returns {*}
*/
function handleCartNumberInputChange(e, item) {
const value = Number(e.detail.value)
const trueStock = item.campaignState === 1 ? item.campaignStock : item.stock // 真实库存 (区分活动和普通)
2023-11-14 17:21:03 +08:00
if (value <= 0) {
storeNum.value = 1
} else if (value > trueStock) {
storeNum.value = trueStock
2023-11-17 20:55:32 +08:00
} else {
storeNum.value = value.toString().replace(/^0+/, '')
2023-11-14 17:21:03 +08:00
}
}
/**
* 购买数量验证
*/
function cartNumberInput(e, item){
const pattern = /^0+|[.]*/g;
nextTick(() => {
storeNum.value = e.detail.value.replace(pattern,'');
})
}
2023-11-14 17:21:03 +08:00
/**
* 用户点击购物车+-改变数量
* @param item
* @param type
* @returns {*}
*/
function handleCartNumberChange(item, type = 'plus') {
const trueStock = item.campaignState === 1 ? item.campaignStock : item.stock // 真实库存 (区分活动和普通)
2023-11-14 17:21:03 +08:00
if (type === 'plus') {
if (storeNum.value + 1 > trueStock) {
storeNum.value = trueStock
} else {
2023-11-17 20:55:32 +08:00
storeNum.value += 1
}
2023-11-14 17:21:03 +08:00
} else {
if (storeNum.value - 1 <= 0) {
2023-11-17 20:55:32 +08:00
storeNum.value = 1
} else {
storeNum.value -= 1
}
2023-11-14 17:21:03 +08:00
}
}
/**
* 设置选中数量
* 当sku发生改变的时候需要调用
*/
const handleSetStoreNum = () => {
if (!curAttr.value) return storeNum.value = 0
if (storeNum.value === 0 && curAttr.value.stock > 0) {
storeNum.value = 1
}
if (storeNum.value > curAttr.value.stock) {
storeNum.value = curAttr.value.stock
}
}
2023-11-14 17:21:03 +08:00
// ========================= 弹窗相关 ============================
const popupRef = ref()
const actionType = ref('select') // select:普通选择 cart:购物车选择 buy:普通下单 activeBuy:活动下单 singleBuy:单独购买
/**
* 打开弹窗
* @param cartNum
* @param action 打开弹窗类型
*/
const open = (cartNum = 1, action = 'select') => {
actionType.value = action
storeNum.value = cartNum || 1
const attrLength = productAttr.value.length
selectedAttr.value = new Array(attrLength).fill('')
handleSetDefaultSkuSelect()
2023-11-14 17:21:03 +08:00
popupRef.value.show()
2023-10-11 11:27:47 +08:00
}
const close = () => {
curAttr.value = null
2023-11-14 17:21:03 +08:00
emit('close')
popupRef.value.close()
2023-10-11 11:27:47 +08:00
}
defineExpose({
open,
close
})
</script>
2023-11-14 17:21:03 +08:00
<style lang="scss">
2023-10-11 11:27:47 +08:00
.goodAttrSelect {
height: 100%;
&-goods {
2023-11-14 17:21:03 +08:00
padding: 40rpx 30rpx;
display: flex;
.attr-image {
}
.attr-info {
2023-11-17 20:55:32 +08:00
max-width: calc(100% - 150rpx - 30rpx);
2023-11-14 17:21:03 +08:00
display: flex;
2023-11-17 20:55:32 +08:00
flex: 1;
2023-11-14 17:21:03 +08:00
flex-direction: column;
justify-content: space-between;
margin-left: 30rpx;
2023-11-14 17:21:03 +08:00
.name {
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
2023-11-17 20:55:32 +08:00
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
2023-11-14 17:21:03 +08:00
}
&-bottom {
display: flex;
justify-content: space-between;
.price {
font-size: 30rpx;
line-height: 42rpx;
color: #ee6d46;
font-weight: bold;
}
.price-name {
background: #ee6d46;
color: #fff;
border-radius: 10rpx;
font-size: 24rpx;
font-style: italic;
padding: 3rpx 10rpx;
margin-right: 5rpx;
}
.old-price {
font-size: 22rpx;
color: grey;
text-decoration: line-through;
2023-11-14 17:21:03 +08:00
}
.stock {
font-size: 24rpx;
line-height: 42rpx;
color: #666666;
}
}
}
2023-10-11 11:27:47 +08:00
}
&-action {
padding: 20rpx 20rpx;
}
&-attr {
2023-11-17 20:55:32 +08:00
padding: 40rpx 30rpx 10rpx 0;
2023-10-11 11:27:47 +08:00
&.row {
display: flex;
align-items: center;
justify-content: space-between;
2023-11-17 20:55:32 +08:00
padding-bottom: 40rpx;
2023-10-11 11:27:47 +08:00
.goodAttrSelect-attr-title {
margin-bottom: 0;
}
}
&-title {
margin: 0 0 30rpx 30rpx;
2023-10-11 11:27:47 +08:00
}
2023-11-14 17:21:03 +08:00
&-content {
}
2023-10-11 11:27:47 +08:00
}
}
.line {
height: 1rpx;
background: #E6E6E6;
}
.attr {
height: 68rpx;
border: 1rpx solid #333333;
padding: 0 20rpx;
font-size: 28rpx;
2023-11-14 17:21:03 +08:00
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: flex;
justify-content: center;
align-items: center;
2023-11-17 20:55:32 +08:00
margin: 0 0 30rpx 30rpx;
2023-10-11 11:27:47 +08:00
&.check {
background: #333333;
color: #fff;
}
&.disabled {
border: none;
background: #f5f7fa;
color: #ccced3;
}
2023-10-11 11:27:47 +08:00
}
2023-11-14 17:21:03 +08:00
.cart-num {
font-size: 24rpx;
.input {
width: 120rpx;
input {
width: 100%;
text-align: center;
color: #333;
}
}
.button {
font-size: 32rpx;
2023-11-17 20:55:32 +08:00
width: 40rpx;
height: 40rpx;
2023-11-14 17:21:03 +08:00
aspect-ratio: 1/1;
border-radius: 5rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
transition: all .3s;
2023-11-17 20:55:32 +08:00
border: 1px solid #999999;
2023-11-14 17:21:03 +08:00
&:active {
scale: 1.2;
}
&.disabled {
2023-11-17 20:55:32 +08:00
border-color: #dddddd;
:deep(.uv-icon__icon) {
2023-11-17 20:55:32 +08:00
color: #dddddd !important;
}
2023-11-17 20:55:32 +08:00
&:active {
scale: 1;
}
}
2023-11-14 17:21:03 +08:00
}
}
.gap {
2023-11-17 20:55:32 +08:00
gap: 20rpx;
}
2023-10-11 11:27:47 +08:00
</style>