2025-07-14 23:42:28 +08:00
|
|
|
|
import { BackgroundFillManager } from "./BackgroundFillManager";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
import {
|
|
|
|
|
|
AddLayerCommand,
|
|
|
|
|
|
PasteLayerCommand,
|
|
|
|
|
|
RemoveLayerCommand,
|
|
|
|
|
|
MoveLayerCommand,
|
|
|
|
|
|
ToggleLayerVisibilityCommand,
|
|
|
|
|
|
RenameLayerCommand,
|
|
|
|
|
|
LayerLockCommand,
|
|
|
|
|
|
SetLayerOpacityCommand,
|
|
|
|
|
|
SetLayerBlendModeCommand,
|
|
|
|
|
|
MergeLayersCommand,
|
|
|
|
|
|
GroupLayersCommand,
|
|
|
|
|
|
UngroupLayersCommand,
|
|
|
|
|
|
MergeLayerObjectsCommand,
|
|
|
|
|
|
LayerObjectsToGroupCommand,
|
|
|
|
|
|
ReorderLayersCommand,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
ToggleChildLayerVisibilityCommand,
|
|
|
|
|
|
RenameChildLayerCommand,
|
|
|
|
|
|
RemoveChildLayerCommand,
|
|
|
|
|
|
ChildLayerLockCommand,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} from "../commands/LayerCommands";
|
|
|
|
|
|
import {
|
|
|
|
|
|
SetActiveLayerCommand,
|
|
|
|
|
|
AddObjectToLayerCommand,
|
|
|
|
|
|
RemoveObjectFromLayerCommand,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
SelectAllLayersCommand,
|
|
|
|
|
|
ClearSelectionCommand,
|
|
|
|
|
|
MoveLayerToTopCommand,
|
|
|
|
|
|
MoveLayerToBottomCommand,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} from "../commands/ObjectLayerCommands";
|
|
|
|
|
|
import {
|
|
|
|
|
|
LayerType,
|
|
|
|
|
|
BlendMode,
|
|
|
|
|
|
createLayer,
|
|
|
|
|
|
createBackgroundLayer,
|
|
|
|
|
|
createFixedLayer,
|
|
|
|
|
|
OperationType,
|
|
|
|
|
|
OperationTypes,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
findLayerRecursively,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} from "../utils/layerHelper";
|
|
|
|
|
|
import {
|
|
|
|
|
|
CreateBackgroundLayerCommand,
|
|
|
|
|
|
UpdateBackgroundCommand,
|
|
|
|
|
|
BackgroundSizeCommand,
|
|
|
|
|
|
BackgroundSizeWithScaleCommand,
|
|
|
|
|
|
} from "../commands/BackgroundCommands";
|
2025-06-22 13:52:28 +08:00
|
|
|
|
import { MergeGroupLayerCommand } from "../commands/GroupCommands";
|
2025-06-23 15:56:01 +08:00
|
|
|
|
import {
|
|
|
|
|
|
ExportLayerToImageCommand,
|
|
|
|
|
|
RasterizeLayerCommand,
|
|
|
|
|
|
} from "../commands/RasterizeLayerCommand";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 导入图层排序相关类和混入
|
2025-07-14 01:00:23 +08:00
|
|
|
|
import { LayerSort, createLayerSort, LayerSortMixin, LayerSortUtils } from "../utils/LayerSort";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
import CanvasConfig from "../config/canvasConfig";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import { isBoolean, template } from "lodash-es";
|
2025-07-14 01:00:23 +08:00
|
|
|
|
import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import { message } from "ant-design-vue";
|
2025-06-22 13:52:28 +08:00
|
|
|
|
import { fabric } from "fabric-with-all";
|
2025-07-10 01:01:46 +08:00
|
|
|
|
import { getOriginObjectInfo } from "../utils/layerUtils";
|
|
|
|
|
|
import { restoreFabricObject } from "../utils/objectHelper";
|
2025-07-14 01:00:23 +08:00
|
|
|
|
import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 图层管理器 - 负责管理画布上的所有图层
|
|
|
|
|
|
* 包含图层的创建、删除、修改、排序等操作
|
|
|
|
|
|
* 现在统一使用命令管理器进行状态管理
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 集成LayerSort高级排序功能
|
2025-06-09 10:25:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export class LayerManager {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建图层管理器
|
|
|
|
|
|
* @param {Object} options 配置选项
|
|
|
|
|
|
* @param {Object} options.canvas fabric.js画布实例
|
|
|
|
|
|
* @param {Object} options.layers 图层数组响应式引用
|
|
|
|
|
|
* @param {Object} options.activeLayerId 当前激活图层ID响应式引用
|
|
|
|
|
|
* @param {Object} options.commandManager 命令管理器
|
|
|
|
|
|
* @param {Object} options.canvasManager 画布管理器
|
|
|
|
|
|
* @param {String} options.editorMode 编辑器模式
|
|
|
|
|
|
* @param {Number} options.canvasWidth 画布宽度
|
|
|
|
|
|
* @param {Number} options.canvasHeight 画布高度
|
|
|
|
|
|
* @param {String} options.backgroundColor 背景颜色
|
|
|
|
|
|
*/
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.activeLayerId = options.activeLayerId;
|
|
|
|
|
|
this.commandManager = options.commandManager;
|
|
|
|
|
|
this.canvasManager = options.canvasManager || null;
|
|
|
|
|
|
|
2025-07-14 23:42:28 +08:00
|
|
|
|
this.backgroundFillManager = new BackgroundFillManager({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
commandManager: this.commandManager,
|
|
|
|
|
|
canvasManager: this.canvasManager,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 编辑器模式:draw(绘画)、select(选择)、pan(拖拽)
|
|
|
|
|
|
this.editorMode = options.editorMode || CanvasConfig.defaultTool;
|
|
|
|
|
|
|
|
|
|
|
|
// 画布尺寸
|
|
|
|
|
|
this.canvasWidth = options.canvasWidth || 800;
|
|
|
|
|
|
this.canvasHeight = options.canvasHeight || 600;
|
|
|
|
|
|
|
|
|
|
|
|
// 默认背景颜色
|
2025-06-29 23:29:47 +08:00
|
|
|
|
this.backgroundColor = options.backgroundColor || { value: "#ffffff" };
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 复制粘贴相关
|
|
|
|
|
|
this.clipboardData = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 操作状态标志,用于控制状态保存
|
|
|
|
|
|
this.isExecutingCommand = false;
|
|
|
|
|
|
// 是否正在执行操作
|
|
|
|
|
|
this.operationInProgress = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 红绿图模式相关
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.isRedGreenMode = options.isRedGreenMode || false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.redGreenModeManager = null;
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 初始化图层排序工具
|
|
|
|
|
|
this.layerSort = null;
|
|
|
|
|
|
this.initLayerSort();
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 初始化相关命令
|
|
|
|
|
|
this.initCommandManager();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化命令管理器
|
|
|
|
|
|
* 现在直接使用命令类,不再需要注册命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
initCommandManager() {
|
|
|
|
|
|
// 命令注册逻辑已移除,现在直接使用命令类实例化和执行
|
|
|
|
|
|
console.log("CommandManager 已初始化,使用直接命令调用模式");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化图层排序工具
|
|
|
|
|
|
* 创建LayerSort实例并绑定到LayerManager
|
|
|
|
|
|
*/
|
|
|
|
|
|
initLayerSort() {
|
|
|
|
|
|
if (this.canvas && this.layers) {
|
|
|
|
|
|
this.layerSort = createLayerSort(this.canvas, this.layers, {
|
|
|
|
|
|
commandManager: this.commandManager,
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log("图层排序工具已初始化");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 设置编辑器模式
|
|
|
|
|
|
* @param {string} mode 'draw'、'select'或'pan'
|
|
|
|
|
|
*/
|
|
|
|
|
|
toolChanged(mode) {
|
|
|
|
|
|
if (!OperationTypes.includes(mode)) {
|
|
|
|
|
|
console.warn(`不支持的编辑器模式: ${mode}`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.editorMode = mode;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新所有对象的交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`已切换到${mode}模式`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setToolManager(toolManager) {
|
|
|
|
|
|
this.toolManager = toolManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新所有画布对象的交互性
|
|
|
|
|
|
* 根据当前编辑模式和图层状态设置对象的交互属性
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async updateLayersObjectsInteractivity(isUseOptimize = true) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.canvas) return;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (isUseOptimize) {
|
|
|
|
|
|
// 优化渲染 - 统一批处理 支持异步回调
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 应用图层交互规则
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this._applyInteractionRules();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 直接应用图层交互规则
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this._applyInteractionRules();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// // 性能优化:使用requestAnimationFrame
|
|
|
|
|
|
// requestAnimationFrame(() => {
|
|
|
|
|
|
// // 暂停渲染以提高性能
|
|
|
|
|
|
// this.canvas.skipTargetFind = true;
|
|
|
|
|
|
// // this.canvas.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
// // 暂停实时渲染和对象查找
|
|
|
|
|
|
// const wasRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
|
|
|
|
|
// this.canvas.renderOnAddRemove = false;
|
|
|
|
|
|
|
|
|
|
|
|
// // 应用图层交互规则
|
|
|
|
|
|
// this._applyInteractionRules();
|
|
|
|
|
|
|
|
|
|
|
|
// // 恢复渲染设置
|
|
|
|
|
|
// this.canvas.renderOnAddRemove = wasRenderOnAddRemove;
|
|
|
|
|
|
// this.canvas.skipTargetFind = false;
|
|
|
|
|
|
// // this.canvas.renderAll();
|
|
|
|
|
|
// this.canvas.renderAll(); // 确保画布重新渲染 - 同步渲染
|
|
|
|
|
|
// });
|
|
|
|
|
|
}
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async _setObjectInteractivity(obj, layer, editorMode) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 设置可见性
|
|
|
|
|
|
obj.visible = layer.visible;
|
|
|
|
|
|
|
|
|
|
|
|
// 判断对象是否在当前活动图层上
|
|
|
|
|
|
const isInActiveLayer = obj.layerId === this.activeLayerId.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
|
|
|
|
|
|
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
|
|
|
|
|
|
obj.erasable =
|
2025-07-14 01:00:23 +08:00
|
|
|
|
isInActiveLayer && layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 图层状态决定交互性
|
|
|
|
|
|
if (layer.isBackground || obj.isBackground || layer.isFixed) {
|
2025-06-24 01:54:37 +08:00
|
|
|
|
//|| layer.isFixed
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 背景层永远不可选择和擦除
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = false;
|
2025-06-24 01:54:37 +08:00
|
|
|
|
obj.erasable = !layer.locked;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
} else if (layer.locked) {
|
|
|
|
|
|
// 锁定图层不可交互和擦除
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = false;
|
|
|
|
|
|
obj.erasable = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 根据编辑模式设置交互性
|
|
|
|
|
|
switch (editorMode) {
|
|
|
|
|
|
case OperationType.SELECT:
|
|
|
|
|
|
obj.selectable = true;
|
|
|
|
|
|
obj.evented = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OperationType.ERASER:
|
|
|
|
|
|
// 橡皮擦模式:利用 fabric-with-erasing 的内置机制
|
|
|
|
|
|
// 只需要设置 erasable 属性,库会自动处理擦除逻辑
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = true; // 需要设置为 true 以接收鼠标事件
|
|
|
|
|
|
// erasable 已在上面根据图层状态设置
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OperationType.DRAW:
|
|
|
|
|
|
case OperationType.EYEDROPPER:
|
|
|
|
|
|
case OperationType.PAN:
|
|
|
|
|
|
case OperationType.WAVE:
|
|
|
|
|
|
case OperationType.LIQUIFY:
|
|
|
|
|
|
case OperationType.LASSO:
|
|
|
|
|
|
case OperationType.LASSO_RECTANGLE:
|
|
|
|
|
|
case OperationType.AREA_CUSTOM:
|
|
|
|
|
|
case OperationType.AREA_RECTANGLE:
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = false;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (this.isRedGreenMode) {
|
|
|
|
|
|
// 红绿图模式下 所有普通图层都可擦除
|
2025-07-14 01:00:23 +08:00
|
|
|
|
obj.erasable = layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 平移模式下,禁用多选和擦除
|
|
|
|
|
|
if (editorMode === OperationType.PAN) {
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = false;
|
|
|
|
|
|
obj.erasable = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 应用图层视觉属性
|
|
|
|
|
|
if (layer.opacity !== undefined) obj.opacity = layer.opacity;
|
|
|
|
|
|
if (layer.blendMode) obj.globalCompositeOperation = layer.blendMode;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:应用交互规则
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async _applyInteractionRules() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.log("updateLayersObjectsInteractivity ===>", this.editorMode);
|
|
|
|
|
|
const objects = this.canvas.getObjects();
|
|
|
|
|
|
const editorMode = this.editorMode || CanvasConfig.defaultTool;
|
|
|
|
|
|
const layers = this.layers?.value || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 创建缓存以避免重复查找
|
|
|
|
|
|
const layerMap = {};
|
|
|
|
|
|
layers.forEach((layer) => {
|
|
|
|
|
|
layerMap[layer.id] = layer;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layers?.children?.forEach((childLayer) => {
|
|
|
|
|
|
layerMap[childLayer.id] = childLayer;
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 批量更新对象
|
2025-06-22 13:52:28 +08:00
|
|
|
|
objects.forEach(async (obj) => {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const layer = layerMap[obj.layerId];
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!obj.layerId) {
|
|
|
|
|
|
// 没有关联图层的对象使用默认设置
|
|
|
|
|
|
obj.selectable = false;
|
|
|
|
|
|
obj.evented = false;
|
|
|
|
|
|
obj.erasable = false; // 未关联图层的对象不可擦除
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!layer) return;
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 设置一级图层对象的交互性
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this._setObjectInteractivity(obj, layer, editorMode);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 设置子图层对象的交互性
|
2025-06-22 13:52:28 +08:00
|
|
|
|
layer?.childLayer?.forEach(async (childLayer) => {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (childObj) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this._setObjectInteractivity(childObj, childLayer, editorMode);
|
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-30 18:05:20 +08:00
|
|
|
|
|
|
|
|
|
|
// 设置裁剪对象
|
2025-07-10 01:01:46 +08:00
|
|
|
|
layers.forEach(async (layer) => {
|
2025-07-11 00:26:38 +08:00
|
|
|
|
let clippingMaskFabricObject = null;
|
2025-06-30 18:05:20 +08:00
|
|
|
|
if (layer.clippingMask) {
|
2025-07-10 01:01:46 +08:00
|
|
|
|
// 反序列化 clippingMask
|
2025-07-14 01:00:23 +08:00
|
|
|
|
clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
|
2025-07-10 01:01:46 +08:00
|
|
|
|
clippingMaskFabricObject.clipPath = null;
|
|
|
|
|
|
|
|
|
|
|
|
clippingMaskFabricObject.set({
|
|
|
|
|
|
// 设置绝对定位
|
|
|
|
|
|
// ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位
|
|
|
|
|
|
absolutePositioned: true,
|
|
|
|
|
|
});
|
2025-07-11 00:26:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 如果是组图层 则给所有子对象设置裁剪对象
|
|
|
|
|
|
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
2025-07-16 11:35:52 +08:00
|
|
|
|
// if (layer.fill) {
|
|
|
|
|
|
// const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id);
|
|
|
|
|
|
// if (fabricObject) {
|
|
|
|
|
|
// fabricObject.clipPath = clippingMaskFabricObject;
|
|
|
|
|
|
// fabricObject.dirty = true; // 标记为脏对象
|
|
|
|
|
|
// fabricObject.setCoords();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2025-07-11 00:26:38 +08:00
|
|
|
|
layer.children.forEach((childLayer) => {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
2025-07-11 00:26:38 +08:00
|
|
|
|
if (childObj) {
|
|
|
|
|
|
childObj.clipPath = clippingMaskFabricObject;
|
|
|
|
|
|
childObj.dirty = true; // 标记为脏对象
|
|
|
|
|
|
childObj.setCoords();
|
|
|
|
|
|
}
|
2025-07-14 23:42:28 +08:00
|
|
|
|
|
2025-07-16 11:35:52 +08:00
|
|
|
|
// if (childLayer.fill) {
|
|
|
|
|
|
// const fabricObject = this.canvas.getObjects().find((o) => o.id === childLayer.fill.id);
|
|
|
|
|
|
// if (fabricObject) {
|
|
|
|
|
|
// fabricObject.clipPath = clippingMaskFabricObject;
|
|
|
|
|
|
// fabricObject.dirty = true; // 标记为脏对象
|
|
|
|
|
|
// fabricObject.setCoords();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2025-07-11 00:26:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
layer.fabricObjects?.forEach((obj) => {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
|
2025-07-11 00:26:38 +08:00
|
|
|
|
if (fabricObject) {
|
|
|
|
|
|
fabricObject.clipPath = clippingMaskFabricObject;
|
|
|
|
|
|
fabricObject.dirty = true; // 标记为脏对象
|
|
|
|
|
|
fabricObject.setCoords();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-07-14 23:42:28 +08:00
|
|
|
|
|
2025-07-16 11:35:52 +08:00
|
|
|
|
// if (layer.fill) {
|
|
|
|
|
|
// const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id);
|
|
|
|
|
|
// if (fabricObject) {
|
|
|
|
|
|
// fabricObject.clipPath = clippingMaskFabricObject;
|
|
|
|
|
|
// fabricObject.dirty = true; // 标记为脏对象
|
|
|
|
|
|
// fabricObject.setCoords();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2025-06-30 18:05:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-07-14 23:42:28 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 填充图层背景
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @param {string} fillColor 填充颜色
|
2025-07-16 11:35:52 +08:00
|
|
|
|
* @param {boolean} undoable 是否可撤销
|
2025-07-14 23:42:28 +08:00
|
|
|
|
*/
|
2025-07-16 11:35:52 +08:00
|
|
|
|
async fillLayerBackground(layerId, fillColor, undoable = true) {
|
|
|
|
|
|
await this.backgroundFillManager.fillLayerBackground(layerId, fillColor, undoable);
|
2025-07-14 23:42:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 创建新图层
|
|
|
|
|
|
* @param {string} name 图层名称
|
|
|
|
|
|
* @param {string} type 图层类型
|
|
|
|
|
|
* @param {Object} options 额外选项
|
|
|
|
|
|
* @returns {string} 新创建的图层ID
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async createLayer(name = null, type = LayerType.EMPTY, options = {}) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 生成唯一ID
|
2025-07-04 03:16:18 +08:00
|
|
|
|
const layerId = options.id || options.layerId || generateId("layer_");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
// 计算普通图层数量(非背景、非固定)
|
|
|
|
|
|
const normalLayersCount = this.layers.value.filter(
|
|
|
|
|
|
(layer) => !layer.isBackground && !layer.isFixed
|
|
|
|
|
|
).length;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 计算插入位置,如果没有指定insertIndex,则根据当前选中图层决定插入位置
|
|
|
|
|
|
// 添加到图层列表
|
2025-07-04 03:16:18 +08:00
|
|
|
|
// let insertIndex = this._getInsertIndexAboveActiveLayer();
|
|
|
|
|
|
// if (options.insertIndex !== undefined) {
|
|
|
|
|
|
// insertIndex = options.insertIndex;
|
|
|
|
|
|
// }
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 创建新图层
|
|
|
|
|
|
const newLayer = createLayer({
|
|
|
|
|
|
id: layerId,
|
2025-07-04 03:16:18 +08:00
|
|
|
|
name: name || `图层 ${normalLayersCount + 1}`,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
type: type,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
blendMode: BlendMode.NORMAL,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
...options,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new AddLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
newLayer: newLayer,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
2025-07-04 03:16:18 +08:00
|
|
|
|
options,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果有额外选项,设置命令的undoable属性
|
|
|
|
|
|
command.undoable = options.undoable;
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 如果是第一个图层,或者普通图层数量小于等于3,设置为不可撤销
|
2025-06-30 18:05:20 +08:00
|
|
|
|
if (normalLayersCount < 1) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
command.undoable = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this.commandManager.execute(command);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await command.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return layerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建背景图层
|
|
|
|
|
|
* @param {string} name 图层名称
|
|
|
|
|
|
* @returns {string} 创建的背景层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
createBackgroundLayer(name = "背景") {
|
|
|
|
|
|
// 检查是否已有背景图层
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (hasBackgroundLayer) {
|
|
|
|
|
|
console.warn("已存在背景层,不再创建新的背景层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建背景图层
|
|
|
|
|
|
const bgLayer = createBackgroundLayer({
|
|
|
|
|
|
name: name,
|
|
|
|
|
|
canvasWidth: this.canvasWidth,
|
|
|
|
|
|
canvasHeight: this.canvasHeight,
|
2025-06-29 23:29:47 +08:00
|
|
|
|
backgroundColor: this.backgroundColor.value,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new CreateBackgroundLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
canvasManager: this.canvasManager,
|
|
|
|
|
|
backgroundLayer: bgLayer,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 背景图层设置为不可撤销
|
|
|
|
|
|
command.undoable = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回创建的背景层ID
|
|
|
|
|
|
return bgLayer.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建固定图层 - 位于背景图层之上,普通图层之下
|
|
|
|
|
|
* @param {string} name 图层名称
|
|
|
|
|
|
* @returns {string} 创建的固定图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
createFixedLayer(name = "固定图层") {
|
|
|
|
|
|
// 检查是否已有固定图层
|
|
|
|
|
|
const hasFixedLayer = this.layers.value.some((layer) => layer.isFixed);
|
|
|
|
|
|
|
|
|
|
|
|
if (hasFixedLayer) {
|
|
|
|
|
|
console.warn("已存在固定图层,不再创建新的固定图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成唯一ID
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const layerId = `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 创建固定图层
|
|
|
|
|
|
const fixedLayer = createFixedLayer({
|
|
|
|
|
|
id: layerId,
|
|
|
|
|
|
name: name,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new AddLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
newLayer: fixedLayer,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
insertIndex: this._getFixedLayerInsertionIndex(),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 固定图层设置为不可撤销
|
|
|
|
|
|
command.undoable = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return layerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取应该插入固定图层的位置索引(在背景图层之上)
|
|
|
|
|
|
* @private
|
|
|
|
|
|
* @returns {Number} 插入索引
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getFixedLayerInsertionIndex() {
|
|
|
|
|
|
// 找到背景图层的位置
|
|
|
|
|
|
const bgIndex = this.layers.value.findIndex((layer) => layer.isBackground);
|
|
|
|
|
|
if (bgIndex !== -1) {
|
|
|
|
|
|
return bgIndex; // 插入到背景图层之前(在数组中这意味着位于背景图层之上)
|
|
|
|
|
|
}
|
|
|
|
|
|
return this.layers.value.length; // 如果没有背景图层,则添加到最后
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化图层,确保有背景层、固定图层和一个空白图层
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async initializeLayers() {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 如果没有任何图层,创建背景层、固定图层和一个空白图层
|
|
|
|
|
|
if (this.layers.value.length === 0) {
|
|
|
|
|
|
// 创建背景图层
|
|
|
|
|
|
this.createBackgroundLayer();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建固定图层,位于背景图层之上
|
|
|
|
|
|
this.createFixedLayer();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个空白图层(默认位于背景图层和固定图层之上)
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this.createLayer("图层 1");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 检查是否已有背景层
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (!hasBackgroundLayer) {
|
|
|
|
|
|
this.createBackgroundLayer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已有固定图层
|
|
|
|
|
|
const hasFixedLayer = this.layers.value.some((layer) => layer.isFixed);
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasFixedLayer) {
|
|
|
|
|
|
this.createFixedLayer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否至少有一个普通图层(非背景、非固定)
|
|
|
|
|
|
const hasNormalLayer = this.layers.value.some(
|
|
|
|
|
|
(layer) => !layer.isBackground && !layer.isFixed
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasNormalLayer) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
await this.createLayer("图层 1");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 排序图层
|
|
|
|
|
|
this.sortLayers();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 添加对象到图层
|
|
|
|
|
|
* @param {Object} fabricObject fabric对象
|
|
|
|
|
|
* @param {string} layerId 目标图层ID,如果不提供则使用当前活动图层
|
|
|
|
|
|
* @returns {Object} 添加的对象
|
|
|
|
|
|
*/
|
2025-06-26 00:37:07 +08:00
|
|
|
|
addObjectToLayer(fabricObject, layerId = null, options = {}) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const targetLayerId = layerId || this.activeLayerId.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有指定图层ID,也没有活动图层,则返回错误
|
|
|
|
|
|
if (!targetLayerId) {
|
|
|
|
|
|
console.warn("没有指定目标图层ID且没有活动图层,无法添加对象");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证目标图层是否存在
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!targetLayer) {
|
|
|
|
|
|
console.error(`目标图层 ${targetLayerId} 不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new AddObjectToLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: targetLayerId,
|
|
|
|
|
|
fabricObject: fabricObject,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerManager: this,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// 设置命令的撤销状态
|
|
|
|
|
|
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return fabricObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 向固定图层添加对象
|
|
|
|
|
|
* @param {Object} fabricObject fabric对象
|
|
|
|
|
|
* @returns {Object|null} 添加的对象或null(如果添加失败)
|
|
|
|
|
|
*/
|
|
|
|
|
|
addObjectToFixedLayer(fabricObject) {
|
|
|
|
|
|
// 查找固定图层
|
|
|
|
|
|
const fixedLayer = this.layers.value.find((layer) => layer.isFixed);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有固定图层,则创建一个
|
|
|
|
|
|
if (!fixedLayer) {
|
|
|
|
|
|
const fixedLayerId = this.createFixedLayer();
|
|
|
|
|
|
return this.addObjectToLayer(fabricObject, fixedLayerId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加对象到固定图层
|
|
|
|
|
|
return this.addObjectToLayer(fabricObject, fixedLayer.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从图层中移除对象
|
|
|
|
|
|
* @param {string|Object} objectOrId 要移除的对象或其ID
|
|
|
|
|
|
* @returns {boolean} 是否移除成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
removeObjectFromLayer(objectOrId) {
|
|
|
|
|
|
// 获取对象ID
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const objectId = typeof objectOrId === "string" ? objectOrId : objectOrId.id;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (!objectId) {
|
|
|
|
|
|
console.error("无效的对象ID");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new RemoveObjectFromLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
objectId: objectId,
|
|
|
|
|
|
objectOrId: objectOrId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
return this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置活动图层
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
setActiveLayer(layerId, options = {}) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (layerId === this.activeLayerId.value) {
|
|
|
|
|
|
console.warn("当前图层已是活动图层,无需重复设置");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new SetActiveLayerCommand({
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
layerId: layerId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
parentId: options?.parentId,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
editorMode: this.editorMode,
|
|
|
|
|
|
...options,
|
|
|
|
|
|
});
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果有额外选项,设置命令的undoable属性
|
|
|
|
|
|
command.undoable = !!options.undoable;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前活动图层
|
|
|
|
|
|
* @return {Object} layer 图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
getActiveLayer() {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
if (!this.activeLayerId.value) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
console.warn("没有活动图层ID,无法获取活动图层 ==== 默认设置第一个图层为活动图层");
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.activeLayerId.value = this.layers.value[0]?.id || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { layer: activeLayer } = findLayerRecursively(
|
|
|
|
|
|
this.layers.value,
|
|
|
|
|
|
this.activeLayerId.value
|
|
|
|
|
|
);
|
|
|
|
|
|
if (activeLayer) {
|
|
|
|
|
|
return activeLayer;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn("没有活动图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取活动图层失败:", error);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前活动图层ID
|
|
|
|
|
|
getActiveLayerId() {
|
|
|
|
|
|
return this.activeLayerId.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据ID获取图层
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {Object|null} 图层对象或null
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerById(layerId) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
|
|
|
|
|
return layer;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前图层对象的列表
|
|
|
|
|
|
* @param {string} layerId 可选,指定图层ID,默认使用当前活动图层
|
|
|
|
|
|
* @returns {Array} 图层中的对象列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerObjects(layerId = null) {
|
|
|
|
|
|
const targetLayerId = layerId || this.activeLayerId.value;
|
|
|
|
|
|
if (!targetLayerId) return [];
|
|
|
|
|
|
|
|
|
|
|
|
const layer = this.getLayerById(targetLayerId);
|
|
|
|
|
|
if (!layer) return [];
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是背景图层且有单个对象
|
|
|
|
|
|
if (layer.isBackground && layer.fabricObject) {
|
|
|
|
|
|
return [layer.fabricObject];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 普通图层返回对象列表
|
|
|
|
|
|
return Array.isArray(layer.fabricObjects) ? layer.fabricObjects : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移除图层
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 是否移除成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
removeLayer(layerId) {
|
|
|
|
|
|
// 查找要删除的图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 如果是背景层或固定层,不允许删除
|
|
|
|
|
|
if (layer && (layer.isBackground || layer.isFixed)) {
|
|
|
|
|
|
console.warn(layer.isBackground ? "背景层不可删除" : "固定层不可删除");
|
2025-06-18 11:05:23 +08:00
|
|
|
|
message.warning(layer.isBackground ? "背景层不可删除" : "固定层不可删除");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果图层有子图层,提示确认
|
|
|
|
|
|
if (layer && layer.children && layer.children.length > 0) {
|
|
|
|
|
|
console.warn("该图层包含子图层,删除将同时删除所有子图层");
|
2025-06-18 11:05:23 +08:00
|
|
|
|
message.warning("该图层包含子图层,删除将同时删除所有子图层");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new RemoveLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
updateLayerObject(layerId, fabricObject) {
|
|
|
|
|
|
// 查找图层
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tempFabricObject =
|
2025-07-14 01:00:23 +08:00
|
|
|
|
fabricObject?.toObject?.(["id", "layerId", "layerName", "isBackgroud", "isFixed"]) ||
|
|
|
|
|
|
fabricObject;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (layer.isFixed || layer.isBackground) {
|
|
|
|
|
|
layer.fabricObject = tempFabricObject;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
layer.fabricObjects =
|
|
|
|
|
|
layer.fabricObjects.map((item) => {
|
|
|
|
|
|
if (item.id === tempFabricObject.id) {
|
|
|
|
|
|
// 更新对象属性
|
|
|
|
|
|
return tempFabricObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
return item;
|
|
|
|
|
|
}) || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 移动图层位置
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* @param {string} direction 移动方向,'up'、'down'、'toTop'或'toBottom'
|
2025-06-09 10:25:54 +08:00
|
|
|
|
* @returns {boolean} 是否移动成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
moveLayer(layerId, direction) {
|
|
|
|
|
|
// 查找要移动的图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是背景层或固定层,不允许移动
|
|
|
|
|
|
if (layer && (layer.isBackground || layer.isFixed)) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
console.warn(layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动"));
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
let command;
|
|
|
|
|
|
|
|
|
|
|
|
// 根据方向选择相应的命令
|
|
|
|
|
|
switch (direction) {
|
|
|
|
|
|
case "toTop":
|
|
|
|
|
|
command = new MoveLayerToTopCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
layerSort: this.layerSort,
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "toBottom":
|
|
|
|
|
|
command = new MoveLayerToBottomCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
layerSort: this.layerSort,
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "up":
|
|
|
|
|
|
case "down":
|
|
|
|
|
|
default:
|
|
|
|
|
|
command = new MoveLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
layerSort: this.layerSort,
|
|
|
|
|
|
direction: direction,
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const result = this.commandManager.execute(command);
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log(`✅ 图层移动成功: ${direction}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const result = command.execute();
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
// 更新画布渲染顺序
|
|
|
|
|
|
console.log(`✅ 图层移动成功: ${direction}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
// 设置激活当前图层下画布中的所有对象,并变成选择组
|
|
|
|
|
|
setAllActiveGroupLayerCanvasObject(layer) {
|
|
|
|
|
|
// 获取当前图层下所有元素
|
|
|
|
|
|
// 选择当前组下所有画布元素
|
|
|
|
|
|
const allObjects = layer.children.reduce((acc, child) => {
|
|
|
|
|
|
// 如果子图层有fabricObjects,则添加到结果数组
|
|
|
|
|
|
child?.fabricObjects?.forEach((obj) => {
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, obj.id);
|
|
|
|
|
|
if (object) {
|
2025-07-10 01:01:46 +08:00
|
|
|
|
// if (!layerMask) {
|
|
|
|
|
|
// layerMask = fabric.util.object.clone(object.clipPath);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
2025-07-04 03:16:18 +08:00
|
|
|
|
acc.push(object);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (child?.fabricObject) {
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, child?.fabricObject.id);
|
|
|
|
|
|
if (object) {
|
2025-07-10 01:01:46 +08:00
|
|
|
|
// if (!layerMask) {
|
|
|
|
|
|
// layerMask = fabric.util.object.clone(object.clipPath);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
2025-07-04 03:16:18 +08:00
|
|
|
|
acc.push(object);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-14 23:42:28 +08:00
|
|
|
|
if (child.fill) {
|
|
|
|
|
|
// 如果图层有填充颜色,设置所有对象的填充颜色
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, child.fill.id);
|
|
|
|
|
|
if (object) {
|
|
|
|
|
|
acc.push(object);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
return acc;
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-07-16 11:35:52 +08:00
|
|
|
|
// if (layer.fill) {
|
|
|
|
|
|
// // 如果图层有填充颜色,设置所有对象的填充颜色
|
|
|
|
|
|
// const { object } = findObjectById(this.canvas, layer.fill.id);
|
|
|
|
|
|
// if (object) {
|
|
|
|
|
|
// allObjects.push(object);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2025-07-14 23:42:28 +08:00
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
if (allObjects.length) {
|
|
|
|
|
|
// 切换到选择模式
|
|
|
|
|
|
this?.toolManager?.setTool(OperationType.SELECT);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有对象,创建选择组
|
|
|
|
|
|
this.canvas.discardActiveObject(); // 取消当前活动对象
|
2025-07-10 01:01:46 +08:00
|
|
|
|
this.canvas.renderAll(); // 确保画布渲染
|
|
|
|
|
|
|
|
|
|
|
|
// const { object } = findObjectById(this.canvas, layer.clippingMask?.id);
|
|
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
// 选中多个对象,不是创建组
|
|
|
|
|
|
// 多个对象时创建活动选择组
|
2025-07-10 01:01:46 +08:00
|
|
|
|
let activeSelection = new fabric.ActiveSelection(allObjects, {
|
2025-07-04 03:16:18 +08:00
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-07-10 01:01:46 +08:00
|
|
|
|
// if (object) {
|
|
|
|
|
|
// const tempClipPath = fabric.util.object.clone(object);
|
|
|
|
|
|
// tempClipPath.clipPath = null;
|
|
|
|
|
|
// tempClipPath.set({
|
|
|
|
|
|
// // 设置绝对定位
|
|
|
|
|
|
// // ...layer.clippingMask, // 恢复原定位
|
|
|
|
|
|
// ...getOriginObjectInfo(layer.clippingMask),
|
|
|
|
|
|
// absolutePositioned: true,
|
|
|
|
|
|
// });
|
|
|
|
|
|
// activeSelection.clipPath = tempClipPath; // 保留第一个对象的裁剪路径
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 监听选择取消事件,恢复原始裁剪路径
|
|
|
|
|
|
// const restoreClipPaths = () => {
|
|
|
|
|
|
// allObjects.forEach((obj) => {
|
|
|
|
|
|
// if (obj._originalClipPath !== undefined) {
|
|
|
|
|
|
// obj.clipPath = obj._originalClipPath;
|
|
|
|
|
|
// delete obj._originalClipPath;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// });
|
|
|
|
|
|
// this.canvas.off("selection:cleared", restoreClipPaths);
|
|
|
|
|
|
// this.canvas.off("selection:updated", restoreClipPaths);
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
// this.canvas.on("selection:cleared", restoreClipPaths);
|
|
|
|
|
|
// this.canvas.on("selection:updated", restoreClipPaths);
|
2025-07-08 01:08:45 +08:00
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
// 设置活动选择组的属性
|
|
|
|
|
|
this.canvas.setActiveObject(activeSelection);
|
2025-07-14 01:00:23 +08:00
|
|
|
|
// 为活动选择组添加移动事件监听器,用于同步更新遮罩位置
|
|
|
|
|
|
this._setupGroupMaskMovementSync(activeSelection, layer);
|
|
|
|
|
|
|
2025-07-10 01:01:46 +08:00
|
|
|
|
activeSelection = null; // 清理引用,避免内存泄漏
|
|
|
|
|
|
// 确保选择组正确渲染
|
|
|
|
|
|
// activeSelection.setCoords();
|
2025-07-04 03:16:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 切换图层可见性
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 更新后的可见性状态
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async toggleLayerVisibility(layerId) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new ToggleLayerVisibilityCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
layerManager: this,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return await this.commandManager.execute(command);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return await command.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-04 03:16:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 查询图层可见性
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 图层是否可见
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerVisibility(layerId) {
|
|
|
|
|
|
// 查找图层
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 返回图层的可见性状态
|
|
|
|
|
|
return layer.visible;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 切换图层锁定状态
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 更新后的锁定状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
toggleLayerLock(layerId) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new LayerLockCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerManager: this,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前锁定状态
|
|
|
|
|
|
const layer = this.layers.value.find((layer) => layer.id === layerId);
|
|
|
|
|
|
return layer ? layer.locked : false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置图层不透明度
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @param {number} opacity 不透明度值 (0-1)
|
|
|
|
|
|
*/
|
|
|
|
|
|
setLayerOpacity(layerId, opacity) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new SetLayerOpacityCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
opacity: opacity,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层对象不透明度
|
|
|
|
|
|
const layer = this.layers.value.find((layer) => layer.id === layerId);
|
|
|
|
|
|
if (layer && layer.fabricObjects) {
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
obj.opacity = opacity;
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置图层混合模式
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @param {string} blendMode 混合模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
setLayerBlendMode(layerId, blendMode) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new SetLayerBlendModeCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
blendMode: blendMode,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层对象混合模式
|
|
|
|
|
|
const layer = this.layers.value.find((layer) => layer.id === layerId);
|
|
|
|
|
|
if (layer && layer.fabricObjects) {
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
obj.globalCompositeOperation = blendMode;
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重命名图层
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @param {string} newName 新名称
|
|
|
|
|
|
*/
|
|
|
|
|
|
renameLayer(layerId, newName) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new RenameLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
newName: newName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层对象上的图层名称
|
|
|
|
|
|
const layer = this.layers.value.find((layer) => layer.id === layerId);
|
|
|
|
|
|
if (layer && layer.fabricObjects) {
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
obj.layerName = newName;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并多个图层
|
|
|
|
|
|
* @param {Array<string>} layerIds 要合并的图层ID数组
|
|
|
|
|
|
* @param {string} newName 合并后的图层名称,可选
|
|
|
|
|
|
* @returns {string} 合并后的新图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
mergeLayers(layerIds, newName = null) {
|
|
|
|
|
|
// 检查参数
|
|
|
|
|
|
if (!layerIds || !Array.isArray(layerIds) || layerIds.length < 2) {
|
|
|
|
|
|
console.error("合并图层至少需要两个图层ID");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new MergeLayersCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerIds: layerIds,
|
|
|
|
|
|
newName: newName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return command.newLayerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将多个图层组合为一个组
|
|
|
|
|
|
* @param {Array<string>} layerIds 要组合的图层ID数组
|
|
|
|
|
|
* @param {string} groupName 组名称,可选
|
|
|
|
|
|
* @returns {string} 新组图层ID
|
|
|
|
|
|
*/
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async groupLayers(layerIds, groupName = null) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 检查参数
|
|
|
|
|
|
if (!layerIds || !Array.isArray(layerIds) || layerIds.length < 2) {
|
|
|
|
|
|
console.error("组合图层至少需要两个图层ID");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new GroupLayersCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
activeLayerId: this.activeLayerId,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
layerIds: layerIds,
|
|
|
|
|
|
groupName: groupName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return await this.commandManager.execute(command);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return await command.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解组一个组图层
|
|
|
|
|
|
* @param {string} groupId 要解组的组图层ID
|
|
|
|
|
|
* @returns {Array<string>} 解组后的图层ID数组
|
|
|
|
|
|
*/
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async ungroupLayers(groupId) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 查找组图层
|
|
|
|
|
|
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
2025-07-14 01:00:23 +08:00
|
|
|
|
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.error(`${groupId} 不是有效的组图层或不包含子图层`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new UngroupLayersCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
groupId: groupId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
activeLayerId: this.activeLayerId,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return await this.commandManager.execute(command);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return await command.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 调整画布和背景图层尺寸
|
|
|
|
|
|
* @param {number} width 宽度
|
|
|
|
|
|
* @param {number} height 高度
|
|
|
|
|
|
*/
|
|
|
|
|
|
resizeCanvas(width, height, options = {}) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new BackgroundSizeCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
canvasManager: this.canvasManager,
|
|
|
|
|
|
newWidth: width,
|
|
|
|
|
|
newHeight: height,
|
|
|
|
|
|
isRedGreenMode: this.isInRedGreenMode(), // 传递红绿图模式状态
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
command.undoable = options.undoable; // 设置为可撤销
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新存储的尺寸
|
|
|
|
|
|
this.canvasWidth = width;
|
|
|
|
|
|
this.canvasHeight = height;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 调整背景大小并等比缩放所有其他元素
|
|
|
|
|
|
* @param {number} width 宽度
|
|
|
|
|
|
* @param {number} height 高度
|
|
|
|
|
|
*/
|
|
|
|
|
|
resizeCanvasWithScale(width, height, options = {}) {
|
|
|
|
|
|
// 检查是否有除背景层外的其他元素
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const hasOtherElements = this.canvas.getObjects().some((obj) => !obj.isBackground);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (hasOtherElements) {
|
|
|
|
|
|
// 有其他元素时使用带缩放的命令
|
|
|
|
|
|
const command = new BackgroundSizeWithScaleCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
canvasManager: this.canvasManager,
|
|
|
|
|
|
newWidth: width,
|
|
|
|
|
|
newHeight: height,
|
|
|
|
|
|
isRedGreenMode: this.isInRedGreenMode(), // 传递红绿图模式状态
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
command.undoable = false; // 设置为可撤销
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 没有其他元素时使用普通的背景尺寸调整命令
|
|
|
|
|
|
this.resizeCanvas(width, height, options);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新存储的尺寸
|
|
|
|
|
|
this.canvasWidth = width;
|
|
|
|
|
|
this.canvasHeight = height;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 排序图层,确保图层顺序: 普通图层 > 固定图层 > 背景图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
sortLayers() {
|
|
|
|
|
|
// 对图层进行排序:背景图层在最底层(数组最后),固定图层在中间
|
|
|
|
|
|
this.layers.value.sort((a, b) => {
|
|
|
|
|
|
// 如果a是背景图层,它应该排在后面(最底层)
|
|
|
|
|
|
if (a.isBackground) return 1;
|
|
|
|
|
|
// 如果b是背景图层,它应该排在后面(最底层)
|
|
|
|
|
|
if (b.isBackground) return -1;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果a是固定图层而b不是固定图层,a应该排在后面(固定图层在普通图层下方)
|
|
|
|
|
|
if (a.isFixed && !b.isFixed) return 1;
|
|
|
|
|
|
// 如果b是固定图层而a不是固定图层,b应该排在后面(固定图层在普通图层下方)
|
|
|
|
|
|
if (b.isFixed && !a.isFixed) return -1;
|
|
|
|
|
|
|
|
|
|
|
|
// 其他情况保持原有顺序
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新画布对象顺序
|
|
|
|
|
|
this._rearrangeObjects();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重新排列画布上的对象以匹配图层顺序
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_rearrangeObjects() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (this.layerSort) {
|
|
|
|
|
|
// 使用LayerSort的高级排序
|
|
|
|
|
|
this.layerSort.rearrangeObjects();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 传统排序逻辑(保持原有逻辑作为备用)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.canvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取画布上的所有对象
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const canvasObjects = [...this.canvas.getObjects(["id", "layerId", "layerName"])];
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 清空画布
|
|
|
|
|
|
this.canvas.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// 按图层顺序(从底到顶)重新添加对象
|
|
|
|
|
|
// 注意:图层数组是从顶到底的顺序,需要反向遍历
|
|
|
|
|
|
for (let i = this.layers.value.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const layer = this.layers.value[i];
|
|
|
|
|
|
|
|
|
|
|
|
// 跳过不可见图层
|
|
|
|
|
|
if (!layer.visible) continue;
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (layer.isBackground && layer.fabricObject) {
|
|
|
|
|
|
// 背景图层
|
2025-07-14 01:00:23 +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);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.canvas.add(layer.fabricObject);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-07-14 01:00:23 +08:00
|
|
|
|
} else if (layer.isFixed && 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);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-07-14 01:00:23 +08:00
|
|
|
|
} else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 普通图层,添加所有fabricObjects
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
|
|
|
|
|
if (originalObj) {
|
|
|
|
|
|
this.canvas.add(originalObj);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 同步画布对象到图层数据
|
|
|
|
|
|
* 确保画布上的对象和图层数据一致
|
|
|
|
|
|
*/
|
|
|
|
|
|
syncCanvasToLayers() {
|
|
|
|
|
|
if (!this.canvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取画布上的所有对象
|
|
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有图层
|
|
|
|
|
|
this.layers.value.forEach((layer) => {
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
// 背景图层处理
|
|
|
|
|
|
if (layer.fabricObject) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const existsOnCanvas = canvasObjects.some((obj) => obj.id === layer.fabricObject.id);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!existsOnCanvas) {
|
|
|
|
|
|
this.canvas.add(layer.fabricObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (Array.isArray(layer.fabricObjects)) {
|
|
|
|
|
|
// 更新图层中的对象列表
|
|
|
|
|
|
const updatedObjects = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 处理已有对象
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
const canvasObj = canvasObjects.find((cObj) => cObj.id === obj.id);
|
|
|
|
|
|
if (canvasObj) {
|
|
|
|
|
|
updatedObjects.push(canvasObj);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 对象不在画布上,添加到画布
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
updatedObjects.push(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有新对象需要添加到图层
|
|
|
|
|
|
canvasObjects.forEach((canvasObj) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
canvasObj.layerId === layer.id &&
|
|
|
|
|
|
!updatedObjects.some((obj) => obj.id === canvasObj.id)
|
|
|
|
|
|
) {
|
|
|
|
|
|
updatedObjects.push(canvasObj);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层的对象列表
|
|
|
|
|
|
layer.fabricObjects = updatedObjects;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 重新排列对象以匹配图层顺序
|
|
|
|
|
|
this._rearrangeObjects();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 导出当前图层数据
|
|
|
|
|
|
* @returns {Object} 图层数据对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
exportLayersData() {
|
|
|
|
|
|
// 深拷贝图层数据,避免修改原始数据
|
|
|
|
|
|
const layersData = JSON.parse(JSON.stringify(this.layers.value));
|
|
|
|
|
|
|
|
|
|
|
|
// 移除无法序列化的属性
|
|
|
|
|
|
layersData.forEach((layer) => {
|
|
|
|
|
|
if (layer.fabricObjects) {
|
|
|
|
|
|
// 序列化对象
|
|
|
|
|
|
layer.serializedObjects = layer.fabricObjects
|
|
|
|
|
|
.map((obj) => {
|
|
|
|
|
|
if (typeof obj.toObject === "function") {
|
|
|
|
|
|
return obj.toObject(["id", "layerId", "layerName"]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
// 删除原始对象引用
|
|
|
|
|
|
delete layer.fabricObjects;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (layer.fabricObject) {
|
|
|
|
|
|
layer.serializedBackgroundObject =
|
|
|
|
|
|
typeof layer.fabricObject.toObject === "function"
|
|
|
|
|
|
? layer.fabricObject.toObject(["id", "layerId", "layerName"])
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
delete layer.fabricObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
layers: layersData,
|
|
|
|
|
|
activeLayerId: this.activeLayerId.value,
|
|
|
|
|
|
canvasWidth: this.canvasWidth,
|
|
|
|
|
|
canvasHeight: this.canvasHeight,
|
2025-06-29 23:29:47 +08:00
|
|
|
|
backgroundColor: this.backgroundColor.value,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
editorMode: this.editorMode,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 复制图层数据到剪贴板
|
|
|
|
|
|
* @param {string} layerId 要复制的图层ID
|
|
|
|
|
|
* @returns {Object} 复制的图层数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
copyLayer(layerId) {
|
|
|
|
|
|
const layer = this.layers.value.find((l) => l.id === layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不允许复制背景图层
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
console.warn("不能复制背景图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化图层对象
|
|
|
|
|
|
const layerCopy = JSON.parse(JSON.stringify(layer));
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化fabricObjects数组
|
|
|
|
|
|
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
|
|
|
|
|
layerCopy.serializedObjects = layer.fabricObjects
|
|
|
|
|
|
.map((obj) =>
|
2025-07-14 01:00:23 +08:00
|
|
|
|
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
2025-06-09 10:25:54 +08:00
|
|
|
|
)
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 存储到剪贴板
|
|
|
|
|
|
this.clipboardData = layerCopy;
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`已复制图层:${layer.name}`);
|
|
|
|
|
|
|
|
|
|
|
|
return this.clipboardData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 剪切图层数据到剪贴板
|
|
|
|
|
|
* @param {string} layerId 要剪切的图层ID
|
|
|
|
|
|
* @returns {Object} 剪切的图层数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
cutLayer(layerId) {
|
|
|
|
|
|
const layer = this.layers.value.find((l) => l.id === layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不允许剪切背景图层
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
console.warn("不能剪切背景图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是唯一的普通图层
|
|
|
|
|
|
const normalLayers = this.layers.value.filter((l) => !l.isBackground);
|
|
|
|
|
|
if (normalLayers.length === 1) {
|
|
|
|
|
|
console.warn("不能剪切唯一的普通图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化图层对象
|
|
|
|
|
|
const layerCopy = JSON.parse(JSON.stringify(layer));
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化fabricObjects数组
|
|
|
|
|
|
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
|
|
|
|
|
layerCopy.serializedObjects = layer.fabricObjects
|
|
|
|
|
|
.map((obj) =>
|
2025-07-14 01:00:23 +08:00
|
|
|
|
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
2025-06-09 10:25:54 +08:00
|
|
|
|
)
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 存储到剪贴板
|
|
|
|
|
|
this.clipboardData = layerCopy;
|
|
|
|
|
|
|
|
|
|
|
|
// 记录是剪切操作,用于粘贴时的处理
|
|
|
|
|
|
this.clipboardData.isCut = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 从画布中移除图层中的所有对象
|
|
|
|
|
|
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
this.canvas.remove(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果剪切的是当前活动图层,需要切换到其他图层
|
|
|
|
|
|
if (this.activeLayerId.value === layerId) {
|
|
|
|
|
|
// 查找下一个可用的图层
|
|
|
|
|
|
const nextLayer = this._findNextAvailableLayer(layerId);
|
|
|
|
|
|
if (nextLayer) {
|
|
|
|
|
|
this.setActiveLayer(nextLayer.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new RemoveLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`已剪切图层:${layer.name}`);
|
|
|
|
|
|
|
|
|
|
|
|
return this.clipboardData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 粘贴图层
|
|
|
|
|
|
* @returns {string} 新创建的图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 粘贴图层
|
|
|
|
|
|
* @returns {string} 新创建的图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
pasteLayer() {
|
|
|
|
|
|
if (!this.clipboardData) {
|
|
|
|
|
|
console.error("剪贴板中没有图层数据");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 创建粘贴图层命令
|
|
|
|
|
|
const command = new PasteLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
clipboardData: this.clipboardData,
|
|
|
|
|
|
layerManager: this, // 传递LayerManager实例
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
return command.execute();
|
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
|
|
|
|
selectAll() {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new SelectAllLayersCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
editorMode: this.editorMode,
|
|
|
|
|
|
});
|
|
|
|
|
|
command.undoable = false; // 设置为不可撤销
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
command?.execute?.();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清除当前选择
|
|
|
|
|
|
*/
|
|
|
|
|
|
clearSelection() {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new ClearSelectionCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
editorMode: this.editorMode,
|
|
|
|
|
|
});
|
|
|
|
|
|
command.undoable = false; // 设置为不可撤销
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
command?.execute?.();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查找下一个可用的图层(非背景、非锁定)
|
|
|
|
|
|
* @param {string} excludeLayerId 要排除的图层ID
|
|
|
|
|
|
* @returns {Object|null} 下一个可用的图层
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_findNextAvailableLayer(excludeLayerId) {
|
|
|
|
|
|
// 查找第一个非背景、非锁定的图层,排除指定的图层
|
|
|
|
|
|
return (
|
|
|
|
|
|
this.layers.value.find(
|
2025-07-14 01:00:23 +08:00
|
|
|
|
(layer) => layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
|
2025-06-18 11:05:23 +08:00
|
|
|
|
) || null
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取活动图层上方的插入索引
|
|
|
|
|
|
* @returns {number} 插入索引
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getInsertIndexAboveActiveLayer() {
|
|
|
|
|
|
if (!this.activeLayerId.value) return 0;
|
|
|
|
|
|
|
|
|
|
|
|
const activeLayerIndex = this.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.activeLayerId.value
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return activeLayerIndex !== -1 ? activeLayerIndex : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 保存画布状态
|
|
|
|
|
|
* 现在统一通过命令管理器的智能状态保存
|
|
|
|
|
|
*/
|
|
|
|
|
|
saveCanvasState() {
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
// 使用智能状态保存,避免不必要的状态保存
|
|
|
|
|
|
this.commandManager.saveStateIfNeeded({
|
|
|
|
|
|
name: "图层状态更新",
|
|
|
|
|
|
stateType: "full",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 执行带状态保存的操作
|
|
|
|
|
|
* 统一的状态管理入口
|
|
|
|
|
|
*/
|
|
|
|
|
|
executeWithState(operation, name = "图层操作") {
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
return this.commandManager.executeWithState(operation, {
|
|
|
|
|
|
name,
|
|
|
|
|
|
stateType: "full",
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 降级处理
|
|
|
|
|
|
return typeof operation === "function" ? operation() : operation;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量执行图层操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
batchExecute(operations, name = "批量图层操作") {
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
return this.commandManager.batch(operations, name);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 降级处理
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
for (const operation of operations) {
|
|
|
|
|
|
results.push(typeof operation === "function" ? operation() : operation);
|
|
|
|
|
|
}
|
|
|
|
|
|
return results;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清理画布,移除所有图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
clearCanvas() {
|
|
|
|
|
|
// 清空画布
|
|
|
|
|
|
this.canvas.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// 清空图层列表
|
|
|
|
|
|
this.layers.value = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 重新初始化基本图层
|
|
|
|
|
|
this.initializeLayers();
|
|
|
|
|
|
|
|
|
|
|
|
console.log("已清空画布");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并图层内的对象为单一图像
|
|
|
|
|
|
* @param {string} layerId 图层ID,默认使用当前活动图层
|
|
|
|
|
|
* @param {fabric.Image} newImage 要合并的新图像(可选)
|
|
|
|
|
|
* @returns {Promise<string>} 合并后的图像ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
async mergeLayerObjects(activeLayer, fabricImage = null) {
|
|
|
|
|
|
if (!activeLayer) {
|
|
|
|
|
|
console.error(`活动图层不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new MergeLayerObjectsCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
fabricImage,
|
|
|
|
|
|
activeLayer,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
return command;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并图层内对象成组的命令
|
|
|
|
|
|
* 将新的图像与图层内现有对象合并为一个组对象
|
|
|
|
|
|
* 注意:此命令与 MergeLayerObjectsCommand 类似,但它创建一个组而不是单个图像对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
async LayerObjectsToGroup(activeLayer, fabricImage = null) {
|
|
|
|
|
|
// 检查活动图层是否存在
|
|
|
|
|
|
if (!activeLayer) {
|
|
|
|
|
|
console.error(`活动图层不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new LayerObjectsToGroupCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerManager: this,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
fabricImage,
|
|
|
|
|
|
activeLayer,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
return command;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新背景图层颜色
|
|
|
|
|
|
* @param {string} backgroundColor 背景颜色
|
|
|
|
|
|
*/
|
2025-07-03 00:04:05 +08:00
|
|
|
|
updateBackgroundColor(backgroundColor, options = {}) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const backgroundLayer = this.layers.value.find((layer) => layer.isBackground);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (!backgroundLayer) {
|
|
|
|
|
|
console.warn("没有找到背景图层");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new UpdateBackgroundCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
canvasManager: this.canvasManager,
|
2025-07-03 00:04:05 +08:00
|
|
|
|
layerManger: this,
|
2025-06-29 23:29:47 +08:00
|
|
|
|
backgroundColor,
|
|
|
|
|
|
backgroundColorValue: this.backgroundColor,
|
2025-07-03 00:04:05 +08:00
|
|
|
|
oldColor: options.oldColor,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-07-03 00:04:05 +08:00
|
|
|
|
command.undoable = isBoolean(options.undoable) ? options.undoable : true; // 设置为可撤销
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取图层缩略图
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @param {number} width 缩略图宽度
|
|
|
|
|
|
* @param {number} height 缩略图高度
|
|
|
|
|
|
* @returns {string} Base64编码的缩略图
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerThumbnail(layerId, width = 100, height = 100) {
|
|
|
|
|
|
const layer = this.getLayerById(layerId);
|
|
|
|
|
|
if (!layer) {
|
|
|
|
|
|
console.error(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建临时画布
|
|
|
|
|
|
const tempCanvas = document.createElement("canvas");
|
|
|
|
|
|
tempCanvas.width = width;
|
|
|
|
|
|
tempCanvas.height = height;
|
|
|
|
|
|
const ctx = tempCanvas.getContext("2d");
|
|
|
|
|
|
|
|
|
|
|
|
// 设置背景色
|
|
|
|
|
|
ctx.fillStyle = "#ffffff";
|
|
|
|
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (layer.isBackground && layer.fabricObject) {
|
|
|
|
|
|
// 背景图层
|
|
|
|
|
|
const bgObj = layer.fabricObject;
|
|
|
|
|
|
if (bgObj.toCanvasElement) {
|
|
|
|
|
|
const element = bgObj.toCanvasElement();
|
|
|
|
|
|
ctx.drawImage(element, 0, 0, width, height);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
|
|
|
|
|
// 普通图层
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj.toCanvasElement) {
|
|
|
|
|
|
const element = obj.toCanvasElement();
|
|
|
|
|
|
const scaleX = width / this.canvasWidth;
|
|
|
|
|
|
const scaleY = height / this.canvasHeight;
|
|
|
|
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
|
ctx.scale(scaleX, scaleY);
|
|
|
|
|
|
ctx.drawImage(element, obj.left || 0, obj.top || 0);
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return tempCanvas.toDataURL();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("生成图层缩略图失败:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查图层是否为空
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 是否为空图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
isLayerEmpty(layerId) {
|
|
|
|
|
|
const layer = this.getLayerById(layerId);
|
|
|
|
|
|
if (!layer) return true;
|
|
|
|
|
|
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
return !layer.fabricObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return !layer.fabricObjects || layer.fabricObjects.length === 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取图层统计信息
|
|
|
|
|
|
* @returns {Object} 图层统计信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerStats() {
|
|
|
|
|
|
const stats = {
|
|
|
|
|
|
totalLayers: this.layers.value.length,
|
|
|
|
|
|
backgroundLayers: 0,
|
|
|
|
|
|
normalLayers: 0,
|
|
|
|
|
|
lockedLayers: 0,
|
|
|
|
|
|
hiddenLayers: 0,
|
|
|
|
|
|
emptyLayers: 0,
|
|
|
|
|
|
totalObjects: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.layers.value.forEach((layer) => {
|
|
|
|
|
|
if (layer.isBackground) {
|
|
|
|
|
|
stats.backgroundLayers++;
|
|
|
|
|
|
if (layer.fabricObject) {
|
|
|
|
|
|
stats.totalObjects++;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
stats.normalLayers++;
|
|
|
|
|
|
if (layer.fabricObjects) {
|
|
|
|
|
|
stats.totalObjects += layer.fabricObjects.length;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (layer.locked) stats.lockedLayers++;
|
|
|
|
|
|
if (!layer.visible) stats.hiddenLayers++;
|
|
|
|
|
|
if (this.isLayerEmpty(layer.id)) stats.emptyLayers++;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return stats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清理空图层
|
|
|
|
|
|
* @returns {Array<string>} 被清理的图层ID数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
cleanupEmptyLayers() {
|
|
|
|
|
|
const emptyLayerIds = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 找出所有空的非背景图层
|
|
|
|
|
|
this.layers.value.forEach((layer) => {
|
|
|
|
|
|
if (!layer.isBackground && this.isLayerEmpty(layer.id)) {
|
|
|
|
|
|
emptyLayerIds.push(layer.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 删除空图层
|
|
|
|
|
|
emptyLayerIds.forEach((layerId) => {
|
|
|
|
|
|
this.removeLayer(layerId);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (emptyLayerIds.length > 0) {
|
|
|
|
|
|
console.log(`已清理 ${emptyLayerIds.length} 个空图层`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return emptyLayerIds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 销毁图层管理器
|
|
|
|
|
|
* 清理所有引用和事件监听器
|
|
|
|
|
|
*/
|
|
|
|
|
|
dispose() {
|
|
|
|
|
|
// 清空画布
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空图层数据
|
|
|
|
|
|
if (this.layers && this.layers.value) {
|
|
|
|
|
|
this.layers.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空剪贴板
|
|
|
|
|
|
this.clipboardData = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 清除引用
|
|
|
|
|
|
this.canvas = null;
|
|
|
|
|
|
this.layers = null;
|
|
|
|
|
|
this.activeLayerId = null;
|
|
|
|
|
|
this.commandManager = null;
|
|
|
|
|
|
this.canvasManager = null;
|
|
|
|
|
|
this.toolManager = null;
|
|
|
|
|
|
|
|
|
|
|
|
console.log("LayerManager 已销毁");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建文本图层并添加文本对象
|
|
|
|
|
|
* @param {Object} textObject Fabric文本对象
|
|
|
|
|
|
* @param {Object} options 文本选项
|
|
|
|
|
|
* @returns {Object} 创建的文本对象
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async createTextLayerWithObject(textObject, options = {}) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!this.canvas || !textObject) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 确保对象有ID
|
2025-07-14 01:00:23 +08:00
|
|
|
|
textObject.id = textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 创建文本图层
|
|
|
|
|
|
const layerName = options.name || "文本图层";
|
2025-06-22 13:52:28 +08:00
|
|
|
|
const layerId = await this.createLayer(layerName, LayerType.TEXT, {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
layerProperties: {
|
|
|
|
|
|
text: options.text || textObject.text || "新文本",
|
|
|
|
|
|
fontFamily: options.fontFamily || textObject.fontFamily || "Arial",
|
|
|
|
|
|
fontSize: options.fontSize || textObject.fontSize || 24,
|
|
|
|
|
|
fontWeight: options.fontWeight || textObject.fontWeight || "normal",
|
|
|
|
|
|
fontStyle: options.fontStyle || textObject.fontStyle || "normal",
|
|
|
|
|
|
textAlign: options.textAlign || textObject.textAlign || "left",
|
|
|
|
|
|
underline: options.underline || textObject.underline || false,
|
|
|
|
|
|
linethrough: options.linethrough || textObject.linethrough || false,
|
|
|
|
|
|
overline: options.overline || textObject.overline || false,
|
|
|
|
|
|
fill: options.fill || textObject.fill || "#000000",
|
|
|
|
|
|
textBackgroundColor:
|
2025-07-14 01:00:23 +08:00
|
|
|
|
options.textBackgroundColor || textObject.textBackgroundColor || "transparent",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
|
|
|
|
|
|
charSpacing: options.charSpacing || textObject.charSpacing || 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 把对象添加到新图层
|
|
|
|
|
|
textObject.layerId = layerId;
|
|
|
|
|
|
textObject.layerName = layerName;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到画布,如果还未添加
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const isOnCanvas = this.canvas.getObjects().some((obj) => obj.id === textObject.id);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!isOnCanvas) {
|
|
|
|
|
|
this.canvas.add(textObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图层中的对象列表
|
|
|
|
|
|
const layer = this.getLayerById(layerId);
|
|
|
|
|
|
if (layer) {
|
|
|
|
|
|
layer.fabricObjects = layer.fabricObjects || [];
|
2025-07-14 01:00:23 +08:00
|
|
|
|
layer.fabricObjects.push(textObject.toObject(["id", "layerId", "layerName"]));
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置此图层为活动图层
|
|
|
|
|
|
this.setActiveLayer(layerId);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
return textObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据fabric对象查找所属图层
|
|
|
|
|
|
* @param {Object} fabricObject fabric对象
|
|
|
|
|
|
* @returns {Object|null} 图层对象或null
|
|
|
|
|
|
*/
|
|
|
|
|
|
findLayerByObject(fabricObject) {
|
|
|
|
|
|
if (!fabricObject || !fabricObject.id) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有图层查找包含该对象的图层
|
|
|
|
|
|
for (const layer of this.layers.value) {
|
|
|
|
|
|
// 检查背景图层
|
|
|
|
|
|
if (layer.isBackground && layer.fabricObject) {
|
|
|
|
|
|
if (layer.fabricObject.id === fabricObject.id) {
|
|
|
|
|
|
return layer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查普通图层
|
|
|
|
|
|
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const foundObject = layer.fabricObjects.find((obj) => obj.id === fabricObject.id);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (foundObject) {
|
|
|
|
|
|
return layer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 拖拽排序图层
|
|
|
|
|
|
* @param {number} oldIndex 原索引
|
|
|
|
|
|
* @param {number} newIndex 新索引
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 是否排序成功
|
|
|
|
|
|
*/
|
2025-06-18 11:05:23 +08:00
|
|
|
|
async reorderLayers(oldIndex, newIndex, layerId) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 检查索引有效性
|
|
|
|
|
|
if (
|
|
|
|
|
|
oldIndex < 0 ||
|
|
|
|
|
|
newIndex < 0 ||
|
|
|
|
|
|
oldIndex >= this.layers.value.length ||
|
|
|
|
|
|
newIndex >= this.layers.value.length
|
|
|
|
|
|
) {
|
|
|
|
|
|
console.warn("图层排序索引无效");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是同一位置
|
|
|
|
|
|
if (oldIndex === newIndex) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取要移动的图层
|
|
|
|
|
|
const layer = this.layers.value[oldIndex];
|
|
|
|
|
|
if (!layer || layer.id !== layerId) {
|
|
|
|
|
|
console.warn("图层ID与索引不匹配");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是背景层或固定层(不允许排序)
|
|
|
|
|
|
if (layer.isBackground || layer.isFixed) {
|
|
|
|
|
|
console.warn("背景层和固定层不能参与排序");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查目标位置是否合法(不能移到背景层或固定层的位置)
|
|
|
|
|
|
const targetLayer = this.layers.value[newIndex];
|
|
|
|
|
|
if (targetLayer && (targetLayer.isBackground || targetLayer.isFixed)) {
|
|
|
|
|
|
console.warn("不能移动到背景层或固定层的位置");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并执行拖拽排序命令
|
|
|
|
|
|
const command = new ReorderLayersCommand({
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
oldIndex: oldIndex,
|
|
|
|
|
|
newIndex: newIndex,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
canvas: this.canvas,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layerSort: this.layerSort,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return await this.commandManager.execute(command);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return await command.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 拖拽排序子图层
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @param {number} oldIndex 原索引
|
|
|
|
|
|
* @param {number} newIndex 新索引
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @returns {boolean} 是否排序成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
return this.layerSort?.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据图层ID和父图层ID查找子图层
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @returns {Object|null} 子图层对象或null
|
|
|
|
|
|
*/
|
|
|
|
|
|
findChildLayer(layerId, parentId) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const parentLayer = this.getLayerById(parentId);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!parentLayer || !parentLayer.children) return null;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return parentLayer.children.find((child) => child.id === layerId) || null;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 删除子图层
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @returns {boolean} 是否删除成功
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async removeChildLayer(layerId, parentId) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new RemoveChildLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
parentId: parentId,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return await this.commandManager.execute(command);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
} else {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return await command.execute();
|
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
|
|
|
|
/**
|
|
|
|
|
|
* 重命名子图层
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @param {string} newName 新名称
|
|
|
|
|
|
*/
|
|
|
|
|
|
renameChildLayer(layerId, parentId, newName) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new RenameChildLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
parentId: parentId,
|
|
|
|
|
|
newName: newName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
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
|
|
|
|
/**
|
|
|
|
|
|
* 切换子图层锁定状态
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @returns {boolean} 更新后的锁定状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
toggleChildLayerLock(layerId, parentId) {
|
|
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new ChildLayerLockCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
parentId: parentId,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前锁定状态
|
|
|
|
|
|
const childLayer = this.findChildLayer(layerId, parentId);
|
|
|
|
|
|
return childLayer ? childLayer.locked : false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换子图层可见性
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @returns {boolean} 更新后的可见性状态
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
async toggleChildLayerVisibility(layerId, parentId) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 直接创建和执行命令
|
|
|
|
|
|
const command = new ToggleChildLayerVisibilityCommand({
|
2025-06-09 10:25:54 +08:00
|
|
|
|
canvas: this.canvas,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layerId,
|
|
|
|
|
|
parentId: parentId,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
layerManager: this,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return await this.commandManager.execute(command);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} else {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return await command.execute();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// ==================== 红绿图模式相关操作 ====================
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 设置红绿图模式管理器
|
|
|
|
|
|
setRedGreenModeManager(redGreenModeManager) {
|
|
|
|
|
|
this.redGreenModeManager = redGreenModeManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启用红绿图模式
|
|
|
|
|
|
enableRedGreenMode() {
|
|
|
|
|
|
this.isRedGreenMode = true;
|
|
|
|
|
|
console.log("图层管理器:红绿图模式已启用");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用红绿图模式
|
|
|
|
|
|
disableRedGreenMode() {
|
|
|
|
|
|
this.isRedGreenMode = false;
|
|
|
|
|
|
console.log("图层管理器:红绿图模式已禁用");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为红绿图模式
|
|
|
|
|
|
isInRedGreenMode() {
|
|
|
|
|
|
return this.isRedGreenMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查背景图层是否需要跟随画布大小变化(红绿图模式下)
|
|
|
|
|
|
shouldBackgroundFollowCanvasSize() {
|
|
|
|
|
|
return this.isRedGreenMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在红绿图模式下创建图层 - 限制功能
|
|
|
|
|
|
createLayerInRedGreenMode() {
|
|
|
|
|
|
console.warn("红绿图模式下不支持创建新图层");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在红绿图模式下移除图层 - 限制功能
|
|
|
|
|
|
removeLayerInRedGreenMode(layerId) {
|
|
|
|
|
|
console.warn("红绿图模式下不支持删除图层");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 高级图层排序功能 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用LayerSort工具重新排列画布对象
|
|
|
|
|
|
* 这是页面操作排序图层后重建画布顺序的核心方法
|
|
|
|
|
|
* @param {boolean} async 是否使用异步处理,默认根据对象数量自动决定
|
|
|
|
|
|
* @returns {Promise<void>|void} 如果使用异步则返回Promise
|
|
|
|
|
|
*/
|
|
|
|
|
|
rearrangeCanvasObjects(async = null) {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化,使用传统排序方法");
|
|
|
|
|
|
this._rearrangeObjects();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否需要异步处理
|
|
|
|
|
|
const objectsCount = this.canvas?.getObjects()?.length || 0;
|
|
|
|
|
|
const layersCount = this.layers?.value?.length || 0;
|
|
|
|
|
|
|
|
|
|
|
|
const shouldUseAsync =
|
2025-07-14 01:00:23 +08:00
|
|
|
|
async !== null ? async : LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (shouldUseAsync) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
console.log(`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
return this.layerSort.rearrangeObjectsAsync();
|
|
|
|
|
|
} else {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
console.log(`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layerSort.rearrangeObjects();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 智能排序图层
|
|
|
|
|
|
* 根据对象类型和位置自动调整图层顺序
|
|
|
|
|
|
* @param {Array<string>} targetLayerIds 要排序的图层ID数组,null表示排序所有普通图层
|
|
|
|
|
|
* @returns {boolean} 是否排序成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
smartSortLayers(targetLayerIds = null) {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = this.layerSort.smartSort(targetLayerIds);
|
|
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log("智能排序完成");
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 优化图层结构
|
|
|
|
|
|
* 清理空图层、合并相邻相似图层等
|
|
|
|
|
|
* @returns {Object} 优化结果统计
|
|
|
|
|
|
*/
|
|
|
|
|
|
optimizeLayerStructure() {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化,使用基础清理方法");
|
|
|
|
|
|
return {
|
|
|
|
|
|
removedEmptyLayers: this.cleanupEmptyLayers().length,
|
|
|
|
|
|
mergedLayers: 0,
|
|
|
|
|
|
reorderedLayers: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const stats = this.layerSort.optimizeLayerStructure();
|
|
|
|
|
|
|
|
|
|
|
|
if (stats.removedEmptyLayers > 0 || stats.reorderedLayers > 0) {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`图层结构优化完成: 清理空图层 ${stats.removedEmptyLayers} 个, 重新排序 ${stats.reorderedLayers} 个图层`
|
|
|
|
|
|
);
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return stats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 验证画布对象顺序是否正确
|
|
|
|
|
|
* @returns {boolean} 顺序是否正确
|
|
|
|
|
|
*/
|
|
|
|
|
|
validateObjectOrder() {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化");
|
|
|
|
|
|
return true; // 无法验证,假设正确
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isValid = this.layerSort.validateObjectOrder();
|
|
|
|
|
|
|
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
|
console.warn("画布对象顺序不正确,建议重新排列");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return isValid;
|
|
|
|
|
|
}
|
|
|
|
|
|
canMoveToTop(layerId) {
|
|
|
|
|
|
// 检查图层所在索引是否为0
|
|
|
|
|
|
const layerIndex = this.getLayerIndex(layerId);
|
|
|
|
|
|
if (layerIndex === -1) {
|
|
|
|
|
|
console.warn(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return layerIndex > 0; // 如果索引大于0,则可以移动到顶部
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
canMoveToBottom(layerId) {
|
|
|
|
|
|
// 检查图层所在索引是否为0
|
|
|
|
|
|
const layerIndex = this.getLayerIndex(layerId);
|
|
|
|
|
|
if (layerIndex === -1) {
|
|
|
|
|
|
console.warn(`图层 ${layerId} 不存在`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { parent } = findLayerRecursively(this.layers.value, layerId);
|
|
|
|
|
|
if (parent?.children.length) {
|
|
|
|
|
|
// 如果是子图层,检查是否有兄弟图层
|
|
|
|
|
|
return layerIndex < parent.children.length - 1; // 如果不是最后一个子图层,则可以移动到底部
|
|
|
|
|
|
}
|
|
|
|
|
|
return layerIndex < this.layers?.value?.length - 3; // 如果索引小于长度 - 3,则可以移动到底部
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移动图层到指定位置
|
|
|
|
|
|
* 使用LayerSort的高级移动功能
|
|
|
|
|
|
* @param {string} layerId 要移动的图层ID
|
|
|
|
|
|
* @param {number} newIndex 新位置索引
|
|
|
|
|
|
* @returns {boolean} 是否移动成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化,使用基础移动方法");
|
2025-07-14 01:00:23 +08:00
|
|
|
|
return this.moveLayer(layerId, newIndex > this.getLayerIndex(layerId) ? "down" : "up");
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = this.layerSort.moveLayerToIndex({
|
|
|
|
|
|
parentId,
|
|
|
|
|
|
oldIndex,
|
|
|
|
|
|
newIndex,
|
|
|
|
|
|
layerId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (result) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
console.log(`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
// this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取图层的有效移动范围
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {Object} 包含最小和最大索引的对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerMoveRange(layerId) {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
// 基础实现:只能在普通图层范围内移动
|
|
|
|
|
|
const normalLayers = this.layers.value
|
|
|
|
|
|
.map((layer, index) => ({ layer, index }))
|
|
|
|
|
|
.filter((item) => !item.layer.isBackground && !item.layer.isFixed);
|
|
|
|
|
|
|
|
|
|
|
|
if (normalLayers.length === 0) {
|
|
|
|
|
|
return { minIndex: -1, maxIndex: -1 };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
minIndex: normalLayers[0].index,
|
|
|
|
|
|
maxIndex: normalLayers[normalLayers.length - 1].index,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.layerSort.getLayerMoveRange(layerId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取图层在数组中的索引
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {number} 图层索引,-1表示未找到
|
|
|
|
|
|
*/
|
|
|
|
|
|
getLayerIndex(layerId) {
|
|
|
|
|
|
if (!layerId) {
|
|
|
|
|
|
console.warn("未提供图层ID");
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-14 01:00:23 +08:00
|
|
|
|
let layerIndex = this.layers.value.findIndex((layer) => layer.id === layerId);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (layerIndex >= 0) return layerIndex;
|
|
|
|
|
|
// 如果未找到,尝试在子图层中查找
|
|
|
|
|
|
const { parent } = findLayerRecursively(this.layers.value, layerId);
|
|
|
|
|
|
parent?.children?.findIndex((child) => {
|
|
|
|
|
|
if (child.id === layerId) {
|
|
|
|
|
|
layerIndex = parent.children.indexOf(child);
|
|
|
|
|
|
return true; // 找到后停止查找
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
return layerIndex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 高级拖拽排序图层
|
|
|
|
|
|
* 使用LayerSort的专业排序功能
|
|
|
|
|
|
* @param {number} oldIndex 原索引
|
|
|
|
|
|
* @param {number} newIndex 新索引
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 是否排序成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
advancedReorderLayers(oldIndex, newIndex, layerId) {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化,使用基础排序方法");
|
|
|
|
|
|
return this.reorderLayers(oldIndex, newIndex, layerId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId);
|
|
|
|
|
|
|
|
|
|
|
|
if (result) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
console.log(`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 高级拖拽排序子图层
|
|
|
|
|
|
* @param {string} parentId 父图层ID
|
|
|
|
|
|
* @param {number} oldIndex 原索引
|
|
|
|
|
|
* @param {number} newIndex 新索引
|
|
|
|
|
|
* @param {string} layerId 子图层ID
|
|
|
|
|
|
* @returns {boolean} 是否排序成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const result = this.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`高级子图层排序完成: 图层 ${layerId} 在父图层 ${parentId} 中从位置 ${oldIndex} 移动到 ${newIndex}`
|
|
|
|
|
|
);
|
|
|
|
|
|
// 重新排列画布对象
|
|
|
|
|
|
this.rearrangeCanvasObjects();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用LayerSort工具进行图层排序
|
|
|
|
|
|
* 这会同时更新图层数组和画布对象顺序
|
|
|
|
|
|
*/
|
|
|
|
|
|
sortLayersWithTool() {
|
|
|
|
|
|
if (!this.layerSort) {
|
|
|
|
|
|
console.warn("图层排序工具未初始化,使用基础排序方法");
|
|
|
|
|
|
this.sortLayers();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用LayerSort工具排序
|
|
|
|
|
|
this.layers.value = this.layerSort.sortLayers();
|
|
|
|
|
|
|
|
|
|
|
|
// 重新排列画布对象
|
|
|
|
|
|
this.rearrangeCanvasObjects();
|
|
|
|
|
|
|
|
|
|
|
|
console.log("使用LayerSort工具完成图层排序");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 强制重建画布对象顺序
|
|
|
|
|
|
* 当图层顺序发生变化后调用此方法确保画布对象顺序正确
|
|
|
|
|
|
*/
|
|
|
|
|
|
forceRebuildCanvasOrder() {
|
|
|
|
|
|
console.log("强制重建画布对象顺序");
|
|
|
|
|
|
|
|
|
|
|
|
if (this.layerSort) {
|
|
|
|
|
|
// 使用LayerSort的高级排序
|
|
|
|
|
|
this.rearrangeCanvasObjects();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 使用传统方法
|
|
|
|
|
|
this._rearrangeObjects();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证排序结果
|
|
|
|
|
|
const isValid = this.validateObjectOrder();
|
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
|
console.warn("画布对象顺序验证失败,可能需要手动调整");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新对象交互性
|
|
|
|
|
|
this.updateLayersObjectsInteractivity();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量重新排序多个图层
|
|
|
|
|
|
* @param {Array} reorderOperations 排序操作数组 [{layerId, oldIndex, newIndex}]
|
|
|
|
|
|
* @returns {boolean} 是否全部操作成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
batchReorderLayers(reorderOperations) {
|
|
|
|
|
|
if (!reorderOperations || !Array.isArray(reorderOperations)) {
|
|
|
|
|
|
console.error("无效的批量排序操作");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`开始批量重新排序 ${reorderOperations.length} 个图层`);
|
|
|
|
|
|
|
|
|
|
|
|
let allSuccessful = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 暂停渲染以提高性能
|
|
|
|
|
|
const wasRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderOnAddRemove = false;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 按照操作顺序执行
|
|
|
|
|
|
for (const operation of reorderOperations) {
|
|
|
|
|
|
const { layerId, oldIndex, newIndex } = operation;
|
|
|
|
|
|
|
|
|
|
|
|
const success = this.layerSort
|
|
|
|
|
|
? this.layerSort.reorderLayers(oldIndex, newIndex, layerId)
|
|
|
|
|
|
: this.reorderLayers(oldIndex, newIndex, layerId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
|
console.warn(`图层 ${layerId} 排序失败`);
|
|
|
|
|
|
allSuccessful = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新排列画布对象
|
|
|
|
|
|
this.rearrangeCanvasObjects();
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 恢复渲染
|
|
|
|
|
|
this.canvas.renderOnAddRemove = wasRenderOnAddRemove;
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (allSuccessful) {
|
|
|
|
|
|
console.log("批量图层排序完成");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn("批量图层排序部分失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return allSuccessful;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 合并组图层
|
|
|
|
|
|
* @param {string} groupId 组图层ID
|
|
|
|
|
|
* @returns {Array<string>} 所有子图层成组的图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
async mergeGroupLayers(groupId) {
|
|
|
|
|
|
// 查找组图层
|
|
|
|
|
|
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
2025-07-14 01:00:23 +08:00
|
|
|
|
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.warn($t("找不到有效的组图层或组图层为空"));
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行解组命令
|
|
|
|
|
|
const command = new MergeGroupLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: groupId,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
const result = await this.commandManager.execute(command);
|
|
|
|
|
|
result && console.log(`✅ 成功合并组图层: ${groupLayer.name}`);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const result = await command.execute();
|
|
|
|
|
|
result && console.log(`✅ 成功合并组图层: ${groupLayer.name}`);
|
|
|
|
|
|
return result || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 栅格化图层
|
|
|
|
|
|
* @param {string} layerId 图层ID,默认使用当前活动图层
|
|
|
|
|
|
* @returns {Promise<boolean>} 是否栅格化成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
async rasterizeLayer(layerId = null) {
|
|
|
|
|
|
const targetLayerId = layerId || this.activeLayerId.value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!targetLayerId) {
|
|
|
|
|
|
console.warn($t("没有指定要栅格化的图层"));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找目标图层
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// const targetLayer = this.getLayerById(targetLayerId);
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!targetLayer) {
|
|
|
|
|
|
console.error($t("图层不存在", { layerId: targetLayerId }));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不允许栅格化背景图层和固定图层
|
|
|
|
|
|
if (targetLayer.isBackground || targetLayer.isFixed) {
|
|
|
|
|
|
console.warn($t("背景图层和固定图层不能栅格化"));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行栅格化命令
|
|
|
|
|
|
const command = new RasterizeLayerCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: targetLayerId,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
const result = await this.commandManager.execute(command);
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log(`✅ 成功栅格化图层: ${targetLayer.name}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const result = await command.execute();
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log(`✅ 成功栅格化图层: ${targetLayer.name}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查图层是否可以栅格化
|
|
|
|
|
|
* @param {string} layerId 图层ID
|
|
|
|
|
|
* @returns {boolean} 是否可以栅格化
|
|
|
|
|
|
*/
|
|
|
|
|
|
canRasterizeLayer(layerId) {
|
2025-07-14 23:42:28 +08:00
|
|
|
|
const layer = findLayerRecursively(this.layers.value, layerId)?.layer;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
if (!layer) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 不允许栅格化背景图层和固定图层
|
|
|
|
|
|
if (layer.isBackground || layer.isFixed) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查图层是否有内容可以栅格化
|
2025-07-14 23:42:28 +08:00
|
|
|
|
if (layer.type === "group" || layer.children.length > 0) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 组图层:检查是否有子图层且子图层有内容
|
2025-07-14 23:42:28 +08:00
|
|
|
|
return layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 普通图层:检查是否有对象
|
|
|
|
|
|
return layer.fabricObjects && layer.fabricObjects.length > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 15:56:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 导出图层 -- 下载图层图片
|
|
|
|
|
|
* @param {string} layerId 图层ID,默认使用当前活动图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
async exportLayerToImage(layerId = null) {
|
|
|
|
|
|
const targetLayerId = layerId || this.activeLayerId.value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!targetLayerId) {
|
|
|
|
|
|
console.warn($t("没有指定要栅格化的图层"));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找目标图层
|
|
|
|
|
|
// const targetLayer = this.getLayerById(targetLayerId);
|
2025-07-14 01:00:23 +08:00
|
|
|
|
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
2025-06-23 15:56:01 +08:00
|
|
|
|
|
|
|
|
|
|
if (!targetLayer) {
|
|
|
|
|
|
console.error($t("图层不存在", { layerId: targetLayerId }));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建和执行导出命令
|
|
|
|
|
|
const command = new ExportLayerToImageCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: targetLayerId,
|
|
|
|
|
|
activeLayerId: this.activeLayerId,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
command.undoable = false; // 导出操作通常不需要撤销
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
const result = await this.commandManager.execute(command);
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log(`✅ 成功导出图层: ${targetLayer.name}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const result = await command.execute();
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log(`✅ 成功导出图层: ${targetLayer.name}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
2025-07-14 01:00:23 +08:00
|
|
|
|
* 为组图层的活动选择组设置遮罩移动同步(修复版)
|
|
|
|
|
|
* @param {fabric.ActiveSelection} activeSelection 活动选择组
|
|
|
|
|
|
* @param {Object} layer 组图层对象
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
2025-07-14 01:00:23 +08:00
|
|
|
|
_setupGroupMaskMovementSync(activeSelection, layer) {
|
|
|
|
|
|
if (!activeSelection || !layer || !layer.clippingMask) {
|
|
|
|
|
|
return;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-07-14 01:00:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 记录初始位置
|
|
|
|
|
|
let initialLeft = activeSelection.left;
|
|
|
|
|
|
let initialTop = activeSelection.top;
|
|
|
|
|
|
|
|
|
|
|
|
// 记录遮罩初始位置
|
|
|
|
|
|
let maskInitialLeft = layer.clippingMask.left || 0;
|
|
|
|
|
|
let maskInitialTop = layer.clippingMask.top || 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 用于节流和状态管理的变量
|
|
|
|
|
|
let isUpdating = false;
|
|
|
|
|
|
let lastUpdateTime = 0;
|
|
|
|
|
|
let hasMoved = false; // 追踪是否实际发生了移动
|
2025-07-14 23:42:28 +08:00
|
|
|
|
const UPDATE_THRESHOLD = 32; // 约60fps
|
2025-07-14 01:00:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 移动开始事件处理
|
|
|
|
|
|
const handleMovingStart = (e) => {
|
|
|
|
|
|
if (e.target === activeSelection) {
|
|
|
|
|
|
hasMoved = false; // 重置移动状态
|
|
|
|
|
|
console.log("🎯 开始移动组选择对象");
|
|
|
|
|
|
// 记录遮罩初始位置
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
"🖼️ 记录遮罩初始位置",
|
|
|
|
|
|
`${layer.clippingMask.left || 0}, ${layer.clippingMask.top || 0}`
|
|
|
|
|
|
);
|
|
|
|
|
|
// 记录初始位置
|
|
|
|
|
|
initialLeft = activeSelection.left;
|
|
|
|
|
|
initialTop = activeSelection.top;
|
|
|
|
|
|
|
|
|
|
|
|
maskInitialLeft = layer.clippingMask.left || 0;
|
|
|
|
|
|
maskInitialTop = layer.clippingMask.top || 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 移动中事件处理函数(带节流)
|
|
|
|
|
|
const handleMoving = (e) => {
|
|
|
|
|
|
const target = e.target;
|
|
|
|
|
|
if (target === activeSelection) {
|
|
|
|
|
|
hasMoved = true; // 标记发生了移动
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
|
|
|
|
|
|
// 节流处理,避免过于频繁的更新
|
|
|
|
|
|
if (now - lastUpdateTime < UPDATE_THRESHOLD) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isUpdating) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isUpdating = true;
|
|
|
|
|
|
lastUpdateTime = now;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 requestAnimationFrame 优化渲染
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 计算移动距离
|
|
|
|
|
|
const deltaX = target.left - initialLeft;
|
|
|
|
|
|
const deltaY = target.top - initialTop;
|
|
|
|
|
|
|
|
|
|
|
|
// 创建更新遮罩位置的命令
|
|
|
|
|
|
const command = new UpdateGroupMaskPositionCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerId: layer.id,
|
|
|
|
|
|
deltaX: deltaX,
|
|
|
|
|
|
deltaY: deltaY,
|
|
|
|
|
|
maskInitialLeft: maskInitialLeft,
|
|
|
|
|
|
maskInitialTop: maskInitialTop,
|
|
|
|
|
|
isExecuteRealtime: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行实时更新
|
|
|
|
|
|
command.executeRealtime();
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
isUpdating = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 修改事件处理函数 - 使用 object:modified 替代 object:moved
|
|
|
|
|
|
const handleModified = (e) => {
|
|
|
|
|
|
const target = e.target;
|
|
|
|
|
|
if (target === activeSelection && hasMoved) {
|
|
|
|
|
|
console.log("✅ 组选择对象移动完成");
|
|
|
|
|
|
|
|
|
|
|
|
// 计算最终移动距离
|
|
|
|
|
|
const deltaX = target.left - initialLeft;
|
|
|
|
|
|
const deltaY = target.top - initialTop;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有实际移动,创建可撤销的命令
|
|
|
|
|
|
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
|
|
|
|
|
|
const command = new UpdateGroupMaskPositionCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
layerManager: this,
|
|
|
|
|
|
layerId: layer.id,
|
|
|
|
|
|
deltaX: deltaX,
|
|
|
|
|
|
deltaY: deltaY,
|
|
|
|
|
|
maskInitialLeft: maskInitialLeft,
|
|
|
|
|
|
maskInitialTop: maskInitialTop,
|
|
|
|
|
|
activeSelection,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行可撤销的命令
|
|
|
|
|
|
if (this.commandManager) {
|
|
|
|
|
|
this.commandManager.execute(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
command.execute();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hasMoved = false; // 重置移动状态
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 鼠标抬起事件处理 - 备用方案
|
|
|
|
|
|
const handleMouseUp = (e) => {
|
|
|
|
|
|
if (hasMoved && this.canvas.getActiveObject() === activeSelection) {
|
|
|
|
|
|
console.log("🖱️ 鼠标抬起 - 备用移动完成处理");
|
|
|
|
|
|
handleModified(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 清理事件监听器的函数
|
|
|
|
|
|
const cleanup = () => {
|
|
|
|
|
|
this.canvas.off("object:moving", handleMoving);
|
|
|
|
|
|
this.canvas.off("object:modified", handleModified);
|
|
|
|
|
|
this.canvas.off("mouse:down", handleMovingStart);
|
|
|
|
|
|
this.canvas.off("mouse:up", handleMouseUp);
|
|
|
|
|
|
this.canvas.off("selection:cleared", cleanup);
|
|
|
|
|
|
this.canvas.off("selection:updated", cleanup);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("🧹 清理组遮罩移动同步事件监听器");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定事件监听器
|
|
|
|
|
|
this.canvas.on("mouse:down", handleMovingStart);
|
|
|
|
|
|
this.canvas.on("object:moving", handleMoving);
|
|
|
|
|
|
this.canvas.on("object:modified", handleModified); // 使用 modified 替代 moved
|
|
|
|
|
|
this.canvas.on("mouse:up", handleMouseUp); // 备用方案
|
|
|
|
|
|
|
|
|
|
|
|
// 当选择被清除或更新时清理事件监听器
|
|
|
|
|
|
this.canvas.on("selection:cleared", cleanup);
|
|
|
|
|
|
this.canvas.on("selection:updated", cleanup);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|