diff --git a/src/app/api/room/create/route.ts b/src/app/api/room/create/route.ts index 94bbcdc..d4c0921 100644 --- a/src/app/api/room/create/route.ts +++ b/src/app/api/room/create/route.ts @@ -42,10 +42,14 @@ function mapPoiToRestaurant(poi: AmapPoiV5, defaultImage: string): Restaurant { const price = costStr && costStr !== "[]" && costStr !== "0" ? `¥${costStr}` : "未知"; - const image = - poi.photos && poi.photos.length > 0 && poi.photos[0].url - ? poi.photos[0].url - : defaultImage; + const images = + poi.photos && poi.photos.length > 0 + ? poi.photos + .map((p) => p.url) + .filter(Boolean) + .slice(0, 5) + : []; + if (images.length === 0) images.push(defaultImage); const openTime = cleanField(poi.business?.opentime_week) || @@ -57,7 +61,7 @@ function mapPoiToRestaurant(poi: AmapPoiV5, defaultImage: string): Restaurant { rating, price, distance: poi.distance ? `${poi.distance}m` : "", - image, + images, category: extractCategory(poi.type), address: cleanField(poi.address), openTime, diff --git a/src/app/globals.css b/src/app/globals.css index f76e68f..22f9ba3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -34,3 +34,8 @@ body { .scrollbar-none::-webkit-scrollbar { display: none; } + +@keyframes img-fade-out { + from { opacity: 1; } + to { opacity: 0; } +} diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 12f278e..041af5b 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -24,6 +24,13 @@ import { getUserId, getCachedProfile, setCachedProfile, setCachedPreferences, lo import { getAvatarBg, AVATARS } from "@/lib/avatars"; import type { UserProfile, UserPreferences, DecisionRecord, FavoriteRecord, Restaurant } from "@/types"; +function firstImage(r: Restaurant): string { + if (r.images?.length > 0) return r.images[0]; + // backward compat: old DB records may have `image` instead of `images` + const legacy = (r as Record).image; + return typeof legacy === "string" ? legacy : ""; +} + export default function ProfilePage() { const router = useRouter(); const [userId, setUserId] = useState(""); @@ -534,9 +541,9 @@ export default function ProfilePage() { rel="noopener noreferrer" className="flex gap-3 rounded-xl bg-zinc-50 p-2.5 transition-colors active:bg-zinc-100" > - {d.restaurantData.image && ( + {firstImage(d.restaurantData) && ( {d.restaurantName} - {r.image && ( + {firstImage(r) && ( {r.name} - {restaurant.name} + {restaurant.images?.[0] && ( + {restaurant.name} + )}

{restaurant.name} @@ -348,12 +350,14 @@ export default function MatchResult({ animate={{ y: 0, opacity: 1 }} transition={{ type: "spring", stiffness: 180, damping: 18, delay: 0.5 }} > - {restaurant.name} + {restaurant.images?.[0] && ( + {restaurant.name} + )}

diff --git a/src/components/RestaurantCard.tsx b/src/components/RestaurantCard.tsx index e65558c..c22cf39 100644 --- a/src/components/RestaurantCard.tsx +++ b/src/components/RestaurantCard.tsx @@ -1,7 +1,7 @@ "use client"; -import { useCallback, useState } from "react"; -import { Star, MapPin, Clock, ExternalLink, Flame, Bookmark } from "lucide-react"; +import { useCallback, useState, useEffect } from "react"; +import { Star, MapPin, Clock, ExternalLink, Flame, Bookmark, ChevronLeft, ChevronRight } from "lucide-react"; import { Restaurant } from "@/types"; import { getUserId, isRegistered } from "@/lib/userId"; @@ -14,9 +14,115 @@ function stopAll(e: React.SyntheticEvent) { e.stopPropagation(); } +function ImageGallery({ images, name }: { images: string[]; name: string }) { + const [idx, setIdx] = useState(0); + const [fadingOut, setFadingOut] = useState(null); + const [showHint, setShowHint] = useState(true); + const count = images.length; + const hasMultiple = count > 1; + + useEffect(() => { + if (!hasMultiple || !showHint) return; + const t = setTimeout(() => setShowHint(false), 2500); + return () => clearTimeout(t); + }, [hasMultiple, showHint]); + + const goTo = useCallback( + (newIdx: number) => { + if (newIdx === idx) return; + setFadingOut(idx); + setIdx(newIdx); + }, + [idx], + ); + + const handleTap = useCallback( + (e: React.MouseEvent) => { + if (!hasMultiple) return; + e.stopPropagation(); + e.preventDefault(); + setShowHint(false); + + const rect = e.currentTarget.getBoundingClientRect(); + const tapX = e.clientX - rect.left; + const isRight = tapX > rect.width / 2; + + const newIdx = isRight + ? Math.min(idx + 1, count - 1) + : Math.max(idx - 1, 0); + goTo(newIdx); + }, + [hasMultiple, count, idx, goTo], + ); + + return ( +
+ {name} + + {fadingOut !== null && ( + setFadingOut(null)} + draggable={false} + referrerPolicy="no-referrer" + /> + )} + + {hasMultiple && ( + <> +
+ {images.map((_, i) => ( + + ))} +
+ + {idx > 0 && ( +
+ +
+ )} + {idx < count - 1 && ( +
+ +
+ )} + + {showHint && idx === 0 && ( +
+ + 点击图片左右切换 · 共 {count} 张 + +
+ )} + + )} +
+ ); +} + export default function RestaurantCard({ restaurant, likeCount = 0 }: RestaurantCardProps) { const [favorited, setFavorited] = useState(false); + const images = restaurant.images?.filter(Boolean); + const hasImage = images && images.length > 0; + const handleFavorite = useCallback(async (e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation(); e.preventDefault(); @@ -45,15 +151,9 @@ export default function RestaurantCard({ restaurant, likeCount = 0 }: Restaurant return (
-
- {restaurant.name} -
+
+ {hasImage && } +
{restaurant.category && ( diff --git a/src/types/index.ts b/src/types/index.ts index 7ff5fe0..76a6b20 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,7 +6,7 @@ export interface Restaurant { rating: number; price: string; distance: string; - image: string; + images: string[]; category: string; address: string; openTime: string;