feat: 液化撤销问题修复+选取更改逻辑+右键删除组图层问题修复

This commit is contained in:
bighuixiang
2025-07-09 00:22:03 +08:00
parent 5cc93aeba4
commit 943b49c1d7
9 changed files with 1668 additions and 545 deletions

View File

@@ -5,7 +5,10 @@ import {
} from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js";
import { CreateImageLayerCommand } from "./LayerCommands.js";
import {
CreateImageLayerCommand,
RemoveLayerCommand,
} from "./LayerCommands.js";
import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js";
@@ -41,6 +44,13 @@ export class LassoCutoutCommand extends CompositeCommand {
// 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象
this._clonedSelectionObject = null;
this._initializeClonedSelection();
// 新增:保存原图层信息用于撤销恢复
this.originalLayer = null; // 保存原图层的完整信息
this.originalLayerIndex = -1; // 保存原图层在layers数组中的索引
this.originalFabricObjects = []; // 保存原图层的所有fabric对象序列化
this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象
this._initializeOriginalLayerInfo();
}
/**
@@ -70,6 +80,43 @@ export class LassoCutoutCommand extends CompositeCommand {
}
}
/**
* 初始化原图层信息
* @private
*/
_initializeOriginalLayerInfo() {
if (this.layerManager) {
const activeLayer = this.layerManager.getActiveLayer();
if (activeLayer) {
// 保存原图层的完整信息
this.originalLayer = JSON.parse(JSON.stringify(activeLayer)); // 深拷贝
// 保存原图层在layers数组中的索引
this.originalLayerIndex = this.layerManager.layers.value.findIndex(
(layer) => layer.id === activeLayer.id
);
// 获取并序列化原图层的所有fabric对象
const sourceObjects = this._getLayerObjects(activeLayer);
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
this.originalFabricObjects = sourceObjects.map((obj) =>
obj.toObject([
"id",
"layerId",
"layerName",
"parentId",
"type",
"custom",
])
);
console.log(
`套索抠图:已保存原图层信息,图层名: ${activeLayer.name},对象数量: ${sourceObjects.length}`
);
}
}
}
async execute() {
if (!this.canvas || !this.layerManager || !this.selectionManager) {
console.error("无法执行套索抠图:参数无效");
@@ -146,7 +193,19 @@ export class LassoCutoutCommand extends CompositeCommand {
this.newLayerId = createImageLayerCmd.newLayerId;
this.executedCommands.push(createImageLayerCmd);
// 2. 清除选区命令
// 2. 删除原图层命令
const removeOriginalLayerCmd = new RemoveLayerCommand({
canvas: this.canvas,
layers: this.layerManager.layers,
layerId: this.originalLayer.id,
activeLayerId: this.layerManager.activeLayerId,
});
// 执行删除原图层命令
await removeOriginalLayerCmd.execute();
this.executedCommands.push(removeOriginalLayerCmd);
// 3. 清除选区命令
const clearSelectionCmd = new ClearSelectionCommand({
canvas: this.canvas,
selectionManager: this.selectionManager,
@@ -276,6 +335,7 @@ export class LassoCutoutCommand extends CompositeCommand {
}
// 2. 逆序撤销所有已执行的子命令
// RemoveLayerCommand的undo会自动恢复原图层所以不需要再调用_restoreOriginalLayer
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
const command = this.executedCommands[i];
if (command && typeof command.undo === "function") {
@@ -293,6 +353,12 @@ export class LassoCutoutCommand extends CompositeCommand {
}
}
// 注意不需要调用_restoreOriginalLayer因为RemoveLayerCommand的undo已经恢复了原图层
// 但是需要确保画布状态正确,所以手动触发一次渲染
if (this.canvas) {
this.canvas.renderAll();
}
// 3. 清理状态
this.executedCommands = [];
this.newLayerId = null;
@@ -331,6 +397,13 @@ export class LassoCutoutCommand extends CompositeCommand {
baseResolutionScale: this.baseResolutionScale,
hasSerializedSelection: !!this.serializedSelectionObject,
selectionType: this.serializedSelectionObject?.type || null,
// 新增:原图层信息
hasOriginalLayer: !!this.originalLayer,
originalLayerName: this.originalLayer?.name || null,
originalLayerId: this.originalLayer?.id || null,
originalLayerIndex: this.originalLayerIndex,
originalFabricObjectsCount: this.originalFabricObjects.length,
originalCanvasObjectsCount: this.originalCanvasObjects.length,
subCommands: this.executedCommands.map((cmd) => ({
name: cmd.constructor.name,
info: cmd.getInfo ? cmd.getInfo() : {},
@@ -815,6 +888,109 @@ export class LassoCutoutCommand extends CompositeCommand {
return null;
}
}
/**
* 恢复原图层和其fabric对象
* @private
*/
async _restoreOriginalLayer() {
if (!this.originalLayer) {
console.warn("没有保存的原图层信息,无法恢复");
return false;
}
try {
console.log(`↩️ 开始恢复原图层: ${this.originalLayer.name}`);
// 1. 恢复图层到原位置
if (this.originalLayerIndex !== -1) {
this.layerManager.layers.value.splice(
this.originalLayerIndex,
0,
this.originalLayer
);
} else {
// 如果没有保存索引,添加到末尾
this.layerManager.layers.value.push(this.originalLayer);
}
// 2. 恢复fabric对象到画布
if (this.originalFabricObjects.length > 0) {
console.log(
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
);
// 使用fabric.util.enlivenObjects批量反序列化对象
await new Promise((resolve, reject) => {
fabric.util.enlivenObjects(
this.originalFabricObjects,
(restoredObjects) => {
if (!restoredObjects || restoredObjects.length === 0) {
console.warn("没有成功反序列化任何对象");
resolve();
return;
}
// 将恢复的对象添加到画布
restoredObjects.forEach((obj) => {
if (obj) {
// 确保对象的图层信息正确
obj.layerId = this.originalLayer.id;
obj.layerName = this.originalLayer.name;
obj.setCoords();
this.canvas.add(obj);
}
});
console.log(`✅ 成功恢复 ${restoredObjects.length} 个fabric对象`);
resolve();
},
// 命名空间,用于自定义对象类型
""
);
});
}
// 3. 如果恢复的是活动图层,设置为当前活动图层
if (this.originalLayer.id === this.layerManager.activeLayerId.value) {
this.layerManager.activeLayerId.value = this.originalLayer.id;
} else {
// 如果当前没有活动图层,将恢复的图层设为活动图层
if (!this.layerManager.activeLayerId.value) {
this.layerManager.activeLayerId.value = this.originalLayer.id;
}
}
// 4. 重新渲染画布
this.canvas.renderAll();
console.log(`✅ 原图层恢复完成: ${this.originalLayer.name}`);
return true;
} catch (error) {
console.error("❌ 恢复原图层失败:", error);
return false;
}
}
/**
* 重做操作
* @returns {Promise<Object|boolean>} 执行结果
*/
async redo() {
console.log(`🔄 开始重做套索抠图操作`);
// 重做操作等同于重新执行,但需要先清理一些状态
this.executedCommands = [];
this.newLayerId = null;
this.cutoutImageUrl = null;
this.fabricImage = null;
this.groupLayer = null;
// 重新生成groupId确保唯一性可选也可以复用原来的
// this.groupId = `cutout-group-${Date.now()}`;
return await this.execute();
}
}
/**