This commit is contained in:
yml2213
2026-08-20 14:17:59 +08:00
commit d591df5033
34 changed files with 7555 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
<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>