Files
dachui-mall-web/src/views/Cart.vue
T
2026-08-20 14:17:59 +08:00

194 lines
7.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="max-w-site mx-auto px-4 py-8 space-y-6">
<!-- Title -->
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<h1 class="text-2xl font-bold text-gray-900">我的购物车</h1>
<span class="text-xs text-gray-400">({{ cartStore.validList.length }} 件商品)</span>
</div>
<router-link to="/products" class="text-xs text-primary font-semibold hover:underline">
&lt; 继续选购
</router-link>
</div>
<!-- Cart Main Content -->
<div v-if="cartStore.validList.length > 0" class="space-y-4">
<!-- Cart Table -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<!-- Table Header -->
<div class="grid grid-cols-12 gap-4 px-6 py-4 bg-gray-50 border-b border-gray-100 text-xs font-semibold text-gray-500">
<div class="col-span-1 flex items-center">
<el-checkbox v-model="isAllSelected" @change="toggleSelectAll">全选</el-checkbox>
</div>
<div class="col-span-5">商品信息</div>
<div class="col-span-2 text-center">单价</div>
<div class="col-span-2 text-center">数量</div>
<div class="col-span-1 text-center">小计</div>
<div class="col-span-1 text-right">操作</div>
</div>
<!-- Table Rows -->
<div class="divide-y divide-gray-100">
<div
v-for="item in cartStore.validList"
:key="item.id"
class="grid grid-cols-12 gap-4 px-6 py-5 items-center hover:bg-gray-50/50 transition-colors"
>
<!-- Checkbox -->
<div class="col-span-1">
<el-checkbox v-model="item.selected" />
</div>
<!-- Product info -->
<div class="col-span-5 flex items-center space-x-4">
<router-link :to="`/product/${item.product_id}`" class="w-20 h-20 rounded-xl overflow-hidden bg-gray-50 border border-gray-100 flex-shrink-0">
<img :src="item.productInfo?.attrInfo?.image || item.productInfo?.image" class="w-full h-full object-cover" />
</router-link>
<div class="space-y-1">
<router-link :to="`/product/${item.product_id}`" class="text-xs font-bold text-gray-800 hover:text-primary line-clamp-2 leading-relaxed">
{{ item.productInfo?.store_name }}
</router-link>
<p v-if="item.productInfo?.attrInfo?.suk" class="text-[11px] text-gray-400 bg-gray-100 px-2 py-0.5 rounded inline-block">
规格{{ item.productInfo?.attrInfo?.suk }}
</p>
</div>
</div>
<!-- Unit Price -->
<div class="col-span-2 text-center text-xs font-semibold text-gray-700">
¥{{ item.truePrice || item.productInfo?.attrInfo?.price || item.productInfo?.price }}
</div>
<!-- Quantity Stepper -->
<div class="col-span-2 flex justify-center">
<el-input-number
v-model="item.cart_num"
:min="1"
:max="item.productInfo?.attrInfo?.stock || item.productInfo?.stock || 99"
size="small"
@change="(val) => handleNumChange(item.id, val)"
/>
</div>
<!-- Subtotal -->
<div class="col-span-1 text-center text-xs font-bold text-primary">
¥{{ ((item.truePrice || item.productInfo?.attrInfo?.price || item.productInfo?.price) * item.cart_num).toFixed(2) }}
</div>
<!-- Delete Action -->
<div class="col-span-1 text-right">
<button @click="handleDelete(item.id)" class="text-xs text-gray-400 hover:text-primary transition-colors">
删除
</button>
</div>
</div>
</div>
</div>
<!-- Bottom Summary Bar -->
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 flex items-center justify-between sticky bottom-4 z-30">
<div class="flex items-center space-x-6 text-xs text-gray-500">
<el-checkbox v-model="isAllSelected" @change="toggleSelectAll">全选</el-checkbox>
<button @click="batchDelete" class="hover:text-primary transition-colors">
删除选中商品
</button>
</div>
<div class="flex items-center space-x-6">
<div class="text-right">
<span class="text-xs text-gray-500">已选择 <strong class="text-primary font-bold">{{ selectedCount }}</strong> 件商品合计</span>
<span class="text-2xl font-extrabold text-primary ml-2">¥{{ totalPrice.toFixed(2) }}</span>
</div>
<button
:disabled="selectedCount === 0"
@click="handleCheckout"
class="px-8 py-3.5 bg-primary hover:bg-primary-hover disabled:bg-gray-300 text-white font-bold text-sm rounded-xl shadow-lg shadow-red-200 transition-all"
>
去结算 ({{ selectedCount }})
</button>
</div>
</div>
</div>
<!-- Empty State -->
<div v-else class="bg-white rounded-2xl p-20 text-center shadow-sm border border-gray-100">
<div class="text-6xl mb-4">🛒</div>
<h3 class="text-lg font-bold text-gray-800">您的购物车还是空的</h3>
<p class="text-xs text-gray-400 mt-1 mb-6">快去挑选您心仪的优质好物吧</p>
<router-link to="/products" class="inline-block px-8 py-3 bg-primary text-white text-xs font-bold rounded-xl hover:bg-primary-hover shadow-md shadow-red-200 transition-all">
立即去逛逛
</router-link>
</div>
</div>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useCartStore } from '@/store/cart'
import { ElMessageBox, ElMessage } from 'element-plus'
const router = useRouter()
const cartStore = useCartStore()
const isAllSelected = computed({
get() {
return cartStore.validList.length > 0 && cartStore.validList.every(i => i.selected)
},
set(val) {
cartStore.validList.forEach(i => i.selected = val)
}
})
const selectedItems = computed(() => cartStore.validList.filter(i => i.selected))
const selectedCount = computed(() => selectedItems.value.reduce((acc, cur) => acc + cur.cart_num, 0))
const totalPrice = computed(() => {
return selectedItems.value.reduce((acc, cur) => {
const price = cur.truePrice || cur.productInfo?.attrInfo?.price || cur.productInfo?.price || 0
return acc + (price * cur.cart_num)
}, 0)
})
const toggleSelectAll = (val) => {
cartStore.validList.forEach(i => i.selected = val)
}
const handleNumChange = async (id, num) => {
await cartStore.updateCartNum(id, num)
}
const handleDelete = (id) => {
ElMessageBox.confirm('确定要从购物车中移除该商品吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await cartStore.removeCartItems([id])
})
}
const batchDelete = () => {
const ids = selectedItems.value.map(i => i.id)
if (!ids.length) {
ElMessage.warning('请选择需要删除的商品')
return
}
ElMessageBox.confirm(`确定要删除选中的 ${ids.length} 件商品吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await cartStore.removeCartItems(ids)
})
}
const handleCheckout = () => {
const cartIds = selectedItems.value.map(i => i.id).join(',')
router.push(`/checkout?cartId=${cartIds}`)
}
onMounted(() => {
cartStore.fetchCartList()
})
</script>