From 584f6a7db0a1e922d4890b5dc358dcdf18d2e294 Mon Sep 17 00:00:00 2001 From: X1627315083 <1627315083@qq.com> Date: Sun, 22 Jun 2025 13:52:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E7=94=BB=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 10 + .../commands/BackgroundCommands.js | 4 + .../CanvasEditor/commands/GroupCommands.js | 574 +---------- .../CanvasEditor/commands/LayerCommands.js | 442 ++++----- .../commands/ObjectLayerCommands.js | 28 +- .../commands/RasterizeLayerCommand.js | 378 ++++++++ .../CanvasEditor/commands/RedGreenCommands.js | 56 +- .../CanvasEditor/commands/TextCommands.js | 322 +++++++ .../components/BrushControlPanel.vue | 1 - .../CanvasEditor/components/HeaderMenu.vue | 96 +- .../components/LayersPanel/LayerItem.vue | 15 +- .../components/LayersPanel/LayersList.vue | 3 +- .../components/LayersPanel/LayersPanel.vue | 33 +- .../components/LayersPanel/layersPanel.less | 2 + .../CanvasEditor/components/ToolsSidebar.vue | 1 - src/component/Canvas/CanvasEditor/index.vue | 58 +- .../CanvasEditor/managers/CanvasManager.js | 307 +++--- .../CanvasEditor/managers/ExportManager.js | 749 ++++++++------- .../CanvasEditor/managers/LayerManager.js | 134 +-- .../CanvasEditor/managers/ThumbnailManager.js | 449 +++------ .../CanvasEditor/managers/ToolManager.js | 30 +- .../managers/brushes/FABRIC_ERASING_GUIDE.md | 106 ++ .../CanvasEditor/managers/brushes/README.md | 280 ++++++ .../managers/brushes/brushManager.js | 2 +- .../managers/brushes/fabric.brushes.js | 6 +- .../managers/events/CanvasEventManager.js | 13 +- .../managers/examples/CommandManager-Guide.md | 727 ++++++++++++++ .../Canvas/CanvasEditor/utils/imageHelper.js | 902 ++++++++++++++++++ .../Canvas/CanvasEditor/utils/layerHelper.js | 52 +- .../Canvas/CanvasEditor/utils/layerUtils.js | 205 +++- .../CanvasEditor/utils/rasterizedImage.js | 339 +++++++ .../DetailCopy/model/modelPosition.vue | 6 +- src/component/HomePage/Generate.vue | 4 +- .../HomePage/NewCollectionReview.vue | 2 +- src/component/home/design/collection.vue | 9 +- .../home/design/editDesignType/index.vue | 10 +- src/component/home/design/index.vue | 2 +- src/component/home/design/tools.vue | 1 + src/component/home/index.vue | 2 +- .../home/tools/poseTransfer/index.vue | 48 +- src/component/home/tools/toProduct/index.vue | 6 +- src/component/modules/generalDrag.vue | 2 +- src/tool/https.js | 48 +- src/tool/listData.js | 4 +- src/views/HomeView/library.vue | 2 +- src/vite-env.d.ts | 20 + vite.config.js | 2 +- 47 files changed, 4540 insertions(+), 1952 deletions(-) create mode 100644 src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js create mode 100644 src/component/Canvas/CanvasEditor/managers/brushes/FABRIC_ERASING_GUIDE.md create mode 100644 src/component/Canvas/CanvasEditor/managers/brushes/README.md create mode 100644 src/component/Canvas/CanvasEditor/managers/examples/CommandManager-Guide.md create mode 100644 src/component/Canvas/CanvasEditor/utils/rasterizedImage.js create mode 100644 src/vite-env.d.ts diff --git a/components.d.ts b/components.d.ts index 2a668200..940b8980 100644 --- a/components.d.ts +++ b/components.d.ts @@ -9,9 +9,14 @@ export {} declare module 'vue' { export interface GlobalComponents { ABadge: typeof import('ant-design-vue/es')['Badge'] + ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] ADrawer: typeof import('ant-design-vue/es')['Drawer'] AImage: typeof import('ant-design-vue/es')['Image'] + AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AModal: typeof import('ant-design-vue/es')['Modal'] APagination: typeof import('ant-design-vue/es')['Pagination'] APopover: typeof import('ant-design-vue/es')['Popover'] @@ -20,9 +25,14 @@ declare module 'vue' { ASelect: typeof import('ant-design-vue/es')['Select'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASlider: typeof import('ant-design-vue/es')['Slider'] + ASpace: typeof import('ant-design-vue/es')['Space'] ASpin: typeof import('ant-design-vue/es')['Spin'] + ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] + ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker'] AUpload: typeof import('ant-design-vue/es')['Upload'] ElCascader: typeof import('element-plus/es')['ElCascader'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js index 9bd933f0..fe351b8d 100644 --- a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js @@ -212,6 +212,10 @@ export class BackgroundSizeCommand extends Command { this.canvas.setWidth(this.newWidth); this.canvas.setHeight(this.newHeight); + console.log( + `调整画布大小:${this.oldWidth}x${this.oldHeight} -> ${this.newWidth}x${this.newHeight}` + ); + // 如果使用 CanvasManager,通知它画布大小变化 if ( this.canvasManager && diff --git a/src/component/Canvas/CanvasEditor/commands/GroupCommands.js b/src/component/Canvas/CanvasEditor/commands/GroupCommands.js index c6e6c4e1..c9762d09 100644 --- a/src/component/Canvas/CanvasEditor/commands/GroupCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/GroupCommands.js @@ -3,6 +3,7 @@ import { createLayer, findLayerRecursively, LayerType, + OperationType, } from "../utils/layerHelper"; import { Command } from "./Command"; import { @@ -11,6 +12,7 @@ import { optimizeCanvasRendering, } from "../utils/helper"; import { fabric } from "fabric-with-all"; +import { rasterizeCanvasObjects } from "../utils/imageHelper"; /** * 合并组图层命令 - 将图层及其子图层合并为单个图层 @@ -30,7 +32,9 @@ export class MergeGroupLayerCommand extends Command { this.originalLayers = [...this.layers.value]; this.originalObjects = [...this.canvas.getObjects()]; this.flattenedLayer = null; - this.flattenedLayerId = null; + this.flattenedLayerId = + generateId("flattened_") || + `flattened_${Date.now()}_${Math.floor(Math.random() * 1000)}`; this.existingGroupId = null; // 用于查找现有组对象 // 组对象相关 @@ -150,11 +154,6 @@ export class MergeGroupLayerCommand extends Command { console.log("单个对象,已更新关联关系"); } - // 生成新图层ID - this.flattenedLayerId = `flattened_${Date.now()}_${Math.floor( - Math.random() * 1000 - )}`; - // 创建展平后的图层 this.flattenedLayer = createLayer({ id: this.flattenedLayerId, @@ -190,12 +189,14 @@ export class MergeGroupLayerCommand extends Command { this.activeLayerId.value = this.flattenedLayerId; // 重新渲染画布 - await optimizeCanvasRendering(this.canvas, () => { - this.canvas.renderAll(); - }); + // await optimizeCanvasRendering(this.canvas, () => { + // this.canvas.renderAll(); + // }); await this.layerManager?.updateLayersObjectsInteractivity(false); + this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.newGroupId); + console.log(`已合并图层:${this.flattenedLayer.name}`); return this.flattenedLayerId; } @@ -530,558 +531,3 @@ export class MergeGroupLayerCommand extends Command { }; } } - -/** - * 栅格化图层命令 - * 将图层中的所有矢量对象转换为位图图像 - * 支持普通图层和组图层的栅格化 - */ -export class RasterizeLayerCommand extends Command { - constructor(options) { - super({ - name: "栅格化图层", - saveState: true, - }); - this.canvas = options.canvas; - this.layers = options.layers; - this.layerId = options.layerId; - this.activeLayerId = options.activeLayerId; - this.layerManager = options.layerManager; - - // 查找目标图层 - const { layer, parent } = findLayerRecursively( - this.layers.value, - this.layerId - ); - this.layer = layer; - this.parentLayer = parent; - this.isGroupLayer = this.layer?.children && this.layer.children.length > 0; - - // 保存原始状态用于撤销 - this.originalLayers = [...this.layers.value]; - this.originalCanvasObjects = [...this.canvas.getObjects()]; - this.originalObjectStates = new Map(); - - // 栅格化结果 - this.rasterizedImage = null; - this.rasterizedImageId = null; - this.rasterizedLayerId = null; - this.rasterizedLayer = null; - - // 要栅格化的图层和对象 - this.layersToRasterize = []; - this.objectsToRasterize = []; - } - - async execute() { - if (!this.layer) { - throw new Error(`图层 ${this.layerId} 不存在`); - } - - // 检查是否可以栅格化 - if (this.layer.isBackground || this.layer.isFixed) { - throw new Error("背景图层和固定图层不能栅格化"); - } - - try { - // 收集要栅格化的图层和对象 - this._collectLayersAndObjects(); - - if (this.objectsToRasterize.length === 0) { - throw new Error("图层没有内容可栅格化"); - } - - // 保存原始对象状态 - this._saveOriginalObjectStates(); - - // 创建栅格化图像 - const rasterizedImage = await this._createRasterizedImage(); - - // 创建新的栅格化图层并替换原图层 - await this._createRasterizedLayer(rasterizedImage); - - console.log(`✅ 图层 ${this.layer.name} 栅格化完成`); - return this.rasterizedLayerId; - } catch (error) { - console.error("栅格化图层失败:", error); - throw error; - } - } - - async undo() { - if (!this.originalLayers || !this.originalCanvasObjects) { - throw new Error("没有可恢复的原始数据"); - } - - try { - await optimizeCanvasRendering(this.canvas, async () => { - // 清空画布 - this.canvas.discardActiveObject(); - this.canvas.clear(); - - // 恢复原始对象及其状态 - this.originalCanvasObjects.forEach((obj) => { - // 如果保存了该对象的原始状态,则恢复状态 - if (this.originalObjectStates.has(obj.id)) { - const originalState = this.originalObjectStates.get(obj.id); - obj.set(originalState); - } - - this.canvas.add(obj); - obj.setCoords(); - }); - - // 恢复原始图层结构 - this.layers.value = [...this.originalLayers]; - - // 恢复原活动图层 - this.activeLayerId.value = this.layerId; - - // 更新画布交互性 - await this.layerManager?.updateLayersObjectsInteractivity(false); - }); - - console.log(`↩️ 图层 ${this.layer.name} 栅格化已撤销`); - return true; - } catch (error) { - console.error("撤销栅格化失败:", error); - throw error; - } - } - - /** - * 收集要栅格化的图层和对象 - * @private - */ - _collectLayersAndObjects() { - if (this.isGroupLayer) { - // 组图层:收集自身和所有子图层 - this.layersToRasterize = this._collectLayersToRasterize(this.layer); - } else { - // 普通图层:只收集自身 - this.layersToRasterize = [this.layer]; - } - - // 收集所有图层的fabricObjects并按画布z-index顺序排序 - const allCanvasObjects = this.canvas.getObjects(); - const objectsWithZIndex = []; - - this.layersToRasterize.forEach((layer) => { - if (layer.fabricObjects && layer.fabricObjects.length > 0) { - layer.fabricObjects.forEach((layerObj) => { - if (layerObj && layerObj.id) { - const { object } = findObjectById(this.canvas, layerObj.id); - if (object) { - // 获取对象在画布中的z-index(数组索引) - const zIndex = allCanvasObjects.indexOf(object); - objectsWithZIndex.push({ - object: object, - zIndex: zIndex, - layerObj: layerObj, - }); - } - } - }); - } - }); - - // 按z-index排序,确保保持原有的渲染顺序 - objectsWithZIndex.sort((a, b) => a.zIndex - b.zIndex); - - // 提取排序后的对象 - this.objectsToRasterize = objectsWithZIndex.map((item) => item.object); - - console.log( - `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化` - ); - console.log( - "🔢 对象z-index顺序:", - objectsWithZIndex.map((item) => ({ - id: item.object.id, - type: item.object.type, - zIndex: item.zIndex, - })) - ); - } - - /** - * 收集要栅格化的图层(递归收集子图层) - * @param {Object} sourceLayer 源图层 - * @returns {Array} 图层数组 - * @private - */ - _collectLayersToRasterize(sourceLayer) { - const result = [sourceLayer]; - - // 如果是组图层,收集所有子图层 - if (sourceLayer.children && sourceLayer.children.length > 0) { - sourceLayer.children.forEach((childLayer) => { - if (childLayer) { - result.push(...this._collectLayersToRasterize(childLayer)); - } - }); - } - - return result; - } - - /** - * 保存原始对象状态 - * @private - */ - _saveOriginalObjectStates() { - this.objectsToRasterize.forEach((object) => { - if (object && object.id) { - const originalState = { - left: object.left, - top: object.top, - scaleX: object.scaleX, - scaleY: object.scaleY, - angle: object.angle, - flipX: object.flipX, - flipY: object.flipY, - opacity: object.opacity, - originX: object.originX, - originY: object.originY, - layerId: object.layerId, - layerName: object.layerName, - width: object.width, - height: object.height, - strokeWidth: object.strokeWidth, - visible: object.visible, - }; - this.originalObjectStates.set(object.id, originalState); - } - }); - } - - /** - * 创建栅格化图像 - * @returns {Promise} 栅格化后的图像对象 - * @private - */ - async _createRasterizedImage() { - return new Promise((resolve, reject) => { - try { - // 计算所有对象的总边界 - const bounds = this._calculateObjectsBounds(this.objectsToRasterize); - if (!bounds) { - reject(new Error("无法计算对象边界")); - return; - } - - console.log("🎯 栅格化边界信息:", bounds); - - // 创建临时画布进行栅格化 - const tempCanvas = this._createTempCanvas(bounds); - const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, { - width: bounds.width, - height: bounds.height, - backgroundColor: "transparent", - renderOnAddRemove: false, - }); - - // 直接复制对象到临时画布,不使用临时组 - this._addObjectsToTempCanvas( - tempFabricCanvas, - this.objectsToRasterize, - bounds - ); - - // 强制渲染临时画布 - tempFabricCanvas.renderOnAddRemove = true; - tempFabricCanvas.renderAll(); - - // 等待渲染完成后生成图像 - setTimeout(() => { - try { - // 生成高质量图像 - const dataUrl = tempFabricCanvas.toDataURL({ - format: "png", - quality: 1.0, - multiplier: 1, // 降低multiplier避免性能问题 - }); - - console.log("📷 栅格化图像生成完成,DataURL长度:", dataUrl.length); - - // 创建fabric图像对象 - fabric.Image.fromURL( - dataUrl, - (img) => { - // 设置图像属性,使用原始边界的中心点 - this.rasterizedImageId = generateId("rasterized_"); - img.set({ - id: this.rasterizedImageId, - left: bounds.centerX, - top: bounds.centerY, - originX: "center", - originY: "center", - selectable: true, - evented: true, - }); - - this.rasterizedImage = img; - - // 清理临时画布 - this._cleanupTempCanvas(tempFabricCanvas); - - console.log("✅ 栅格化图像对象创建完成"); - resolve(img); - }, - { crossOrigin: "anonymous" } - ); - } catch (error) { - console.error("生成图像时发生错误:", error); - this._cleanupTempCanvas(tempFabricCanvas); - reject(error); - } - }, 100); // 给一点时间确保渲染完成 - } catch (error) { - console.error("创建栅格化图像时发生错误:", error); - reject(error); - } - }); - } - - /** - * 计算对象边界 - * @param {Array} objects 对象数组 - * @returns {Object|null} 边界信息 - * @private - */ - _calculateObjectsBounds(objects) { - if (!objects || objects.length === 0) { - return null; - } - - let minLeft = Infinity; - let minTop = Infinity; - let maxRight = -Infinity; - let maxBottom = -Infinity; - - objects.forEach((obj) => { - try { - // 使用更准确的边界计算,不包含绝对变换 - const bounds = obj.getBoundingRect(false, false); - - console.log(`📐 对象 ${obj.id || obj.type} 边界:`, bounds); - - minLeft = Math.min(minLeft, bounds.left); - minTop = Math.min(minTop, bounds.top); - maxRight = Math.max(maxRight, bounds.left + bounds.width); - maxBottom = Math.max(maxBottom, bounds.top + bounds.height); - } catch (error) { - console.warn(`计算对象 ${obj.id || obj.type} 边界时发生错误:`, error); - // 备选方案:使用对象的基础位置信息 - const left = obj.left || 0; - const top = obj.top || 0; - const width = (obj.width || 100) * (obj.scaleX || 1); - const height = (obj.height || 100) * (obj.scaleY || 1); - - minLeft = Math.min(minLeft, left - width / 2); - minTop = Math.min(minTop, top - height / 2); - maxRight = Math.max(maxRight, left + width / 2); - maxBottom = Math.max(maxBottom, top + height / 2); - } - }); - - const padding = 10; // 增加边距确保不被裁剪 - - const bounds = { - left: minLeft - padding, - top: minTop - padding, - width: maxRight - minLeft + padding * 2, - height: maxBottom - minTop + padding * 2, - centerX: (minLeft + maxRight) / 2, - centerY: (minTop + maxBottom) / 2, - }; - - // 确保最小尺寸 - bounds.width = Math.max(bounds.width, 50); - bounds.height = Math.max(bounds.height, 50); - - return bounds; - } - - /** - * 创建临时画布 - * @param {Object} bounds 边界信息 - * @returns {HTMLCanvasElement} 临时画布 - * @private - */ - _createTempCanvas(bounds) { - const tempCanvas = document.createElement("canvas"); - tempCanvas.width = Math.ceil(bounds.width); - tempCanvas.height = Math.ceil(bounds.height); - - const ctx = tempCanvas.getContext("2d"); - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = "high"; - - // 设置透明背景 - ctx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); - - console.log( - `🖼️ 创建临时画布尺寸: ${tempCanvas.width} x ${tempCanvas.height}` - ); - - return tempCanvas; - } - - /** - * 添加对象到临时画布 - * @param {fabric.StaticCanvas} tempCanvas 临时画布 - * @param {Array} objects 对象数组 - * @param {Object} bounds 边界信息 - * @private - */ - _addObjectsToTempCanvas(tempCanvas, objects, bounds) { - console.log(`📥 开始添加 ${objects.length} 个对象到临时画布`); - - objects.forEach((obj, index) => { - try { - // 深度克隆对象,避免影响原对象 - const clonedObj = fabric.util.object.clone(obj); - - // 计算对象在临时画布中的新位置 - // 将对象从原始画布坐标系转换到临时画布坐标系 - const newLeft = obj.left - bounds.left; - const newTop = obj.top - bounds.top; - - // 设置对象在临时画布中的位置,保持原有的originX和originY - clonedObj.set({ - left: newLeft, - top: newTop, - // 保持原有的变换属性 - scaleX: obj.scaleX, - scaleY: obj.scaleY, - angle: obj.angle, - flipX: obj.flipX, - flipY: obj.flipY, - opacity: obj.opacity, - originX: obj.originX, - originY: obj.originY, - // 确保可见 - visible: true, - }); - - console.log( - `📍 对象 ${index + 1} 位置转换: (${obj.left}, ${ - obj.top - }) -> (${newLeft}, ${newTop})` - ); - - tempCanvas.add(clonedObj); - } catch (error) { - console.error(`添加对象 ${obj.id || obj.type} 到临时画布失败:`, error); - } - }); - - console.log( - `✅ 临时画布对象添加完成,共 ${tempCanvas.getObjects().length} 个对象` - ); - } - - /** - * 清理临时画布 - * @param {fabric.StaticCanvas} tempCanvas 临时画布 - * @private - */ - _cleanupTempCanvas(tempCanvas) { - try { - if (tempCanvas) { - tempCanvas.clear(); - tempCanvas.dispose(); - } - } catch (error) { - console.warn("清理临时画布时发生错误:", error); - } - } - - /** - * 创建栅格化图层并替换原图层 - * @param {fabric.Image} rasterizedImage 栅格化后的图像 - * @private - */ - async _createRasterizedLayer(rasterizedImage) { - // 从画布中移除原有对象 - this.objectsToRasterize.forEach((obj) => { - removeCanvasObjectByObject(this.canvas, obj); - }); - - // 添加栅格化图像到画布 - this.canvas.add(rasterizedImage); - this.canvas.setActiveObject(rasterizedImage); - - // 生成新图层ID - this.rasterizedLayerId = generateId("rasterized_layer_"); - - // 创建新的栅格化图层 - this.rasterizedLayer = createLayer({ - id: this.rasterizedLayerId, - name: `${this.layer.name} (栅格化)`, - type: LayerType.BITMAP, - visible: this.layer.visible, - locked: this.layer.locked, - opacity: this.layer.opacity, - fabricObjects: [rasterizedImage], - }); - - // 更新图像对象的图层关联 - rasterizedImage.set({ - layerId: this.rasterizedLayerId, - layerName: this.rasterizedLayer.name, - }); - - // 替换图层结构 - if (this.isGroupLayer) { - // 组图层:移除所有相关图层 - const layerIdsToRemove = this.layersToRasterize.map((layer) => layer.id); - this.layers.value = this.layers.value.filter( - (layer) => !layerIdsToRemove.includes(layer.id) - ); - } else { - // 普通图层:移除原图层 - const layerIndex = this.layers.value.findIndex( - (l) => l.id === this.layerId - ); - if (layerIndex !== -1) { - this.layers.value.splice(layerIndex, 1); - } - } - - // 在适当位置添加新的栅格化图层 - const insertIndex = this.layers.value.findIndex( - (l) => l.id === (this.parentLayer?.id || this.layerId) - ); - if (insertIndex !== -1) { - this.layers.value.splice(insertIndex, 0, this.rasterizedLayer); - } else { - this.layers.value.push(this.rasterizedLayer); - } - - // 设置为活动图层 - this.activeLayerId.value = this.rasterizedLayerId; - - // 重新渲染画布 - await optimizeCanvasRendering(this.canvas, () => { - this.canvas.renderAll(); - }); - - await this.layerManager?.updateLayersObjectsInteractivity(false); - - console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`); - } - - getInfo() { - return { - name: this.name, - originalLayerId: this.layerId, - originalLayerName: this.layer?.name, - rasterizedLayerId: this.rasterizedLayerId, - rasterizedLayerName: this.rasterizedLayer?.name, - isGroupLayer: this.isGroupLayer, - objectCount: this.objectsToRasterize?.length || 0, - }; - } -} diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 1d699387..af6cf4ea 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -302,7 +302,18 @@ export class RemoveLayerCommand extends Command { ); this.removedLayer = this.layers.value[this.layerIndex]; this.isActiveLayer = this.layerId === this.activeLayerId.value; - // this.beforeLayers = [...this.layers.value]; // 备份原图层列表 + + // 从Canvas中找到真实对象并备份,确保撤销和重做时对象的一致性 + this.originalObjects = []; + if (this.removedLayer) { + // 从画布中获取真实的对象引用 + this.originalObjects = this.canvas.getObjects().filter((obj) => { + return obj.layerId === this.layerId; + }); + } + + // 备份原活动图层ID + this.originalActiveLayerId = this.activeLayerId.value; } execute() { @@ -311,19 +322,22 @@ export class RemoveLayerCommand extends Command { return false; } - // 从画布中移除图层中的所有对象 - if ( - this.removedLayer.fabricObjects && - this.removedLayer.fabricObjects.length > 0 - ) { - this.removedLayer.fabricObjects.forEach((obj) => { + // 从画布中移除图层中的所有真实对象 + this.originalObjects.forEach((obj) => { + if (this.canvas.getObjects().includes(obj)) { this.canvas.remove(obj); - }); - } + } + }); // 如果是背景图层,移除特殊对象 if (this.removedLayer.isBackground && this.removedLayer.fabricObject) { - this.canvas.remove(this.removedLayer.fabricObject); + const { object } = findObjectById( + this.canvas, + this.removedLayer.fabricObject.id + ); + if (object) { + this.canvas.remove(object); + } } // 从图层列表中删除 @@ -347,38 +361,49 @@ export class RemoveLayerCommand extends Command { this.canvas.renderAll(); } + console.log( + `✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId})` + ); return true; } undo() { - // 恢复图层 + // 恢复图层到原位置 if (this.layerIndex !== -1 && this.removedLayer) { this.layers.value.splice(this.layerIndex, 0, this.removedLayer); - // 恢复图层中的所有对象到画布 - if ( - this.removedLayer.fabricObjects && - this.removedLayer.fabricObjects.length > 0 - ) { - this.removedLayer.fabricObjects.forEach((obj) => { + // 使用优化渲染批处理恢复真实对象到画布 + optimizeCanvasRendering(this.canvas, () => { + this.originalObjects.forEach((obj) => { + // 恢复对象到画布 this.canvas.add(obj); + // 确保对象的图层信息正确 + obj.layerId = this.layerId; + obj.layerName = this.removedLayer.name; + obj.setCoords(); // 更新坐标 }); - } - // 如果是背景图层,恢复特殊对象 - if (this.removedLayer.isBackground && this.removedLayer.fabricObject) { - this.canvas.add(this.removedLayer.fabricObject); - } + // 如果是背景图层,恢复特殊对象 + if (this.removedLayer.isBackground && this.removedLayer.fabricObject) { + // 检查对象是否已在画布中 + const { object } = findObjectById( + this.canvas, + this.removedLayer.fabricObject.id + ); + if (!object) { + this.canvas.add(this.removedLayer.fabricObject); + } + } + }); // 如果删除的是当前活动图层,恢复活动图层 if (this.isActiveLayer) { this.activeLayerId.value = this.layerId; } - // 重新渲染画布 - if (this.canvas) { - this.canvas.renderAll(); - } + console.log( + `↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId})` + ); } } @@ -387,6 +412,7 @@ export class RemoveLayerCommand extends Command { name: this.name, layerName: this.removedLayer?.name || "未知图层", layerId: this.layerId, + objectCount: this.originalObjects.length, }; } } @@ -503,56 +529,40 @@ export class ToggleLayerVisibilityCommand extends Command { this.canvas = options.canvas; this.layers = options.layers; this.layerId = options.layerId; + this.layerManager = options.layerManager; // 查找图层 const { layer } = findLayerRecursively(this.layers.value, this.layerId); this.layer = layer; - this.oldVisibility = this.layer ? this.layer.visible : null; + // this.oldVisibility = this.layer ? this.layer.visible : null; } - execute() { + async execute() { if (!this.layer) { console.error(`图层 ${this.layerId} 不存在`); return false; } // 切换可见性 - this.layer.visible = !this.oldVisibility; + this.layer.visible = !this.layer.visible; // 更新画布上图层对象的可见性 if (this.canvas) { const layerObjects = this.canvas .getObjects() .filter((obj) => obj.layerId === this.layerId); - layerObjects.forEach((obj) => { obj.visible = this.layer.visible; }); - - this.canvas.renderAll(); } + // 更新画布上对象的可选择状态 + await this.layerManager?.updateLayersObjectsInteractivity(); - return true; + return this.layer.visible; } - undo() { - if (this.layer) { - // 恢复可见性 - this.layer.visible = this.oldVisibility; - - // 更新画布上图层对象的可见性 - if (this.canvas) { - const layerObjects = this.canvas - .getObjects() - .filter((obj) => obj.layerId === this.layerId); - - layerObjects.forEach((obj) => { - obj.visible = this.oldVisibility; - }); - - this.canvas.renderAll(); - } - } + async undo() { + return await this.execute(); // 直接调用execute方法来恢复可见性 } getInfo() { @@ -565,6 +575,71 @@ export class ToggleLayerVisibilityCommand extends Command { } } +/** + * 切换子图层可见性命令 + */ +export class ToggleChildLayerVisibilityCommand extends Command { + constructor(options) { + super({ + name: "切换子图层可见性", + saveState: false, + }); + this.canvas = options.canvas; + this.layers = options.layers; + this.layerId = options.layerId; + this.parentId = options.parentId; + this.layerManager = options.layerManager; + + // 查找父图层和子图层 + const { layer, parent } = findLayerRecursively( + this.layers.value, + this.layerId + ); + this.parentLayer = parent; + this.childLayer = layer; + + // this.oldVisibility = this.childLayer ? this.childLayer.visible : null; + } + + async execute() { + if (!this.childLayer) { + throw new Error("找不到要切换可见性的子图层"); + } + + // 切换可见性 + this.childLayer.visible = !this.childLayer.visible; + + // 更新画布上图层对象的可见性 + if (this.canvas) { + const layerObjects = this.canvas + .getObjects() + .filter((obj) => obj.layerId === this.layerId); + + layerObjects.forEach((obj) => { + obj.visible = this.childLayer.visible; + }); + } + + // 更新画布上对象的可选择状态 + await this.layerManager?.updateLayersObjectsInteractivity(); + return this.childLayer.visible; + } + + async undo() { + return await this.execute(); // 直接调用execute方法来恢复可见性 + } + + getInfo() { + return { + name: this.name, + layerName: this.childLayer?.name || "未知子图层", + layerId: this.layerId, + parentId: this.parentId, + newVisibility: this.childLayer?.visible, + }; + } +} + /** * 重命名图层命令 */ @@ -1031,9 +1106,9 @@ export class GroupLayersCommand extends Command { // 备份原图层 this.originalLayers = [...this.layers.value]; // 新组ID - this.groupId = `group_layer_${Date.now()}_${Math.floor( - Math.random() * 1000 - )}`; + this.groupId = + generateId("group_layer_") || + `group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; this.originalActiveLayerId = this.activeLayerId.value; // 备份原活动图层ID } @@ -1097,6 +1172,8 @@ export class GroupLayersCommand extends Command { // 更新当前活动图层 this.activeLayerId.value = this.layerIds[0]; + this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.groupId); + return this.groupId; } @@ -2199,9 +2276,9 @@ export class LayerObjectsToGroupCommand extends Command { * @private */ _updateThumbnail() { - if (this.canvas.thumbnailManager) { - this.canvas.thumbnailManager.generateLayerThumbnail(this.activeLayer.id); - } + // this.canvas?.thumbnailManager?.generateLayerThumbnail?.( + // this.activeLayer.id + // ); } getInfo() { @@ -2232,8 +2309,12 @@ export class CreateImageLayerCommand extends Command { this.toolManager = options.toolManager; this.layerName = options.layerName || null; + this.imageId = generateId("image_"); + // 存储执行过程中的结果 - this.newLayerId = null; + this.newLayerId = + generateId("layer_image_") || + `layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`; this.commands = []; this.executedCommands = []; } @@ -2251,12 +2332,15 @@ export class CreateImageLayerCommand extends Command { const fileName = this.layerName || `图片 ${new Date().toLocaleTimeString()}`; + this.fabricImage.set({ + id: this.imageId, + }); // 1. 创建新图层命令 const createLayerCmd = new AddLayerCommand({ canvas: this.layerManager.canvas, layers: this.layerManager.layers, newLayer: createLayer({ - id: this.newLayerId || generateId("layer"), + id: this.newLayerId, name: fileName, type: LayerType.BITMAP, visible: true, @@ -2658,6 +2742,11 @@ export class CutLayerCommand extends Command { this.cutLayer = null; this.clipboardData = null; this.wasActiveLayer = false; + + // 生成新图层ID和名称 + this.newLayerId = + generateId("layer_") || + `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; } async execute() { @@ -2678,9 +2767,6 @@ export class CutLayerCommand extends Command { console.warn("不能复制背景图层"); return null; } - - // 生成新图层ID和名称 - this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; const newName = `${sourceLayer.name} copy`; // 创建新图层 @@ -2781,100 +2867,6 @@ export class CutLayerCommand extends Command { await this.layerManager?.updateLayersObjectsInteractivity(false); } - getInfo() { - return { - name: this.name, - sourceLayerId: this.layerId, - newLayerId: this.newLayerId, - newLayerName: this.newLayer?.name, - objectCount: this.createdObjects.length, - isChildLayer: this.isChildLayer, - parentLayerId: this.parentLayer?.id, - }; - } -} - -/** - * 复制图层到指定位置命令 - */ -export class DuplicateLayerCommand extends Command { - constructor(options) { - super({ - name: "复制图层", - saveState: true, - }); - this.canvas = options.canvas; - this.layers = options.layers; - this.layerId = options.layerId; - this.activeLayerId = options.activeLayerId; - this.insertIndex = options.insertIndex || null; - this.layerManager = options.layerManager; - - this.newLayer = null; - this.newLayerId = null; - this.createdObjects = []; - } - - async execute() { - const { layer } = findLayerRecursively(this.layers.value, this.layerId); - const sourceLayer = layer; - if (!sourceLayer) { - console.error(`源图层 ${this.layerId} 不存在`); - return null; - } - - // 不允许复制背景图层 - if (sourceLayer.isBackground) { - console.warn("不能复制背景图层"); - return null; - } - - // 生成新图层ID和名称 - this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; - const newName = `${sourceLayer.name} copy`; - - // 创建新图层 - this.newLayer = createLayer({ - id: this.newLayerId, - name: newName, - type: sourceLayer.type, - visible: sourceLayer.visible, - locked: sourceLayer.locked, - opacity: sourceLayer.opacity, - blendMode: sourceLayer.blendMode, - fabricObjects: [], - children: sourceLayer.children ? [...sourceLayer.children] : [], - layerProperties: sourceLayer.layerProperties - ? { ...sourceLayer.layerProperties } - : {}, - metadata: sourceLayer.metadata ? { ...sourceLayer.metadata } : {}, - }); - - // 计算插入位置 - const sourceIndex = this.layers.value.findIndex( - (l) => l.id === this.layerId - ); - const insertIndex = - this.insertIndex !== null ? this.insertIndex : sourceIndex + 1; - - // 插入新图层 - this.layers.value.splice(insertIndex, 0, this.newLayer); - - // 复制源图层中的对象 - if (sourceLayer.fabricObjects && sourceLayer.fabricObjects.length > 0) { - await this._duplicateObjects(sourceLayer.fabricObjects); - } - - // 设置为活动图层 - this.activeLayerId.value = this.newLayerId; - - // 重新渲染画布 - await this.layerManager?.updateLayersObjectsInteractivity(false); - - console.log(`已复制图层:${newName}`); - return this.newLayerId; - } - async _duplicateObjects(sourceObjects) { const serializedObjects = sourceObjects.map((obj) => { // 序列化对象时保留必要的属性 @@ -2914,27 +2906,6 @@ export class DuplicateLayerCommand extends Command { }); } - async undo() { - if (!this.newLayer) return; - - // 从图层列表中删除新图层 - 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.activeLayerId.value = this.layerId; - - // 重新渲染画布 - await this.layerManager?.updateLayersObjectsInteractivity(false); - } - getInfo() { return { name: this.name, @@ -2942,6 +2913,8 @@ export class DuplicateLayerCommand extends Command { newLayerId: this.newLayerId, newLayerName: this.newLayer?.name, objectCount: this.createdObjects.length, + isChildLayer: this.isChildLayer, + parentLayerId: this.parentLayer?.id, }; } } @@ -2963,15 +2936,13 @@ export class CreateAdjustmentLayerCommand extends Command { this.insertIndex = options.insertIndex || null; this.newLayer = null; - this.newLayerId = null; + // 生成新图层ID + this.newLayerId = + generateId("adj_layer_") || + `adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; } execute() { - // 生成新图层ID - this.newLayerId = `adj_layer_${Date.now()}_${Math.floor( - Math.random() * 1000 - )}`; - // 创建调整图层 this.newLayer = createLayer({ id: this.newLayerId, @@ -3959,6 +3930,10 @@ export class RemoveChildLayerCommand extends Command { ) ?? -1; this.removedChild = this.parentLayer?.children?.[this.childIndex]; this.isActiveLayer = this.layerId === this.activeLayerId.value; + + this.originalObjects = this.canvas.getObjects().filter((obj) => { + return obj.layerId === this.layerId; + }); } async execute() { @@ -3972,8 +3947,9 @@ export class RemoveChildLayerCommand extends Command { this.removedChild.fabricObjects.length > 0 ) { this.removedChild.fabricObjects.forEach((obj) => { - if (this.canvas.getObjects().includes(obj)) { - this.canvas.remove(obj); + const { object } = findObjectById(this.canvas, obj.id); + if (object) { + this.canvas.remove(...this.originalObjects); } }); } @@ -4001,29 +3977,34 @@ export class RemoveChildLayerCommand extends Command { } async undo() { - if (!this.parentLayer || this.childIndex === -1 || !this.removedChild) + if ( + !this.parentLayer || + this.childIndex === -1 || + !this.removedChild || + this.originalObjects.length === 0 + ) { return; - + } // 恢复子图层到原位置 this.parentLayer.children.splice(this.childIndex, 0, this.removedChild); - - // 恢复子图层中的所有对象到画布 - if ( - this.removedChild.fabricObjects && - this.removedChild.fabricObjects.length > 0 - ) { - this.removedChild.fabricObjects.forEach((obj) => { + optimizeCanvasRendering(this.canvas, async () => { + this.originalObjects.forEach((obj) => { + // 恢复对象到画布 this.canvas.add(obj); + // 恢复对象的图层信息 + obj.layerId = this.layerId; + obj.layerName = this.removedChild.name; + obj.setCoords(); // 更新坐标 }); - } - // 如果是原活动图层,恢复活动图层 - if (this.isActiveLayer) { - this.activeLayerId.value = this.layerId; - } + // 如果是原活动图层,恢复活动图层 + if (this.isActiveLayer) { + this.activeLayerId.value = this.layerId; + } - // 重新渲染画布 - await this.layerManager?.updateLayersObjectsInteractivity(); + // 重新渲染画布 + await this.layerManager?.updateLayersObjectsInteractivity(false); + }); } getInfo() { @@ -4117,6 +4098,7 @@ export class ChildLayerLockCommand extends Command { this.layers = options.layers; this.layerId = options.layerId; this.parentId = options.parentId; + this.layerManager = options.layerManager; // 查找父图层和子图层 const { layer, parent } = findLayerRecursively( @@ -4163,61 +4145,3 @@ export class ChildLayerLockCommand extends Command { }; } } - -/** - * 切换子图层可见性命令 - */ -export class ToggleChildLayerVisibilityCommand extends Command { - constructor(options) { - super({ - name: "切换子图层可见性", - saveState: false, - }); - this.canvas = options.canvas; - this.layers = options.layers; - this.layerId = options.layerId; - this.parentId = options.parentId; - - // 查找父图层和子图层 - const { layer, parent } = findLayerRecursively( - this.layers.value, - this.layerId - ); - this.parentLayer = parent; - this.childLayer = layer; - - this.oldVisibility = this.childLayer ? this.childLayer.visible : null; - } - - async execute() { - if (!this.childLayer) { - throw new Error("找不到要切换可见性的子图层"); - } - - // 切换可见性 - this.childLayer.visible = !this.oldVisibility; - - // 更新画布上对象的可选择状态 - await this.layerManager?.updateLayersObjectsInteractivity(); - return true; - } - - async undo() { - if (this.childLayer) { - this.childLayer.visible = this.oldVisibility; - - // 更新画布上对象的可选择状态 - await this.layerManager?.updateLayersObjectsInteractivity(); - } - } - - getInfo() { - return { - name: this.name, - layerName: this.childLayer?.name || "未知子图层", - layerId: this.layerId, - parentId: this.parentId, - newVisibility: this.childLayer?.visible, - }; - } -} diff --git a/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js b/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js index 0859013e..ad082a9a 100644 --- a/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js @@ -342,7 +342,7 @@ export class AddObjectToLayerCommand extends Command { this.canvas.remove(object); } - this.layerManager?.updateLayersObjectsInteractivity?.(false); + await this.layerManager?.updateLayersObjectsInteractivity?.(false); // 重置为首次执行状态,以便重做时能正确恢复位置 this.isFirstExecution = false; // 保持为false,因为已经执行过一次了 }); @@ -473,18 +473,28 @@ export class ChangeFixedImageCommand extends Command { this.scale = options.scale || { x: 1, y: 1 }; this.preserveTransform = options.preserveTransform ?? false; // 默认不保留变换 + this.options = options || {}; + + this.scale.x = options.scaleX || 1; + this.scale.y = options.scaleY || 1; + this.position.x = options.left || this.canvas.width / 2; + this.position.y = options.top || this.canvas.height / 2; + // 用于回滚的状态 this.previousImage = null; this.previousTransform = null; this.previousObjectId = null; // 保存之前对象的ID this.targetLayer = null; - this.newObjectId = null; // 保存新对象的ID + this.newObjectId = generateId("fixed_"); // 保存新对象的ID this.isExecuted = false; // 错误处理 this.maxRetries = options.maxRetries || 3; this.retryCount = 0; this.timeoutMs = options.timeoutMs || 10000; + + // 查找目标图层 + this.targetLayer = this.findTargetLayer(); } async execute() { @@ -668,12 +678,9 @@ export class ChangeFixedImageCommand extends Command { async applyImageToLayer(newImage) { await optimizeCanvasRendering(this.canvas, async () => { - // 生成新对象ID - this.newObjectId = generateId(); - // 设置基本属性 newImage.set({ - id: this.newObjectId, + id: this.targetLayer?.fabricObject?.id || this.newObjectId, layerId: this.targetLayer.id, layerName: this.targetLayer.name, isBackground: this.targetLayer.isBackground, @@ -884,6 +891,8 @@ export class AddImageToLayerCommand extends Command { this.targetLayer = null; this.isExecuted = false; + this.imageId = generateId("image_"); + // 错误处理 this.maxRetries = options.maxRetries || 3; this.retryCount = 0; @@ -907,7 +916,7 @@ export class AddImageToLayerCommand extends Command { const newImage = await this.loadImageWithRetry(); // 添加图像到图层 - await this.addImageToLayer(newImage); + await this.addImageToLayer(newImage, this.imageId); this.isExecuted = true; @@ -1061,10 +1070,7 @@ export class AddImageToLayerCommand extends Command { }); } - async addImageToLayer(newImage) { - // 生成唯一ID - const objectId = generateId(); - + async addImageToLayer(newImage, objectId) { // 设置图像属性 newImage.set({ id: objectId, diff --git a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js new file mode 100644 index 00000000..86568254 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js @@ -0,0 +1,378 @@ +import { Command } from "./Command"; +import { fabric } from "fabric-with-all"; +import { + LayerType, + OperationType, + createLayer, + findLayerRecursively, +} from "../utils/layerHelper"; +import { + generateId, + optimizeCanvasRendering, + findObjectById, + removeCanvasObjectByObject, +} from "../utils/helper"; +import { createRasterizedImage } from "../utils/rasterizedImage"; + +/** + * 栅格化图层命令 + * 将图层中的所有矢量对象转换为位图图像 + * 支持普通图层和组图层的栅格化 + */ +export class RasterizeLayerCommand extends Command { + constructor(options) { + super({ + name: "栅格化图层", + saveState: true, + }); + this.canvas = options.canvas; + this.layers = options.layers; + this.layerId = options.layerId; // 指定要栅格化的图层ID + // 是否包含锁定对象 + this.hasLocked = options.hasLocked || true; + // 是否包含隐藏对象 + this.hasHidden = options.hasHidden || false; + + this.activeLayerId = options.activeLayerId; + this.layerManager = options.layerManager; + + // 查找目标图层 + const { layer, parent } = findLayerRecursively( + this.layers.value, + this.layerId + ); + this.layer = layer; + this.parentLayer = parent; + this.isGroupLayer = this.layer?.children && this.layer.children.length > 0; + + // 保存原始状态用于撤销 + this.originalLayers = [...this.layers.value]; + this.originalCanvasObjects = [...this.canvas.getObjects()]; + this.originalObjectStates = new Map(); + + // 栅格化结果 + this.rasterizedImage = null; + this.rasterizedImageId = null; + // 生成新图层ID + this.rasterizedLayerId = generateId("rasterized_layer_"); + this.resterizedId = generateId("rasterized_"); + + this.rasterizedLayer = null; + + // 要栅格化的图层和对象 + this.layersToRasterize = []; + this.objectsToRasterize = []; + } + + async execute() { + // 查找目标图层 + const { layer, parent } = findLayerRecursively( + this.layers.value, + this.layerId + ); + this.layer = layer; + this.parentLayer = parent; + this.isGroupLayer = this.layer?.children && this.layer.children.length > 0; + + if (!this.layer) { + throw new Error(`图层 ${this.layerId} 不存在`); + } + + // 检查是否可以栅格化 + if (this.layer.isBackground || this.layer.isFixed) { + throw new Error("背景图层和固定图层不能栅格化"); + } + + try { + // 收集要栅格化的图层和对象 + this._collectLayersAndObjects(); + + if (this.objectsToRasterize.length === 0) { + throw new Error("图层没有内容可栅格化"); + } + + // 保存原始对象状态 + this._saveOriginalObjectStates(); + + this.canvas.discardActiveObject(); + this.canvas.renderAll(); + + // 创建栅格化图像 + const rasterizedImage = await createRasterizedImage({ + canvas: this.canvas, + fabricObjects: this.objectsToRasterize, + }); + + // 创建新的栅格化图层并替换原图层 + await this._createRasterizedLayer(rasterizedImage); + + // 切换到选择工具 + this.layerManager?.toolManager?.setTool?.(OperationType.SELECT); + + console.log(`✅ 图层 ${this.layer.name} 栅格化完成`); + + this.canvas?.thumbnailManager?.generateLayerThumbnail?.( + this.rasterizedLayerId + ); + return this.rasterizedLayerId; + } catch (error) { + console.error("栅格化图层失败:", error); + throw error; + } + } + + async undo() { + if (!this.originalLayers || !this.originalCanvasObjects) { + throw new Error("没有可恢复的原始数据"); + } + + try { + await optimizeCanvasRendering(this.canvas, async () => { + // 清空画布 + this.canvas.discardActiveObject(); + this.canvas.clear(); + + // 恢复原始对象及其状态 + this.originalCanvasObjects.forEach((obj) => { + // 如果保存了该对象的原始状态,则恢复状态 + if (this.originalObjectStates.has(obj.id)) { + const originalState = this.originalObjectStates.get(obj.id); + obj.set(originalState); + } + + this.canvas.add(obj); + obj.setCoords(); + }); + + // 恢复原始图层结构 + this.layers.value = [...this.originalLayers]; + + // 恢复原活动图层 + this.activeLayerId.value = this.layerId; + + // 更新画布交互性 + await this.layerManager?.updateLayersObjectsInteractivity(false); + }); + + console.log(`↩️ 图层 ${this.layer.name} 栅格化已撤销`); + return true; + } catch (error) { + console.error("撤销栅格化失败:", error); + throw error; + } + } + + /** + * 收集要栅格化的图层和对象 + * @private + */ + _collectLayersAndObjects() { + if (this.isGroupLayer) { + // 组图层:收集自身和所有子图层 + this.layersToRasterize = this._collectLayersToRasterize(this.layer); + } else { + // 普通图层:只收集自身 + this.layersToRasterize = [this.layer]; + } + + // 收集所有图层的fabricObjects并按画布z-index顺序排序 + const allCanvasObjects = this.canvas.getObjects(); + const objectsWithZIndex = []; + + this.layersToRasterize.forEach((layer) => { + if (layer.fabricObjects && layer.fabricObjects.length > 0) { + layer.fabricObjects.forEach((layerObj) => { + if (layerObj && layerObj.id) { + const { object } = findObjectById(this.canvas, layerObj.id); + if (object) { + // 获取对象在画布中的z-index(数组索引) + const zIndex = allCanvasObjects.indexOf(object); + objectsWithZIndex.push({ + object: object, + zIndex: zIndex, + layerObj: layerObj, + }); + } + } + }); + } + }); + + // 按z-index排序,确保保持原有的渲染顺序 + objectsWithZIndex.sort((a, b) => a.zIndex - b.zIndex); + + // 提取排序后的对象 + this.objectsToRasterize = objectsWithZIndex.map((item) => item.object); + + console.log( + `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化` + ); + console.log( + "🔢 对象z-index顺序:", + objectsWithZIndex.map((item) => ({ + id: item.object.id, + type: item.object.type, + zIndex: item.zIndex, + })) + ); + } + + /** + * 收集要栅格化的图层(递归收集子图层) + * @param {Object} sourceLayer 源图层 + * @returns {Array} 图层数组 + * @private + */ + _collectLayersToRasterize(sourceLayer) { + const result = [sourceLayer]; + + // 如果是组图层,收集所有子图层 + if (sourceLayer.children && sourceLayer.children.length > 0) { + sourceLayer.children.forEach((childLayer) => { + if (childLayer) { + result.push(...this._collectLayersToRasterize(childLayer)); + } + }); + } + + return result; + } + + /** + * 保存原始对象状态 + * @private + */ + _saveOriginalObjectStates() { + this.objectsToRasterize.forEach((object) => { + if (object && object.id) { + const originalState = { + left: object.left, + top: object.top, + scaleX: object.scaleX, + scaleY: object.scaleY, + angle: object.angle, + flipX: object.flipX, + flipY: object.flipY, + opacity: object.opacity, + originX: object.originX, + originY: object.originY, + layerId: object.layerId, + layerName: object.layerName, + width: object.width, + height: object.height, + strokeWidth: object.strokeWidth, + visible: object.visible, + }; + this.originalObjectStates.set(object.id, originalState); + } + }); + } + + /** + * 创建栅格化图层并替换原图层 + * @param {fabric.Image} rasterizedImage 栅格化后的图像 + * @private + */ + async _createRasterizedLayer(rasterizedImage) { + // 从画布中移除原有对象 + this.objectsToRasterize.forEach((obj) => { + removeCanvasObjectByObject(this.canvas, obj); + }); + + // 添加栅格化图像到画布 + this.canvas.add(rasterizedImage); + this.canvas.setActiveObject(rasterizedImage); + + // 创建新的栅格化图层 + this.rasterizedLayer = createLayer({ + id: this.rasterizedLayerId, + name: `${this.layer.name} (栅格化)`, + type: LayerType.BITMAP, + visible: this.layer.visible, + locked: this.layer.locked, + opacity: this.layer.opacity, + fabricObjects: [rasterizedImage], + }); + + // 更新图像对象的图层关联 + rasterizedImage.set({ + id: this.resterizedId, + type: "image", + layerId: this.rasterizedLayerId, + layerName: this.rasterizedLayer.name, + }); + + // 在适当位置添加新的栅格化图层 + // 1.当前如果是子图层,则插入到子图层的位置 + const { layer, parent } = findLayerRecursively( + this.layers.value, + this.layerId + ); + + let insertIndex = 0; + // 说明是子图层 + if (parent) { + this.layers.value.some((l, index) => { + if (l.id === parent.id) { + insertIndex = this.layers.value?.[index].children?.findIndex( + (fItem) => fItem.id === this.layerId + ); + return true; // 找到父图层,停止循环 + } + return false; // 继续查找 + }); + } else { + insertIndex = this.layers.value.findIndex((l) => l.id === this.layerId); + } + + if (insertIndex !== -1) { + if (parent) { + const pIndex = this.layers.value.findIndex((l) => l.id === parent.id); + this.layers.value[pIndex].children?.splice?.( + insertIndex, + 1, + this.rasterizedLayer + ); + } else this.layers.value.splice(insertIndex, 1, this.rasterizedLayer); + } else { + // 2.如果没有找到父图层,则添加到顶层 + this.layers.value.unshift(this.rasterizedLayer); + } + + // // 替换图层结构 + // if (this.isGroupLayer) { + // // 组图层:移除所有相关图层 + // const layerIdsToRemove = this.layersToRasterize.map((layer) => layer.id); + // this.layers.value = this.layers.value.filter( + // (layer) => !layerIdsToRemove.includes(layer.id) + // ); + // } else { + // // 普通图层:移除原图层 + // const layerIndex = this.layers.value.findIndex( + // (l) => l.id === this.layerId + // ); + // if (layerIndex !== -1) { + // this.layers.value.splice(layerIndex, 1); + // } + // } + + // 设置为活动图层 + this.activeLayerId.value = this.rasterizedLayerId; + + await this.layerManager?.updateLayersObjectsInteractivity(false); + + console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`); + } + + getInfo() { + return { + name: this.name, + originalLayerId: this.layerId, + originalLayerName: this.layer?.name, + rasterizedLayerId: this.rasterizedLayerId, + rasterizedLayerName: this.rasterizedLayer?.name, + isGroupLayer: this.isGroupLayer, + objectCount: this.objectsToRasterize?.length || 0, + }; + } +} diff --git a/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js b/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js index dbe1e4cc..a6e41b98 100644 --- a/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js @@ -39,6 +39,7 @@ export class BatchInitializeRedGreenModeCommand extends Command { // 存储加载的图片对象 this.clothingImage = null; this.redGreenImage = null; + this.redGreenImageMask = null; // 存储新创建的图层ID this.newEmptyLayerId = null; @@ -68,7 +69,11 @@ export class BatchInitializeRedGreenModeCommand extends Command { // 3. 保存原始状态 this.originalBackgroundObject = backgroundLayer.fabricObject ? { - ...backgroundLayer.fabricObject.toObject(), + ...backgroundLayer.fabricObject.toObject([ + "id", + "type", + "layerId", + ]), ref: backgroundLayer.fabricObject, } : null; @@ -136,12 +141,31 @@ export class BatchInitializeRedGreenModeCommand extends Command { // clipPathImg.set({ // absolutePositioned: true, // }); - this.redGreenImage.set({ - absolutePositioned: true, + + // 克隆衣服底图作为裁剪对象 + this.redGreenImageMask = await new Promise((resolve, reject) => { + this.redGreenImage.clone((clonedImg) => { + if (!clonedImg) { + reject(new Error("无法克隆红绿图")); + return; + } + resolve(clonedImg); + }); }); + this.redGreenImageMask.set({ + absolutePositioned: true, + opacity: 0.01, // 设置为几乎透明 + type: "redGreenImageMask", + id: generateId("redGreenImageMask_"), + }); + // this.canvas.add(this.redGreenImageMask); + this.canvas.clipPath = this.redGreenImageMask; + this.redGreenImageMask.sendToBack(); + this.redGreenImageMask.setCoords(); + const activeLayer = this.layerManager.getActiveLayer(); - activeLayer.clippingMask = this.redGreenImage.toObject(["id"]); + // activeLayer.clippingMask = this.redGreenImageMask.toObject(["id"]); activeLayer.opacity = this.normalLayerOpacity; // activeLayer?.fabricObjects.forEach((obj) => { // obj.set({ @@ -181,9 +205,9 @@ export class BatchInitializeRedGreenModeCommand extends Command { async _createAndActivateEmptyLayer() { // 创建新的空白图层 const newLayerName = "绘制图层"; - const newLayerId = this.layerManager.createLayer( + const newLayerId = await this.layerManager.createLayer( newLayerName, - LayerType.GROUP, + LayerType.BITMAP, { undoable: false, } @@ -306,7 +330,7 @@ export class BatchInitializeRedGreenModeCommand extends Command { */ async _setupBackgroundLayer(backgroundLayer, clothingImage) { let backgroundObject = backgroundLayer.fabricObject; - let { object } = findObjectById(this.canvas, backgroundObject.id); + const { object } = findObjectById(this.canvas, backgroundObject.id); if (!object) { // 创建白色背景矩形 @@ -389,12 +413,16 @@ export class BatchInitializeRedGreenModeCommand extends Command { // 清除固定图层原有内容 if (fixedLayer.fabricObject) { - this.canvas.remove(fixedLayer.fabricObject); + const { object } = findObjectById( + this.canvas, + fixedLayer.fabricObject.id + ); + if (object) this.canvas.remove(object); } // 添加到画布和固定图层 this.canvas.add(img); - fixedLayer.fabricObject = img; + fixedLayer.fabricObject = img.toObject(["id", "type", "layerId"]); this.clothingImage = img; } @@ -424,15 +452,15 @@ export class BatchInitializeRedGreenModeCommand extends Command { // 清除普通图层原有内容 if (normalLayer.fabricObjects) { normalLayer.fabricObjects.forEach((obj) => { - this.canvas.remove(obj); + const { object } = findObjectById(this.canvas, obj.id); + if (object) { + this.canvas.remove(object); + } }); } - - // 给img设置裁剪,裁剪图为衣服底图 - // 添加到画布和普通图层 this.canvas.add(img); - normalLayer.fabricObjects = [img]; + normalLayer.fabricObjects = [img.toObject(["id", "type", "layerId"])]; this.redGreenImage = img; } diff --git a/src/component/Canvas/CanvasEditor/commands/TextCommands.js b/src/component/Canvas/CanvasEditor/commands/TextCommands.js index b9604f3f..6deb2b58 100644 --- a/src/component/Canvas/CanvasEditor/commands/TextCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/TextCommands.js @@ -1,3 +1,5 @@ +import { generateId, optimizeCanvasRendering } from "../utils/helper"; +import { createLayer, LayerType, OperationType } from "../utils/layerHelper"; import { Command } from "./Command"; /** @@ -302,3 +304,323 @@ export class CompositeTextCommand extends Command { return true; } } + +/** + * 创建文本命令 + * 用于创建文本对象和图层的组合操作 + */ +export class CreateTextCommand extends Command { + constructor(options) { + super({ + name: "创建文本", + }); + this.canvas = options.canvas; + this.layerManager = options.layerManager; + this.x = options.x; + this.y = options.y; + this.textOptions = options.textOptions || {}; + // 生成唯一ID + this.textId = options?.textId || this.generateId("text_"); + this.layerId = options?.layerId || generateId("text_layer_"); + + // 生成的对象和图层信息 + this.textObject = null; + this.oldActiveLayerId = null; + + // 默认文本属性 + this.defaultOptions = { + text: "双击编辑文本", + fontFamily: "Arial", + fontSize: 24, + fontWeight: "normal", + fontStyle: "normal", + textAlign: "left", + fill: "#000000", + opacity: 1, + underline: false, + overline: false, + linethrough: false, + textBackgroundColor: "transparent", + lineHeight: 1.16, + charSpacing: 0, + }; + } + + async execute() { + if (!this.canvas || !this.layerManager) { + console.error("Canvas或LayerManager不存在"); + return null; + } + // 保存当前活动图层 + this.oldActiveLayerId = this.layerManager.activeLayerId?.value; + + // 合并默认选项和用户选项 + const finalOptions = { + ...this.defaultOptions, + ...this.textOptions, + left: this.x, + top: this.y, + }; + + try { + await optimizeCanvasRendering(this.canvas, async () => { + // 创建文本对象 + this.textObject = new fabric.IText(finalOptions.text, { + ...finalOptions, + originX: "center", + originY: "center", + }); + + // 创建文本图层 + const layerName = this.textOptions.layerName || "文本图层"; + const layer = createLayer({ + id: this.layerId, + name: layerName, + type: LayerType.TEXT, + }); + + // 设置对象的图层关联 + this.textObject.set({ + id: this.textId, + layerId: this.layerId, + layerName: layerName, + }); + + // 智能插入图层到合适位置 + this._insertLayerAtCorrectPosition(layer); + + // 添加到画布 + this.canvas.add(this.textObject); + + // 取消其他对象的选中状态 + this.canvas.discardActiveObject(); + + // 设置新创建的文本对象为活动对象 + this.canvas.setActiveObject(this.textObject); + + // 更新图层的对象列表 + if (layer) { + layer.fabricObjects = layer.fabricObjects || []; + layer.fabricObjects.push( + this.textObject.toObject(["id", "layerId", "layerName"]) + ); + } + + // 现在可以安全地设置为活动图层 + this.layerManager.setActiveLayer(this.layerId); + + // 更新对象交互性 + await this.layerManager?.updateLayersObjectsInteractivity?.(false); + + // 切换到选择工具 + this.layerManager?.toolManager?.setTool?.(OperationType.SELECT); + }); + + console.log( + `✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})` + ); + return this.textObject; + } catch (error) { + console.error("创建文本对象失败:", error); + // 如果创建失败,需要清理已创建的资源 + await this.undo(); + throw error; + } + } + + /** + * 智能插入图层到正确位置 + * 根据当前激活图层位置确定新图层插入位置 + * @param {Object} newLayer 要插入的新图层 + * @private + */ + _insertLayerAtCorrectPosition(newLayer) { + const layers = this.layerManager.layers.value; + const currentActiveLayerId = this.layerManager.activeLayerId?.value; + + // 如果没有当前激活图层,插入到顶部(索引0) + if (!currentActiveLayerId) { + layers.splice(0, 0, newLayer); + return; + } + + // 查找当前激活图层的位置 + const { + layer: activeLayer, + parent: parentLayer, + index: activeIndex, + } = this._findLayerPosition(currentActiveLayerId); + + if (!activeLayer) { + // 没找到激活图层,插入到顶部 + layers.splice(0, 0, newLayer); + return; + } + + // 确定插入位置 + let insertIndex = 0; + + if (parentLayer) { + // 当前激活图层是子图层 + // 在同一父图层内,插入到激活子图层之上 + insertIndex = Math.max(0, activeIndex); + parentLayer.children = parentLayer.children || []; + parentLayer.children.splice(insertIndex, 0, newLayer); + + console.log( + `新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})` + ); + } else { + // 当前激活图层是一级图层 + // 在一级图层中,插入到激活图层之上 + const activeLayerIndex = layers.findIndex( + (layer) => layer.id === currentActiveLayerId + ); + insertIndex = Math.max(0, activeLayerIndex); + layers.splice(insertIndex, 0, newLayer); + + console.log(`新图层已插入到一级图层位置: ${insertIndex}`); + } + } + + /** + * 查找图层位置信息 + * @param {String} layerId 图层ID + * @returns {Object} 包含图层、父图层和索引的对象 + * @private + */ + _findLayerPosition(layerId) { + const layers = this.layerManager.layers.value; + + // 先在一级图层中查找 + for (let i = 0; i < layers.length; i++) { + const layer = layers[i]; + + if (layer.id === layerId) { + return { + layer: layer, + parent: null, + index: i, + }; + } + + // 在子图层中查找 + if (layer.children && Array.isArray(layer.children)) { + for (let j = 0; j < layer.children.length; j++) { + const childLayer = layer.children[j]; + if (childLayer.id === layerId) { + return { + layer: childLayer, + parent: layer, + index: j, + }; + } + } + } + } + + return { + layer: null, + parent: null, + index: -1, + }; + } + + /** + * 生成唯一ID + * @returns {String} 唯一ID + */ + generateId() { + return `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`; + } + + getInfo() { + return { + name: this.name, + textId: this.textObject?.id, + layerId: this.layerId, + text: this.textOptions.text || this.defaultOptions.text, + position: { x: this.x, y: this.y }, + }; + } + + async undo() { + try { + // 从画布移除文本对象 + if (this.textObject && this.canvas) { + this.canvas.remove(this.textObject); + } + + // 智能移除创建的图层 + if (this.layerId && this.layerManager) { + this._removeLayerFromCorrectPosition(); + } + + // 恢复原活动图层 + if (this.oldActiveLayerId && this.layerManager) { + // 检查原活动图层是否还存在 + const originalLayer = this.layerManager.getLayerById( + this.oldActiveLayerId + ); + if (originalLayer) { + this.layerManager.setActiveLayer(this.oldActiveLayerId); + } else { + // 如果原图层不存在,设置为第一个可用的普通图层 + const availableLayers = this.layerManager.layers.value.filter( + (layer) => !layer.isBackground && !layer.isFixed && !layer.locked + ); + if (availableLayers.length > 0) { + this.layerManager.setActiveLayer(availableLayers[0].id); + } + } + } + + // 更新对象交互性 + await this.layerManager.updateLayersObjectsInteractivity(); + + // 重新渲染画布 + if (this.canvas) { + this.canvas.renderAll(); + } + + console.log(`↩️ 文本创建操作已撤销`); + return true; + } catch (error) { + console.error("撤销文本创建操作失败:", error); + return false; + } + } + + /** + * 智能移除图层 + * 根据图层位置(一级图层或子图层)进行相应的移除操作 + * @private + */ + _removeLayerFromCorrectPosition() { + const layers = this.layerManager.layers.value; + + // 查找图层位置信息 + const positionInfo = this._findLayerPosition(this.layerId); + + if (!positionInfo.layer) { + console.warn(`要移除的图层不存在: ${this.layerId}`); + return; + } + + if (positionInfo.parent) { + // 从子图层中移除 + if (positionInfo.parent.children && positionInfo.index >= 0) { + positionInfo.parent.children.splice(positionInfo.index, 1); + console.log( + `已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})` + ); + } + } else { + // 从一级图层中移除 + if (positionInfo.index >= 0) { + layers.splice(positionInfo.index, 1); + console.log(`已从一级图层移除: ${this.layerId}`); + } + } + } +} diff --git a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue index d03f873f..3517a46d 100644 --- a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue @@ -148,7 +148,6 @@ import { BrushStore } from "../store/BrushStore"; import { OperationType } from "../utils/layerHelper"; import { inject } from "vue"; import VerticalSlider from "./VerticalSlider.vue"; -import SvgIcon from "@/component/Canvas/SvgIcon/index.vue" const props = defineProps({ activeTool: { diff --git a/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue b/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue index a17d6865..6926638e 100644 --- a/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue +++ b/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue @@ -11,8 +11,6 @@ import { import { OperationType } from "../utils/layerHelper"; import BrushPanel from "./BrushPanel.vue"; import { BrushStore } from "../store/BrushStore"; -import SvgIcon from "@/component/Canvas/SvgIcon/index.vue" - // 提供brushStore给子组件 provide("brushStore", BrushStore); @@ -47,30 +45,34 @@ const brushPanelRef = ref(null); // return props.activeTool === OperationType.DRAW; // }); -function updateCanvasSize() { +function updateCanvasSize( + { width, height } = { width: props.width, height: props.height } +) { if (!layerManager) { console.warn("LayerManager 未初始化,无法调整背景层尺寸"); return; } - // 检查画布上是否有除了背景层的其他元素 - const hasOtherElements = layerManager.layers.value.some((layer) => { - if (layer.isBackground) return false; - // 检查普通图层是否有对象 - if (layer.fabricObjects && layer.fabricObjects.length > 0) return true; - // 检查固定图层是否有对象 - if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) - return true; - return false; - }); + layerManager.resizeCanvas(width, height); - if (hasOtherElements) { - // 有其他元素时使用等比缩放命令 - layerManager.resizeCanvasWithScale(props.canvasWidth, props.canvasHeight); - } else { - // 只有背景层时使用普通调整命令 - layerManager.resizeCanvas(props.canvasWidth, props.canvasHeight); - } + // // 检查画布上是否有除了背景层的其他元素 + // const hasOtherElements = layerManager.layers.value.some((layer) => { + // if (layer.isBackground) return false; + // // 检查普通图层是否有对象 + // if (layer.fabricObjects && layer.fabricObjects.length > 0) return true; + // // 检查固定图层是否有对象 + // if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) + // return true; + // return false; + // }); + + // if (hasOtherElements) { + // // 有其他元素时使用等比缩放命令 + // layerManager.resizeCanvasWithScale(width, height); + // } else { + // // 只有背景层时使用普通调整命令 + // layerManager.resizeCanvas(width, height); + // } emit("canvas-size-change"); } @@ -268,27 +270,39 @@ onMounted(() => { " -->
- Width - {{ $t("宽度") }} +
- Height - {{ $t("高度") }} +
- Color + {{ $t("颜色") }}
{
--> -
+
@@ -439,10 +454,11 @@ onMounted(() => { } .setting-input { - width: 60px; + width: 80px; +} + +.setting-input :deep(.ant-input-number-input) { padding: 4px 8px; - border: 1px solid #ddd; - border-radius: 4px; font-size: 14px; } @@ -494,15 +510,7 @@ onMounted(() => { } .brush-selector { - // display: flex; - // align-items: center; - // border: 1px solid #ddd; - // border-radius: 4px; - // padding: 5px; - // cursor: pointer; - // background-color: white; - // width: 80px; - // justify-content: space-between; + display: block; } .brush-preview { diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue index c2da15e7..32b1cc20 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue @@ -43,10 +43,10 @@ const props = defineProps({ type: Boolean, default: true, }, - thumbnailUrl: { - type: String, - default: null, - }, + // thumbnailUrl: { + // type: String, + // default: null, + // }, isHidenDragHandle: { type: Boolean, default: false, @@ -404,14 +404,11 @@ function findParentLayerId() {
- {{ - getLayerTypeIcon(layer) - }}
diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue index 126a7094..253d19a8 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersList.vue @@ -119,6 +119,8 @@ const canDeleteComputed = computed(() => {