diff --git a/src/component/Canvas/CanvasEditor/commands/CrossLevelMoveCommands.js b/src/component/Canvas/CanvasEditor/commands/CrossLevelMoveCommands.js index c9f12343..dc91bd91 100644 --- a/src/component/Canvas/CanvasEditor/commands/CrossLevelMoveCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/CrossLevelMoveCommands.js @@ -181,7 +181,7 @@ export class CrossLevelMoveCommand extends Command { return ancestor ? checkChildren(ancestor.children) : false; } - execute() { + async execute() { console.log("🎯 执行跨层级移动命令:", { layerId: this.layerId, from: this.fromContainerType, @@ -220,10 +220,13 @@ export class CrossLevelMoveCommand extends Command { this.saveAfterState(); // 刷新画布 - if (this.canvas) { - this.canvas.renderAll(); - console.log("🎨 画布已刷新"); - } + // if (this.canvas) { + // this.canvas.renderAll(); + // console.log("🎨 画布已刷新"); + // } + + await this.layerManager?.updateLayersObjectsInteractivity(); + this.canvas?.renderAll(); console.log("✅ 跨层级移动命令执行成功"); return true; @@ -248,7 +251,7 @@ export class CrossLevelMoveCommand extends Command { } } - undo() { + async undo() { if (!this.beforeState) { throw new Error("没有保存的前置状态,无法撤销"); } @@ -262,14 +265,10 @@ export class CrossLevelMoveCommand extends Command { // 恢复fabric对象状态 this.restoreFabricObjectStates(this.beforeState.fabricObjects); + await this.layerManager?.updateLayersObjectsInteractivity(); + this.canvas?.renderAll(); // 刷新画布 - if (this.canvas) { - // 使用 requestAnimationFrame 确保DOM更新完成后再渲染 - requestAnimationFrame(() => { - this.canvas.renderAll(); - }); - } - + this.canvas?.renderAll(); console.log("✅ 跨层级移动命令撤销成功"); return true; } catch (error) { @@ -331,7 +330,7 @@ export class CrossLevelMoveCommand extends Command { /** * 重做命令 */ - redo() { + async redo() { if (!this.afterState) { throw new Error("没有保存的后置状态,无法重做"); } @@ -345,13 +344,9 @@ export class CrossLevelMoveCommand extends Command { // 恢复fabric对象状态 this.restoreFabricObjectStates(this.afterState.fabricObjects); + await this.layerManager?.updateLayersObjectsInteractivity(); // 刷新画布 - if (this.canvas) { - // 使用 requestAnimationFrame 确保DOM更新完成后再渲染 - requestAnimationFrame(() => { - this.canvas.renderAll(); - }); - } + this.canvas?.renderAll(); console.log("✅ 跨层级移动命令重做成功"); return true; diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js index 3c04459e..b7702422 100644 --- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js @@ -2,6 +2,7 @@ import { createLayer, findInChildLayers, LayerType, + OperationType, } from "../utils/layerHelper.js"; import { createRasterizedImage } from "../utils/selectionToImage.js"; import { CompositeCommand, Command } from "./Command.js"; @@ -11,6 +12,7 @@ import { } from "./LayerCommands.js"; import { fabric } from "fabric-with-all"; import { generateId } from "../utils/helper.js"; +import { ToolCommand } from "./ToolCommands.js"; /** * 套索抠图命令 @@ -256,16 +258,16 @@ export class LassoCutoutCommand extends CompositeCommand { // this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), // ]; // 2. 删除原图层命令 - const removeOriginalLayerCmd = new RemoveLayerCommand({ - canvas: this.canvas, - layers: this.layerManager.layers, - layerId: this.originalLayer.id, - activeLayerId: this.layerManager.activeLayerId, - }); + // const removeOriginalLayerCmd = new RemoveLayerCommand({ + // canvas: this.canvas, + // layers: this.layerManager.layers, + // layerId: this.originalLayer.id, + // activeLayerId: this.layerManager.activeLayerId,l + // }); - // 执行删除原图层命令 - await removeOriginalLayerCmd.execute(); - this.executedCommands.push(removeOriginalLayerCmd); + // // 执行删除原图层命令 + // await removeOriginalLayerCmd.execute(); + // this.executedCommands.push(removeOriginalLayerCmd); this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像 @@ -275,6 +277,21 @@ export class LassoCutoutCommand extends CompositeCommand { this.layerManager.activeLayerId.value = selectLayer.id; // 设置新组图层为活动图层 + // 切换工具到选择模式 + // 3. 切换工具到选择模式命令 + if (this.toolManager) { + const toolCmd = new ToolCommand({ + toolManager: this.toolManager, + tool: OperationType.SELECT, + previousTool: this.toolManager.getCurrentTool(), + }); + + // 执行工具切换命令 + await toolCmd.execute(); + this.commands.push(toolCmd); + this.executedCommands.push(toolCmd); + } + this.canvas.discardActiveObject(); // this.canvas.setActiveObject(this.fabricImage); await this.layerManager.updateLayersObjectsInteractivity(true); diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 0a28d67d..092bbf05 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -2033,8 +2033,12 @@ export class LayerObjectsToGroupCommand extends Command { const allObjects = [...originalObjects, ...newObjects]; + const clipPath = allObjects?.[0]?.clipPath || null; // 从画布中移除所有要组合的对象 allObjects.forEach((obj) => { + obj.clipPath = null; + obj.dirty = true; // 标记为脏对象 + // obj.setCoords(); obj.opacity = 1; removeCanvasObjectByObject(this.canvas, obj); }); @@ -2052,6 +2056,10 @@ export class LayerObjectsToGroupCommand extends Command { // 添加组对象到画布 this.canvas.add(groupObject); + groupObject.clipPath = clipPath; // 恢复剪切路径 + groupObject.dirty = true; // 标记为脏对象 + groupObject.setCoords(); + // 更新图层的对象列表 // this.activeLayer.fabricObjects = [groupObject]; this.activeLayer.fabricObjects = [ diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue index bc2daf5c..2e54f410 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue @@ -498,11 +498,13 @@ const canDeleteComputed = computed(() => { .chosen { opacity: 0.8; - transform: scale(1.02); + transform: scale(1); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); z-index: 999; background: linear-gradient(90deg, #fff2e8 0%, #ffd8bf 100%); - border: 2px solid #fa8c16; + border: 1px solid #fa8c16; + // border-left: none; + // border-right: none; } .drag { diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index 266efc24..78f569dc 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -1348,9 +1348,10 @@ async function executeDirectMove(moveData) { } // 刷新画布渲染 - if (layerManager?.canvas) { - layerManager.canvas.renderAll(); - } + // if (layerManager?.canvas) { + // layerManager.canvas.renderAll(); + // } + await layerManager?.updateLayersObjectsInteractivity?.(); // 清除选择状态 clearSelection(); diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/layersPanel.less b/src/component/Canvas/CanvasEditor/components/LayersPanel/layersPanel.less index fea6a3d7..8aa19673 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/layersPanel.less +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/layersPanel.less @@ -568,7 +568,7 @@ .ghost { opacity: 0.4; background-color: #fff7e6; - border: 2px dashed #faad14; + border: 1px dashed #faad14; } .chosen { diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index c3ee6485..52702ce3 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -783,6 +783,7 @@ defineExpose({ exportImage: ({ isContainBg = false, // 是否包含背景图层 isContainFixed = false, // 是否包含固定图层 + isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小 layerId = "", // 导出具体图层ID layerIdArray = [], // 导出多个图层ID数组 expPicType = "png", // 导出图片类型 JPG 或 PNG ,SVG @@ -790,6 +791,7 @@ defineExpose({ return canvasManager.exportImage({ isContainBg, isContainFixed, + isCropByBg, layerId, layerIdArray, expPicType, diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index c393faab..e44eba5e 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -977,7 +977,6 @@ export class CanvasManager { } this.layers.value = tempLayers; - debugger; // this.canvasWidth.value = parsedJson.canvasWidth || this.width; // this.canvasHeight.value = parsedJson.canvasHeight || this.height; @@ -1039,8 +1038,10 @@ export class CanvasManager { // 使用LayerSort工具重新排列画布对象(如果可用) await this?.layerManager?.layerSort?.rearrangeObjects(); - this.layerManager.activeLayerId.value = - this.layers.value[0]?.id || parsedJson?.activeLayerId || null; + this.layerManager.activeLayerId.value = this.layers.value[0] + .children?.length + ? this.layers.value[0].children[0].id + : this.layers.value[0]?.id || parsedJson?.activeLayerId || null; // // 如果检测到红绿图模式内容,进行缩放调整 // if (this.enabledRedGreenMode) { diff --git a/src/component/Canvas/CanvasEditor/managers/ExportManager.js b/src/component/Canvas/CanvasEditor/managers/ExportManager.js index 32afeabf..47cfdc8a 100644 --- a/src/component/Canvas/CanvasEditor/managers/ExportManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ExportManager.js @@ -28,6 +28,7 @@ export class ExportManager { const { isContainBg = false, isContainFixed = false, + isCropByBg = false, // 是否使用背景大小裁剪 layerId = "", layerIdArray = [], expPicType = "png", @@ -43,7 +44,8 @@ export class ExportManager { layerId, expPicType, isRedGreenMode, - restoreOpacityInRedGreen + restoreOpacityInRedGreen, + isCropByBg ); } @@ -52,10 +54,11 @@ export class ExportManager { return this._exportMultipleLayers( layerIdArray, expPicType, - isContainBg, isContainFixed, isRedGreenMode, - restoreOpacityInRedGreen + restoreOpacityInRedGreen, + isContainBg, + isCropByBg ); } @@ -65,7 +68,8 @@ export class ExportManager { isContainBg, isContainFixed, isRedGreenMode, - restoreOpacityInRedGreen + restoreOpacityInRedGreen, + isCropByBg ); } catch (error) { console.error("导出图片失败:", error); @@ -143,7 +147,8 @@ export class ExportManager { isContainBg, isContainFixed, isRedGreenMode, - restoreOpacityInRedGreen + restoreOpacityInRedGreen, + isCropByBg ) { if (!this.layerManager) { throw new Error("图层管理器未初始化"); @@ -193,7 +198,8 @@ export class ExportManager { isContainBg, isContainFixed, isRedGreenMode, - restoreOpacityInRedGreen + restoreOpacityInRedGreen, + isCropByBg ) { // 按图层顺序收集对象(从底到顶) const objectsToExport = this._collectObjectsByLayerOrder( @@ -215,13 +221,38 @@ export class ExportManager { restoreOpacityInRedGreen ); } - + let canvasClipPath = this.canvas.clipPath; + if (isCropByBg) { + const cropWidth = + this.canvasManager?.canvasWidth?.value || + this.canvas?.canvasWidth || + this.canvas.width; + const cropHeight = + this.canvasManager?.canvasHeight?.value || + this.canvas?.canvasHeight || + this.canvas.height; + canvasClipPath = new fabric.Rect({ + left: this.canvas.width / 2, + top: this.canvas.height / 2, + width: cropWidth, + height: cropHeight, + originX: "center", + originY: "center", + fill: "#fff", + stroke: "transparent", + strokeWidth: 0, + }); + canvasClipPath.set({ + absolutePositioned: true, + }); + canvasClipPath.setCoords(); + } // 普通模式使用画布尺寸 return this._exportWithCanvasSize( objectsToExport, expPicType, restoreOpacityInRedGreen, - this.canvas.clipPath + canvasClipPath ); } diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index 065ed53a..59c233a9 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -336,9 +336,10 @@ export class LayerManager { // 设置裁剪对象 layers.forEach(async (layer) => { + let clippingMaskFabricObject = null; if (layer.clippingMask) { // 反序列化 clippingMask - const clippingMaskFabricObject = await restoreFabricObject( + clippingMaskFabricObject = await restoreFabricObject( layer.clippingMask, this.canvas ); @@ -349,80 +350,30 @@ export class LayerManager { // ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位 absolutePositioned: true, }); - - // const activeObject = this.canvas.getActiveObject(); - // if (activeObject?._objects?.length > 1) { - // const { object } = findObjectById( - // this.canvas, - // layer.clippingMask?.id - // ); - // if (!object) return; - // const tempClipPath = fabric.util.object.clone(object); - // tempClipPath.clipPath = null; - // tempClipPath.set({ - // // 设置绝对定位 - // ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位 - // absolutePositioned: true, - // }); - // activeObject.clipPath = tempClipPath; - // // 确保选择组正确渲染 - // // activeObject.setCoords(); - // console.log(activeObject?._objects?.length); - // return; // 如果是多选对象,则不设置裁剪路径 - // } - // 如果是组图层 则给所有子对象设置裁剪对象 - if (layer.type === LayerType.GROUP || layer.children?.length > 0) { - layer.children.forEach((childLayer) => { - if (clippingMaskFabricObject) { - const childObj = this.canvas - .getObjects() - .find((o) => o.layerId === childLayer.id); - if (childObj) { - childObj.clipPath = clippingMaskFabricObject; - } - } - // const { object } = findObjectById( - // this.canvas, - // layer.clippingMask?.id - // ); - // if (object) { - // const tempClipPath = fabric.util.object.clone(object); - // tempClipPath.clipPath = null; - // tempClipPath.set({ - // // 设置绝对定位 - // // ...layer.clippingMask, // 恢复原定位 - // ...getOriginObjectInfo(layer.clippingMask), - // absolutePositioned: true, - // }); - // const childObj = this.canvas - // .getObjects() - // .find((o) => o.layerId === childLayer.id); - // if (childObj) { - // childObj.clipPath = tempClipPath; - // } - // } - }); - } else if (clippingMaskFabricObject) { - obj.clipPath = clippingMaskFabricObject; - } - - // { - // // const { object } = findObjectById( - // // this.canvas, - // // layer.clippingMask?.id - // // ); - // // if (object) { - // // const tempClipPath = fabric.util.object.clone(object); - // // tempClipPath.clipPath = null; // 确保克隆的遮罩没有clipPath - // // tempClipPath.set({ - // // // 设置绝对定位 - // // // ...layer.clippingMask, // 恢复原定位 - // // ...getOriginObjectInfo(layer.clippingMask), - // // absolutePositioned: true, - // // }); - // // obj.clipPath = tempClipPath; - // // } - // } + } + // 如果是组图层 则给所有子对象设置裁剪对象 + if (layer.type === LayerType.GROUP || layer.children?.length > 0) { + layer.children.forEach((childLayer) => { + const childObj = this.canvas + .getObjects() + .find((o) => o.layerId === childLayer.id); + if (childObj) { + childObj.clipPath = clippingMaskFabricObject; + childObj.dirty = true; // 标记为脏对象 + childObj.setCoords(); + } + }); + } else { + layer.fabricObjects?.forEach((obj) => { + const fabricObject = this.canvas + .getObjects() + .find((o) => o.id === obj.id); + if (fabricObject) { + fabricObject.clipPath = clippingMaskFabricObject; + fabricObject.dirty = true; // 标记为脏对象 + fabricObject.setCoords(); + } + }); } }); }