feat: 更新填充组图层背景命令,增强图层管理和颜色填充功能,优化图层选择和渲染逻辑
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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})` : "" }}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -223,6 +223,7 @@ onMounted(async () => {
|
||||
canvasHeight: canvasHeight.value,
|
||||
backgroundColor: canvasColor,
|
||||
isRedGreenMode: props.enabledRedGreenMode,
|
||||
lastSelectLayerId,
|
||||
layers,
|
||||
activeLayerId,
|
||||
canvasManager, // 添加对 canvasManager 的引用
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}, []);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user