补齐画布多产物生成回填
将角色、图标图集和UI素材提取的原图登记为项目资源并随处理结果写入画布 保持后处理结果为生成器主锚点并将原图错开放置 新增原图标签、后端多产物布局测试和前端快照回归 同步更新图片画布方案与共享决策记录
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-13 图片画布多产物生成任务必须回填全部产物
|
||||
|
||||
- 背景:角色形象、图标 spritesheet 和 UI 素材提取会先得到带纯色背景的原图,再执行抠图或拆分;此前原图只登记到 OSS,后端完成快照只把后处理结果放到画布,用户无法在画布继续使用同一任务的完整产物。
|
||||
- 决策:凡一次资产生成任务产生多个可复用产物,所有产物都必须登记为 `editor_project_resource` 并由同一次后端 `canvasCompletion` 写入权威画布快照,不能只返回最终产物或由前端临时补图。角色形象、图标 spritesheet 和 UI 素材提取至少同时保留纯色背景原图与透明后处理结果;UI 素材提取还继续保留拆分素材。生成器 `generatedLayerId` 始终锚定主后处理结果,附属产物在主结果右侧错开放置,避免重叠并保持后续改造入口语义不变。没有项目上下文时继续只持久化 OSS / asset object,不虚构画布资源。
|
||||
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、图片画布生成完成快照、角色形象 / 图标 spritesheet / UI 素材提取链路和图片画布文档。
|
||||
- 验证方式:覆盖多产物画布布局、主结果锚定和资源去重的定向 Rust 测试,并运行 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
|
||||
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
|
||||
|
||||
## 2026-07-12 泥点充值收敛为四档并统一资产入口
|
||||
|
||||
- 背景:主站与图片画板的泥点余额入口、余额明细和充值弹窗存在不同实现,旧充值口径仍展示六档泥点、首充双倍和会员购买 / 升级入口,容易让展示、商品资格与后端余额真相发生漂移。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -116,6 +116,7 @@
|
||||
- 生成占位图和生成器对话框不是临时浮层,必须作为画布布局数据保存。
|
||||
- 保存时在现有画布布局数组中追加 `itemType: "generation-dialog"` 项,记录生成器 ID、模式、提示词、参数、参考图、状态、占位框位置和 `generatedLayerId`。
|
||||
- 生成成功后仍保留生成器快照;画布渲染优先用 `generatedLayerId` 锚定到成品图层,不再重复显示灰色占位框。
|
||||
- 一次生成任务产生多个可复用产物时,全部产物都必须由后端登记为项目资源并随同一次完成快照加入画布,不能只保留最终产物或由前端临时追加。角色形象、图标 spritesheet 和 UI 素材提取至少同时回填纯色背景原图与透明后处理结果;UI 素材提取继续一并回填拆分素材。`generatedLayerId` 仍锚定透明后处理主结果,附属产物从主结果右侧开始错开放置。
|
||||
- 图片、视频和音频生成结果都要写入账号级素材库;视频 / 音频结果由后端持久化到 OSS 并回传 `objectKey` / `assetObjectId`,前端保存素材库时一并记录,后续预览和再次加入画布走统一换签链路。
|
||||
- 刷新项目后,画布需要同时恢复图层、生成器快照和生成输入框跟随关系。
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1145,6 +1145,7 @@ export function canvasAssetKindOrNull(value: unknown): CanvasAssetKind | null {
|
||||
value === 'icon' ||
|
||||
value === 'icon-spritesheet' ||
|
||||
value === 'icon-spec' ||
|
||||
value === 'editor_green_screen_source' ||
|
||||
value === 'publication-material' ||
|
||||
value === 'ui-design' ||
|
||||
value === 'video' ||
|
||||
|
||||
@@ -20,6 +20,7 @@ export type CanvasAssetKind =
|
||||
| 'icon'
|
||||
| 'icon-spritesheet'
|
||||
| 'icon-spec'
|
||||
| 'editor_green_screen_source'
|
||||
| 'publication-material'
|
||||
| 'ui-design'
|
||||
| 'video'
|
||||
|
||||
@@ -281,6 +281,12 @@ describe('ImageCanvasWorldView', () => {
|
||||
title: '角色标签图层',
|
||||
assetKind: 'character',
|
||||
}),
|
||||
createLayer({
|
||||
id: 'layer-source',
|
||||
resourceId: 'resource-source',
|
||||
title: '角色原图',
|
||||
assetKind: 'editor_green_screen_source',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -288,9 +294,11 @@ describe('ImageCanvasWorldView', () => {
|
||||
const characterButton = screen.getByRole('button', {
|
||||
name: '选择角色标签图层',
|
||||
});
|
||||
const sourceButton = screen.getByRole('button', { name: '选择角色原图' });
|
||||
|
||||
expect(within(layerButton).getByText('未知')).toBeTruthy();
|
||||
expect(within(characterButton).getByText('角色')).toBeTruthy();
|
||||
expect(within(sourceButton).getByText('原图')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders snap guides, marquee and floating generation status', () => {
|
||||
|
||||
@@ -205,6 +205,11 @@ const CANVAS_ASSET_KIND_TAG_OPTIONS: Array<{
|
||||
{ value: 'icon', label: '图标', className: 'icon' },
|
||||
{ value: 'icon-spritesheet', label: '图集', className: 'icon-spritesheet' },
|
||||
{ value: 'icon-spec', label: '图标规范', className: 'icon-spec' },
|
||||
{
|
||||
value: 'editor_green_screen_source',
|
||||
label: '原图',
|
||||
className: 'source-image',
|
||||
},
|
||||
{
|
||||
value: 'publication-material',
|
||||
label: '宣发素材',
|
||||
|
||||
@@ -1307,8 +1307,32 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
layerId: 'layer-editor-resource-character',
|
||||
resourceId: 'editor-resource-character',
|
||||
},
|
||||
{
|
||||
layerId: 'layer-editor-resource-character-source',
|
||||
resourceId: 'editor-resource-character-source',
|
||||
},
|
||||
],
|
||||
resources: [
|
||||
{
|
||||
resourceId: 'editor-resource-character',
|
||||
projectId: 'editor-project-1',
|
||||
imageSrc: '/generated-character-drafts/editor/character.png',
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
sourceType: 'generated',
|
||||
assetKind: 'character',
|
||||
},
|
||||
{
|
||||
resourceId: 'editor-resource-character-source',
|
||||
projectId: 'editor-project-1',
|
||||
imageSrc:
|
||||
'/generated-character-drafts/editor/character-green-screen-source.png',
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
sourceType: 'generated',
|
||||
assetKind: 'editor_green_screen_source',
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
updatedAt: '2026-06-21T12:16:22.000Z',
|
||||
},
|
||||
});
|
||||
@@ -1366,6 +1390,24 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
expect.objectContaining({
|
||||
projectId: 'editor-project-1',
|
||||
title: '角色项目',
|
||||
layers: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
resourceId: 'editor-resource-character',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
resourceId: 'editor-resource-character-source',
|
||||
}),
|
||||
]),
|
||||
resources: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
resourceId: 'editor-resource-character',
|
||||
assetKind: 'character',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
resourceId: 'editor-resource-character-source',
|
||||
assetKind: 'editor_green_screen_source',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6335,6 +6335,11 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
box-shadow: 0 8px 18px rgba(13, 148, 136, 0.24);
|
||||
}
|
||||
|
||||
.image-canvas-editor__kind-badge--source-image {
|
||||
background: rgba(71, 85, 105, 0.9);
|
||||
box-shadow: 0 8px 18px rgba(71, 85, 105, 0.22);
|
||||
}
|
||||
|
||||
.image-canvas-editor__kind-badge--ui-design {
|
||||
background: rgba(245, 158, 11, 0.94);
|
||||
box-shadow: 0 8px 18px rgba(245, 158, 11, 0.24);
|
||||
|
||||
Reference in New Issue
Block a user