Files
smalltown/app/page.tsx

187 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}