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.
 
 
 
 
 
xueshan/pages/cart/index.vue

718 lines
17 KiB

<template>
<view class="container">
<!-- 页面顶部 -->
<view v-if="list.length" class="head-info">
<view class="cart-total">
<text>共</text>
<text class="active">{{ total }}</text>
<text>件商品</text>
</view>
<view class="cart-edit" @click="handleToggleMode">
<view v-if="mode == 'normal'" class="normal">
<text class="icon iconfont icon-bianji"></text>
<text>编辑</text>
</view>
<view v-if="mode == 'edit'" class="edit">
<text>完成</text>
</view>
</view>
</view>
<!-- 购物车商品列表 -->
<view v-if="list.length" class="cart-list">
<view class="cart-item" v-for="(item, index) in list" :key="index">
<label class="item-radio" @click.stop="handleCheckItem(item.id)">
<radio class="radio" color="#17C161" :checked="inArray(item.id, checkedIds)" />
</label>
<view class="goods-image" @click="onTargetGoods(item.goods_id)">
<image class="image" :src="item.goods.goods_image" mode="scaleToFill"></image>
</view>
<view class="item-content">
<view class="goods-title" @click="onTargetGoods(item.goods_id)">
<text class="twoline-hide">{{ item.goods.goods_name }}</text>
</view>
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.goods.skuInfo.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
<view class="item-foot">
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ item.goods.skuInfo.goods_price }}</text>
</view>
<view class="stepper">
<u-number-box :min="1" :value="item.goods_num" :step="1" @change="onChangeStepper($event, item)" />
</view>
</view>
</view>
</view>
</view>
<!-- 购物车数据为空 -->
<empty v-if="!list.length" :isLoading="isLoading" :custom-style="{ padding: '180rpx 50rpx' }" tips="您的购物车是空的, 快去逛逛吧">
<view slot="slot" class="empty-ipt" @click="onTargetIndex">
<text>去逛逛</text>
</view>
</empty>
<!-- 推荐商品列表 -->
<view slot="slot" class="empty-ipt2" >
<text> - 热门推荐 - </text>
</view>
<view v-if="goodslist.data.length" class="goods-list clearfix" :class="['column-' + (showView ? '1' : '2')]">
<view class="goods-item" v-for="(item, index) in goodslist.data" :key="index" @click="onTargetDetail(item.goods_id)">
<!-- 多列显示 -->
<view class="">
<!-- 商品图片 -->
<view class="goods-image">
<image class="image" mode="aspectFill" :src="item.goods_image"></image>
</view>
<view class="detail">
<!-- 商品标题 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods_name }}</text>
</view>
<!-- 商品价格 -->
<view class="detail-price oneline-hide">
<text class="goods-price f-30 col-m">¥{{ item.goods_price_min }}</text>
<text v-if="item.line_price_min > 0" class="line-price col-9 f-24">¥{{ item.line_price_min }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view v-if="list.length" class="footer-fixed">
<label class="all-radio" @click="handleCheckAll">
<radio class="radio" color="#17C161" :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.join() == '' }" @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 { inArray, arrayIntersect, debounce } from '@/utils/util'
import { checkLogin, setCartTotalNum, setCartTabBadge } from '@/core/app'
import * as CartApi from '@/api/cart'
import * as GoodsApi from '@/api/goods'
//import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import Empty from '@/components/empty'
const CartIdsIndex = 'CartIds'
export default {
components: {
Empty
},
data() {
return {
inArray,
// 正在加载
isLoading: true,
// 当前模式: normal正常 edit编辑
mode: 'normal',
// 购物车商品列表
list: [],
goodslist: [], // 商品列表数据
goodstotal:0,
// 购物车商品总数量
total: null,
// 选中的商品ID记录
checkedIds: [],
// 选中的商品总金额
totalPrice: '0.00'
}
},
watch: {
// 监听选中的商品
checkedIds: {
handler(val) {
// 计算合计金额
this.onCalcTotalPrice()
// 记录到缓存中
uni.setStorageSync(CartIdsIndex, val)
},
immediate: false
},
// 监听购物车商品总数量
total(val) {
// 缓存并设置角标
setCartTotalNum(val)
setCartTabBadge()
}
},
/**
* 生命周期函数--监听页面显示
*/
onShow(options) {
// 获取购物车商品列表
checkLogin() ? this.getCartList() : this.isLoading = false
// 获取缓存中的选中记录
this.checkedIds = uni.getStorageSync(CartIdsIndex)
//获取推荐商品列表
this.getGoodsList()
},
methods: {
// 计算合计金额 (根据选中的商品)
onCalcTotalPrice() {
const app = this
// 选中的商品记录
const checkedList = app.list.filter(item => inArray(item.id, app.checkedIds))
// 计算总金额
let tempPrice = 0;
checkedList.forEach(item => {
// 商品单价, 为了方便计算先转换单位为分 (整数)
const unitPrice = item.goods.skuInfo.goods_price * 100
tempPrice += unitPrice * item.goods_num
})
app.totalPrice = (tempPrice / 100).toFixed(2)
},
// 跳转商品详情页
onTargetDetail(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
/**
* 获取商品列表
* @param {number} pageNo 页码
*/
getGoodsList(pageNo = 1) {
const app = this
const param = {
sortType: 'sales',
categoryId: 0,
goodsName: '',
page: pageNo
}
GoodsApi.cartList(param)
.then(result => {
console.log(result);
app.goodslist = result.data.list
app.goodstotal = result.data.total
})
.finally(() => app.isLoading = false)
},
// 获取购物车商品列表
getCartList() {
const app = this
app.isLoading = true
CartApi.list()
.then(result => {
app.list = result.data.list
app.total = result.data.cartTotal
// 清除checkedIds中无效的ID
app.onClearInvalidId()
})
.finally(() => app.isLoading = false)
},
// 清除checkedIds中无效的ID
onClearInvalidId() {
const app = this
const listIds = app.list.map(item => item.id)
app.checkedIds = arrayIntersect(listIds, app.checkedIds)
},
// 切换当前模式
handleToggleMode() {
this.mode = this.mode == 'normal' ? 'edit' : 'normal'
},
// 监听步进器更改事件
onChangeStepper({ value }, item) {
// 这里是组织首次启动时的执行
if (item.goods_num == value) return
// 记录一个节流函数句柄
if (!item.debounceHandle) {
item.oldValue = item.goods_num
item.debounceHandle = debounce(this.onUpdateCartNum, 500)
}
// 更新商品数量
item.goods_num = value
// 提交更新购物车数量 (节流)
item.debounceHandle(item, item.oldValue, value)
},
// 提交更新购物车数量
onUpdateCartNum(item, oldValue, newValue) {
const app = this
CartApi.update(item.goods_id, item.goods_sku_id, newValue)
.then(result => {
// 更新商品数量
app.total = result.data.cartTotal
// 重新计算合计金额
app.onCalcTotalPrice()
// 清除节流函数句柄
item.debounceHandle = null
})
.catch(err => {
// 还原商品数量
item.goods_num = oldValue
setTimeout(() => app.$toast(err.errMsg), 10)
})
},
// 跳转到商品详情页
onTargetGoods(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
// 点击去逛逛按钮, 跳转到首页
onTargetIndex() {
this.$navTo('pages/index/index')
},
// 选中商品
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 app = this
if (app.checkedIds.length) {
const cartIds = app.checkedIds.join()
app.$navTo('pages/checkout/index', { mode: 'cart', cartIds })
}
},
// 删除选中的商品弹窗事件
handleDelete() {
const app = this
if (!app.checkedIds.length) {
return false
}
uni.showModal({
title: '友情提示',
content: '您确定要删除该商品吗?',
showCancel: true,
success({ confirm }) {
// 确认删除
confirm && app.onClearCart()
}
})
},
// 确认删除商品
onClearCart() {
const app = this
CartApi.clear(app.checkedIds)
.then(result => {
app.getCartList()
app.handleToggleMode()
})
}
}
}
</script>
<style>
page {
background: #f5f5f5;
}
</style>
<style lang="scss" scoped>
// 页面顶部
.head-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4rpx 30rpx;
// background-color: #fff;
height: 80rpx;
.cart-total {
font-size: 28rpx;
color: #333;
.active {
color: #17C161;
margin: 0 2rpx;
}
}
.cart-edit {
padding-left: 20rpx;
.icon {
margin-right: 12rpx;
}
.edit {
color: #17C161;
}
}
}
// 购物车列表
.cart-list {
padding: 0 16rpx 16rpx 16rpx;
}
.cart-item {
background: #fff;
border-radius: 12rpx;
display: flex;
align-items: center;
padding: 30rpx 16rpx;
margin-bottom: 24rpx;
.item-radio {
width: 56rpx;
height: 80rpx;
line-height: 80rpx;
margin-right: 10rpx;
text-align: center;
.radio {
transform: scale(0.76)
}
}
.goods-image {
width: 200rpx;
height: 200rpx;
.image {
display: block;
width: 100%;
height: 100%;
border-radius: 8rpx;
}
}
.item-content {
flex: 1;
padding-left: 24rpx;
.goods-title {
font-size: 28rpx;
max-height: 76rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
.item-foot {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
.goods-price {
vertical-align: bottom;
// color: $uni-text-color-active;
color:#F85452;
.unit {
font-size: 24rpx;
}
.value {
font-size: 32rpx;
}
}
}
}
}
// 空数据按钮
.empty-ipt {
width: 220rpx;
margin: 0 auto;
font-size: 32rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
color: #fff;
border-radius: 50rpx;
background: linear-gradient(to right, #f9211c, #ff6335);
}
.empty-ipt2 {
width: 100%;
margin: 20rpx auto;
margin-top: 0;
font-size: 32rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
font-weight: bold;
}
// 底部操作栏
.footer-fixed {
display: flex;
align-items: center;
height: 96rpx;
background: #fff;
padding: 0 30rpx;
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
.all-radio {
width: 140rpx;
display: flex;
align-items: center;
.radio {
margin-bottom: -4rpx;
transform: scale(0.76)
}
}
.total-info {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 30rpx;
.goods-price {
vertical-align: bottom;
color: #FF483C;
.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: linear-gradient(to right, #f9211c, #ff6335);
background-color: #17C161;
// 禁用按钮
&.disabled {
background: #ff9779;
}
}
}
}
// 商品列表
.goods-list {
padding: 4rpx;
box-sizing: border-box;
margin: 0rpx auto 120rpx auto;
padding-bottom: 100rpx;
}
// 单列显示
.goods-list.column-1 {
.goods-item {
width: 100%;
height: 280rpx;
margin-bottom: 12rpx;
padding: 20rpx;
box-sizing: border-box;
background: #fff;
line-height: 1.6;
&:last-child {
margin-bottom: 0;
}
}
.goods-item_left {
display: flex;
width: 300rpx;
background: #fff;
align-items: center;
.image {
display: block;
width: 240rpx;
height: 240rpx;
}
}
.goods-item_right {
position: relative;
// width: 450rpx;
flex: 1;
.goods-name {
margin-top: 10rpx;
min-height: 68rpx;
line-height: 1.3;
white-space: normal;
color: #484848;
font-size: 26rpx;
}
}
.goods-item_desc {
margin-top: 8rpx;
}
.desc-selling_point {
width: 400rpx;
font-size: 24rpx;
color: #e49a3d;
}
.desc-goods_sales {
color: #999;
font-size: 24rpx;
}
.desc_footer {
font-size: 24rpx;
.price_x {
margin-right: 16rpx;
color: #f03c3c;
font-size: 30rpx;
}
.price_y {
text-decoration: line-through;
}
}
}
// 平铺显示
.goods-list.column-2 {
.goods-item {
width: 50%;
}
}
.goods-item {
float: left;
box-sizing: border-box;
padding: 6rpx;
.goods-image {
position: relative;
width: 100%;
height: 0;
padding-bottom: 100%;
overflow: hidden;
background: #fff;
&:after {
content: '';
display: block;
margin-top: 100%;
}
.image {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
-o-object-fit: cover;
object-fit: cover;
}
}
.detail {
padding: 8rpx;
background: #fff;
.goods-name {
min-height: 68rpx;
line-height: 32rpx;
white-space: normal;
color: #484848;
font-size: 26rpx;
}
.detail-price {
.goods-price {
margin-right: 8rpx;
}
.line-price {
text-decoration: line-through;
}
}
}
}
</style>