212 lines
8.1 KiB
TypeScript
212 lines
8.1 KiB
TypeScript
"use client"
|
|
|
|
import { MainNav } from "@/components/main-nav"
|
|
import { SearchBar } from "@/components/search-bar"
|
|
import { ThemeToggle } from "@/components/theme-toggle"
|
|
import { UserNav } from "@/components/user-nav"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { useToast } from "@/components/ui/use-toast"
|
|
import { useAuth } from "@/context/auth-context"
|
|
import { useCart } from "@/context/cart-context"
|
|
import { Minus, Plus, ShoppingCart, Trash2 } from "lucide-react"
|
|
import { useRouter } from "next/navigation"
|
|
import { fetchWithAuth } from "@/lib/api"
|
|
|
|
export default function CartPage() {
|
|
const { cart, updateQuantity, removeFromCart, clearCart } = useCart()
|
|
const { user } = useAuth()
|
|
const { toast } = useToast()
|
|
const router = useRouter()
|
|
|
|
const totalPrice = cart.reduce((total, item) => total + item.price * item.quantity, 0)
|
|
|
|
const handleCheckout = async () => {
|
|
if (!user) {
|
|
toast({
|
|
title: "请先登录",
|
|
description: "您需要登录后才能结账",
|
|
variant: "destructive",
|
|
})
|
|
router.push("/login")
|
|
return
|
|
}
|
|
|
|
if (cart.length === 0) {
|
|
toast({
|
|
title: "购物车为空",
|
|
description: "请先添加商品到购物车",
|
|
variant: "destructive",
|
|
})
|
|
return
|
|
}
|
|
|
|
try {
|
|
const orderData = {
|
|
items: cart.map((item) => ({
|
|
bookId: item.id,
|
|
quantity: item.quantity,
|
|
})),
|
|
}
|
|
|
|
const response = await fetchWithAuth("orders/create", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(orderData),
|
|
})
|
|
|
|
const result = await response.json()
|
|
|
|
if (result.code === 0) {
|
|
toast({
|
|
title: "订单创建成功",
|
|
description: `订单号: ${result.data.orderId}`,
|
|
})
|
|
clearCart()
|
|
router.push(`/orders/${result.data.orderId}`)
|
|
} else {
|
|
toast({
|
|
variant: "destructive",
|
|
title: "订单创建失败",
|
|
description: result.msg || "创建订单时出错",
|
|
})
|
|
}
|
|
} catch (error) {
|
|
toast({
|
|
variant: "destructive",
|
|
title: "订单创建失败",
|
|
description: "服务器连接错误,请稍后再试",
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen flex-col">
|
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
<div className="container flex h-16 items-center justify-between">
|
|
<MainNav />
|
|
<div className="flex items-center gap-4">
|
|
<SearchBar />
|
|
<ThemeToggle />
|
|
<UserNav />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<main className="flex-1">
|
|
<div className="container py-8">
|
|
<div className="flex flex-col space-y-6">
|
|
<div className="flex items-center space-x-2">
|
|
<ShoppingCart className="h-6 w-6" />
|
|
<h1 className="text-3xl font-bold tracking-tight">购物车</h1>
|
|
</div>
|
|
{cart.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center py-12">
|
|
<ShoppingCart className="h-12 w-12 text-muted-foreground mb-4" />
|
|
<h2 className="text-xl font-semibold">您的购物车是空的</h2>
|
|
<p className="text-muted-foreground mt-2">去添加一些图书吧</p>
|
|
<Button className="mt-4" onClick={() => router.push("/books")}>
|
|
浏览图书
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-8 md:grid-cols-3">
|
|
<div className="md:col-span-2 space-y-4">
|
|
{cart.map((item) => (
|
|
<Card key={item.id}>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center gap-4">
|
|
<img
|
|
src={item.coverImage || "/placeholder.svg?height=80&width=60"}
|
|
alt={item.title}
|
|
className="h-20 w-16 object-cover rounded"
|
|
/>
|
|
<div className="flex-1 space-y-1">
|
|
<h3 className="font-medium">{item.title}</h3>
|
|
<p className="text-sm text-muted-foreground">单价: ¥{item.price.toFixed(2)}</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}
|
|
>
|
|
<Minus className="h-4 w-4" />
|
|
</Button>
|
|
<Input
|
|
type="number"
|
|
min="1"
|
|
value={item.quantity}
|
|
onChange={(e) => updateQuantity(item.id, Number.parseInt(e.target.value) || 1)}
|
|
className="h-8 w-16 text-center"
|
|
/>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => updateQuantity(item.id, item.quantity + 1)}
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
<div className="w-20 text-right">¥{(item.price * item.quantity).toFixed(2)}</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8 text-destructive"
|
|
onClick={() => removeFromCart(item.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
<div>
|
|
<Card className="sticky top-24">
|
|
<CardHeader>
|
|
<CardTitle>订单摘要</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex justify-between">
|
|
<span>小计</span>
|
|
<span>¥{totalPrice.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>运费</span>
|
|
<span>免费</span>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex justify-between font-medium">
|
|
<span>总计</span>
|
|
<span>¥{totalPrice.toFixed(2)}</span>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter>
|
|
<Button className="w-full" onClick={handleCheckout}>
|
|
结账
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</main>
|
|
<footer className="border-t py-6 md:py-0">
|
|
<div className="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
|
<p className="text-center text-sm leading-loose text-muted-foreground md:text-left">
|
|
© {new Date().getFullYear()} 图书管理系统. 保留所有权利.
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
)
|
|
}
|