<template> <view class="container" :style="appThemeStyle"> <!-- 页面顶部 --> <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"> <view v-if="storeVersion == 1 && item.goods.merchant_id > 0 && item.goods.merchant" class="merchant-name" @click="toShop(item)"> <image :src="item.goods.merchant.logoImage && item.goods.merchant.logoImage[0].external_url" mode="aspectFill"></image> <text>{{ item.goods.merchant.shop_name }}</text> <image src="@/static/arrow-right.png" mode="aspectFill"></image> </view> <view> <view class="item-radio" @click="handleCheckItem(item.id)"> <u-checkbox :modelValue="inArray(item.id, checkedIds)" shape="circle" :activeColor="appTheme.mainBg" /> </view> <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?Number(item.goods.skuInfo.goods_price):item.goods.skuInfo.goods_price }}</text> </view> <view class="stepper"> <u-number-box :min="1" class='countNumber' :modelValue="item.goods_num" :step="1" @change="onChangeStepper($event, item)" /> </view> </view> </view> </view> </view> </view> <!-- 购物车数据为空 --> <empty v-if="!list.length" :isLoading="isLoading" :custom-style="{ padding: '180rpx 50rpx' }" tips="您的购物车是空的, 快去逛逛吧"> <template v-slot:slot> <view class="empty-ipt" @click="onTargetIndex()"> <text>去逛逛</text> </view> </template> </empty> <!-- 商品推荐 --> <view class="goodsRecommend-title"> <image :src="$picUrl+'/static/user/left.png'"></image>为您推荐<image :src="$picUrl+'/static/user/right.png'"> </image> </view> <view class="goodsRecommend"> <!-- <u-waterfall v-model="goodsRecommend" ref="uWaterfall1"> --> <!-- <template v-slot:left="{leftList}"> --> <view class="goodsItem" v-for="(item,index) in goodsRecommend" @click="onTargetGoods(item.goods_id)" :key="index"> <view class="pic"> <image :src="item.goods_image" mode="aspectFill"></image> </view> <view class="goodsInfo"> <view class="title"> <!-- <text v-if="item.selling_point" class="ziying">{{item.selling_point}}</text> --> <text class="name">{{item.goods_name}}</text> </view> </view> <!-- 商品价格 --> <view class="shenBox"> <view class="detail-price oneline-hide"> <text class="goods-price f-30 col-m"><text style="font-size: 26rpx;">¥</text>{{ item.goods_price_min>0?Number(item.goods_price_min):0.00}} <text class="delPrice"></text></text> <text v-if="item.line_price_min > 0" class="line-price col-9 f-24">¥{{ item.line_price_min>0?Number(item.line_price_min):0.00 }}</text> </view> <image :src="$picUrl+'/static/detail/redShen.png'" v-if="item.is_check==1" mode=""></image> </view> <!-- <view class="goodsInfo1" v-if="item.cmmdty_model"> <view class="oneTip"> {{item.cmmdty_model}} </view> </view> --> <!-- <view class="goodsSend"> <view class="sendLeft"> <view class="left_1"> {{item.goods_source}} </view> <view class="left_2"> {{Number(item.discount)}}折 </view> </view> <view class="sendRight"> <text v-if="item.delivery_time==0">24小时内发货</text> <text v-if="item.delivery_time==1">48小时内发货</text> <text v-if="item.delivery_time==2">72小时内发货</text> <text v-if="item.delivery_time==3">7天内发货</text> <text v-if="item.delivery_time==4">15天内发货</text> <text v-if="item.delivery_time==5">30天内发货</text> <text v-if="item.delivery_time==6">45天内发货</text> </view> </view> --> </view> <!-- </template> --> <!-- <template v-slot:right="{rightList}"> <view class="goodsItem" style="margin-right: 0;" v-for="(item,index) in rightList" @click="onTargetGoods(item.goods_id)" :key="index"> <view class="pic"> <image :src="item.goods_image" mode="aspectFill"></image> </view> <view class="goodsInfo"> <view class="title"> <text v-if="item.selling_point" class="ziying">{{item.selling_point}}</text> <text class="name">{{item.goods_name}}</text> </view> </view> <view class="shenBox"> <view class="detail-price oneline-hide"> <text class="goods-price f-30 col-m"><text style="font-size: 26rpx;">¥</text>{{ item.goods_price_min>0?Number(item.goods_price_min):0.00}} <text class="delPrice"></text></text> <text v-if="item.line_price_min > 0" class="line-price col-9 f-24">¥{{ item.line_price_min>0?Number(item.line_price_min):0.00 }}</text> </view> <image :src="$picUrl+'/static/detail/redShen.png'" v-if="item.is_check==1" mode=""></image> </view> <view class="goodsInfo1" v-if="item.cmmdty_model"> <view class="oneTip"> {{item.cmmdty_model}} </view> </view> <view class="goodsSend"> <view class="sendLeft"> <view class="left_1"> {{item.goods_source}} </view> <view class="left_2"> {{Number(item.discount)}}折 </view> </view> <view class="sendRight"> <text v-if="item.delivery_time==0">24小时内发货</text> <text v-if="item.delivery_time==1">48小时内发货</text> <text v-if="item.delivery_time==2">72小时内发货</text> <text v-if="item.delivery_time==3">7天内发货</text> <text v-if="item.delivery_time==4">15天内发货</text> <text v-if="item.delivery_time==5">30天内发货</text> <text v-if="item.delivery_time==6">45天内发货</text> </view> </view> </view> </template> --> <!-- </u-waterfall> --> <view v-if="goodsRecommend.length" class="finished">{{ recommendLoadTitle }}</view> <u-empty text="暂无数据显示哦~" v-if="goodsRecommend.length==0" mode="list" style="width: 100%"></u-empty> </view> <!-- 底部操作栏 --> <view v-if="list.length" class="footer-fixed"> <view class="all-radio"> <u-checkbox :modelValue="checkedIds.length > 0 && checkedIds.length === list.length" shape="circle" :activeColor="appTheme.mainBg" @change="handleCheckAll()">全选</u-checkbox> </view> <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 ? `(${total})` : '' }}</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 Empty from '@/components/empty' // import Recommended from '@/components/recommended' import { inArray, arrayIntersect, debounce } from '@/utils/util' import { checkLogin, setCartTotalNum, setCartTabBadge } from '@/core/app' import * as CartApi from '@/api/cart' import * as Api from '@/api/goods' const CartIdsIndex = 'CartIds' export default { components: { Empty }, data() { return { goodsRecommend: [], inArray, // 正在加载 isLoading: true, // 当前模式: normal正常 edit编辑 mode: 'normal', // 购物车商品列表 list: [], // 购物车商品总数量 total: null, // 选中的商品ID记录 checkedIds: [], // 选中的商品总金额 totalPrice: '0.00', recommendPage: 1, recommendFinish: false, recommendLoadTitle: '' } }, computed: { storeVersion() { const version = uni.getStorageSync('storeVersion') == 1 ? 1 : 0; console.log(version); return version; } }, watch: { // 监听选中的商品 checkedIds: { handler(val) { // 计算合计金额 this.onCalcTotalPrice() // 记录到缓存中 uni.setStorageSync(CartIdsIndex, val) }, deep: true, immediate: false }, // 监听购物车商品总数量 total(val) { // 缓存并设置角标 setCartTotalNum(val) setCartTabBadge() } }, /** * 生命周期函数--监听页面显示 */ onShow(options) { // 获取缓存中的选中记录 this.checkedIds = uni.getStorageSync(CartIdsIndex) // 获取购物车商品列表 checkLogin() ? this.getCartList() : this.isLoading = false this.recommendPage = 1; this.recommendFinish = false; this.recommendLoadTitle = ''; this.goodsRecommend = []; this.getSuggest() }, onReachBottom() { if (!this.recommendFinish) { this.getSuggest() } }, methods: { getSuggest() { // 获取推荐商品 Api.recommendedNew({ page: this.recommendPage++ }).then(res => { console.log(res); let arr = res.data.goodsList.data if (arr && arr.length > 0) { for (let i = 0; i < arr.length; i++) { arr[i].goods_price_min = Number(arr[i].goods_price_min); arr[i].goods_price_max = Number(arr[i].goods_price_max) arr[i].line_price_min = Number(arr[i].line_price_min); arr[i].line_price_max = Number(arr[i].line_price_max) } } if (arr.length < res.data.goodsList.per_page) { this.recommendFinish = true; } this.recommendLoadTitle = this.recommendFinish ? "已全部加载完" : "上拉加载更多"; this.goodsRecommend = [...this.goodsRecommend, ...arr] }) }, // 计算合计金额 (根据选中的商品) 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) }, // 获取购物车商品列表 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() const merchant_ids = []; app.list.forEach(item => { if (app.checkedIds.includes(item.id) && !merchant_ids.includes(item.goods.merchant_id)) { merchant_ids.push(item.goods.merchant_id); } }); if (merchant_ids.length > 1) { app.$toast('结算商品来源于多家商户,无法结算!'); return false; } // app.$navTo('pages/checkout/index', { // mode: 'cart', // cartIds // }) app.$navTo('pages/sureOrder/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() }) }, toShop(item) { uni.navigateTo({ url: `/pages/shopList/shopPage?id=${item.goods.merchant_id}`, }) }, } } </script> <style> page { background: #f5f5f5; } </style> <style lang="scss" scoped> ::v-deep .u-empty { padding: 100rpx 0; } .container { padding-bottom: 120rpx; } .goodsRecommend-title { padding: 20rpx 25rpx; display: flex; align-items: center; font-size: 32rpx; font-weight: 600; color: #3B3B3B; justify-content: center; image { width: 30rpx; height: 30rpx; margin: 0 10rpx; } } .goodsRecommend { overflow: hidden; margin: 10rpx 22rpx 20rpx 22rpx; display: flex; flex-wrap: wrap; justify-content: space-between; .goodsItem { width: 348rpx; border-radius: 8rpx; overflow: hidden; background-color: #ffffff; padding: 20rpx; box-sizing: border-box; margin: 0rpx 0 20rpx 0; .pic { width: 100%; align-items: center; display: flex; height: 256rpx; overflow: hidden; image { width: 100%; height: 100%; } } .goodsInfo { margin-bottom: 12rpx; height: 60rpx; .title { display: flex; align-items: center; margin-top: 10rpx; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 2; line-height: 33rpx; white-space: normal; .ziying { padding: 4rpx 10rpx; background: #FF4438; border-radius: 2px 2px 2px 2px; opacity: 1; font-size: 24rpx; font-weight: 400; color: #FFFFFF; } .name { font-family: PingFang SC, PingFang SC; font-weight: 500; font-size: 28rpx; color: #2B2B2B; line-height: 33rpx; text-align: left; font-style: normal; text-transform: none; } } .isExpress { height: 30rpx; display: flex; align-items: center; margin: 10rpx 0; text { display: inline-block; width: 44rpx; height: 30rpx; line-height: 28rpx; border: 1px solid #F21A1C; border-radius: 2px 2px 2px 2px; background: #FFFFFF; text-align: center; font-size: 16rpx; font-family: PingFang SC, PingFang SC; font-weight: 400; color: #F21A1C; } } .price { color: #F21A1C; display: flex; align-items: baseline; } .comment { margin-top: 6rpx; font-size: 22rpx; font-weight: 400; color: #949494; } } } .finished { font-size: 28rpx; line-height: 100rpx; text-align: center; color: #bbb; width: 100%; } } // 页面顶部 .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: $main-bg; margin: 0 2rpx; } } .cart-edit { padding-left: 20rpx; .icon { margin-right: 12rpx; } .edit { color: $main-bg; } } } // 购物车列表 .cart-list { padding: 0 16rpx 0 16rpx; } .cart-item { background: #fff; border-radius: 12rpx; padding: 30rpx 16rpx; margin-bottom: 24rpx; .merchant-name { font-size: 30rpx; color: #333; font-weight: bold; >text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: calc(100% - 73rpx); } >image { margin-left: 10rpx; width: 13rpx; height: 24rpx; flex-shrink: 0; &:first-child { width: 40rpx; height: 40rpx; margin-right: 10rpx; } } } >view { display: flex; align-items: center; } .item-radio { width: 56rpx; height: 80rpx; margin-right: 10rpx; display: flex; justify-content: center; align-items: center; } .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: $main-bg; flex: 1; .unit { font-size: 24rpx; } .value { font-size: 32rpx; } } } } } .stepper { flex: 1; display: flex; // width: 154rpx; height: 56rpx; background: #FFFFFF; border-radius: 76rpx 76rpx 76rpx 76rpx; opacity: 1; margin-right: 20rpx; // border: 2rpx solid #E6E6E6; .del { width: 50rpx; height: 56rpx; color: #C2C2C2; opacity: 1; text-align: center; line-height: 56rpx; border-right: 2rpx solid #E6E6E6; } .allNum { width: 54rpx; height: 56rpx; opacity: 1; text-align: center; line-height: 56rpx; } .add { width: 50rpx; height: 56rpx; color: #C2C2C2; opacity: 1; text-align: center; line-height: 56rpx; border-left: 2rpx solid #E6E6E6; } .countNumber { width: 170rpx; background: none; } } // 空数据按钮 .empty-ipt { margin: 0 auto; width: 250rpx; height: 70rpx; font-size: 32rpx; text-align: center; color: #fff; border-radius: 50rpx; background: linear-gradient(to right, $main-bg, $main-bg2); color: $main-text; display: flex; justify-content: center; align-items: center; } // 底部操作栏 .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 { flex: 1; vertical-align: bottom; color: $main-bg; .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; color: #fff; border-radius: 50rpx; display: flex; justify-content: center; align-items: center; } // 去结算按钮 .btn-main { background: linear-gradient(to right, $main-bg, $main-bg2); color: $main-text; // 禁用按钮 &.disabled { opacity: 0.6; } } } } .shenBox { overflow: hidden; display: flex; justify-content: space-between; align-items: center; image { width: 28rpx; height: 28rpx; } } .goods-price { font-family: PingFang SC, PingFang SC; font-weight: bold; font-size: 32rpx; color: #F21A1C !important; } .line-price { text-decoration: line-through; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 22rpx; color: #949494; } .goodsInfo1 { display: flex; align-items: center; margin: 18rpx 0 0 0rpx; .oneTip { padding: 0 16rpx; height: 34rpx; background: #F6F6F6; border-radius: 4rpx; font-family: PingFang SC, PingFang SC; font-weight: 500; font-size: 22rpx; color: #333333; line-height: 34rpx; text-align: left; font-style: normal; text-transform: none; max-width: 300rpx; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 1; white-space: nowrap; } } .goodsSend { display: flex; justify-content: space-between; align-items: center; margin-top: 18rpx; .sendLeft { width: 120rpx; border: 1rpx solid #F21A1C; border-radius: 4rpx; height: 30rpx; display: flex; justify-content: space-between; align-items: center; .left_1 { height: 100%; width: 48rpx; line-height: 25rpx; font-family: PingFang SC, PingFang SC; font-weight: bold; font-size: 22rpx; color: #F21A1C; text-align: center; } .left_2 { height: 100%; width: 72rpx; background-color: #F21A1C; font-family: PingFang SC, PingFang SC; font-weight: 500; font-size: 20rpx; color: #FFFFFF; line-height: 26rpx; text-align: center; font-style: normal; text-transform: none; } } .sendRight { height: 32rpx; background: #FDEDED; padding: 0 10rpx; border-radius: 4rpx; font-family: PingFang SC, PingFang SC; font-weight: 500; font-size: 20rpx; color: #F21A1C; line-height: 32rpx; text-align: left; font-style: normal; text-transform: none; } } .delPrice { font-size: 20rpx; font-weight: 500; color: #F21A1C; } </style>