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;