diff --git a/src/app/api/room/create/route.ts b/src/app/api/room/create/route.ts index 2812fd7..94bbcdc 100644 --- a/src/app/api/room/create/route.ts +++ b/src/app/api/room/create/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { createRoom } from "@/lib/store"; -import { Restaurant } from "@/types"; +import { Restaurant, SceneType } from "@/types"; +import { getSceneConfig } from "@/lib/sceneConfig"; interface AmapPoiV5 { id: string; @@ -20,9 +21,6 @@ interface AmapPoiV5 { photos?: { url: string }[]; } -const DEFAULT_IMAGE = - "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&q=80"; - function extractCategory(type?: string): string { if (!type) return ""; const parts = type.split(";"); @@ -35,7 +33,7 @@ function cleanField(val: unknown): string { return String(val); } -function mapPoiToRestaurant(poi: AmapPoiV5): Restaurant { +function mapPoiToRestaurant(poi: AmapPoiV5, defaultImage: string): Restaurant { const ratingStr = poi.business?.rating; const rating = ratingStr && ratingStr !== "[]" ? parseFloat(ratingStr) || 4.0 : 4.0; @@ -47,7 +45,7 @@ function mapPoiToRestaurant(poi: AmapPoiV5): Restaurant { const image = poi.photos && poi.photos.length > 0 && poi.photos[0].url ? poi.photos[0].url - : DEFAULT_IMAGE; + : defaultImage; const openTime = cleanField(poi.business?.opentime_week) || @@ -81,10 +79,16 @@ function filterByPrice( if (isNaN(cost)) return true; switch (priceRange) { + case "under20": + return cost < 20; case "under50": return cost < 50; + case "20to50": + return cost >= 20 && cost <= 50; case "50to100": return cost >= 50 && cost <= 100; + case "over50": + return cost > 50; case "over100": return cost > 100; default: @@ -103,8 +107,11 @@ export async function POST(req: Request) { priceRange = "any", cuisine = "不限", userId = "", + scene = "eat" as SceneType, } = body; + const sceneConfig = getSceneConfig(scene === "drink" ? "drink" : "eat"); + if (!lat || !lng) { return NextResponse.json( { error: "无法获取位置信息,请允许定位权限后重试" }, @@ -125,7 +132,7 @@ export async function POST(req: Request) { url.searchParams.set("key", apiKey); url.searchParams.set("location", `${lng},${lat}`); url.searchParams.set("radius", String(radius)); - url.searchParams.set("types", "050000"); + url.searchParams.set("types", sceneConfig.poiTypes); url.searchParams.set("show_fields", "business,photos"); url.searchParams.set("sortrule", "weight"); @@ -141,24 +148,26 @@ export async function POST(req: Request) { let restaurants: Restaurant[] = []; if (amapData.status === "1" && amapData.pois?.length > 0) { - let results: Restaurant[] = amapData.pois.map(mapPoiToRestaurant); + let results: Restaurant[] = amapData.pois.map( + (poi: AmapPoiV5) => mapPoiToRestaurant(poi, sceneConfig.defaultImage), + ); results = filterByPrice(results, priceRange); restaurants = results.slice(0, 15); } if (restaurants.length === 0) { return NextResponse.json( - { error: "附近没有找到餐厅,试试扩大搜索范围或换个菜系" }, + { error: sceneConfig.emptyError }, { status: 404 }, ); } - const roomId = await createRoom(restaurants, userId); + const roomId = await createRoom(restaurants, userId, sceneConfig.key); return NextResponse.json({ roomId, restaurants }); } catch (e) { console.error("Failed to create room:", e); return NextResponse.json( - { error: "搜索餐厅失败,请检查网络后重试" }, + { error: "搜索失败,请检查网络后重试" }, { status: 500 }, ); } diff --git a/src/app/invite/[id]/page.tsx b/src/app/invite/[id]/page.tsx index c7a65e1..84ef7e8 100644 --- a/src/app/invite/[id]/page.tsx +++ b/src/app/invite/[id]/page.tsx @@ -10,8 +10,11 @@ import { Sparkles, ChevronRight, Loader2, + Coffee, } from "lucide-react"; import { getUserId } from "@/lib/userId"; +import { getSceneConfig } from "@/lib/sceneConfig"; +import type { SceneType } from "@/types"; export default function InvitePage() { const params = useParams<{ id: string }>(); @@ -22,8 +25,11 @@ export default function InvitePage() { "loading", ); const [userCount, setUserCount] = useState(0); + const [scene, setScene] = useState("eat"); const [joining, setJoining] = useState(false); + const sceneConfig = getSceneConfig(scene); + useEffect(() => { fetch(`/api/room/${roomId}`) .then((res) => { @@ -32,6 +38,7 @@ export default function InvitePage() { }) .then((data) => { setUserCount(data.userCount ?? 0); + if (data.scene) setScene(data.scene); setStatus("ready"); }) .catch(() => setStatus("not_found")); @@ -89,7 +96,7 @@ export default function InvitePage() { transition={{ duration: 0.5 }} >
- + {scene === "drink" ? : }

@@ -107,7 +114,7 @@ export default function InvitePage() { transition={{ duration: 0.5, delay: 0.1 }} >

- 有人邀请你一起选餐厅 + {sceneConfig.inviteText}

diff --git a/src/app/page.tsx b/src/app/page.tsx index 8e55d1a..3c33fa2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,7 +7,8 @@ import { Plus, LogIn, Loader2, MapPin, Navigation, X, Users, Heart, Sparkles, Ch import { getUserId, getCachedProfile, getCachedPreferences } from "@/lib/userId"; import { getAvatarBg } from "@/lib/avatars"; import AuthModal from "@/components/AuthModal"; -import type { UserProfile } from "@/types"; +import { SCENES, getSceneConfig } from "@/lib/sceneConfig"; +import type { UserProfile, SceneType } from "@/types"; interface LocationSuggestion { id: string; @@ -26,15 +27,6 @@ const DISTANCE_OPTIONS = [ { 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 HOT_CUISINES = ["火锅", "日料", "烧烤", "西餐", "川菜", "咖啡甜品", "小吃快餐"] as const; - type GpsResult = | { ok: true; lat: number; lng: number } | { ok: false; reason: "unsupported" | "denied" | "timeout" | "unknown" }; @@ -95,6 +87,9 @@ export default function LandingPage() { const [gpsCoords, setGpsCoords] = useState<{ lat: number; lng: number } | null>(null); const [gpsLocationName, setGpsLocationName] = useState(null); + const [scene, setScene] = useState("eat"); + const sceneConfig = getSceneConfig(scene); + const [profile, setProfile] = useState(null); const [authModalOpen, setAuthModalOpen] = useState(false); @@ -108,6 +103,12 @@ export default function LandingPage() { if (prefs.radius) setRadius(prefs.radius); }, []); + const handleSceneChange = useCallback((s: SceneType) => { + setScene(s); + setCuisine(""); + setPriceRange("any"); + }, []); + const doGpsLocate = useCallback(async () => { setGpsStatus("locating"); const result = await requestGps(); @@ -208,12 +209,12 @@ export default function LandingPage() { setLoading(true); try { - setLoadingText("正在搜索周边美食..."); + setLoadingText(sceneConfig.loadingText); const res = await fetch("/api/room/create", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ ...coords, radius, priceRange, cuisine, userId: getUserId() }), + body: JSON.stringify({ ...coords, radius, priceRange, cuisine, userId: getUserId(), scene }), }); const data = await res.json(); @@ -289,7 +290,7 @@ export default function LandingPage() { 别说随便

- 和朋友一起滑卡片,再也不用纠结吃什么 + {sceneConfig.subtitle}

@@ -329,7 +330,34 @@ export default function LandingPage() { + {SCENES.map((s) => { + const cfg = getSceneConfig(s); + const active = scene === s; + return ( + + ); + })} + + +
- 美食 + {sceneConfig.tagLabel}
setCuisine(e.target.value)} disabled={loading} @@ -463,7 +491,7 @@ export default function LandingPage() {
- {HOT_CUISINES.map((tag) => ( + {sceneConfig.hotTags.map((tag) => (