Files
xdd-uniapp-new/pages/cart.vue
T

926 lines
25 KiB
Vue

<template>
<view
:style="{
'padding-top': (statusBarHeight + 54) + 'px'
}"
class="pages-cart tabbar-page"
>
<u-navbar
:left-icon="selectCount > 0 ? '' : ''"
title="购物车"
fixed
/>
<view v-if="isLoad">
<view
v-if="list.length === 0"
class="tc"
style="margin-top: 300rpx"
>
<image
:src="$VUE_APP_RESOURCES_URL + '/20240106211138206789.png'"
style="width: 420rpx;height: 418rpx"
mode="scaleToFill"
/>
</view>
<view class="v12-justify-between v12-px-2">
<view class="v12-font-28 v12-dark-text v12-align-center" @click="show = true">
<image :src="webUrl+'/orderIcon/map.png'" style="width: 30rpx;height: 30rpx" class="v12-mr-1"/>
<text>{{ addressInfo.city }}</text>
<view class="v12-ml-1" style="margin-top: 2px;">
<u-icon name="arrow-down"></u-icon>
</view>
</view>
<view class="v12-font-28 v12-font-bold v12-dark-text" @click="editCart">{{ isEdit ? '完成' : '管理' }}</view>
</view>
<view
v-for="(item, index) in list"
:key="index"
class="group-item"
>
<view class="flex jc-between">
<view class="flex ai-center mer-left">
<!-- <CheckboxIcon
:default-state="item['state']"
:mark="[index]"
style="margin-right: 24rpx;"
@change="onChange"
/> -->
<image
:src="item['merAvatar']"
class="store-cover"
mode="scaleToFill"
/>
<view class="mer-name v12-font-24 v12-font-bold">{{ item['merName'] }}</view>
</view>
<view
class="flex ai-center hotle-btn v12-primary-text"
@click="navToStore(item['hotelId'])"
>
进店逛逛>
<!-- <image
:src="$VUE_APP_RESOURCES_URL + '/20240109234706982937.png'"
style="width: 24rpx;height: 24rpx"
mode="scaleToFill"
/> -->
</view>
</view>
<view class="good-box">
<view
v-for="(good, gIndex) in item['carts']"
:key="gIndex"
class="good flex v12-align-start"
>
<view class="v12-align-center">
<CheckboxIcon
:default-state="good['state']"
:mark="[index, gIndex]"
style="margin-right: 24rpx;"
@change="onChange"
/>
<image
v-if="good['productInfo']['attrInfo']['image']"
:src="good['productInfo']['attrInfo']['image']"
class="good-cover flex-0 v12-radius-16"
mode="scaleToFill"
@click="navToGood(good)"
/>
<image
v-else
:src="good['productInfo']['image']"
mode="scaleToFill"
class="good-cover flex-0"
@click="navToGood(good)"
/>
</view>
<view style="width: 100%">
<view
class="one-t v12-font-28"
@click="navToGood(good)"
>{{ good['productInfo']['storeName'] }}</view>
<view
class="sku ai-center"
@click="changeGoodAttrHandle(good)"
>
规格:{{ good['productInfo']['attrInfo']['sku'] }}
</view>
<!-- <view class="v12-mt-1 v12-align-center">
<view class="v12-primary-text v12-primary-border v12-radius-8 v12-font-24 v12-px-1">
<text>券</text>
</view>
<view class="v12-primary-text v12-primary-border v12-radius-8 v12-font-24 v12-px-1">
<text>满100-30</text>
</view>
</view> -->
<view
class="flex jc-between ai-center"
style="width: 100%;margin-top: 4rpx;"
>
<view
class="v12-font-bold v12-primary-text"
@click="navToGood(good)"
>
<text class="bold v12-font-22">¥</text>
<text class="v12-font-36">{{ good['truePrice'] }}</text>
</view>
<u-number-box
:value="good.cartNum"
:max="good['productInfo']['attrInfo']['stock']"
integer
disabledInput
iconStyle="color: #fff"
class="num-step"
@change="e => changeNumHandle(index, gIndex, good, e)"
>
<template v-slot:minus>
<view class="v12-minus-btn v12-font-bold flex jc-center ai-center">
<u-icon name="minus" size="12" color="#FFFFFF"/>
</view>
</template>
<template v-slot:input>
<view class="num-input tc v12-num-input">{{ good['cartNum'] }}</view>
</template>
<template v-slot:plus>
<view class="v12-plus-btn v12-font-bold flex jc-center ai-center v12-primary">
<u-icon name="plus" size="12" color="#FFFFFF"/>
</view>
</template>
</u-number-box>
</view>
</view>
</view>
</view>
</view>
<view class="footer-fixed flex jc-between ai-center">
<view style="margin-left: 32rpx;">
<CheckboxIcon
style="margin-right: 24rpx;"
:default-state="isAll"
:mark="[-1]"
@change="onChange"
/>
<text class="v12-dark-text v12-font-32 v12-font-bold">全选({{ selectCount }})</text>
</view>
<view class="flex ai-center" v-if="!isEdit">
<view >
<text class="v12-font-24 v12-dark-text v12-font-weight-550"> 合计:</text>
<text class="v12-font-22 v12-primary-text v12-font-bold">¥</text>
<text class="v12-font-26 v12-primary-text v12-font-bold">{{ total }}</text>
</view>
<view class="btn-submit v12-mx-3 flex jc-center ai-center v12-radius-80 v12-primary" @click="submit">结算</view>
</view>
<view class="flex ai-center" v-else>
<view @click="clearCart" class="v12-justify-between v12-align-center">
<view style="margin-top: 1px"><u-icon name="trash"></u-icon> </view>
<view>清空</view>
</view>
<view @click="remove" class="btn-submit v12-mx-3 flex jc-center ai-center v12-radius-80 v12-primary">删除</view>
</view>
</view>
<view
v-show="showAttr"
class="good-attr-wrap"
>
<view class="layer" @click="closeAttr" />
<view class="content">
<view class="close">
<text class="iconfont icon-guanbi" @click="closeAttr"></text>
</view>
<view class="good-info">
<view class="name-img">
<image :src="currGoodAttr.image" class="img" />
<view class="name">
<view class="more-t">
{{ currGood.productInfo.storeName }}
</view>
<view class="price">
<text class="txt">¥</text>{{ currGoodAttr.price }}
</view>
</view>
</view>
</view>
<view class="attr-wrap">
<view
v-for="(item, index) in goodAttr.productAttr"
:key="index"
class="item"
>
<view class="title">{{ item.attrName }}</view>
<view class="acea-row row-middle">
<view
v-for="(attr, attrIndex) in item.attrValue"
:key="attr"
:class="{
'on': attr.check,
'disabled': getAttrItemData(attr.attr).stock === 0
}"
class="attr"
@click="attrItemClick(attr, attrIndex, index)"
>
{{ attr.attr }}
<text
v-if="getAttrItemData(attr.attr).stock === 0"
class="tag"
>缺货</text>
</view>
</view>
</view>
</view>
<view class="bottom">
<view
:class="currGoodAttr.stock === 0 ? 'disabled' : ''"
class="btn"
@click="changeAttrConfirm"
>
确认
</view>
</view>
</view>
</view>
</view>
<goo-skeleton
v-else
:show-avatar="false"
/>
<u-popup :show="show " @close="closePopup" closeable>
<view class="v12-secondary-dark-text v12-font-32 v12-text-center v12-py-4">收货地址</view>
<view class="v12-px-3 v12-mb-3 v12-font-32 v12-justify-start v12-align-center">
<view style="opacity: 0; width: 28px">
<image :src="webUrl+'/orderIcon/map.png'" style="width: 30rpx;height: 30rpx" class="v12-mr-1"/>
</view>
<view>收货人:{{ addressInfo.realName }} {{ addressInfo.phone }}</view>
</view>
<view class="v12-px-3 v12-mb-3 v12-font-28 v12-justify-between v12-align-start">
<view style="width: 30px;">
<image :src="webUrl+'/orderIcon/map.png'" style="width: 30rpx;height: 30rpx" class="v12-mr-1"/>
</view>
<view style="margin-bottom: 4px;width: 100%" class="more-t v12-text-left">{{ addressInfo.province }}{{ addressInfo.city }}{{ addressInfo.district }}{{ addressInfo.detail }}</view>
<view style="width: 50px;" @click="editAddress">
<image :src="webUrl+'/icon/icon-setting.png'" style="width: 30rpx;height: 30rpx" class="v12-mr-1"/>
</view>
</view>
<view class=" v12-px-3">
<view class="v12-py-2 v12-primary-text v12-font-28 v12-radius-60 v12-text-center plus-btn" @click="addNewAddress">+ 添加新地址</view>
</view>
</u-popup>
<xdd-tabbar :curr-index="2" />
</view>
</template>
<script>
import XddTabbar from '@/components/xdd-tabbar'
import {getAddressList} from "@/api/user";
import {
changeCartNum,
getCartGroup,
postCartDel,
getCartGoodAttr,
cartGoodChangeAttr
} from '@/api/store'
import CheckboxIcon from '@/components/CheckboxIcon.vue'
import { pageListenMixins } from '@/mixins/pageListenMixins'
export default {
components: {
CheckboxIcon,
XddTabbar
},
mixins: [pageListenMixins],
data() {
return {
webUrl: this.$VUE_APP_RESOURCES_URL,
show: false,
statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
isLoad: false,
list: [],
isAll: false,
currGood: {},
currGoodAttr: {},
goodAttr: {
productAttr: [],
productValueArr: []
},
showAttr: false,
pageKeyId: Object.freeze('landmarkGoodsIndex'),
isEdit: false,
addressList:[],
addressInfo: {}
}
},
computed: {
total() {
let price = 0
this.list.forEach(item => {
item['carts'].forEach(good => {
if (good.state) price += Math.round(good['cartNum'] * good['truePrice'] * 100) / 100
})
})
return Math.round(price * 100) / 100
},
selectCount() {
let count = 0
this.list.forEach(item => {
item['carts'].forEach(good => {
if (good.state) count += good.cartNum || 1
})
})
return count
}
},
created() {
this.getCart()
this.getAddress()
},
onLoad() {
uni.$on('chooseAddress', (res) => {
this.addressInfo = res;
})
},
onShow() {
if (!this.isLoad) {
return
}
this.closeAttr()
this.getCart()
},
onHide() {
this.pageToHideHandle()
},
methods: {
closePopup() {
this.show = false
},
editAddress() {
this.$yrouter.push({
path: "/pages/user/address/AddressManagement/index",
query: {
choosMode: 1
}
});
},
addNewAddress() {
this.$yrouter.push({
path: "/pages/user/address/AddAddress/index"
});
},
getAddress() {
getAddressList({page: 1, limit: 9999}).then(res => {
this.addressList = res.data
this.addressInfo = res.data.find(e => e.isDefault == 1) || {}
});
},
clearCart() {
const ids = this.list.map(l => {
return l.carts.map(g => g.id)
}).flat()
uni.showModal({
title: '确定清空购物车吗?',
content: '',
showCancel: true,
cancelText: '取消',
confirmText: '确认',
success: async (result) => {
if (result.confirm) {
uni.showLoading()
postCartDel(ids).then(res => {
const { success } = res
if (success) {
this.getCart()
}
}).finally(() => {
uni.hideLoading()
})
}
}
})
},
editCart() {
this.isEdit = !this.isEdit
},
async getCart() {
const res = await getCartGroup()
if (res.success) {
this.isLoad = true
this.list = res.data['valid']
this.handleAll(false)
}
},
handleAll(state) {
this.isAll = state
this.list = this.list.map(item => {
item.state = state
item.carts = item.carts.map(good => {
good.state = state
return good
})
return item
})
},
navToStore(id) {
if (id === 0) {
uni.showToast({
title: '店铺装修中~',
icon: 'none',
duration: 3000
})
return
}
uni.navigateTo({
url: '/pagesInn/inn/innHome?id=' + id
})
},
onChange(state, mark) {
if (mark.length === 1) {
if (mark[0] === -1) {
this.handleAll(state)
} else {
this.list[mark[0]]['state'] = state
this.list[mark[0]]['carts'] = this.list[mark[0]]['carts'].map(good => {
good.state = state
return good
})
}
}
if (mark.length === 2) {
this.list[mark[0]]['carts'][mark[1]]['state'] = state
const length = this.list[mark[0]]['carts'].length
let count = 0
this.list[mark[0]]['carts'] = this.list[mark[0]]['carts'].map((good, gIndex) => {
if (gIndex === mark[1]) good['state'] = state
if (good['state']) count++
return good
})
this.list[mark[0]]['state'] = count === length
}
let shopCheckNumber = 0
this.list.map(item => {
if (item.state) {
shopCheckNumber++
}
})
this.isAll = shopCheckNumber === this.list.length
},
changeNumHandle(index, gIndex, good, e) {
const { type } = e
const value = good.cartNum + (type === 'plus' ? 1 : -1)
const id = good.id
// uni.showLoading()
changeCartNum(id, value).then(res => {
const { success } = res
if (success) {
// uni.hideLoading()
this.$set(this.list[index].carts[gIndex], 'cartNum', value)
}
}).catch(() => {
setTimeout(() => {
uni.hideLoading()
}, 3000)
})
},
handleIds() {
if (this.selectCount === 0) {
uni.showToast({
title: '请选择产品',
icon: 'none',
duration: 2000
})
return []
}
const ids = []
this.list.forEach(item => {
item['carts'].forEach(good => {
if (good['state']) ids.push(good['id'])
})
})
return ids
},
remove() {
const ids = this.handleIds()
if (ids.length === 0) return
uni.showModal({
title: '确认删除已选商品?',
content: '',
showCancel: true,
cancelText: '取消',
confirmText: '确认',
success: async (result) => {
if (result.confirm) {
uni.showLoading()
postCartDel(ids).then(res => {
const { success } = res
if (success) {
this.getCart()
}
}).finally(() => {
uni.hideLoading()
})
}
}
})
},
changeGoodAttrHandle(good) {
if (good.seckillId) {
return
}
const id = good.productInfo.id
const unique = good.productInfo.attrInfo.unique
// uni.showLoading()
getCartGoodAttr(id, unique).then(res => {
const { success, data } = res
if (success) {
this.currGood = good
this.goodAttr.productAttr = data.productAttr
for (const key in data.productValue) {
this.goodAttr.productValueArr.push({
attrItemkey: key,
...data.productValue[key]
})
}
this.goodAttr.productAttr.map((item, index) => {
item.attrValue.map((attr, attrIndex) => {
attr.check = data.defaultSkuIndex[index] === attrIndex
})
})
this.currGoodAttr = data.productValue[data.defaultSku.sort().join(',')]
this.showAttr = true
}
}).finally(() => {
uni.hideLoading()
})
},
getAttrItemData(attr) {
let result = {
stock: 0
}
// 只有一组规格
if (this.goodAttr.productAttr.length === 1) {
this.goodAttr.productValueArr.map(item => {
if (item.attrItemkey === attr) {
result = item
}
})
} else {
result.stock = 1
}
return result
},
// 规格切换
attrItemClick(attr, attrIndex, index) {
const tempArr = JSON.parse(JSON.stringify(this.goodAttr))
tempArr.productAttr[index].attrValue.map(attrItem => {
attrItem.check = false
})
// 预设目标规格选中
tempArr.productAttr[index].attrValue[attrIndex].check = true
const arr = []
tempArr.productAttr.map((item) => {
item.attrValue.map((attrItem) => {
if (attrItem.check) {
// 将预选中的规格组合起来
arr.push(attrItem.attr)
}
})
})
if (arr.length > 0) {
const attrStr = arr.sort().join(',')
let targat = null
this.goodAttr.productValueArr.map(item => {
if (item.attrItemkey === attrStr) {
targat = item
}
})
if (targat) {
const stock = targat.stock
this.currGoodAttr = targat
this.goodAttr.productAttr[index].attrValue.map(attrItem => {
attrItem.check = false
})
this.goodAttr.productAttr[index].attrValue[attrIndex].check = true
if (stock === 0) {
uni.showToast({
title: '库存不足',
icon: 'none',
duration: 3000
})
}
this.$forceUpdate()
} else {
uni.showToast({
title: '库存不足',
icon: 'none',
duration: 3000
})
}
}
},
// 确认切换
changeAttrConfirm() {
if (this.currGoodAttr.stock === 0) {
return
}
uni.showLoading()
cartGoodChangeAttr({
id: this.currGood.id,
uniqueId: this.currGoodAttr.unique
}).then(res => {
const { success } = res
if (success) {
this.getCart()
this.closeAttr()
}
}).catch(() => {
uni.hideLoading()
}).finally(() => {
uni.hideLoading()
})
},
closeAttr() {
this.currGoodAttr = {}
this.currGood = {}
this.showAttr = false
},
navToGood(good) {
const query = {
id: good.productId
}
if (good.seckillId) {
query.activityId = good.seckillId
query.from = 'kill'
}
this.$yrouter.push({
path: '/pages/shop/GoodsCon/index',
query
})
},
submit() {
const ids = this.handleIds()
if (ids.length === 0) return
uni.navigateTo({
url: '/pages/order/OrderSubmission/index?id=' + ids.join(',') + '&address=' + JSON.stringify(this.addressInfo)
})
}
}
}
</script>
<style scoped lang="less">
.plus-btn{
background: rgba(197,39,51,0.2);
}
.v12-minus-btn{
border-radius: 28rpx 0rpx 0rpx 28rpx;
border: 1px solid #E6E6E6;
background-color: rgba(208,208,208,0.39);
height: 40rpx;
width: 40rpx;
padding: 0 20rpx;
font-weight: bold;
}
.v12-plus-btn{
border-radius: 0rpx 28rpx 28rpx 0rpx;
border: 1px solid #C52733;
background-color: #C52733;
height: 40rpx;
width: 40rpx;
padding: 0 20rpx;
font-weight: bold;
}
.v12-num-input{
height: 40rpx;
// width: 40rpx;
border: 1px solid #E6E6E6;
line-height: 40rpx;
font-weight: bold;
font-size: 28rpx;
color: #333;
display: flex;
align-items: center;
justify-content: center;
}
view {
box-sizing: border-box;
}
.pages-cart {
min-height: 100vh;
padding-bottom: 100rpx;
}
.group-item {
width: 710rpx;
margin: 12rpx 20rpx;
padding: 20rpx 8rpx 32rpx 20rpx;
background: #FFFFFF;
border-radius: 12rpx;
.store-cover {
width: 48rpx;
height: 48rpx;
margin-right: 8rpx;
border-radius: 50%;
}
.mer-left {
width: calc(100% - 140rpx);
}
.mer-name {
width: calc(100% - 104rpx);
color: #333;
font-size: 30rpx;
}
.hotle-btn {
justify-content: flex-end;
width: 140rpx;
color: #E92727;
font-size: 24rpx;
}
}
.good-box {
.good {
margin-top: 24rpx;
.good-cover {
width: 140rpx;
height: 140rpx;
margin-right: 16rpx;
border-radius: 16rpx;
}
.one-t {
width: 420rpx;
color: #333333;
font-size: 30rpx;
line-height: 42rpx;
font-weight: bold;
}
.sku {
display: inline-flex;
margin: 4rpx;
padding: 10rpx 20rpx;
border-radius: 60rpx;
background: rgba(240,240,240,0.6);
color: #666666;
font-size: 24rpx;
}
.num-btn {
width: 40rpx;
height: 40rpx;
background: rgba(236, 236, 238, 1);
// border: 2px solid rgba(236, 236, 238, 1);
border-radius: 40rpx;
}
.num-input {
width: 64rpx;
color: #333333;
font-size: 30rpx;
}
}
}
.footer-fixed {
position: fixed;
left: 0;
bottom: 180rpx;
width: 100vw;
height: 100rpx;
box-shadow: 0 6rpx 12rpx rgba(0, 0, 0, 0.16);
background: #FFFFFF;
.btn-submit {
width: 196rpx;
height: 64rpx;
background: #C52733;
color: #FFFFFF;
font-size: 32rpx;
}
}
.good-attr-wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
.layer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
background-color: rgba(0, 0, 0, 0.5);
}
.content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
max-height: 75vh;
padding: 30rpx;
border-top-left-radius: 30rpx;
border-top-right-radius: 30rpx;
box-sizing: border-box;
background-color: #fff;
overflow-y: auto;
.close {
position: absolute;
top: 30rpx;
right: 30rpx;
.iconfont {
font-size: 16px;
color: #666;
}
}
.good-info {
padding: 20rpx 0 0 0;
.name-img {
display: flex;
.img {
display: block;
width: 160rpx;
height: 160rpx;
margin: 0 20rpx 0 0;
}
.name {
width: calc(100% - 220rpx);
color: #333;
font-size: 16px;
}
.price {
margin: 20rpx 0 0 0;
color: #E92727;
font-size: 16px;
.txt{
font-size: 12px;
}
}
}
}
.attr-wrap {
padding: 30rpx 0 0 0;
.item + .item {
margin: 20rpx 0 0 0;
}
.title {
margin: 0 0 10rpx 0;
color: #999;
font-size: 28rpx;
}
.attr {
position: relative;
padding: 7rpx 33rpx;
margin: 14rpx 14rpx 0 0;
border: 1rpx solid #bbb;
border-radius: 6rpx;
color: #333;
font-size: 26rpx;
&.on {
color: #fff;
border-color: #eb3729;
background-color: #eb3729;
}
&.disabled {
color: #333;
border-color: #f1f1f1;
background-color: #f1f1f1;
.tag {
position: absolute;
top: -18upx;
right: -18upx;
height: 28upx;
padding: 0 6upx;
border-radius: 6upx;
color: #fff;
line-height: 28upx;
font-size: 10px;
background-color: #FD5749;
}
}
}
}
.bottom {
padding: 30rpx 0 0 0;
.btn {
height: 80rpx;
border-radius: 40rpx;
line-height: 80rpx;
text-align: center;
color: #fff;
font-size: 16px;
background-color: #E92727;
&.disabled {
color: #666;
background-color: #f1f1f1;
}
}
}
}
}
.tabbar-page {
padding: 0 0 180rpx 0;
}
</style>