shop_app/components/sku/multiple.vue

721 lines
20 KiB
Vue

<template>
<!-- 选规格弹窗 -->
<view>
<up-popup :show="showSku" mode="bottom" :round="10" @close="close" :safe-area-inset-bottom="true" catchtouchmove="preventD" :lock-scroll="true" :z-index="202">
<view class="wholeBox">
<view class="skuTop">
<image class="img" :src="viewImg" @click="preViewImg(viewImg)" :style="{width: imgWidth + 'rpx', height: imgWidth + 'rpx'}" mode="aspectFill" />
<view class="topInfo" :class="imgWidth == 200 ? '' : 'smallfont'">
<view class="title" v-if="imgWidth == 200">{{skuInfo.title}}</view>
<view class="priceBox">
<!-- 会员下普通价格 -->
<view class="text1" v-if="vipOn && chooseItem.vip_price > 0 && (is_vip || (!is_vip && vip_price_show))">
<text class="icon1">¥</text>
<text>{{chooseItem.price}}</text>
</view>
<view>
<!-- 显示会员价 -->
<view v-if="vipOn && chooseItem.vip_price > 0 && (is_vip || (!is_vip && vip_price_show))" class="vip">
<view class="vip-tag"><text class="txt">会员价&nbsp;¥</text>{{chooseItem.vip_price}}</view>
</view>
<!-- 显示普通价格 -->
<view v-else class="vip">
<text class="icon1">¥</text>
<text>{{chooseItem.price}}</text>
</view>
</view>
</view>
<view class="text" v-if="show_stock === 1">剩余{{ chooseItem.stock > 0 ? chooseItem.stock : 0 }}件</view>
<view class="sel">已选:{{skuName}}</view>
</view>
</view>
<scroll-view class="goodBox" scroll-y="true" enhanced="true" @scroll="skuScroll" :scroll-top="topNum">
<view class="skuBox">
<view class="sku_list" v-for="(items, idx) in specsList" :key="idx">
<view class="title">第{{idx + 1}}件商品</view>
<view class="sku_item" v-for="(item, index) in items" :key="index">
<view class="spec_name">{{item.spec_name}}</view>
<view class="sku_tag">
<view class="tag" v-for="(it, i) in item.value" :key="it.id"
@click="!it.show ? chooseSku(it, index, i, idx, items) :''"
:class="pickSkuList[idx]['v'+ (index + 1) +'_id'] === it.id ? 'choose' : it.show ? 'dis' : '' ">
<image :src="it.img" v-if="it.img" class="tag_img"></image>
<text>{{it.value}}</text>
</view>
</view>
</view>
</view>
<view class="sku_num">
<view class="num">
<text>购买数量</text>
<text v-if="show_stock === 1">(最多{{ chooseItem.stock > 0 ? chooseItem.stock : 0 }}件)</text>
<view v-if="skuInfo.limit_type == 2" class="box_red">限购{{skuInfo.limit}}件</view>
<view v-if="skuInfo.limit_type == 1" class="box_red">{{skuInfo.start_sale_num}}件起购</view>
</view>
<up-number-box
v-model="chooseItem.num"
:min="skuInfo.limit_type == 1 ? parseInt(skuInfo.start_sale_num) : skuInfo.limit_type === 0 ? 1 : 1 "
:max="skuInfo.limit_type == 2 ? parseInt(skuInfo.limit) : skuInfo.limit_type === 0 ? (chooseItem.stock > 0 ? chooseItem.stock : 0) : 0"
@overlimit="plusBtn(skuInfo.limit_type == 1 ? 0 : skuInfo.limit_type == 2 ? 1 : 2 ,chooseItem.num, skuInfo.limit)"
disabledInput
bgColor="#f2f3f5"
:longPress="false"
@change="changeNum">
</up-number-box>
</view>
<view class="sku_buy" v-if="sales > 0">
<view class="img" v-for="img in skuInfo.avatars" :key="img">
<image :src="img" class="sku-buy-img"></image>
</view>
<view class="text">已有<text class="num">{{sales}}</text>个社群好友购买</view>
</view>
</view>
<view class="edition" v-if="textModule.length != 0">
<view v-for="(item, index) in textModule" :key="index">
<!-- 大图 -->
<view v-if="item.type == 1" class="edition_box" @click="preViewImg(item.imgs[0])">
<view v-for="(imgs, i) in item.imgs" :key="i">
<image :src="imgs + '?x-oss-process=image/format,webp'" :webp="true" mode="widthFix" :lazy-load="true" style="width:100%"></image>
</view>
</view>
<!-- 小图 -->
<view class="edition_box" v-if="item.type == 2">
<view class="small_img">
<view v-for="(list, index1) in item.img" :key="index1" class="imgs" @click="getTextimg(item.img, list)">
<image :src="list + '?x-oss-process=image/format,webp'" mode="aspectFill" :webp="true" class="image"></image>
</view>
</view>
</view>
<!-- 视频 -->
<view class="edition_box" v-if="item.type == 3">
<video :src="item.video[0]" height="100%" width="250" class="video_box" style="display: block;margin: 0 auto;"></video>
</view>
<!-- 文字 -->
<view class="edition_box text1_box" v-if="item.type == 4">
<text selectable="true" :user-select="true" class="text">{{item.text}}</text>
</view>
</view>
</view>
<view v-else-if="images.length != 0" class="imgbox">
<image v-for="img in images" :key="img" :lazy-load="true" :src="img" class="img" mode="widthFix"></image>
</view>
<view v-if="images.length === 0 && mainImg.length !== 0" class="imgbox">
<image v-for="img in mainImg" :key="img" :lazy-load="true" :src="img" class="img" mode="widthFix"></image>
</view>
</scroll-view>
<view class="sku_btn" v-if="soldStatus == 2" @click="arrivalNotice()">
<view class="btn" v-if="!remindStock">到货提醒</view>
<view class="btn dis" v-else>取消到货提醒</view>
</view>
<view class="sku_btn" v-else @click="(chooseItem.stock > 0 && canBuy) ? addToCart() : ''">
<view class="btn" :class="(chooseItem.stock <= 0 || !canBuy) ? 'dis' : ''">加入购物车</view>
</view>
</view>
</up-popup>
</view>
</template>
<script>
import { ref, reactive, toRefs, watch } from 'vue'
import { showToast, goodsItem, toMiniProgram } from '../common.js'
import { get, post } from '@/api/request.js'
import { Style } from '@/utils/list.js'
export default {
props: {
show: {
type: Boolean,
default: false
},
show_stock: {
type: Number,
default: 0
},
sourceId: {
type: Number,
default: 0
},
sourceType: {
type: String,
default: 'normal'
},
canBuy: {
type: Boolean,
default: true
},
goodId: {
type: Number,
default: 0
},
groupId: {
type: Number,
default: 0
},
specsNum: {
type: Number,
default: 0
},
remindStock: {
type: Boolean,
default: false
},
soldStatus: {
type: Number,
default: 0
},
sales: {
type: Number,
default: 0
},
remindTmplId: {
type: String,
default: ''
}
},
setup(props, context) {
const data = reactive({
showSku: false,
Color: uni.getStorageSync('theme_color'),
priceColor: Style[uni.getStorageSync('theme_index') * 1].priceColor,
cartColor: Style[uni.getStorageSync('theme_index') * 1].cartColor,
bgColor: Style[uni.getStorageSync('theme_index') * 1].bgColor,
vipOn: uni.getStorageSync('vip_on') === 1,
vip_price_show: uni.getStorageSync('vip_price_show'),
sourceId: 0,
sourceType: '',
viewImg: '',
imgWidth: 200,
skuInfo: {},
skuName: '',
is_vip: uni.getStorageSync('is_vip'),
specsList: [],
textModule: [],
mainImg: [],
images: [],
chooseItem: {},
goodId: 0,
choose: [],
sku_skuInfo: {},
chooseIndex: [],
pickSkuList: [],
curIndex: 0,
action: false,
topNum: 0,
specsNum: 0,
isSale: uni.getStorageSync('role') == 1,
canClick: true,
remindStock: false
})
const preventD = () => {
return
}
// 预览图片
const preViewImg = (img) => {
let img_preview_suffix = uni.getStorageSync('img_preview_suffix') || ''
let imurl = img + img_preview_suffix
if(img.indexOf('.gif') >= 0 || img.indexOf('.GIF') >= 0) {
imurl = img
}
uni.previewImage({
urls: [imurl],
current: 0
})
}
const hanleShare = () => {
context.emit('buttomShow', true)
}
function skuScroll(e) {
if(e.detail.scrollTop > 200){
data.imgWidth = 120
} else {
data.imgWidth = 200
}
}
function chooseSku(e, index, i_index, idx, items) {
data.action = true
if (e.img || e.img_800) {
data.viewImg = e.img_800 || e.img
}
data.curIndex = idx
data.choose[idx]['v' + (index + 1) + '_id'] = items[index].value[i_index].id
data.pickSkuList[idx]['v' + (index + 1) + '_id'] = items[index].value[i_index].id
data.pickSkuList[idx]['v' + (index + 1)] = items[index].value[i_index].value
data.chooseIndex[idx].push(index)
data.chooseIndex[idx] = Array.from(new Set(data.chooseIndex[idx]))
}
watch(props, (newProps) => {
if(newProps.show) {
data.showSku = true
data.chooseItem = {}
data.chooseItem.num = 1
data.goodId = newProps.goodId
data.sourceId = newProps.sourceId
data.sourceType = newProps.sourceType
data.specsNum = newProps.specsNum
data.remindStock = newProps.remindStock
data.images = []
data.specsList = []
data.pickSkuList = []
data.choose = []
data.chooseIndex = []
get(`/api/v1/goods/spec/${data.goodId}`).then((res) => {
data.topNum = data.topNum == 0 ? 0.1 : 0
data.skuInfo = res.data
data.viewImg = res.data.face_img
data.mainImg = res.data.gallery || []
data.limit = res.data.limit
if(res.data.specs) {
res.data.specs[0].value.forEach((it) => {
if(it.img_800){
data.images.push(it.img_800)
}
})
let tempSpecs = JSON.parse(JSON.stringify(res.data.specs))
if (res.data.specs.length === 1) {
let sku = []
res.data.skus.forEach((ress) => {
if (ress.stock <= 0 || ress.price == 0) {
sku.push(ress.v1_id)
}
})
tempSpecs.forEach((res, i) => {
res.value.forEach((req) => {
if (sku.includes(req.id)) {
req.show = true
} else {
req.show = false
}
})
})
}
for (let index = 0; index < data.specsNum; index++) {
data.pickSkuList[index] = {}
for (let i = 0; i < tempSpecs.length; i++) {
data.pickSkuList[index]['v' + (i + 1) + '_id'] = 0
}
data.specsList.push(JSON.parse(JSON.stringify(tempSpecs)))
data.choose.push({})
data.chooseIndex.push([])
}
}
data.textModule = res.data.text_modules || []
if(props.soldStatus == 2) {
res.data.stock = 0
res.data.skus.forEach((ress) => {
ress.stock = 0
})
}
data.sku_skuInfo = res.data.skus
data.chooseItem = JSON.parse(JSON.stringify(res.data.skus[0]))
data.chooseItem.stock = res.data.stock
setTimeout(() => {
data.imgWidth = 200
}, 100)
})
}
})
watch(
() => data.choose,
(newChoose) => {
if(!data.action) {
return
}
if (data.specsList[data.curIndex].length - Object.keys(data.choose[data.curIndex]).length == 1) {
// 如果只有一个规格类未选中,则去判断未选中的这一项是否可选
data.specsList[data.curIndex].forEach((req, i) => {
if (data.chooseIndex[data.curIndex].indexOf(i) == -1) {
req.value.forEach((rej, j) => {
data.sku_skuInfo.forEach((ress, m) => {
let flag = [rej.id === ress['v' + (i + 1) + '_id']]
for (const key in data.choose[data.curIndex]) {
flag.push(ress[key] == data.choose[data.curIndex][key])
}
if (flag.findIndex(target => target === false) == -1) {
if (ress.stock <= 0 || ress.price == 0) {
rej.show = true
} else {
rej.show = false
}
}
})
})
}
})
} else if (data.specsList[data.curIndex].length == Object.keys(data.choose[data.curIndex]).length) {
for (const key in data.choose[data.curIndex]) {
let i = parseInt(key.substr(1, 1))
data.specsList[data.curIndex][i - 1].value.forEach((req) => {
data.sku_skuInfo.forEach((ress, m) => {
let flag = [req.id == ress['v' + i + '_id']]
for (const k in data.choose[data.curIndex]) {
let j = parseInt(k.substr(1, 1))
if (i != j) {
flag.push(ress[k] == data.choose[data.curIndex][k])
}
}
if (flag.findIndex(target => target === false) == -1) {
if ((ress.stock <= 0 || ress.price == 0)) {
req.show = true
} else {
req.show = false
}
}
})
})
}
}
// 判断选择规格值是否完整
for (let i = data.specsList[data.curIndex].length; i >= 1; i--) {
if (!newChoose[data.curIndex]['v' + i + '_id']) {
return
}
}
// 对比选中项
data.sku_skuInfo.forEach((sku, s_index) => {
let num = data.chooseItem.num
if (
sku.v1_id === newChoose[data.curIndex].v1_id &&
sku.v2_id === newChoose[data.curIndex].v2_id &&
sku.v3_id === newChoose[data.curIndex].v3_id &&
sku.v4_id === newChoose[data.curIndex].v4_id
) {
data.chooseItem = {
...sku
}
if (num > 0) {
data.chooseItem.num = num < sku.stock ? num : (sku.stock > 0 ? sku.stock : 0)
} else if (sku.stock > 0) {
data.chooseItem.num = data.skuInfo.limit_type == 0 ? 1 : data.skuInfo.limit_type == 1 ? parseInt(data.skuInfo.start_sale_num) : 1
} else {
data.chooseItem.num = 0
}
}
})
}, {
deep: true
}
)
watch(() => data.pickSkuList, (newPickList) => {
data.skuName = ''
newPickList.forEach((item) => {
let name = []
for (let i = 0; i < data.specsNum; i++) {
if(item['v' + (i + 1)]) {
name.push(item['v' + (i + 1)])
}
}
if(name.length) {
data.skuName += '【' + name.join('-') + '】'
}
})
}, {
deep: true
})
function addToCart() {
let params = [], flag = true
for (let i = 0; i < data.pickSkuList.length; i++) {
let pick = data.pickSkuList[i]
for (let m = 0; m < data.sku_skuInfo.length; m++) {
let sku = data.sku_skuInfo[m]
if(pick.v1_id == sku.v1_id && pick.v2_id == sku.v2_id && pick.v3_id == sku.v3_id && pick.v4_id == sku.v4_id) {
params.push({
shop_goods_id: sku.shop_goods_id,
shop_goods_sku_id: sku.id,
shop_group_goods_id: props.groupId,
num: data.chooseItem.num,
source_id: data.sourceId,
source_type: data.sourceType
})
}
}
}
console.log(params)
if(params.length != data.specsNum) {
showToast('请选择完整规格')
return
}
post('/api/v1/carts/batch', { goods: params}).then((res) => {
showToast('已加入购物车')
getNum()
context.emit('getnum')
close()
})
}
function close(e) {
data.action = false
data.showSku = false
context.emit('close')
}
// 商品数量
function changeNum(e) {
data.chooseItem.num = e
}
async function arrivalNotice() {
// 取消提醒
if(data.remindStock) {
post(`/api/v1/goods/remind/cancel/${data.groupId}`, {type: 1}).then((res) => {
data.remindStock = false
uni.showToast({
title: '已取消到货提醒',
icon: 'none'
})
context.emit('remind', false)
})
} else {
toMiniProgram('pages/groups/index?id=' + data.groupId)
}
}
const { getNum } = goodsItem()
return {
toMiniProgram,
...toRefs(data),
preventD,
preViewImg,
hanleShare,
skuScroll,
chooseSku,
close,
addToCart,
getNum,
changeNum,
arrivalNotice
}
}
}
</script>
<style lang="scss" scoped>
.wholeBox{
background-color: #fff;
height: 70vh;
position: relative;
overflow: hidden;
.skuTop{
display: flex;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.img{
background: #bbbbbb;
border-radius: 8rpx;
margin-right: 30rpx;
transition: all 0.3s;
width: 200rpx;
height: 200rpx;
}
.topInfo{
display: flex;
width: calc(100% - 250rpx);
flex-direction: column;
&.smallfont{
.title{
display: none;
}
.price{
font-size: 28rpx;
}
.text{
display: none;
}
.sel{
font-size: 24rpx;
margin-top: 10rpx;
}
}
.title{
width: calc(100% - 50rpx);
margin-bottom: 10rpx;
}
.priceBox{
display: flex;
align-items: center;
flex-wrap: wrap;
.text1{
font-size: 32rpx;
color: v-bind('priceColor');
margin-right: 10rpx;
font-weight: 600;
}
.icon1{
font-size: 24rpx;
font-weight: normal;
}
.vip{
display: flex;
align-items: center;
font-size: 34rpx;
color: v-bind('priceColor');
}
}
.text{
font-size: 24rpx;
color: #999999;
margin-top: 20rpx;
display: block;
}
.sel{
margin-top: 20rpx;
font-size: 28rpx;
}
}
}
.goodBox{
height: calc(70vh - 280rpx);
.skuBox{
margin-bottom: 30rpx;
.sku_list{
padding: 10rpx 30rpx 30rpx;
border-bottom: 1rpx solid #eee;
position: relative;
.title{
font-size: 30rpx;
font-weight: 600;
margin-top: 10rpx;
}
.sku_item{
.spec_name{
font-size: 26rpx;
margin-top: 20rpx;
}
.sku_tag{
display: flex;
flex-wrap: wrap;
font-size: 24rpx;
.tag{
display: flex;
align-items: center;
justify-content: center;
color: #000;
background-color: #f4f4f4;
margin-right: 22rpx;
margin-top: 20rpx;
padding: 10rpx 18rpx;
box-sizing: border-box;
border-radius: 6rpx;
.tag_img{
width: 35rpx;
height: 35rpx;
margin-right: 15rpx;
}
}
.dis{
opacity: 0.35;
}
.choose {
background-color: v-bind('bgColor');
color: v-bind('Color');
border-radius: 8rpx;
}
}
}
}
.sku_num{
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
.num{
font-size: 28rpx;
display: flex;
align-items: center;
.box_red{
padding: 5rpx 10rpx;
box-sizing: border-box;
font-size: 25rpx;
display: inline-block;
color: #656565;
}
}
}
.sku_buy{
display: flex;
align-items: center;
padding: 30rpx;
border-top: 1rpx solid #eee;
border-bottom: 1rpx solid #eee;
.img{
width: 50rpx;
height: 50rpx;
margin-left: -10rpx;
border: 4rpx solid #fff;
box-sizing: border-box;
.sku-buy-img {
width: 100%;
height: 100%;
border-radius: 10rpx;
}
}
.text{
margin-left: 20rpx;
font-size: 28rpx;
.num {
color: v-bind('Color');
}
}
}
}
}
.sku_btn{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
z-index: 2;
background-color: #fff;
display: flex;
.btn{
height: 104rpx;
color: #fff;
font-size: 30rpx;
text-align: center;
line-height: 104rpx;
background: v-bind('Color');
width: 100%;
}
.dis{
opacity: 0.7;
color: #eee;
}
}
}
.vip-tag {
font-size: 32rpx;
color: #fbe6c3;
background: #353648;
border-radius: 20rpx 0 0 0;
padding: 8rpx 10rpx;
line-height: 1;
.txt{
font-size: 22rpx;
}
}
</style>