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

558 lines
13 KiB

<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>
<view v-if="mode == 'normal'" class="head--edit" @click="handleToggleMode"></view>
<view v-else class="head--finish" @click="handleToggleMode"></view>
</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.push('/')"></view>
</BsEmpty>
<!-- 购物车商品列表 -->
<view v-else class="cart-list">
<view class="cart-item" v-for="(item, index) in list" :key="index"
@click="$Router.push(`goodsDetail?id=${item.id}`)">
<label class="cart-item--radio" @click.stop="handleCheckItem(item.id)">
<radio class="radio" color="#FF875B"
:checked="checkedIds.length ? checkedIds.includes(item.id) : false" />
</label>
<image class="cart-item--image" :src="item.product.mainPicture" mode="scaleToFill"></image>
<view class="cart-item--content">
<view class="cart-item--title"><text>{{ item.product.name }}</text></view>
<view class="cart-item--props">
<view class="cart-item--props-item" >
<text>{{ item.productSku.name }}</text>
</view>
</view>
<view class="cart-item--footer">
<view class="cart-item--price">
<text class="unit"></text>
<text class="value">{{ item.productSku.sellPrice }}</text>
</view>
<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>
</view>
</view>
</view>
<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"
:checked="checkedIds.length > 0 && checkedIds.length === list.length" />
<text>全选</text>
</label>
<view class="total-info">
<text>合计</text>
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ totalPrice }}</text>
</view>
</view>
<view class="cart-action">
<view class="btn-wrapper">
<!-- dev:下面的disabled条件使用checkedIds.join方式判断 -->
<!-- dev:通常情况下vue项目使用checkedIds.length更合理, 但是length属性在微信小程序中不起作用 -->
<view v-if="mode == 'normal'" class="btn-item btn-main" :class="{ disabled: checkedIds.length === 0 }"
@click="handleOrder()">
<text>去结算 {{ checkedIds.length > 0 ? `(${checkedIds.length})` : '' }}</text>
</view>
<view v-if="mode == 'edit'" class="btn-item btn-main" :class="{ disabled: !checkedIds.length }"
@click="handleDelete()">
<text>删除</text>
</view>
</view>
</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';
const CartIdsIndex = 'CartIds';
export default {
components: {
BsEmpty,
BsChoiceGoods,
UiPageHeader
},
props : {
type : {
type : String,
// 页面类型 inner内页 tabBar tabBar页面
default : 'inner'
}
},
data() {
return {
// 正在加载
isLoading: true,
// 当前模式: normal正常 edit编辑
mode: 'normal',
// 购物车商品列表
list: [],
// 购物车商品总数量
total: null,
// 选中的商品ID记录
checkedIds: [],
}
},
watch: {
// 监听选中的商品
checkedIds: {
handler(val) {
// 记录到缓存中
uni.setStorageSync(CartIdsIndex, val)
},
immediate: false
}
},
computed : {
totalPrice(){
const checkedList = this.list.filter(item => this.checkedIds.includes(item.id));
let tempPrice = 0;
checkedList.forEach(item => {
// 商品单价, 为了方便计算先转换单位为分 (整数)
const unitPrice = item.productSku.sellPrice * 100
tempPrice += unitPrice * item.number
});
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;
const {error, result} = await ApiGetCartList();
if(error){
uni.$u.toast(error.message);
return false;
}
this.list = result.map(item => {
const singleBuyLimit = item.product.singleBuyLimit;
const stock = item.productSku.stock;
const maxBuyNum = singleBuyLimit ? Math.min(singleBuyLimit, stock || 1) : stock
return {
...item,
maxBuyNum
}
});
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'
},
/**
* 监听步进器更改事件
*/
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) {
const { checkedIds } = this
const index = checkedIds.findIndex(id => id === cartId)
index < 0 ? checkedIds.push(cartId) : checkedIds.splice(index, 1)
},
/**
* 全选事件
*/
handleCheckAll() {
const { checkedIds, list } = this
this.checkedIds = checkedIds.length === list.length ? [] : list.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
}
uni.showModal({
title: '友情提示',
content: '您确定要删除该商品吗?',
showCancel: true,
success:async ()=> {
const {error} = await ApiDeleteCartGoods({idList : ids.join(',')});
if(error){
uni.$u.toast(error.message);
return false;
}
this.list = this.list.filter(item => !ids.includes(item.id));
// this.checkedIds = [];
this.onClearInvalidId();
}
})
}
}
}
</script>
<style>
page {
background: #f5f5f5;
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: #fff;
position: sticky;
top: var(--window-top);
z-index: 999;
&--totla{
font-size: 36rpx;
color: #333;
}
&--edit,&--finish{
font-size: 28rpx;
color: #999;
}
&--finish{
color: #FF512B;
}
&__tabBar{
justify-content: center;
.head--edit,.head--finish{
position: absolute;
right: 30rpx;
}
}
}
// 购物车列表
.cart-list {
background: #fff;
}
.cart-item {
width: 690rpx;
display: flex;
align-items: center;
padding: 30rpx 0;
margin: 0 auto 24rpx auto;
border-bottom: 1px solid #eee;
&--radio {
width: 56rpx;
height: 80rpx;
line-height: 80rpx;
margin-right: 10rpx;
text-align: center;
.radio {
transform: scale(0.76)
}
}
&--image {
width: 170rpx;
height: 170rpx;
display: block;
border-radius: 12rpx;
}
&--content {
flex: 1;
padding-left: 24rpx;
}
&--title {
font-size: 28rpx;
line-height: 39rpx;
max-height: 76rpx;
overflow: hidden;
text-overflow: ellipsis;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
}
&--props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
&-item {
display: inline-block;
margin-right: 14rpx;
}
}
&--footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
}
&--price {
vertical-align: bottom;
color: #FF512B;
.unit {
font-size: 20rpx;
}
.value {
font-size: 22rpx;
font-weight: bold;
}
}
&--stepper-icon{
font-size: 38rpx;
color: #ccc;
margin:0 14rpx;
}
&--stepper-input{
height:40rpx;
margin: 0 14rpx;
}
}
// 空数据按钮
.empty{
background: #fff;
&--btn {
width: 220rpx;
margin: 0 auto 10rpx auto;
font-size: 28rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
color: #333;
border-radius: 50rpx;
border: 1px solid rgb(192, 185, 185);
}
&--icon{
width: 304rpx;
height: 192rpx;
}
}
.title{
font-size: 32rpx;
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, #CCCCCC 0%, rgba(204, 204, 204, 0) 100%);
}
&::before{
background: linear-gradient(270deg, #CCCCCC 0%, rgba(204, 204, 204, 0) 100%);
}
}
// 底部操作栏
.footer {
display: flex;
align-items: center;
height: 96rpx;
background: #fff;
padding: 0 30rpx;
position: fixed;
bottom: 120rpx;
left: 0;
right: 0;
z-index: 11;
&__inner{
bottom: 0;
}
.all-radio {
width: 140rpx;
display: flex;
align-items: center;
color: #666;
font-size: 24rpx;
.radio {
margin-bottom: -4rpx;
transform: scale(0.76)
}
}
.total-info {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 30rpx;
font-size: 24rpx;
color: #666;
.goods-price {
vertical-align: bottom;
color: #FF875B;
.unit {
font-size: 24rpx;
}
.value {
font-size: 32rpx;
}
}
}
.cart-action {
width: 200rpx;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 72rpx;
line-height: 72rpx;
text-align: center;
color: #fff;
border-radius: 50rpx;
}
// 立即购买按钮
.btn-main {
background: #FF875B;//linear-gradient(to right, #f9211c, #ff6335);
// 禁用按钮
&.disabled {
background: rgba($color: #FF875B, $alpha: .7);
}
}
}
}
</style>