210 lines
6.9 KiB
TypeScript
210 lines
6.9 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import Link from "next/link";
|
||
import { useParams, useRouter } from "next/navigation";
|
||
|
||
interface House {
|
||
id: string;
|
||
owner: string;
|
||
title: string;
|
||
description: string;
|
||
price: number;
|
||
district: string;
|
||
address: string;
|
||
phone: string;
|
||
images: string[];
|
||
createdAt: string;
|
||
}
|
||
|
||
export default function HouseDetail() {
|
||
const params = useParams();
|
||
const router = useRouter();
|
||
const [house, setHouse] = useState<House | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [currentImage, setCurrentImage] = useState(0);
|
||
const [showContact, setShowContact] = useState(false);
|
||
|
||
useEffect(() => {
|
||
fetchHouse();
|
||
}, []);
|
||
|
||
async function fetchHouse() {
|
||
try {
|
||
const res = await fetch(`/api/houses/${params.id}`);
|
||
const data = await res.json();
|
||
if (data.house) {
|
||
setHouse(data.house);
|
||
}
|
||
} catch (error) {
|
||
console.error("获取房屋详情失败", error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
function formatDate(dateStr: string) {
|
||
return new Date(dateStr).toLocaleDateString("zh-CN");
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center">
|
||
<div className="text-center">
|
||
<div className="text-4xl mb-2 animate-bounce">🏠</div>
|
||
<p className="text-gray-500">加载中...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!house) {
|
||
return (
|
||
<div className="min-h-screen flex flex-col items-center justify-center">
|
||
<div className="text-6xl mb-4">😢</div>
|
||
<p className="text-gray-500 mb-4">房屋不存在或已下架</p>
|
||
<Link href="/" className="px-4 py-2 bg-orange-500 text-white rounded-lg">
|
||
返回首页
|
||
</Link>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const images = house.images && house.images.length > 0 ? house.images : [];
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 pb-32">
|
||
<header className="bg-white sticky top-0 z-20 px-4 py-3 flex items-center gap-4 shadow-sm">
|
||
<button onClick={() => router.back()} className="text-2xl">
|
||
←
|
||
</button>
|
||
<h1 className="font-semibold text-gray-900 truncate flex-1">房屋详情</h1>
|
||
</header>
|
||
|
||
{images.length > 0 ? (
|
||
<div className="relative">
|
||
<div className="overflow-x-auto snap-x snap-mandatory flex">
|
||
{images.map((img, index) => (
|
||
<img
|
||
key={index}
|
||
src={img}
|
||
alt={`${house.title} ${index + 1}`}
|
||
className="w-full h-72 object-cover flex-shrink-0 snap-center"
|
||
/>
|
||
))}
|
||
</div>
|
||
{images.length > 1 && (
|
||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
|
||
{images.map((_, index) => (
|
||
<button
|
||
key={index}
|
||
onClick={() => setCurrentImage(index)}
|
||
className={`w-2 h-2 rounded-full ${
|
||
index === currentImage ? "bg-white" : "bg-white/50"
|
||
}`}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<div className="w-full h-72 bg-gradient-to-br from-orange-100 to-orange-200 flex items-center justify-center text-8xl">
|
||
🏠
|
||
</div>
|
||
)}
|
||
|
||
<div className="px-4 py-4">
|
||
<div className="bg-white rounded-xl p-4 mb-4 shadow-sm">
|
||
<div className="flex items-start justify-between mb-3">
|
||
<div>
|
||
<h2 className="text-xl font-bold text-gray-900 mb-1">{house.title}</h2>
|
||
<span className="inline-block bg-orange-100 text-orange-600 text-xs px-2 py-1 rounded">
|
||
{house.district}
|
||
</span>
|
||
</div>
|
||
<div className="text-right">
|
||
<span className="text-2xl font-bold text-orange-500">¥{house.price}</span>
|
||
<span className="text-gray-400 text-sm">/月</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center text-gray-500 text-sm mb-3">
|
||
<span className="text-lg mr-2">📍</span>
|
||
<span>{house.address}</span>
|
||
</div>
|
||
|
||
<div className="flex items-center text-gray-500 text-sm">
|
||
<span className="text-lg mr-2">👤</span>
|
||
<span>房东:{house.owner}</span>
|
||
</div>
|
||
</div>
|
||
|
||
{house.description && (
|
||
<div className="bg-white rounded-xl p-4 mb-4 shadow-sm">
|
||
<h3 className="font-semibold text-gray-900 mb-2">房屋描述</h3>
|
||
<p className="text-gray-600 text-sm leading-relaxed whitespace-pre-wrap">
|
||
{house.description}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
<div className="bg-white rounded-xl p-4 shadow-sm">
|
||
<h3 className="font-semibold text-gray-900 mb-3">发布时间</h3>
|
||
<p className="text-gray-500 text-sm">{formatDate(house.createdAt)}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-4 max-w-lg mx-auto">
|
||
<button
|
||
onClick={() => setShowContact(true)}
|
||
className="w-full py-4 bg-gradient-to-r from-orange-500 to-orange-600 text-white font-semibold rounded-xl shadow-lg active:scale-[0.98] transition-transform"
|
||
>
|
||
📞 联系房东
|
||
</button>
|
||
</div>
|
||
|
||
{showContact && (
|
||
<div className="fixed inset-0 bg-black/50 z-50 flex items-end">
|
||
<div className="bg-white w-full rounded-t-3xl p-6 animate-slide-up">
|
||
<div className="w-12 h-1 bg-gray-300 rounded-full mx-auto mb-6"></div>
|
||
<h3 className="text-xl font-bold text-gray-900 mb-4 text-center">联系方式</h3>
|
||
<div className="bg-orange-50 rounded-xl p-6 text-center mb-6">
|
||
<div className="text-4xl mb-2">📱</div>
|
||
<p className="text-3xl font-bold text-orange-500 tracking-wider">{house.phone}</p>
|
||
<p className="text-gray-500 text-sm mt-2">拨打前请说明在平台上看到</p>
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<a
|
||
href={`tel:${house.phone}`}
|
||
className="flex-1 py-4 bg-orange-500 text-white font-semibold rounded-xl text-center"
|
||
>
|
||
立即拨打
|
||
</a>
|
||
<button
|
||
onClick={() => setShowContact(false)}
|
||
className="flex-1 py-4 bg-gray-100 text-gray-700 font-semibold rounded-xl"
|
||
>
|
||
关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<style jsx>{`
|
||
@keyframes slide-up {
|
||
from {
|
||
transform: translateY(100%);
|
||
}
|
||
to {
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
.animate-slide-up {
|
||
animation: slide-up 0.3s ease-out;
|
||
}
|
||
`}</style>
|
||
</div>
|
||
);
|
||
}
|