fake_shop/app/checkout/page.jsx

247 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Image from "next/image";
import PaymentModal from "@/components/PaymentModal";
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);
const [showPaymentModal, setShowPaymentModal] = useState(false);
// 获取地址的函数
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()}`;
};
const handleSubmitOrder = () => {
setShowPaymentModal(true);
};
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}
onClick={handleSubmitOrder}
>
提交订单
</button>
</div>
<PaymentModal
isOpen={showPaymentModal}
onClose={() => setShowPaymentModal(false)}
amount={total}
/>
</div>
</div>
);
}
export default function Checkout() {
return (
<Suspense fallback={<div className="p-4">加载中...</div>}>
<CheckoutContent />
</Suspense>
);
}