feat: 更新填充组图层背景命令,增强图层管理和颜色填充功能,优化图层选择和渲染逻辑

This commit is contained in:
bighuixiang
2025-07-17 13:46:13 +08:00
parent 26581b234a
commit 695f8045f9
8 changed files with 126 additions and 54 deletions

View File

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752460567249" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3540" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M876.8 176h-294.4l-67.2 134.4H137.6v128H16l121.6 409.6h806.4v-672z m-230.4 67.2h227.2v67.2h-259.2z m176 195.2H211.2v-60.8h665.6v243.2z" fill="#D8987C" p-id="3541"></path><path d="M873.6 540.192a250.464 250.464 0 0 1 50.4 45.408 149.312 149.312 0 0 1 5.44 194.784 165.856 165.856 0 0 1-101.184 56.48 298.464 298.464 0 0 1-108.192-2.336 352.416 352.416 0 0 0-51.488-5.632 28.8 28.8 0 0 0-33.568 23.648 30.24 30.24 0 0 0 5.76 23.744 339.616 339.616 0 0 0 27.136 35.456 69.728 69.728 0 0 1 16.928 31.68 25.184 25.184 0 0 1-15.712 30.272 203.776 203.776 0 0 1-100.448 17.664 479.36 479.36 0 0 1-247.552-81.76 218.816 218.816 0 0 1-93.696-141.312 187.904 187.904 0 0 1 32-145.408 325.76 325.76 0 0 1 138.176-115.2 481.728 481.728 0 0 1 373.44-19.2c1.344 0.64 100.256 53.664 102.56 51.712z" fill="#A86749" p-id="3542"></path><path d="M359.872 725.632a79.136 79.136 0 0 1 51.04 16.448 36.032 36.032 0 0 1 10.24 49.056 35.2 35.2 0 0 1-10.24 10.496 88.608 88.608 0 0 1-102.4 0 36.096 36.096 0 0 1-10.528-49.12 34.752 34.752 0 0 1 10.752-10.944 78.784 78.784 0 0 1 51.136-15.936z" fill="#3783FF" p-id="3543"></path><path d="M350.88 685.792a61.568 61.568 0 0 1-37.504-11.872 28.8 28.8 0 0 1-9.6-38.848 27.872 27.872 0 0 1 9.28-9.6 67.616 67.616 0 0 1 78.656 0.8 28.352 28.352 0 0 1 7.52 38.784 27.648 27.648 0 0 1-7.2 7.488 62.496 62.496 0 0 1-41.152 13.248z" fill="#4DE94C" p-id="3544"></path><path d="M451.488 591.68a46.88 46.88 0 0 1-34.304-12.32 23.072 23.072 0 0 1-2.784-32 26.048 26.048 0 0 1 2.784-2.848 52.992 52.992 0 0 1 67.968 0 23.072 23.072 0 0 1 2.784 32 21.696 21.696 0 0 1-4.096 3.936 47.552 47.552 0 0 1-32.352 11.232z" fill="#FFEE00" p-id="3545"></path><path d="M507.456 813.184a89.6 89.6 0 0 1 57.376 18.176 39.488 39.488 0 0 1 11.936 53.664 38.4 38.4 0 0 1-11.936 12.224 101.024 101.024 0 0 1-115.2 0 39.584 39.584 0 0 1-12.48-53.696 38.56 38.56 0 0 1 12.8-12.8 89.6 89.6 0 0 1 57.504-17.568z" fill="#4C1AC6" p-id="3546"></path><path d="M992 430.912a185.152 185.152 0 0 1-36.832 61.088 1493.056 1493.056 0 0 1-119.968 130.688 16.416 16.416 0 0 1-17.184 6.048 21.92 21.92 0 0 1-8.032-4.896q-13.728-13.088-27.008-26.656a15.2 15.2 0 0 1-2.432-20.896 17.824 17.824 0 0 1 1.664-1.824 21.248 21.248 0 0 1 1.632-1.76 1551.104 1551.104 0 0 1 131.456-118.4 272 272 0 0 1 45.472-30.272 81.824 81.824 0 0 1 16.224-6.048 11.296 11.296 0 0 1 14.912 6.208v0.224z" fill="#2A3E4F" p-id="3547"></path><path d="M640 734.816a2.56 2.56 0 0 1 0.96 0.192A72.288 72.288 0 0 0 736 720a49.248 49.248 0 0 0 13.184-38.72 37.536 37.536 0 0 0-32-32.192 32.8 32.8 0 0 0-29.536 13.056 49.504 49.504 0 0 0-9.088 21.024A82.368 82.368 0 0 1 640 734.016z" fill="#7E8C8D" p-id="3548"></path><path d="M805.12 633.344c-10.432-10.464-20.992-20.832-31.52-31.232a7.552 7.552 0 0 0-10.752-1.536 7.648 7.648 0 0 0-1.984 2.304q-13.408 17.408-26.592 34.944a7.008 7.008 0 0 0 0.256 9.6l0.352 0.32q11.808 11.712 23.648 23.296a7.104 7.104 0 0 0 10.144 0.8c12-8.864 24.032-17.728 35.84-26.848a25.6 25.6 0 0 0 4.352-6.4 37.28 37.28 0 0 0-3.744-5.248z" fill="#F89B36" p-id="3549"></path><path d="M571.648 547.872a36.64 36.64 0 0 1-27.488-10.272 19.744 19.744 0 0 1-1.536-27.392l1.536-1.6a41.184 41.184 0 0 1 54.4 0 19.744 19.744 0 0 1 1.536 27.424 18.656 18.656 0 0 1-2.56 2.464 37.28 37.28 0 0 1-25.888 9.376z" fill="#FF8C00" p-id="3550"></path><path d="M640 734.816s125.28-36.352 85.088-83.648c0 0-27.424-12.576-46.528 32.224A125.664 125.664 0 0 1 640 734.816z" fill="#8F9FA0" p-id="3551"></path><path d="M745.76 658.72l43.968-40.704-16-16a7.648 7.648 0 0 0-10.976-1.248 7.072 7.072 0 0 0-1.76 2.016c-6.4 8.288-36.896 34.24-26.016 45.024z" fill="#E9BB42" p-id="3552"></path><path d="M796.064 610.336L992 424.128s1.312-12.576-14.976-6.4-25.6-5.664-194.976 156.64c0 0-12.48 9.6 0.768 22.72z" fill="#32495D" p-id="3553"></path><path d="M695.072 557.12a36.832 36.832 0 0 1-27.488-10.272 19.744 19.744 0 0 1-1.536-27.424 17.76 17.76 0 0 1 1.536-1.568 41.184 41.184 0 0 1 54.4 0 19.744 19.744 0 0 1 1.536 27.36 17.568 17.568 0 0 1-2.624 2.496 36.992 36.992 0 0 1-25.824 9.408z" fill="#F60000" p-id="3554"></path></svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752725844944" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1883" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M482.417039 241.103682c147.531806-60.119921 20.031974-189.029751 53.16093-237.388687-53.885929 17.175977-175.392769 85.601887-101.698866 221.221708 16.201979 5.416993 32.333957 10.757986 48.537936 16.166979zM518.145992 23.08997c-62.215918 50.670933-88.707883 114.913849-62.904917 199.028737a2169.68714 2169.68714 0 0 1-12.266984-4.109994c-43.811942-104.185863 1.474998-157.632792 75.171901-194.918743z m-37.85395 229.273697l-50.526934-16.742978-85.998886 181.95776 97.148872 32.265958 39.376948-197.48074zM363.401196 408.338462l70.368907-159.86579 16.127979 5.373993-55.395927 164.891783-31.100959-10.399986z m-62.903917 110.255854c19.627974 8.618989 45.977939 13.895982 74.914901 13.895982 16.712978 0 32.517957-1.736998 46.699939-4.907994 5.919992-21.652971 11.369985-43.529943 16.421978-65.683913a59379.039717 59379.039717 0 0 1-102.023866-33.849955 1378.798182 1378.798182 0 0 0-36.012952 90.54588zM994.728364 554.499269c-60.69592-163.121785-268.239646-266.549649-494.085349-259.442658l-27.569964 128.332831c21.720971 11.726985 35.945953 27.968963 38.25395 46.626938 5.229993 41.535945-50.452933 79.538895-124.254836 84.847888-73.799903 5.338993-137.858818-24.034968-143.091812-65.613913-4.043995-32.408957 29.016962-62.719917 78.926896-76.937899l41.718945-99.461868c-61.318919 14.291981-116.713846 36.160952-164.707783 63.658916C63.357591 454.8254 68.300585 495.999346 197.392415 580.774234c47.779937 31.396959 96.355873 62.535918 107.646858 85.167888 15.48598 30.888959-77.152898 37.89295-163.841784 41.647945-117.940845 4.691994-169.400777 14.86598-81.378893 132.766825 107.580858 144.10681 344.686546 217.909713 576.44624 168.142778 269.685644-57.886924 430.245433-261.217656 358.463528-454.000401zM250.153345 863.314862c-46.124939 0-83.51189-19.603974-83.51189-43.778942s37.386951-43.742942 83.51189-43.742943c46.120939 0 83.57889 19.566974 83.57889 43.742943s-37.457951 43.778942-83.57889 43.778942z m208.914725 69.612908c-46.194939 0-83.58189-19.562974-83.58189-43.738942 0-24.182968 37.386951-43.738942 83.58189-43.738943 46.123939 0 83.54289 19.556974 83.54289 43.738943 0 24.174968-37.418951 43.738942-83.54289 43.738942z m232.807693-45.32594c-46.155939 0-83.58189-19.595974-83.58189-43.738943 0-24.182968 37.425951-43.778942 83.58189-43.778942 46.120939 0 83.54289 19.596974 83.54289 43.778942 0 24.142968-37.421951 43.738942-83.54289 43.738943z m141.175814-112.306852c-46.116939 0-83.57789-19.632974-83.57789-43.775942 0-24.178968 37.460951-43.778942 83.57789-43.778943 46.160939 0 83.54789 19.600974 83.54789 43.778943 0 24.142968-37.390951 43.775942-83.54789 43.775942z m17.400977-138.799817c-46.124939 0-83.54989-19.559974-83.54989-43.735942 0-24.183968 37.424951-43.745942 83.54989-43.745943 46.116939 0 83.53989 19.562974 83.53989 43.745943 0 24.176968-37.422951 43.735942-83.53989 43.735942z" fill="" p-id="1884"></path></svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -24,9 +24,10 @@ export class FillGroupLayerBackgroundCommand extends Command {
this.oldFillColor = null;
this.newFill = null;
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layer = layer;
this.parent = parent;
this.group = null;
@@ -65,7 +66,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
scaleY: clippingMaskFabricObject.scaleY || 1,
// type: "fill",
});
this.newFill.clipPath = clippingMaskFabricObject;
// this.newFill.clipPath = clippingMaskFabricObject;
// this.newFill.dirty = true;
// this.newFill.setCoords();
} else {
@@ -102,29 +103,56 @@ export class FillGroupLayerBackgroundCommand extends Command {
// canvasObj.addWithUpdate();
canvasObj.setCoords();
canvasObj.setObjectsCoords();
// canvasObj.dirty = true; // 标记为脏对象
canvasObj.dirty = true; // 标记为脏对象
// this.canvas.renderAll();
// this.group = canvasObj;
} else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
// 普通对象,组成新组
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
const layerObjects =
this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId) || [];
// layerObjects?.forEach((obj) => {
// obj.clipPath = null;
// obj.dirty = true;
// obj.setCoords();
// });
let insertIndex = this.canvas.getObjects()?.findIndex((obj) => obj.id === firstObj?.id) ?? 0;
insertIndex = insertIndex === -1 ? 0 : insertIndex;
layerObjects.forEach((obj) => {
obj.clipPath = null;
});
this.group = new fabric.Group([this.newFill, ...layerObjects]);
this.group.set({
id: layerObjects[0]?.id || generateId("group-"),
layerId: layerObjects[0]?.layerId,
layerId: this.layer?.id,
});
this.group.setCoords();
removeCanvasObjectByObject(this.canvas, layerObjects[0]);
insertObjectAtZIndex(this.canvas, this.group, insertIndex, false);
// this.group.setCoords();
// this.group.setObjectsCoords();
// this.group.dirty = true; // 标记为脏对象
if (this.parent?.clippingMask) {
const clipPath = await restoreFabricObject(this.parent?.clippingMask, this.canvas);
clipPath.clipPath = null;
clipPath.set({ absolutePositioned: true });
this.group.clipPath = clipPath;
}
layer.fabricObjects = [this.group.toObject(["id", "layerId"]) || this.group];
// removeCanvasObjectByObject(this.canvas, layerObjects?.[0]);
insertObjectAtZIndex(this.canvas, this.group, insertIndex, false, true);
}
// this.group?.addWithUpdate?.();
// layer.fabricObjects = [this.group?.toObject?.(["id", "layerId"]) || this.group];
this.canvas.renderAll();
// this.canvas.renderAll();
layer.fill = null; // this.newFill.toObject(["id", "layerId"]);
layer.fillColor = this.fillColor;
// 取消激活对象
this.canvas.discardActiveObject(); // 取消当前活动对象
// 重新排序
await this.layerManager?.sortLayersWithTool?.();
// 更新画布上对象的可选择状态
await this.layerManager?.updateLayersObjectsInteractivity?.();
this.canvas.renderAll();
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id);
return true;
}
@@ -159,19 +187,25 @@ export class FillGroupLayerBackgroundCommand extends Command {
this.group = null;
}
this.canvas.discardActiveObject(); // 取消当前活动对象
// 重新排序
await this.layerManager?.sortLayersWithTool?.();
// 更新画布上对象的可选择状态
await this.layerManager?.updateLayersObjectsInteractivity?.();
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id);
return true;
}
_collectOriginalObjects() {
if (this.layer.children && this.layer.children.length > 0) {
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) {
} 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;

View File

@@ -653,35 +653,35 @@ function buildContextMenuItems(layer) {
action: () => deleteSelectedLayers(),
},
// 组合图层
{
label: "填充图层",
icon: "CThemeColor",
disabled:
layer.isBackground ||
layer.isFixed ||
!layerManager?.canRasterizeLayer?.(layer.id) ||
layer.isGroup ||
layer?.children?.length > 0, // 如果是组图层或有子图层则禁用
action: () => {
// 调用浏览器原生颜色选择器
fillColorRef.value.click();
currLayerId.value = layer.id;
// // 监听颜色选择器的变化
// 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: "CThemeColor",
// disabled:
// layer.isBackground ||
// layer.isFixed ||
// !layerManager?.canRasterizeLayer?.(layer.id) ||
// layer.isGroup ||
// layer?.children?.length > 0, // 如果是组图层或有子图层则禁用
// action: () => {
// // 调用浏览器原生颜色选择器
// fillColorRef.value.click();
// currLayerId.value = layer.id;
// // // 监听颜色选择器的变化
// // 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: "组合图层",
@@ -1512,14 +1512,14 @@ async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex
<div class="layers-panel-inner" @click="handlePanelClick">
<div class="layers-header">
<!-- 颜色填充选择组件 -->
<input
<!-- <input
class="fillColor-input"
v-model="fillColor"
ref="fillColorRef"
type="color"
@change="fillColorChange"
style="width: 0; height: 0; opacity: 0"
/>
/> -->
<h3>
{{ $t("图层") }}
{{ selectedLayerIds.length > 0 ? `(${selectedLayerIds.length})` : "" }}

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref, inject, computed, onMounted, onUnmounted } from "vue";
import { OperationType } from "../utils/layerHelper";
import { findLayerRecursively, OperationType } from "../utils/layerHelper";
import ToolButton from "../../ExistsImageList/ToolButton.vue";
const emit = defineEmits([
@@ -29,11 +29,19 @@ const props = defineProps({
});
const commandManager = inject("commandManager");
const layerManager = inject("layerManager"); // 图层管理器
const lastSelectLayerId = inject("lastSelectLayerId"); // 上次选中的图层ID
// 撤销/重做按钮状态
const canUndo = ref(false);
const canRedo = ref(false);
// 颜色填充相关
const currLayerId = ref(null); // 当前图层ID
const fillColor = ref("#ffffff"); // 默认填充颜色
const fillColorRef = ref(null);
// 监听命令管理器状态变化
commandManager.setChangeCallback((info) => {
canUndo.value = info.canUndo;
@@ -80,6 +88,13 @@ const normalToolsList = ref([
icon: { name: "CEraser", size: "22" },
class: "eraser-btn",
},
{
id: "fillColor",
title: "Fill Color",
action: () => fillColorRef.value.click(),
icon: { name: "CThemeColor", size: "22" },
class: "fill-color-btn",
},
{
id: OperationType.PAN,
title: "Pan",
@@ -280,6 +295,11 @@ function handleKeyDown(event) {
}
}
// 填充颜色选择器
function fillColorChange() {
layerManager.fillLayerBackground(lastSelectLayerId.value, fillColor.value, true);
}
onMounted(() => {
// 添加键盘事件监听
window.addEventListener("keydown", handleKeyDown);
@@ -298,6 +318,15 @@ const handleToolClick = (tool) => {
<template>
<div class="tools-sidebar">
<input
class="fillColor-input"
v-model="fillColor"
ref="fillColorRef"
type="color"
@change="fillColorChange"
style="width: 0; height: 0; opacity: 0"
/>
<ToolButton
v-for="tool in toolsList"
:key="tool.id"

View File

@@ -223,6 +223,7 @@ onMounted(async () => {
canvasHeight: canvasHeight.value,
backgroundColor: canvasColor,
isRedGreenMode: props.enabledRedGreenMode,
lastSelectLayerId,
layers,
activeLayerId,
canvasManager, // 添加对 canvasManager 的引用

View File

@@ -17,6 +17,11 @@ export class BackgroundFillManager {
* @returns {Promise<void>}
*/
async fillLayerBackground(layerId, fillColor, undoable) {
if (!layerId || !fillColor) {
console.warn("图层ID或填充颜色不能为空");
return;
}
const command = new FillGroupLayerBackgroundCommand({
canvas: this.canvas,
layers: this.layers,

View File

@@ -89,6 +89,7 @@ export class LayerManager {
this.activeLayerId = options.activeLayerId;
this.commandManager = options.commandManager;
this.canvasManager = options.canvasManager || null;
this.lastSelectLayerId = options.lastSelectLayerId || { value: null }; // 上次选中的图层ID
this.backgroundFillManager = new BackgroundFillManager({
canvas: this.canvas,
@@ -394,6 +395,7 @@ export class LayerManager {
* @param {boolean} undoable 是否可撤销
*/
async fillLayerBackground(layerId, fillColor, undoable = true) {
layerId = this.activeLayerId.value || layerId;
await this.backgroundFillManager.fillLayerBackground(layerId, fillColor, undoable);
}
@@ -708,6 +710,7 @@ export class LayerManager {
* @param {string} layerId 图层ID
*/
setActiveLayer(layerId, options = {}) {
// this.lastSelectLayerId.value = layerId; // 更新最后选择的图层ID
if (layerId === this.activeLayerId.value) {
console.warn("当前图层已是活动图层,无需重复设置");
return;
@@ -953,13 +956,13 @@ export class LayerManager {
}
}
if (child.fill) {
// 如果图层有填充颜色,设置所有对象的填充颜色
const { object } = findObjectById(this.canvas, child.fill.id);
if (object) {
acc.push(object);
}
}
// if (child.fill) {
// // 如果图层有填充颜色,设置所有对象的填充颜色
// const { object } = findObjectById(this.canvas, child.fill.id);
// if (object) {
// acc.push(object);
// }
// }
return acc;
}, []);

View File

@@ -602,7 +602,7 @@ export function getObjectZIndex(canvas, targetObj) {
* @param {boolean} renderAll 是否立即渲染默认true
* @returns {boolean} 是否成功插入
*/
export function insertObjectAtZIndex(canvas, object, zIndex, renderAll = true) {
export function insertObjectAtZIndex(canvas, object, zIndex, renderAll = true, isReplace = false) {
if (!canvas || !object || zIndex < 0) {
return false;
}
@@ -612,7 +612,7 @@ export function insertObjectAtZIndex(canvas, object, zIndex, renderAll = true) {
const maxIndex = canvas.getObjects().length;
const safeZIndex = Math.min(zIndex, maxIndex);
canvas.insertAt(object, safeZIndex, false);
canvas.insertAt(object, safeZIndex, isReplace);
if (renderAll) {
canvas.renderAll();