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