"use client"; import { useCallback, useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Star, MapPin, Clock, ExternalLink, Flame, Bookmark, ChevronLeft, ChevronRight } from "lucide-react"; import { Restaurant } from "@/types"; import { getUserId, isRegistered } from "@/lib/userId"; import RestaurantImage from "@/components/RestaurantImage"; interface RestaurantCardProps { restaurant: Restaurant; likeCount?: number; } 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 (
{fadingOut !== null && ( setFadingOut(null)} draggable={false} /> )} {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 [likeBounce, setLikeBounce] = useState(false); const prevLikeRef = useRef(likeCount); useEffect(() => { if (likeCount > prevLikeRef.current) { setLikeBounce(true); const t = setTimeout(() => setLikeBounce(false), 600); return () => clearTimeout(t); } prevLikeRef.current = likeCount; }, [likeCount]); 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(); if (favorited) return; try { const res = await fetch("/api/user/favorite", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: getUserId(), restaurant }), }); if (res.ok) setFavorited(true); } catch {} }, [restaurant, favorited]); const openLink = useCallback( (url: string) => (e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation(); e.preventDefault(); window.open(url, "_blank", "noopener,noreferrer"); }, [], ); const amapUrl = `https://uri.amap.com/poidetail?poiid=${restaurant.id}`; const dianpingUrl = `https://m.dianping.com/search/keyword/0/0_${encodeURIComponent(restaurant.name)}`; return (
{hasImage && }
{restaurant.category && ( {restaurant.category} )} {likeCount > 0 && ( {likeCount} 人想去 )}

{restaurant.name}

{isRegistered() && ( )}
{restaurant.rating}
{restaurant.price} {restaurant.distance && (
{restaurant.distance}
)}
{restaurant.address && (

{restaurant.address}

)} {restaurant.openTime && (
{restaurant.openTime}
)} {restaurant.tag && (
{restaurant.tag .split(",") .slice(0, 3) .map((t) => ( {t.trim()} ))}
)}
); }