2025-06-18 11:05:23 +08:00
|
|
|
|
import { findLayerRecursively, OperationType } from "../utils/layerHelper";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
import { Command } from "./Command";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import {
|
|
|
|
|
|
generateId,
|
|
|
|
|
|
optimizeCanvasRendering,
|
|
|
|
|
|
findObjectById,
|
|
|
|
|
|
objectIsInCanvas,
|
|
|
|
|
|
removeCanvasObjectByObject,
|
|
|
|
|
|
getObjectZIndex,
|
|
|
|
|
|
insertObjectAtZIndex,
|
|
|
|
|
|
} from "../utils/helper";
|
|
|
|
|
|
import { fabric } from "fabric-with-all";
|
2025-06-23 15:56:01 +08:00
|
|
|
|
import { imageModeHandler } from "../utils/imageHelper";
|
2025-07-21 23:07:47 +08:00
|
|
|
|
import { restoreFabricObject } from "../utils/objectHelper";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置活动图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class SetActiveLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "设置活动图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.layerId = options.layerId;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.parentId = options.parentId;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.oldActiveLayerId = this.activeLayerId.value;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.oldActiveObjects = [];
|
|
|
|
|
|
this.newLayer = null;
|
|
|
|
|
|
this.editorMode = options.editorMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const { layer } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId,
|
|
|
|
|
|
this.parentId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.newLayer = layer;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (!this.newLayer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是背景层,不设置为活动图层
|
|
|
|
|
|
if (this.newLayer.isBackground) {
|
|
|
|
|
|
console.warn("背景层不能设为活动图层");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果图层已锁定,不设置为活动图层
|
|
|
|
|
|
if (this.newLayer.locked) {
|
|
|
|
|
|
console.warn("锁定图层不能设为活动图层");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.oldActiveObjects = this.canvas.getActiveObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置为活动图层
|
|
|
|
|
|
this.activeLayerId.value = this.layerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果在选择模式下,取消所有选择
|
|
|
|
|
|
if (this.editorMode === OperationType.SELECT && this.canvas) {
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置为新的图层下的对象为激活,但需要确保对象存在于画布上
|
2025-07-21 23:07:47 +08:00
|
|
|
|
if (
|
|
|
|
|
|
this.newLayer.fabricObjects &&
|
|
|
|
|
|
this.newLayer.fabricObjects.length > 0
|
|
|
|
|
|
) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
const validObjects = this.newLayer.fabricObjects.filter(
|
|
|
|
|
|
(obj) => obj && canvasObjects.includes(obj)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (validObjects.length > 0) {
|
|
|
|
|
|
if (validObjects.length === 1) {
|
|
|
|
|
|
// 只有一个对象时直接设置
|
|
|
|
|
|
this.canvas.setActiveObject(validObjects[0]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 多个对象时创建活动选择组
|
|
|
|
|
|
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas.setActiveObject(activeSelection);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
// 恢复原活动图层ID
|
|
|
|
|
|
this.activeLayerId.value = this.oldActiveLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果在选择模式下,恢复取消的所有选择
|
|
|
|
|
|
if (this.editorMode === OperationType.SELECT && this.canvas) {
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
// 修复:确保对象存在于画布上才激活
|
|
|
|
|
|
if (this.oldActiveObjects && this.oldActiveObjects.length > 0) {
|
|
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
const validObjects = this.oldActiveObjects.filter(
|
|
|
|
|
|
(obj) => obj && canvasObjects.includes(obj)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (validObjects.length > 0) {
|
|
|
|
|
|
if (validObjects.length > 1) {
|
|
|
|
|
|
// 如果有多个对象,需要创建一个活动选择组
|
|
|
|
|
|
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas.setActiveObject(activeSelection);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 只有一个对象时直接设置
|
|
|
|
|
|
this.canvas.setActiveObject(validObjects[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldActiveLayerId: this.oldActiveLayerId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 添加对象到图层命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class AddObjectToLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "添加对象到图层",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.fabricObject = options.fabricObject;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layerManager = options.layerManager;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存对象原始状态和ID
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.objectId =
|
|
|
|
|
|
this.fabricObject.id ||
|
|
|
|
|
|
`obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.fabricObject.set({
|
|
|
|
|
|
id: this.objectId,
|
|
|
|
|
|
layerId: options.layerId,
|
|
|
|
|
|
layerName: options.layerName || "",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 保存完整的对象状态,包括位置和变换信息
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.originalObjectState = this.fabricObject.toObject([
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
]);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// // 新增:保存详细的位置和变换信息,用于重做时恢复
|
|
|
|
|
|
// this.originalPosition = {
|
|
|
|
|
|
// left: this.originalObjectState.left || 0,
|
|
|
|
|
|
// top: this.originalObjectState.top || 0,
|
|
|
|
|
|
// scaleX: this.fabricObject.scaleX || 1,
|
|
|
|
|
|
// scaleY: this.fabricObject.scaleY || 1,
|
|
|
|
|
|
// angle: this.fabricObject.angle || 0,
|
|
|
|
|
|
// flipX: this.fabricObject.flipX || false,
|
|
|
|
|
|
// flipY: this.fabricObject.flipY || false,
|
|
|
|
|
|
// opacity: this.fabricObject.opacity || 1,
|
|
|
|
|
|
// originX: this.fabricObject.originX || "left",
|
|
|
|
|
|
// originY: this.fabricObject.originY || "top",
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
// 记录执行状态,用于区分首次执行和重做
|
|
|
|
|
|
this.isFirstExecution = true;
|
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
|
|
|
|
// 查找目标图层
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是背景层,不允许添加对象
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
console.warn("不能向背景层添加对象");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(this.originalObjectState);
|
|
|
|
|
|
|
|
|
|
|
|
// 优化渲染 - 统一批处理 支持异步回调
|
|
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
fabric.util.enlivenObjects(
|
|
|
|
|
|
[this.originalObjectState],
|
|
|
|
|
|
async (objects) => {
|
|
|
|
|
|
this.fabricObject = objects[0];
|
|
|
|
|
|
// 将对象添加到画布
|
|
|
|
|
|
this.fabricObject.set({
|
|
|
|
|
|
id: this.objectId, // 确保对象有唯一ID
|
|
|
|
|
|
layerId: this.layerId, // 设置图层ID
|
|
|
|
|
|
layerName: layer.name, // 设置图层名称
|
|
|
|
|
|
isBackground: layer.isBackground || false, // 是否为背景层
|
|
|
|
|
|
isFixed: layer.isFixed || false, // 是否为固定层
|
|
|
|
|
|
isLocked: layer.locked || false, // 是否锁定
|
|
|
|
|
|
isVisible: layer.visible !== false, // 是否可见
|
|
|
|
|
|
eraser: layer.eraser || false, // 是否为橡皮擦
|
2025-07-24 21:37:21 +08:00
|
|
|
|
// type: this.fabricObject.type || "object", // 对象类型
|
2025-07-21 23:07:47 +08:00
|
|
|
|
selectable: true, // 设置对象可选择
|
|
|
|
|
|
evented: true, // 设置对象可事件
|
|
|
|
|
|
});
|
|
|
|
|
|
// 如果对象父图层有 裁剪 则更新裁剪
|
2025-07-22 13:59:40 +08:00
|
|
|
|
if (parent?.clippingMask) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const clippingMaskFabricObject = await restoreFabricObject(
|
|
|
|
|
|
parent.clippingMask,
|
|
|
|
|
|
this.canvas
|
|
|
|
|
|
);
|
|
|
|
|
|
clippingMaskFabricObject.clipPath = null;
|
|
|
|
|
|
clippingMaskFabricObject.set({ absolutePositioned: true });
|
|
|
|
|
|
this.fabricObject.clipPath = clippingMaskFabricObject;
|
|
|
|
|
|
// 标记为脏对象
|
|
|
|
|
|
this.fabricObject.dirty = true;
|
|
|
|
|
|
this.fabricObject.setCoords();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
// this.canvas.renderAll();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.add(this.fabricObject);
|
2025-07-21 23:07:47 +08:00
|
|
|
|
// this.canvas.setActiveObject(this.fabricObject);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 将对象添加到图层的fabricObjects数组
|
|
|
|
|
|
layer.fabricObjects = layer.fabricObjects || [];
|
|
|
|
|
|
layer.fabricObjects.push(
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.fabricObject?.toObject?.(["id", "layerId", "layerName"]) ||
|
|
|
|
|
|
this.fabricObject
|
2025-06-18 11:05:23 +08:00
|
|
|
|
);
|
|
|
|
|
|
// 标记为非首次执行
|
|
|
|
|
|
this.isFirstExecution = false;
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`✅ 对象已添加到图层 "${layer.name}",位置: (${this.fabricObject.left}, ${this.fabricObject.top})`
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity?.();
|
|
|
|
|
|
this.layerManager?.updateLayersObjectsInteractivity?.();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果是重做,使用保存的原始位置
|
|
|
|
|
|
|
|
|
|
|
|
// this.canvas.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
// // 确保对象确实存在于画布上才激活
|
|
|
|
|
|
// const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
// const validObjects = layer.fabricObjects.filter(
|
|
|
|
|
|
// (obj) => obj && canvasObjects.includes(obj)
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
|
|
// if (validObjects.length > 0) {
|
|
|
|
|
|
// if (validObjects.length === 1) {
|
|
|
|
|
|
// // 只有一个对象时直接设置
|
|
|
|
|
|
// this.canvas.setActiveObject(validObjects[0]);
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// // 多个对象时创建活动选择组
|
|
|
|
|
|
// const activeSelection = new fabric.ActiveSelection(validObjects, {
|
|
|
|
|
|
// canvas: this.canvas,
|
|
|
|
|
|
// });
|
|
|
|
|
|
// this.canvas.setActiveObject(activeSelection);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
return this.fabricObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async undo() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层的fabricObjects数组中移除对象
|
|
|
|
|
|
if (layer.fabricObjects) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
layer.fabricObjects = layer.fabricObjects.filter(
|
|
|
|
|
|
(obj) => obj.id !== this.objectId
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 优化渲染 - 统一批处理 支持异步回调
|
|
|
|
|
|
optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
// 从画布移除对象
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const object = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.find((obj) => obj.id === this.objectId);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (object) {
|
|
|
|
|
|
// 先丢弃活动对象,避免控制点渲染错误
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
this.canvas.remove(object);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 重置为首次执行状态,以便重做时能正确恢复位置
|
|
|
|
|
|
this.isFirstExecution = false; // 保持为false,因为已经执行过一次了
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
objectId: this.objectId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
isFirstExecution: this.isFirstExecution,
|
|
|
|
|
|
originalPosition: this.originalPosition,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从图层中移除对象命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class RemoveObjectFromLayerCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "从图层中移除对象",
|
|
|
|
|
|
saveState: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.objectId = options.objectId;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找对象和图层
|
|
|
|
|
|
this.object =
|
|
|
|
|
|
typeof options.objectOrId === "object"
|
|
|
|
|
|
? options.objectOrId
|
|
|
|
|
|
: this.canvas.getObjects().find((obj) => obj.id === this.objectId);
|
|
|
|
|
|
|
|
|
|
|
|
if (this.object) {
|
|
|
|
|
|
this.layerId = this.object.layerId;
|
|
|
|
|
|
this.objectData = this.object.toObject(["id", "layerId", "layerName"]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
if (!this.object) {
|
|
|
|
|
|
console.error(`对象 ${this.objectId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.layerId) {
|
|
|
|
|
|
console.error(`对象 ${this.objectId} 未关联到任何图层`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从画布移除对象
|
|
|
|
|
|
this.canvas.remove(this.object);
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层的fabricObjects数组移除对象
|
|
|
|
|
|
if (layer.fabricObjects) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
layer.fabricObjects = layer.fabricObjects.filter(
|
|
|
|
|
|
(obj) => obj.id !== this.objectId
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
if (!this.objectData || !this.layerId) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!layer) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复对象到画布
|
|
|
|
|
|
fabric.util.enlivenObjects([this.objectData], (objects) => {
|
|
|
|
|
|
const restoredObject = objects[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 将对象添加到画布
|
|
|
|
|
|
this.canvas.add(restoredObject);
|
|
|
|
|
|
|
|
|
|
|
|
// 将对象添加回图层
|
|
|
|
|
|
layer.fabricObjects = layer.fabricObjects || [];
|
|
|
|
|
|
layer.fabricObjects.push(restoredObject);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
objectId: this.objectId,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更换固定图层图像命令
|
|
|
|
|
|
* 专门用于更换固定图层(如背景图层)的图像
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ChangeFixedImageCommand extends Command {
|
|
|
|
|
|
constructor(options = {}) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.imageUrl = options.imageUrl;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.targetLayerType = options.targetLayerType || "fixed"; // 'background', 'fixed', etc.
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.position = options.position || { x: 0, y: 0 };
|
|
|
|
|
|
this.scale = options.scale || { x: 1, y: 1 };
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.preserveTransform = options.preserveTransform ?? false; // 默认不保留变换
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.options = options || {};
|
|
|
|
|
|
|
|
|
|
|
|
this.scale.x = options.scaleX || 1;
|
|
|
|
|
|
this.scale.y = options.scaleY || 1;
|
|
|
|
|
|
this.position.x = options.left || this.canvas.width / 2;
|
|
|
|
|
|
this.position.y = options.top || this.canvas.height / 2;
|
|
|
|
|
|
|
2025-06-23 09:27:29 +08:00
|
|
|
|
this.canvasWidth = options?.canvasWidth?.value || this.canvas.width;
|
|
|
|
|
|
this.canvasHeight = options?.canvasHeight?.value || this.canvas.height;
|
|
|
|
|
|
|
|
|
|
|
|
// 底图加载方式 1.平铺 2.拉伸 3.拉伸平铺 4.拉伸平铺并裁剪 5.包含
|
|
|
|
|
|
this.imageMode = options?.imageMode || ""; // 默认 contains, stretch,tile, stretchTile, stretchTileCrop
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 用于回滚的状态
|
|
|
|
|
|
this.previousImage = null;
|
|
|
|
|
|
this.previousTransform = null;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.previousObjectId = null; // 保存之前对象的ID
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.targetLayer = null;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.newObjectId = generateId("fixed_"); // 保存新对象的ID
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.isExecuted = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 错误处理
|
|
|
|
|
|
this.maxRetries = options.maxRetries || 3;
|
|
|
|
|
|
this.retryCount = 0;
|
|
|
|
|
|
this.timeoutMs = options.timeoutMs || 10000;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 查找目标图层
|
|
|
|
|
|
this.targetLayer = this.findTargetLayer();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 查找目标图层
|
|
|
|
|
|
this.targetLayer = this.findTargetLayer();
|
|
|
|
|
|
if (!this.targetLayer) {
|
|
|
|
|
|
throw new Error(`找不到目标图层类型: ${this.targetLayerType}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 保存当前状态
|
2025-06-09 10:25:54 +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) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.undo();
|
|
|
|
|
|
} catch (rollbackError) {
|
|
|
|
|
|
console.error("回滚失败:", rollbackError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (!this.isExecuted || !this.targetLayer) {
|
|
|
|
|
|
throw new Error("命令未执行或目标图层不存在");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
this.isExecuted = false;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
action: "reverted",
|
|
|
|
|
|
layerId: this.targetLayer.id,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("ChangeFixedImageCommand撤销失败:", error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 通过画布查找当前图层的对象,而不是直接使用图层引用
|
|
|
|
|
|
const currentObjects = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.filter((obj) => obj.layerId === this.targetLayer.id);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (currentObjects.length > 0) {
|
|
|
|
|
|
const currentObj = currentObjects[0]; // 固定图层通常只有一个对象
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 验证对象确实在画布中
|
|
|
|
|
|
const canvasCheck = objectIsInCanvas(this.canvas, currentObj);
|
|
|
|
|
|
if (!canvasCheck.flag) {
|
|
|
|
|
|
console.warn("图层对象不在画布中,跳过状态保存");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
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}`);
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async loadImageWithRetry() {
|
|
|
|
|
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await this.loadImage();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.retryCount = attempt;
|
|
|
|
|
|
|
|
|
|
|
|
if (attempt === this.maxRetries) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
throw new Error(
|
|
|
|
|
|
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 指数退避重试
|
|
|
|
|
|
const delay = Math.pow(2, attempt) * 1000;
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
console.warn(
|
|
|
|
|
|
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
|
|
|
|
|
error.message
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadImage() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
reject(
|
|
|
|
|
|
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}, 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) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
// 设置基本属性
|
2025-06-23 09:27:29 +08:00
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
newImage.set({
|
2025-06-22 13:52:28 +08:00
|
|
|
|
id: this.targetLayer?.fabricObject?.id || this.newObjectId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerId: this.targetLayer.id,
|
|
|
|
|
|
layerName: this.targetLayer.name,
|
|
|
|
|
|
isBackground: this.targetLayer.isBackground,
|
|
|
|
|
|
isFixed: this.targetLayer.isFixed,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 移除旧对象(如果存在)
|
|
|
|
|
|
if (this.previousObjectId) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const { object: oldObject } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.previousObjectId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (oldObject) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const removeSuccess = removeCanvasObjectByObject(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
oldObject
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!removeSuccess) {
|
|
|
|
|
|
console.warn("移除旧对象失败,但继续执行");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 应用位置和变换
|
|
|
|
|
|
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: "cehnter",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-23 15:56:01 +08:00
|
|
|
|
// 通用处理图片模式
|
|
|
|
|
|
imageModeHandler({
|
|
|
|
|
|
imageMode: this.imageMode,
|
|
|
|
|
|
newImage,
|
|
|
|
|
|
canvasWidth: this.canvasWidth,
|
|
|
|
|
|
canvasHeight: this.canvasHeight,
|
|
|
|
|
|
});
|
2025-06-23 09:27:29 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 使用帮助函数在指定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("新图像添加到顶层");
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
newImage.setCoords();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新图层引用
|
|
|
|
|
|
this.targetLayer.fabricObject = newImage;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新图层管理器
|
|
|
|
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, newImage);
|
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
|
|
|
|
async restorePreviousImage() {
|
|
|
|
|
|
if (!this.previousImage?.objectData) {
|
|
|
|
|
|
throw new Error("没有可恢复的图像数据");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
// 恢复之前的变换
|
|
|
|
|
|
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恢复失败,恢复图像添加到顶层");
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
} else {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
// 如果没有保存的z-index,添加到顶层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.add(restoredImage);
|
2025-07-21 23:07:47 +08:00
|
|
|
|
console.log("恢复图像添加到顶层");
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-07-14 01:00:23 +08:00
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
restoredImage.setCoords();
|
2025-07-14 01:00:23 +08:00
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
// 更新引用
|
|
|
|
|
|
this.targetLayer.fabricObject = restoredImage;
|
|
|
|
|
|
this.layerManager.updateLayerObject(
|
|
|
|
|
|
this.targetLayer.id,
|
|
|
|
|
|
restoredImage
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-07-14 01:00:23 +08:00
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
resolve();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-07-21 23:07:47 +08:00
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async removeCurrentImage() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
|
|
|
|
|
if (this.newObjectId) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const { object: currentObject } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.newObjectId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (currentObject) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const removeSuccess = removeCanvasObjectByObject(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
currentObject
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (removeSuccess) {
|
|
|
|
|
|
this.targetLayer.fabricObject = null;
|
|
|
|
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, null);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
emitEvent(eventName, data) {
|
|
|
|
|
|
if (this.canvas && this.canvas.fire) {
|
|
|
|
|
|
this.canvas.fire(eventName, data);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取命令信息用于调试
|
|
|
|
|
|
getCommandInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: "ChangeFixedImageCommand",
|
|
|
|
|
|
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();
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.imageId = generateId("image_");
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 错误处理
|
|
|
|
|
|
this.maxRetries = options.maxRetries || 3;
|
|
|
|
|
|
this.retryCount = 0;
|
|
|
|
|
|
this.timeoutMs = options.timeoutMs || 10000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.validateInputs();
|
|
|
|
|
|
|
|
|
|
|
|
// 查找目标图层
|
|
|
|
|
|
this.targetLayer = this.findTargetLayer();
|
|
|
|
|
|
if (!this.targetLayer) {
|
|
|
|
|
|
throw new Error(`找不到目标图层: ${this.layerId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查图层是否可编辑
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// this.validateLayerEditability();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载新图像
|
|
|
|
|
|
const newImage = await this.loadImageWithRetry();
|
|
|
|
|
|
|
|
|
|
|
|
// 添加图像到图层
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this.addImageToLayer(newImage, this.imageId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
// 从图层管理器中移除
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.layerManager.removeObjectFromLayer(
|
|
|
|
|
|
this.addedObject.id,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
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) {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
throw new Error(
|
|
|
|
|
|
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 指数退避重试
|
|
|
|
|
|
const delay = Math.pow(2, attempt) * 1000;
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
console.warn(
|
|
|
|
|
|
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
|
|
|
|
|
error.message
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadImage() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
2025-07-21 23:07:47 +08:00
|
|
|
|
reject(
|
|
|
|
|
|
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}, this.timeoutMs);
|
|
|
|
|
|
|
|
|
|
|
|
fabric.Image.fromURL(
|
|
|
|
|
|
this.imageUrl,
|
|
|
|
|
|
(img) => {
|
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
|
|
|
|
|
|
|
if (!img || !img.getElement()) {
|
|
|
|
|
|
reject(new Error("图像加载失败或无效"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resolve(img);
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
crossOrigin: "anonymous",
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async addImageToLayer(newImage, objectId) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 设置图像属性
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取命令信息用于调试
|
|
|
|
|
|
getCommandInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: "AddImageToLayerCommand",
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
imageUrl: this.imageUrl,
|
|
|
|
|
|
position: this.position,
|
|
|
|
|
|
scale: this.scale,
|
|
|
|
|
|
isExecuted: this.isExecuted,
|
|
|
|
|
|
retryCount: this.retryCount,
|
|
|
|
|
|
addedObjectId: this.addedObject?.id,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
export class SelectAllLayersCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "设置全选图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerManager = options.layerManager || null;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.oldActiveLayerId = this.activeLayerId.value;
|
|
|
|
|
|
this.editorMode = options.editorMode || OperationType.SELECT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
this.oldActiveObjects = this.canvas.getActiveObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果在选择模式下,取消所有选择
|
|
|
|
|
|
if (this.editorMode === OperationType.SELECT && this.canvas) {
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置为新的图层下的对象为激活,但需要确保对象存在于画布上
|
|
|
|
|
|
const canvasObjects =
|
|
|
|
|
|
this.canvas.getObjects()?.filter((fItem) => {
|
|
|
|
|
|
const layerId = fItem.layerId;
|
|
|
|
|
|
if (!layerId) return false; // 忽略没有图层ID的对象
|
|
|
|
|
|
// 确保当前图层存在且可见
|
|
|
|
|
|
const currLayer = this.layers.value.find((l) => l.id === layerId);
|
|
|
|
|
|
return (
|
|
|
|
|
|
!currLayer.isBackground &&
|
|
|
|
|
|
!currLayer.isFixed &&
|
|
|
|
|
|
!currLayer.isLocked &&
|
|
|
|
|
|
!currLayer.isVisible
|
|
|
|
|
|
);
|
|
|
|
|
|
}) || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 多个对象时创建活动选择组
|
|
|
|
|
|
const activeSelection = new fabric.ActiveSelection(canvasObjects, {
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
});
|
|
|
|
|
|
// 设置活动选择组
|
|
|
|
|
|
this.canvas.setActiveObject(activeSelection);
|
|
|
|
|
|
}
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
// 恢复原活动图层ID
|
|
|
|
|
|
this.activeLayerId.value = this.oldActiveLayerId;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果在选择模式下,恢复取消的所有选择
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas?.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
// 修复:确保对象存在于画布上才激活
|
|
|
|
|
|
if (this.oldActiveObjects && this.oldActiveObjects.length > 0) {
|
|
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
const validObjects = this.oldActiveObjects.filter(
|
|
|
|
|
|
(obj) => obj && canvasObjects.includes(obj)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (validObjects.length > 0) {
|
|
|
|
|
|
if (validObjects.length > 1) {
|
|
|
|
|
|
// 如果有多个对象,需要创建一个活动选择组
|
|
|
|
|
|
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas.setActiveObject(activeSelection);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 只有一个对象时直接设置
|
|
|
|
|
|
this.canvas.setActiveObject(validObjects[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldActiveLayerId: this.oldActiveLayerId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class ClearSelectionCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "设置取消所有图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerManager = options.layerManager || null;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.oldActiveLayerId = this.activeLayerId.value;
|
|
|
|
|
|
this.editorMode = options.editorMode || OperationType.SELECT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
execute() {
|
|
|
|
|
|
this.oldActiveObjects = this.canvas.getActiveObjects();
|
|
|
|
|
|
// 如果在选择模式下,取消所有选择
|
|
|
|
|
|
if (this.editorMode === OperationType.SELECT && this.canvas) {
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
}
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
undo() {
|
|
|
|
|
|
// 恢复原活动图层ID
|
|
|
|
|
|
this.activeLayerId.value = this.oldActiveLayerId;
|
|
|
|
|
|
// 如果在选择模式下,恢复取消的所有选择
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas?.discardActiveObject();
|
|
|
|
|
|
// 修复:确保对象存在于画布上才激活
|
|
|
|
|
|
if (this.oldActiveObjects && this.oldActiveObjects.length > 0) {
|
|
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
const validObjects = this.oldActiveObjects.filter(
|
|
|
|
|
|
(obj) => obj && canvasObjects.includes(obj)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (validObjects.length > 0) {
|
|
|
|
|
|
if (validObjects.length > 1) {
|
|
|
|
|
|
// 如果有多个对象,需要创建一个活动选择组
|
|
|
|
|
|
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas.setActiveObject(activeSelection);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 只有一个对象时直接设置
|
|
|
|
|
|
this.canvas.setActiveObject(validObjects[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
oldActiveLayerId: this.oldActiveLayerId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移动图层到顶部命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MoveLayerToTopCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "置顶图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.layerSort = options.layerSort;
|
|
|
|
|
|
|
|
|
|
|
|
this.parentLayer = null; // 父图层
|
|
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
|
|
|
|
|
|
if (parent?.id) {
|
|
|
|
|
|
// 查找子图层索引
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.layerIndex = parent?.children?.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.parentLayer = parent;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 查找图层索引
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.layerIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
this.originalIndex = this.layerIndex;
|
|
|
|
|
|
// 目标位置
|
|
|
|
|
|
this.targetIndex = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (this.layerIndex === -1 || !this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是背景层或固定层(不允许移动)
|
|
|
|
|
|
if (this.layer.isBackground || this.layer.isFixed) {
|
|
|
|
|
|
console.warn("背景层和固定层不能移动");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经在顶部,无需移动
|
|
|
|
|
|
if (this.layerIndex === this.targetIndex) {
|
|
|
|
|
|
console.log("图层已在顶部位置");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行移动
|
|
|
|
|
|
if (this.parentLayer) {
|
|
|
|
|
|
this.parentLayer?.children?.splice(this.layerIndex, 1);
|
|
|
|
|
|
this.parentLayer?.children?.splice(this.targetIndex, 0, this.layer);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.layers.value.splice(this.layerIndex, 1);
|
|
|
|
|
|
this.layers.value.splice(this.targetIndex, 0, this.layer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用LayerSort工具重新排列画布对象(如果可用)
|
|
|
|
|
|
await this.layerSort?.rearrangeObjects();
|
|
|
|
|
|
console.log(`✅ 图层 ${this.layer.name} 已置顶`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
const tempOldIndex = this.layerIndex;
|
|
|
|
|
|
this.targetIndex = tempOldIndex;
|
|
|
|
|
|
this.layerIndex = this.targetIndex;
|
|
|
|
|
|
await this.execute();
|
|
|
|
|
|
|
|
|
|
|
|
// 执行完成恢复索引
|
|
|
|
|
|
this.layerIndex = tempOldIndex;
|
|
|
|
|
|
this.targetIndex = this.layerIndex;
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`↩️ 图层 ${this.layer.name} 已恢复到原位置`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
layerName: this.layer?.name,
|
|
|
|
|
|
originalIndex: this.originalIndex,
|
|
|
|
|
|
targetIndex: this.targetIndex,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移动图层到底部命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MoveLayerToBottomCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "置底图层",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.layerSort = options.layerSort;
|
|
|
|
|
|
|
|
|
|
|
|
this.parentLayer = null; // 父图层
|
|
|
|
|
|
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const { layer, parent } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
|
|
|
|
|
|
if (parent?.id) {
|
|
|
|
|
|
// 查找子图层索引
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.layerIndex = parent?.children?.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.parentLayer = parent;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 查找图层索引
|
2025-07-21 23:07:47 +08:00
|
|
|
|
this.layerIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
|
this.originalIndex = this.layerIndex;
|
|
|
|
|
|
// 计算目标位置(最后一个非背景、非固定图层的位置)
|
|
|
|
|
|
this.targetIndex = this._calculateBottomPosition();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (this.layerIndex === -1 || !this.layer) {
|
|
|
|
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是背景层或固定层(不允许移动)
|
|
|
|
|
|
if (this.layer.isBackground || this.layer.isFixed) {
|
|
|
|
|
|
console.warn("背景层和固定层不能移动");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经在底部,无需移动
|
|
|
|
|
|
if (this.layerIndex === this.targetIndex) {
|
|
|
|
|
|
console.log("图层已在底部位置");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行移动
|
|
|
|
|
|
this.layers.value.splice(this.layerIndex, 1);
|
|
|
|
|
|
this.layers.value.splice(this.targetIndex, 0, this.layer);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布渲染顺序
|
|
|
|
|
|
this.layerSort?.rearrangeObjects?.();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 图层 ${this.layer.name} 已置底`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (this.originalIndex !== -1 && this.layer) {
|
|
|
|
|
|
// 获取当前位置
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const currentIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.layerId
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (currentIndex !== -1) {
|
|
|
|
|
|
// 移除当前位置
|
|
|
|
|
|
this.layers.value.splice(currentIndex, 1);
|
|
|
|
|
|
// 插入到原位置
|
|
|
|
|
|
this.layers.value.splice(this.originalIndex, 0, this.layer);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布渲染顺序
|
|
|
|
|
|
this._rearrangeCanvasObjects();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`↩️ 图层 ${this.layer.name} 已恢复到原位置`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算置底的目标位置
|
|
|
|
|
|
* @returns {number} 目标索引
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_calculateBottomPosition() {
|
|
|
|
|
|
// 找到最后一个非背景、非固定图层的位置
|
|
|
|
|
|
for (let i = this.layers.value.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const layer = this.layers.value[i];
|
|
|
|
|
|
if (!layer.isBackground && !layer.isFixed) {
|
|
|
|
|
|
return i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return this.layers.value.length - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重新排列画布对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_rearrangeCanvasObjects() {
|
|
|
|
|
|
if (!this.canvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取画布上的所有对象
|
|
|
|
|
|
const canvasObjects = [...this.canvas.getObjects()];
|
|
|
|
|
|
|
|
|
|
|
|
// 清空画布
|
|
|
|
|
|
this.canvas.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// 按图层顺序(从底到顶)重新添加对象
|
|
|
|
|
|
for (let i = this.layers.value.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const layer = this.layers.value[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (layer.isBackground && layer.fabricObject) {
|
|
|
|
|
|
// 背景图层
|
2025-07-21 23:07:47 +08:00
|
|
|
|
const originalObj = canvasObjects.find(
|
|
|
|
|
|
(o) => o.id === layer.fabricObject.id
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (originalObj) {
|
|
|
|
|
|
this.canvas.add(originalObj);
|
|
|
|
|
|
}
|
2025-07-21 23:07:47 +08:00
|
|
|
|
} else if (
|
|
|
|
|
|
Array.isArray(layer.fabricObjects) &&
|
|
|
|
|
|
layer.fabricObjects.length > 0
|
|
|
|
|
|
) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 普通图层和固定图层
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
|
|
|
|
|
if (originalObj) {
|
|
|
|
|
|
this.canvas.add(originalObj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
layerId: this.layerId,
|
|
|
|
|
|
layerName: this.layer?.name,
|
|
|
|
|
|
originalIndex: this.originalIndex,
|
|
|
|
|
|
targetIndex: this.targetIndex,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|