187 lines
6.6 KiB
TypeScript
187 lines
6.6 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect, Suspense } from "react";
|
||
import Link from "next/link";
|
||
import { useSearchParams } from "next/navigation";
|
||
|
||
interface House {
|
||
id: string;
|
||
title: string;
|
||
description: string;
|
||
price: number;
|
||
district: string;
|
||
address: string;
|
||
phone: string;
|
||
images: string[];
|
||
createdAt: string;
|
||
}
|
||
|
||
function HomeContent() {
|
||
const searchParams = useSearchParams();
|
||
const [houses, setHouses] = useState<House[]>([]);
|
||
const [districts, setDistricts] = useState<string[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [district, setDistrict] = useState(searchParams.get("district") || "全部");
|
||
const [keyword, setKeyword] = useState(searchParams.get("keyword") || "");
|
||
|
||
useEffect(() => {
|
||
fetchDistricts();
|
||
fetchHouses();
|
||
}, [district]);
|
||
|
||
async function fetchDistricts() {
|
||
try {
|
||
const res = await fetch("/api/districts");
|
||
const data = await res.json();
|
||
setDistricts(["全部", ...(data.districts || [])]);
|
||
} catch (error) {
|
||
console.error("获取地区失败", error);
|
||
setDistricts(["全部"]);
|
||
}
|
||
}
|
||
|
||
async function fetchHouses() {
|
||
setLoading(true);
|
||
try {
|
||
const params = new URLSearchParams();
|
||
if (district !== "全部") params.set("district", district);
|
||
if (keyword) params.set("keyword", keyword);
|
||
|
||
const res = await fetch(`/api/houses?${params}`);
|
||
const data = await res.json();
|
||
setHouses(data.houses || []);
|
||
} catch (error) {
|
||
console.error("获取房屋失败", error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
function handleSearch(e: React.FormEvent) {
|
||
e.preventDefault();
|
||
fetchHouses();
|
||
}
|
||
|
||
function formatDate(dateStr: string) {
|
||
const date = new Date(dateStr);
|
||
const now = new Date();
|
||
const diff = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
|
||
if (diff === 0) return "今天";
|
||
if (diff === 1) return "昨天";
|
||
if (diff < 7) return `${diff}天前`;
|
||
return `${Math.floor(diff / 7)}周前`;
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen pb-20">
|
||
<header className="bg-white sticky top-0 z-10 shadow-sm">
|
||
<div className="px-4 py-3">
|
||
<h1 className="text-xl font-bold text-orange-500 mb-3">🏠 城中村租房</h1>
|
||
<form onSubmit={handleSearch} className="flex gap-2">
|
||
<select
|
||
value={district}
|
||
onChange={(e) => setDistrict(e.target.value)}
|
||
className="px-3 py-2 rounded-lg border border-gray-200 text-sm bg-white flex-shrink-0"
|
||
>
|
||
{districts.map((d) => (
|
||
<option key={d} value={d}>{d}</option>
|
||
))}
|
||
</select>
|
||
<input
|
||
type="text"
|
||
value={keyword}
|
||
onChange={(e) => setKeyword(e.target.value)}
|
||
placeholder="搜索区域、小区名称..."
|
||
className="flex-1 px-3 py-2 rounded-lg border border-gray-200 text-sm"
|
||
/>
|
||
<button type="submit" className="px-4 py-2 bg-orange-500 text-white rounded-lg text-sm font-medium">
|
||
搜索
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</header>
|
||
|
||
<main className="px-4 py-4">
|
||
{loading ? (
|
||
<div className="space-y-4">
|
||
{[1, 2, 3].map((i) => (
|
||
<div key={i} className="bg-white rounded-xl p-4 animate-pulse">
|
||
<div className="bg-gray-200 h-40 rounded-lg mb-3"></div>
|
||
<div className="bg-gray-200 h-5 w-3/4 rounded mb-2"></div>
|
||
<div className="bg-gray-200 h-4 w-1/2 rounded"></div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : houses.length === 0 ? (
|
||
<div className="text-center py-20">
|
||
<div className="text-6xl mb-4">🏠</div>
|
||
<p className="text-gray-500 mb-2">暂无房源</p>
|
||
<p className="text-gray-400 text-sm">看看其他区域吧</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{houses.map((house) => (
|
||
<Link key={house.id} href={`/house/${house.id}`}>
|
||
<article className="bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow">
|
||
<div className="relative">
|
||
{house.images && house.images.length > 0 ? (
|
||
<img
|
||
src={house.images[0]}
|
||
alt={house.title}
|
||
className="w-full h-48 object-cover"
|
||
/>
|
||
) : (
|
||
<div className="w-full h-48 bg-gradient-to-br from-orange-100 to-orange-200 flex items-center justify-center text-6xl">
|
||
🏠
|
||
</div>
|
||
)}
|
||
<span className="absolute top-2 left-2 bg-orange-500 text-white text-xs px-2 py-1 rounded">
|
||
{house.district}
|
||
</span>
|
||
</div>
|
||
<div className="p-4">
|
||
<h3 className="font-semibold text-gray-900 mb-1 line-clamp-1">{house.title}</h3>
|
||
<p className="text-gray-500 text-sm mb-2 line-clamp-1">{house.address}</p>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-orange-500 font-bold text-lg">
|
||
¥{house.price}<span className="text-gray-400 text-sm font-normal">/月</span>
|
||
</span>
|
||
<span className="text-gray-400 text-xs">{formatDate(house.createdAt)}</span>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
)}
|
||
</main>
|
||
|
||
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-6 py-2 flex justify-between items-center max-w-lg mx-auto">
|
||
<div className="flex flex-col items-center text-orange-500">
|
||
<span className="text-xl">🏠</span>
|
||
<span className="text-xs mt-1">找房</span>
|
||
</div>
|
||
<Link href="/owner" className="flex flex-col items-center text-gray-400 hover:text-orange-500 transition-colors">
|
||
<span className="text-xl">🏗️</span>
|
||
<span className="text-xs mt-1">房东入口</span>
|
||
</Link>
|
||
</nav>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function Home() {
|
||
return (
|
||
<Suspense fallback={
|
||
<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>
|
||
}>
|
||
<HomeContent />
|
||
</Suspense>
|
||
);
|
||
}
|