feat: 创建房间时支持自定义筛选条件(距离/人均/菜系)
替换硬编码搜索参数,用户可选择距离范围(1/3/5km)、 人均价格区间(50以下/50-100/100+)和菜系偏好(火锅、 日料、烧烤等),直接提升推荐质量。
This commit is contained in:
@@ -70,12 +70,42 @@ function mapPoiToRestaurant(poi: AmapPoiV5): Restaurant {
|
||||
};
|
||||
}
|
||||
|
||||
function filterByPrice(
|
||||
restaurants: Restaurant[],
|
||||
priceRange: string,
|
||||
): Restaurant[] {
|
||||
if (priceRange === "any") return restaurants;
|
||||
|
||||
return restaurants.filter((r) => {
|
||||
if (r.price === "未知") return true;
|
||||
const cost = parseInt(r.price.replace(/[¥¥]/g, ""), 10);
|
||||
if (isNaN(cost)) return true;
|
||||
|
||||
switch (priceRange) {
|
||||
case "under50":
|
||||
return cost < 50;
|
||||
case "50to100":
|
||||
return cost >= 50 && cost <= 100;
|
||||
case "over100":
|
||||
return cost > 100;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
let restaurants: Restaurant[] = fallbackRestaurants;
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { lat, lng } = body;
|
||||
const {
|
||||
lat,
|
||||
lng,
|
||||
radius = 3000,
|
||||
priceRange = "any",
|
||||
cuisine = "不限",
|
||||
} = body;
|
||||
|
||||
if (lat && lng) {
|
||||
const apiKey = process.env.AMAP_API_KEY;
|
||||
@@ -85,17 +115,25 @@ export async function POST(req: Request) {
|
||||
const url = new URL("https://restapi.amap.com/v5/place/around");
|
||||
url.searchParams.set("key", apiKey);
|
||||
url.searchParams.set("location", `${lng},${lat}`);
|
||||
url.searchParams.set("radius", "3000");
|
||||
url.searchParams.set("radius", String(radius));
|
||||
url.searchParams.set("types", "050000");
|
||||
url.searchParams.set("show_fields", "business,photos");
|
||||
url.searchParams.set("page_size", "15");
|
||||
url.searchParams.set("sortrule", "weight");
|
||||
|
||||
const needsPriceFilter = priceRange !== "any";
|
||||
url.searchParams.set("page_size", needsPriceFilter ? "25" : "15");
|
||||
|
||||
if (cuisine && cuisine !== "不限") {
|
||||
url.searchParams.set("keywords", cuisine);
|
||||
}
|
||||
|
||||
const amapRes = await fetch(url.toString());
|
||||
const amapData = await amapRes.json();
|
||||
|
||||
if (amapData.status === "1" && amapData.pois?.length > 0) {
|
||||
restaurants = amapData.pois.map(mapPoiToRestaurant);
|
||||
let results: Restaurant[] = amapData.pois.map(mapPoiToRestaurant);
|
||||
results = filterByPrice(results, priceRange);
|
||||
restaurants = results.slice(0, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
-1
@@ -17,6 +17,21 @@ interface LocationSuggestion {
|
||||
|
||||
const SHANGHAI_COORDS = { lat: 31.2222, lng: 121.4764 };
|
||||
|
||||
const DISTANCE_OPTIONS = [
|
||||
{ label: "1km", value: 1000 },
|
||||
{ label: "3km", value: 3000 },
|
||||
{ label: "5km", value: 5000 },
|
||||
] as const;
|
||||
|
||||
const PRICE_OPTIONS = [
|
||||
{ label: "不限", value: "any" },
|
||||
{ label: "¥50以下", value: "under50" },
|
||||
{ label: "¥50-100", value: "50to100" },
|
||||
{ label: "¥100+", value: "over100" },
|
||||
] as const;
|
||||
|
||||
const CUISINE_OPTIONS = ["不限", "火锅", "日料", "烧烤", "西餐", "川菜", "粤菜", "东南亚"] as const;
|
||||
|
||||
function getLocation(): Promise<{ lat: number; lng: number }> {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return Promise.resolve(SHANGHAI_COORDS);
|
||||
@@ -49,6 +64,9 @@ export default function LandingPage() {
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
const [selectedLocation, setSelectedLocation] = useState<LocationSuggestion | null>(null);
|
||||
const [fetchingSuggestions, setFetchingSuggestions] = useState(false);
|
||||
const [radius, setRadius] = useState(3000);
|
||||
const [priceRange, setPriceRange] = useState("any");
|
||||
const [cuisine, setCuisine] = useState("不限");
|
||||
const suggestRef = useRef<HTMLDivElement>(null);
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
@@ -132,7 +150,7 @@ export default function LandingPage() {
|
||||
const res = await fetch("/api/room/create", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(coords),
|
||||
body: JSON.stringify({ ...coords, radius, priceRange, cuisine }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -304,6 +322,71 @@ export default function LandingPage() {
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 rounded-xl border border-zinc-100 bg-zinc-50/50 px-3 py-2.5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-8 shrink-0 text-xs font-medium text-zinc-400">距离</span>
|
||||
<div className="flex gap-1.5">
|
||||
{DISTANCE_OPTIONS.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => setRadius(opt.value)}
|
||||
disabled={loading}
|
||||
className={`h-7 rounded-full px-3 text-xs font-medium transition-colors disabled:opacity-50 ${
|
||||
radius === opt.value
|
||||
? "bg-emerald-500 text-white shadow-sm"
|
||||
: "bg-white text-zinc-500 hover:bg-zinc-200"
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-8 shrink-0 text-xs font-medium text-zinc-400">人均</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{PRICE_OPTIONS.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => setPriceRange(opt.value)}
|
||||
disabled={loading}
|
||||
className={`h-7 rounded-full px-3 text-xs font-medium transition-colors disabled:opacity-50 ${
|
||||
priceRange === opt.value
|
||||
? "bg-emerald-500 text-white shadow-sm"
|
||||
: "bg-white text-zinc-500 hover:bg-zinc-200"
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="w-8 shrink-0 pt-1.5 text-xs font-medium text-zinc-400">菜系</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{CUISINE_OPTIONS.map((opt) => (
|
||||
<button
|
||||
key={opt}
|
||||
type="button"
|
||||
onClick={() => setCuisine(opt)}
|
||||
disabled={loading}
|
||||
className={`h-7 rounded-full px-3 text-xs font-medium transition-colors disabled:opacity-50 ${
|
||||
cuisine === opt
|
||||
? "bg-emerald-500 text-white shadow-sm"
|
||||
: "bg-white text-zinc-500 hover:bg-zinc-200"
|
||||
}`}
|
||||
>
|
||||
{opt}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleCreate}
|
||||
disabled={loading}
|
||||
|
||||
Reference in New Issue
Block a user