2025-06-09 10:25:54 +08:00
|
|
|
|
import { Command } from "./Command";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import {
|
|
|
|
|
|
createLayer,
|
|
|
|
|
|
findLayerRecursively,
|
|
|
|
|
|
LayerType,
|
|
|
|
|
|
OperationType,
|
|
|
|
|
|
} from "../utils/layerHelper";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
import { createStaticCanvas } from "../utils/canvasFactory";
|
|
|
|
|
|
import { AddObjectToLayerCommand } from "./ObjectLayerCommands";
|
|
|
|
|
|
import { ToolCommand } from "./ToolCommands";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import {
|
|
|
|
|
|
findObjectById,
|
|
|
|
|
|
generateId,
|
|
|
|
|
|
objectIsInCanvas,
|
|
|
|
|
|
optimizeCanvasRendering,
|
|
|
|
|
|
removeCanvasObjectByObject,
|
|
|
|
|
|
} from "../utils/helper";
|
|
|
|
|
|
import { fabric } from "fabric-with-all";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 添加图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class AddLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "添加图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.newLayer = options.newLayer;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
this.insertIndex = options.insertIndex;
|
|
|
|
|
|
this.oldActiveLayerId = null;
|
|
|
|
|
|
this.beforeLayers = [...this.layers.value]; // 备份原图层列表
|
2025-07-04 03:16:18 +08:00
|
|
|
|
|
|
|
|
|
|
this.options = options.options || {};
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
// 保存当前活动图层ID
|
|
|
|
|
|
this.oldActiveLayerId = this.activeLayerId.value;
|
|
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
// 执行添加顶级图层操作
|
|
|
|
|
|
if (this.options?.insertTop) {
|
|
|
|
|
|
this.layers.value.splice(0, 0, this.newLayer);
|
|
|
|
|
|
this.activeLayerId.value = this.newLayer.id;
|
|
|
|
|
|
return this.newLayer.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-30 18:05:20 +08:00
|
|
|
|
// 智能插入图层到合适位置
|
|
|
|
|
|
this._insertLayerAtCorrectPosition(this.newLayer);
|
|
|
|
|
|
// if (this.insertIndex !== undefined && this.insertIndex !== null) {
|
|
|
|
|
|
// this.layers.value.splice(this.insertIndex, 0, this.newLayer);
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// this.layers.value.push(this.newLayer);
|
|
|
|
|
|
// }
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-30 18:05:20 +08:00
|
|
|
|
// // 更新活动图层
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.newLayer.isBackground) {
|
|
|
|
|
|
this.activeLayerId.value = this.newLayer.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.newLayer.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
// 从图层列表删除该图层
|
|
|
|
|
|
this.layers.value = this.beforeLayers;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.oldActiveLayerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-30 18:05:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 智能插入图层到正确位置
|
|
|
|
|
|
* 根据当前激活图层位置确定新图层插入位置
|
|
|
|
|
|
* @param {Object} newLayer 要插入的新图层
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_insertLayerAtCorrectPosition(newLayer) {
|
|
|
|
|
|
const layers = this.layers.value;
|
|
|
|
|
|
const currentActiveLayerId = this.activeLayerId?.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有当前激活图层,插入到顶部(索引0)
|
|
|
|
|
|
if (!currentActiveLayerId) {
|
|
|
|
|
|
layers.splice(0, 0, newLayer);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找当前激活图层的位置
|
|
|
|
|
|
const {
|
|
|
|
|
|
layer: activeLayer,
|
|
|
|
|
|
parent: parentLayer,
|
|
|
|
|
|
index: activeIndex,
|
|
|
|
|
|
} = this._findLayerPosition(currentActiveLayerId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!activeLayer) {
|
|
|
|
|
|
// 没找到激活图层,插入到顶部
|
|
|
|
|
|
layers.splice(0, 0, newLayer);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确定插入位置
|
|
|
|
|
|
let insertIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (parentLayer) {
|
|
|
|
|
|
// 当前激活图层是子图层
|
|
|
|
|
|
// 在同一父图层内,插入到激活子图层之上
|
|
|
|
|
|
insertIndex = Math.max(0, activeIndex);
|
|
|
|
|
|
parentLayer.children = parentLayer.children || [];
|
|
|
|
|
|
parentLayer.children.splice(insertIndex, 0, newLayer);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 当前激活图层是一级图层
|
|
|
|
|
|
// 在一级图层中,插入到激活图层之上
|
|
|
|
|
|
const activeLayerIndex = layers.findIndex(
|
|
|
|
|
|
(layer) => layer.id === currentActiveLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
insertIndex = Math.max(0, activeLayerIndex);
|
|
|
|
|
|
layers.splice(insertIndex, 0, newLayer);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`新图层已插入到一级图层位置: ${insertIndex}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查找图层位置信息
|
|
|
|
|
|
* @param {String} layerId 图层ID
|
|
|
|
|
|
* @returns {Object} 包含图层、父图层和索引的对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_findLayerPosition(layerId) {
|
|
|
|
|
|
const layers = this.layers.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 先在一级图层中查找
|
|
|
|
|
|
for (let i = 0; i < layers.length; i++) {
|
|
|
|
|
|
const layer = layers[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (layer.id === layerId) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
layer: layer,
|
|
|
|
|
|
parent: null,
|
|
|
|
|
|
index: i,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在子图层中查找
|
|
|
|
|
|
if (layer.children && Array.isArray(layer.children)) {
|
|
|
|
|
|
for (let j = 0; j < layer.children.length; j++) {
|
|
|
|
|
|
const childLayer = layer.children[j];
|
|
|
|
|
|
if (childLayer.id === layerId) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
layer: childLayer,
|
|
|
|
|
|
parent: layer,
|
|
|
|
|
|
index: j,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
layer: null,
|
|
|
|
|
|
parent: null,
|
|
|
|
|
|
index: -1,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.newLayer.name,
|
|
|
|
|
|
layerId: this.newLayer.id,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 粘贴并创建图层命令 - 更新为支持异步操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class PasteLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "粘贴图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.clipboardData = options.clipboardData;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
|
|
|
|
|
|
// 新图层相关属性
|
|
|
|
|
|
this.newLayer = null;
|
|
|
|
|
|
this.newLayerId = null;
|
|
|
|
|
|
this.insertIndex = null;
|
|
|
|
|
|
this.oldActiveLayerId = null;
|
|
|
|
|
|
this.createdObjects = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.clipboardData) {
|
|
|
|
|
|
console.error("剪贴板中没有图层数据");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = this.clipboardData;
|
|
|
|
|
|
const fabric = window.fabric;
|
|
|
|
|
|
|
|
|
|
|
|
if (!fabric) {
|
|
|
|
|
|
console.error("未找到fabric库");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成新的图层ID
|
|
|
|
|
|
this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新图层
|
|
|
|
|
|
this.newLayer = {
|
|
|
|
|
|
...data,
|
|
|
|
|
|
id: this.newLayerId,
|
|
|
|
|
|
name: `${data.name} 副本`,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
isCut: undefined,
|
|
|
|
|
|
serializedObjects: undefined,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 保存当前活动图层ID
|
|
|
|
|
|
this.oldActiveLayerId = this.activeLayerId.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算插入位置
|
|
|
|
|
|
this.insertIndex = this.layerManager._getInsertIndexAboveActiveLayer();
|
|
|
|
|
|
|
|
|
|
|
|
// 执行添加图层操作
|
|
|
|
|
|
if (this.insertIndex !== undefined && this.insertIndex !== null) {
|
|
|
|
|
|
this.layers.value.splice(this.insertIndex, 0, this.newLayer);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.layers.value.push(this.newLayer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新活动图层
|
|
|
|
|
|
if (!this.newLayer.isBackground) {
|
|
|
|
|
|
this.activeLayerId.value = this.newLayer.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有序列化的对象,异步恢复它们
|
|
|
|
|
|
if (
|
|
|
|
|
|
data.serializedObjects &&
|
|
|
|
|
|
Array.isArray(data.serializedObjects) &&
|
|
|
|
|
|
data.serializedObjects.length > 0
|
|
|
|
|
|
) {
|
|
|
|
|
|
await this._restoreObjectsAsync(data);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this._onObjectsRestored(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.newLayerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 异步恢复序列化的对象
|
|
|
|
|
|
* @param {Object} data 剪贴板数据
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _restoreObjectsAsync(data) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
fabric.util.enlivenObjects(data.serializedObjects, (objects) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
objects.forEach((obj) => {
|
|
|
|
|
|
// 生成新的对象ID
|
|
|
|
|
|
const newObjId = `obj_${Date.now()}_${Math.floor(
|
|
|
|
|
|
Math.random() * 1000
|
|
|
|
|
|
)}`;
|
|
|
|
|
|
obj.id = newObjId;
|
|
|
|
|
|
obj.layerId = this.newLayerId;
|
|
|
|
|
|
obj.layerName = this.newLayer.name;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是复制操作,给对象添加偏移量
|
|
|
|
|
|
if (!data.isCut) {
|
|
|
|
|
|
const offset = 10;
|
|
|
|
|
|
if (obj.left !== undefined) obj.left += offset;
|
|
|
|
|
|
if (obj.top !== undefined) obj.top += offset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到画布
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到图层
|
|
|
|
|
|
this.newLayer.fabricObjects.push(obj);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录创建的对象,用于撤销
|
|
|
|
|
|
this.createdObjects.push(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this._onObjectsRestored(data);
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("恢复对象时发生错误:", error);
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 对象恢复完成后的处理
|
|
|
|
|
|
* @param {Object} data 剪贴板数据
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_onObjectsRestored(data) {
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.layerManager?.updateLayersObjectsInteractivity?.();
|
|
|
|
|
|
|
|
|
|
|
|
// 重新排列对象
|
|
|
|
|
|
this.layerManager?._rearrangeObjects?.();
|
|
|
|
|
|
|
|
|
|
|
|
// 判断如果是剪切操作,粘贴完后需要删除剪贴板数据
|
|
|
|
|
|
if (data.isCut && this.layerManager) {
|
|
|
|
|
|
this.layerManager.clipboardData = null;
|
|
|
|
|
|
console.log(`已粘贴图层:${this.newLayer.name}(剪切)`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`已粘贴图层:${this.newLayer.name}(复制)`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (!this.newLayer || !this.newLayerId) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层列表删除该图层
|
|
|
|
|
|
const index = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.newLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
this.layers.value.splice(index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.oldActiveLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 从画布移除所有创建的对象
|
|
|
|
|
|
this.createdObjects.forEach((obj) => {
|
|
|
|
|
|
this.canvas.remove(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 如果图层有其他fabric对象,也要移除
|
|
|
|
|
|
if (this.newLayer.fabricObjects && this.newLayer.fabricObjects.length > 0) {
|
|
|
|
|
|
this.newLayer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (!this.createdObjects.includes(obj)) {
|
|
|
|
|
|
this.canvas.remove(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是剪切操作的撤销,需要恢复剪贴板数据
|
|
|
|
|
|
if (this.clipboardData && this.clipboardData.isCut && this.layerManager) {
|
|
|
|
|
|
this.layerManager.clipboardData = this.clipboardData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.layerManager &&
|
|
|
|
|
|
typeof this.layerManager.updateLayersObjectsInteractivity === "function"
|
|
|
|
|
|
) {
|
|
|
|
|
|
this.layerManager.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.newLayer?.name || "未知图层",
|
|
|
|
|
|
layerId: this.newLayerId,
|
|
|
|
|
|
objectCount: this.createdObjects.length,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移除图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class RemoveLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "移除图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找要删除的图层
|
|
|
|
|
|
this.layerIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
this.removedLayer = this.layers.value[this.layerIndex];
|
|
|
|
|
|
this.isActiveLayer = this.layerId === this.activeLayerId.value;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 从Canvas中找到真实对象并备份,确保撤销和重做时对象的一致性
|
|
|
|
|
|
this.originalObjects = [];
|
|
|
|
|
|
if (this.removedLayer) {
|
|
|
|
|
|
// 从画布中获取真实的对象引用
|
|
|
|
|
|
this.originalObjects = this.canvas.getObjects().filter((obj) => {
|
|
|
|
|
|
return obj.layerId === this.layerId;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 备份原活动图层ID
|
|
|
|
|
|
this.originalActiveLayerId = this.activeLayerId.value;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (this.layerIndex === -1 || !this.removedLayer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 从画布中移除图层中的所有真实对象
|
|
|
|
|
|
this.originalObjects.forEach((obj) => {
|
|
|
|
|
|
if (this.canvas.getObjects().includes(obj)) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.canvas.remove(obj);
|
2025-06-22 13:52:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是背景图层,移除特殊对象
|
|
|
|
|
|
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
const { object } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.removedLayer.fabricObject.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (object) {
|
|
|
|
|
|
this.canvas.remove(object);
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层列表中删除
|
|
|
|
|
|
this.layers.value.splice(this.layerIndex, 1);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果删除的是当前活动图层,需要更新活动图层
|
|
|
|
|
|
if (this.isActiveLayer) {
|
|
|
|
|
|
// 查找最近的非背景层作为新的活动图层
|
|
|
|
|
|
const newActiveLayer = this.layers.value.find(
|
|
|
|
|
|
(layer) => !layer.isBackground
|
|
|
|
|
|
);
|
|
|
|
|
|
if (newActiveLayer) {
|
|
|
|
|
|
this.activeLayerId.value = newActiveLayer.id;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.activeLayerId.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
console.log(
|
|
|
|
|
|
`✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId})`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 恢复图层到原位置
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (this.layerIndex !== -1 && this.removedLayer) {
|
|
|
|
|
|
this.layers.value.splice(this.layerIndex, 0, this.removedLayer);
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 使用优化渲染批处理恢复真实对象到画布
|
|
|
|
|
|
optimizeCanvasRendering(this.canvas, () => {
|
|
|
|
|
|
this.originalObjects.forEach((obj) => {
|
|
|
|
|
|
// 恢复对象到画布
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.canvas.add(obj);
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 确保对象的图层信息正确
|
|
|
|
|
|
obj.layerId = this.layerId;
|
|
|
|
|
|
obj.layerName = this.removedLayer.name;
|
|
|
|
|
|
obj.setCoords(); // 更新坐标
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 如果是背景图层,恢复特殊对象
|
|
|
|
|
|
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
|
|
|
|
|
|
// 检查对象是否已在画布中
|
|
|
|
|
|
const { object } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.removedLayer.fabricObject.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!object) {
|
|
|
|
|
|
this.canvas.add(this.removedLayer.fabricObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果删除的是当前活动图层,恢复活动图层
|
|
|
|
|
|
if (this.isActiveLayer) {
|
|
|
|
|
|
this.activeLayerId.value = this.layerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
console.log(
|
|
|
|
|
|
`↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId})`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.removedLayer?.name || "未知图层",
|
|
|
|
|
|
layerId: this.layerId,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
objectCount: this.originalObjects.length,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移动图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MoveLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: `移动图层 ${options.direction === "up" ? "上移" : "下移"}`,
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.direction = options.direction; // "up" or "down"
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layerSort = options.layerSort;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.parentLayer = null; // 父图层
|
|
|
|
|
|
|
|
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
2025-06-09 10:25:54 +08:00
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
|
|
|
|
|
|
if (parent?.id) {
|
|
|
|
|
|
// 查找子图层索引
|
|
|
|
|
|
this.layerIndex = parent?.children?.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
this.parentLayer = parent;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 查找图层索引
|
|
|
|
|
|
this.layerIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
this.originalIndex = this.layerIndex;
|
|
|
|
|
|
// 目标位置
|
|
|
|
|
|
this.targetIndex =
|
|
|
|
|
|
options.direction === "up" ? this.layerIndex - 1 : this.layerIndex + 1;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
|
|
|
|
|
if (this.layerIndex === -1 || !this.layer) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 检查是否是背景层或固定层(不允许移动)
|
|
|
|
|
|
if (this.layer.isBackground || this.layer.isFixed) {
|
|
|
|
|
|
console.warn("背景层和固定层不能移动");
|
|
|
|
|
|
return false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果已经在顶部,无需移动
|
|
|
|
|
|
if (this.layerIndex === this.targetIndex) {
|
|
|
|
|
|
console.log("图层已在顶部位置");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 执行移动
|
|
|
|
|
|
if (this.parentLayer) {
|
|
|
|
|
|
const tempLayer = this.parentLayer.children[this.targetIndex];
|
|
|
|
|
|
this.parentLayer.children[this.layerIndex] = tempLayer;
|
|
|
|
|
|
this.parentLayer.children[this.targetIndex] = this.layer;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const tempLayer = this.layers.value[this.targetIndex];
|
|
|
|
|
|
this.layers.value[this.layerIndex] = tempLayer;
|
|
|
|
|
|
this.layers.value[this.targetIndex] = this.layer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用LayerSort工具重新排列画布对象(如果可用)
|
|
|
|
|
|
await this.layerSort?.rearrangeObjects();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`图层${this.layer.name}已经移动`);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
|
|
|
|
|
const tempOldIndex = this.layerIndex;
|
|
|
|
|
|
this.layerIndex = this.targetIndex;
|
|
|
|
|
|
this.targetIndex = tempOldIndex;
|
|
|
|
|
|
await this.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 执行完成恢复索引
|
|
|
|
|
|
this.targetIndex = this.layerIndex;
|
|
|
|
|
|
this.layerIndex = tempOldIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(`↩️ 图层 ${this.layer.name} 已恢复到原位置`);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
direction: this.direction,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换图层可见性命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ToggleLayerVisibilityCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "切换图层可见性",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.layerManager = options.layerManager;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
this.layer = layer;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// this.oldVisibility = this.layer ? this.layer.visible : null;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async execute() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换可见性
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.layer.visible = !this.layer.visible;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新画布上图层对象的可见性
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.visible = this.layer.visible;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 更新画布上对象的可选择状态
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return this.layer.visible;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async undo() {
|
|
|
|
|
|
return await this.execute(); // 直接调用execute方法来恢复可见性
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.layer?.name || "未知图层",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
newVisibility: this.layer?.visible,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 切换子图层可见性命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ToggleChildLayerVisibilityCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "切换子图层可见性",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.parentId = options.parentId;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 查找父图层和子图层
|
|
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
this.parentLayer = parent;
|
|
|
|
|
|
this.childLayer = layer;
|
|
|
|
|
|
|
|
|
|
|
|
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.childLayer) {
|
|
|
|
|
|
throw new Error("找不到要切换可见性的子图层");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换可见性
|
|
|
|
|
|
this.childLayer.visible = !this.childLayer.visible;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上图层对象的可见性
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.visible = this.childLayer.visible;
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的可选择状态
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
|
|
|
|
|
return this.childLayer.visible;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
return await this.execute(); // 直接调用execute方法来恢复可见性
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
layerName: this.childLayer?.name || "未知子图层",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
layerId: this.layerId,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
parentId: this.parentId,
|
|
|
|
|
|
newVisibility: this.childLayer?.visible,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重命名图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class RenameLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "重命名图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.newName = options.newName;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.oldName = this.layer ? this.layer.name : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层名称
|
|
|
|
|
|
this.layer.name = this.newName;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层对象上的图层名称
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.layerName = this.newName;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (this.layer && this.oldName) {
|
|
|
|
|
|
// 恢复图层名称
|
|
|
|
|
|
this.layer.name = this.oldName;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复图层对象上的图层名称
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.layerName = this.oldName;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldName: this.oldName,
|
|
|
|
|
|
newName: this.newName,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 图层锁定/解锁命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class LayerLockCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "图层锁定/解锁",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layerManager = options.layerManager;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 查找图层(包括子图层)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
this.parentLayer = parent || null; // 父图层
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.oldLocked = this.layer ? this.layer.locked : null;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 更新图层及其子图层的锁定状态
|
|
|
|
|
|
* @param {Object} layer 要更新的图层
|
|
|
|
|
|
* @param {boolean} locked 锁定状态
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_updateLayerLockState(layer, locked) {
|
|
|
|
|
|
if (!layer) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新当前图层的锁定状态
|
|
|
|
|
|
layer.locked = locked;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是组图层,递归更新所有子图层
|
|
|
|
|
|
if (
|
|
|
|
|
|
layer.type === "group" &&
|
|
|
|
|
|
layer.children &&
|
|
|
|
|
|
Array.isArray(layer.children)
|
|
|
|
|
|
) {
|
|
|
|
|
|
layer.children.forEach((child) => {
|
|
|
|
|
|
this._updateLayerLockState(child, locked);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换锁定状态
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const newLocked = !this.oldLocked;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新图层及其子图层的锁定状态
|
|
|
|
|
|
this._updateLayerLockState(this.layer, newLocked);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新画布上对象的可选择状态
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(
|
|
|
|
|
|
`${newLocked ? "锁定" : "解锁"}图层: ${this.layer.name} (ID: ${
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
})`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (this.layer) {
|
|
|
|
|
|
// 恢复锁定状态
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this._updateLayerLockState(this.layer, this.oldLocked);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的可选择状态
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(
|
|
|
|
|
|
`恢复图层锁定状态: ${this.layer.name} (锁定: ${this.oldLocked})`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.layer?.name || "未知图层",
|
|
|
|
|
|
layerId: this.layerId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
isChildLayer: !!this.parentLayer,
|
|
|
|
|
|
parentLayerName: this.parentLayer?.name,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
newLocked: this.layer?.locked,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
oldLocked: this.oldLocked,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置图层不透明度命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class SetLayerOpacityCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "设置图层不透明度",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.opacity = options.opacity;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
this.layer = layer;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.oldOpacity = this.layer ? this.layer.opacity : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置图层不透明度
|
|
|
|
|
|
this.layer.opacity = this.opacity;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的不透明度
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.opacity = this.opacity;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (this.layer && this.oldOpacity !== null) {
|
|
|
|
|
|
// 恢复图层不透明度
|
|
|
|
|
|
this.layer.opacity = this.oldOpacity;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的不透明度
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.opacity = this.oldOpacity;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.layer?.name || "未知图层",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldOpacity: this.oldOpacity,
|
|
|
|
|
|
newOpacity: this.opacity,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置图层混合模式命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class SetLayerBlendModeCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "设置图层混合模式",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.blendMode = options.blendMode;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.oldBlendMode = this.layer ? this.layer.blendMode : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置图层混合模式
|
|
|
|
|
|
this.layer.blendMode = this.blendMode;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的混合模式
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.globalCompositeOperation = this.blendMode;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (this.layer && this.oldBlendMode) {
|
|
|
|
|
|
// 恢复图层混合模式
|
|
|
|
|
|
this.layer.blendMode = this.oldBlendMode;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的混合模式
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
const layerObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
layerObjects.forEach((obj) => {
|
|
|
|
|
|
obj.globalCompositeOperation = this.oldBlendMode;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.layer?.name || "未知图层",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldBlendMode: this.oldBlendMode,
|
|
|
|
|
|
newBlendMode: this.blendMode,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MergeLayersCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "合并图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerIds = options.layerIds;
|
|
|
|
|
|
this.newName = options.newName;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId; // <--- 新增
|
|
|
|
|
|
|
|
|
|
|
|
// 备份原图层
|
|
|
|
|
|
this.originalLayers = [...this.layers.value];
|
|
|
|
|
|
// 新图层ID
|
|
|
|
|
|
this.newLayerId = `merged_layer_${Date.now()}_${Math.floor(
|
|
|
|
|
|
Math.random() * 1000
|
|
|
|
|
|
)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!this.layerIds ||
|
|
|
|
|
|
!Array.isArray(this.layerIds) ||
|
|
|
|
|
|
this.layerIds.length < 2
|
|
|
|
|
|
) {
|
|
|
|
|
|
console.error("合并图层至少需要两个图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找所有要合并的图层
|
|
|
|
|
|
const layersToMerge = this.layerIds
|
|
|
|
|
|
.map((id) => this.layers.value.find((layer) => layer.id === id))
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
if (layersToMerge.length < 2) {
|
|
|
|
|
|
console.error("找不到足够的图层进行合并");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否包含背景图层
|
|
|
|
|
|
if (layersToMerge.some((layer) => layer.isBackground)) {
|
|
|
|
|
|
console.error("不能合并背景图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const topLayerIndex = Math.min(
|
2025-06-18 11:05:23 +08:00
|
|
|
|
...layersToGroup.map((layer) =>
|
|
|
|
|
|
this.layers.value.findIndex((fLayer) => fLayer.id === layer.id)
|
|
|
|
|
|
)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取要保留的所有对象
|
|
|
|
|
|
const allObjects = [];
|
|
|
|
|
|
layersToMerge.forEach((layer) => {
|
|
|
|
|
|
if (Array.isArray(layer.fabricObjects)) {
|
|
|
|
|
|
allObjects.push(...layer.fabricObjects);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的合并图层
|
|
|
|
|
|
const mergedLayer = createLayer({
|
|
|
|
|
|
id: this.newLayerId,
|
|
|
|
|
|
name: this.newName || `合并图层`,
|
|
|
|
|
|
type: LayerType.BITMAP,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
fabricObjects: allObjects,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象与新图层的关联
|
|
|
|
|
|
allObjects.forEach((obj) => {
|
|
|
|
|
|
obj.layerId = mergedLayer.id;
|
|
|
|
|
|
obj.layerName = mergedLayer.name;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 移除原图层
|
|
|
|
|
|
this.layers.value = this.layers.value.filter(
|
|
|
|
|
|
(layer) => !this.layerIds.includes(layer.id)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 插入新图层
|
|
|
|
|
|
this.layers.value.splice(topLayerIndex, 0, mergedLayer);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新当前活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.newLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.newLayerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
// 恢复原始图层状态
|
|
|
|
|
|
this.layers.value = [...this.originalLayers];
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复活动图层
|
|
|
|
|
|
if (this.activeLayerId) {
|
|
|
|
|
|
// 恢复到合并前的活动图层(取第一个合并前图层)
|
|
|
|
|
|
this.activeLayerId.value = this.layerIds[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerIds: this.layerIds,
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
newName: this.newName,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 图层组合命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class GroupLayersCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "组合图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerIds = options.layerIds;
|
|
|
|
|
|
this.groupName = options.groupName;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId; // <--- 新增
|
|
|
|
|
|
|
|
|
|
|
|
// 备份原图层
|
|
|
|
|
|
this.originalLayers = [...this.layers.value];
|
|
|
|
|
|
// 新组ID
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.groupId =
|
|
|
|
|
|
generateId("group_layer_") ||
|
|
|
|
|
|
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
this.originalActiveLayerId = this.activeLayerId.value; // 备份原活动图层ID
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (
|
|
|
|
|
|
!this.layerIds ||
|
|
|
|
|
|
!Array.isArray(this.layerIds) ||
|
|
|
|
|
|
this.layerIds.length < 2
|
|
|
|
|
|
) {
|
|
|
|
|
|
console.error("组合图层至少需要两个图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找所有要组合的图层
|
|
|
|
|
|
const layersToGroup = this.layerIds
|
2025-06-18 11:05:23 +08:00
|
|
|
|
.map((id) => {
|
|
|
|
|
|
const tempLayer = this.layers.value.find((layer) => layer.id === id); // 需要给子图层加上parentId
|
|
|
|
|
|
return tempLayer ? { ...tempLayer, parentId: this.groupId } : false;
|
|
|
|
|
|
})
|
2025-06-09 10:25:54 +08:00
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
if (layersToGroup.length < 2) {
|
|
|
|
|
|
console.error("找不到足够的图层进行组合");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否包含背景图层
|
|
|
|
|
|
if (layersToGroup.some((layer) => layer.isBackground)) {
|
|
|
|
|
|
console.error("不能组合背景图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找最顶层图层的索引,用于插入组图层
|
|
|
|
|
|
const topLayerIndex = Math.min(
|
2025-06-18 11:05:23 +08:00
|
|
|
|
...layersToGroup.map((layer) =>
|
|
|
|
|
|
this.layers.value.findIndex((fLayer) => fLayer.id === layer.id)
|
|
|
|
|
|
)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的组图层
|
|
|
|
|
|
const groupLayer = createLayer({
|
|
|
|
|
|
id: this.groupId,
|
|
|
|
|
|
name: this.groupName || `图层组`,
|
|
|
|
|
|
type: LayerType.GROUP,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
children: layersToGroup,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 移除原图层
|
|
|
|
|
|
this.layers.value = this.layers.value.filter(
|
|
|
|
|
|
(layer) => !this.layerIds.includes(layer.id)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 插入新组图层
|
|
|
|
|
|
this.layers.value.splice(topLayerIndex, 0, groupLayer);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新当前活动图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.activeLayerId.value = this.layerIds[0];
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.groupId);
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return this.groupId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 恢复原始图层状态
|
|
|
|
|
|
this.layers.value = [...this.originalLayers];
|
|
|
|
|
|
// 恢复活动图层为原先第一个合并前层
|
|
|
|
|
|
if (this.activeLayerId) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.activeLayerId.value = this.originalActiveLayerId;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerIds: this.layerIds,
|
|
|
|
|
|
groupId: this.groupId,
|
|
|
|
|
|
groupName: this.groupName,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解组图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class UngroupLayersCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "解组图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.groupId = options.groupId;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId; // <--- 新增
|
|
|
|
|
|
|
|
|
|
|
|
// 备份原图层
|
|
|
|
|
|
this.originalLayers = [...this.layers.value];
|
|
|
|
|
|
// 子图层ID列表
|
|
|
|
|
|
this.childLayerIds = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 查找组图层
|
|
|
|
|
|
const groupIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.groupId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (groupIndex === -1) {
|
|
|
|
|
|
console.error(`找不到组图层 ${this.groupId}`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const groupLayer = this.layers.value[groupIndex];
|
|
|
|
|
|
|
|
|
|
|
|
if (!groupLayer.children || groupLayer.children.length === 0) {
|
|
|
|
|
|
console.error(`组图层 ${this.groupId} 没有子图层`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集子图层ID
|
|
|
|
|
|
this.childLayerIds = groupLayer.children.map((layer) => layer.id);
|
|
|
|
|
|
|
|
|
|
|
|
// 将子图层添加到原位置
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layers.value.splice(
|
|
|
|
|
|
groupIndex,
|
|
|
|
|
|
1,
|
|
|
|
|
|
...groupLayer.children?.map((child) => {
|
|
|
|
|
|
// 为子图层添加parentId
|
|
|
|
|
|
delete child.parentId; // 删除parentId属性
|
|
|
|
|
|
return { ...child };
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 更新当前活动图层为第一个子图层
|
|
|
|
|
|
if (this.childLayerIds.length > 0 && this.activeLayerId) {
|
|
|
|
|
|
this.activeLayerId.value = this.childLayerIds[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.childLayerIds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 恢复原始图层状态
|
|
|
|
|
|
this.layers.value = [...this.originalLayers];
|
|
|
|
|
|
// 恢复活动图层为原始组ID
|
|
|
|
|
|
if (this.activeLayerId) {
|
|
|
|
|
|
this.activeLayerId.value = this.groupId;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
groupId: this.groupId,
|
|
|
|
|
|
childLayerIds: this.childLayerIds,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并图层内对象命令(重构版本)
|
|
|
|
|
|
* 将新的图像与图层内现有对象合并为一个高保真图像对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MergeLayerObjectsCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "合并图层内对象",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.fabricImage = options.fabricImage;
|
|
|
|
|
|
this.activeLayer = options.activeLayer;
|
|
|
|
|
|
|
|
|
|
|
|
// 备份原始对象,用于撤销
|
|
|
|
|
|
if (this.activeLayer && Array.isArray(this.activeLayer.fabricObjects)) {
|
|
|
|
|
|
this.originalObjects =
|
|
|
|
|
|
this.canvas
|
|
|
|
|
|
?.getObjects()
|
|
|
|
|
|
?.filter((fItem) => fItem.layerId === this.activeLayer.id) || [];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.originalObjects = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新合并图像对象
|
|
|
|
|
|
this.mergedImage = null;
|
|
|
|
|
|
this.newImageId = `merged_image_${Date.now()}_${Math.floor(
|
|
|
|
|
|
Math.random() * 1000
|
|
|
|
|
|
)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.activeLayer || !this.canvas) {
|
|
|
|
|
|
console.error("图层或Canvas未初始化");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有需要合并的对象(按正确的层级顺序)
|
|
|
|
|
|
const objectsToMerge = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 先添加图层中的现有对象(作为底层)
|
|
|
|
|
|
if (this.originalObjects.length > 0) {
|
|
|
|
|
|
objectsToMerge.push(...this.originalObjects);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 再添加新的图像对象(作为顶层)
|
|
|
|
|
|
if (this.fabricImage) {
|
|
|
|
|
|
objectsToMerge.push(this.fabricImage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (objectsToMerge.length === 0) {
|
|
|
|
|
|
console.log("没有对象需要合并");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算所有对象的合并边界
|
|
|
|
|
|
const bounds = this._calculateMergedBounds(objectsToMerge);
|
|
|
|
|
|
if (!bounds) {
|
|
|
|
|
|
console.error("无法计算合并边界");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 异步处理图像合并
|
|
|
|
|
|
try {
|
|
|
|
|
|
const mergedImage = await this._createMergedImageAsync(
|
|
|
|
|
|
objectsToMerge,
|
|
|
|
|
|
bounds
|
|
|
|
|
|
);
|
|
|
|
|
|
this._setupMergedImage(mergedImage, bounds);
|
|
|
|
|
|
this._replaceObjects(mergedImage);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("图像合并完成");
|
|
|
|
|
|
return this.newImageId;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("图像合并执行失败:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 异步创建合并图像
|
|
|
|
|
|
* @param {Array} objectsToMerge 要合并的对象
|
|
|
|
|
|
* @param {Object} bounds 边界信息
|
|
|
|
|
|
* @returns {Promise<fabric.Image>} 合并后的图像
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _createMergedImageAsync(objectsToMerge, bounds) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 创建高保真临时画布
|
|
|
|
|
|
const tempCanvas = this._createHighQualityTempCanvas(bounds);
|
|
|
|
|
|
const tempFabricCanvas = createStaticCanvas(tempCanvas, {
|
|
|
|
|
|
width: bounds.width,
|
|
|
|
|
|
height: bounds.height,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 设置高质量渲染选项
|
|
|
|
|
|
tempFabricCanvas.enableRetinaScaling = true;
|
|
|
|
|
|
tempFabricCanvas.imageSmoothingEnabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 将所有对象添加到临时画布并调整位置
|
|
|
|
|
|
this._addObjectsToTempCanvas(tempFabricCanvas, objectsToMerge, bounds);
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染临时画布
|
|
|
|
|
|
tempFabricCanvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
// 生成高质量图像
|
|
|
|
|
|
const dataUrl = tempFabricCanvas.toDataURL({
|
|
|
|
|
|
format: "png",
|
|
|
|
|
|
quality: 1.0,
|
|
|
|
|
|
multiplier: 1,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的合并图像
|
|
|
|
|
|
fabric.Image.fromURL(
|
|
|
|
|
|
dataUrl,
|
|
|
|
|
|
(mergedImg) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 清理临时资源
|
|
|
|
|
|
this._cleanupTempCanvas(tempFabricCanvas);
|
|
|
|
|
|
resolve(mergedImg);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("设置合并图像时发生错误:", error);
|
|
|
|
|
|
this._cleanupTempCanvas(tempFabricCanvas);
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
crossOrigin: "anonymous",
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("合并过程中发生错误:", error);
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算所有对象的合并边界
|
|
|
|
|
|
* @param {Array} objects 要合并的对象数组
|
|
|
|
|
|
* @returns {Object} 边界信息
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_calculateMergedBounds(objects) {
|
|
|
|
|
|
if (!objects || objects.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
|
|
let minLeft = Infinity;
|
|
|
|
|
|
let minTop = Infinity;
|
|
|
|
|
|
let maxRight = -Infinity;
|
|
|
|
|
|
let maxBottom = -Infinity;
|
|
|
|
|
|
|
|
|
|
|
|
objects.forEach((obj) => {
|
|
|
|
|
|
if (!obj) return;
|
|
|
|
|
|
|
|
|
|
|
|
const bounds = obj.getBoundingRect(true, true);
|
|
|
|
|
|
minLeft = Math.min(minLeft, bounds.left);
|
|
|
|
|
|
minTop = Math.min(minTop, bounds.top);
|
|
|
|
|
|
maxRight = Math.max(maxRight, bounds.left + bounds.width);
|
|
|
|
|
|
maxBottom = Math.max(maxBottom, bounds.top + bounds.height);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const padding = 1;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
left: minLeft - padding,
|
|
|
|
|
|
top: minTop - padding,
|
|
|
|
|
|
width: maxRight - minLeft + padding * 2,
|
|
|
|
|
|
height: maxBottom - minTop + padding * 2,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建高质量临时画布
|
|
|
|
|
|
* @param {Object} bounds 边界信息
|
|
|
|
|
|
* @returns {HTMLCanvasElement} 临时画布
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_createHighQualityTempCanvas(bounds) {
|
|
|
|
|
|
const tempCanvas = document.createElement("canvas");
|
|
|
|
|
|
tempCanvas.width = Math.ceil(bounds.width);
|
|
|
|
|
|
tempCanvas.height = Math.ceil(bounds.height);
|
|
|
|
|
|
tempCanvas.style.width = bounds.width + "px";
|
|
|
|
|
|
tempCanvas.style.height = bounds.height + "px";
|
|
|
|
|
|
|
|
|
|
|
|
const ctx = tempCanvas.getContext("2d");
|
|
|
|
|
|
ctx.imageSmoothingEnabled = true;
|
|
|
|
|
|
ctx.imageSmoothingQuality = "high";
|
|
|
|
|
|
|
|
|
|
|
|
return tempCanvas;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将对象添加到临时画布并调整位置
|
|
|
|
|
|
* @param {fabric.Canvas} tempCanvas 临时画布
|
|
|
|
|
|
* @param {Array} objects 对象数组
|
|
|
|
|
|
* @param {Object} bounds 边界信息
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_addObjectsToTempCanvas(tempCanvas, objects, bounds) {
|
|
|
|
|
|
objects.forEach((obj, index) => {
|
|
|
|
|
|
if (!obj) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const clonedObj = fabric.util.object.clone(obj);
|
|
|
|
|
|
const objBounds = obj.getBoundingRect(true, true);
|
|
|
|
|
|
const offsetX = objBounds.left - bounds.left;
|
|
|
|
|
|
const offsetY = objBounds.top - bounds.top;
|
|
|
|
|
|
|
|
|
|
|
|
clonedObj.set({
|
|
|
|
|
|
left: offsetX + (clonedObj.width * clonedObj.scaleX) / 2,
|
|
|
|
|
|
top: offsetY + (clonedObj.height * clonedObj.scaleY) / 2,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
tempCanvas.add(clonedObj);
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`添加对象 ${index + 1}/${objects.length}: ${obj.type || "unknown"}`
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`添加对象到临时画布时发生错误:`, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置合并后的图像属性
|
|
|
|
|
|
* @param {fabric.Image} mergedImg 合并后的图像
|
|
|
|
|
|
* @param {Object} bounds 边界信息
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_setupMergedImage(mergedImg, bounds) {
|
|
|
|
|
|
mergedImg.set({
|
|
|
|
|
|
id: this.newImageId,
|
|
|
|
|
|
layerId: this.activeLayer.id,
|
|
|
|
|
|
layerName: this.activeLayer.name,
|
|
|
|
|
|
left: bounds.left + bounds.width / 2,
|
|
|
|
|
|
top: bounds.top + bounds.height / 2,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.mergedImage = mergedImg;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 替换原有对象为合并后的图像
|
|
|
|
|
|
* @param {fabric.Image} mergedImg 合并后的图像
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_replaceObjects(mergedImg) {
|
|
|
|
|
|
if (!mergedImg || !this.canvas || !this.activeLayer) {
|
|
|
|
|
|
console.error("_replaceObjects: 缺少必要的参数");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const wasRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderOnAddRemove = false;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 清空图层的对象列表
|
|
|
|
|
|
this.activeLayer.fabricObjects = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 添加合并后的图像到画布和图层
|
|
|
|
|
|
this.canvas.add(mergedImg);
|
|
|
|
|
|
this.activeLayer.fabricObjects.push(mergedImg);
|
|
|
|
|
|
|
|
|
|
|
|
// 从画布中移除所有原始对象
|
|
|
|
|
|
this.originalObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj && this.canvas.getObjects().includes(obj)) {
|
|
|
|
|
|
this.canvas.remove(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有新的图像对象,也要移除
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.fabricImage &&
|
|
|
|
|
|
this.canvas.getObjects().includes(this.fabricImage)
|
|
|
|
|
|
) {
|
|
|
|
|
|
this.canvas.remove(this.fabricImage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`成功替换图层 ${this.activeLayer.name} 中的 ${this.originalObjects.length} 个对象为合并图像`
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("替换对象时发生错误:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.canvas.renderOnAddRemove = wasRenderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderAll(); // 同步渲染画布
|
|
|
|
|
|
|
|
|
|
|
|
// 更新缩略图
|
|
|
|
|
|
if (this.canvas.thumbnailManager) {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.canvas.thumbnailManager.generateLayerThumbnail(
|
|
|
|
|
|
this.activeLayer.id
|
|
|
|
|
|
);
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (!this.activeLayer || !this.canvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
const wasRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderOnAddRemove = false;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 移除合并后的图像
|
|
|
|
|
|
if (this.mergedImage) {
|
|
|
|
|
|
this.canvas.remove(this.mergedImage);
|
|
|
|
|
|
|
|
|
|
|
|
const imageIndex = this.activeLayer.fabricObjects.findIndex(
|
|
|
|
|
|
(obj) => obj.id === this.newImageId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (imageIndex !== -1) {
|
|
|
|
|
|
this.activeLayer.fabricObjects.splice(imageIndex, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按原始顺序恢复对象到画布
|
|
|
|
|
|
this.originalObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj) {
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复图层对象列表
|
|
|
|
|
|
this.activeLayer.fabricObjects = [...this.originalObjects];
|
|
|
|
|
|
console.log(`成功撤销图层 ${this.activeLayer.name} 的对象合并操作`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("撤销合并操作时发生错误:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.canvas.renderOnAddRemove = wasRenderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新缩略图
|
|
|
|
|
|
if (this.canvas.thumbnailManager) {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.canvas.thumbnailManager.generateLayerThumbnail(
|
|
|
|
|
|
this.activeLayer.id
|
|
|
|
|
|
);
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 安全地清理临时画布资源
|
|
|
|
|
|
* @param {fabric.Canvas} tempCanvas 临时画布
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_cleanupTempCanvas(tempCanvas) {
|
|
|
|
|
|
if (!tempCanvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
tempCanvas.clear();
|
|
|
|
|
|
|
|
|
|
|
|
const objects = tempCanvas.getObjects();
|
|
|
|
|
|
objects.forEach((obj) => {
|
|
|
|
|
|
tempCanvas.remove(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const canvasEl = tempCanvas.getElement();
|
|
|
|
|
|
if (canvasEl && canvasEl.getContext) {
|
|
|
|
|
|
const ctx = canvasEl.getContext("2d");
|
|
|
|
|
|
if (ctx) {
|
|
|
|
|
|
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof tempCanvas.destroy === "function") {
|
|
|
|
|
|
tempCanvas.destroy();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (canvasEl && canvasEl.parentNode) {
|
|
|
|
|
|
canvasEl.parentNode.removeChild(canvasEl);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log("临时画布资源已安全清理");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn("清理临时画布时发生警告:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.activeLayer?.id,
|
|
|
|
|
|
layerName: this.activeLayer?.name || "未知图层",
|
|
|
|
|
|
originalObjectCount: this.originalObjects.length,
|
|
|
|
|
|
hasNewImage: !!this.fabricImage,
|
|
|
|
|
|
mergedImageId: this.newImageId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并图层内对象成组的命令
|
|
|
|
|
|
* 将新的图像与图层内现有对象合并为一个组对象
|
|
|
|
|
|
* 支持向现有组添加对象,以及移除空组
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class LayerObjectsToGroupCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "图层内对象合并为组",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.fabricImage = options.fabricImage;
|
|
|
|
|
|
this.activeLayer = options.activeLayer;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
|
|
|
|
|
|
// 存储对象ID而不是对象引用,避免引用丢失问题
|
|
|
|
|
|
this.originalObjectIds = [];
|
|
|
|
|
|
this.addedObjectIds = [];
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 备份原始对象,用于撤销
|
|
|
|
|
|
if (this.activeLayer && Array.isArray(this.activeLayer.fabricObjects)) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.originalObjectIds = this.activeLayer.fabricObjects
|
|
|
|
|
|
.filter((obj) => obj && obj.id)
|
|
|
|
|
|
.map((obj) => obj.id);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 组对象相关
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.existingGroupId = null;
|
|
|
|
|
|
this.groupObjectId = null;
|
|
|
|
|
|
this.newGroupId =
|
|
|
|
|
|
generateId("group") ||
|
|
|
|
|
|
`group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
|
|
|
|
this.wasGroupCreated = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 保存原始位置信息
|
|
|
|
|
|
this.originalObjectPositions = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
this.originalObjectIds.forEach((id) => {
|
|
|
|
|
|
const result = findObjectById(this.canvas, id);
|
|
|
|
|
|
if (result.object) {
|
|
|
|
|
|
this.originalObjectPositions.set(id, {
|
|
|
|
|
|
left: result.object.left,
|
|
|
|
|
|
top: result.object.top,
|
|
|
|
|
|
scaleX: result.object.scaleX,
|
|
|
|
|
|
scaleY: result.object.scaleY,
|
|
|
|
|
|
angle: result.object.angle,
|
|
|
|
|
|
originX: result.object.originX,
|
|
|
|
|
|
originY: result.object.originY,
|
|
|
|
|
|
flipX: result.object.flipX,
|
|
|
|
|
|
flipY: result.object.flipY,
|
|
|
|
|
|
layerId: result.object.layerId,
|
|
|
|
|
|
layerName: result.object.layerName,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:保存新添加对象的位置信息
|
|
|
|
|
|
this.newObjectPositions = new Map();
|
|
|
|
|
|
if (this.fabricImage && this.fabricImage.id) {
|
|
|
|
|
|
this.newObjectPositions.set(this.fabricImage.id, {
|
|
|
|
|
|
left: this.fabricImage.left,
|
|
|
|
|
|
top: this.fabricImage.top,
|
|
|
|
|
|
scaleX: this.fabricImage.scaleX,
|
|
|
|
|
|
scaleY: this.fabricImage.scaleY,
|
|
|
|
|
|
angle: this.fabricImage.angle,
|
|
|
|
|
|
originX: this.fabricImage.originX,
|
|
|
|
|
|
originY: this.fabricImage.originY,
|
|
|
|
|
|
flipX: this.fabricImage.flipX,
|
|
|
|
|
|
flipY: this.fabricImage.flipY,
|
|
|
|
|
|
layerId: this.fabricImage.layerId,
|
|
|
|
|
|
layerName: this.fabricImage.layerName,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录执行状态,用于区分首次执行和重做
|
|
|
|
|
|
this.isFirstExecution = true;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.activeLayer || !this.canvas) {
|
|
|
|
|
|
console.error("图层或Canvas未初始化");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找图层中是否已有组对象
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const existingGroup = this._findExistingGroup();
|
|
|
|
|
|
this.existingGroupId = existingGroup?.id || null;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 准备要添加的新对象
|
|
|
|
|
|
const newObjectsToAdd = [];
|
|
|
|
|
|
if (this.fabricImage) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果是重做,恢复新对象的原始位置
|
|
|
|
|
|
if (
|
|
|
|
|
|
!this.isFirstExecution &&
|
|
|
|
|
|
this.newObjectPositions.has(this.fabricImage.id)
|
|
|
|
|
|
) {
|
|
|
|
|
|
const savedPosition = this.newObjectPositions.get(this.fabricImage.id);
|
|
|
|
|
|
this.fabricImage.set({
|
|
|
|
|
|
left: savedPosition.left,
|
|
|
|
|
|
top: savedPosition.top,
|
|
|
|
|
|
scaleX: savedPosition.scaleX,
|
|
|
|
|
|
scaleY: savedPosition.scaleY,
|
|
|
|
|
|
angle: savedPosition.angle,
|
|
|
|
|
|
originX: savedPosition.originX,
|
|
|
|
|
|
originY: savedPosition.originY,
|
|
|
|
|
|
flipX: savedPosition.flipX,
|
|
|
|
|
|
flipY: savedPosition.flipY,
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`🔄 重做时恢复新对象位置: (${savedPosition.left}, ${savedPosition.top})`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
newObjectsToAdd.push(this.fabricImage);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.addedObjectIds.push(this.fabricImage.id);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (newObjectsToAdd.length === 0) {
|
|
|
|
|
|
console.log("没有新对象需要添加到组");
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return this.existingGroupId;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await optimizeCanvasRendering(this.canvas, () => {
|
|
|
|
|
|
if (existingGroup) {
|
|
|
|
|
|
// 向现有组添加对象
|
|
|
|
|
|
this._addObjectsToExistingGroup(existingGroup, newObjectsToAdd);
|
|
|
|
|
|
this.groupObjectId = existingGroup.id;
|
|
|
|
|
|
this.wasGroupCreated = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 创建新组包含所有对象
|
|
|
|
|
|
this._createNewGroupWithAllObjects(newObjectsToAdd);
|
|
|
|
|
|
this.groupObjectId = this.newGroupId;
|
|
|
|
|
|
this.wasGroupCreated = true;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新交互性
|
|
|
|
|
|
this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新缩略图
|
|
|
|
|
|
this._updateThumbnail();
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 标记为非首次执行
|
|
|
|
|
|
this.isFirstExecution = false;
|
|
|
|
|
|
return this.groupObjectId;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("执行组合操作时发生错误:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查找图层中现有的组对象
|
|
|
|
|
|
* @returns {fabric.Group|null}
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_findExistingGroup() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 查找当前的组对象
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.activeLayer.fabricObjects) return null;
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!this.existingGroupId) {
|
|
|
|
|
|
const groupObjects =
|
|
|
|
|
|
this.activeLayer.fabricObjects.find(
|
|
|
|
|
|
(obj) => obj && obj.type === "group"
|
|
|
|
|
|
) || null;
|
|
|
|
|
|
|
|
|
|
|
|
this.existingGroupId = groupObjects?.id || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.existingGroupId) {
|
|
|
|
|
|
console.warn("未找到有效的组对象");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, this.existingGroupId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!object || object.type !== "group") {
|
|
|
|
|
|
console.warn("未找到有效的组对象");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return object;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将新对象添加到现有组中
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* @param {fabric.Group} existingGroup 现有组
|
2025-06-09 10:25:54 +08:00
|
|
|
|
* @param {Array} newObjects 要添加的新对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
2025-06-18 11:05:23 +08:00
|
|
|
|
_addObjectsToExistingGroup(existingGroup, newObjects) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 先从画布移除新对象(避免重复添加)
|
|
|
|
|
|
newObjects.forEach((obj) => {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
removeCanvasObjectByObject(this.canvas, obj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 addWithUpdate 方法正确添加对象到组
|
|
|
|
|
|
newObjects.forEach((obj) => {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
existingGroup.addWithUpdate(obj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建包含所有对象的新组
|
|
|
|
|
|
* @param {Array} newObjects 新添加的对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_createNewGroupWithAllObjects(newObjects) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 获取原始对象(通过ID重新查找)
|
|
|
|
|
|
const originalObjects = this._getOriginalObjectsFromCanvas();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是重做,恢复原始对象的位置
|
|
|
|
|
|
if (!this.isFirstExecution) {
|
|
|
|
|
|
originalObjects.forEach((obj) => {
|
|
|
|
|
|
if (this.originalObjectPositions.has(obj.id)) {
|
|
|
|
|
|
const savedPosition = this.originalObjectPositions.get(obj.id);
|
|
|
|
|
|
obj.set({
|
|
|
|
|
|
left: savedPosition.left,
|
|
|
|
|
|
top: savedPosition.top,
|
|
|
|
|
|
scaleX: savedPosition.scaleX,
|
|
|
|
|
|
scaleY: savedPosition.scaleY,
|
|
|
|
|
|
angle: savedPosition.angle,
|
|
|
|
|
|
originX: savedPosition.originX,
|
|
|
|
|
|
originY: savedPosition.originY,
|
|
|
|
|
|
flipX: savedPosition.flipX,
|
|
|
|
|
|
flipY: savedPosition.flipY,
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`🔄 重做时恢复原始对象位置: ${obj.id} (${savedPosition.left}, ${savedPosition.top})`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const allObjects = [...originalObjects, ...newObjects];
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 从画布中移除所有要组合的对象
|
|
|
|
|
|
allObjects.forEach((obj) => {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
obj.opacity = 1;
|
|
|
|
|
|
removeCanvasObjectByObject(this.canvas, obj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 创建组对象
|
|
|
|
|
|
const groupObject = new fabric.Group(allObjects, {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
id: this.newGroupId,
|
|
|
|
|
|
layerId: this.activeLayer.id,
|
|
|
|
|
|
layerName: this.activeLayer.name,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
type: "group",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加组对象到画布
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.add(groupObject);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层的对象列表
|
|
|
|
|
|
// this.activeLayer.fabricObjects = [groupObject];
|
|
|
|
|
|
this.activeLayer.fabricObjects = [
|
|
|
|
|
|
groupObject.toObject(["id", "layerId", "layerName"]),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从画布中重新获取原始对象
|
|
|
|
|
|
* @returns {Array} 原始对象数组
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getOriginalObjectsFromCanvas() {
|
|
|
|
|
|
const objects = [];
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
for (const objectId of this.originalObjectIds) {
|
|
|
|
|
|
const result = findObjectById(this.canvas, objectId);
|
|
|
|
|
|
if (result.object) {
|
|
|
|
|
|
objects.push(result.object);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return objects;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
|
|
|
|
|
if (!this.activeLayer || !this.canvas || !this.groupObjectId) return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
if (this.wasGroupCreated) {
|
|
|
|
|
|
// 如果是新创建的组,完全撤销到原始状态
|
|
|
|
|
|
this._undoNewGroupCreation();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果是向现有组添加对象,只移除新添加的对象
|
|
|
|
|
|
this._undoAddToExistingGroup();
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新缩略图
|
|
|
|
|
|
this._updateThumbnail();
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重置为首次执行状态,以便重做时能正确恢复位置
|
|
|
|
|
|
this.isFirstExecution = false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.log(`↩️ 成功撤销图层 ${this.activeLayer.name} 的对象组合操作`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("撤销组合操作时发生错误:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 撤销新组的创建(修正版 - 删除新添加的元素)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_undoNewGroupCreation() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 查找当前的组对象
|
|
|
|
|
|
const groupResult = findObjectById(this.canvas, this.groupObjectId);
|
|
|
|
|
|
if (!groupResult.object) {
|
|
|
|
|
|
console.error("找不到要撤销的组对象");
|
|
|
|
|
|
return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const groupObject = groupResult.object;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取组内的所有对象
|
|
|
|
|
|
const groupObjects = groupObject.getObjects();
|
|
|
|
|
|
|
|
|
|
|
|
if (groupObjects.length === 0) {
|
|
|
|
|
|
console.warn("组对象为空,直接移除");
|
|
|
|
|
|
this.canvas.remove(groupObject);
|
|
|
|
|
|
return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 分离原始对象和新添加的对象
|
|
|
|
|
|
const objectsToRestore = [];
|
|
|
|
|
|
const objectsToDelete = [];
|
|
|
|
|
|
|
|
|
|
|
|
groupObjects.forEach((obj) => {
|
|
|
|
|
|
if (this.originalObjectIds.includes(obj.id)) {
|
|
|
|
|
|
// 这是原始对象,需要恢复
|
|
|
|
|
|
const originalPosition = this.originalObjectPositions.get(obj.id);
|
|
|
|
|
|
|
|
|
|
|
|
let restorePosition;
|
|
|
|
|
|
if (originalPosition) {
|
|
|
|
|
|
// 优先使用原始保存的位置
|
|
|
|
|
|
restorePosition = { ...originalPosition };
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 计算对象在画布中的当前绝对位置
|
|
|
|
|
|
restorePosition = this._calculateObjectAbsolutePosition(
|
|
|
|
|
|
obj,
|
|
|
|
|
|
groupObject
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
objectsToRestore.push({
|
|
|
|
|
|
object: obj,
|
|
|
|
|
|
position: restorePosition,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (this.addedObjectIds.includes(obj.id)) {
|
|
|
|
|
|
// 这是新添加的对象,需要删除
|
|
|
|
|
|
objectsToDelete.push(obj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 从画布中移除组对象
|
|
|
|
|
|
this.canvas.remove(groupObject);
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用渲染以提高性能
|
|
|
|
|
|
const wasRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderOnAddRemove = false;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 逐个恢复原始对象到画布
|
|
|
|
|
|
const restoredObjects = [];
|
|
|
|
|
|
|
|
|
|
|
|
objectsToRestore.forEach(({ object: obj, position }) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 从组中移除对象(这会重置对象的变换状态)
|
|
|
|
|
|
groupObject.removeWithUpdate(obj);
|
|
|
|
|
|
|
|
|
|
|
|
// 重新设置对象的位置和属性
|
|
|
|
|
|
obj.set({
|
|
|
|
|
|
left: position.left,
|
|
|
|
|
|
top: position.top,
|
|
|
|
|
|
scaleX: position.scaleX || 1,
|
|
|
|
|
|
scaleY: position.scaleY || 1,
|
|
|
|
|
|
angle: position.angle || 0,
|
|
|
|
|
|
flipX: position.flipX || false,
|
|
|
|
|
|
flipY: position.flipY || false,
|
|
|
|
|
|
originX: position.originX || "center",
|
|
|
|
|
|
originY: position.originY || "center",
|
|
|
|
|
|
layerId: this.activeLayer.id,
|
|
|
|
|
|
layerName: this.activeLayer.name,
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
opacity: position.opacity || 1,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象坐标
|
|
|
|
|
|
obj.setCoords();
|
|
|
|
|
|
|
|
|
|
|
|
// 将对象添加回画布
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
restoredObjects.push(obj);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`✅ 恢复原始对象 ${obj.id || obj.type} 到位置 (${position.left}, ${
|
|
|
|
|
|
position.top
|
|
|
|
|
|
})`
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`恢复对象 ${obj.id || obj.type} 时发生错误:`, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 删除新添加的对象(不添加回画布)
|
|
|
|
|
|
objectsToDelete.forEach((obj) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 从组中移除对象
|
|
|
|
|
|
groupObject.removeWithUpdate(obj);
|
|
|
|
|
|
|
|
|
|
|
|
// 销毁对象以释放内存
|
|
|
|
|
|
if (typeof obj.destroy === "function") {
|
|
|
|
|
|
obj.destroy();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`🗑️ 删除新添加的对象 ${obj.id || obj.type}`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`删除对象 ${obj.id || obj.type} 时发生错误:`, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层对象列表(只包含恢复的原始对象)
|
|
|
|
|
|
this.activeLayer.fabricObjects = restoredObjects;
|
|
|
|
|
|
|
|
|
|
|
|
// 销毁组对象释放内存
|
|
|
|
|
|
if (typeof groupObject.destroy === "function") {
|
|
|
|
|
|
groupObject.destroy();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`✅ 成功撤销组创建,恢复了 ${restoredObjects.length} 个原始对象,删除了 ${objectsToDelete.length} 个新对象`
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("撤销组创建过程中发生错误:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复渲染设置
|
|
|
|
|
|
this.canvas.renderOnAddRemove = wasRenderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算对象在画布中的绝对位置(改进版)
|
|
|
|
|
|
* @param {fabric.Object} obj 组内对象
|
|
|
|
|
|
* @param {fabric.Group} group 组对象
|
|
|
|
|
|
* @returns {Object} 绝对位置信息
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_calculateObjectAbsolutePosition(obj, group) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取组的变换矩阵
|
|
|
|
|
|
const groupTransform = group.calcTransformMatrix();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取对象在组内的相对位置
|
|
|
|
|
|
const objectPoint = new fabric.Point(obj.left || 0, obj.top || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 应用组的变换矩阵计算绝对位置
|
|
|
|
|
|
const absolutePoint = fabric.util.transformPoint(
|
|
|
|
|
|
objectPoint,
|
|
|
|
|
|
groupTransform
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算缩放比例
|
|
|
|
|
|
const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1);
|
|
|
|
|
|
const totalScaleY = (group.scaleY || 1) * (obj.scaleY || 1);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总旋转角度
|
|
|
|
|
|
const totalAngle = (group.angle || 0) + (obj.angle || 0);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
left: absolutePoint.x,
|
|
|
|
|
|
top: absolutePoint.y,
|
|
|
|
|
|
scaleX: totalScaleX,
|
|
|
|
|
|
scaleY: totalScaleY,
|
|
|
|
|
|
angle: totalAngle,
|
|
|
|
|
|
flipX: obj.flipX || false,
|
|
|
|
|
|
flipY: obj.flipY || false,
|
|
|
|
|
|
originX: obj.originX || "center",
|
|
|
|
|
|
originY: obj.originY || "center",
|
|
|
|
|
|
opacity: obj.opacity || 1,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn("使用变换矩阵计算位置失败,使用简化计算:", error);
|
|
|
|
|
|
|
|
|
|
|
|
// 备选方案:基于组的中心点和对象的相对偏移计算
|
|
|
|
|
|
const groupCenterX = group.left || 0;
|
|
|
|
|
|
const groupCenterY = group.top || 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 考虑组的缩放和旋转影响
|
|
|
|
|
|
const scaleX = group.scaleX || 1;
|
|
|
|
|
|
const scaleY = group.scaleY || 1;
|
|
|
|
|
|
const angle = group.angle || 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算对象相对于组中心的偏移
|
|
|
|
|
|
const offsetX = (obj.left || 0) * scaleX;
|
|
|
|
|
|
const offsetY = (obj.top || 0) * scaleY;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果组有旋转,需要旋转偏移量
|
|
|
|
|
|
let finalX = groupCenterX + offsetX;
|
|
|
|
|
|
let finalY = groupCenterY + offsetY;
|
|
|
|
|
|
|
|
|
|
|
|
if (angle !== 0) {
|
|
|
|
|
|
const cos = Math.cos(fabric.util.degreesToRadians(angle));
|
|
|
|
|
|
const sin = Math.sin(fabric.util.degreesToRadians(angle));
|
|
|
|
|
|
|
|
|
|
|
|
finalX = groupCenterX + offsetX * cos - offsetY * sin;
|
|
|
|
|
|
finalY = groupCenterY + offsetX * sin + offsetY * cos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
left: finalX,
|
|
|
|
|
|
top: finalY,
|
|
|
|
|
|
scaleX: scaleX * (obj.scaleX || 1),
|
|
|
|
|
|
scaleY: scaleY * (obj.scaleY || 1),
|
|
|
|
|
|
angle: angle + (obj.angle || 0),
|
|
|
|
|
|
flipX: obj.flipX || false,
|
|
|
|
|
|
flipY: obj.flipY || false,
|
|
|
|
|
|
originX: obj.originX || "center",
|
|
|
|
|
|
originY: obj.originY || "center",
|
|
|
|
|
|
opacity: obj.opacity || 1,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 撤销向现有组添加对象的操作(优化版)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_undoAddToExistingGroup() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 查找现有的组对象
|
|
|
|
|
|
const groupResult = findObjectById(this.canvas, this.existingGroupId);
|
|
|
|
|
|
if (!groupResult.object) {
|
|
|
|
|
|
console.error("找不到现有的组对象");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const existingGroup = groupResult.object;
|
|
|
|
|
|
|
|
|
|
|
|
// 移除新添加的对象
|
|
|
|
|
|
for (const objectId of this.addedObjectIds) {
|
|
|
|
|
|
const objResult = objectIsInCanvas(this.canvas, { id: objectId });
|
|
|
|
|
|
|
|
|
|
|
|
if (objResult.flag && objResult.parent === existingGroup) {
|
|
|
|
|
|
// 对象在组中,使用 removeWithUpdate 移除
|
|
|
|
|
|
existingGroup.removeWithUpdate(objResult.object);
|
|
|
|
|
|
// 从画布中完全移除这个对象
|
|
|
|
|
|
if (this.canvas.getObjects().includes(objResult.object)) {
|
|
|
|
|
|
this.canvas.remove(objResult.object);
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查组的状态
|
|
|
|
|
|
const remainingObjects = existingGroup.getObjects();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (remainingObjects.length === 0) {
|
|
|
|
|
|
// 组为空,移除组
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.remove(existingGroup);
|
|
|
|
|
|
const groupIndex = this.activeLayer.fabricObjects.indexOf(existingGroup);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (groupIndex !== -1) {
|
|
|
|
|
|
this.activeLayer.fabricObjects.splice(groupIndex, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (remainingObjects.length === 1) {
|
|
|
|
|
|
// 只剩一个对象,解散组
|
|
|
|
|
|
const singleObj = remainingObjects[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 从画布移除组
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.remove(existingGroup);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 使用 removeWithUpdate 移除最后一个对象(会自动添加到画布)
|
|
|
|
|
|
existingGroup.removeWithUpdate(singleObj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 重新设置对象属性
|
2025-06-18 11:05:23 +08:00
|
|
|
|
singleObj.set({
|
|
|
|
|
|
layerId: this.activeLayer.id,
|
|
|
|
|
|
layerName: this.activeLayer.name,
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新图层对象列表
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const groupIndex = this.activeLayer.fabricObjects.indexOf(existingGroup);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (groupIndex !== -1) {
|
|
|
|
|
|
this.activeLayer.fabricObjects[groupIndex] = singleObj;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新缩略图
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_updateThumbnail() {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// this.canvas?.thumbnailManager?.generateLayerThumbnail?.(
|
|
|
|
|
|
// this.activeLayer.id
|
|
|
|
|
|
// );
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.activeLayer?.id,
|
|
|
|
|
|
layerName: this.activeLayer?.name || "未知图层",
|
2025-06-18 11:05:23 +08:00
|
|
|
|
originalObjectCount: this.originalObjectIds.length,
|
|
|
|
|
|
addedObjectsCount: this.addedObjectIds.length,
|
|
|
|
|
|
groupId: this.groupObjectId,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
wasGroupCreated: this.wasGroupCreated,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建图片图层复合命令
|
|
|
|
|
|
* 包含创建图层、添加对象到图层、切换工具等操作的复合命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class CreateImageLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "创建图片图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.fabricImage = options.fabricImage;
|
|
|
|
|
|
this.toolManager = options.toolManager;
|
|
|
|
|
|
this.layerName = options.layerName || null;
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.imageId = generateId("image_");
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 存储执行过程中的结果
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.newLayerId =
|
|
|
|
|
|
generateId("layer_image_") ||
|
|
|
|
|
|
`layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.commands = [];
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.layerManager || !this.fabricImage) {
|
|
|
|
|
|
throw new Error("图层管理器或图片对象无效");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.commands = [];
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 生成图层名称
|
|
|
|
|
|
const fileName =
|
|
|
|
|
|
this.layerName || `图片 ${new Date().toLocaleTimeString()}`;
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.fabricImage.set({
|
|
|
|
|
|
id: this.imageId,
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 1. 创建新图层命令
|
|
|
|
|
|
const createLayerCmd = new AddLayerCommand({
|
|
|
|
|
|
canvas: this.layerManager.canvas,
|
|
|
|
|
|
layers: this.layerManager.layers,
|
|
|
|
|
|
newLayer: createLayer({
|
2025-06-22 13:52:28 +08:00
|
|
|
|
id: this.newLayerId,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
name: fileName,
|
|
|
|
|
|
type: LayerType.BITMAP,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
}),
|
|
|
|
|
|
activeLayerId: this.layerManager.activeLayerId,
|
|
|
|
|
|
insertIndex: this.layerManager._getInsertIndexAboveActiveLayer(),
|
|
|
|
|
|
});
|
|
|
|
|
|
// 执行创建图层命令
|
|
|
|
|
|
this.newLayerId = await createLayerCmd.execute();
|
|
|
|
|
|
this.commands.push(createLayerCmd);
|
|
|
|
|
|
this.executedCommands.push(createLayerCmd);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 添加图片对象到图层命令
|
|
|
|
|
|
const addObjectCmd = new AddObjectToLayerCommand({
|
|
|
|
|
|
canvas: this.layerManager.canvas,
|
|
|
|
|
|
layers: this.layerManager.layers,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerManager: this.layerManager,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
layerId: this.newLayerId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerName: this.layerName,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
fabricObject: this.fabricImage,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行添加对象命令
|
|
|
|
|
|
await addObjectCmd.execute();
|
|
|
|
|
|
this.commands.push(addObjectCmd);
|
|
|
|
|
|
this.executedCommands.push(addObjectCmd);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 切换工具到选择模式命令
|
|
|
|
|
|
if (this.toolManager) {
|
|
|
|
|
|
const toolCmd = new ToolCommand({
|
|
|
|
|
|
toolManager: this.toolManager,
|
|
|
|
|
|
tool: OperationType.SELECT,
|
|
|
|
|
|
previousTool: this.toolManager.getCurrentTool(),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行工具切换命令
|
|
|
|
|
|
await toolCmd.execute();
|
|
|
|
|
|
this.commands.push(toolCmd);
|
|
|
|
|
|
this.executedCommands.push(toolCmd);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 创建图片图层完成: ${fileName}, ID: ${this.newLayerId}`);
|
|
|
|
|
|
return this.newLayerId;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("创建图片图层失败:", error);
|
|
|
|
|
|
// 回滚已执行的命令
|
|
|
|
|
|
await this._rollbackExecutedCommands();
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (this.executedCommands.length === 0) {
|
|
|
|
|
|
console.warn("没有已执行的命令需要撤销");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`↩️ 开始撤销创建图片图层操作,共 ${this.executedCommands.length} 个子命令`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 逆序撤销已执行的命令
|
|
|
|
|
|
const commands = [...this.executedCommands].reverse();
|
|
|
|
|
|
|
|
|
|
|
|
for (const command of commands) {
|
|
|
|
|
|
if (typeof command.undo === "function") {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log(`↩️ 撤销子命令: ${command.constructor.name}`);
|
|
|
|
|
|
const result = command.undo();
|
|
|
|
|
|
if (this._isPromise(result)) {
|
|
|
|
|
|
await result;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
|
|
|
|
|
error
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
console.log(`✅ 创建图片图层撤销完成`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ 撤销创建图片图层过程中发生错误:", error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 回滚已执行的命令(内部使用)
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _rollbackExecutedCommands() {
|
|
|
|
|
|
console.log(`🔄 开始回滚已执行的 ${this.executedCommands.length} 个子命令`);
|
|
|
|
|
|
|
|
|
|
|
|
const commands = [...this.executedCommands].reverse();
|
|
|
|
|
|
|
|
|
|
|
|
for (const command of commands) {
|
|
|
|
|
|
if (typeof command.undo === "function") {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log(`🔄 回滚子命令: ${command.constructor.name}`);
|
|
|
|
|
|
const result = command.undo();
|
|
|
|
|
|
if (this._isPromise(result)) {
|
|
|
|
|
|
await result;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`✅ 子命令回滚成功: ${command.constructor.name}`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
`❌ 子命令回滚失败: ${command.constructor.name}`,
|
|
|
|
|
|
error
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
console.log(`✅ 回滚完成`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查返回值是否为Promise
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_isPromise(value) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
value &&
|
|
|
|
|
|
typeof value === "object" &&
|
|
|
|
|
|
typeof value.then === "function" &&
|
|
|
|
|
|
typeof value.catch === "function"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.layerName,
|
|
|
|
|
|
layerId: this.newLayerId,
|
|
|
|
|
|
commandCount: this.commands.length,
|
|
|
|
|
|
executedCount: this.executedCommands.length,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 拖拽排序图层命令
|
2025-06-09 10:25:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export class ReorderLayersCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
2025-06-18 11:05:23 +08:00
|
|
|
|
name: "拖拽排序图层",
|
|
|
|
|
|
saveState: true,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.oldIndex = options.oldIndex;
|
|
|
|
|
|
this.newIndex = options.newIndex;
|
|
|
|
|
|
this.layerId = options.layerId;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerSort = options.layerSort || null; // LayerSort实例
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
|
|
|
|
|
// 验证索引有效性
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.oldIndex < 0 ||
|
|
|
|
|
|
this.newIndex < 0 ||
|
|
|
|
|
|
this.oldIndex >= this.layers.value.length ||
|
|
|
|
|
|
this.newIndex >= this.layers.value.length ||
|
|
|
|
|
|
this.oldIndex === this.newIndex
|
2025-06-09 10:25:54 +08:00
|
|
|
|
) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 验证图层ID
|
|
|
|
|
|
const layer = this.layers.value[this.oldIndex];
|
|
|
|
|
|
if (!layer || layer.id !== this.layerId) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 不允许移动背景层和固定层
|
|
|
|
|
|
if (layer.isBackground || layer.isFixed) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 执行排序 - 直接操作数组 不重排页面数据
|
|
|
|
|
|
// const layersArray = [...this.layers.value];
|
|
|
|
|
|
// const [movedLayer] = layersArray.splice(this.oldIndex, 1);
|
|
|
|
|
|
// layersArray.splice(this.newIndex, 0, movedLayer);
|
|
|
|
|
|
// this.layers.value = layersArray;
|
|
|
|
|
|
// this.layers.value
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新父图层的children数组
|
|
|
|
|
|
this.layers.value[this.oldIndex] = this.layers.value[this.newIndex]; // 确保旧索引位置的图层ID正确
|
|
|
|
|
|
this.layers.value[this.newIndex] = layer; // 确保旧索引位置的图层ID正确
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 使用LayerSort工具重新排列画布对象(如果可用)
|
|
|
|
|
|
await this.layerSort?.rearrangeObjects();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
|
|
|
|
|
// 交换索引进行反向操作
|
|
|
|
|
|
const tempOldIndex = this.oldIndex;
|
|
|
|
|
|
this.oldIndex = this.newIndex;
|
|
|
|
|
|
this.newIndex = tempOldIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const result = await this.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 恢复原始索引
|
|
|
|
|
|
this.newIndex = this.oldIndex;
|
|
|
|
|
|
this.oldIndex = tempOldIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return result;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldIndex: this.oldIndex,
|
|
|
|
|
|
newIndex: this.newIndex,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 拖拽排序子图层命令
|
2025-06-09 10:25:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export class ReorderChildLayersCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
2025-06-18 11:05:23 +08:00
|
|
|
|
name: "拖拽排序子图层",
|
|
|
|
|
|
saveState: true,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.parentId = options.parentId;
|
|
|
|
|
|
this.oldIndex = options.oldIndex;
|
|
|
|
|
|
this.newIndex = options.newIndex;
|
|
|
|
|
|
this.layerId = options.layerId;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerSort = options.layerSort || null; // LayerSort实例
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 查找父图层
|
|
|
|
|
|
const parentLayer = this.layers.value.find(
|
|
|
|
|
|
(layer) => layer.id === this.parentId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!parentLayer) return false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 获取子图层
|
|
|
|
|
|
const childLayers = parentLayer?.children || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 验证索引
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.oldIndex < 0 ||
|
|
|
|
|
|
this.newIndex < 0 ||
|
|
|
|
|
|
this.oldIndex >= childLayers.length ||
|
|
|
|
|
|
this.newIndex >= childLayers.length ||
|
|
|
|
|
|
this.oldIndex === this.newIndex
|
|
|
|
|
|
) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 验证子图层ID
|
|
|
|
|
|
const layer = childLayers[this.oldIndex];
|
|
|
|
|
|
if (!layer || layer.id !== this.layerId) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新父图层的children数组
|
|
|
|
|
|
parentLayer.children[this.oldIndex] = parentLayer.children[this.newIndex]; // 确保旧索引位置的图层ID正确
|
|
|
|
|
|
parentLayer.children[this.newIndex] = layer; // 确保旧索引位置的图层ID正确
|
|
|
|
|
|
|
|
|
|
|
|
// 使用LayerSort工具重新排列画布对象(如果可用)
|
|
|
|
|
|
await this.layerSort?.rearrangeObjects();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
|
|
|
|
|
// 交换索引进行反向操作
|
|
|
|
|
|
const tempOldIndex = this.oldIndex;
|
|
|
|
|
|
this.oldIndex = this.newIndex;
|
|
|
|
|
|
this.newIndex = tempOldIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const result = await this.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 恢复原始索引
|
|
|
|
|
|
this.newIndex = this.oldIndex;
|
|
|
|
|
|
this.oldIndex = tempOldIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return result;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
parentId: this.parentId,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldIndex: this.oldIndex,
|
|
|
|
|
|
newIndex: this.newIndex,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 复制图层命令
|
2025-06-09 10:25:54 +08:00
|
|
|
|
*/
|
2025-06-18 11:05:23 +08:00
|
|
|
|
export class CopyLayerCommand extends Command {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
2025-06-18 11:05:23 +08:00
|
|
|
|
name: "复制图层",
|
|
|
|
|
|
saveState: false,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.clipboardData = null;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
execute() {
|
|
|
|
|
|
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 不允许复制背景图层
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
console.warn("不能复制背景图层");
|
|
|
|
|
|
return false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 序列化图层中的对象
|
|
|
|
|
|
const serializedObjects = [];
|
|
|
|
|
|
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj && typeof obj.toObject === "function") {
|
|
|
|
|
|
serializedObjects.push(obj.toObject(["id", "layerId", "layerName"]));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 创建剪贴板数据
|
|
|
|
|
|
this.clipboardData = {
|
|
|
|
|
|
...layer,
|
|
|
|
|
|
serializedObjects,
|
|
|
|
|
|
isCut: false,
|
|
|
|
|
|
originalLayerId: layer.id,
|
|
|
|
|
|
};
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 保存到图层管理器的剪贴板
|
|
|
|
|
|
if (this.layerManager) {
|
|
|
|
|
|
this.layerManager.clipboardData = this.clipboardData;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(`已复制图层:${layer.name}`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
undo() {
|
|
|
|
|
|
// 复制操作无需撤销,只需清空剪贴板
|
|
|
|
|
|
if (this.layerManager) {
|
|
|
|
|
|
this.layerManager.clipboardData = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
};
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 剪切图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class CutLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "剪切图层",
|
|
|
|
|
|
saveState: true,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
|
|
|
|
|
|
this.layerIndex = -1;
|
|
|
|
|
|
this.cutLayer = null;
|
|
|
|
|
|
this.clipboardData = null;
|
|
|
|
|
|
this.wasActiveLayer = false;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 生成新图层ID和名称
|
|
|
|
|
|
this.newLayerId =
|
|
|
|
|
|
generateId("layer_") ||
|
|
|
|
|
|
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
|
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
const sourceLayer = layer;
|
|
|
|
|
|
const parentLayer = parent;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!sourceLayer) {
|
|
|
|
|
|
console.error(`源图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不允许复制背景图层
|
|
|
|
|
|
if (sourceLayer.isBackground) {
|
|
|
|
|
|
console.warn("不能复制背景图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
const newName = `${sourceLayer.name} copy`;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 创建新图层
|
|
|
|
|
|
this.newLayer = createLayer({
|
|
|
|
|
|
id: this.newLayerId,
|
|
|
|
|
|
name: newName,
|
|
|
|
|
|
type: sourceLayer.type,
|
|
|
|
|
|
visible: sourceLayer.visible,
|
|
|
|
|
|
locked: sourceLayer.locked,
|
|
|
|
|
|
opacity: sourceLayer.opacity,
|
|
|
|
|
|
blendMode: sourceLayer.blendMode,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
children: sourceLayer.children ? [...sourceLayer.children] : [],
|
|
|
|
|
|
layerProperties: sourceLayer.layerProperties
|
|
|
|
|
|
? { ...sourceLayer.layerProperties }
|
|
|
|
|
|
: {},
|
|
|
|
|
|
metadata: sourceLayer.metadata ? { ...sourceLayer.metadata } : {},
|
|
|
|
|
|
parentId: parentLayer ? parentLayer.id : undefined, // 保持父图层关系
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 计算插入位置 - 区分主图层和子图层
|
|
|
|
|
|
let insertIndex;
|
|
|
|
|
|
|
|
|
|
|
|
if (parentLayer) {
|
|
|
|
|
|
// 处理子图层:在父图层的children数组中插入
|
|
|
|
|
|
const sourceChildIndex = parentLayer.children.findIndex(
|
|
|
|
|
|
(child) => child.id === this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
insertIndex =
|
|
|
|
|
|
this.insertIndex !== null ? this.insertIndex : sourceChildIndex + 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 插入到父图层的children数组中
|
|
|
|
|
|
parentLayer.children.splice(insertIndex, 0, this.newLayer);
|
|
|
|
|
|
this.isChildLayer = true;
|
|
|
|
|
|
this.parentLayer = parentLayer;
|
|
|
|
|
|
this.childInsertIndex = insertIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 处理主图层:在主图层数组中插入
|
|
|
|
|
|
const sourceIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(l) => l.id === this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
insertIndex =
|
|
|
|
|
|
this.insertIndex !== null ? this.insertIndex : sourceIndex + 1;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 插入到主图层数组中
|
|
|
|
|
|
this.layers.value.splice(insertIndex, 0, this.newLayer);
|
|
|
|
|
|
this.isChildLayer = false;
|
|
|
|
|
|
this.mainInsertIndex = insertIndex;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 复制源图层中的对象
|
|
|
|
|
|
if (sourceLayer.fabricObjects && sourceLayer.fabricObjects.length > 0) {
|
|
|
|
|
|
await this._duplicateObjects(sourceLayer.fabricObjects);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 设置为活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.newLayerId;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(
|
|
|
|
|
|
`已复制图层:${newName} ${this.isChildLayer ? "(子图层)" : "(主图层)"}`
|
|
|
|
|
|
);
|
|
|
|
|
|
return this.newLayerId;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!this.newLayer) return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (this.isChildLayer && this.parentLayer) {
|
|
|
|
|
|
// 撤销子图层:从父图层的children数组中删除
|
|
|
|
|
|
const childIndex = this.parentLayer.children.findIndex(
|
|
|
|
|
|
(child) => child.id === this.newLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (childIndex !== -1) {
|
|
|
|
|
|
this.parentLayer.children.splice(childIndex, 1);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 撤销主图层:从主图层数组中删除
|
|
|
|
|
|
const index = this.layers.value.findIndex(
|
|
|
|
|
|
(l) => l.id === this.newLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
this.layers.value.splice(index, 1);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 从画布中移除所有创建的对象
|
|
|
|
|
|
this.createdObjects.forEach((obj) => {
|
|
|
|
|
|
this.canvas.remove(obj);
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 恢复原活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.layerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async _duplicateObjects(sourceObjects) {
|
|
|
|
|
|
const serializedObjects = sourceObjects.map((obj) => {
|
|
|
|
|
|
// 序列化对象时保留必要的属性
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, obj.id);
|
|
|
|
|
|
if (object) return object.toObject(["id", "layerId", "layerName"]);
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
fabric.util.enlivenObjects(serializedObjects, (objects) => {
|
|
|
|
|
|
objects.forEach((obj) => {
|
|
|
|
|
|
// 生成新的对象ID
|
|
|
|
|
|
const newObjId = `obj_${Date.now()}_${Math.floor(
|
|
|
|
|
|
Math.random() * 1000
|
|
|
|
|
|
)}`;
|
|
|
|
|
|
obj.id = newObjId;
|
|
|
|
|
|
obj.layerId = this.newLayerId;
|
|
|
|
|
|
obj.layerName = this.newLayer.name;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加偏移量
|
|
|
|
|
|
const offset = 10;
|
|
|
|
|
|
if (obj.left !== undefined) obj.left += offset;
|
|
|
|
|
|
if (obj.top !== undefined) obj.top += offset;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到画布
|
|
|
|
|
|
this.canvas.add(obj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 添加到新图层
|
|
|
|
|
|
this.newLayer.fabricObjects.push(obj);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 记录创建的对象
|
|
|
|
|
|
this.createdObjects.push(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
sourceLayerId: this.layerId,
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
newLayerName: this.newLayer?.name,
|
|
|
|
|
|
objectCount: this.createdObjects.length,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
isChildLayer: this.isChildLayer,
|
|
|
|
|
|
parentLayerId: this.parentLayer?.id,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建调整图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class CreateAdjustmentLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "创建调整图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.adjustmentType = options.adjustmentType || "brightness";
|
|
|
|
|
|
this.adjustmentValue = options.adjustmentValue || 0;
|
|
|
|
|
|
this.insertIndex = options.insertIndex || null;
|
|
|
|
|
|
|
|
|
|
|
|
this.newLayer = null;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 生成新图层ID
|
|
|
|
|
|
this.newLayerId =
|
|
|
|
|
|
generateId("adj_layer_") ||
|
|
|
|
|
|
`adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
// 创建调整图层
|
|
|
|
|
|
this.newLayer = createLayer({
|
|
|
|
|
|
id: this.newLayerId,
|
|
|
|
|
|
name: `${this.adjustmentType} 调整`,
|
|
|
|
|
|
type: LayerType.ADJUSTMENT,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
fabricObjects: [],
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerProperties: {
|
|
|
|
|
|
adjustmentType: this.adjustmentType,
|
|
|
|
|
|
value: this.adjustmentValue,
|
|
|
|
|
|
affectedLayerIds: [], // 可以指定受影响的图层
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 计算插入位置
|
|
|
|
|
|
const insertIndex =
|
|
|
|
|
|
this.insertIndex !== null
|
|
|
|
|
|
? this.insertIndex
|
|
|
|
|
|
: this._getInsertIndexAboveActiveLayer();
|
|
|
|
|
|
|
|
|
|
|
|
// 插入新图层
|
|
|
|
|
|
this.layers.value.splice(insertIndex, 0, this.newLayer);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置为活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.newLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`已创建调整图层:${this.newLayer.name}`);
|
|
|
|
|
|
return this.newLayerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_getInsertIndexAboveActiveLayer() {
|
|
|
|
|
|
const activeIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.activeLayerId.value
|
|
|
|
|
|
);
|
|
|
|
|
|
return activeIndex !== -1 ? activeIndex : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (!this.newLayer) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层列表中删除调整图层
|
|
|
|
|
|
const index = this.layers.value.findIndex((l) => l.id === this.newLayerId);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
this.layers.value.splice(index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原活动图层
|
|
|
|
|
|
// 这里需要记录原来的活动图层ID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
adjustmentType: this.adjustmentType,
|
|
|
|
|
|
adjustmentValue: this.adjustmentValue,
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
newLayerName: this.newLayer?.name,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 应用图层样式命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ApplyLayerStyleCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "应用图层样式",
|
|
|
|
|
|
saveState: false,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.styleType = options.styleType; // 'shadow', 'glow', 'stroke' etc.
|
|
|
|
|
|
this.styleOptions = options.styleOptions || {};
|
|
|
|
|
|
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
this.oldStyles = this.layer
|
|
|
|
|
|
? { ...(this.layer.layerProperties?.styles || {}) }
|
|
|
|
|
|
: {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化样式属性
|
|
|
|
|
|
if (!this.layer.layerProperties) {
|
|
|
|
|
|
this.layer.layerProperties = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!this.layer.layerProperties.styles) {
|
|
|
|
|
|
this.layer.layerProperties.styles = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用新样式
|
|
|
|
|
|
this.layer.layerProperties.styles[this.styleType] = this.styleOptions;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上的对象样式
|
|
|
|
|
|
this._applyStyleToObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`已应用${this.styleType}样式到图层:${this.layer.name}`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_applyStyleToObjects() {
|
|
|
|
|
|
if (!this.layer.fabricObjects) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (!obj) return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
switch (this.styleType) {
|
|
|
|
|
|
case "shadow":
|
|
|
|
|
|
if (this.styleOptions.enabled) {
|
|
|
|
|
|
obj.shadow = new fabric.Shadow({
|
|
|
|
|
|
color: this.styleOptions.color || "rgba(0,0,0,0.5)",
|
|
|
|
|
|
blur: this.styleOptions.blur || 5,
|
|
|
|
|
|
offsetX: this.styleOptions.offsetX || 5,
|
|
|
|
|
|
offsetY: this.styleOptions.offsetY || 5,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
obj.shadow = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "stroke":
|
|
|
|
|
|
if (this.styleOptions.enabled) {
|
|
|
|
|
|
obj.stroke = this.styleOptions.color || "#000000";
|
|
|
|
|
|
obj.strokeWidth = this.styleOptions.width || 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
obj.stroke = null;
|
|
|
|
|
|
obj.strokeWidth = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
// 可以添加更多样式类型
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (!this.layer) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原样式
|
|
|
|
|
|
if (this.layer.layerProperties && this.layer.layerProperties.styles) {
|
|
|
|
|
|
this.layer.layerProperties.styles = { ...this.oldStyles };
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重新应用所有样式
|
|
|
|
|
|
this._reapplyAllStyles();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
this.canvas.renderAll();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
_reapplyAllStyles() {
|
|
|
|
|
|
if (!this.layer.fabricObjects) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (!obj) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 重置样式
|
|
|
|
|
|
obj.shadow = null;
|
|
|
|
|
|
obj.stroke = null;
|
|
|
|
|
|
obj.strokeWidth = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 重新应用所有保存的样式
|
|
|
|
|
|
const styles = this.layer.layerProperties?.styles || {};
|
|
|
|
|
|
Object.keys(styles).forEach((styleType) => {
|
|
|
|
|
|
const styleOptions = styles[styleType];
|
|
|
|
|
|
// 这里可以复用 _applyStyleToObjects 中的逻辑
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
styleType: this.styleType,
|
|
|
|
|
|
styleOptions: this.styleOptions,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 图层剪贴蒙版命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class LayerClippingMaskCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "创建/移除剪贴蒙版",
|
|
|
|
|
|
saveState: false,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.maskLayerId = options.maskLayerId || null; // null表示移除蒙版
|
|
|
|
|
|
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
this.oldClippingMask = this.layer ? this.layer.clippingMask : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (!this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.maskLayerId) {
|
|
|
|
|
|
// 创建剪贴蒙版
|
|
|
|
|
|
const maskLayer = this.layers.value.find(
|
|
|
|
|
|
(l) => l.id === this.maskLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!maskLayer) {
|
|
|
|
|
|
console.error(`蒙版图层 ${this.maskLayerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.layer.clippingMask = maskLayer.fabricObjects?.[0] || null;
|
|
|
|
|
|
console.log(`已为图层 ${this.layer.name} 创建剪贴蒙版`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 移除剪贴蒙版
|
|
|
|
|
|
this.layer.clippingMask = null;
|
|
|
|
|
|
console.log(`已移除图层 ${this.layer.name} 的剪贴蒙版`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上的对象
|
|
|
|
|
|
this._updateObjectClipping();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
this.canvas.renderAll();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return true;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
_updateObjectClipping() {
|
|
|
|
|
|
if (!this.layer.fabricObjects) return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (this.layer.clippingMask) {
|
|
|
|
|
|
// 应用剪贴蒙版
|
|
|
|
|
|
obj.clipPath = this.layer.clippingMask;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 移除剪贴蒙版
|
|
|
|
|
|
obj.clipPath = null;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (!this.layer) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原剪贴蒙版
|
|
|
|
|
|
this.layer.clippingMask = this.oldClippingMask;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新画布上的对象
|
|
|
|
|
|
this._updateObjectClipping();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
this.canvas.renderAll();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
maskLayerId: this.maskLayerId,
|
|
|
|
|
|
hasClippingMask: !!this.layer?.clippingMask,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更换固定图层图像命令
|
|
|
|
|
|
* 专门用于更换固定图层(如背景图层)的图像
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ChangeFixedImageCommand extends Command {
|
|
|
|
|
|
constructor(options = {}) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "更换固定图层图像",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.imageUrl = options.imageUrl;
|
|
|
|
|
|
this.targetLayerType = options.targetLayerType || "fixed"; // 'background', 'fixed', etc.
|
|
|
|
|
|
this.position = options.position || { x: 0, y: 0 };
|
|
|
|
|
|
this.scale = options.scale || { x: 1, y: 1 };
|
|
|
|
|
|
this.preserveTransform = options.preserveTransform ?? false; // 默认不保留变换
|
|
|
|
|
|
|
|
|
|
|
|
// 用于回滚的状态
|
|
|
|
|
|
this.previousImage = null;
|
|
|
|
|
|
this.previousTransform = null;
|
|
|
|
|
|
this.previousObjectId = null; // 保存之前对象的ID
|
|
|
|
|
|
this.targetLayer = null;
|
|
|
|
|
|
this.newObjectId = null; // 保存新对象的ID
|
|
|
|
|
|
this.isExecuted = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 错误处理
|
|
|
|
|
|
this.maxRetries = options.maxRetries || 3;
|
|
|
|
|
|
this.retryCount = 0;
|
|
|
|
|
|
this.timeoutMs = options.timeoutMs || 10000;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async execute() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 查找目标图层
|
|
|
|
|
|
this.targetLayer = this.findTargetLayer();
|
|
|
|
|
|
if (!this.targetLayer) {
|
|
|
|
|
|
throw new Error(`找不到目标图层类型: ${this.targetLayerType}`);
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 保存当前状态
|
|
|
|
|
|
await this.saveCurrentState();
|
|
|
|
|
|
|
|
|
|
|
|
// 加载新图像
|
|
|
|
|
|
const newImage = await this.loadImageWithRetry();
|
|
|
|
|
|
|
|
|
|
|
|
// 应用图像到图层
|
|
|
|
|
|
await this.applyImageToLayer(newImage);
|
|
|
|
|
|
|
|
|
|
|
|
this.isExecuted = true;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
layerId: this.targetLayer.id,
|
|
|
|
|
|
imageUrl: this.imageUrl,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("ChangeFixedImageCommand执行失败:", error);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经执行了部分操作,尝试回滚
|
|
|
|
|
|
if (this.isExecuted) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
try {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await this.undo();
|
|
|
|
|
|
} catch (rollbackError) {
|
|
|
|
|
|
console.error("回滚失败:", rollbackError);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!this.isExecuted || !this.targetLayer) {
|
|
|
|
|
|
throw new Error("命令未执行或目标图层不存在");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
if (this.previousImage && this.previousObjectId) {
|
|
|
|
|
|
// 恢复之前的图像
|
|
|
|
|
|
await this.restorePreviousImage();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有之前的图像,移除当前图像
|
|
|
|
|
|
await this.removeCurrentImage();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.isExecuted = false;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
action: "reverted",
|
|
|
|
|
|
layerId: this.targetLayer.id,
|
|
|
|
|
|
};
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} catch (error) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.error("ChangeFixedImageCommand撤销失败:", error);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
findTargetLayer() {
|
|
|
|
|
|
const layers = this.layerManager.layers?.value || [];
|
|
|
|
|
|
|
|
|
|
|
|
switch (this.targetLayerType) {
|
|
|
|
|
|
case "background":
|
|
|
|
|
|
return layers.find((layer) => layer.isBackground);
|
|
|
|
|
|
case "fixed":
|
|
|
|
|
|
return layers.find((layer) => layer.isFixed);
|
|
|
|
|
|
default:
|
|
|
|
|
|
return layers.find((layer) => layer.type === this.targetLayerType);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async saveCurrentState() {
|
|
|
|
|
|
// 通过画布查找当前图层的对象,而不是直接使用图层引用
|
|
|
|
|
|
const currentObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.targetLayer.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (currentObjects.length > 0) {
|
|
|
|
|
|
const currentObj = currentObjects[0]; // 固定图层通常只有一个对象
|
|
|
|
|
|
|
|
|
|
|
|
// 验证对象确实在画布中
|
|
|
|
|
|
const canvasCheck = objectIsInCanvas(this.canvas, currentObj);
|
|
|
|
|
|
if (!canvasCheck.flag) {
|
|
|
|
|
|
console.warn("图层对象不在画布中,跳过状态保存");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.previousObjectId = currentObj.id;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用帮助函数获取z-index位置
|
|
|
|
|
|
this.previousZIndex = getObjectZIndex(this.canvas, currentObj);
|
|
|
|
|
|
|
|
|
|
|
|
// 保存当前图像URL(如果存在)
|
|
|
|
|
|
this.previousImage = {
|
|
|
|
|
|
url: currentObj.getSrc ? currentObj.getSrc() : null,
|
|
|
|
|
|
element: currentObj._element ? currentObj._element.cloneNode() : null,
|
|
|
|
|
|
objectData: currentObj.toObject(["id", "layerId", "layerName"]), // 保存完整对象数据
|
|
|
|
|
|
zIndex: this.previousZIndex, // 保存z-index
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 保存变换状态
|
|
|
|
|
|
this.previousTransform = {
|
|
|
|
|
|
left: currentObj.left,
|
|
|
|
|
|
top: currentObj.top,
|
|
|
|
|
|
scaleX: currentObj.scaleX,
|
|
|
|
|
|
scaleY: currentObj.scaleY,
|
|
|
|
|
|
angle: currentObj.angle,
|
|
|
|
|
|
flipX: currentObj.flipX,
|
|
|
|
|
|
flipY: currentObj.flipY,
|
|
|
|
|
|
opacity: currentObj.opacity,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`保存渲染顺序: z-index = ${this.previousZIndex}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async loadImageWithRetry() {
|
|
|
|
|
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await this.loadImage();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.retryCount = attempt;
|
|
|
|
|
|
|
|
|
|
|
|
if (attempt === this.maxRetries) {
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 指数退避重试
|
|
|
|
|
|
const delay = Math.pow(2, attempt) * 1000;
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
|
|
|
|
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
|
|
|
|
|
error.message
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadImage() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
|
|
reject(
|
|
|
|
|
|
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
|
|
|
|
|
);
|
|
|
|
|
|
}, this.timeoutMs);
|
|
|
|
|
|
|
|
|
|
|
|
fabric.Image.fromURL(
|
|
|
|
|
|
this.imageUrl,
|
|
|
|
|
|
(img) => {
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
|
|
|
|
|
|
|
if (!img || !img.getElement()) {
|
|
|
|
|
|
reject(new Error("图像加载失败或无效"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resolve(img);
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
crossOrigin: "anonymous",
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async applyImageToLayer(newImage) {
|
|
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
// 生成新对象ID
|
|
|
|
|
|
this.newObjectId = generateId();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置基本属性
|
|
|
|
|
|
newImage.set({
|
|
|
|
|
|
id: this.newObjectId,
|
|
|
|
|
|
layerId: this.targetLayer.id,
|
|
|
|
|
|
layerName: this.targetLayer.name,
|
|
|
|
|
|
isBackground: this.targetLayer.isBackground,
|
|
|
|
|
|
isFixed: this.targetLayer.isFixed,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 移除旧对象(如果存在)
|
|
|
|
|
|
if (this.previousObjectId) {
|
|
|
|
|
|
const { object: oldObject } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.previousObjectId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (oldObject) {
|
|
|
|
|
|
const removeSuccess = removeCanvasObjectByObject(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
oldObject
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!removeSuccess) {
|
|
|
|
|
|
console.warn("移除旧对象失败,但继续执行");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用位置和变换
|
|
|
|
|
|
if (this.preserveTransform && this.previousTransform) {
|
|
|
|
|
|
newImage.set(this.previousTransform);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newImage.set({
|
|
|
|
|
|
left: this.position.x || this.canvas.width / 2,
|
|
|
|
|
|
top: this.position.y || this.canvas.height / 2,
|
|
|
|
|
|
scaleX: this.scale.x || 1,
|
|
|
|
|
|
scaleY: this.scale.y || 1,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用帮助函数在指定z-index位置插入新图像
|
|
|
|
|
|
if (this.previousZIndex !== undefined && this.previousZIndex >= 0) {
|
|
|
|
|
|
const insertSuccess = insertObjectAtZIndex(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
newImage,
|
|
|
|
|
|
this.previousZIndex,
|
|
|
|
|
|
false
|
|
|
|
|
|
);
|
|
|
|
|
|
if (insertSuccess) {
|
|
|
|
|
|
console.log(`新图像插入到z-index位置: ${this.previousZIndex}`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果插入失败,回退到普通添加
|
|
|
|
|
|
this.canvas.add(newImage);
|
|
|
|
|
|
console.log("z-index插入失败,新图像添加到顶层");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 否则直接添加到顶层
|
|
|
|
|
|
this.canvas.add(newImage);
|
|
|
|
|
|
console.log("新图像添加到顶层");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
newImage.setCoords();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层引用
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层管理器
|
|
|
|
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, newImage);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async restorePreviousImage() {
|
|
|
|
|
|
if (!this.previousImage?.objectData) {
|
|
|
|
|
|
throw new Error("没有可恢复的图像数据");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
fabric.util.enlivenObjects(
|
|
|
|
|
|
[this.previousImage.objectData],
|
|
|
|
|
|
async (objects) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const restoredImage = objects[0];
|
|
|
|
|
|
|
|
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
// 移除当前对象
|
|
|
|
|
|
if (this.newObjectId) {
|
|
|
|
|
|
const { object: currentObject } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.newObjectId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (currentObject) {
|
|
|
|
|
|
removeCanvasObjectByObject(this.canvas, currentObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复之前的变换
|
|
|
|
|
|
if (this.previousTransform) {
|
|
|
|
|
|
restoredImage.set(this.previousTransform);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置图层属性
|
|
|
|
|
|
restoredImage.set({
|
|
|
|
|
|
id: this.previousObjectId,
|
|
|
|
|
|
layerId: this.targetLayer.id,
|
|
|
|
|
|
layerName: this.targetLayer.name,
|
|
|
|
|
|
isBackground: this.targetLayer.isBackground,
|
|
|
|
|
|
isFixed: this.targetLayer.isFixed,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 使用帮助函数在正确的z-index位置恢复对象
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.previousZIndex !== undefined &&
|
|
|
|
|
|
this.previousZIndex >= 0
|
|
|
|
|
|
) {
|
|
|
|
|
|
const insertSuccess = insertObjectAtZIndex(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
restoredImage,
|
|
|
|
|
|
this.previousZIndex,
|
|
|
|
|
|
false
|
|
|
|
|
|
);
|
|
|
|
|
|
if (insertSuccess) {
|
|
|
|
|
|
console.log(`恢复图像到z-index位置: ${this.previousZIndex}`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果插入失败,回退到普通添加
|
|
|
|
|
|
this.canvas.add(restoredImage);
|
|
|
|
|
|
console.log("z-index恢复失败,恢复图像添加到顶层");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有保存的z-index,添加到顶层
|
|
|
|
|
|
this.canvas.add(restoredImage);
|
|
|
|
|
|
console.log("恢复图像添加到顶层");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
restoredImage.setCoords();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新引用
|
2025-06-25 01:03:39 +08:00
|
|
|
|
this.targetLayer.fabricObject = restoredImage.toObject([
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"type",
|
|
|
|
|
|
]);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layerManager.updateLayerObject(
|
|
|
|
|
|
this.targetLayer.id,
|
|
|
|
|
|
restoredImage
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async removeCurrentImage() {
|
|
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
if (this.newObjectId) {
|
|
|
|
|
|
const { object: currentObject } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.newObjectId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (currentObject) {
|
|
|
|
|
|
const removeSuccess = removeCanvasObjectByObject(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
currentObject
|
|
|
|
|
|
);
|
|
|
|
|
|
if (removeSuccess) {
|
|
|
|
|
|
this.targetLayer.fabricObject = null;
|
|
|
|
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
targetLayerType: this.targetLayerType,
|
|
|
|
|
|
imageUrl: this.imageUrl,
|
|
|
|
|
|
isExecuted: this.isExecuted,
|
|
|
|
|
|
retryCount: this.retryCount,
|
|
|
|
|
|
targetLayerId: this.targetLayer?.id,
|
|
|
|
|
|
preserveTransform: this.preserveTransform,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 向图层添加图像命令
|
|
|
|
|
|
* 用于向指定图层添加新的图像对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class AddImageToLayerCommand extends Command {
|
|
|
|
|
|
constructor(options = {}) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "向图层添加图像",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.imageUrl = options.imageUrl;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.position = options.position || { x: 100, y: 100 };
|
|
|
|
|
|
this.scale = options.scale || { x: 1, y: 1 };
|
|
|
|
|
|
this.zIndex = options.zIndex || null; // 可选的层级控制
|
|
|
|
|
|
|
|
|
|
|
|
// 用于回滚的状态
|
|
|
|
|
|
this.addedObject = null;
|
|
|
|
|
|
this.targetLayer = null;
|
|
|
|
|
|
this.isExecuted = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 错误处理
|
|
|
|
|
|
this.maxRetries = options.maxRetries || 3;
|
|
|
|
|
|
this.retryCount = 0;
|
|
|
|
|
|
this.timeoutMs = options.timeoutMs || 10000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.validateInputs();
|
|
|
|
|
|
|
|
|
|
|
|
// 查找目标图层
|
|
|
|
|
|
const { layer } = findLayerRecursively(
|
|
|
|
|
|
this.layerManager?.layers?.value || [],
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
this.targetLayer = layer;
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.targetLayer) {
|
|
|
|
|
|
throw new Error(`找不到目标图层: ${this.layerId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查图层是否可编辑
|
|
|
|
|
|
this.validateLayerEditability();
|
|
|
|
|
|
|
|
|
|
|
|
// 加载新图像
|
|
|
|
|
|
const newImage = await this.loadImageWithRetry();
|
|
|
|
|
|
|
|
|
|
|
|
// 添加图像到图层
|
|
|
|
|
|
await this.addImageToLayer(newImage);
|
|
|
|
|
|
|
|
|
|
|
|
this.isExecuted = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 触发成功事件
|
|
|
|
|
|
this.emitEvent("image:added", {
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
objectId: this.addedObject.id,
|
|
|
|
|
|
imageUrl: this.imageUrl,
|
|
|
|
|
|
command: this,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
objectId: this.addedObject.id,
|
|
|
|
|
|
imageUrl: this.imageUrl,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("AddImageToLayerCommand执行失败:", error);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经添加了对象,尝试移除
|
|
|
|
|
|
if (this.addedObject) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.undo();
|
|
|
|
|
|
} catch (rollbackError) {
|
|
|
|
|
|
console.error("回滚失败:", rollbackError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (!this.isExecuted || !this.addedObject) {
|
|
|
|
|
|
throw new Error("命令未执行或没有添加的对象");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 移除添加的对象
|
|
|
|
|
|
this.canvas.remove(this.addedObject);
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层管理器中移除
|
|
|
|
|
|
this.layerManager.removeObjectFromLayer(
|
|
|
|
|
|
this.addedObject.id,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
this.isExecuted = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 触发撤销事件
|
|
|
|
|
|
this.emitEvent("image:removed", {
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
objectId: this.addedObject.id,
|
|
|
|
|
|
command: this,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
action: "removed",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
objectId: this.addedObject.id,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("AddImageToLayerCommand撤销失败:", error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validateInputs() {
|
|
|
|
|
|
if (!this.canvas) throw new Error("Canvas实例是必需的");
|
|
|
|
|
|
if (!this.layerManager) throw new Error("LayerManager实例是必需的");
|
|
|
|
|
|
if (!this.imageUrl) throw new Error("图像URL是必需的");
|
|
|
|
|
|
if (!this.layerId) throw new Error("图层ID是必需的");
|
|
|
|
|
|
|
|
|
|
|
|
// 验证URL格式
|
|
|
|
|
|
try {
|
|
|
|
|
|
new URL(this.imageUrl);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
throw new Error("无效的图像URL格式");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
findTargetLayer() {
|
|
|
|
|
|
const layers = this.layerManager.layers?.value || [];
|
|
|
|
|
|
return layers.find((layer) => layer.id === this.layerId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validateLayerEditability() {
|
|
|
|
|
|
if (this.targetLayer.locked) {
|
|
|
|
|
|
throw new Error("目标图层已锁定,无法添加对象");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.targetLayer.visible) {
|
|
|
|
|
|
console.warn("目标图层不可见,添加的对象可能不会显示");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async loadImageWithRetry() {
|
|
|
|
|
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await this.loadImage();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.retryCount = attempt;
|
|
|
|
|
|
|
|
|
|
|
|
if (attempt === this.maxRetries) {
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 指数退避重试
|
|
|
|
|
|
const delay = Math.pow(2, attempt) * 1000;
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
|
|
|
|
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
|
|
|
|
|
error.message
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadImage() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
|
|
reject(
|
|
|
|
|
|
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
|
|
|
|
|
);
|
|
|
|
|
|
}, this.timeoutMs);
|
|
|
|
|
|
|
|
|
|
|
|
fabric.Image.fromURL(
|
|
|
|
|
|
this.imageUrl,
|
|
|
|
|
|
(img) => {
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
|
|
|
|
|
|
|
if (!img || !img.getElement()) {
|
|
|
|
|
|
reject(new Error("图像加载失败或无效"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resolve(img);
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
crossOrigin: "anonymous",
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async addImageToLayer(newImage) {
|
|
|
|
|
|
// 生成唯一ID
|
|
|
|
|
|
const objectId = generateId();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置图像属性
|
|
|
|
|
|
newImage.set({
|
|
|
|
|
|
id: objectId,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
layerName: this.targetLayer.name,
|
|
|
|
|
|
left: this.position.x,
|
|
|
|
|
|
top: this.position.y,
|
|
|
|
|
|
scaleX: this.scale.x,
|
|
|
|
|
|
scaleY: this.scale.y,
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到画布
|
|
|
|
|
|
this.canvas.add(newImage);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置层级
|
|
|
|
|
|
if (this.zIndex !== null) {
|
|
|
|
|
|
this.setObjectZIndex(newImage, this.zIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
newImage.setCoords();
|
|
|
|
|
|
|
|
|
|
|
|
// 保存引用用于回滚
|
|
|
|
|
|
this.addedObject = newImage;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到图层管理器
|
|
|
|
|
|
this.layerManager.addObjectToLayer(newImage, this.layerId);
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setObjectZIndex(object, zIndex) {
|
|
|
|
|
|
if (zIndex === "top") {
|
|
|
|
|
|
object.bringToFront();
|
|
|
|
|
|
} else if (zIndex === "bottom") {
|
|
|
|
|
|
object.sendToBack();
|
|
|
|
|
|
} else if (typeof zIndex === "number") {
|
|
|
|
|
|
object.moveTo(zIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
emitEvent(eventName, data) {
|
|
|
|
|
|
if (this.canvas && this.canvas.fire) {
|
|
|
|
|
|
this.canvas.fire(eventName, data);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
imageUrl: this.imageUrl,
|
|
|
|
|
|
position: this.position,
|
|
|
|
|
|
scale: this.scale,
|
|
|
|
|
|
isExecuted: this.isExecuted,
|
|
|
|
|
|
retryCount: this.retryCount,
|
|
|
|
|
|
addedObjectId: this.addedObject?.id,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除子图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class RemoveChildLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "删除子图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.parentId = options.parentId;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找父图层和子图层
|
|
|
|
|
|
const { parent } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
this.parentLayer = parent;
|
|
|
|
|
|
|
|
|
|
|
|
this.childIndex =
|
|
|
|
|
|
this.parentLayer?.children?.findIndex(
|
|
|
|
|
|
(child) => child.id === this.layerId
|
|
|
|
|
|
) ?? -1;
|
|
|
|
|
|
this.removedChild = this.parentLayer?.children?.[this.childIndex];
|
|
|
|
|
|
this.isActiveLayer = this.layerId === this.activeLayerId.value;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
|
|
|
|
|
this.originalObjects = this.canvas.getObjects().filter((obj) => {
|
|
|
|
|
|
return obj.layerId === this.layerId;
|
|
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.parentLayer || this.childIndex === -1 || !this.removedChild) {
|
|
|
|
|
|
throw new Error("找不到要删除的子图层");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从画布中移除子图层中的所有对象
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.removedChild.fabricObjects &&
|
|
|
|
|
|
this.removedChild.fabricObjects.length > 0
|
|
|
|
|
|
) {
|
|
|
|
|
|
this.removedChild.fabricObjects.forEach((obj) => {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
const { object } = findObjectById(this.canvas, obj.id);
|
|
|
|
|
|
if (object) {
|
|
|
|
|
|
this.canvas.remove(...this.originalObjects);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从父图层的children中删除
|
|
|
|
|
|
this.parentLayer.children.splice(this.childIndex, 1);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果删除的是当前活动图层,需要更新活动图层
|
|
|
|
|
|
if (this.isActiveLayer) {
|
|
|
|
|
|
// 设置父图层为活动图层,或者选择另一个子图层
|
|
|
|
|
|
if (this.parentLayer.children.length > 0) {
|
|
|
|
|
|
this.activeLayerId.value = this.parentLayer.children[0].id;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.activeLayerId.value =
|
|
|
|
|
|
this.layers.value.find(
|
|
|
|
|
|
(layer) => !layer.isBackground || !layer.isFixed
|
|
|
|
|
|
)?.[0]?.id || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
if (
|
|
|
|
|
|
!this.parentLayer ||
|
|
|
|
|
|
this.childIndex === -1 ||
|
|
|
|
|
|
!this.removedChild ||
|
|
|
|
|
|
this.originalObjects.length === 0
|
|
|
|
|
|
) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 恢复子图层到原位置
|
|
|
|
|
|
this.parentLayer.children.splice(this.childIndex, 0, this.removedChild);
|
2025-06-22 13:52:28 +08:00
|
|
|
|
optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
this.originalObjects.forEach((obj) => {
|
|
|
|
|
|
// 恢复对象到画布
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.add(obj);
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 恢复对象的图层信息
|
|
|
|
|
|
obj.layerId = this.layerId;
|
|
|
|
|
|
obj.layerName = this.removedChild.name;
|
|
|
|
|
|
obj.setCoords(); // 更新坐标
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 如果是原活动图层,恢复活动图层
|
|
|
|
|
|
if (this.isActiveLayer) {
|
|
|
|
|
|
this.activeLayerId.value = this.layerId;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 重新渲染画布
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
|
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerName: this.removedChild?.name || "未知子图层",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
parentId: this.parentId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重命名子图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class RenameChildLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "重命名子图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.parentId = options.parentId;
|
|
|
|
|
|
this.newName = options.newName;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找父图层和子图层
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
this.childLayer = layer;
|
|
|
|
|
|
|
|
|
|
|
|
this.oldName = this.childLayer ? this.childLayer.name : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.childLayer) {
|
|
|
|
|
|
throw new Error("找不到要重命名的子图层");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新子图层名称
|
|
|
|
|
|
this.childLayer.name = this.newName;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的图层名称
|
|
|
|
|
|
if (this.canvas && this.childLayer.fabricObjects) {
|
|
|
|
|
|
this.childLayer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj.layerName !== undefined) {
|
|
|
|
|
|
obj.layerName = this.newName;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (this.childLayer && this.oldName) {
|
|
|
|
|
|
this.childLayer.name = this.oldName;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复画布上对象的图层名称
|
|
|
|
|
|
if (this.canvas && this.childLayer.fabricObjects) {
|
|
|
|
|
|
this.childLayer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj.layerName !== undefined) {
|
|
|
|
|
|
obj.layerName = this.oldName;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
parentId: this.parentId,
|
|
|
|
|
|
oldName: this.oldName,
|
|
|
|
|
|
newName: this.newName,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 子图层锁定/解锁命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ChildLayerLockCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "子图层锁定/解锁",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.parentId = options.parentId;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.layerManager = options.layerManager;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 查找父图层和子图层
|
|
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
2025-06-09 10:25:54 +08:00
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.parentLayer = parent;
|
|
|
|
|
|
this.childLayer = layer;
|
|
|
|
|
|
this.oldLocked = this.childLayer ? this.childLayer.locked : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.childLayer) {
|
|
|
|
|
|
throw new Error("找不到要锁定/解锁的子图层");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换锁定状态
|
|
|
|
|
|
this.childLayer.locked = !this.oldLocked;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的可选择状态
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (this.childLayer) {
|
|
|
|
|
|
// 恢复锁定状态
|
|
|
|
|
|
this.childLayer.locked = this.oldLocked;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布上对象的可选择状态
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerName: this.childLayer?.name || "未知子图层",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
parentId: this.parentId,
|
|
|
|
|
|
newLocked: this.childLayer?.locked,
|
|
|
|
|
|
oldLocked: this.oldLocked,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|