From 7e275798e7f82c4f94d4ae765459f348630536f1 Mon Sep 17 00:00:00 2001 From: obroccolio Date: Sat, 12 Sep 2026 18:37:56 +0800 Subject: [PATCH] fix: load authenticated product archive images --- frontend/src/pages/ProductArchivePage.tsx | 61 +++++++++++++++++++++-- frontend/src/styles.css | 10 ++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/ProductArchivePage.tsx b/frontend/src/pages/ProductArchivePage.tsx index 33de11e..8ad718a 100644 --- a/frontend/src/pages/ProductArchivePage.tsx +++ b/frontend/src/pages/ProductArchivePage.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import type { FormEvent } from 'react'; import AppSettingsWindow, { type ThemeMode } from '../components/AppSettingsWindow'; -import { ensureOk } from '../lib/api'; +import { apiUrl, ensureOk } from '../lib/api'; import { IconArchive, IconGrid, IconHelp, IconLock, IconRefresh, IconTrash } from '../components/Icons'; import { deleteProduct, @@ -57,6 +57,51 @@ function formatDateTime(value: string): string { }); } +function AuthenticatedImage({ + token, + imageUrl, + alt, + emptyLabel, +}: { + token: string; + imageUrl: string; + alt: string; + emptyLabel: string; +}) { + const [objectUrl, setObjectUrl] = useState(''); + + useEffect(() => { + let cancelled = false; + let createdUrl = ''; + + const load = async () => { + try { + const res = await fetch(apiUrl(imageUrl), { + headers: { Authorization: `Bearer ${token}` }, + }); + await ensureOk(res, '预览图读取失败'); + const blob = await res.blob(); + createdUrl = URL.createObjectURL(blob); + if (!cancelled) setObjectUrl(createdUrl); + } catch { + if (!cancelled) setObjectUrl(''); + } + }; + + void load(); + return () => { + cancelled = true; + if (createdUrl) URL.revokeObjectURL(createdUrl); + }; + }, [imageUrl, token]); + + if (!objectUrl) { + return
{emptyLabel}
; + } + + return {alt}; +} + export default function ProductArchivePage({ themeMode, systemTheme, @@ -286,7 +331,12 @@ export default function ProductArchivePage({ > {coverUrl - ? {product.name} + ? : 暂无预览图} @@ -352,7 +402,12 @@ export default function ProductArchivePage({
{previewUrl - ? {`${selected.name} + ? :
暂无预览图
}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index c11e74d..4806bab 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -3687,6 +3687,16 @@ body.resizing .studio-panel { height: 100%; object-fit: contain; } +.product-archive-image-empty { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + font-size: 11px; + text-align: center; +} .product-archive-thumb-empty, .product-archive-cover-empty { padding: 4px;