feat: 支持游客购物车,下单时才需要登录
Build and Push Docker Image / build-and-push (push) Successful in 1m1s
Build and Push Docker Image / deploy (push) Successful in 8s

- cart.ts: 使用 localStorage 存储游客购物车,登录后合并
- Cart.vue: 下单时检查登录状态
- Login.vue: 登录成功后自动合并游客购物车
- api/index.ts: cart list 使用静默模式避免 401 跳转

游客可以浏览商品、添加购物车,只有下单时才需要登录
This commit is contained in:
2026-07-13 01:51:56 +00:00
parent db2a2592ea
commit bdc9285845
4 changed files with 135 additions and 16 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ export const productApi = {
} }
export const cartApi = { export const cartApi = {
list: () => api.get('/cart'), list: () => api.get('/cart', { silent: true } as any),
add: (data: any) => api.post('/cart', data), add: (data: any) => api.post('/cart', data),
update: (id: number, data: any) => api.put(`/cart/${id}`, data), update: (id: number, data: any) => api.put(`/cart/${id}`, data),
delete: (id: number) => api.delete(`/cart/${id}`), delete: (id: number) => api.delete(`/cart/${id}`),
+120 -15
View File
@@ -2,51 +2,154 @@ import { ref, watch } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { cartApi } from '../api' import { cartApi } from '../api'
const LOCAL_CART_KEY = 'guest_cart'
export const useCartStore = defineStore('cart', () => { export const useCartStore = defineStore('cart', () => {
const items = ref<any[]>([]) const items = ref<any[]>([])
const loading = ref(false) const loading = ref(false)
const totalAmount = ref(0) const totalAmount = ref(0)
const totalCount = ref(0) const totalCount = ref(0)
// 检查是否登录
function isLoggedIn(): boolean {
return !!localStorage.getItem('token')
}
// 获取游客购物车
function getGuestCart(): any[] {
const cartStr = localStorage.getItem(LOCAL_CART_KEY)
return cartStr ? JSON.parse(cartStr) : []
}
// 保存游客购物车
function saveGuestCart(cart: any[]) {
localStorage.setItem(LOCAL_CART_KEY, JSON.stringify(cart))
}
// 获取购物车(登录用户从服务器,游客从本地)
async function fetchCart() { async function fetchCart() {
loading.value = true loading.value = true
try { try {
const res: any = await cartApi.list() if (isLoggedIn()) {
items.value = res.data || [] // 登录用户:从服务器获取
const res: any = await cartApi.list()
items.value = res.data || []
} else {
// 游客:从本地存储获取
items.value = getGuestCart()
}
} catch {
// 登录用户API失败时,也尝试使用本地数据
items.value = getGuestCart()
} finally { } finally {
loading.value = false loading.value = false
} }
} }
async function addItem(productId: number, quantity: number) { // 添加商品到购物车
await cartApi.add({ product_id: productId, quantity }) async function addItem(productId: number, quantity: number, product?: any) {
await fetchCart() if (isLoggedIn()) {
// 登录用户:添加到服务器
await cartApi.add({ product_id: productId, quantity })
await fetchCart()
} else {
// 游客:添加到本地
const guestCart = getGuestCart()
const existingItem = guestCart.find(item => item.product_id === productId)
if (existingItem) {
existingItem.quantity += quantity
} else {
guestCart.push({
id: Date.now(), // 临时ID
product_id: productId,
quantity,
product: product || null
})
}
saveGuestCart(guestCart)
items.value = guestCart
}
} }
// 更新购物车项数量
async function updateItem(id: number, quantity: number) { async function updateItem(id: number, quantity: number) {
await cartApi.update(id, { quantity }) if (isLoggedIn()) {
await fetchCart() await cartApi.update(id, { quantity })
await fetchCart()
} else {
// 游客:更新本地
const guestCart = getGuestCart()
const item = guestCart.find(item => item.id === id)
if (item) {
item.quantity = quantity
saveGuestCart(guestCart)
items.value = guestCart
}
}
} }
// 删除购物车项
async function removeItem(id: number) { async function removeItem(id: number) {
await cartApi.delete(id) if (isLoggedIn()) {
await fetchCart() await cartApi.delete(id)
await fetchCart()
} else {
// 游客:从本地删除
const guestCart = getGuestCart()
const filtered = guestCart.filter(item => item.id !== id)
saveGuestCart(filtered)
items.value = filtered
}
} }
// 删除多个购物车项
async function removeItems(ids: number[]) { async function removeItems(ids: number[]) {
await Promise.all(ids.map(id => cartApi.delete(id))) if (isLoggedIn()) {
await fetchCart() await Promise.all(ids.map(id => cartApi.delete(id)))
await fetchCart()
} else {
// 游客:批量删除本地项
const guestCart = getGuestCart()
const filtered = guestCart.filter(item => !ids.includes(item.id))
saveGuestCart(filtered)
items.value = filtered
}
} }
// 清空购物车
function clearCart() { function clearCart() {
items.value = [] items.value = []
totalAmount.value = 0 totalAmount.value = 0
totalCount.value = 0 totalCount.value = 0
localStorage.removeItem(LOCAL_CART_KEY)
}
// 登录后合并游客购物车到服务器
async function mergeGuestCart() {
const guestCart = getGuestCart()
if (guestCart.length === 0) return
// 将游客购物车中的商品添加到服务器购物车
for (const item of guestCart) {
try {
await cartApi.add({ product_id: item.product_id, quantity: item.quantity })
} catch {
// 单个商品添加失败不影响其他商品
}
}
// 清空游客购物车
localStorage.removeItem(LOCAL_CART_KEY)
// 刷新购物车
await fetchCart()
} }
function calcTotals() { function calcTotals() {
totalAmount.value = items.value.reduce((sum, item) => sum + (item.product?.price || 0) * item.quantity, 0) totalAmount.value = items.value.reduce((sum: number, item: any) => sum + (item.product?.price || 0) * item.quantity, 0)
totalCount.value = items.value.reduce((sum, item) => sum + item.quantity, 0) totalCount.value = items.value.reduce((sum: number, item: any) => sum + item.quantity, 0)
} }
watch(items, calcTotals, { deep: true }) watch(items, calcTotals, { deep: true })
@@ -61,6 +164,8 @@ export const useCartStore = defineStore('cart', () => {
updateItem, updateItem,
removeItem, removeItem,
removeItems, removeItems,
clearCart clearCart,
mergeGuestCart,
isLoggedIn
} }
}) })
+6
View File
@@ -54,6 +54,12 @@ async function handleLogin() {
loading.value = true loading.value = true
try { try {
await userStore.login(form) await userStore.login(form)
// 登录成功后合并游客购物车
const { useCartStore } = await import('../../store/cart')
const cartStore = useCartStore()
await cartStore.mergeGuestCart()
ElMessage.success(t('auth.loginSuccess')) ElMessage.success(t('auth.loginSuccess'))
const user = userStore.user const user = userStore.user
if (user?.role === 'admin') router.push('/admin') if (user?.role === 'admin') router.push('/admin')
+8
View File
@@ -2,6 +2,7 @@
<div class="cart-page"> <div class="cart-page">
<BackNav /> <BackNav />
<!-- 购物车为空 -->
<el-empty v-if="!items.length" description="购物车是空的" :image-size="120"> <el-empty v-if="!items.length" description="购物车是空的" :image-size="120">
<template #image> <template #image>
<el-icon :size="80" color="rgba(255,255,255,0.2)"><ShoppingCart /></el-icon> <el-icon :size="80" color="rgba(255,255,255,0.2)"><ShoppingCart /></el-icon>
@@ -573,6 +574,13 @@ async function removeSelected() {
} }
async function handleCheckout() { async function handleCheckout() {
// 检查是否登录
if (!cartStore.isLoggedIn()) {
ElMessage.warning('请先登录后再下单')
router.push('/login')
return
}
if (!canCheckout.value) { if (!canCheckout.value) {
if (!selectedAddressId.value) { if (!selectedAddressId.value) {
ElMessage.warning('请选择收货地址') ElMessage.warning('请选择收货地址')