修复画布图层保存体积过大

画布布局不再写入图片base64正文

加载画布时从项目资源补回图层图片地址

补充大图素材保存布局回归测试
This commit is contained in:
2026-06-16 16:39:23 +08:00
parent 1d570605af
commit 94841d4360
2 changed files with 68 additions and 11 deletions

View File

@@ -1623,6 +1623,49 @@ describe('ImageCanvasEditorView', () => {
expect(createEditorAssetMock).not.toHaveBeenCalled(); expect(createEditorAssetMock).not.toHaveBeenCalled();
}); });
it('saves canvas layout without embedding image payloads in layer snapshots', async () => {
loadEditorAssetLibraryMock.mockResolvedValueOnce({
folders: [
{
folderId: 'project',
label: '项目素材',
sortOrder: 0,
collapsed: false,
systemDefault: true,
},
],
assets: [
{
assetId: 'asset-data-heavy',
folderId: 'project',
label: '大图素材',
imageSrc: 'data:image/png;base64,'.concat('a'.repeat(4000)),
width: 1024,
height: 768,
sourceType: 'uploaded',
},
],
});
render(<ImageCanvasEditorView />);
await screen.findByRole('button', { name: '添加大图素材' });
fireEvent.click(screen.getByRole('button', { name: '添加大图素材' }));
await waitFor(() => {
expect(saveEditorProjectLayoutMock).toHaveBeenCalled();
});
const layoutCalls = saveEditorProjectLayoutMock.mock.calls;
const lastLayout = layoutCalls.at(-1)?.[1];
expect(lastLayout.layers).toEqual(
expect.arrayContaining([
expect.not.objectContaining({
src: expect.stringMatching(/^data:image/u),
}),
]),
);
});
it('adds an asset library image to the canvas with pointer dragging', async () => { it('adds an asset library image to the canvas with pointer dragging', async () => {
await renderLoadedEditor(); await renderLoadedEditor();

View File

@@ -539,7 +539,6 @@ function serializeLayer(layer: CanvasLayer): EditorProjectLayerSnapshot {
layerId: layer.id, layerId: layer.id,
resourceId: layer.resourceId, resourceId: layer.resourceId,
title: layer.title, title: layer.title,
src: layer.src,
x: layer.x, x: layer.x,
y: layer.y, y: layer.y,
width: layer.width, width: layer.width,
@@ -565,10 +564,14 @@ function serializeLayer(layer: CanvasLayer): EditorProjectLayerSnapshot {
}; };
} }
function hydrateLayer(snapshot: EditorProjectLayerSnapshot): CanvasLayer | null { function hydrateLayer(
snapshot: EditorProjectLayerSnapshot,
resourcesById: Map<string, { imageSrc: string }>,
): CanvasLayer | null {
const resourceId = typeof snapshot.resourceId === 'string' ? snapshot.resourceId : ''; const resourceId = typeof snapshot.resourceId === 'string' ? snapshot.resourceId : '';
const layerId = typeof snapshot.layerId === 'string' ? snapshot.layerId : ''; const layerId = typeof snapshot.layerId === 'string' ? snapshot.layerId : '';
const src = typeof snapshot.src === 'string' ? snapshot.src : ''; const snapshotSrc = typeof snapshot.src === 'string' ? snapshot.src : '';
const src = snapshotSrc || resourcesById.get(resourceId)?.imageSrc || '';
const title = typeof snapshot.title === 'string' ? snapshot.title : '画布图片'; const title = typeof snapshot.title === 'string' ? snapshot.title : '画布图片';
if (!resourceId || !layerId || !src) { if (!resourceId || !layerId || !src) {
return null; return null;
@@ -1221,8 +1224,14 @@ export function ImageCanvasEditorView() {
setProjectTitle(nextProjectTitle); setProjectTitle(nextProjectTitle);
setProjectRenameValue(nextProjectTitle); setProjectRenameValue(nextProjectTitle);
setViewport(project.viewport); setViewport(project.viewport);
const resourcesById = new Map(
project.resources.map((resource) => [
resource.resourceId,
{ imageSrc: resource.imageSrc },
]),
);
const hydratedLayers = project.layers const hydratedLayers = project.layers
.map(hydrateLayer) .map((layer) => hydrateLayer(layer, resourcesById))
.filter((layer): layer is CanvasLayer => Boolean(layer)); .filter((layer): layer is CanvasLayer => Boolean(layer));
layerCounterRef.current = hydratedLayers.length; layerCounterRef.current = hydratedLayers.length;
setLayers(hydratedLayers); setLayers(hydratedLayers);
@@ -2099,11 +2108,19 @@ export function ImageCanvasEditorView() {
} }
: currentLayer, : currentLayer,
); );
if (options.saveLayout) {
void saveProjectLayoutNow(nextLayers).catch(() => {});
}
return nextLayers; return nextLayers;
}); });
if (options.saveLayout) {
const latestLayers = layersRef.current.map((currentLayer) =>
currentLayer.id === layer.id
? {
...currentLayer,
resourceId: resource.resourceId,
}
: currentLayer,
);
void saveProjectLayoutNow(latestLayers).catch(() => {});
}
return resource; return resource;
}) })
.catch(() => { .catch(() => {
@@ -2228,9 +2245,7 @@ export function ImageCanvasEditorView() {
); );
captureCanvasHistory(); captureCanvasHistory();
setLayers((currentLayers) => { setLayers((currentLayers) => {
const nextLayers = [...currentLayers, nextLayer]; return [...currentLayers, nextLayer];
void saveProjectLayoutNow(nextLayers).catch(() => {});
return nextLayers;
}); });
selectSingleLayer(nextLayer.id); selectSingleLayer(nextLayer.id);
setHoveredLayerId(null); setHoveredLayerId(null);
@@ -2853,7 +2868,6 @@ export function ImageCanvasEditorView() {
} }
: currentLayer, : currentLayer,
); );
void saveProjectLayoutNow(nextLayers).catch(() => {});
return nextLayers; return nextLayers;
}); });
} }