From e6c6e19e5aa36dd468654093bd64a652e2796382 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 3 Nov 2026 09:17:59 +0800 Subject: [PATCH] Handle unavailable home gallery images and brand logo --- components/home/lovart/home-image.tsx | 52 ++++++++++++++++ .../home/lovart/lovart-featured-gallery.tsx | 62 +++++++++++-------- components/home/lovart/lovart-sidebar.tsx | 18 +++--- 3 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 components/home/lovart/home-image.tsx diff --git a/components/home/lovart/home-image.tsx b/components/home/lovart/home-image.tsx new file mode 100644 index 000000000..3d2ea404b --- /dev/null +++ b/components/home/lovart/home-image.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useState, type ReactNode } from "react"; + +type HomeImageProps = { + sources: Array; + alt: string; + className?: string; + imageClassName?: string; + fallback?: ReactNode; + loading?: "lazy" | "eager"; + onUnavailable?: () => void; +}; + +/** Keep failed image elements out of view, trying each distinct source once. */ +export function HomeImage(props: HomeImageProps) { + const sources = [...new Set(props.sources.filter((url): url is string => Boolean(url?.trim())))]; + return ; +} + +function HomeImageAttempt({ + sources, alt, className, imageClassName, fallback, loading = "lazy", onUnavailable, +}: Omit & { sources: string[] }) { + const [index, setIndex] = useState(0); + const [loaded, setLoaded] = useState(false); + const source = sources[index]; + return ( + + {!loaded && ( + + {fallback} + + )} + {source ? ( + {alt} setLoaded(true)} + onError={() => { + setLoaded(false); + setIndex(index + 1); + if (index + 1 >= sources.length) onUnavailable?.(); + }} + /> + ) : null} + + ); +} diff --git a/components/home/lovart/lovart-featured-gallery.tsx b/components/home/lovart/lovart-featured-gallery.tsx index 490d4501f..25a2a2390 100644 --- a/components/home/lovart/lovart-featured-gallery.tsx +++ b/components/home/lovart/lovart-featured-gallery.tsx @@ -1,6 +1,7 @@ import { ReferenceIcon } from "@/components/lovart/reference-icon"; import { Gem, Heart, Loader2, PlayCircle } from "lucide-react"; -import type { Ref } from "react"; +import { useState, type Ref } from "react"; +import { HomeImage } from "./home-image"; import type { FeaturedImage } from "./types"; type LovartFeaturedGalleryProps = { @@ -30,6 +31,10 @@ function getFeaturedPreviewUrl(item: FeaturedImage) { return `${source}?imageView2/2/w/900/q/78`; } +function imageIdentity(item: FeaturedImage) { + return JSON.stringify([item.id, item.thumbnailUrl, item.originalUrl]); +} + export function LovartFeaturedGallery({ onOpenProjects, onOpenMaterials, @@ -42,6 +47,12 @@ export function LovartFeaturedGallery({ isLoadingMoreFeatured, setActiveCategory, }: LovartFeaturedGalleryProps) { + const [unavailable, setUnavailable] = useState>(() => new Set()); + const [retryVersion, setRetryVersion] = useState(0); + const visibleItems = featuredItems.filter((item) => + Boolean(item.thumbnailUrl || item.originalUrl) && !unavailable.has(imageIdentity(item)), + ); + const unavailableCount = featuredItems.length - visibleItems.length; return (
- {featuredItems.length === 0 ? ( + {unavailableCount > 0 && ( +
+ {unavailableCount} 张灵感图片暂时无法加载,已收起。 + +
+ )} + {visibleItems.length === 0 ? (
{isLoadingFeatured ? "灵感内容加载中..." - : "当前没有可展示的灵感数据。"} + : unavailableCount > 0 ? "图片暂时无法加载,请稍后重试。" : "当前没有可展示的灵感数据。"}
) : (
- {featuredItems.map((item, index) => ( + {visibleItems.map((item) => ( {siteName}