fix: retry failed discover artwork

This commit is contained in:
ShukeBta
2026-06-24 17:24:15 +08:00
parent 55673b1675
commit d93a28f18f
6 changed files with 153 additions and 35 deletions
+43 -9
View File
@@ -1,4 +1,5 @@
import { Info } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { ImageOff, Info } from 'lucide-react'
import type { DiscoverItem } from '../api/discover'
import { imageURL } from '../api/client'
@@ -7,10 +8,12 @@ import { discoverItemSource } from './discoverPageModel'
export function ContentRow({
title,
items,
imageVersion,
onSelect,
}: {
title: string
items: DiscoverItem[]
imageVersion?: string
onSelect: (item: DiscoverItem) => void
}) {
return (
@@ -18,7 +21,7 @@ export function ContentRow({
<h2 className="pl-1 font-display text-2xl font-semibold text-ink-600">{title}</h2>
<div className="grid grid-cols-3 gap-4 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-7 xl:grid-cols-8">
{items.map((item, index) => (
<DiscoverCard key={discoverKey(item, index)} item={item} onSelect={onSelect} />
<DiscoverCard key={discoverKey(item, index)} item={item} imageVersion={imageVersion} onSelect={onSelect} />
))}
</div>
</section>
@@ -42,8 +45,28 @@ export function DiscoverSkeleton() {
)
}
function DiscoverCard({ item, onSelect }: { item: DiscoverItem; onSelect: (item: DiscoverItem) => void }) {
function DiscoverCard({
item,
imageVersion,
onSelect,
}: {
item: DiscoverItem
imageVersion?: string
onSelect: (item: DiscoverItem) => void
}) {
const source = discoverItemSource(item)
const [posterFailed, setPosterFailed] = useState(false)
const posterSrc = useMemo(
() => imageURL(item.poster_url, imageVersion, true),
[imageVersion, item.poster_url],
)
useEffect(() => {
setPosterFailed(false)
}, [posterSrc])
const showFallback = !posterSrc || posterFailed
return (
<button
type="button"
@@ -51,17 +74,28 @@ function DiscoverCard({ item, onSelect }: { item: DiscoverItem; onSelect: (item:
className="group relative overflow-hidden rounded-xl border border-gray-200 bg-gray-50 text-left transition-all duration-300 hover:-translate-y-1 hover:border-primary-500/30 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-primary-400/40"
>
<div className="relative aspect-[2/3] w-full overflow-hidden bg-surface-900">
{item.poster_url ? (
{posterSrc && (
<img
src={imageURL(item.poster_url)}
src={posterSrc}
alt={item.title}
loading="lazy"
referrerPolicy="no-referrer"
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
onError={() => setPosterFailed(true)}
onLoad={(event) => {
const img = event.currentTarget
setPosterFailed(img.naturalWidth <= 1 && img.naturalHeight <= 1)
}}
className={
'h-full w-full object-cover transition-transform duration-500 group-hover:scale-105 ' +
(posterFailed ? 'opacity-0' : 'opacity-100')
}
/>
) : (
<div className="flex h-full w-full items-center justify-center text-xs text-gray-500">
无海报
)}
{showFallback && (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-gray-100 px-3 text-center text-gray-500">
<ImageOff size={22} className="text-gray-400" />
<span className="line-clamp-2 text-xs font-medium text-gray-600">{item.title}</span>
<span className="text-[10px] text-gray-400">{posterSrc ? '海报待刷新' : '无海报'}</span>
</div>
)}
<div className="absolute left-1.5 top-1.5 rounded-xl border border-white/20 bg-black/65 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-white backdrop-blur-sm">