diff --git a/src/assets/icons/CThemeColor.svg b/src/assets/icons/CThemeColor.svg new file mode 100644 index 00000000..3bf50549 --- /dev/null +++ b/src/assets/icons/CThemeColor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js b/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js new file mode 100644 index 00000000..4913e7e5 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js @@ -0,0 +1,134 @@ +import { Command } from "./Command"; +import { findLayerRecursively } from "../utils/layerHelper"; +import { fabric } from "fabric-with-all"; +import { + findObjectById, + generateId, + insertObjectAtZIndex, + removeCanvasObjectByObject, +} from "../utils/helper"; + +/** + * 填充组图层背景命令 + */ +export class FillLayerBackgroundCommand extends Command { + constructor(options) { + super({ name: "填充组图层背景", saveState: true }); + this.canvas = options.canvas; + this.layers = options.layers; + this.canvasManager = options.canvasManager; + this.layerId = options.layerId; + this.fillColor = options.fillColor; + this.oldFill = null; + this.oldFillColor = null; + this.newFill = null; + + const { layer } = findLayerRecursively(this.layers.value, this.layerId); + + this.layer = layer; + + this.group = null; + + this.originalfabricObjects = this._collectOriginalObjects(); // 记录所有的原始对象 + } + + async execute() { + const layer = this.layer; + // 只允许填充背景层或普通图层 + if (!layer.isBackground && !layer.isFixed && !layer.fabricObjects?.length) return false; + + // 记录原填充色 + this.oldFill = layer.fill ?? null; + this.oldFillColor = layer.oldFillColor ?? null; + + // 更新fabric对象填充 + // 判断是否有遮罩层 有遮罩使用遮罩层大小 否则使用第一个大小 + const originalInfo = + findObjectById(this.canvas.value, layer.fabricObjects?.[0]?.id)?.object?.getBoundingRect?.( + true, + true + ) || + layer?.clippingMask || + layer.fabricObjects?.[0]; + + this.newFill = new fabric.Rect({ + width: originalInfo.width, + height: originalInfo.height, + left: originalInfo.left || 0, + top: originalInfo.top || 0, + fill: this.fillColor, + layerId: this.layerId, + id: generateId("fill-"), + selectable: false, + evented: false, + originX: originalInfo.originX || "center", + originY: originalInfo.originY || "center", + scaleX: originalInfo.scaleX || 1, + scaleY: originalInfo.scaleY || 1, + }); + + // 判断fabricObjects是否是组,是组则添加填充到最前面,否则创建组 + if (layer.fabricObjects && layer.fabricObjects.length > 0) { + // 如果是组,直接添加到组中 + // 否则创建一个新的组 插入到原本的图层对象前面 + const layerObjects = this.canvas + .getObjects() + .reverse() + .filter((obj) => obj.layerId === this.layerId); + let insertIndex = + this.canvas.getObjects()?.findIndex((obj) => obj.id === layer.fabricObjects?.[0]?.id) || 0; + insertIndex = insertIndex == -1 ? 0 : insertIndex; + + this.group = new fabric.Group([this.newFill, ...layerObjects]); + this.group.set({ + id: layerObjects[0]?.id || generateId("group-"), + layerId: layerObjects[0]?.layerId, + }); + this.group.setCoords(); + layer.fabricObjects = [this.group?.toObject?.(["id", "layerId"]) || this.group]; + removeCanvasObjectByObject(this.canvas, layer.fabricObjects[0]); + + insertObjectAtZIndex(this.canvas, this.group, insertIndex, false); // 插入到画布的指定位置 + // 将新填充对象添加到画布 + } + + this.canvas.renderAll(); + + layer.fill = this.newFill.toObject(["id", "layerId"]); + layer.fillColor = this.fillColor; + + this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); + return true; + } + + async undo() { + this.layer.fillColor = this.oldFillColor; + this.layer.fill = this.oldFill; + + this.group.removeWithUpdate(this.newFill); + this.canvas.remove(this.newFill); + this.canvas.renderAll(); + + this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); + return true; + } + + _collectOriginalObjects() { + if (this.layer.children && this.layer.children.length > 0) { + // 如果是组图层,收集所有子图层的fabric对象 + return this.layer.children + .flatMap((child) => child.fabricObjects || []) + .map((obj) => { + return findObjectById(this.canvas.value, obj.id)?.object || obj; + }); + } else if (this.layer.fabricObjects && this.layer.fabricObjects.length > 0) { + // 如果是普通图层,直接返回其fabric对象 + return this.layer.fabricObjects.map((obj) => { + return findObjectById(this.canvas.value, obj.id)?.object || obj; + }); + } else { + // 如果没有fabric对象,返回空数组 + return []; + } + } +} diff --git a/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js b/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js new file mode 100644 index 00000000..55e10a4b --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js @@ -0,0 +1,181 @@ +import { Command } from "./Command"; +import { findLayerRecursively } from "../utils/layerHelper"; +import { fabric } from "fabric-with-all"; +import { + findObjectById, + generateId, + insertObjectAtZIndex, + removeCanvasObjectByObject, +} from "../utils/helper"; +import { restoreFabricObject } from "../utils/objectHelper"; + +/** + * 填充图层背景命令 + */ +export class FillLayerBackgroundCommand extends Command { + constructor(options) { + super({ name: "填充图层背景", saveState: true }); + this.canvas = options.canvas; + this.layers = options.layers; + this.canvasManager = options.canvasManager; + this.layerId = options.layerId; + this.fillColor = options.fillColor; + this.oldFill = null; + this.oldFillColor = null; + this.newFill = null; + + const { layer } = findLayerRecursively(this.layers.value, this.layerId); + + this.layer = layer; + + this.group = null; + + this.isGroupLayer = layer.isGroup || !!layer.children?.length || false; // 是否为组图层 + + this.originalfabricObjects = this._collectOriginalObjects(); // 记录所有的原始对象 + } + + async execute() { + const layer = this.layer; + // 只允许填充背景层或普通图层 + // if (!layer.isBackground && !layer.isFixed && !layer.fabricObjects?.length) return false; + + // 记录原填充色 + this.oldFill = layer.fill ?? null; + this.oldFillColor = layer.oldFillColor ?? null; + + // 更新fabric对象填充 + // 判断是否有遮罩层 有遮罩使用遮罩层大小 否则使用第一个大小 + const { object } = findObjectById(this.canvas, layer.fabricObjects?.[0]?.id); + + if (object) { + const originalInfo = object?.getBoundingRect?.(true, true); + this.newFill = new fabric.Rect({ + width: originalInfo.width, + height: originalInfo.height, + left: originalInfo.left || 0, + top: originalInfo.top || 0, + fill: this.fillColor, + layerId: this.layerId, + id: generateId("fill-"), + selectable: false, + evented: false, + originX: object.originX || "center", + originY: object.originY || "center", + scaleX: object.scaleX || 1, + scaleY: object.scaleY || 1, + }); + } + + if (layer.clippingMask) { + // 反序列化 clippingMask + const clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas); + clippingMaskFabricObject.clipPath = null; + + clippingMaskFabricObject.set({ + // 设置绝对定位 + // ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位 + absolutePositioned: true, + }); + + this.newFill = new fabric.Rect({ + width: clippingMaskFabricObject.width, + height: clippingMaskFabricObject.height, + left: clippingMaskFabricObject.left || 0, + top: clippingMaskFabricObject.top || 0, + fill: this.fillColor, + layerId: this.layerId, + id: generateId("fill-"), + selectable: false, + evented: false, + originX: clippingMaskFabricObject.originX || "center", + originY: clippingMaskFabricObject.originY || "center", + scaleX: clippingMaskFabricObject.scaleX || 1, + scaleY: clippingMaskFabricObject.scaleY || 1, + }); + + this.newFill.clipPath = clippingMaskFabricObject; // 设置填充的遮罩 + this.newFill.dirty = true; // 标记为脏,以便重新渲染 + this.newFill.setCoords(); + } + + // 判断fabricObjects是否是组,是组则添加填充到最前面,否则创建组 + if (layer.fabricObjects && layer.fabricObjects.length > 0) { + // 如果是组,直接添加到组中 + // 否则创建一个新的组 插入到原本的图层对象前面 + // const layerObjects = this.canvas + // .getObjects() + // .reverse() + // .filter((obj) => obj.layerId === this.layerId); + let insertIndex = + this.canvas.getObjects()?.findIndex((obj) => obj.id === layer.fabricObjects?.[0]?.id) || 0; + insertIndex = insertIndex == -1 ? 0 : insertIndex; + + // this.group = new fabric.Group([this.newFill, ...layerObjects]); + // this.group.set({ + // id: layerObjects[0]?.id || generateId("group-"), + // layerId: layerObjects[0]?.layerId, + // }); + // this.group.setCoords(); + layer.fabricObjects = [this.newFill?.toObject?.(["id", "layerId"]) || this.newFill]; + // removeCanvasObjectByObject(this.canvas, layer.fabricObjects[0]); + insertObjectAtZIndex(this.canvas, this.newFill, insertIndex, false); // 插入到画布的指定位置 + // 将新填充对象添加到画布 + } else if (layer.children && layer.children.length > 0) { + // 如果是组图层,直接添加到组中 + let insertIndex = + this.canvas + .getObjects() + .findIndex( + (obj) => + obj.id === this.originalfabricObjects[this.originalfabricObjects.length - 1]?.id + ) || 0; + insertIndex = insertIndex == -1 ? 0 : insertIndex; + + insertObjectAtZIndex(this.canvas, this.newFill, insertIndex, false); // 插入到画布的指定位置 + } + + this.canvas.renderAll(); + + layer.fill = this.newFill.toObject(["id", "layerId"]); + layer.fillColor = this.fillColor; + + // 重新排序 + // 使用LayerSort工具重新排列画布对象(如果可用) + await this?.canvasManager?.layerManager?.layerSort?.rearrangeObjects?.(); + + this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); + return true; + } + + async undo() { + this.layer.fillColor = this.oldFillColor; + this.layer.fill = this.oldFill; + + this.group.removeWithUpdate(this.newFill); + this.canvas.remove(this.newFill); + this.canvas.renderAll(); + + this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); + return true; + } + + _collectOriginalObjects() { + if (this.layer.children && this.layer.children.length > 0) { + // 如果是组图层,收集所有子图层的fabric对象 + return this.layer.children + .flatMap((child) => child.fabricObjects || []) + .map((obj) => { + return findObjectById(this.canvas.value, obj.id)?.object || obj; + }); + } else if (this.layer.fabricObjects && this.layer.fabricObjects.length > 0) { + // 如果是普通图层,直接返回其fabric对象 + return this.layer.fabricObjects.map((obj) => { + return findObjectById(this.canvas.value, obj.id)?.object || obj; + }); + } else { + // 如果没有fabric对象,返回空数组 + return []; + } + } +} diff --git a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js index 69d2ddc8..6c5d7259 100644 --- a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js @@ -12,19 +12,19 @@ import { message } from "ant-design-vue"; import { restoreFabricObject } from "../utils/objectHelper"; /** - * 栅格化图层命令 + * 组合图层命令 * 将图层中的所有矢量对象转换为位图图像 - * 支持普通图层和组图层的栅格化 + * 支持普通图层和组图层的组合 */ export class RasterizeLayerCommand extends Command { constructor(options) { super({ - name: "栅格化图层", + name: "组合图层", saveState: true, }); this.canvas = options.canvas; this.layers = options.layers; - this.layerId = options.layerId; // 指定要栅格化的图层ID + this.layerId = options.layerId; // 指定要组合的图层ID // 是否包含锁定对象 this.hasLocked = options.hasLocked || true; // 是否包含隐藏对象 @@ -43,9 +43,9 @@ export class RasterizeLayerCommand extends Command { throw new Error(`图层 ${this.layerId} 不存在`); } - // 检查是否可以栅格化 + // 检查是否可以组合 if (this.layer.isBackground || this.layer.isFixed) { - throw new Error("背景图层和固定图层不能栅格化"); + throw new Error("背景图层和固定图层不能组合"); } // 生成新图层ID @@ -53,7 +53,7 @@ export class RasterizeLayerCommand extends Command { this.resterizedId = generateId("rasterized_"); this.rasterizedLayer = null; - // 要栅格化的图层和对象 + // 要组合的图层和对象 this.layersToRasterize = []; this.objectsToRasterize = []; @@ -67,7 +67,7 @@ export class RasterizeLayerCommand extends Command { } if (this.objectsToRasterize.length === 0) { - throw new Error("图层没有内容可栅格化"); + throw new Error("图层没有内容可组合"); } try { @@ -77,7 +77,7 @@ export class RasterizeLayerCommand extends Command { // 检查是否有遮罩对象 const maskObject = await this._getMaskObject(); - // 创建栅格化图像 + // 创建组合图像 const rasterizedImage = await createRasterizedImage({ canvas: this.canvas, fabricObjects: this.objectsToRasterize, @@ -86,18 +86,18 @@ export class RasterizeLayerCommand extends Command { isGroupWithMask: this.isGroupLayer && !!maskObject, // 标记是否为带遮罩的组 }); - // 创建新的栅格化图层并替换原图层 + // 创建新的组合图层并替换原图层 await this._createRasterizedLayer(rasterizedImage); // 切换到选择工具 this.layerManager?.toolManager?.setTool?.(OperationType.SELECT); - console.log(`✅ 图层 ${this.layer.name} 栅格化完成`); + console.log(`✅ 图层 ${this.layer.name} 组合完成`); this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.rasterizedLayerId); return this.rasterizedLayerId; } catch (error) { - console.error("栅格化图层失败:", error); + console.error("组合图层失败:", error); throw error; } } @@ -109,7 +109,7 @@ export class RasterizeLayerCommand extends Command { try { await optimizeCanvasRendering(this.canvas, async () => { - // 移除栅格化后的图像对象 + // 移除组合后的图像对象 if (this.rasterizedImage) { removeCanvasObjectByObject(this.canvas, this.rasterizedImage); } @@ -141,10 +141,10 @@ export class RasterizeLayerCommand extends Command { await this.layerManager?.updateLayersObjectsInteractivity(false); }); - console.log(`↩️ 图层 ${this.layer.name} 栅格化已撤销`); + console.log(`↩️ 图层 ${this.layer.name} 组合已撤销`); return true; } catch (error) { - console.error("撤销栅格化失败:", error); + console.error("撤销组合失败:", error); throw error; } } @@ -154,7 +154,7 @@ export class RasterizeLayerCommand extends Command { * @private */ _initializeCommand() { - // 收集要栅格化的图层和对象 + // 收集要组合的图层和对象 this._collectLayersAndObjects(); // 保存原始图层结构(深拷贝相关部分) @@ -211,7 +211,7 @@ export class RasterizeLayerCommand extends Command { } /** - * 收集要栅格化的图层和对象 + * 收集要组合的图层和对象 * @private */ _collectLayersAndObjects() { @@ -253,7 +253,7 @@ export class RasterizeLayerCommand extends Command { this.objectsToRasterize = objectsWithZIndex.map((item) => item.object); console.log( - `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化` + `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合` ); console.log( "🔢 对象z-index顺序:", @@ -266,7 +266,7 @@ export class RasterizeLayerCommand extends Command { } /** - * 收集要栅格化的图层(递归收集子图层) + * 收集要组合的图层(递归收集子图层) * @param {Object} sourceLayer 源图层 * @returns {Array} 图层数组 * @private @@ -287,12 +287,12 @@ export class RasterizeLayerCommand extends Command { } /** - * 创建栅格化图层并替换原图层 - * @param {fabric.Image} rasterizedImage 栅格化后的图像 + * 创建组合图层并替换原图层 + * @param {fabric.Image} rasterizedImage 组合后的图像 * @private */ async _createRasterizedLayer(rasterizedImage) { - // 保存栅格化图像的引用用于撤销 + // 保存组合图像的引用用于撤销 this.rasterizedImage = rasterizedImage; // 从画布中移除原有对象 @@ -300,14 +300,14 @@ export class RasterizeLayerCommand extends Command { removeCanvasObjectByObject(this.canvas, obj); }); - // 添加栅格化图像到画布 + // 添加组合图像到画布 this.canvas.add(rasterizedImage); this.canvas.setActiveObject(rasterizedImage); - // 创建新的栅格化图层 + // 创建新的组合图层 this.rasterizedLayer = createLayer({ id: this.rasterizedLayerId, - name: `${this.layer.name} (栅格化)`, + name: `${this.layer.name} (组合)`, type: LayerType.BITMAP, visible: this.layer.visible, locked: this.layer.locked, @@ -323,7 +323,7 @@ export class RasterizeLayerCommand extends Command { layerName: this.rasterizedLayer.name, }); - // 在适当位置添加新的栅格化图层 + // 在适当位置添加新的组合图层 this._replaceLayerInStructure(); // 设置为活动图层 @@ -331,7 +331,7 @@ export class RasterizeLayerCommand extends Command { await this.layerManager?.updateLayersObjectsInteractivity(false); - console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`); + console.log(`🎨 组合图层 ${this.rasterizedLayer.name} 创建完成`); } /** @@ -407,7 +407,7 @@ export class RasterizeLayerCommand extends Command { /** * 导出图层命令 * 将图层中的所有矢量对象转换为位图图像 - * 支持普通图层和组图层的栅格化 + * 支持普通图层和组图层的组合 */ export class ExportLayerToImageCommand extends Command { constructor(options) { @@ -487,7 +487,7 @@ export class ExportLayerToImageCommand extends Command { } /** - * 收集要栅格化的图层和对象 + * 收集要组合的图层和对象 * @private */ _collectLayersAndObjects() { @@ -529,7 +529,7 @@ export class ExportLayerToImageCommand extends Command { this.objectsToRasterize = objectsWithZIndex.map((item) => item.object); console.log( - `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化` + `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合` ); console.log( "🔢 对象z-index顺序:", @@ -542,7 +542,7 @@ export class ExportLayerToImageCommand extends Command { } /** - * 收集要栅格化的图层(递归收集子图层) + * 收集要组合的图层(递归收集子图层) * @param {Object} sourceLayer 源图层 * @returns {Array} 图层数组 * @private diff --git a/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js b/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js index cac82099..da2249ee 100644 --- a/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js @@ -206,6 +206,16 @@ export class UpdateGroupMaskPositionCommand extends Command { } }); + if (layer?.fill) { + // 更新组图层的填充颜色 + const fabricObject = this.canvas.getObjects().find((o) => o.id === layer?.fill.id); + if (fabricObject) { + fabricObject.clipPath = clippingMaskFabricObject; + fabricObject.dirty = true; + fabricObject.setCoords(); + } + } + if (isUndo) { this.activeSelection.set({ left: this.originalSelectionPosition.left - this.deltaX, diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index 3eb05503..23d558e1 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -69,6 +69,10 @@ const contextMenuPosition = ref({ x: 0, y: 0 }); const contextMenuLayer = ref(null); const contextMenuItems = ref([]); +// 颜色填充相关 +const fillColor = ref("#ffffff"); // 默认填充颜色 +const fillColorRef = ref(null); + // 计算属性:可排序的根级图层(排除背景层和固定层) const sortableRootLayers = computed(() => { if (!layers) return []; @@ -644,9 +648,33 @@ function buildContextMenuItems(layer) { danger: true, action: () => deleteSelectedLayers(), }, - // 栅格化图层 + // 组合图层 { - label: "栅格化图层", + label: "填充图层", + icon: "CThemeColor", + disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id), + action: () => { + // 调用浏览器原生颜色选择器 + fillColorRef.value.click(); + // 监听颜色选择器的变化 + fillColorRef.value.addEventListener("change", () => { + const selectedColor = fillColor.value; + layerManager + .fillLayerBackground(layer.id, selectedColor) + .then(() => { + console.log(`✅ 已填充图层 ${layer.name} 背景颜色: ${selectedColor}`); + }) + .catch((error) => { + console.error(`❌ 填充图层 ${layer.name} 背景颜色失败:`, error); + }); + }); + // 隐藏右键菜单 + hideContextMenu(); + }, + }, + // 组合图层 + { + label: "组合图层", icon: "CPicture", disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id), action: () => { @@ -654,7 +682,7 @@ function buildContextMenuItems(layer) { hideContextMenu(); }, }, - // 栅格化图层 + // 导出图层 { label: "导出图层", icon: "CExport", @@ -689,7 +717,7 @@ function buildContextMenuItems(layer) { }, }, // { - // label: "合并组", // 不需要了 同栅格化功能一样了 + // label: "合并组", // 不需要了 同组合功能一样了 // icon: "CMergeGroup", // disabled: !isGroupLayer || isMultiple, // action: () => { @@ -1058,22 +1086,22 @@ async function handleChildLayersSort(event, childrenLayers, parentId) { } } -// 栅格化图层 +// 组合图层 async function rasterizeLayer(layerId) { if (!layerManager?.rasterizeLayer) { - console.warn("栅格化功能不可用"); + console.warn("组合功能不可用"); return; } try { const success = await layerManager.rasterizeLayer(layerId); if (success) { - console.log(`✅ 成功栅格化图层: ${layerId}`); + console.log(`✅ 成功组合图层: ${layerId}`); } else { - console.warn("栅格化图层失败"); + console.warn("组合图层失败"); } } catch (error) { - console.error("栅格化图层时发生错误:", error); + console.error("组合图层时发生错误:", error); } } @@ -1591,6 +1619,9 @@ async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex :items="contextMenuItems" @close="hideContextMenu" /> + + + diff --git a/src/component/Canvas/CanvasEditor/managers/BackgroundFillManager.js b/src/component/Canvas/CanvasEditor/managers/BackgroundFillManager.js new file mode 100644 index 00000000..4d4f8454 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/managers/BackgroundFillManager.js @@ -0,0 +1,30 @@ +import { FillLayerBackgroundCommand } from "../commands/FillLayerBackgroundCommand"; + +export class BackgroundFillManager { + constructor({ canvas, layers, commandManager, canvasManager }) { + this.canvas = canvas; + this.layers = layers; + this.commandManager = commandManager; + this.canvasManager = canvasManager; + } + + /** + * 填充指定图层背景 + * @param {string} layerId 图层ID + * @param {string} fillColor 填充颜色 + */ + async fillLayerBackground(layerId, fillColor) { + const command = new FillLayerBackgroundCommand({ + canvas: this.canvas, + layers: this.layers, + layerId, + fillColor, + canvasManager: this.canvasManager, + }); + if (this.commandManager) { + await this.commandManager.execute(command); + } else { + await command.execute(); + } + } +} diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index 0712ef64..23e1db51 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -486,6 +486,23 @@ export class CanvasManager { obj.setCoords(); // 更新对象的控制点坐标 }); + let isMaskLayer = false; + // 更新蒙层位置 + this.layers.value.forEach((layer) => { + if (layer.clippingMask) { + isMaskLayer = true; + // 如果图层有遮罩,更新遮罩位置 + layer.clippingMask.left += deltaX; + layer.clippingMask.top += deltaY; + } + }); + + if (isMaskLayer) { + requestAnimationFrame(() => { + this.layerManager?.updateLayersObjectsInteractivity?.(); + }); + } + // 如果有背景层,更新蒙层位置 if (backgroundObject && CanvasConfig.isCropBackground) { this.updateMaskPosition(backgroundObject); @@ -906,7 +923,7 @@ export class CanvasManager { "eraserable", "erasable", ]), - layers: JSON.stringify(simplifyLayersData), // 简化图层数据 + layers: simplifyLayersData, // 简化图层数据 // layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据 version: "1.0", // 添加版本信息 timestamp: new Date().toISOString(), // 添加时间戳 @@ -939,7 +956,7 @@ export class CanvasManager { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { - const tempLayers = JSON.parse(parsedJson?.layers) || []; + const tempLayers = parsedJson?.layers || []; const canvasData = parsedJson?.canvas; if (!tempLayers) { @@ -999,14 +1016,14 @@ export class CanvasManager { // 重置画布数据 this.setCanvasSize(this.canvas.width, this.canvas.height); // 重新构建对象关系 - restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects()); + // restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects()); // 验证图层关联关系 - 稳定后可以注释 - const isValidate = validateLayerAssociations( - this.layers.value, - this.canvas.getObjects() - ); + // const isValidate = validateLayerAssociations( + // this.layers.value, + // this.canvas.getObjects() + // ); - console.log("图层关联验证结果:", isValidate); + // console.log("图层关联验证结果:", isValidate); // 排序 // 使用LayerSort工具重新排列画布对象(如果可用) await this?.layerManager?.layerSort?.rearrangeObjects(); diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index e15286a2..ed83b2db 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -1,3 +1,4 @@ +import { BackgroundFillManager } from "./BackgroundFillManager"; import { AddLayerCommand, PasteLayerCommand, @@ -89,6 +90,13 @@ export class LayerManager { this.commandManager = options.commandManager; this.canvasManager = options.canvasManager || null; + this.backgroundFillManager = new BackgroundFillManager({ + canvas: this.canvas, + layers: this.layers, + commandManager: this.commandManager, + canvasManager: this.canvasManager, + }); + // 编辑器模式:draw(绘画)、select(选择)、pan(拖拽) this.editorMode = options.editorMode || CanvasConfig.defaultTool; @@ -332,6 +340,14 @@ export class LayerManager { } // 如果是组图层 则给所有子对象设置裁剪对象 if (layer.type === LayerType.GROUP || layer.children?.length > 0) { + if (layer.fill) { + const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id); + if (fabricObject) { + fabricObject.clipPath = clippingMaskFabricObject; + fabricObject.dirty = true; // 标记为脏对象 + fabricObject.setCoords(); + } + } layer.children.forEach((childLayer) => { const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id); if (childObj) { @@ -339,6 +355,15 @@ export class LayerManager { childObj.dirty = true; // 标记为脏对象 childObj.setCoords(); } + + if (childLayer.fill) { + const fabricObject = this.canvas.getObjects().find((o) => o.id === childLayer.fill.id); + if (fabricObject) { + fabricObject.clipPath = clippingMaskFabricObject; + fabricObject.dirty = true; // 标记为脏对象 + fabricObject.setCoords(); + } + } }); } else { layer.fabricObjects?.forEach((obj) => { @@ -349,9 +374,28 @@ export class LayerManager { fabricObject.setCoords(); } }); + + if (layer.fill) { + const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id); + if (fabricObject) { + fabricObject.clipPath = clippingMaskFabricObject; + fabricObject.dirty = true; // 标记为脏对象 + fabricObject.setCoords(); + } + } } }); } + + /** + * 填充图层背景 + * @param {string} layerId 图层ID + * @param {string} fillColor 填充颜色 + */ + async fillLayerBackground(layerId, fillColor) { + await this.backgroundFillManager.fillLayerBackground(layerId, fillColor); + } + /** * 创建新图层 * @param {string} name 图层名称 @@ -908,9 +952,25 @@ export class LayerManager { } } + if (child.fill) { + // 如果图层有填充颜色,设置所有对象的填充颜色 + const { object } = findObjectById(this.canvas, child.fill.id); + if (object) { + acc.push(object); + } + } + return acc; }, []); + if (layer.fill) { + // 如果图层有填充颜色,设置所有对象的填充颜色 + const { object } = findObjectById(this.canvas, layer.fill.id); + if (object) { + allObjects.push(object); + } + } + if (allObjects.length) { // 切换到选择模式 this?.toolManager?.setTool(OperationType.SELECT); @@ -2742,7 +2802,7 @@ export class LayerManager { * @returns {boolean} 是否可以栅格化 */ canRasterizeLayer(layerId) { - const layer = this.getLayerById(layerId); + const layer = findLayerRecursively(this.layers.value, layerId)?.layer; if (!layer) return false; // 不允许栅格化背景图层和固定图层 @@ -2751,13 +2811,9 @@ export class LayerManager { } // 检查图层是否有内容可以栅格化 - if (layer.type === "group") { + if (layer.type === "group" || layer.children.length > 0) { // 组图层:检查是否有子图层且子图层有内容 - return ( - layer.children && - layer.children.length > 0 && - layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0) - ); + return layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0); } else { // 普通图层:检查是否有对象 return layer.fabricObjects && layer.fabricObjects.length > 0; @@ -2835,7 +2891,7 @@ export class LayerManager { let isUpdating = false; let lastUpdateTime = 0; let hasMoved = false; // 追踪是否实际发生了移动 - const UPDATE_THRESHOLD = 16; // 约60fps + const UPDATE_THRESHOLD = 32; // 约60fps // 移动开始事件处理 const handleMovingStart = (e) => { diff --git a/src/component/Canvas/CanvasEditor/managers/brushes/fabric.brushes.js b/src/component/Canvas/CanvasEditor/managers/brushes/fabric.brushes.js index 97661d6e..3e9d859c 100644 --- a/src/component/Canvas/CanvasEditor/managers/brushes/fabric.brushes.js +++ b/src/component/Canvas/CanvasEditor/managers/brushes/fabric.brushes.js @@ -12,6 +12,7 @@ * - https://mrdoob.com/projects/harmony/ * - http://perfectionkills.com/exploring-canvas-drawing-techniques/ */ +import { fabric } from "fabric-with-all"; import { sprayBrushDataUrl } from "./data/sprayBrushData.js"; (function (fabric) { diff --git a/src/component/Canvas/CanvasEditor/utils/LayerSort.js b/src/component/Canvas/CanvasEditor/utils/LayerSort.js index ebcf4620..a07342d3 100644 --- a/src/component/Canvas/CanvasEditor/utils/LayerSort.js +++ b/src/component/Canvas/CanvasEditor/utils/LayerSort.js @@ -96,6 +96,10 @@ export class LayerSort { * @returns {number} 更新后的z-index */ processLayerObjects(layer, currentZIndex, zIndexMap) { + if (layer.fill) { + // 如果图层有填充,则填充在最底层 + zIndexMap.set(layer.fill.id, currentZIndex++); + } // 检查是否有子图层(组图层) if (layer.children?.length > 0) { // 处理每个子图层 diff --git a/src/component/Canvas/CanvasEditor/utils/layerUtils.js b/src/component/Canvas/CanvasEditor/utils/layerUtils.js index 541cd985..1a84e616 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerUtils.js +++ b/src/component/Canvas/CanvasEditor/utils/layerUtils.js @@ -1,4 +1,4 @@ -import { isArray } from "lodash-es"; +import { fill, isArray } from "lodash-es"; /** * 图层关联工具类 @@ -193,6 +193,8 @@ export function simplifyLayers(layers) { .filter((obj) => obj !== null) : [], children: layer.children && isArray(layer.children) ? simplifyLayers(layer.children) : [], + fill: layer?.fill || null, + fillColor: layer.fillColor, }; return simplifiedLayer;