You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
shop-app/pages/cart/components/PageCtx.vue

579 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>