Files
Arkie-Library-Frontend/src/components/messageStream/bubbles/AlbumBubble.tsx
TerryM e04dd7dc2a fix: 修复相册缩略图无法显示(dev 代理 + 前端兜底)
- vite: /uploads 代理指向源站根而非 /apnew 前缀,relative thumbnailUrl
  (/uploads/thumb…) 在 dev 下不再落到 SPA index.html
- BubbleImage: 新增 fallbackSrc 候选链,缩略图加载失败时自动回退到绝对
  thumbUrl/url,全部失败才显示占位符
- AlbumBubble / ImageLightbox: 缩略图传入绝对地址作为兜底

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:59:33 +08:00

81 lines
3.0 KiB
TypeScript

import { useI18n } from "../../../i18n";
import type { Post } from "../../../types/post";
import { AttachmentDownloadPill } from "../AttachmentDownloadPill";
import { BubbleImage } from "../BubbleImage";
import { useLightbox } from "../overlays/ImageLightbox";
import { autolink } from "../utils/autolink";
import { postDisplayText } from "../utils/postText";
const MAX_VISIBLE = 4;
function albumGridClass(count: number) {
const height = "h-[230px] min-[440px]:h-[250px] md:h-[300px] lg:h-[340px]";
if (count === 2) return `${height} grid grid-cols-1 grid-rows-2`;
return `${height} grid grid-cols-2 grid-rows-2`;
}
function albumItemClass(index: number, count: number) {
if (count === 3 && index === 0) return "row-span-2";
return "";
}
export function AlbumBubble({ post }: { post: Post }) {
const { openLightbox } = useLightbox();
const { lang } = useI18n();
const images = post.attachments;
const text = postDisplayText(post, lang);
const visible = images.slice(0, MAX_VISIBLE);
const extra = images.length - MAX_VISIBLE;
const layoutCount = Math.min(images.length, MAX_VISIBLE);
return (
<div className="flex flex-col">
<div className={`${albumGridClass(layoutCount)} gap-px overflow-hidden`}>
{visible.map((att, i) => {
const isLastSlot = i === MAX_VISIBLE - 1 && extra > 0;
return (
<div
key={att.id}
className={`relative h-full w-full overflow-hidden ${albumItemClass(
i,
layoutCount,
)}`}
>
<button
type="button"
onClick={() => openLightbox(images, i, text, post.id)}
className="group block h-full w-full"
aria-label={
isLastSlot ? `View all ${images.length} images` : "View image"
}
>
<BubbleImage
src={att.thumbnailUrl ?? att.thumbUrl ?? att.url}
fallbackSrc={[att.thumbUrl, att.url]}
loading="lazy"
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.03]"
/>
{isLastSlot ? (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-0.5 bg-black/60 text-white backdrop-blur-[1px] transition group-hover:bg-black/50">
<span className="text-3xl font-bold leading-none md:text-4xl">
+{extra}
</span>
</div>
) : null}
</button>
{!isLastSlot ? (
<AttachmentDownloadPill postId={post.id} attachment={att} />
) : null}
</div>
);
})}
</div>
{text ? (
<div className="message-stream-copyable-text select-text whitespace-pre-wrap break-words px-4 pt-3 text-[14px] leading-6 text-neutral-100">
{autolink(text)}
</div>
) : null}
</div>
);
}