fix: show psd image loading placeholder

This commit is contained in:
Your Name 2026-06-02 16:47:47 +08:00
parent 6f4a881238
commit 5e896b8e56

View File

@ -579,6 +579,7 @@ function AssignedPsdLayerPreviewImage({
width,
quality,
style,
onLoadComplete,
}: {
sourceUrl: string;
alt: string;
@ -586,6 +587,7 @@ function AssignedPsdLayerPreviewImage({
width?: number;
quality?: number;
style?: CSSProperties;
onLoadComplete?: (sourceUrl: string) => void;
}) {
const fallbackUrls = useMemo(
() => buildAssignedPsdLayerPreviewFallbackUrls(sourceUrl, { width, quality }),
@ -631,13 +633,48 @@ function AssignedPsdLayerPreviewImage({
current + 1 < fallbackUrls.length ? current + 1 : current
)
}
onLoad={() => setHasLoaded(true)}
onLoad={() => {
setHasLoaded(true);
onLoadComplete?.(sourceUrl);
}}
src={src}
style={style}
/>
);
}
function PsdAssignedImageLoadingOverlay({
pendingCount,
zIndex,
}: {
pendingCount: number;
zIndex: number;
}) {
return (
<div
className="pointer-events-none absolute inset-0 overflow-hidden rounded-[20px] border border-emerald-300/15 bg-[#e9edf2] shadow-[inset_0_0_0_1px_rgba(255,255,255,0.75)]"
style={{ zIndex }}
>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_22%_18%,rgba(255,255,255,0.95),transparent_35%),linear-gradient(135deg,#eef2f6_0%,#dce4ec_45%,#f5f7fa_100%)]" />
<div className="psd-assigned-preview-shimmer absolute inset-y-0 w-1/2 bg-[linear-gradient(105deg,transparent_0%,rgba(255,255,255,0.68)_48%,transparent_100%)]" />
<div className="absolute inset-0 opacity-45 [background-image:linear-gradient(rgba(15,23,42,0.045)_1px,transparent_1px),linear-gradient(90deg,rgba(15,23,42,0.04)_1px,transparent_1px)] [background-size:44px_44px]" />
<div className="absolute inset-0 flex flex-col items-center justify-center px-10 text-center">
<div className="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl border border-white/70 bg-white/60 shadow-[0_18px_50px_rgba(15,23,42,0.12)] backdrop-blur">
<Loader2 className="h-7 w-7 animate-spin text-emerald-500" />
</div>
<div className="rounded-full border border-emerald-500/15 bg-white/70 px-4 py-1.5 font-semibold text-[13px] text-slate-700 shadow-sm backdrop-blur">
正在套入商品图
</div>
<div className="mt-3 max-w-[360px] text-[12px] text-slate-500 leading-relaxed">
{pendingCount > 1
? `正在加载 ${pendingCount} 个图片图层,完成后自动显示模板预览。`
: "原图加载中,完成后自动显示模板预览。"}
</div>
</div>
</div>
);
}
function getScrollAreaViewportElement(root: HTMLDivElement | null) {
return (
(root?.querySelector(
@ -1851,6 +1888,9 @@ export function AgentPsdTemplateEditorDialog({
const [imageSizes, setImageSizes] = useState<
Record<string, AgentPsdImageSize>
>({});
const [loadedAssignedPreviewUrls, setLoadedAssignedPreviewUrls] = useState<
Record<string, string>
>({});
const [layerOrderIds, setLayerOrderIds] = useState<string[]>([]);
const [layerPositionOffsets, setLayerPositionOffsets] = useState<
Record<string, AgentPsdLayerPositionOffset>
@ -2255,6 +2295,33 @@ export function AgentPsdTemplateEditorDialog({
return nextAssignments;
}, [editableImageLayers, imageAssignments, selectedTemplate, sourceImageUrl]);
const pendingAssignedPreviewLayerIds = useMemo(
() =>
orderedVisiblePreviewLayers
.filter((layer): layer is AgentPsdTemplateImageLayerSlot =>
isAgentPsdImageLayerSlot(layer)
)
.map((layer) => {
const assignedUrl = String(
effectiveImageAssignments[layer.id] || ""
).trim();
if (!assignedUrl) {
return "";
}
return loadedAssignedPreviewUrls[layer.id] === assignedUrl
? ""
: layer.id;
})
.filter(Boolean),
[
effectiveImageAssignments,
loadedAssignedPreviewUrls,
orderedVisiblePreviewLayers,
]
);
const assignedPreviewLoadingOverlayZIndex =
(orderedPreviewLayers.length + 12) * PSD_LAYER_Z_INDEX_STEP;
const effectiveTextAssignments = useMemo(() => {
const nextAssignments: Record<string, string> = {};
@ -2872,6 +2939,7 @@ export function AgentPsdTemplateEditorDialog({
setImageAssignments({});
setImagePlacements({});
setImageSizes({});
setLoadedAssignedPreviewUrls({});
setTextAssignments({});
setTextColorAssignments({});
setTextPositionOffsets({});
@ -2915,6 +2983,7 @@ export function AgentPsdTemplateEditorDialog({
setImageAssignments(initialState.imageAssignments);
setImagePlacements(initialState.imagePlacements);
setLayerOrderIds(initialState.layerOrderIds);
setLoadedAssignedPreviewUrls({});
setLayerPositionOffsets(initialState.layerPositionOffsets);
setTextAssignments(initialState.textAssignments);
setTextColorAssignments(initialState.textColorAssignments);
@ -3966,6 +4035,18 @@ export function AgentPsdTemplateEditorDialog({
.psd-layer-visibility-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(16, 185, 129, 0.75);
}
.psd-assigned-preview-shimmer {
transform: translateX(-110%);
animation: psd-assigned-preview-shimmer 1.55s ease-in-out infinite;
}
@keyframes psd-assigned-preview-shimmer {
0% {
transform: translateX(-110%);
}
100% {
transform: translateX(210%);
}
}
`,
}}
/>
@ -4398,6 +4479,22 @@ export function AgentPsdTemplateEditorDialog({
{assignedUrl ? (
<AssignedPsdLayerPreviewImage
alt={layer.layerName}
onLoadComplete={(loadedUrl) => {
setLoadedAssignedPreviewUrls(
(current) => {
if (
current[layer.id] === loadedUrl
) {
return current;
}
return {
...current,
[layer.id]: loadedUrl,
};
}
);
}}
quality={74}
sourceUrl={layerImageUrl}
style={
@ -4911,6 +5008,13 @@ export function AgentPsdTemplateEditorDialog({
);
})}
</div>
{pendingAssignedPreviewLayerIds.length > 0 ? (
<PsdAssignedImageLoadingOverlay
pendingCount={pendingAssignedPreviewLayerIds.length}
zIndex={assignedPreviewLoadingOverlayZIndex}
/>
) : null}
</div>
</div>
)