194 lines
7.6 KiB
Vue
194 lines
7.6 KiB
Vue
<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">
|
||
< 继续选购
|
||
</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>
|