fix: load authenticated product archive images
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { FormEvent } from 'react';
|
import type { FormEvent } from 'react';
|
||||||
import AppSettingsWindow, { type ThemeMode } from '../components/AppSettingsWindow';
|
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 { IconArchive, IconGrid, IconHelp, IconLock, IconRefresh, IconTrash } from '../components/Icons';
|
||||||
import {
|
import {
|
||||||
deleteProduct,
|
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 <div className="product-archive-image-empty">{emptyLabel}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <img src={objectUrl} alt={alt} loading="lazy" decoding="async" />;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ProductArchivePage({
|
export default function ProductArchivePage({
|
||||||
themeMode,
|
themeMode,
|
||||||
systemTheme,
|
systemTheme,
|
||||||
@@ -286,7 +331,12 @@ export default function ProductArchivePage({
|
|||||||
>
|
>
|
||||||
<span className="product-archive-thumb">
|
<span className="product-archive-thumb">
|
||||||
{coverUrl
|
{coverUrl
|
||||||
? <img src={coverUrl} alt={product.name} />
|
? <AuthenticatedImage
|
||||||
|
token={token}
|
||||||
|
imageUrl={coverUrl}
|
||||||
|
alt={product.name}
|
||||||
|
emptyLabel="暂无预览图"
|
||||||
|
/>
|
||||||
: <span className="product-archive-thumb-empty">暂无预览图</span>}
|
: <span className="product-archive-thumb-empty">暂无预览图</span>}
|
||||||
</span>
|
</span>
|
||||||
<span className="product-archive-row-body">
|
<span className="product-archive-row-body">
|
||||||
@@ -352,7 +402,12 @@ export default function ProductArchivePage({
|
|||||||
<div className="product-archive-detail">
|
<div className="product-archive-detail">
|
||||||
<div className="product-archive-cover">
|
<div className="product-archive-cover">
|
||||||
{previewUrl
|
{previewUrl
|
||||||
? <img src={previewUrl} alt={`${selected.name} 预览图`} />
|
? <AuthenticatedImage
|
||||||
|
token={token}
|
||||||
|
imageUrl={previewUrl}
|
||||||
|
alt={`${selected.name} 预览图`}
|
||||||
|
emptyLabel="暂无预览图"
|
||||||
|
/>
|
||||||
: <div className="product-archive-cover-empty">暂无预览图</div>}
|
: <div className="product-archive-cover-empty">暂无预览图</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className="product-archive-detail-body">
|
<div className="product-archive-detail-body">
|
||||||
|
|||||||
@@ -3687,6 +3687,16 @@ body.resizing .studio-panel {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
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-thumb-empty,
|
||||||
.product-archive-cover-empty {
|
.product-archive-cover-empty {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user