560 lines
12 KiB
Vue
560 lines
12 KiB
Vue
<template>
|
||
<view class="cart-page">
|
||
<!-- 购物车列表 -->
|
||
<view class="cart-list" v-if="cartList.length > 0">
|
||
<view class="cart-item" v-for="(item, index) in cartList" :key="item.id">
|
||
<view class="item-checkbox" @click="toggleSelect(index)">
|
||
<view class="checkbox" :class="{ checked: item.selected === 1 }">
|
||
<text class="iconfont icon-gou" v-if="item.selected === 1"></text>
|
||
</view>
|
||
</view>
|
||
<view class="item-image">
|
||
<image :src="item.product_image_url" mode="aspectFill"></image>
|
||
</view>
|
||
<view class="item-info">
|
||
<view class="item-title">{{ item.product_name || item.name }}</view>
|
||
<view class="item-spec" v-if="item.sku_name">
|
||
{{ item.sku_name }}
|
||
</view>
|
||
<view class="item-price-row">
|
||
<view class="item-price">
|
||
<text class="price-symbol">¥</text>
|
||
<text class="price-value">{{ formatPrice(item.price) || formatPrice(item.sku_price) }}</text>
|
||
</view>
|
||
<view class="item-count">
|
||
<view class="count-btn" @click="decreaseCount(index)">-</view>
|
||
<view class="count-num">{{ item.quantity || item.count }}</view>
|
||
<view class="count-btn" @click="increaseCount(index)">+</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="item-delete" @click="deleteItem(index)">
|
||
<text class="iconfont icon-cangpeitubiao_shanchu"></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空购物车 -->
|
||
<view class="empty-cart" v-else>
|
||
<view class="empty-icon">
|
||
<text class="iconfont icon-gouwuche"></text>
|
||
</view>
|
||
<view class="empty-text">购物车是空的</view>
|
||
<view class="empty-tip">快去挑选心仪的商品吧~</view>
|
||
<view class="empty-btn" @click="goShopping">去逛逛</view>
|
||
</view>
|
||
|
||
<!-- 底部结算栏 -->
|
||
<view class="cart-footer" v-if="cartList.length > 0">
|
||
<view class="footer-left">
|
||
<view class="footer-checkbox" @click="toggleSelectAll">
|
||
<view class="checkbox" :class="{ checked: isAllSelected }">
|
||
<text class="iconfont icon-gou" v-if="isAllSelected"></text>
|
||
</view>
|
||
<text class="footer-text">全选</text>
|
||
</view>
|
||
</view>
|
||
<view class="footer-right">
|
||
<view class="footer-total">
|
||
<text class="total-label">合计:</text>
|
||
<text class="total-price">¥{{ formatPrice(totalPrice) }}</text>
|
||
</view>
|
||
<view class="footer-btn" :class="{ disabled: selectedCount === 0 }" @click="checkout">
|
||
结算({{ selectedCount }})
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import request from '@/api/request.js';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
cartList: [],
|
||
loading: false
|
||
};
|
||
},
|
||
computed: {
|
||
// 是否全选
|
||
isAllSelected() {
|
||
if (this.cartList.length === 0) return false;
|
||
return this.cartList.every(item => item.selected === 1);
|
||
},
|
||
// 已选商品数量
|
||
selectedCount() {
|
||
return this.cartList.filter(item => item.selected === 1).length;
|
||
},
|
||
// 总价
|
||
totalPrice() {
|
||
let total = 0;
|
||
this.cartList.forEach(item => {
|
||
if (item.selected === 1) {
|
||
const price = parseFloat(item.price || item.sku_price || 0);
|
||
const quantity = parseInt(item.quantity || item.count || 0);
|
||
total += price * quantity;
|
||
}
|
||
});
|
||
return total.toFixed(2);
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.loadCartList();
|
||
},
|
||
onShow() {
|
||
// 页面显示时刷新购物车
|
||
this.loadCartList();
|
||
},
|
||
methods: {
|
||
formatPrice(value) {
|
||
if(!value) {
|
||
return 0.00;
|
||
}
|
||
// 分转换为元
|
||
value = value / 100;
|
||
// 保留两位小数
|
||
return value.toFixed(2);
|
||
},
|
||
// 加载购物车列表
|
||
async loadCartList() {
|
||
try {
|
||
this.loading = true;
|
||
const res = await request('xcx/carts', 'GET', {page: 1, page_size: 99});
|
||
// 根据实际返回数据结构调整
|
||
const list = res.list || res.data || res || [];
|
||
// 为每个商品添加selected属性,1表示选中,2表示不选中
|
||
this.cartList = list.map(item => ({
|
||
...item,
|
||
selected: item.selected !== undefined ? item.selected : 1
|
||
}));
|
||
} catch (error) {
|
||
console.error('加载购物车失败:', error);
|
||
// 如果接口不存在,使用模拟数据
|
||
if (error.message && error.message.includes('404')) {
|
||
this.cartList = [];
|
||
} else {
|
||
uni.showToast({
|
||
title: '加载购物车失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
// 切换商品选择状态
|
||
toggleSelect(index) {
|
||
// 1表示选中,2表示不选中
|
||
this.cartList[index].selected = this.cartList[index].selected === 1 ? 2 : 1;
|
||
this.updateCartItem(index);
|
||
},
|
||
|
||
// 全选/取消全选
|
||
toggleSelectAll() {
|
||
// 1表示选中,2表示不选中
|
||
const selectAll = this.isAllSelected ? 2 : 1;
|
||
this.cartList.forEach((item, index) => {
|
||
item.selected = selectAll;
|
||
this.updateCartItem(index);
|
||
});
|
||
},
|
||
|
||
// 减少数量
|
||
async decreaseCount(index) {
|
||
const item = this.cartList[index];
|
||
const currentCount = parseInt(item.quantity || item.count || 1);
|
||
if (currentCount <= 1) {
|
||
uni.showToast({
|
||
title: '商品数量不能少于1',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
item.quantity = currentCount - 1;
|
||
item.count = currentCount - 1;
|
||
await this.updateCartItem(index);
|
||
},
|
||
|
||
// 增加数量
|
||
async increaseCount(index) {
|
||
const item = this.cartList[index];
|
||
const currentCount = parseInt(item.quantity || item.count || 1);
|
||
item.quantity = currentCount + 1;
|
||
item.count = currentCount + 1;
|
||
await this.updateCartItem(index);
|
||
},
|
||
|
||
// 更新购物车商品
|
||
async updateCartItem(index) {
|
||
const item = this.cartList[index];
|
||
try {
|
||
// 根据实际接口调整,selected: 1表示选中,2表示不选中
|
||
await request('xcx/cart/' + item.id, 'PUT', {
|
||
quantity: item.quantity || item.count,
|
||
selected: item.selected
|
||
});
|
||
} catch (error) {
|
||
console.error('更新购物车失败:', error);
|
||
// 如果接口不存在,只更新本地数据
|
||
}
|
||
},
|
||
|
||
// 删除商品
|
||
deleteItem(index) {
|
||
const item = this.cartList[index];
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要删除这个商品吗?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
// 使用 DELETE 方法删除,与更新接口风格保持一致
|
||
await request('xcx/cart/' + item.id, 'DELETE');
|
||
// 删除成功后从列表中移除
|
||
this.cartList.splice(index, 1);
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
} catch (error) {
|
||
console.error('删除失败:', error);
|
||
// 如果接口调用失败,仍然删除本地数据(容错处理)
|
||
this.cartList.splice(index, 1);
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
}
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 结算
|
||
checkout() {
|
||
if (this.selectedCount === 0) {
|
||
uni.showToast({
|
||
title: '请选择要结算的商品',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
const selectedItems = this.cartList.filter(item => item.selected === 1);
|
||
// 将选中的商品信息传递给订单页面
|
||
const orderData = selectedItems.map(item => ({
|
||
cart_id: item.id,
|
||
product_id: item.product_id,
|
||
sku_id: item.sku_id,
|
||
quantity: item.quantity || item.count,
|
||
price: item.price || item.sku_price,
|
||
product_image_url: item.product_image_url
|
||
}));
|
||
|
||
// 跳转到订单页面,根据实际路由调整
|
||
uni.navigateTo({
|
||
url: `/pages/order/create?items=${encodeURIComponent(JSON.stringify(orderData))}`
|
||
});
|
||
},
|
||
|
||
// 去逛逛
|
||
goShopping() {
|
||
uni.switchTab({
|
||
url: '/pages/index/index'
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.cart-page {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
padding-bottom: 120rpx;
|
||
}
|
||
|
||
/* 购物车列表 */
|
||
.cart-list {
|
||
padding: 20rpx 0;
|
||
}
|
||
|
||
.cart-item {
|
||
display: flex;
|
||
align-items: center;
|
||
background-color: #fff;
|
||
margin-bottom: 20rpx;
|
||
padding: 30rpx 24rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||
position: relative;
|
||
}
|
||
|
||
.item-checkbox {
|
||
margin-right: 24rpx;
|
||
}
|
||
|
||
.checkbox {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border: 2rpx solid #ddd;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.checkbox.checked {
|
||
background: linear-gradient(135deg, #9810fa 0%, #7a0bc7 100%);
|
||
border-color: #9810fa;
|
||
}
|
||
|
||
.checkbox.checked .iconfont {
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.item-image {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
margin-right: 24rpx;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.item-image image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.item-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
min-height: 160rpx;
|
||
}
|
||
|
||
.item-title {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
line-height: 1.4;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
|
||
.item-spec {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.item-price-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.item-price {
|
||
color: #e7000b;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.price-symbol {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.item-count {
|
||
display: flex;
|
||
align-items: center;
|
||
border: 2rpx solid #e5e5e5;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.count-btn {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
color: #666;
|
||
background-color: #f8f8f8;
|
||
}
|
||
|
||
.count-btn:active {
|
||
background-color: #e8e8e8;
|
||
}
|
||
|
||
.count-num {
|
||
min-width: 60rpx;
|
||
height: 56rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
background-color: #fff;
|
||
border-left: 2rpx solid #e5e5e5;
|
||
border-right: 2rpx solid #e5e5e5;
|
||
}
|
||
|
||
.item-delete {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: 20rpx;
|
||
padding: 10rpx;
|
||
box-sizing: border-box;
|
||
position: absolute;
|
||
top: 20rpx;
|
||
right: 20rpx;
|
||
}
|
||
|
||
.item-delete:active {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.item-delete .iconfont {
|
||
font-size: 36rpx;
|
||
color: #e7000b;
|
||
}
|
||
|
||
/* 空购物车 */
|
||
.empty-cart {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-top: 200rpx;
|
||
}
|
||
|
||
.empty-icon {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f5f5f5;
|
||
border-radius: 50%;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.empty-icon .iconfont {
|
||
font-size: 120rpx;
|
||
color: #ccc;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 32rpx;
|
||
color: #666;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.empty-tip {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-bottom: 60rpx;
|
||
}
|
||
|
||
.empty-btn {
|
||
width: 240rpx;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
text-align: center;
|
||
background: linear-gradient(135deg, #9810fa 0%, #7a0bc7 100%);
|
||
color: #fff;
|
||
border-radius: 40rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
/* 底部结算栏 */
|
||
.cart-footer {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 100rpx;
|
||
background-color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 24rpx;
|
||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||
z-index: 100;
|
||
}
|
||
|
||
.footer-left {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.footer-checkbox {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.footer-text {
|
||
margin-left: 16rpx;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.footer-right {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.footer-total {
|
||
display: flex;
|
||
align-items: baseline;
|
||
margin-right: 24rpx;
|
||
}
|
||
|
||
.total-label {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.total-price {
|
||
font-size: 36rpx;
|
||
color: #e7000b;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.footer-btn {
|
||
min-width: 180rpx;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
text-align: center;
|
||
background: linear-gradient(135deg, #9810fa 0%, #7a0bc7 100%);
|
||
color: #fff;
|
||
border-radius: 36rpx;
|
||
font-size: 28rpx;
|
||
padding: 0 32rpx;
|
||
}
|
||
|
||
.footer-btn.disabled {
|
||
background: #ccc;
|
||
color: #999;
|
||
}
|
||
</style> |