943 lines
24 KiB
JavaScript
943 lines
24 KiB
JavaScript
|
|
import { OperationType } from "../utils/layerHelper";
|
|||
|
|
import { Command } from "./Command";
|
|||
|
|
import { generateId } from "../utils/helper";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置活动图层命令
|
|||
|
|
*/
|
|||
|
|
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;
|
|||
|
|
this.oldActiveLayerId = this.activeLayerId.value;
|
|||
|
|
this.layerManager = options.layerManager;
|
|||
|
|
this.oldActiveObjects = [];
|
|||
|
|
this.newLayer = null;
|
|||
|
|
this.editorMode = options.editorMode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
execute() {
|
|||
|
|
this.newLayer = this.layers.value.find(
|
|||
|
|
(layer) => layer.id === this.layerId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
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();
|
|||
|
|
|
|||
|
|
// 设置为新的图层下的对象为激活,但需要确保对象存在于画布上
|
|||
|
|
if (
|
|||
|
|
this.newLayer.fabricObjects &&
|
|||
|
|
this.newLayer.fabricObjects.length > 0
|
|||
|
|
) {
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
// 保存对象原始状态和ID
|
|||
|
|
this.objectId =
|
|||
|
|
this.fabricObject.id ||
|
|||
|
|
`obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|||
|
|
this.originalObjectState = this.fabricObject.toObject([
|
|||
|
|
"id",
|
|||
|
|
"layerId",
|
|||
|
|
"layerName",
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
execute() {
|
|||
|
|
// 查找目标图层
|
|||
|
|
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
|||
|
|
|
|||
|
|
if (!layer) {
|
|||
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是背景层,不允许添加对象
|
|||
|
|
if (layer.isBackground) {
|
|||
|
|
console.warn("不能向背景层添加对象");
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 为对象生成唯一ID
|
|||
|
|
this.fabricObject.id = this.objectId;
|
|||
|
|
|
|||
|
|
// 设置对象与图层的关联
|
|||
|
|
this.fabricObject.layerId = this.layerId;
|
|||
|
|
this.fabricObject.layerName = layer.name;
|
|||
|
|
|
|||
|
|
// 设置对象可操作可选择
|
|||
|
|
this.fabricObject.selectable = true;
|
|||
|
|
this.fabricObject.evented = true;
|
|||
|
|
|
|||
|
|
// 将对象添加到画布
|
|||
|
|
this.canvas.add(this.fabricObject);
|
|||
|
|
|
|||
|
|
// 将对象添加到图层的fabricObjects数组
|
|||
|
|
layer.fabricObjects = layer.fabricObjects || [];
|
|||
|
|
layer.fabricObjects.push(this.fabricObject);
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新画布
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
|
|||
|
|
return this.fabricObject;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
undo() {
|
|||
|
|
// 查找图层
|
|||
|
|
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
|||
|
|
|
|||
|
|
if (!layer) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从图层的fabricObjects数组中移除对象
|
|||
|
|
if (layer.fabricObjects) {
|
|||
|
|
layer.fabricObjects = layer.fabricObjects.filter(
|
|||
|
|
(obj) => obj.id !== this.objectId
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
// 从画布移除对象
|
|||
|
|
const object = this.canvas
|
|||
|
|
.getObjects()
|
|||
|
|
.find((obj) => obj.id === this.objectId);
|
|||
|
|
if (object) {
|
|||
|
|
// 先丢弃活动对象,避免控制点渲染错误
|
|||
|
|
this.canvas.discardActiveObject();
|
|||
|
|
this.canvas.remove(object);
|
|||
|
|
// 更新画布
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getInfo() {
|
|||
|
|
return {
|
|||
|
|
name: this.name,
|
|||
|
|
layerId: this.layerId,
|
|||
|
|
objectId: this.objectId,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从图层中移除对象命令
|
|||
|
|
*/
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查找图层
|
|||
|
|
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
|||
|
|
if (!layer) {
|
|||
|
|
console.error(`图层 ${this.layerId} 不存在`);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从画布移除对象
|
|||
|
|
this.canvas.remove(this.object);
|
|||
|
|
|
|||
|
|
// 从图层的fabricObjects数组移除对象
|
|||
|
|
if (layer.fabricObjects) {
|
|||
|
|
layer.fabricObjects = layer.fabricObjects.filter(
|
|||
|
|
(obj) => obj.id !== this.objectId
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新画布
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
undo() {
|
|||
|
|
if (!this.objectData || !this.layerId) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查找图层
|
|||
|
|
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
|||
|
|
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;
|
|||
|
|
this.targetLayerType = options.targetLayerType || "background"; // '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.targetLayer = null;
|
|||
|
|
this.isExecuted = false;
|
|||
|
|
|
|||
|
|
// 错误处理
|
|||
|
|
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.targetLayerType}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存当前状态用于回滚
|
|||
|
|
await this.saveCurrentState();
|
|||
|
|
|
|||
|
|
// 加载新图像
|
|||
|
|
const newImage = await this.loadImageWithRetry();
|
|||
|
|
|
|||
|
|
// 应用图像到图层
|
|||
|
|
await this.applyImageToLayer(newImage);
|
|||
|
|
|
|||
|
|
this.isExecuted = true;
|
|||
|
|
|
|||
|
|
// 触发成功事件
|
|||
|
|
this.emitEvent("image:changed", {
|
|||
|
|
layerId: this.targetLayer.id,
|
|||
|
|
newImageUrl: this.imageUrl,
|
|||
|
|
command: this,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
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 {
|
|||
|
|
if (this.previousImage) {
|
|||
|
|
// 恢复之前的图像
|
|||
|
|
await this.restorePreviousImage();
|
|||
|
|
} else {
|
|||
|
|
// 如果没有之前的图像,移除当前图像
|
|||
|
|
await this.removeCurrentImage();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isExecuted = false;
|
|||
|
|
|
|||
|
|
// 触发撤销事件
|
|||
|
|
this.emitEvent("image:reverted", {
|
|||
|
|
layerId: this.targetLayer.id,
|
|||
|
|
command: this,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
action: "reverted",
|
|||
|
|
layerId: this.targetLayer.id,
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error("ChangeFixedImageCommand撤销失败:", 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是必需的");
|
|||
|
|
|
|||
|
|
// 验证URL格式
|
|||
|
|
try {
|
|||
|
|
new URL(this.imageUrl);
|
|||
|
|
} catch {
|
|||
|
|
throw new Error("无效的图像URL格式");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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() {
|
|||
|
|
if (!this.targetLayer.fabricObject) return;
|
|||
|
|
|
|||
|
|
const currentObj = this.targetLayer.fabricObject;
|
|||
|
|
|
|||
|
|
// 保存当前图像URL(如果存在)
|
|||
|
|
this.previousImage = {
|
|||
|
|
url: currentObj.getSrc ? currentObj.getSrc() : null,
|
|||
|
|
element: currentObj._element ? currentObj._element.cloneNode() : null,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存变换状态
|
|||
|
|
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,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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) {
|
|||
|
|
const currentObj = this.targetLayer.fabricObject;
|
|||
|
|
|
|||
|
|
// 设置基本属性
|
|||
|
|
newImage.set({
|
|||
|
|
id: currentObj?.id || generateId(),
|
|||
|
|
layerId: this.targetLayer.id,
|
|||
|
|
layerName: this.targetLayer.name,
|
|||
|
|
isBackground: this.targetLayer.isBackground,
|
|||
|
|
isFixed: this.targetLayer.isFixed,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 应用位置和变换
|
|||
|
|
if (this.preserveTransform && this.previousTransform) {
|
|||
|
|
newImage.set(this.previousTransform);
|
|||
|
|
} else {
|
|||
|
|
newImage.set({
|
|||
|
|
left: this.position.x,
|
|||
|
|
top: this.position.y,
|
|||
|
|
scaleX: this.scale.x,
|
|||
|
|
scaleY: this.scale.y,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移除旧对象(如果存在)
|
|||
|
|
if (currentObj) {
|
|||
|
|
this.canvas.remove(currentObj);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加新图像
|
|||
|
|
this.canvas.add(newImage);
|
|||
|
|
newImage.setCoords();
|
|||
|
|
|
|||
|
|
// 更新图层引用
|
|||
|
|
this.targetLayer.fabricObject = newImage;
|
|||
|
|
|
|||
|
|
// 更新图层管理器
|
|||
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, newImage);
|
|||
|
|
|
|||
|
|
// 重新渲染画布
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async restorePreviousImage() {
|
|||
|
|
if (!this.previousImage.url) return;
|
|||
|
|
|
|||
|
|
const restoredImage = await this.loadImageFromUrl(this.previousImage.url);
|
|||
|
|
|
|||
|
|
// 恢复之前的变换
|
|||
|
|
if (this.previousTransform) {
|
|||
|
|
restoredImage.set(this.previousTransform);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置图层属性
|
|||
|
|
restoredImage.set({
|
|||
|
|
id: this.targetLayer.fabricObject?.id || generateId(),
|
|||
|
|
layerId: this.targetLayer.id,
|
|||
|
|
layerName: this.targetLayer.name,
|
|||
|
|
isBackground: this.targetLayer.isBackground,
|
|||
|
|
isFixed: this.targetLayer.isFixed,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 替换当前对象
|
|||
|
|
if (this.targetLayer.fabricObject) {
|
|||
|
|
this.canvas.remove(this.targetLayer.fabricObject);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.canvas.add(restoredImage);
|
|||
|
|
restoredImage.setCoords();
|
|||
|
|
|
|||
|
|
// 更新引用
|
|||
|
|
this.targetLayer.fabricObject = restoredImage;
|
|||
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, restoredImage);
|
|||
|
|
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async removeCurrentImage() {
|
|||
|
|
if (this.targetLayer.fabricObject) {
|
|||
|
|
this.canvas.remove(this.targetLayer.fabricObject);
|
|||
|
|
this.targetLayer.fabricObject = null;
|
|||
|
|
this.layerManager.updateLayerObject(this.targetLayer.id, null);
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
loadImageFromUrl(url) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
fabric.Image.fromURL(
|
|||
|
|
url,
|
|||
|
|
(img) => {
|
|||
|
|
if (!img || !img.getElement()) {
|
|||
|
|
reject(new Error("恢复图像加载失败"));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
resolve(img);
|
|||
|
|
},
|
|||
|
|
{ crossOrigin: "anonymous" }
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
// 错误处理
|
|||
|
|
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}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查图层是否可编辑
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取命令信息用于调试
|
|||
|
|
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,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|