feat: 城中村租房平台 - 租客浏览、房东发布房源、图片上传
This commit is contained in:
209
app/house/[id]/page.tsx
Normal file
209
app/house/[id]/page.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user