From 26581b234a5bb60559b71ec58eebbd1968a08c92 Mon Sep 17 00:00:00 2001 From: bighuixiang <472705331@qq.com> Date: Wed, 16 Jul 2025 18:08:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9=E5=9B=BE?= =?UTF-8?q?=E5=B1=82=E6=93=8D=E4=BD=9C=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=BE=E5=B1=82=E7=B2=98=E8=B4=B4=E5=92=8C?= =?UTF-8?q?=E5=8F=98=E6=8D=A2=E5=91=BD=E4=BB=A4=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E7=BB=84=E5=9B=BE=E5=B1=82=E9=81=AE=E7=BD=A9=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CanvasEditor/commands/LayerCommands.js | 346 ++++++++++++------ .../CanvasEditor/commands/StateCommands.js | 58 ++- .../UpdateGroupMaskPositionCommand.js | 71 ++-- .../components/LayersPanel/LayersPanel.vue | 2 + src/component/Canvas/CanvasEditor/index.vue | 6 +- .../CanvasEditor/managers/CanvasManager.js | 2 + .../CanvasEditor/managers/LayerManager.js | 84 ++++- .../managers/events/CanvasEventManager.js | 4 + 8 files changed, 409 insertions(+), 164 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 70ab4918..95572291 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -13,6 +13,7 @@ import { removeCanvasObjectByObject, } from "../utils/helper"; import { fabric } from "fabric-with-all"; +import { restoreFabricObject } from "../utils/objectHelper"; /** * 添加图层命令 @@ -188,12 +189,13 @@ export class PasteLayerCommand extends Command { this.clipboardData = options.clipboardData; this.layerManager = options.layerManager; - // 新图层相关属性 this.newLayer = null; - this.newLayerId = null; + this.newLayerId = generateId("layer_"); this.insertIndex = null; this.oldActiveLayerId = null; this.createdObjects = []; + this.isGroupLayer = false; + this.childLayerIdMap = new Map(); } async execute() { @@ -201,27 +203,9 @@ export class PasteLayerCommand extends Command { console.error("剪贴板中没有图层数据"); return null; } - - const data = this.clipboardData; - const fabric = window.fabric; - - if (!fabric) { - console.error("未找到fabric库"); - return null; - } - - // 生成新的图层ID - this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; - - // 创建新图层 - this.newLayer = { - ...data, - id: this.newLayerId, - name: `${data.name} 副本`, - fabricObjects: [], - isCut: undefined, - serializedObjects: undefined, - }; + const data = JSON.parse(JSON.stringify(this.clipboardData)); + // 判断是否为组图层 + this.isGroupLayer = data.type === "group" || !!data?.children?.length; // 保存当前活动图层ID this.oldActiveLayerId = this.activeLayerId.value; @@ -229,144 +213,263 @@ export class PasteLayerCommand extends Command { // 计算插入位置 this.insertIndex = this.layerManager._getInsertIndexAboveActiveLayer(); - // 执行添加图层操作 - if (this.insertIndex !== undefined && this.insertIndex !== null) { - this.layers.value.splice(this.insertIndex, 0, this.newLayer); - } else { - this.layers.value.push(this.newLayer); - } + if (this.isGroupLayer) { + // 粘贴为新的组图层,递归生成子图层ID + const { groupLayer, allObjects } = await this._createGroupLayerFromClipboard(data); + if (groupLayer?.clippingMask) { + // 给遮罩蒙层也添加上偏移 + groupLayer.clippingMask.left += 30; + groupLayer.clippingMask.top += 30; + } + this.newLayer = groupLayer; + this.newLayer.id = this.newLayerId; - // 更新活动图层 - if (!this.newLayer.isBackground) { + // 插入组图层 + if (this.insertIndex !== undefined && this.insertIndex !== null) { + this.layers.value.splice(this.insertIndex, 0, this.newLayer); + } else { + this.layers.value.push(this.newLayer); + } + // 重新创建遮罩对象 + const clippingMaskFabricObject = + (await restoreFabricObject(groupLayer?.clippingMask, this.canvas)) || null; + + clippingMaskFabricObject.clipPath = null; + clippingMaskFabricObject.set({ + absolutePositioned: true, + }); + + clippingMaskFabricObject.dirty = true; + clippingMaskFabricObject.setCoords(); + // 添加所有对象到画布 + allObjects.forEach((obj) => { + obj.clipPath = clippingMaskFabricObject; + obj.dirty = true; + obj.setCoords(); + this.canvas.add(obj); + }); + this.createdObjects = allObjects; + + // 设置活动图层为新组图层 this.activeLayerId.value = this.newLayer.id; - } - - // 如果有序列化的对象,异步恢复它们 - if ( - data.serializedObjects && - Array.isArray(data.serializedObjects) && - data.serializedObjects.length > 0 - ) { - await this._restoreObjectsAsync(data); } else { - this._onObjectsRestored(data); + // 普通图层粘贴逻辑 + this.newLayer = { + ...data, + id: this.newLayerId, + name: `${data.name} 副本`, + fabricObjects: [], + isCut: undefined, + serializedObjects: undefined, + }; + + if (this.insertIndex !== undefined && this.insertIndex !== null) { + this.layers.value.splice(this.insertIndex, 0, this.newLayer); + } else { + this.layers.value.push(this.newLayer); + } + + if (!this.newLayer.isBackground) { + this.activeLayerId.value = this.newLayer.id; + } + + if ( + data.serializedObjects && + Array.isArray(data.serializedObjects) && + data.serializedObjects.length > 0 + ) { + await this._restoreObjectsAsync(data); + } else { + this._onObjectsRestored(data); + } } + // 取消画布激活对象 + + this.canvas.discardActiveObject(); // 取消当前活动对象 + // 重新排序 + await this.layerManager?.sortLayersWithTool?.(); + + // 更新画布上对象的可选择状态 + await this.layerManager?.updateLayersObjectsInteractivity?.(); return this.newLayerId; } /** - * 异步恢复序列化的对象 - * @param {Object} data 剪贴板数据 - * @private + * 递归创建组图层及其所有子图层和对象 */ - async _restoreObjectsAsync(data) { + async _createGroupLayerFromClipboard(data) { + const groupId = generateId("group_layer_"); + const childLayerIdMap = new Map(); + const allObjects = []; + + // 递归处理子图层 + const processChildren = async (children) => { + const result = []; + for (const child of children) { + const newChildId = generateId("layer_"); + childLayerIdMap.set(child.id, newChildId); + + // 递归处理子组 + let childLayer; + if (child.type === "group" && Array.isArray(child.children)) { + const { groupLayer, allObjects: childObjs } = + await this._createGroupLayerFromClipboard(child); + childLayer = groupLayer; + allObjects.push(...childObjs); + } else { + // 普通子图层 + childLayer = { + ...child, + id: newChildId, + name: `${child.name} 副本`, + fabricObjects: [], + isCut: undefined, + serializedObjects: undefined, + }; + // 恢复对象 + if ( + child.serializedObjects && + Array.isArray(child.serializedObjects) && + child.serializedObjects.length > 0 + ) { + const objs = await this._enlivenObjects( + child.serializedObjects, + newChildId, + `${child.name} 副本` + ); + childLayer.fabricObjects = objs.map((obj) => + obj.toObject(["id", "layerId", "layerName"]) + ); + allObjects.push(...objs); + } + } + result.push(childLayer); + } + return result; + }; + + // 处理当前组的子图层 + const children = await processChildren(data.children); + + // 创建组图层对象 + const groupLayer = { + ...data, + id: groupId, + name: `${data.name} 副本`, + type: "group", + children, + fabricObjects: [], + isCut: undefined, + serializedObjects: undefined, + }; + + this.childLayerIdMap = childLayerIdMap; + + // 取消画布激活对象 + + this.canvas.discardActiveObject(); // 取消当前活动对象 + return { groupLayer, allObjects }; + } + + /** + * 恢复对象(用于普通图层和组图层的子图层) + */ + async _enlivenObjects(serializedObjects, layerId, layerName) { return new Promise((resolve, reject) => { - fabric.util.enlivenObjects(data.serializedObjects, (objects) => { + fabric.util.enlivenObjects(serializedObjects, (objects) => { try { objects.forEach((obj) => { - // 生成新的对象ID - const newObjId = `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`; - obj.id = newObjId; - obj.layerId = this.newLayerId; - obj.layerName = this.newLayer.name; - - // 如果是复制操作,给对象添加偏移量 - if (!data.isCut) { - const offset = 10; - if (obj.left !== undefined) obj.left += offset; - if (obj.top !== undefined) obj.top += offset; - } - - // 添加到画布 - this.canvas.add(obj); - - // 添加到图层 - this.newLayer.fabricObjects.push(obj); - - // 记录创建的对象,用于撤销 - this.createdObjects.push(obj); + obj.id = generateId("obj_"); + obj.layerId = layerId; + obj.layerName = layerName; + // 添加偏移 + const offset = 30; + if (obj.left !== undefined) obj.left += offset; + if (obj.top !== undefined) obj.top += offset; }); - - this._onObjectsRestored(data); - resolve(); + resolve(objects); + } catch (error) { + reject(error); + } + }); + }); + } + + async _restoreObjectsAsync(data) { + return new Promise((resolve, reject) => { + fabric.util.enlivenObjects(data.serializedObjects, (objects) => { + try { + objects.forEach((obj) => { + const newObjId = generateId("obj_"); + obj.id = newObjId; + obj.layerId = this.newLayerId; + obj.layerName = this.newLayer.name; + if (!data.isCut) { + const offset = 30; + if (obj.left !== undefined) obj.left += offset; + if (obj.top !== undefined) obj.top += offset; + } + this.canvas.add(obj); + this.newLayer.fabricObjects.push(obj.toObject(["id", "layerId", "layerName"])); + this.createdObjects.push(obj); + }); + this._onObjectsRestored(data); + resolve(); } catch (error) { - console.error("恢复对象时发生错误:", error); reject(error); } }); }); } - /** - * 对象恢复完成后的处理 - * @param {Object} data 剪贴板数据 - * @private - */ _onObjectsRestored(data) { - // 更新对象交互性 this.layerManager?.updateLayersObjectsInteractivity?.(); - - // 重新排列对象 this.layerManager?._rearrangeObjects?.(); - - // 判断如果是剪切操作,粘贴完后需要删除剪贴板数据 if (data.isCut && this.layerManager) { this.layerManager.clipboardData = null; - console.log(`已粘贴图层:${this.newLayer.name}(剪切)`); - } else { - console.log(`已粘贴图层:${this.newLayer.name}(复制)`); } - - // 重新渲染画布 if (this.canvas) { this.canvas.renderAll(); } } - undo() { + async undo() { if (!this.newLayer || !this.newLayerId) return; - - // 从图层列表删除该图层 - const index = this.layers.value.findIndex((layer) => layer.id === this.newLayerId); - if (index !== -1) { - this.layers.value.splice(index, 1); + const { layer, parent } = findLayerRecursively(this.layers.value, this.newLayerId); + if (!layer) { + console.error(`图层 ${this.newLayerId} 不存在, 无法撤销`); + return false; } - // 恢复原活动图层 - this.activeLayerId.value = this.oldActiveLayerId; + if (parent) { + // 如果是子图层,直接从父图层中删除 + const index = parent.children.findIndex((child) => child.id === this.newLayerId); + if (index !== -1) { + parent.children.splice(index, 1); + } + } else { + // 如果是顶级图层,直接从图层列表中删除 + const index = this.layers.value.findIndex((l) => l.id === this.newLayerId); + if (index !== -1) { + this.layers.value.splice(index, 1); + } + } - // 从画布移除所有创建的对象 - this.createdObjects.forEach((obj) => { - this.canvas.remove(obj); + this.createdObjects?.forEach((obj) => { + removeCanvasObjectByObject(this.canvas, obj); }); - // 如果图层有其他fabric对象,也要移除 - if (this.newLayer.fabricObjects && this.newLayer.fabricObjects.length > 0) { - this.newLayer.fabricObjects.forEach((obj) => { - if (!this.createdObjects.includes(obj)) { - this.canvas.remove(obj); - } - }); - } + this.activeLayerId.value = this.oldActiveLayerId; - // 如果是剪切操作的撤销,需要恢复剪贴板数据 - if (this.clipboardData && this.clipboardData.isCut && this.layerManager) { - this.layerManager.clipboardData = this.clipboardData; - } + // 取消激活对象 - // 重新渲染画布 - if (this.canvas) { - this.canvas.renderAll(); - } - - // 更新对象交互性 - if ( - this.layerManager && - typeof this.layerManager.updateLayersObjectsInteractivity === "function" - ) { - this.layerManager.updateLayersObjectsInteractivity(); - } + this.canvas.discardActiveObject(); // 取消当前活动对象 + // 重新排序 + await this.layerManager?.sortLayersWithTool?.(); + // 更新画布上对象的可选择状态 + await this.layerManager?.updateLayersObjectsInteractivity?.(); + return true; } getInfo() { @@ -375,6 +478,7 @@ export class PasteLayerCommand extends Command { layerName: this.newLayer?.name || "未知图层", layerId: this.newLayerId, objectCount: this.createdObjects.length, + isGroupLayer: this.isGroupLayer, }; } } diff --git a/src/component/Canvas/CanvasEditor/commands/StateCommands.js b/src/component/Canvas/CanvasEditor/commands/StateCommands.js index 10ebb15a..a7eaa739 100644 --- a/src/component/Canvas/CanvasEditor/commands/StateCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/StateCommands.js @@ -1,3 +1,6 @@ +import { findObjectById } from "../utils/helper"; +import { findLayerRecursively } from "../utils/layerHelper"; +import { restoreFabricObject } from "../utils/objectHelper"; import { Command } from "./Command"; /** @@ -18,6 +21,18 @@ export class TransformCommand extends Command { this.initialState = options.initialState || null; this.finalState = options.finalState || null; this.objectType = options.objectType || "object"; + this.layerManager = options.layerManager; + this.layers = options.layers || null; + this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID + + const targetObject = findObjectById(this.canvas, this.objectId)?.object || null; + + const { layer, parent } = findLayerRecursively(this.layers.value, targetObject?.layerId); + + this.layer = layer; + this.parent = parent; + + this.isSginleObject = parent?.id === this.lastSelectLayerId?.value; // 是否需要记录遮罩的变换位置 如果是组图层且组下只有一个图层有对象 且 用户最后点击的是父图层 则记录遮罩变更 } /** @@ -25,7 +40,7 @@ export class TransformCommand extends Command { * 如果是首次执行,记录初始和最终状态 * 如果是重做,应用最终状态 */ - execute() { + async execute() { if (!this.finalState) { console.warn("没有最终状态可应用"); return false; @@ -39,7 +54,7 @@ export class TransformCommand extends Command { } // 应用最终变换状态 - this._applyTransform(targetObject, this.finalState); + await this._applyTransform(targetObject, this.finalState); // 触发画布更新 this.canvas.renderAll(); @@ -51,7 +66,7 @@ export class TransformCommand extends Command { * 撤销命令 * 应用初始状态 */ - undo() { + async undo() { if (!this.initialState) { console.warn("没有初始状态可恢复"); return false; @@ -65,7 +80,7 @@ export class TransformCommand extends Command { } // 应用初始变换状态 - this._applyTransform(targetObject, this.initialState); + await this._applyTransform(targetObject, this.initialState); // 触发画布更新 this.canvas.renderAll(); @@ -86,8 +101,41 @@ export class TransformCommand extends Command { * 应用变换状态到对象 * @private */ - _applyTransform(object, transformState) { + async _applyTransform(object, transformState) { if (!object || !transformState) return; + // 变换遮罩层 - 如果当前图层是组图层,且有遮罩并且组下只有一个图层有对象 则应用遮罩转换 + if ( + this.parent && + this.parent?.clippingMask && + this.parent?.children?.length === 1 && + this.isSginleObject + ) { + // 计算对象的变换位置 + const moveLeft = object.left - transformState.left; // 计算移动的水平距离 + const moveTop = object.top - transformState.top; // 计算移动的垂直距离 + + this.parent.clippingMask.left -= moveLeft; + this.parent.clippingMask.top -= moveTop; + + // 重新创建遮罩对象 + const clippingMaskFabricObject = await restoreFabricObject( + this.parent.clippingMask, + this.canvas + ); + + if (clippingMaskFabricObject) { + clippingMaskFabricObject.clipPath = null; + clippingMaskFabricObject.set({ + absolutePositioned: true, + }); + + clippingMaskFabricObject.dirty = true; + clippingMaskFabricObject.setCoords(); + + const clippingMask = this.parent.clippingMask; + object.clipPath = clippingMask; + } + } // 应用变换属性,只设置真正变化的值 Object.entries(transformState).forEach(([key, value]) => { diff --git a/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js b/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js index da2249ee..4ef5be80 100644 --- a/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js @@ -1,4 +1,4 @@ -import { findLayerRecursively } from "../utils/layerHelper"; +import { findLayerRecursively, isGroupLayer } from "../utils/layerHelper"; import { restoreFabricObject } from "../utils/objectHelper"; import { Command } from "./Command"; @@ -23,6 +23,8 @@ export class UpdateGroupMaskPositionCommand extends Command { this.maskInitialTop = options.maskInitialTop || 0; this.isExecuteRealtime = options.isExecuteRealtime || false; this.activeSelection = this.canvas.getActiveObject() || {}; + this.isSginleObject = options.isSginleObject || false; // 组内是否单个对象移动 + this.target = options.target || null; // 当前操作的目标对象 this.isFristExecute = true; @@ -42,17 +44,18 @@ export class UpdateGroupMaskPositionCommand extends Command { }; // 收集当前选择的所有对象位置 - this.originalObjectsPostion = this.activeSelection.getObjects().map((obj) => { - return { - left: obj.left || 0, - top: obj.top || 0, - id: obj.id, - }; - }); + this.originalObjectsPostion = + this.activeSelection.getObjects?.()?.map((obj) => { + return { + left: obj.left || 0, + top: obj.top || 0, + id: obj.id, + }; + }) || this.target?.getBoundingRect?.(true, true); this.originalSelectionPosition = { - left: this.activeSelection.left || 0, - top: this.activeSelection.top || 0, + left: this.isSginleObject ? this.target.left : this.activeSelection.left || 0, + top: this.isSginleObject ? this.target.top : this.activeSelection.top || 0, }; console.log( @@ -147,6 +150,10 @@ export class UpdateGroupMaskPositionCommand extends Command { // 更新所有使用此遮罩的子图层对象(不需要等待) this._updateChildObjectsClipPath(layer); + this.target.set({ + isSginleObject: this.isSginleObject, + }); + return true; } catch (error) { console.error("实时更新组图层遮罩位置失败:", error); @@ -217,21 +224,41 @@ export class UpdateGroupMaskPositionCommand extends Command { } if (isUndo) { - this.activeSelection.set({ - left: this.originalSelectionPosition.left - this.deltaX, - top: this.originalSelectionPosition.top - this.deltaY, - }); - this.activeSelection.dirty = true; - this.activeSelection.setCoords(); + if (this.isSginleObject) { + // 如果是单个对象移动,直接更新目标对象位置 + // this.target.set({ + // left: this.target.left - this.deltaX, + // top: this.target.top - this.deltaY, + // }); + // this.target.dirty = true; + // this.target.setCoords(); + } else { + this.activeSelection.set({ + left: this.originalSelectionPosition.left - this.deltaX, + top: this.originalSelectionPosition.top - this.deltaY, + }); + this.activeSelection.dirty = true; + this.activeSelection.setCoords(); + } } if (isExecute && !this.isFristExecute) { - this.activeSelection.set({ - left: this.activeSelection.left + this.deltaX, - top: this.activeSelection.top + this.deltaY, - }); - this.activeSelection.dirty = true; - this.activeSelection.setCoords(); + if (this.isSginleObject) { + // 如果是单个对象移动,直接更新目标对象位置 + // this.target.set({ + // left: this.target.left + this.deltaX, + // top: this.target.top + this.deltaY, + // }); + // this.target.dirty = true; + // this.target.setCoords(); + } else { + this.activeSelection.set({ + left: this.activeSelection.left + this.deltaX, + top: this.activeSelection.top + this.deltaY, + }); + this.activeSelection.dirty = true; + this.activeSelection.setCoords(); + } } // 触发画布重新渲染 diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index d01efea6..4def5915 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -40,6 +40,7 @@ const emit = defineEmits([ ]); const layers = inject("layers", []); +const lastSelectLayerId = inject("lastSelectLayerId", null); const layerManager = inject("layerManager", {}); @@ -573,6 +574,7 @@ function handleLayerClick(layer, event) { if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) { toggleLayerSelection(layer, event); } else { + lastSelectLayerId.value = layer.id; // 更新最后选中的图层ID // 普通点击:进入单选模式 // selectedLayerIds.value = [layer.id]; // isMultiSelectMode.value = false; diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 582393fd..b3eb26e4 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -33,7 +33,7 @@ import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑 import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板 import { LayerType, OperationType } from "./utils/layerHelper.js"; import { ToolManager } from "./managers/toolManager.js"; -import { fabric } from "fabric-with-all"; +// import { fabric } from "fabric-with-all"; import { uploadImageAndCreateLayer, loadImageUrlToLayer, loadImage } from "./utils/imageHelper.js"; // import MinimapPanel from "./components/MinimapPanel.vue"; const KeyboardShortcutHelp = defineAsyncComponent( @@ -124,6 +124,7 @@ provide("isShowLayerPanel", isShowLayerPanel); // 提供红绿图模式状态给 const layers = ref([]); const activeLayerId = ref(null); const activeElementId = ref(null); +const lastSelectLayerId = ref(null); // 最后选择的图层ID // 当前选择的工具 const activeTool = ref(CanvasConfig.defaultTool); // 默认工具 @@ -199,6 +200,7 @@ onMounted(async () => { // backgroundColor: canvasColor.value, currentZoom, layers, + lastSelectLayerId, canvasWidth, canvasHeight, canvasColor, @@ -275,6 +277,8 @@ onMounted(async () => { provide("liquifyManager", () => liquifyManager); // 提供液化管理器 provide("texturePresetManager", texturePresetManager); // 提供纹理预设管理器 provide("layers", layers); // 提供图层数据 + provide("lastSelectLayerId", lastSelectLayerId); // 提供最后选择的图层ID + // 初始化网格设置 // toggleGridVisibility(gridEnabled.value); diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index 23e1db51..76c2307e 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -35,6 +35,7 @@ export class CanvasManager { this.maskLayer = null; // 添加蒙层引用 this.editorMode = CanvasConfig.defaultTool; // 默认编辑器模式 this.layers = options.layers || null; // 图层引用 + this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID this.canvasWidth = options.canvasWidth || this.width; // 画布宽度 this.canvasHeight = options.canvasHeight || this.height; // 画布高度 this.canvasColor = options.canvasColor || "#ffffff"; // 画布背景颜色 @@ -282,6 +283,7 @@ export class CanvasManager { activeElementId: activeElementId, layerManager: layerManager, layers: this.layers, + lastSelectLayerId: this.lastSelectLayerId, }); // 设置动画交互效果 diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index a058690e..41c72700 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -971,7 +971,6 @@ export class LayerManager { // allObjects.push(object); // } // } - if (allObjects.length) { // 切换到选择模式 this?.toolManager?.setTool(OperationType.SELECT); @@ -1525,13 +1524,32 @@ export class LayerManager { }; } + /** + * 获取当前图层序列化对象 + * @param {Object} layer 图层对象 + * @returns {Array} 图层中的序列化对象列表 + */ + getCurrLayerSerializedObjects(layer) { + if (!layer || layer?.fabricObjects?.length === 0) { + return []; + } + // 序列化图层中的对象 + return layer.fabricObjects + .map((obj) => { + const { object } = findObjectById(this.canvas, obj.id); + if (object) return object.toObject(["id", "layerId", "layerName"]); + return false; + }) + .filter(Boolean); + } + /** * 复制图层数据到剪贴板 * @param {string} layerId 要复制的图层ID * @returns {Object} 复制的图层数据 */ copyLayer(layerId) { - const layer = this.layers.value.find((l) => l.id === layerId); + const { layer } = findLayerRecursively(this.layers.value, layerId); if (!layer) { console.error(`图层 ${layerId} 不存在`); return null; @@ -1548,11 +1566,18 @@ export class LayerManager { // 序列化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); + layerCopy.serializedObjects = this.getCurrLayerSerializedObjects(layer); + } + + // 处理子图层 + if (layer?.children?.length) { + layerCopy.children = layer.children.map((child) => { + const childCopy = JSON.parse(JSON.stringify(child)); + if (child.fabricObjects && child.fabricObjects.length > 0) { + childCopy.serializedObjects = this.getCurrLayerSerializedObjects(child); + } + return childCopy; + }); } // 存储到剪贴板 @@ -1653,7 +1678,7 @@ export class LayerManager { * 粘贴图层 * @returns {string} 新创建的图层ID */ - pasteLayer() { + async pasteLayer() { if (!this.clipboardData) { console.error("剪贴板中没有图层数据"); return null; @@ -1668,8 +1693,20 @@ export class LayerManager { layerManager: this, // 传递LayerManager实例 }); + // 设置命令为可撤销 + command.undoable = true; + if (this.commandManager) { + // 使用命令管理器执行命令 + const result = await this.commandManager.execute(command); + this.clipboardData = null; // 清空剪贴板数据 复制一次 + // 执行命令 + return result; + } + + const result = await command.execute(); + this.clipboardData = null; // 清空剪贴板数据 复制一次就清空,避免重复粘贴 出现 错误后也清空 总之就是清空 不用给自己找麻烦 // 执行命令 - return command.execute(); + return result; } /** @@ -2893,10 +2930,11 @@ export class LayerManager { let lastUpdateTime = 0; let hasMoved = false; // 追踪是否实际发生了移动 const UPDATE_THRESHOLD = 32; // 约60fps - // 移动开始事件处理 const handleMovingStart = (e) => { - if (e.target === activeSelection) { + // 判断活动对象是否只有一个 + const isSginleObject = e.target === activeSelection?._objects?.[0]; + if (e.target === activeSelection || isSginleObject) { hasMoved = false; // 重置移动状态 console.log("🎯 开始移动组选择对象"); // 记录遮罩初始位置 @@ -2905,8 +2943,8 @@ export class LayerManager { `${layer.clippingMask.left || 0}, ${layer.clippingMask.top || 0}` ); // 记录初始位置 - initialLeft = activeSelection.left; - initialTop = activeSelection.top; + initialLeft = isSginleObject ? e.target.left : activeSelection.left; + initialTop = isSginleObject ? e.target.top : activeSelection.top; maskInitialLeft = layer.clippingMask.left || 0; maskInitialTop = layer.clippingMask.top || 0; @@ -2914,8 +2952,10 @@ export class LayerManager { }; // 移动中事件处理函数(带节流) const handleMoving = (e) => { + // 判断活动对象是否只有一个 + const isSginleObject = e.target === activeSelection?._objects?.[0]; const target = e.target; - if (target === activeSelection) { + if (target === activeSelection || isSginleObject) { hasMoved = true; // 标记发生了移动 const now = Date.now(); @@ -2949,6 +2989,8 @@ export class LayerManager { maskInitialLeft: maskInitialLeft, maskInitialTop: maskInitialTop, isExecuteRealtime: true, + isSginleObject, + target, }); // 执行实时更新 @@ -2963,7 +3005,15 @@ export class LayerManager { // 修改事件处理函数 - 使用 object:modified 替代 object:moved const handleModified = (e) => { const target = e.target; - if (target === activeSelection && hasMoved) { + // 判断活动对象是否只有一个 + const isSginleObject = e.target === activeSelection?._objects?.[0]; + if (isSginleObject) { + // 如果是单个对象,不处理 + console.log("🚫 单个对象不处理移动完成"); + hasMoved = false; // 重置移动状态 + return; + } + if ((target === activeSelection || isSginleObject) && hasMoved) { console.log("✅ 组选择对象移动完成"); // 计算最终移动距离 @@ -2982,8 +3032,12 @@ export class LayerManager { maskInitialLeft: maskInitialLeft, maskInitialTop: maskInitialTop, activeSelection, + isSginleObject, + target, }); + command.undoable = isSginleObject ? false : true; // 设置为可撤销的命令 + // 执行可撤销的命令 if (this.commandManager) { this.commandManager.execute(command); diff --git a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js index 4e6b60e8..6408878f 100644 --- a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js +++ b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js @@ -13,6 +13,7 @@ export class CanvasEventManager { this.activeElementId = options.activeElementId || { value: null }; this.layerManager = options.layerManager || null; this.layers = options.layers || null; + this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID // 事件处理的内部状态 - 优化设备检测 this.deviceInfo = this._detectDeviceType(); @@ -410,6 +411,9 @@ export class CanvasEventManager { finalState: TransformCommand.captureTransformState(activeObj), objectType: activeObj.type, name: `变换 ${activeObj.type || "对象"}`, + layerManager: this.layerManager, + layers: this.layers, + lastSelectLayerId: this.lastSelectLayerId, }); // 执行并将命令添加到历史栈