From 1881823a1aac4b34ee73a22473fcf3bfc679b21c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A4=A7=E8=84=8F=E7=8B=BC?= <786316265@qq.com>
Date: Tue, 26 Aug 2025 09:44:28 +0800
Subject: [PATCH] 111
---
src/views/ProductDetail.vue | 519 +++++++++++++++++++++++++++++++++++-
1 file changed, 513 insertions(+), 6 deletions(-)
diff --git a/src/views/ProductDetail.vue b/src/views/ProductDetail.vue
index 7c57f48..e572bef 100644
--- a/src/views/ProductDetail.vue
+++ b/src/views/ProductDetail.vue
@@ -243,7 +243,105 @@
direction="rtl"
size="80%"
>
-
+
+
+
+
+
+
+
+
购物车是空的
+
快去挑选心仪的商品吧~
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
{{ item.name }}
+
+
+ {{ item.points }} 积分
+
+
库存:{{ item.stock }}
+
+
+
+
+
+ -
+
+ {{ item.quantity }}
+
+ +
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
@@ -258,7 +356,8 @@ import {
ShoppingCart,
Coin,
ChatDotRound,
- User
+ User,
+ Loading
} from '@element-plus/icons-vue'
import api from '@/utils/api'
@@ -275,7 +374,9 @@ const quantity = ref(1)
const reviews = ref([])
const recommendedProducts = ref([])
const showCart = ref(false)
+const cartLoading = ref(false)
const userPoints = ref(0)
+const cartItems = ref([])
const cartCount = ref(0)
// 计算属性
@@ -283,6 +384,28 @@ const totalPoints = computed(() => {
return product.value ? product.value.points * quantity.value : 0
})
+// 购物车相关计算属性
+const cartTotalPoints = computed(() => {
+ return cartItems.value.reduce((total, item) => total + (item.points * item.quantity), 0)
+})
+
+const cartTotalItems = computed(() => {
+ return cartItems.value.reduce((total, item) => total + item.quantity, 0)
+})
+
+// 更新购物车计数
+watch(cartTotalItems, (newCount) => {
+ cartCount.value = newCount
+}, { immediate: true })
+
+// 监听购物车抽屉打开状态,打开时从后端加载数据
+watch(showCart, async (newValue) => {
+ if (newValue) {
+ // 购物车打开时从后端加载数据
+ await loadCartFromBackend()
+ }
+})
+
// 方法
const getProductDetail = async () => {
try {
@@ -309,10 +432,196 @@ const getProductDetail = async () => {
}
}
-const addToCart = () => {
- // 添加到购物车逻辑
- ElMessage.success('已添加到购物车')
- cartCount.value++
+const addToCart = async () => {
+ if (!product.value) {
+ ElMessage.error('商品信息加载中,请稍后再试')
+ return
+ }
+
+ if (product.value.stock === 0) {
+ ElMessage.error('商品已售罄')
+ return
+ }
+
+ if (quantity.value <= 0) {
+ ElMessage.error('请选择有效数量')
+ return
+ }
+
+ // 检查是否已存在该商品
+ const existingItemIndex = cartItems.value.findIndex(item => item.id === product.value.id)
+
+ if (existingItemIndex !== -1) {
+ // 商品已存在,增加数量
+ const existingItem = cartItems.value[existingItemIndex]
+ const newQuantity = existingItem.quantity + quantity.value
+
+ if (newQuantity > product.value.stock) {
+ ElMessage.error(`库存不足,最多只能添加 ${product.value.stock - existingItem.quantity} 个`)
+ return
+ }
+
+ existingItem.quantity = newQuantity
+ ElMessage.success(`已将 ${quantity.value} 个商品添加到购物车`)
+ } else {
+ // 新商品,直接添加
+ if (quantity.value > product.value.stock) {
+ ElMessage.error(`库存不足,最多只能添加 ${product.value.stock} 个`)
+ return
+ }
+
+ const cartItem = {
+ id: product.value.id,
+ name: product.value.name,
+ image: product.value.images?.[0] || product.value.image,
+ points: product.value.points,
+ quantity: quantity.value,
+ stock: product.value.stock
+ }
+
+ cartItems.value.push(cartItem)
+ ElMessage.success(`已将 ${quantity.value} 个商品添加到购物车`)
+ }
+
+ // 重置数量选择器
+ quantity.value = 1
+
+ // 同步购物车数据到后端
+ await syncCartToBackend()
+}
+
+// 购物车商品管理方法
+const updateCartItemQuantity = async (itemId, newQuantity) => {
+ const item = cartItems.value.find(item => item.id === itemId)
+ if (!item) return
+
+ if (newQuantity <= 0) {
+ removeFromCart(itemId)
+ return
+ }
+
+ if (newQuantity > item.stock) {
+ ElMessage.error(`库存不足,最多只能选择 ${item.stock} 个`)
+ return
+ }
+
+ item.quantity = newQuantity
+
+ // 同步购物车数据到后端
+ await syncCartToBackend()
+}
+
+const removeFromCart = async (itemId) => {
+ const index = cartItems.value.findIndex(item => item.id === itemId)
+ if (index !== -1) {
+ const item = cartItems.value[index]
+ cartItems.value.splice(index, 1)
+ ElMessage.success(`已从购物车移除 ${item.name}`)
+
+ // 同步购物车数据到后端
+ await syncCartToBackend()
+ }
+}
+
+const clearCart = () => {
+ ElMessageBox.confirm(
+ '确定要清空购物车吗?',
+ '确认清空',
+ {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }
+ ).then(async () => {
+ cartItems.value = []
+ ElMessage.success('购物车已清空')
+
+ // 同步购物车数据到后端
+ await syncCartToBackend()
+ }).catch(() => {})
+ }
+
+// 购物车数据同步到后端
+const syncCartToBackend = async () => {
+ try {
+ const cartData = {
+ items: cartItems.value.map(item => ({
+ productId: item.id,
+ quantity: item.quantity,
+ points: item.points,
+ name: item.name,
+ image: item.image,
+ stock: item.stock
+ }))
+ }
+ await api.post('/cart/sync', cartData)
+ } catch (error) {
+ console.error('购物车同步失败:', error)
+ }
+}
+
+// 从后端读取购物车数据
+const loadCartFromBackend = async () => {
+ cartLoading.value = true
+ try {
+ const response = await api.get('/cart')
+ if (response.data && response.data.items) {
+ cartItems.value = response.data.items
+ }
+ } catch (error) {
+ console.error('购物车数据加载失败:', error)
+ ElMessage.error('购物车数据加载失败,请重试')
+ // 如果加载失败,保持当前购物车状态
+ } finally {
+ cartLoading.value = false
+ }
+}
+
+// 购物车结算功能
+const checkoutCart = async () => {
+ if (cartItems.value.length === 0) {
+ ElMessage.error('购物车是空的')
+ return
+ }
+
+ if (cartTotalPoints.value > userPoints.value) {
+ ElMessage.error('积分不足,无法结算')
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm(
+ `确定要花费 ${cartTotalPoints.value} 积分购买这些商品吗?`,
+ '确认结算',
+ {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }
+ )
+
+ const orderData = {
+ items: cartItems.value.map(item => ({
+ productId: item.id,
+ quantity: item.quantity,
+ points: item.points
+ })),
+ totalPoints: cartTotalPoints.value
+ }
+
+ await api.post('/orders', orderData)
+
+ // 清空购物车
+ cartItems.value = []
+ showCart.value = false
+
+ ElMessage.success('结算成功!')
+ router.push('/orders')
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('结算失败,请重试')
+ }
+ }
}
const buyNow = async () => {
@@ -826,4 +1135,202 @@ watch(
grid-template-columns: repeat(2, 1fr);
}
}
+
+/* 购物车样式 */
+.cart-content {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.cart-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 50%;
+ color: #666;
+ gap: 12px;
+}
+
+.cart-loading .el-icon {
+ font-size: 32px;
+ color: #409eff;
+}
+
+.empty-cart {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 50%;
+ color: #999;
+}
+
+.empty-icon {
+ font-size: 64px;
+ margin-bottom: 16px;
+ color: #ddd;
+}
+
+.empty-tip {
+ font-size: 14px;
+ margin-top: 8px;
+}
+
+.cart-items {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.cart-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 0;
+ border-bottom: 1px solid #eee;
+ font-size: 14px;
+ color: #666;
+}
+
+.clear-btn {
+ color: #ff4757;
+ font-size: 12px;
+}
+
+.cart-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px 0;
+}
+
+.cart-item {
+ display: flex;
+ gap: 12px;
+ padding: 16px 0;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.cart-item:last-child {
+ border-bottom: none;
+}
+
+.item-image {
+ width: 60px;
+ height: 60px;
+ flex-shrink: 0;
+}
+
+.item-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 4px;
+}
+
+.item-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.item-name {
+ margin: 0 0 8px 0;
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ line-height: 1.4;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.item-price {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: #ff6b35;
+ font-weight: 600;
+ font-size: 14px;
+ margin-bottom: 4px;
+}
+
+.item-stock {
+ font-size: 12px;
+ color: #999;
+}
+
+.item-actions {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 8px;
+}
+
+.quantity-control {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.quantity-control .el-button {
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ min-height: 24px;
+}
+
+.quantity {
+ font-size: 14px;
+ font-weight: 500;
+ min-width: 20px;
+ text-align: center;
+}
+
+.remove-btn {
+ color: #ff4757;
+ font-size: 12px;
+ padding: 0;
+}
+
+.cart-footer {
+ border-top: 1px solid #eee;
+ padding: 16px 0 0 0;
+ margin-top: auto;
+}
+
+.total-info {
+ margin-bottom: 16px;
+}
+
+.total-points {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 8px;
+}
+
+.total-points .points {
+ color: #ff6b35;
+ font-size: 18px;
+}
+
+.user-points {
+ font-size: 14px;
+ color: #666;
+}
+
+.checkout-actions {
+ display: flex;
+}
+
+.checkout-btn {
+ width: 100%;
+ height: 44px;
+}
\ No newline at end of file