Oroqen-Vue/src/views/Cart.vue

733 lines
16 KiB
Vue
Raw Normal View History

2025-08-07 21:29:04 +08:00
<template>
<div class="cart">
<!-- 页面头部 -->
<section class="page-header">
<div class="container">
<div class="header-content">
<h1 class="page-title">购物车</h1>
<p class="page-subtitle">确认您的商品信息</p>
</div>
</div>
</section>
<div class="container">
<!-- 购物车内容 -->
<div v-if="cartItems.length > 0" class="cart-content">
<!-- 购物车列表 -->
<div class="cart-list">
<div class="cart-header">
<div class="select-all">
<el-checkbox
v-model="selectAll"
@change="handleSelectAll"
:indeterminate="isIndeterminate"
>
全选
</el-checkbox>
</div>
<div class="header-labels">
<span class="product-label">商品信息</span>
<span class="price-label">单价</span>
<span class="quantity-label">数量</span>
<span class="total-label">小计</span>
<span class="action-label">操作</span>
</div>
</div>
<div class="cart-items">
<div
v-for="item in cartItems"
:key="item.id"
class="cart-item"
>
<div class="item-select">
<el-checkbox
v-model="item.selected"
@change="updateSelection"
/>
</div>
<div class="item-product">
<div class="product-image">
<div class="image-placeholder">
<svg viewBox="0 0 100 80" width="100%" height="80">
<rect width="100" height="80" :fill="item.color" opacity="0.2"/>
<circle cx="50" cy="40" r="20" :fill="item.color" opacity="0.5"/>
<text x="50" y="45" text-anchor="middle" :fill="item.color"
font-size="10" font-weight="bold">{{ item.name.substring(0, 2) }}</text>
</svg>
</div>
</div>
<div class="product-info">
<h3 class="product-name">{{ item.name }}</h3>
<p class="product-specs">{{ item.specs || '规格:标准版' }}</p>
<p class="product-craftsman" v-if="item.craftsman">
<el-icon><User /></el-icon>
{{ item.craftsman }}
</p>
</div>
</div>
<div class="item-price">
<span class="current-price">¥{{ item.price }}</span>
<span v-if="item.originalPrice" class="original-price">¥{{ item.originalPrice }}</span>
</div>
<div class="item-quantity">
<div class="quantity-controls">
<el-button
size="small"
@click="decreaseQuantity(item)"
:disabled="item.quantity <= 1"
>
-
</el-button>
<span class="quantity">{{ item.quantity }}</span>
<el-button
size="small"
@click="increaseQuantity(item)"
:disabled="item.quantity >= item.stock"
>
+
</el-button>
</div>
<div class="stock-info">
<span class="stock-text">库存{{ item.stock }}</span>
</div>
</div>
<div class="item-total">
<span class="total-price">¥{{ (item.price * item.quantity).toFixed(2) }}</span>
</div>
<div class="item-actions">
<el-button
type="text"
size="small"
@click="removeFromCart(item.id)"
class="remove-btn"
>
删除
</el-button>
</div>
</div>
</div>
</div>
<!-- 购物车汇总 -->
<div class="cart-summary">
<div class="summary-content">
<div class="summary-info">
<div class="selected-count">
已选择 {{ selectedItems.length }} 件商品
</div>
<div class="total-amount">
<span class="label">合计</span>
<span class="amount">¥{{ totalAmount.toFixed(2) }}</span>
</div>
</div>
<div class="summary-actions">
<el-button
type="primary"
size="large"
:disabled="selectedItems.length === 0"
@click="checkout"
>
结算 ({{ selectedItems.length }})
</el-button>
</div>
</div>
</div>
</div>
<!-- 空购物车 -->
<div v-else class="empty-cart">
<div class="empty-icon">
<el-icon size="80" color="#ddd"><ShoppingCart /></el-icon>
</div>
<h3>购物车是空的</h3>
<p>快去挑选您喜欢的商品吧</p>
<el-button type="primary" @click="$router.push('/products')">
去购物
</el-button>
</div>
<!-- 推荐商品 -->
<div v-if="cartItems.length > 0" class="recommended-products">
<h2 class="section-title">为您推荐</h2>
<div class="products-grid">
<div
v-for="product in recommendedProducts"
:key="product.id"
class="product-card card"
@click="$router.push(`/product/${product.id}`)"
>
<div class="product-image">
<div class="image-placeholder">
<svg viewBox="0 0 200 150" width="100%" height="150">
<rect width="200" height="150" :fill="product.color" opacity="0.2"/>
<circle cx="100" cy="75" r="30" :fill="product.color" opacity="0.5"/>
<text x="100" y="80" text-anchor="middle" :fill="product.color"
font-size="12" font-weight="bold">{{ product.name.substring(0, 2) }}</text>
</svg>
</div>
</div>
<div class="product-info">
<h3 class="product-name">{{ product.name }}</h3>
<div class="product-price">¥{{ product.price }}</div>
<el-button
type="primary"
size="small"
@click.stop="addRecommendedToCart(product)"
>
加入购物车
</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useMainStore } from '../stores'
import { User, ShoppingCart } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
const store = useMainStore()
// 响应式数据
const selectAll = ref(false)
// 推荐商品
const recommendedProducts = ref([
{
id: 2,
name: '鄂伦春族刺绣挂画',
price: 288,
color: '#2d5016'
},
{
id: 4,
name: '手工木雕摆件',
price: 128,
color: '#8b4513'
},
{
id: 5,
name: '野生蓝莓干',
price: 58,
color: '#2d5016'
},
{
id: 6,
name: '鄂伦春文化笔记本',
price: 35,
color: '#8b4513'
}
])
// 计算属性
const cartItems = computed(() => {
return store.cartItems.map(item => ({
...item,
selected: item.selected !== false, // 默认选中
color: getProductColor(item.id)
}))
})
const selectedItems = computed(() => {
return cartItems.value.filter(item => item.selected)
})
const totalAmount = computed(() => {
return selectedItems.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})
const isIndeterminate = computed(() => {
const selectedCount = selectedItems.value.length
return selectedCount > 0 && selectedCount < cartItems.value.length
})
// 方法
const getProductColor = (productId: number) => {
const colors = ['#8b4513', '#2d5016', '#4a5568', '#2d3748']
return colors[productId % colors.length]
}
const handleSelectAll = (checked: boolean) => {
cartItems.value.forEach(item => {
item.selected = checked
})
updateCartSelection()
}
const updateSelection = () => {
const selectedCount = cartItems.value.filter(item => item.selected).length
selectAll.value = selectedCount === cartItems.value.length
updateCartSelection()
}
const updateCartSelection = () => {
// 更新store中的选中状态
cartItems.value.forEach(item => {
const storeItem = store.cartItems.find(si => si.id === item.id)
if (storeItem) {
storeItem.selected = item.selected
}
})
}
const increaseQuantity = (item: any) => {
if (item.quantity < item.stock) {
store.updateCartItemQuantity(item.id, item.quantity + 1)
} else {
ElMessage.warning('库存不足')
}
}
const decreaseQuantity = (item: any) => {
if (item.quantity > 1) {
store.updateCartItemQuantity(item.id, item.quantity - 1)
}
}
const removeFromCart = async (itemId: number) => {
try {
await ElMessageBox.confirm(
'确定要删除这件商品吗?',
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
store.removeFromCart(itemId)
ElMessage.success('商品已删除')
// 重新计算全选状态
updateSelection()
} catch {
// 用户取消删除
}
}
const addRecommendedToCart = (product: any) => {
store.addToCart(product)
ElMessage.success('已添加到购物车')
}
const checkout = () => {
if (selectedItems.value.length === 0) {
ElMessage.warning('请选择要结算的商品')
return
}
// 这里应该跳转到结算页面
ElMessage.success('跳转到结算页面')
// router.push('/checkout')
}
// 生命周期
onMounted(() => {
// 初始化选中状态
updateSelection()
})
</script>
<style scoped>
/* 页面头部 */
.page-header {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
padding: 3rem 0 2rem;
text-align: center;
}
.page-title {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
}
/* 购物车内容 */
.cart-content {
display: grid;
grid-template-columns: 1fr 300px;
gap: 2rem;
margin: 2rem 0;
}
/* 购物车列表 */
.cart-list {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.cart-header {
background: var(--background-light);
padding: 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
}
.select-all {
width: 120px;
}
.header-labels {
flex: 1;
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1fr;
gap: 1rem;
font-weight: 500;
color: var(--text-color);
}
.cart-items {
max-height: 600px;
overflow-y: auto;
}
.cart-item {
padding: 1.5rem 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 1rem;
}
.cart-item:last-child {
border-bottom: none;
}
.item-select {
width: 40px;
}
.item-product {
flex: 2;
display: flex;
gap: 1rem;
align-items: center;
}
.product-image {
width: 80px;
height: 80px;
border-radius: 4px;
overflow: hidden;
flex-shrink: 0;
}
.image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--background-light);
}
.product-info {
flex: 1;
}
.product-name {
font-size: 1.1rem;
font-weight: 500;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.product-specs {
color: var(--text-light);
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.product-craftsman {
color: var(--text-light);
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.25rem;
}
.item-price {
flex: 1;
text-align: center;
}
.current-price {
font-size: 1.1rem;
font-weight: 500;
color: var(--secondary-color);
}
.original-price {
display: block;
font-size: 0.9rem;
color: var(--text-light);
text-decoration: line-through;
margin-top: 0.25rem;
}
.item-quantity {
flex: 1;
text-align: center;
}
.quantity-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.quantity {
font-size: 1rem;
font-weight: 500;
min-width: 2rem;
text-align: center;
}
.stock-info {
font-size: 0.8rem;
color: var(--text-light);
}
.item-total {
flex: 1;
text-align: center;
}
.total-price {
font-size: 1.2rem;
font-weight: bold;
color: var(--secondary-color);
}
.item-actions {
flex: 1;
text-align: center;
}
.remove-btn {
color: var(--danger-color);
}
.remove-btn:hover {
color: var(--danger-color);
background: rgba(218, 54, 51, 0.1);
}
/* 购物车汇总 */
.cart-summary {
position: sticky;
top: 2rem;
height: fit-content;
}
.summary-content {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
}
.summary-info {
margin-bottom: 1.5rem;
}
.selected-count {
color: var(--text-light);
margin-bottom: 1rem;
}
.total-amount {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.2rem;
}
.total-amount .label {
color: var(--text-color);
}
.total-amount .amount {
font-size: 1.5rem;
font-weight: bold;
color: var(--secondary-color);
}
.summary-actions .el-button {
width: 100%;
height: 50px;
font-size: 1.1rem;
}
/* 空购物车 */
.empty-cart {
text-align: center;
padding: 4rem 2rem;
}
.empty-icon {
margin-bottom: 2rem;
}
.empty-cart h3 {
font-size: 1.5rem;
color: var(--text-color);
margin-bottom: 1rem;
}
.empty-cart p {
color: var(--text-light);
margin-bottom: 2rem;
}
/* 推荐商品 */
.recommended-products {
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid var(--border-color);
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.product-card {
cursor: pointer;
transition: all 0.3s ease;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.product-card .product-image {
width: 100%;
height: 150px;
margin-bottom: 1rem;
}
.product-card .product-info {
padding: 1rem;
}
.product-card .product-name {
font-size: 1rem;
font-weight: 500;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.product-card .product-price {
font-size: 1.1rem;
font-weight: bold;
color: var(--secondary-color);
margin-bottom: 1rem;
}
.product-card .el-button {
width: 100%;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.cart-content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.cart-summary {
position: static;
}
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.cart-header {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.header-labels {
display: none;
}
.cart-item {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.item-select {
width: auto;
align-self: flex-start;
}
.item-product {
flex-direction: column;
text-align: center;
}
.item-price,
.item-quantity,
.item-total,
.item-actions {
text-align: center;
}
.quantity-controls {
justify-content: center;
}
.products-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
@media (max-width: 480px) {
.cart-item {
padding: 1rem 0.5rem;
}
.summary-content {
padding: 1rem;
}
.products-grid {
grid-template-columns: 1fr 1fr;
}
}
</style>