198 lines
5.3 KiB
React
198 lines
5.3 KiB
React
|
"use client";
|
|||
|
|
|||
|
import { useParams } from "next/navigation";
|
|||
|
import { useEffect, useState } from "react";
|
|||
|
import Image from "next/image";
|
|||
|
import { useRouter } from "next/navigation";
|
|||
|
import toast from "react-hot-toast";
|
|||
|
import AddToCartModal from "@/components/AddToCartModal";
|
|||
|
|
|||
|
export default function ProductDetail() {
|
|||
|
const params = useParams();
|
|||
|
const [product, setProduct] = useState(null);
|
|||
|
const [loading, setLoading] = useState(true);
|
|||
|
const [error, setError] = useState(null);
|
|||
|
const [quantity, setQuantity] = useState(1);
|
|||
|
const router = useRouter();
|
|||
|
const [showModal, setShowModal] = useState(false);
|
|||
|
|
|||
|
useEffect(() => {
|
|||
|
const fetchProduct = async () => {
|
|||
|
try {
|
|||
|
const res = await fetch(`/api/products/${params.id}`);
|
|||
|
if (!res.ok) {
|
|||
|
throw new Error("获取商品详情失败");
|
|||
|
}
|
|||
|
const data = await res.json();
|
|||
|
setProduct(data);
|
|||
|
} catch (err) {
|
|||
|
setError(err.message);
|
|||
|
} finally {
|
|||
|
setLoading(false);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
fetchProduct();
|
|||
|
}, [params.id]);
|
|||
|
|
|||
|
const handleAddToCart = async () => {
|
|||
|
try {
|
|||
|
const token = localStorage.getItem("token");
|
|||
|
if (!token) {
|
|||
|
router.push("/login");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
const res = await fetch("/api/cart", {
|
|||
|
method: "POST",
|
|||
|
headers: {
|
|||
|
"Content-Type": "application/json",
|
|||
|
Authorization: `Bearer ${token}`,
|
|||
|
},
|
|||
|
body: JSON.stringify({
|
|||
|
productId: params.id,
|
|||
|
quantity: quantity,
|
|||
|
}),
|
|||
|
});
|
|||
|
|
|||
|
if (!res.ok) {
|
|||
|
throw new Error("添加购物车失败");
|
|||
|
}
|
|||
|
|
|||
|
toast.success("添加成功");
|
|||
|
handleModalClose();
|
|||
|
} catch (error) {
|
|||
|
toast.error(error.message);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
const handleBuyNow = () => {
|
|||
|
const token = localStorage.getItem("token");
|
|||
|
if (!token) {
|
|||
|
router.push("/login");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
router.push(`/checkout?products=${params.id}&quantity=${quantity}`);
|
|||
|
};
|
|||
|
|
|||
|
const handleAddToCartClick = () => {
|
|||
|
const token = localStorage.getItem("token");
|
|||
|
if (!token) {
|
|||
|
router.push("/login");
|
|||
|
return;
|
|||
|
}
|
|||
|
setShowModal(true);
|
|||
|
};
|
|||
|
|
|||
|
const handleModalClose = () => {
|
|||
|
setShowModal(false);
|
|||
|
};
|
|||
|
|
|||
|
if (loading) {
|
|||
|
return <div className="p-4">加载中...</div>;
|
|||
|
}
|
|||
|
|
|||
|
if (error) {
|
|||
|
return <div className="p-4 text-red-500">错误: {error}</div>;
|
|||
|
}
|
|||
|
|
|||
|
if (!product) {
|
|||
|
return <div className="p-4">商品不存在</div>;
|
|||
|
}
|
|||
|
|
|||
|
return (
|
|||
|
<div className="flex flex-col min-h-screen pb-20 bg-gray-50">
|
|||
|
{/* 商品图片 */}
|
|||
|
<div className="relative aspect-square">
|
|||
|
<Image
|
|||
|
src={product.imageUrl}
|
|||
|
alt={product.title}
|
|||
|
fill
|
|||
|
className="object-cover"
|
|||
|
/>
|
|||
|
</div>
|
|||
|
|
|||
|
{/* 商品信息 */}
|
|||
|
<div className="flex-1 p-4 bg-white">
|
|||
|
<div className="flex items-center">
|
|||
|
<span className="text-2xl text-red-500 font-bold">
|
|||
|
¥{product.price}
|
|||
|
</span>
|
|||
|
{product.isVip && (
|
|||
|
<span className="ml-2 px-2 py-0.5 text-xs text-yellow-600 border border-yellow-600 rounded">
|
|||
|
VIP
|
|||
|
</span>
|
|||
|
)}
|
|||
|
</div>
|
|||
|
|
|||
|
<h1 className="mt-2 text-lg font-medium">{product.title}</h1>
|
|||
|
|
|||
|
<div className="mt-2 text-sm text-gray-500">
|
|||
|
<span>库存: {product.stock}件</span>
|
|||
|
<span className="ml-4">销量: {product.sales}件</span>
|
|||
|
</div>
|
|||
|
|
|||
|
{/* 配送信息 */}
|
|||
|
<div className="mt-4 p-3 bg-gray-50 rounded-lg">
|
|||
|
<div className="flex justify-between items-center">
|
|||
|
<span className="text-gray-500">配送</span>
|
|||
|
<span>{product.delivery}</span>
|
|||
|
</div>
|
|||
|
<div className="mt-2 flex justify-between items-center">
|
|||
|
<span className="text-gray-500">服务</span>
|
|||
|
<div className="flex gap-2">
|
|||
|
{product.services.map((service, index) => (
|
|||
|
<span key={index} className="text-sm text-gray-600">
|
|||
|
{service}
|
|||
|
</span>
|
|||
|
))}
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
{/* 商品描述 */}
|
|||
|
<div className="mt-6">
|
|||
|
<h2 className="text-lg font-bold mb-2">商品详情</h2>
|
|||
|
<p className="text-gray-600">{product.description}</p>
|
|||
|
|
|||
|
<h3 className="mt-4 font-bold">产品特点:</h3>
|
|||
|
<ul className="mt-2 space-y-2">
|
|||
|
{product.features.map((feature, index) => (
|
|||
|
<li key={index} className="flex items-center text-gray-600">
|
|||
|
<span className="mr-2">•</span>
|
|||
|
{feature}
|
|||
|
</li>
|
|||
|
))}
|
|||
|
</ul>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
{/* 底部购买栏 */}
|
|||
|
<div className="fixed bottom-0 left-0 right-0 flex items-center p-4 bg-white border-t">
|
|||
|
<button
|
|||
|
className="flex-1 bg-yellow-400 text-white py-2 rounded-full"
|
|||
|
onClick={handleAddToCartClick}
|
|||
|
>
|
|||
|
加入购物车
|
|||
|
</button>
|
|||
|
<button
|
|||
|
className="flex-1 ml-2 bg-red-500 text-white py-2 rounded-full"
|
|||
|
onClick={handleBuyNow}
|
|||
|
>
|
|||
|
立即购买
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
|
|||
|
<AddToCartModal
|
|||
|
isOpen={showModal}
|
|||
|
onClose={handleModalClose}
|
|||
|
product={product}
|
|||
|
quantity={quantity}
|
|||
|
setQuantity={setQuantity}
|
|||
|
onAddToCart={handleAddToCart}
|
|||
|
/>
|
|||
|
</div>
|
|||
|
);
|
|||
|
}
|