fake_shop/app/checkout/page.jsx

247 lines
7.2 KiB
React
Raw Normal View History

2025-02-10 19:24:19 +08:00
"use client";
import { useEffect, useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Image from "next/image";
2025-02-11 16:50:04 +08:00
import PaymentModal from "@/components/PaymentModal";
2025-02-10 19:24:19 +08:00
function CheckoutContent() {
const router = useRouter();
const searchParams = useSearchParams();
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [address, setAddress] = useState(null);
2025-02-11 16:50:04 +08:00
const [showPaymentModal, setShowPaymentModal] = useState(false);
2025-02-10 19:24:19 +08:00
// 获取地址的函数
const fetchAddress = async (token) => {
const addressRes = await fetch("/api/address", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (addressRes.ok) {
const addresses = await addressRes.json();
const selectedAddress = localStorage.getItem("selectedAddress");
if (selectedAddress) {
const parsedSelectedAddress = JSON.parse(selectedAddress);
// 在服务器返回的地址列表中查找选中的地址
const matchedAddress = addresses.find(
(addr) => addr._id === parsedSelectedAddress._id
);
if (matchedAddress) {
setAddress(matchedAddress);
localStorage.removeItem("selectedAddress");
return;
}
}
// 如果没有匹配到选中的地址,使用默认地址
const defaultAddress = addresses.find((addr) => addr.isDefault);
if (defaultAddress) {
setAddress(defaultAddress);
} else if (addresses.length > 0) {
setAddress(addresses[0]);
}
}
};
// 获取商品信息的函数
const fetchProducts = async () => {
const items = searchParams.get("items");
if (items) {
const productPromises = items.split(",").map(async (item) => {
const [productId, quantity] = item.split(":");
const res = await fetch(`/api/products/${productId}`);
if (!res.ok) {
throw new Error("获取商品信息失败");
}
const product = await res.json();
return { ...product, quantity: parseInt(quantity) };
});
const productsData = await Promise.all(productPromises);
setProducts(productsData);
return;
}
const productId = searchParams.get("products");
const quantity = searchParams.get("quantity");
if (!productId || !quantity) {
throw new Error("商品信息不完整");
}
const res = await fetch(`/api/products/${productId}`);
if (!res.ok) {
throw new Error("获取商品信息失败");
}
const product = await res.json();
setProducts([{ ...product, quantity: parseInt(quantity) }]);
};
useEffect(() => {
const token = localStorage.getItem("token");
if (!token) {
router.push("/login");
return;
}
const fetchData = async () => {
try {
await fetchAddress(token);
await fetchProducts();
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <div className="p-4">加载中...</div>;
if (error) return <div className="p-4 text-red-500">错误: {error}</div>;
const total = products.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const getReturnUrl = () => {
const items = searchParams.get("items");
const products = searchParams.get("products");
const quantity = searchParams.get("quantity");
let params = new URLSearchParams();
if (items) {
params.append("items", items);
} else if (products && quantity) {
params.append("products", products);
params.append("quantity", quantity);
}
return `/address?returnUrl=${encodeURIComponent(
"/checkout"
)}&${params.toString()}`;
};
2025-02-11 16:50:04 +08:00
const handleSubmitOrder = () => {
setShowPaymentModal(true);
};
2025-02-10 19:24:19 +08:00
return (
<div className="min-h-screen bg-gray-50 pb-20">
<div className="p-4">
<h1 className="text-xl font-bold mb-4">确认订单</h1>
{/* 收货地址 */}
<button
onClick={() => router.replace(getReturnUrl())}
className="block w-full bg-white rounded-lg p-4 mb-4 text-left"
>
{address ? (
<div className="flex justify-between items-center">
<div>
<div className="flex items-center gap-2">
<span className="font-medium">{address.name}</span>
<span>{address.phone}</span>
</div>
<div className="text-gray-500 text-sm mt-1">
{address.address}
</div>
</div>
<svg
className="w-4 h-4 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</div>
) : (
<div className="flex justify-between items-center text-gray-500">
<span>请选择收货地址</span>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</div>
)}
</button>
{/* 商品列表 */}
<div className="bg-white rounded-lg p-4 mb-4">
{products.map((product) => (
<div key={product._id} className="flex items-center">
<Image
src={product.imageUrl}
alt={product.title}
width={80}
height={80}
className="rounded"
/>
<div className="ml-4 flex-1">
<h3 className="font-medium">{product.title}</h3>
<div className="flex justify-between mt-2">
<span className="text-red-500">¥{product.price}</span>
<span>x{product.quantity}</span>
</div>
</div>
</div>
))}
</div>
{/* 订单总计 */}
<div className="fixed bottom-0 left-0 right-0 bg-white p-4 border-t">
<div className="flex justify-between items-center mb-4">
<span>总计</span>
<span className="text-xl text-red-500 font-bold">¥{total}</span>
</div>
<button
className="w-full bg-red-500 text-white py-3 rounded-full"
disabled={!address}
2025-02-11 16:50:04 +08:00
onClick={handleSubmitOrder}
2025-02-10 19:24:19 +08:00
>
提交订单
</button>
</div>
2025-02-11 16:50:04 +08:00
<PaymentModal
isOpen={showPaymentModal}
onClose={() => setShowPaymentModal(false)}
amount={total}
/>
2025-02-10 19:24:19 +08:00
</div>
</div>
);
}
export default function Checkout() {
return (
<Suspense fallback={<div className="p-4">加载中...</div>}>
<CheckoutContent />
</Suspense>
);
}