2936 lines
80 KiB
JavaScript
2936 lines
80 KiB
JavaScript
import {
|
||
AddLayerCommand,
|
||
PasteLayerCommand,
|
||
RemoveLayerCommand,
|
||
MoveLayerCommand,
|
||
ToggleLayerVisibilityCommand,
|
||
RenameLayerCommand,
|
||
LayerLockCommand,
|
||
SetLayerOpacityCommand,
|
||
SetLayerBlendModeCommand,
|
||
MergeLayersCommand,
|
||
GroupLayersCommand,
|
||
UngroupLayersCommand,
|
||
MergeLayerObjectsCommand,
|
||
LayerObjectsToGroupCommand,
|
||
ReorderLayersCommand,
|
||
ToggleChildLayerVisibilityCommand,
|
||
RenameChildLayerCommand,
|
||
RemoveChildLayerCommand,
|
||
ChildLayerLockCommand,
|
||
} from "../commands/LayerCommands";
|
||
import {
|
||
SetActiveLayerCommand,
|
||
AddObjectToLayerCommand,
|
||
RemoveObjectFromLayerCommand,
|
||
SelectAllLayersCommand,
|
||
ClearSelectionCommand,
|
||
MoveLayerToTopCommand,
|
||
MoveLayerToBottomCommand,
|
||
} from "../commands/ObjectLayerCommands";
|
||
import {
|
||
LayerType,
|
||
BlendMode,
|
||
createLayer,
|
||
createBackgroundLayer,
|
||
createFixedLayer,
|
||
OperationType,
|
||
OperationTypes,
|
||
findLayerRecursively,
|
||
} from "../utils/layerHelper";
|
||
import {
|
||
CreateBackgroundLayerCommand,
|
||
UpdateBackgroundCommand,
|
||
BackgroundSizeCommand,
|
||
BackgroundSizeWithScaleCommand,
|
||
} from "../commands/BackgroundCommands";
|
||
import { MergeGroupLayerCommand } from "../commands/GroupCommands";
|
||
import {
|
||
ExportLayerToImageCommand,
|
||
RasterizeLayerCommand,
|
||
} from "../commands/RasterizeLayerCommand";
|
||
|
||
// 导入图层排序相关类和混入
|
||
import {
|
||
LayerSort,
|
||
createLayerSort,
|
||
LayerSortMixin,
|
||
LayerSortUtils,
|
||
} from "../utils/LayerSort";
|
||
|
||
import CanvasConfig from "../config/canvasConfig";
|
||
import { isBoolean, template } from "lodash-es";
|
||
import {
|
||
findObjectById,
|
||
generateId,
|
||
optimizeCanvasRendering,
|
||
} from "../utils/helper";
|
||
import { message } from "ant-design-vue";
|
||
import { fabric } from "fabric-with-all";
|
||
|
||
/**
|
||
* 图层管理器 - 负责管理画布上的所有图层
|
||
* 包含图层的创建、删除、修改、排序等操作
|
||
* 现在统一使用命令管理器进行状态管理
|
||
* 集成LayerSort高级排序功能
|
||
*/
|
||
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;
|
||
|
||
// 编辑器模式:draw(绘画)、select(选择)、pan(拖拽)
|
||
this.editorMode = options.editorMode || CanvasConfig.defaultTool;
|
||
|
||
// 画布尺寸
|
||
this.canvasWidth = options.canvasWidth || 800;
|
||
this.canvasHeight = options.canvasHeight || 600;
|
||
|
||
// 默认背景颜色
|
||
this.backgroundColor = options.backgroundColor || "#ffffff";
|
||
|
||
// 复制粘贴相关
|
||
this.clipboardData = null;
|
||
|
||
// 操作状态标志,用于控制状态保存
|
||
this.isExecutingCommand = false;
|
||
// 是否正在执行操作
|
||
this.operationInProgress = false;
|
||
|
||
// 红绿图模式相关
|
||
this.isRedGreenMode = options.isRedGreenMode || false;
|
||
this.redGreenModeManager = null;
|
||
|
||
// 初始化图层排序工具
|
||
this.layerSort = null;
|
||
this.initLayerSort();
|
||
|
||
// 初始化相关命令
|
||
this.initCommandManager();
|
||
}
|
||
|
||
/**
|
||
* 初始化命令管理器
|
||
* 现在直接使用命令类,不再需要注册命令
|
||
*/
|
||
initCommandManager() {
|
||
// 命令注册逻辑已移除,现在直接使用命令类实例化和执行
|
||
console.log("CommandManager 已初始化,使用直接命令调用模式");
|
||
}
|
||
|
||
/**
|
||
* 初始化图层排序工具
|
||
* 创建LayerSort实例并绑定到LayerManager
|
||
*/
|
||
initLayerSort() {
|
||
if (this.canvas && this.layers) {
|
||
this.layerSort = createLayerSort(this.canvas, this.layers, {
|
||
commandManager: this.commandManager,
|
||
});
|
||
console.log("图层排序工具已初始化");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置编辑器模式
|
||
* @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
|
||
*/
|
||
async updateLayersObjectsInteractivity(isUseOptimize = true) {
|
||
if (!this.canvas) return;
|
||
if (isUseOptimize) {
|
||
// 优化渲染 - 统一批处理 支持异步回调
|
||
await optimizeCanvasRendering(this.canvas, async () => {
|
||
// 应用图层交互规则
|
||
await this._applyInteractionRules();
|
||
});
|
||
} else {
|
||
// 直接应用图层交互规则
|
||
await this._applyInteractionRules();
|
||
}
|
||
|
||
// // 性能优化:使用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(); // 确保画布重新渲染 - 同步渲染
|
||
// });
|
||
}
|
||
async _setObjectInteractivity(obj, layer, editorMode) {
|
||
// 设置可见性
|
||
obj.visible = layer.visible;
|
||
|
||
// 判断对象是否在当前活动图层上
|
||
const isInActiveLayer = obj.layerId === this.activeLayerId.value;
|
||
|
||
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
|
||
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
|
||
obj.erasable =
|
||
isInActiveLayer &&
|
||
layer.visible &&
|
||
!layer.locked &&
|
||
!layer.isBackground &&
|
||
!layer.isFixed;
|
||
|
||
// 图层状态决定交互性
|
||
if (layer.isBackground || obj.isBackground || layer.isFixed) {
|
||
//|| layer.isFixed
|
||
// 背景层永远不可选择和擦除
|
||
obj.selectable = false;
|
||
obj.evented = false;
|
||
obj.erasable = !layer.locked;
|
||
} 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;
|
||
}
|
||
|
||
if (this.isRedGreenMode) {
|
||
// 红绿图模式下 所有普通图层都可擦除
|
||
obj.erasable =
|
||
layer.visible &&
|
||
!layer.locked &&
|
||
!layer.isBackground &&
|
||
!layer.isFixed;
|
||
}
|
||
|
||
// 平移模式下,禁用多选和擦除
|
||
if (editorMode === OperationType.PAN) {
|
||
obj.selectable = false;
|
||
obj.evented = false;
|
||
obj.erasable = false;
|
||
}
|
||
}
|
||
|
||
// 应用图层视觉属性
|
||
if (layer.opacity !== undefined) obj.opacity = layer.opacity;
|
||
if (layer.blendMode) obj.globalCompositeOperation = layer.blendMode;
|
||
if (layer.clippingMask) {
|
||
const { object } = findObjectById(this.canvas, layer.clippingMask?.id);
|
||
if (object) {
|
||
obj.clipPath = object;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 私有方法:应用交互规则
|
||
async _applyInteractionRules() {
|
||
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;
|
||
layers?.children?.forEach((childLayer) => {
|
||
layerMap[childLayer.id] = childLayer;
|
||
});
|
||
});
|
||
|
||
// 批量更新对象
|
||
objects.forEach(async (obj) => {
|
||
const layer = layerMap[obj.layerId];
|
||
|
||
if (!obj.layerId) {
|
||
// 没有关联图层的对象使用默认设置
|
||
obj.selectable = false;
|
||
obj.evented = false;
|
||
obj.erasable = false; // 未关联图层的对象不可擦除
|
||
return;
|
||
}
|
||
|
||
if (!layer) return;
|
||
|
||
// 设置一级图层对象的交互性
|
||
await this._setObjectInteractivity(obj, layer, editorMode);
|
||
|
||
// 设置子图层对象的交互性
|
||
layer?.childLayer?.forEach(async (childLayer) => {
|
||
const childObj = this.canvas
|
||
.getObjects()
|
||
.find((o) => o.layerId === childLayer.id);
|
||
if (childObj) {
|
||
await this._setObjectInteractivity(childObj, childLayer, editorMode);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 创建新图层
|
||
* @param {string} name 图层名称
|
||
* @param {string} type 图层类型
|
||
* @param {Object} options 额外选项
|
||
* @returns {string} 新创建的图层ID
|
||
*/
|
||
async createLayer(name = null, type = LayerType.EMPTY, options = {}) {
|
||
// 生成唯一ID
|
||
const layerId =
|
||
options.id ||
|
||
options.layerId ||
|
||
generateId("layer_") ||
|
||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||
const layerIndex = this.layers.value.length;
|
||
|
||
// 计算插入位置,如果没有指定insertIndex,则根据当前选中图层决定插入位置
|
||
// 添加到图层列表
|
||
let insertIndex = this._getInsertIndexAboveActiveLayer();
|
||
if (options.insertIndex !== undefined) {
|
||
insertIndex = options.insertIndex;
|
||
}
|
||
|
||
// 创建新图层
|
||
const newLayer = createLayer({
|
||
id: layerId,
|
||
name: name || `图层 ${layerIndex + 1}`,
|
||
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,
|
||
insertIndex: insertIndex,
|
||
});
|
||
|
||
// 计算普通图层数量(非背景、非固定)
|
||
const normalLayersCount = this.layers.value.filter(
|
||
(layer) => !layer.isBackground && !layer.isFixed
|
||
).length;
|
||
|
||
// 如果有额外选项,设置命令的undoable属性
|
||
command.undoable = options.undoable;
|
||
|
||
// 如果是第一个图层,或者普通图层数量小于等于3,设置为不可撤销
|
||
if (this.layers.value.length === 3 || normalLayersCount < 1) {
|
||
command.undoable = false;
|
||
}
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
await this.commandManager.execute(command);
|
||
} else {
|
||
await command.execute();
|
||
}
|
||
|
||
return layerId;
|
||
}
|
||
|
||
/**
|
||
* 创建背景图层
|
||
* @param {string} name 图层名称
|
||
* @returns {string} 创建的背景层ID
|
||
*/
|
||
createBackgroundLayer(name = "背景") {
|
||
// 检查是否已有背景图层
|
||
const hasBackgroundLayer = this.layers.value.some(
|
||
(layer) => layer.isBackground
|
||
);
|
||
|
||
if (hasBackgroundLayer) {
|
||
console.warn("已存在背景层,不再创建新的背景层");
|
||
return null;
|
||
}
|
||
|
||
// 创建背景图层
|
||
const bgLayer = createBackgroundLayer({
|
||
name: name,
|
||
canvasWidth: this.canvasWidth,
|
||
canvasHeight: this.canvasHeight,
|
||
backgroundColor: this.backgroundColor,
|
||
});
|
||
|
||
// 直接创建和执行命令
|
||
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
|
||
const layerId = `fixed_layer_${Date.now()}_${Math.floor(
|
||
Math.random() * 1000
|
||
)}`;
|
||
|
||
// 创建固定图层
|
||
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; // 如果没有背景图层,则添加到最后
|
||
}
|
||
|
||
/**
|
||
* 初始化图层,确保有背景层、固定图层和一个空白图层
|
||
*/
|
||
async initializeLayers() {
|
||
// 如果没有任何图层,创建背景层、固定图层和一个空白图层
|
||
if (this.layers.value.length === 0) {
|
||
// 创建背景图层
|
||
this.createBackgroundLayer();
|
||
|
||
// 创建固定图层,位于背景图层之上
|
||
this.createFixedLayer();
|
||
|
||
// 创建一个空白图层(默认位于背景图层和固定图层之上)
|
||
await this.createLayer("图层 1");
|
||
} else {
|
||
// 检查是否已有背景层
|
||
const hasBackgroundLayer = this.layers.value.some(
|
||
(layer) => layer.isBackground
|
||
);
|
||
|
||
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) {
|
||
await this.createLayer("图层 1");
|
||
}
|
||
}
|
||
|
||
// 排序图层
|
||
this.sortLayers();
|
||
|
||
// 更新对象交互性
|
||
this.updateLayersObjectsInteractivity();
|
||
}
|
||
|
||
/**
|
||
* 添加对象到图层
|
||
* @param {Object} fabricObject fabric对象
|
||
* @param {string} layerId 目标图层ID,如果不提供则使用当前活动图层
|
||
* @returns {Object} 添加的对象
|
||
*/
|
||
addObjectToLayer(fabricObject, layerId = null) {
|
||
const targetLayerId = layerId || this.activeLayerId.value;
|
||
|
||
// 如果没有指定图层ID,也没有活动图层,则返回错误
|
||
if (!targetLayerId) {
|
||
console.warn("没有指定目标图层ID且没有活动图层,无法添加对象");
|
||
return null;
|
||
}
|
||
|
||
// 验证目标图层是否存在
|
||
const { layer: targetLayer } = findLayerRecursively(
|
||
this.layers.value,
|
||
targetLayerId
|
||
);
|
||
if (!targetLayer) {
|
||
console.error(`目标图层 ${targetLayerId} 不存在`);
|
||
return null;
|
||
}
|
||
|
||
// 直接创建和执行命令
|
||
const command = new AddObjectToLayerCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
layerId: targetLayerId,
|
||
fabricObject: fabricObject,
|
||
layerManager: this,
|
||
});
|
||
|
||
// 执行命令
|
||
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
|
||
const objectId =
|
||
typeof objectOrId === "string" ? objectOrId : objectOrId.id;
|
||
|
||
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 = {}) {
|
||
if (layerId === this.activeLayerId.value) {
|
||
console.warn("当前图层已是活动图层,无需重复设置");
|
||
return;
|
||
}
|
||
// 直接创建和执行命令
|
||
const command = new SetActiveLayerCommand({
|
||
layers: this.layers,
|
||
canvas: this.canvas,
|
||
layerManager: this,
|
||
activeLayerId: this.activeLayerId,
|
||
layerId: layerId,
|
||
parentId: options?.parentId,
|
||
editorMode: this.editorMode,
|
||
...options,
|
||
});
|
||
// 如果有额外选项,设置命令的undoable属性
|
||
command.undoable = !!options.undoable;
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
this.commandManager.execute(command);
|
||
} else {
|
||
command.execute();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取当前活动图层
|
||
* @return {Object} layer 图层对象
|
||
*/
|
||
getActiveLayer() {
|
||
if (!this.activeLayerId.value) {
|
||
console.warn(
|
||
"没有活动图层ID,无法获取活动图层 ==== 默认设置第一个图层为活动图层"
|
||
);
|
||
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);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 获取当前活动图层ID
|
||
getActiveLayerId() {
|
||
return this.activeLayerId.value;
|
||
}
|
||
|
||
/**
|
||
* 根据ID获取图层
|
||
* @param {string} layerId 图层ID
|
||
* @returns {Object|null} 图层对象或null
|
||
*/
|
||
getLayerById(layerId) {
|
||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||
return layer;
|
||
}
|
||
|
||
/**
|
||
* 获取当前图层对象的列表
|
||
* @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) {
|
||
// 查找要删除的图层
|
||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||
// 如果是背景层或固定层,不允许删除
|
||
if (layer && (layer.isBackground || layer.isFixed)) {
|
||
console.warn(layer.isBackground ? "背景层不可删除" : "固定层不可删除");
|
||
message.warning(layer.isBackground ? "背景层不可删除" : "固定层不可删除");
|
||
return false;
|
||
}
|
||
|
||
// 如果图层有子图层,提示确认
|
||
if (layer && layer.children && layer.children.length > 0) {
|
||
console.warn("该图层包含子图层,删除将同时删除所有子图层");
|
||
message.warning("该图层包含子图层,删除将同时删除所有子图层");
|
||
}
|
||
|
||
// 直接创建和执行命令
|
||
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;
|
||
}
|
||
|
||
updateLayerObject(layerId, fabricObject) {
|
||
// 查找图层
|
||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||
if (!layer) {
|
||
console.error(`图层 ${layerId} 不存在`);
|
||
return;
|
||
}
|
||
|
||
const tempFabricObject =
|
||
fabricObject?.toObject?.([
|
||
"id",
|
||
"layerId",
|
||
"layerName",
|
||
"isBackgroud",
|
||
"isFixed",
|
||
]) || fabricObject;
|
||
if (layer.isFixed || layer.isBackground) {
|
||
layer.fabricObject = tempFabricObject;
|
||
} else {
|
||
layer.fabricObjects =
|
||
layer.fabricObjects.map((item) => {
|
||
if (item.id === tempFabricObject.id) {
|
||
// 更新对象属性
|
||
return tempFabricObject;
|
||
}
|
||
return item;
|
||
}) || [];
|
||
}
|
||
}
|
||
/**
|
||
* 移动图层位置
|
||
* @param {string} layerId 图层ID
|
||
* @param {string} direction 移动方向,'up'、'down'、'toTop'或'toBottom'
|
||
* @returns {boolean} 是否移动成功
|
||
*/
|
||
moveLayer(layerId, direction) {
|
||
// 查找要移动的图层
|
||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||
|
||
// 如果是背景层或固定层,不允许移动
|
||
if (layer && (layer.isBackground || layer.isFixed)) {
|
||
console.warn(
|
||
layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动")
|
||
);
|
||
return false;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
const result = this.commandManager.execute(command);
|
||
if (result) {
|
||
console.log(`✅ 图层移动成功: ${direction}`);
|
||
}
|
||
return result;
|
||
} else {
|
||
const result = command.execute();
|
||
if (result) {
|
||
// 更新画布渲染顺序
|
||
console.log(`✅ 图层移动成功: ${direction}`);
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换图层可见性
|
||
* @param {string} layerId 图层ID
|
||
* @returns {boolean} 更新后的可见性状态
|
||
*/
|
||
async toggleLayerVisibility(layerId) {
|
||
// 直接创建和执行命令
|
||
const command = new ToggleLayerVisibilityCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
layerId: layerId,
|
||
layerManager: this,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
return await this.commandManager.execute(command);
|
||
} else {
|
||
return await command.execute();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换图层锁定状态
|
||
* @param {string} layerId 图层ID
|
||
* @returns {boolean} 更新后的锁定状态
|
||
*/
|
||
toggleLayerLock(layerId) {
|
||
// 直接创建和执行命令
|
||
const command = new LayerLockCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
layerId: layerId,
|
||
layerManager: this,
|
||
});
|
||
|
||
// 执行命令
|
||
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
|
||
*/
|
||
async groupLayers(layerIds, groupName = null) {
|
||
// 检查参数
|
||
if (!layerIds || !Array.isArray(layerIds) || layerIds.length < 2) {
|
||
console.error("组合图层至少需要两个图层ID");
|
||
return null;
|
||
}
|
||
|
||
// 直接创建和执行命令
|
||
const command = new GroupLayersCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
activeLayerId: this.activeLayerId,
|
||
layerIds: layerIds,
|
||
groupName: groupName,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
return await this.commandManager.execute(command);
|
||
} else {
|
||
return await command.execute();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解组一个组图层
|
||
* @param {string} groupId 要解组的组图层ID
|
||
* @returns {Array<string>} 解组后的图层ID数组
|
||
*/
|
||
async ungroupLayers(groupId) {
|
||
// 查找组图层
|
||
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
||
if (
|
||
!groupLayer ||
|
||
!groupLayer.children ||
|
||
groupLayer.children.length === 0
|
||
) {
|
||
console.error(`${groupId} 不是有效的组图层或不包含子图层`);
|
||
return null;
|
||
}
|
||
|
||
// 直接创建和执行命令
|
||
const command = new UngroupLayersCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
groupId: groupId,
|
||
activeLayerId: this.activeLayerId,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
return await this.commandManager.execute(command);
|
||
} else {
|
||
return await command.execute();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 调整画布和背景图层尺寸
|
||
* @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(), // 传递红绿图模式状态
|
||
});
|
||
|
||
command.undoable = options.undoable; // 设置为可撤销
|
||
// 执行命令
|
||
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 = {}) {
|
||
// 检查是否有除背景层外的其他元素
|
||
const hasOtherElements = this.canvas
|
||
.getObjects()
|
||
.some((obj) => !obj.isBackground);
|
||
|
||
if (hasOtherElements) {
|
||
// 有其他元素时使用带缩放的命令
|
||
const command = new BackgroundSizeWithScaleCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
canvasManager: this.canvasManager,
|
||
newWidth: width,
|
||
newHeight: height,
|
||
isRedGreenMode: this.isInRedGreenMode(), // 传递红绿图模式状态
|
||
});
|
||
|
||
command.undoable = false; // 设置为可撤销
|
||
// 执行命令
|
||
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() {
|
||
if (this.layerSort) {
|
||
// 使用LayerSort的高级排序
|
||
this.layerSort.rearrangeObjects();
|
||
return;
|
||
}
|
||
|
||
// 传统排序逻辑(保持原有逻辑作为备用)
|
||
if (!this.canvas) return;
|
||
|
||
// 获取画布上的所有对象
|
||
const canvasObjects = [
|
||
...this.canvas.getObjects(["id", "layerId", "layerName"]),
|
||
];
|
||
|
||
// 清空画布
|
||
this.canvas.clear();
|
||
|
||
// 按图层顺序(从底到顶)重新添加对象
|
||
// 注意:图层数组是从顶到底的顺序,需要反向遍历
|
||
for (let i = this.layers.value.length - 1; i >= 0; i--) {
|
||
const layer = this.layers.value[i];
|
||
|
||
// 跳过不可见图层
|
||
if (!layer.visible) continue;
|
||
|
||
if (layer.isBackground && layer.fabricObject) {
|
||
// 背景图层
|
||
const originalObj = canvasObjects.find(
|
||
(o) => o.id === layer.fabricObject.id
|
||
);
|
||
if (originalObj) {
|
||
this.canvas.add(originalObj);
|
||
} else {
|
||
this.canvas.add(layer.fabricObject);
|
||
}
|
||
} else if (
|
||
layer.isFixed &&
|
||
layer.fabricObjects &&
|
||
layer.fabricObjects.length > 0
|
||
) {
|
||
// 固定图层
|
||
layer.fabricObjects.forEach((obj) => {
|
||
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
||
if (originalObj) {
|
||
this.canvas.add(originalObj);
|
||
} else {
|
||
this.canvas.add(obj);
|
||
}
|
||
});
|
||
} else if (
|
||
Array.isArray(layer.fabricObjects) &&
|
||
layer.fabricObjects.length > 0
|
||
) {
|
||
// 普通图层,添加所有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) {
|
||
const existsOnCanvas = canvasObjects.some(
|
||
(obj) => obj.id === layer.fabricObject.id
|
||
);
|
||
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,
|
||
backgroundColor: this.backgroundColor,
|
||
editorMode: this.editorMode,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 导入图层数据
|
||
* @param {Object} data 图层数据对象
|
||
* @returns {boolean} 是否导入成功
|
||
*/
|
||
importLayersData(data) {
|
||
if (!data || !data.layers || !Array.isArray(data.layers)) {
|
||
console.error("无效的图层数据");
|
||
return false;
|
||
}
|
||
|
||
const fabric = window.fabric;
|
||
if (!fabric) {
|
||
console.error("未找到fabric库");
|
||
return false;
|
||
}
|
||
|
||
// 清除画布
|
||
this.canvas.clear();
|
||
|
||
// 清空图层列表
|
||
this.layers.value = [];
|
||
|
||
// 设置画布尺寸
|
||
if (data.canvasWidth && data.canvasHeight) {
|
||
this.canvasWidth = data.canvasWidth;
|
||
this.canvasHeight = data.canvasHeight;
|
||
this.canvas.setWidth(data.canvasWidth);
|
||
this.canvas.setHeight(data.canvasHeight);
|
||
}
|
||
|
||
// 设置背景颜色
|
||
if (data.backgroundColor) {
|
||
this.backgroundColor = data.backgroundColor;
|
||
}
|
||
|
||
// 设置编辑模式
|
||
if (data.editorMode) {
|
||
this.editorMode = data.editorMode;
|
||
}
|
||
|
||
// 导入图层
|
||
const promises = data.layers.map((layerData) => {
|
||
return new Promise((resolve) => {
|
||
const newLayer = {
|
||
...layerData,
|
||
fabricObjects: [],
|
||
children: layerData.children || [],
|
||
};
|
||
|
||
// 如果有序列化的对象,恢复它们
|
||
if (
|
||
layerData.serializedObjects &&
|
||
Array.isArray(layerData.serializedObjects)
|
||
) {
|
||
fabric.util.enlivenObjects(layerData.serializedObjects, (objects) => {
|
||
objects.forEach((obj) => {
|
||
// 关联到图层
|
||
obj.layerId = newLayer.id;
|
||
obj.layerName = newLayer.name;
|
||
|
||
// 添加到画布
|
||
this.canvas.add(obj);
|
||
|
||
// 添加到图层
|
||
newLayer.fabricObjects.push(obj);
|
||
});
|
||
|
||
resolve(newLayer);
|
||
});
|
||
} else if (
|
||
layerData.isBackground &&
|
||
layerData.serializedBackgroundObject
|
||
) {
|
||
// 恢复背景对象
|
||
fabric.util.enlivenObjects(
|
||
[layerData.serializedBackgroundObject],
|
||
([bgObject]) => {
|
||
if (bgObject) {
|
||
bgObject.layerId = newLayer.id;
|
||
bgObject.layerName = newLayer.name;
|
||
this.canvas.add(bgObject);
|
||
newLayer.fabricObject = bgObject;
|
||
}
|
||
|
||
resolve(newLayer);
|
||
}
|
||
);
|
||
} else {
|
||
resolve(newLayer);
|
||
}
|
||
});
|
||
});
|
||
|
||
// 等待所有图层加载完成
|
||
Promise.all(promises).then((layers) => {
|
||
// 更新图层列表
|
||
this.layers.value = layers;
|
||
|
||
// 设置活动图层
|
||
if (data.activeLayerId) {
|
||
const activeLayer = layers.find(
|
||
(layer) => layer.id === data.activeLayerId
|
||
);
|
||
if (activeLayer && !activeLayer.isBackground && !activeLayer.locked) {
|
||
this.activeLayerId.value = data.activeLayerId;
|
||
} else {
|
||
// 查找第一个非背景、非锁定的图层
|
||
const firstNormalLayer = layers.find(
|
||
(layer) => !layer.isBackground && !layer.locked
|
||
);
|
||
if (firstNormalLayer) {
|
||
this.activeLayerId.value = firstNormalLayer.id;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确保至少有一个背景图层和一个普通图层
|
||
this.initializeLayers();
|
||
|
||
// 更新对象交互性
|
||
this.updateLayersObjectsInteractivity();
|
||
|
||
// 重新排列对象
|
||
this._rearrangeObjects();
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 复制图层数据到剪贴板
|
||
* @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) =>
|
||
typeof obj.toObject === "function"
|
||
? obj.toObject(["id", "layerId", "layerName"])
|
||
: null
|
||
)
|
||
.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) =>
|
||
typeof obj.toObject === "function"
|
||
? obj.toObject(["id", "layerId", "layerName"])
|
||
: null
|
||
)
|
||
.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;
|
||
}
|
||
|
||
// 创建粘贴图层命令
|
||
const command = new PasteLayerCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
activeLayerId: this.activeLayerId,
|
||
clipboardData: this.clipboardData,
|
||
layerManager: this, // 传递LayerManager实例
|
||
});
|
||
|
||
// 执行命令
|
||
return command.execute();
|
||
}
|
||
|
||
/**
|
||
* 选择所有图层
|
||
*/
|
||
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(
|
||
(layer) =>
|
||
layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
|
||
) || null
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取活动图层上方的插入索引
|
||
* @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,
|
||
layerManager: this,
|
||
fabricImage,
|
||
activeLayer,
|
||
});
|
||
|
||
// 执行命令
|
||
return command;
|
||
}
|
||
|
||
/**
|
||
* 更新背景图层颜色
|
||
* @param {string} backgroundColor 背景颜色
|
||
*/
|
||
updateBackgroundColor(backgroundColor) {
|
||
// 查找背景图层
|
||
const backgroundLayer = this.layers.value.find(
|
||
(layer) => layer.isBackground
|
||
);
|
||
|
||
if (!backgroundLayer) {
|
||
console.warn("没有找到背景图层");
|
||
return;
|
||
}
|
||
|
||
// 直接创建和执行命令
|
||
const command = new UpdateBackgroundCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
canvasManager: this.canvasManager,
|
||
backgroundColor: backgroundColor,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
this.commandManager.execute(command);
|
||
} else {
|
||
command.execute();
|
||
}
|
||
|
||
// 更新存储的背景颜色
|
||
this.backgroundColor = backgroundColor;
|
||
}
|
||
|
||
/**
|
||
* 获取图层缩略图
|
||
* @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} 创建的文本对象
|
||
*/
|
||
async createTextLayerWithObject(textObject, options = {}) {
|
||
if (!this.canvas || !textObject) return null;
|
||
|
||
// 确保对象有ID
|
||
textObject.id =
|
||
textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||
|
||
// 创建文本图层
|
||
const layerName = options.name || "文本图层";
|
||
const layerId = await this.createLayer(layerName, LayerType.TEXT, {
|
||
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:
|
||
options.textBackgroundColor ||
|
||
textObject.textBackgroundColor ||
|
||
"transparent",
|
||
lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
|
||
charSpacing: options.charSpacing || textObject.charSpacing || 0,
|
||
},
|
||
});
|
||
|
||
// 把对象添加到新图层
|
||
textObject.layerId = layerId;
|
||
textObject.layerName = layerName;
|
||
|
||
// 添加到画布,如果还未添加
|
||
const isOnCanvas = this.canvas
|
||
.getObjects()
|
||
.some((obj) => obj.id === textObject.id);
|
||
if (!isOnCanvas) {
|
||
this.canvas.add(textObject);
|
||
}
|
||
|
||
// 更新图层中的对象列表
|
||
const layer = this.getLayerById(layerId);
|
||
if (layer) {
|
||
layer.fabricObjects = layer.fabricObjects || [];
|
||
layer.fabricObjects.push(
|
||
textObject.toObject(["id", "layerId", "layerName"])
|
||
);
|
||
}
|
||
|
||
// 设置此图层为活动图层
|
||
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)) {
|
||
const foundObject = layer.fabricObjects.find(
|
||
(obj) => obj.id === fabricObject.id
|
||
);
|
||
if (foundObject) {
|
||
return layer;
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 拖拽排序图层
|
||
* @param {number} oldIndex 原索引
|
||
* @param {number} newIndex 新索引
|
||
* @param {string} layerId 图层ID
|
||
* @returns {boolean} 是否排序成功
|
||
*/
|
||
async reorderLayers(oldIndex, newIndex, layerId) {
|
||
// 检查索引有效性
|
||
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,
|
||
layerSort: this.layerSort,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
return await this.commandManager.execute(command);
|
||
} else {
|
||
return await command.execute();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 拖拽排序子图层
|
||
* @param {string} parentId 父图层ID
|
||
* @param {number} oldIndex 原索引
|
||
* @param {number} newIndex 新索引
|
||
* @param {string} layerId 子图层ID
|
||
* @returns {boolean} 是否排序成功
|
||
*/
|
||
reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
||
return this.layerSort?.reorderChildLayers(
|
||
parentId,
|
||
oldIndex,
|
||
newIndex,
|
||
layerId
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 根据图层ID和父图层ID查找子图层
|
||
* @param {string} layerId 子图层ID
|
||
* @param {string} parentId 父图层ID
|
||
* @returns {Object|null} 子图层对象或null
|
||
*/
|
||
findChildLayer(layerId, parentId) {
|
||
const parentLayer = this.getLayerById(parentId);
|
||
if (!parentLayer || !parentLayer.children) return null;
|
||
|
||
return parentLayer.children.find((child) => child.id === layerId) || null;
|
||
}
|
||
|
||
/**
|
||
* 删除子图层
|
||
* @param {string} layerId 子图层ID
|
||
* @param {string} parentId 父图层ID
|
||
* @returns {boolean} 是否删除成功
|
||
*/
|
||
async removeChildLayer(layerId, parentId) {
|
||
// 直接创建和执行命令
|
||
const command = new RemoveChildLayerCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
layerId: layerId,
|
||
parentId: parentId,
|
||
activeLayerId: this.activeLayerId,
|
||
layerManager: this,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
return await this.commandManager.execute(command);
|
||
} else {
|
||
return await command.execute();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重命名子图层
|
||
* @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();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换子图层锁定状态
|
||
* @param {string} layerId 子图层ID
|
||
* @param {string} parentId 父图层ID
|
||
* @returns {boolean} 更新后的锁定状态
|
||
*/
|
||
toggleChildLayerLock(layerId, parentId) {
|
||
// 直接创建和执行命令
|
||
const command = new ChildLayerLockCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
layerId: layerId,
|
||
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} 更新后的可见性状态
|
||
*/
|
||
async toggleChildLayerVisibility(layerId, parentId) {
|
||
// 直接创建和执行命令
|
||
const command = new ToggleChildLayerVisibilityCommand({
|
||
canvas: this.canvas,
|
||
layers: this.layers,
|
||
layerId: layerId,
|
||
parentId: parentId,
|
||
layerManager: this,
|
||
});
|
||
|
||
// 执行命令
|
||
if (this.commandManager) {
|
||
return await this.commandManager.execute(command);
|
||
} else {
|
||
return await command.execute();
|
||
}
|
||
}
|
||
|
||
// ==================== 红绿图模式相关操作 ====================
|
||
|
||
// 设置红绿图模式管理器
|
||
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;
|
||
}
|
||
|
||
// ==================== 高级图层排序功能 ====================
|
||
|
||
/**
|
||
* 使用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 =
|
||
async !== null
|
||
? async
|
||
: LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
|
||
|
||
if (shouldUseAsync) {
|
||
console.log(
|
||
`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
|
||
);
|
||
return this.layerSort.rearrangeObjectsAsync();
|
||
} else {
|
||
console.log(
|
||
`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
|
||
);
|
||
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("图层排序工具未初始化,使用基础移动方法");
|
||
return this.moveLayer(
|
||
layerId,
|
||
newIndex > this.getLayerIndex(layerId) ? "down" : "up"
|
||
);
|
||
}
|
||
|
||
const result = this.layerSort.moveLayerToIndex({
|
||
parentId,
|
||
oldIndex,
|
||
newIndex,
|
||
layerId,
|
||
});
|
||
|
||
if (result) {
|
||
console.log(
|
||
`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`
|
||
);
|
||
// 更新对象交互性
|
||
// 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;
|
||
}
|
||
|
||
let layerIndex = this.layers.value.findIndex(
|
||
(layer) => layer.id === layerId
|
||
);
|
||
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) {
|
||
console.log(
|
||
`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`
|
||
);
|
||
// 更新对象交互性
|
||
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) {
|
||
const result = this.reorderChildLayers(
|
||
parentId,
|
||
oldIndex,
|
||
newIndex,
|
||
layerId
|
||
);
|
||
|
||
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);
|
||
if (
|
||
!groupLayer ||
|
||
!groupLayer.children ||
|
||
groupLayer.children.length === 0
|
||
) {
|
||
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;
|
||
}
|
||
|
||
// 查找目标图层
|
||
// const targetLayer = this.getLayerById(targetLayerId);
|
||
const { layer: targetLayer } = findLayerRecursively(
|
||
this.layers.value,
|
||
targetLayerId
|
||
);
|
||
|
||
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) {
|
||
const layer = this.getLayerById(layerId);
|
||
if (!layer) return false;
|
||
|
||
// 不允许栅格化背景图层和固定图层
|
||
if (layer.isBackground || layer.isFixed) {
|
||
return false;
|
||
}
|
||
|
||
// 检查图层是否有内容可以栅格化
|
||
if (layer.type === "group") {
|
||
// 组图层:检查是否有子图层且子图层有内容
|
||
return (
|
||
layer.children &&
|
||
layer.children.length > 0 &&
|
||
layer.children.some(
|
||
(child) => child.fabricObjects && child.fabricObjects.length > 0
|
||
)
|
||
);
|
||
} else {
|
||
// 普通图层:检查是否有对象
|
||
return layer.fabricObjects && layer.fabricObjects.length > 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导出图层 -- 下载图层图片
|
||
* @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);
|
||
const { layer: targetLayer } = findLayerRecursively(
|
||
this.layers.value,
|
||
targetLayerId
|
||
);
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新图层缩略图
|
||
* @param {string} layerId 图层ID
|
||
* @private
|
||
*/
|
||
_updateLayerThumbnail(layerId) {
|
||
if (this.canvas && this.canvas.thumbnailManager) {
|
||
setTimeout(() => {
|
||
this.canvas.thumbnailManager.generateLayerThumbnail(layerId);
|
||
}, 100);
|
||
}
|
||
}
|
||
}
|