feat: 商家卡片支持多图展示,点击左右切换带 crossfade 过渡
- Restaurant.image 改为 images: string[],API 层从高德取最多 5 张图 - RestaurantCard 新增 ImageGallery:点击图片左右区域切换、顶部圆点指示器、 左右箭头提示、首次查看时文字引导气泡(2.5s 自动消失) - 图片切换使用 crossfade 动画(旧图渐隐 280ms),过渡平滑 - MatchResult / Profile 页面兼容新旧数据格式,无图时条件渲染
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -34,3 +34,8 @@ body {
|
||||
.scrollbar-none::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes img-fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
@@ -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<string, unknown>).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) && (
|
||||
<img
|
||||
src={d.restaurantData.image}
|
||||
src={firstImage(d.restaurantData)}
|
||||
alt={d.restaurantName}
|
||||
className="h-12 w-12 shrink-0 rounded-lg object-cover"
|
||||
referrerPolicy="no-referrer"
|
||||
@@ -611,9 +618,9 @@ export default function ProfilePage() {
|
||||
key={f.id}
|
||||
className="flex gap-3 rounded-xl bg-zinc-50 p-2.5"
|
||||
>
|
||||
{r.image && (
|
||||
{firstImage(r) && (
|
||||
<img
|
||||
src={r.image}
|
||||
src={firstImage(r)}
|
||||
alt={r.name}
|
||||
className="h-12 w-12 shrink-0 rounded-lg object-cover"
|
||||
referrerPolicy="no-referrer"
|
||||
|
||||
Reference in New Issue
Block a user