|
|
<template>
|
|
|
<view class="container">
|
|
|
<!-- 页面顶部 -->
|
|
|
<UiPageHeader :back="type == 'inner'" :class="`head__${type}`">
|
|
|
<template slot="custom">
|
|
|
<view class="head--title">
|
|
|
<text>购物车</text>
|
|
|
<text v-if="list.length">({{list.length}})</text>
|
|
|
</view>
|
|
|
<template v-if="list.length">
|
|
|
<view v-if="mode == 'normal'" class="head--edit" @click="handleToggleMode">编辑</view>
|
|
|
<view v-else class="head--finish" @click="handleToggleMode">完成</view>
|
|
|
</template>
|
|
|
</template>
|
|
|
</UiPageHeader>
|
|
|
|
|
|
<u-loadmore v-if="isLoading" status="loading" />
|
|
|
<!-- 购物车数据为空 -->
|
|
|
<BsEmpty v-if="!list.length && !isLoading" class="empty"
|
|
|
tips="去挑点喜欢的好货吧~">
|
|
|
<image slot="icon" class="empty--icon" src="@/static/goods/cart.png"></image>
|
|
|
<view slot="btn" class="empty--btn" @click="$Router.pushTab('/')">去逛逛</view>
|
|
|
</BsEmpty>
|
|
|
|
|
|
<!-- 购物车商品列表 -->
|
|
|
<template v-else >
|
|
|
<UiWhiteBox class="cart-item" v-for="(item, index) in list" :key="index" >
|
|
|
<label class="cart-item--radio" @click.stop="handleCheckItem(item.id, item.status)">
|
|
|
<radio class="radio" color="#FF875B"
|
|
|
:checked="checkedIds.length ? checkedIds.includes(item.id) : false"
|
|
|
:disabled="item.status !== 'normal'"/>
|
|
|
</label>
|
|
|
<image class="cart-item--image" :src="item.product.mainPicture" mode="scaleToFill"
|
|
|
@click="$Router.push(`/goodsDetail?id=${item.productId}`)"></image>
|
|
|
<view class="cart-item--content" @click="$Router.push(`/goodsDetail?id=${item.productId}`)">
|
|
|
<view class="cart-item--title"
|
|
|
:class="(item.status !== 'normal' || !item.productSku) && 'cart-item--title__disabled'">
|
|
|
{{ item.product.name }}</view>
|
|
|
<view v-if="item.status === 'notSku' || !item.productSku" class="cart-item--props">请重新选择商品规格</view>
|
|
|
<view v-if="item.status === 'isDisable'" class="cart-item--props">宝贝已失效,暂时无法购买</view>
|
|
|
<template v-if="item.status === 'normal' && item.productSku">
|
|
|
<view class="cart-item--props">{{ item.productSku.name }}</view>
|
|
|
<view class="cart-item--footer">
|
|
|
<UiMoney class="cart-item--price" :money="item.productSku.sellPrice" prefix></UiMoney>
|
|
|
<view class="cart-item--stepper">
|
|
|
<u-number-box :min="1" button-size="40rpx" bgColor="#F5F6FA"
|
|
|
:value="item.number" :max="item.maxBuyNum" @change="onChangeStepper($event, item)" >
|
|
|
<text slot="minus" class="cart-item--stepper-icon">-</text>
|
|
|
<text slot="plus" class="cart-item--stepper-icon">+</text>
|
|
|
</u-number-box>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
<view v-if="item.status === 'notSku'">
|
|
|
<UiButton size="small" class="cart-item--reset-btn" type="line">重新选择</UiButton>
|
|
|
</view>
|
|
|
</view>
|
|
|
</UiWhiteBox>
|
|
|
</template>
|
|
|
|
|
|
<template v-if="!isLoading">
|
|
|
<view class="title">为您推荐</view>
|
|
|
<BsChoiceGoods></BsChoiceGoods>
|
|
|
</template>
|
|
|
|
|
|
<!-- 底部操作栏 -->
|
|
|
<view v-if="list.length" :class="`footer footer__${type}`">
|
|
|
<label class="all-radio" @click="handleCheckAll">
|
|
|
<radio class="radio" color="#FF875B" :disabled="list && list.filter(i => i.status == 'normal').length == 0"
|
|
|
:checked="checkedIds.length > 0 && checkedIds.length === list.filter(i => i.status == 'normal').length" />
|
|
|
<text>全选</text>
|
|
|
</label>
|
|
|
<view class="total-info" v-if="mode == 'normal'">
|
|
|
<text>合计:</text>
|
|
|
<UiMoney class="goods-price" :money="totalPrice" prefix/>
|
|
|
</view>
|
|
|
<view class="cart-action">
|
|
|
<UiButton v-if="mode == 'edit'" size="max" :disable="!checkedIds.length" @click="handleDelete">删除({{checkedIds.length}})</UiButton>
|
|
|
<UiButton v-else type="gradual" size="max" :disable="!checkedIds.length" @click="handleOrder">去结算({{checkedIds.length}})</UiButton>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { Debounce } from '@/common/utils';
|
|
|
import BsEmpty from '@/components/BsEmpty.vue';
|
|
|
import BsChoiceGoods from '@/components/BsChoiceGoods.vue';
|
|
|
import UiPageHeader from '@/components/UiPageHeader.vue';
|
|
|
import {ApiGetCartList, ApiPutCartNum, ApiDeleteCartGoods} from '@/common/api/cart';
|
|
|
import UiMoney from '../../../components/UiMoney.vue';
|
|
|
import UiButton from '../../../components/UiButton.vue';
|
|
|
import UiWhiteBox from '../../../components/UiWhiteBox.vue';
|
|
|
|
|
|
const CartIdsIndex = 'CartIds';
|
|
|
|
|
|
export default {
|
|
|
components: {
|
|
|
BsEmpty,
|
|
|
BsChoiceGoods,
|
|
|
UiPageHeader,
|
|
|
UiMoney,
|
|
|
UiButton,
|
|
|
UiWhiteBox
|
|
|
},
|
|
|
props : {
|
|
|
type : {
|
|
|
type : String,
|
|
|
// 页面类型 inner内页 tabBar tabBar页面
|
|
|
default : 'inner'
|
|
|
}
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
// 正在加载
|
|
|
isLoading: true,
|
|
|
// 当前模式: normal正常 edit编辑
|
|
|
mode: 'normal',
|
|
|
// 购物车结算模式时商品列表,商品有notSku无SDK库存,正常normal 下架isDisable
|
|
|
settlementList : [],
|
|
|
// 购物车删除模式时的列表 ,所有商品状态为正常
|
|
|
delList : [],
|
|
|
// 购物车商品总数量
|
|
|
total: null,
|
|
|
// 选中的商品ID记录
|
|
|
checkedIds: [],
|
|
|
}
|
|
|
},
|
|
|
watch: {
|
|
|
// 监听选中的商品
|
|
|
checkedIds: {
|
|
|
handler(val) {
|
|
|
// 记录到缓存中
|
|
|
uni.setStorageSync(CartIdsIndex, val)
|
|
|
},
|
|
|
immediate: false
|
|
|
}
|
|
|
},
|
|
|
computed : {
|
|
|
// 根据模式最终显示的商品列表
|
|
|
list(){
|
|
|
return (this.mode === 'normal' ? this.settlementList : this.delList) || [];
|
|
|
},
|
|
|
totalPrice(){
|
|
|
const checkedList = this.list.filter(item => this.checkedIds.includes(item.id)) || [];
|
|
|
let tempPrice = 0;
|
|
|
checkedList.forEach(item => {
|
|
|
// 商品单价, 为了方便计算先转换单位为分 (整数)
|
|
|
const unitPrice = item.productSku ? item.productSku.sellPrice * 100 : 0
|
|
|
tempPrice += unitPrice * (item.number || 0)
|
|
|
});
|
|
|
return (tempPrice / 100).toFixed(2);
|
|
|
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
/**
|
|
|
* 组件缺少onShow onLoad生命周期,提供一个open方法给页面及组件使用
|
|
|
*/
|
|
|
open(options) {
|
|
|
|
|
|
// 获取购物车商品列表
|
|
|
this.getCartList();
|
|
|
this.isLoading = false;
|
|
|
// 获取缓存中的选中记录
|
|
|
this.checkedIds = uni.getStorageSync(CartIdsIndex) || [];
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 获取购物车商品列表
|
|
|
*/
|
|
|
async getCartList() {
|
|
|
this.isLoading = true;
|
|
|
// APP有Bug到拦截器里跳转不了 待研究问题
|
|
|
if(!this.$store.state.token){
|
|
|
uni.redirectTo({url: '/login'});
|
|
|
}
|
|
|
const {error, result} = await ApiGetCartList();
|
|
|
if(error){
|
|
|
uni.$u.toast(error.message);
|
|
|
return false;
|
|
|
}
|
|
|
this.settlementList = result.map(item => {
|
|
|
const singleBuyLimit = item.product.singleBuyLimit;
|
|
|
const stock = item.productSku && item.productSku.stock;
|
|
|
const maxBuyNum = singleBuyLimit ? Math.min(singleBuyLimit, stock || 1) : stock
|
|
|
return {
|
|
|
...item,
|
|
|
status : item.product.isEnable ?
|
|
|
(!item.productSku || item.productSku.stock == 0) ? 'notSku': 'normal' :
|
|
|
'isDisable',
|
|
|
maxBuyNum
|
|
|
}
|
|
|
});
|
|
|
this.delList = result.map(item => {
|
|
|
return {
|
|
|
...item,
|
|
|
status : 'normal'
|
|
|
}
|
|
|
})
|
|
|
this.onClearInvalidId();
|
|
|
this.isLoading = false;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 清除checkedIds中无效的ID
|
|
|
*/
|
|
|
onClearInvalidId() {
|
|
|
this.checkedIds = this.checkedIds.filter(i => this.list.findIndex(item => item.id === i) > -1);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 切换当前模式
|
|
|
*/
|
|
|
handleToggleMode() {
|
|
|
this.mode = this.mode == 'normal' ? 'edit' : 'normal';
|
|
|
if(this.mode == 'normal'){
|
|
|
this.checkedIds = this.checkedIds.filter(i => {
|
|
|
return this.list.find(item => {
|
|
|
return item.status === 'normal' && item.id === i;
|
|
|
}) ? true : false
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 监听步进器更改事件
|
|
|
*/
|
|
|
onChangeStepper({ value }, item) {
|
|
|
|
|
|
// 这里是组织首次启动时的执行
|
|
|
if (item.number == value) {
|
|
|
uni.$u.toast('数量不能再少了');
|
|
|
return false
|
|
|
}
|
|
|
// 记录一个节流函数句柄
|
|
|
if (!item.debounceHandle) {
|
|
|
item.oldValue = item.number
|
|
|
item.debounceHandle = Debounce(this.updateCartNum, 500)
|
|
|
}
|
|
|
// 更新商品数量
|
|
|
item.number = value
|
|
|
// 提交更新购物车数量 (节流)
|
|
|
item.debounceHandle(item, item.oldValue, value);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 提交更新购物车数量
|
|
|
*/
|
|
|
async updateCartNum(item, oldValue, newValue) {
|
|
|
const {error, result} = await ApiPutCartNum({
|
|
|
id : item.id,
|
|
|
number : item.number
|
|
|
});
|
|
|
if(error){
|
|
|
this.$toast(error.message);
|
|
|
item.number = item.sku;
|
|
|
return false;
|
|
|
}
|
|
|
if(result.isBeyondMaxLimit){
|
|
|
uni.$u.toast('数量超出范围');
|
|
|
item.number = result.canSetShoppingCartNumber;
|
|
|
return false;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 选中商品
|
|
|
*/
|
|
|
handleCheckItem(cartId, status) {
|
|
|
const { checkedIds } = this
|
|
|
const index = checkedIds.findIndex(id => id === cartId)
|
|
|
index < 0 ? checkedIds.push(cartId) : checkedIds.splice(index, 1)
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 全选事件
|
|
|
* 取到当前列表状态正常的商品列表。
|
|
|
* 如果选中IDS等于正常状态列表,则判断为取消全部
|
|
|
* 否则选中全部正常状态商品
|
|
|
*/
|
|
|
handleCheckAll() {
|
|
|
const { checkedIds, list } = this;
|
|
|
const normalList = list.filter(i => i.status == 'normal');
|
|
|
if(normalList.length == 0){
|
|
|
return false;
|
|
|
}
|
|
|
this.checkedIds = checkedIds.length === normalList.length ? [] :
|
|
|
normalList.map(item => item.id);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 结算选中的商品
|
|
|
*/
|
|
|
handleOrder() {
|
|
|
const ids = this.checkedIds;
|
|
|
if (ids.length) {
|
|
|
this.$Router.push({
|
|
|
path : '/orderSubmit',
|
|
|
query : {
|
|
|
mode: 'cart',
|
|
|
ids:ids.join(',')
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 删除选中的商品
|
|
|
*/
|
|
|
handleDelete() {
|
|
|
const ids = this.checkedIds;
|
|
|
if (!ids.length) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
this.$msb.confirm({
|
|
|
content : '您确定要删除该商品吗?',
|
|
|
confirm : async ()=>{
|
|
|
const {error} = await ApiDeleteCartGoods({idList : ids.join(',')});
|
|
|
if(error){
|
|
|
uni.$u.toast(error.message);
|
|
|
return false;
|
|
|
}
|
|
|
this.delList = this.delList.filter(item => !ids.includes(item.id));
|
|
|
this.settlementList = this.settlementList.filter(item => !ids.includes(item.id));
|
|
|
|
|
|
this.checkedIds = [];
|
|
|
this.onClearInvalidId();
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss">
|
|
|
page {
|
|
|
background: $color-grey1;
|
|
|
padding-bottom: 240rpx;
|
|
|
|
|
|
}
|
|
|
</style>
|
|
|
<style lang="scss" scoped>
|
|
|
// 页面顶部
|
|
|
.head {
|
|
|
height: 88rpx;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 0 30rpx;
|
|
|
background-color: $color-grey0;
|
|
|
position: sticky;
|
|
|
top: var(--window-top);
|
|
|
z-index: 999;
|
|
|
&--title text{
|
|
|
font-size: 36rpx;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
&--totla{
|
|
|
font-size: 36rpx;
|
|
|
color: $color-grey6;
|
|
|
}
|
|
|
&--edit,&--finish{
|
|
|
font-size: $font-size-base;
|
|
|
color: $color-grey4;
|
|
|
}
|
|
|
&--finish{
|
|
|
color: $color-yellow4;
|
|
|
}
|
|
|
&__tabBar{
|
|
|
justify-content: center;
|
|
|
.head--edit,.head--finish{
|
|
|
position: absolute;
|
|
|
right: 30rpx;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 购物车列
|
|
|
.cart-item {
|
|
|
width: 690rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 50rpx 40rpx 40rpx 24rpx;
|
|
|
margin: 0 auto 20rpx;
|
|
|
&--radio {
|
|
|
width: 56rpx;
|
|
|
height: 80rpx;
|
|
|
line-height: 80rpx;
|
|
|
margin-right: 10rpx;
|
|
|
text-align: center;
|
|
|
}
|
|
|
&--image {
|
|
|
width: 170rpx;
|
|
|
height: 170rpx;
|
|
|
display: block;
|
|
|
border-radius: 12rpx;
|
|
|
}
|
|
|
|
|
|
&--content {
|
|
|
flex: 1;
|
|
|
padding-left: 24rpx;
|
|
|
}
|
|
|
|
|
|
&--title {
|
|
|
font-size: $font-size-base;
|
|
|
line-height: 36rpx;
|
|
|
font-weight: bold;
|
|
|
max-height: 76rpx;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
display:-webkit-box;
|
|
|
-webkit-box-orient:vertical;
|
|
|
-webkit-line-clamp:2;
|
|
|
&__disabled{
|
|
|
color: $color-grey3;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
&--props {
|
|
|
margin-top: 14rpx;
|
|
|
height: 40rpx;
|
|
|
color: $color-grey4;
|
|
|
font-size: $font-size-sm;
|
|
|
overflow: hidden;
|
|
|
|
|
|
&-item {
|
|
|
display: inline-block;
|
|
|
margin-right: 14rpx;
|
|
|
}
|
|
|
}
|
|
|
&--footer {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-top: 20rpx;
|
|
|
}
|
|
|
&--stepper-icon{
|
|
|
font-size: 38rpx;
|
|
|
color: $color-grey3;
|
|
|
margin:0 14rpx;
|
|
|
}
|
|
|
&--stepper-input{
|
|
|
height:40rpx;
|
|
|
margin: 0 14rpx;
|
|
|
}
|
|
|
&--reset-btn{
|
|
|
margin-top: 22rpx;
|
|
|
float: right;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 空数据按钮
|
|
|
.empty{
|
|
|
background: $color-grey0;
|
|
|
&--btn {
|
|
|
width: 220rpx;
|
|
|
margin: 0 auto 10rpx auto;
|
|
|
font-size: $font-size-base;
|
|
|
height: 64rpx;
|
|
|
line-height: 64rpx;
|
|
|
text-align: center;
|
|
|
color: $color-grey6;
|
|
|
border-radius: 50rpx;
|
|
|
border: 1px solid rgb(192, 185, 185);
|
|
|
}
|
|
|
&--icon{
|
|
|
width: 304rpx;
|
|
|
height: 192rpx;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.title{
|
|
|
font-size: $font-size-lg;
|
|
|
text-align: center;
|
|
|
margin: 51rpx auto 30rpx auto;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
width: 500rpx;
|
|
|
&::after,&::before{
|
|
|
display: inline-block;
|
|
|
content: '';
|
|
|
width: 160rpx;
|
|
|
height: 2rpx;
|
|
|
background: linear-gradient(90deg, $color-grey3 0%, rgba(204, 204, 204, 0) 100%);
|
|
|
|
|
|
}
|
|
|
&::before{
|
|
|
|
|
|
background: linear-gradient(270deg, $color-grey3 0%, rgba(204, 204, 204, 0) 100%);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 底部操作栏
|
|
|
.footer {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
height: 140rpx;
|
|
|
background: $color-grey0;
|
|
|
padding: 0 30rpx;
|
|
|
position: fixed;
|
|
|
bottom: 0;
|
|
|
/* #ifdef H5 */
|
|
|
bottom: 50px;
|
|
|
/* #endif */
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
z-index: 11;
|
|
|
margin-bottom: constant(safe-area-inset-bottom);
|
|
|
margin-bottom: env(safe-area-inset-bottom);
|
|
|
&__inner{
|
|
|
bottom: 0;
|
|
|
}
|
|
|
.all-radio {
|
|
|
width: 140rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
text{
|
|
|
color: $color-grey5;
|
|
|
font-size: $font-size-base;
|
|
|
}
|
|
|
|
|
|
.radio {
|
|
|
margin-right: 5rpx;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.total-info {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: flex-end;
|
|
|
padding-right: 30rpx;
|
|
|
text{
|
|
|
font-size: $font-size-base;
|
|
|
color: $color-grey5;
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
/deep/{
|
|
|
.cart-item--price{
|
|
|
text{
|
|
|
font-size: $font-size-lg;
|
|
|
color: $color-yellow4;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
.ui-money--prefix{
|
|
|
font-size: $font-size-sm;
|
|
|
}
|
|
|
}
|
|
|
.uni-radio-input{
|
|
|
width: 36rpx;
|
|
|
height: 36rpx;
|
|
|
}
|
|
|
.goods-price{
|
|
|
margin-bottom: 10rpx;
|
|
|
text{
|
|
|
font-size: 40rpx;
|
|
|
color: $color-yellow4;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
.ui-money--prefix{
|
|
|
font-size: $font-size-lg;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</style>
|