feat: 完善选区功能,新增组图层自由编辑
This commit is contained in:
@@ -0,0 +1,767 @@
|
|||||||
|
import { Command } from "./Command";
|
||||||
|
import { isGroupLayer } from "../utils/layerHelper";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨层级移动命令基类
|
||||||
|
*/
|
||||||
|
export class CrossLevelMoveCommand extends Command {
|
||||||
|
constructor(options) {
|
||||||
|
super({
|
||||||
|
name: "跨层级移动",
|
||||||
|
undoable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.layers = options.layers;
|
||||||
|
this.layerManager = options.layerManager;
|
||||||
|
this.canvas = options.canvas;
|
||||||
|
|
||||||
|
// 移动相关参数
|
||||||
|
this.layerId = options.layerId;
|
||||||
|
this.fromContainerType = options.fromContainerType; // 'root' | 'child'
|
||||||
|
this.toContainerType = options.toContainerType; // 'root' | 'child'
|
||||||
|
this.fromParentId = options.fromParentId;
|
||||||
|
this.toParentId = options.toParentId;
|
||||||
|
this.newIndex = options.newIndex;
|
||||||
|
this.oldIndex = options.oldIndex;
|
||||||
|
|
||||||
|
// 保存状态用于撤销
|
||||||
|
this.beforeState = null;
|
||||||
|
this.afterState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存移动前的状态
|
||||||
|
*/
|
||||||
|
saveBeforeState() {
|
||||||
|
this.beforeState = {
|
||||||
|
layerStructure: this.cloneLayerStructure(),
|
||||||
|
fabricObjects: this.saveFabricObjectStates(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存移动后的状态
|
||||||
|
*/
|
||||||
|
saveAfterState() {
|
||||||
|
this.afterState = {
|
||||||
|
layerStructure: this.cloneLayerStructure(),
|
||||||
|
fabricObjects: this.saveFabricObjectStates(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 克隆图层结构
|
||||||
|
*/
|
||||||
|
cloneLayerStructure() {
|
||||||
|
return JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
this.layers.value.map((layer) => ({
|
||||||
|
id: layer.id,
|
||||||
|
name: layer.name,
|
||||||
|
parentId: layer.parentId,
|
||||||
|
children: layer.children
|
||||||
|
? layer.children.map((child) => ({
|
||||||
|
id: child.id,
|
||||||
|
name: child.name,
|
||||||
|
parentId: child.parentId,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
isBackground: layer.isBackground,
|
||||||
|
isFixed: layer.isFixed,
|
||||||
|
visible: layer.visible,
|
||||||
|
locked: layer.locked,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存fabric对象状态
|
||||||
|
*/
|
||||||
|
saveFabricObjectStates() {
|
||||||
|
const states = {};
|
||||||
|
|
||||||
|
// 只保存fabric对象的容器关系,不保存具体的属性
|
||||||
|
this.layers.value.forEach((layer) => {
|
||||||
|
if (layer.fabricObject) {
|
||||||
|
states[layer.id] = {
|
||||||
|
isInCanvas: this.canvas
|
||||||
|
? this.canvas.contains(layer.fabricObject)
|
||||||
|
: false,
|
||||||
|
parentGroupId: null,
|
||||||
|
visible: layer.fabricObject.visible,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否在某个组中
|
||||||
|
this.layers.value.forEach((otherLayer) => {
|
||||||
|
if (
|
||||||
|
otherLayer.fabricObject &&
|
||||||
|
otherLayer.fabricObject.type === "group" &&
|
||||||
|
otherLayer.fabricObject.contains &&
|
||||||
|
otherLayer.fabricObject.contains(layer.fabricObject)
|
||||||
|
) {
|
||||||
|
states[layer.id].parentGroupId = otherLayer.id;
|
||||||
|
states[layer.id].isInCanvas = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return states;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找图层
|
||||||
|
*/
|
||||||
|
findLayer(layerId, containerType, parentId) {
|
||||||
|
if (containerType === "root") {
|
||||||
|
return this.layers.value.find((layer) => layer.id === layerId);
|
||||||
|
} else if (containerType === "child" && parentId) {
|
||||||
|
const parent = this.layers.value.find((layer) => layer.id === parentId);
|
||||||
|
return parent?.children?.find((child) => child.id === layerId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证移动的有效性
|
||||||
|
*/
|
||||||
|
validateMove() {
|
||||||
|
const layer = this.findLayer(
|
||||||
|
this.layerId,
|
||||||
|
this.fromContainerType,
|
||||||
|
this.fromParentId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!layer) {
|
||||||
|
throw new Error(`找不到要移动的图层: ${this.layerId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为不可移动的图层
|
||||||
|
if (layer.isBackground || layer.isFixed) {
|
||||||
|
throw new Error("背景层和固定层不能移动");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否试图将图层移动到自己的子层级中(防止循环引用)
|
||||||
|
if (
|
||||||
|
this.toContainerType === "child" &&
|
||||||
|
this.toParentId &&
|
||||||
|
this.isDescendantOf(this.toParentId, this.layerId)
|
||||||
|
) {
|
||||||
|
throw new Error("不能将图层移动到自己的子层级中");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目标父图层是否为组
|
||||||
|
if (this.toContainerType === "child") {
|
||||||
|
const targetParent = this.layers.value.find(
|
||||||
|
(layer) => layer.id === this.toParentId
|
||||||
|
);
|
||||||
|
if (!targetParent || !isGroupLayer(targetParent)) {
|
||||||
|
throw new Error("目标图层不是组图层");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为子层级关系(防止循环引用)
|
||||||
|
*/
|
||||||
|
isDescendantOf(ancestorId, layerId) {
|
||||||
|
const checkChildren = (children) => {
|
||||||
|
if (!children) return false;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.id === layerId) return true;
|
||||||
|
if (child.children && checkChildren(child.children)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ancestor = this.layers.value.find((layer) => layer.id === ancestorId);
|
||||||
|
return ancestor ? checkChildren(ancestor.children) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute() {
|
||||||
|
console.log("🎯 执行跨层级移动命令:", {
|
||||||
|
layerId: this.layerId,
|
||||||
|
from: this.fromContainerType,
|
||||||
|
to: this.toContainerType,
|
||||||
|
fromParentId: this.fromParentId,
|
||||||
|
toParentId: this.toParentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证移动
|
||||||
|
const draggedLayer = this.validateMove();
|
||||||
|
|
||||||
|
// 保存移动前状态
|
||||||
|
this.saveBeforeState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 根据移动类型执行对应操作
|
||||||
|
if (
|
||||||
|
this.fromContainerType === "root" &&
|
||||||
|
this.toContainerType === "child"
|
||||||
|
) {
|
||||||
|
this.moveRootToGroup(draggedLayer);
|
||||||
|
} else if (
|
||||||
|
this.fromContainerType === "child" &&
|
||||||
|
this.toContainerType === "root"
|
||||||
|
) {
|
||||||
|
this.moveGroupToRoot(draggedLayer);
|
||||||
|
} else if (
|
||||||
|
this.fromContainerType === "child" &&
|
||||||
|
this.toContainerType === "child" &&
|
||||||
|
this.fromParentId !== this.toParentId
|
||||||
|
) {
|
||||||
|
this.moveGroupToGroup(draggedLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存移动后状态
|
||||||
|
this.saveAfterState();
|
||||||
|
|
||||||
|
// 刷新画布
|
||||||
|
if (this.canvas) {
|
||||||
|
this.canvas.renderAll();
|
||||||
|
console.log("🎨 画布已刷新");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 跨层级移动命令执行成功");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 跨层级移动命令执行失败:", error);
|
||||||
|
|
||||||
|
// 如果执行失败,尝试恢复到之前的状态
|
||||||
|
if (this.beforeState) {
|
||||||
|
try {
|
||||||
|
this.restoreLayerStructure(this.beforeState.layerStructure);
|
||||||
|
this.restoreFabricObjectStates(this.beforeState.fabricObjects);
|
||||||
|
if (this.canvas) {
|
||||||
|
this.canvas.renderAll();
|
||||||
|
}
|
||||||
|
console.log("🔄 已恢复到移动前状态");
|
||||||
|
} catch (restoreError) {
|
||||||
|
console.error("❌ 恢复状态失败:", restoreError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
if (!this.beforeState) {
|
||||||
|
throw new Error("没有保存的前置状态,无法撤销");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🔄 撤销跨层级移动命令");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 恢复图层结构
|
||||||
|
this.restoreLayerStructure(this.beforeState.layerStructure);
|
||||||
|
|
||||||
|
// 恢复fabric对象状态
|
||||||
|
this.restoreFabricObjectStates(this.beforeState.fabricObjects);
|
||||||
|
|
||||||
|
// 刷新画布
|
||||||
|
if (this.canvas) {
|
||||||
|
// 使用 requestAnimationFrame 确保DOM更新完成后再渲染
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.canvas.renderAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 跨层级移动命令撤销成功");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 跨层级移动命令撤销失败:", error);
|
||||||
|
|
||||||
|
// 尝试基本的恢复操作
|
||||||
|
try {
|
||||||
|
console.log("🔄 尝试基本恢复操作");
|
||||||
|
this.basicRestore();
|
||||||
|
} catch (restoreError) {
|
||||||
|
console.error("❌ 基本恢复也失败:", restoreError);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基本恢复操作(当标准撤销失败时使用)
|
||||||
|
*/
|
||||||
|
basicRestore() {
|
||||||
|
// 重新组织所有图层的fabric对象
|
||||||
|
this.layers.value.forEach((layer) => {
|
||||||
|
if (layer.fabricObject) {
|
||||||
|
try {
|
||||||
|
// 先从所有容器中移除
|
||||||
|
this.removeFromAllContainers(layer.fabricObject);
|
||||||
|
|
||||||
|
// 根据图层的parentId重新放置
|
||||||
|
if (layer.parentId) {
|
||||||
|
const parentLayer = this.layers.value.find(
|
||||||
|
(l) => l.id === layer.parentId
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
parentLayer &&
|
||||||
|
parentLayer.fabricObject &&
|
||||||
|
parentLayer.fabricObject.type === "group"
|
||||||
|
) {
|
||||||
|
this.safeAddToContainer(
|
||||||
|
layer.fabricObject,
|
||||||
|
parentLayer.fabricObject,
|
||||||
|
`父组${layer.parentId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.safeAddToContainer(layer.fabricObject, this.canvas, "画布");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 基本恢复图层 ${layer.id} 失败:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.canvas) {
|
||||||
|
this.canvas.renderAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重做命令
|
||||||
|
*/
|
||||||
|
redo() {
|
||||||
|
if (!this.afterState) {
|
||||||
|
throw new Error("没有保存的后置状态,无法重做");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🔄 重做跨层级移动命令");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 恢复图层结构
|
||||||
|
this.restoreLayerStructure(this.afterState.layerStructure);
|
||||||
|
|
||||||
|
// 恢复fabric对象状态
|
||||||
|
this.restoreFabricObjectStates(this.afterState.fabricObjects);
|
||||||
|
|
||||||
|
// 刷新画布
|
||||||
|
if (this.canvas) {
|
||||||
|
// 使用 requestAnimationFrame 确保DOM更新完成后再渲染
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.canvas.renderAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 跨层级移动命令重做成功");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 跨层级移动命令重做失败:", error);
|
||||||
|
|
||||||
|
// 尝试基本的恢复操作
|
||||||
|
try {
|
||||||
|
console.log("🔄 尝试基本恢复操作");
|
||||||
|
this.basicRestore();
|
||||||
|
} catch (restoreError) {
|
||||||
|
console.error("❌ 基本恢复也失败:", restoreError);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复图层结构
|
||||||
|
*/
|
||||||
|
restoreLayerStructure(layerStructure) {
|
||||||
|
// 保存原始图层数组的引用
|
||||||
|
const originalLayers = [...this.layers.value];
|
||||||
|
|
||||||
|
// 清空当前图层
|
||||||
|
this.layers.value.splice(0);
|
||||||
|
|
||||||
|
// 重建图层结构
|
||||||
|
layerStructure.forEach((layerData) => {
|
||||||
|
// 首先从原始图层数组中查找图层
|
||||||
|
let layer = originalLayers.find((l) => l.id === layerData.id);
|
||||||
|
|
||||||
|
// 如果没找到,尝试在嵌套结构中查找
|
||||||
|
if (!layer) {
|
||||||
|
layer = this.findLayerInOriginalStructure(layerData.id, originalLayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer) {
|
||||||
|
// 更新图层属性
|
||||||
|
layer.parentId = layerData.parentId;
|
||||||
|
layer.visible = layerData.visible;
|
||||||
|
layer.locked = layerData.locked;
|
||||||
|
|
||||||
|
// 重建children数组
|
||||||
|
if (layerData.children && layerData.children.length > 0) {
|
||||||
|
layer.children = layerData.children
|
||||||
|
.map((childData) => {
|
||||||
|
// 从原始图层数组中查找子图层
|
||||||
|
let childLayer = originalLayers.find(
|
||||||
|
(l) => l.id === childData.id
|
||||||
|
);
|
||||||
|
if (!childLayer) {
|
||||||
|
childLayer = this.findLayerInOriginalStructure(
|
||||||
|
childData.id,
|
||||||
|
originalLayers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (childLayer) {
|
||||||
|
// 更新子图层属性
|
||||||
|
childLayer.parentId = childData.parentId;
|
||||||
|
childLayer.visible = childData.visible;
|
||||||
|
childLayer.locked = childData.locked;
|
||||||
|
return childLayer;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
} else {
|
||||||
|
layer.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layers.value.push(layer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在原始结构中查找图层
|
||||||
|
*/
|
||||||
|
findLayerInOriginalStructure(layerId, layers = null) {
|
||||||
|
const layersToSearch = layers || this.layers.value;
|
||||||
|
|
||||||
|
const findInLayers = (layersList) => {
|
||||||
|
for (const layer of layersList) {
|
||||||
|
if (layer.id === layerId) return layer;
|
||||||
|
if (layer.children) {
|
||||||
|
const found = findInLayers(layer.children);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return findInLayers(layersToSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复fabric对象状态
|
||||||
|
*/
|
||||||
|
restoreFabricObjectStates(fabricStates) {
|
||||||
|
if (!this.canvas || !fabricStates) return;
|
||||||
|
|
||||||
|
console.log("🔧 开始恢复fabric对象状态");
|
||||||
|
|
||||||
|
// 重新组织fabric对象的层级关系
|
||||||
|
this.layers.value.forEach((layer) => {
|
||||||
|
if (layer.fabricObject && fabricStates[layer.id]) {
|
||||||
|
const targetState = fabricStates[layer.id];
|
||||||
|
console.log(`🔍 恢复图层 ${layer.id} 的fabric对象状态:`, targetState);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 首先从所有容器中移除对象
|
||||||
|
this.removeFromAllContainers(layer.fabricObject);
|
||||||
|
|
||||||
|
// 根据目标状态放置对象
|
||||||
|
if (targetState.parentGroupId) {
|
||||||
|
// 应该在特定组中
|
||||||
|
const parentLayer = this.layers.value.find(
|
||||||
|
(l) => l.id === targetState.parentGroupId
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
parentLayer &&
|
||||||
|
parentLayer.fabricObject &&
|
||||||
|
parentLayer.fabricObject.type === "group"
|
||||||
|
) {
|
||||||
|
this.safeAddToContainer(
|
||||||
|
layer.fabricObject,
|
||||||
|
parentLayer.fabricObject,
|
||||||
|
`父组${targetState.parentGroupId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (targetState.isInCanvas) {
|
||||||
|
// 应该在画布中
|
||||||
|
this.safeAddToContainer(layer.fabricObject, this.canvas, "画布");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复可见性
|
||||||
|
if (layer.fabricObject.visible !== targetState.visible) {
|
||||||
|
layer.fabricObject.visible = targetState.visible;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 恢复图层 ${layer.id} 的fabric对象时出错:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 强制重新渲染画布
|
||||||
|
if (this.canvas) {
|
||||||
|
this.canvas.renderAll();
|
||||||
|
console.log("✅ 画布已重新渲染");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地处理fabric对象操作
|
||||||
|
*/
|
||||||
|
safeFabricOperation(operation, operationName = "fabric操作") {
|
||||||
|
try {
|
||||||
|
return operation();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${operationName}失败:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地添加fabric对象到容器
|
||||||
|
*/
|
||||||
|
safeAddToContainer(fabricObject, container, containerName) {
|
||||||
|
return this.safeFabricOperation(() => {
|
||||||
|
if (container && container.add && !container.contains(fabricObject)) {
|
||||||
|
container.add(fabricObject);
|
||||||
|
console.log(`✅ 成功将对象添加到${containerName}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, `添加对象到${containerName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地从容器移除fabric对象
|
||||||
|
*/
|
||||||
|
safeRemoveFromContainer(fabricObject, container, containerName) {
|
||||||
|
return this.safeFabricOperation(() => {
|
||||||
|
if (
|
||||||
|
container &&
|
||||||
|
container.remove &&
|
||||||
|
container.contains &&
|
||||||
|
container.contains(fabricObject)
|
||||||
|
) {
|
||||||
|
container.remove(fabricObject);
|
||||||
|
console.log(`✅ 成功从${containerName}移除对象`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, `从${containerName}移除对象`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从所有容器中移除fabric对象
|
||||||
|
*/
|
||||||
|
removeFromAllContainers(fabricObject) {
|
||||||
|
// 从画布中移除
|
||||||
|
this.safeRemoveFromContainer(fabricObject, this.canvas, "画布");
|
||||||
|
|
||||||
|
// 从所有组中移除
|
||||||
|
this.layers.value.forEach((layer) => {
|
||||||
|
if (layer.fabricObject && layer.fabricObject.type === "group") {
|
||||||
|
this.safeRemoveFromContainer(
|
||||||
|
fabricObject,
|
||||||
|
layer.fabricObject,
|
||||||
|
`组${layer.id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从顶级图层移动到组图层内
|
||||||
|
*/
|
||||||
|
moveRootToGroup(draggedLayer) {
|
||||||
|
console.log("📥 执行顶级图层移动到组内:", {
|
||||||
|
layerId: draggedLayer.id,
|
||||||
|
toParentId: this.toParentId,
|
||||||
|
newIndex: this.newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetParent = this.layers.value.find(
|
||||||
|
(layer) => layer.id === this.toParentId
|
||||||
|
);
|
||||||
|
if (!targetParent) {
|
||||||
|
throw new Error(`找不到目标父图层: ${this.toParentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保父图层有children数组
|
||||||
|
if (!targetParent.children) {
|
||||||
|
targetParent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从顶级图层数组中移除
|
||||||
|
const rootIndex = this.layers.value.findIndex(
|
||||||
|
(layer) => layer.id === draggedLayer.id
|
||||||
|
);
|
||||||
|
if (rootIndex !== -1) {
|
||||||
|
this.layers.value.splice(rootIndex, 1);
|
||||||
|
console.log(`🗑️ 从顶级图层数组中移除图层 ${draggedLayer.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图层关系
|
||||||
|
draggedLayer.parentId = this.toParentId;
|
||||||
|
targetParent.children.splice(this.newIndex, 0, draggedLayer);
|
||||||
|
console.log(
|
||||||
|
`📂 将图层 ${draggedLayer.id} 添加到父图层 ${this.toParentId} 的children中`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理fabric对象的层级关系
|
||||||
|
if (draggedLayer.fabricObject && targetParent.fabricObject) {
|
||||||
|
console.log(`🎨 处理fabric对象层级关系`);
|
||||||
|
|
||||||
|
// 从画布中移除
|
||||||
|
this.safeRemoveFromContainer(
|
||||||
|
draggedLayer.fabricObject,
|
||||||
|
this.canvas,
|
||||||
|
"画布"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加到父组
|
||||||
|
this.safeAddToContainer(
|
||||||
|
draggedLayer.fabricObject,
|
||||||
|
targetParent.fabricObject,
|
||||||
|
`父组${this.toParentId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 顶级图层移动到组内完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从组图层内移动到顶级图层
|
||||||
|
*/
|
||||||
|
moveGroupToRoot(draggedLayer) {
|
||||||
|
console.log("📤 执行组内图层移动到顶级:", {
|
||||||
|
layerId: draggedLayer.id,
|
||||||
|
fromParentId: this.fromParentId,
|
||||||
|
newIndex: this.newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceParent = this.layers.value.find(
|
||||||
|
(layer) => layer.id === this.fromParentId
|
||||||
|
);
|
||||||
|
if (!sourceParent || !sourceParent.children) {
|
||||||
|
throw new Error(`找不到源父图层或其children数组: ${this.fromParentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源父图层的children中移除
|
||||||
|
const childIndex = sourceParent.children.findIndex(
|
||||||
|
(child) => child.id === draggedLayer.id
|
||||||
|
);
|
||||||
|
if (childIndex !== -1) {
|
||||||
|
sourceParent.children.splice(childIndex, 1);
|
||||||
|
console.log(
|
||||||
|
`🗑️ 从父图层 ${this.fromParentId} 的children中移除图层 ${draggedLayer.id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理fabric对象的层级关系
|
||||||
|
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
|
||||||
|
console.log(`🎨 处理fabric对象层级关系`);
|
||||||
|
|
||||||
|
// 从父组中移除
|
||||||
|
this.safeRemoveFromContainer(
|
||||||
|
draggedLayer.fabricObject,
|
||||||
|
sourceParent.fabricObject,
|
||||||
|
`父组${this.fromParentId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加到画布
|
||||||
|
this.safeAddToContainer(draggedLayer.fabricObject, this.canvas, "画布");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除图层的parentId
|
||||||
|
delete draggedLayer.parentId;
|
||||||
|
console.log(`🔗 清除图层 ${draggedLayer.id} 的parentId`);
|
||||||
|
|
||||||
|
// 计算在顶级图层中的插入位置
|
||||||
|
const targetIndex = Math.min(this.newIndex, this.layers.value.length);
|
||||||
|
this.layers.value.splice(targetIndex, 0, draggedLayer);
|
||||||
|
console.log(
|
||||||
|
`📂 将图层 ${draggedLayer.id} 添加到顶级图层数组位置 ${targetIndex}`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("✅ 组内图层移动到顶级完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在不同组之间移动
|
||||||
|
*/
|
||||||
|
moveGroupToGroup(draggedLayer) {
|
||||||
|
console.log("🔄 执行在不同组间移动:", {
|
||||||
|
layerId: draggedLayer.id,
|
||||||
|
fromParentId: this.fromParentId,
|
||||||
|
toParentId: this.toParentId,
|
||||||
|
newIndex: this.newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceParent = this.layers.value.find(
|
||||||
|
(layer) => layer.id === this.fromParentId
|
||||||
|
);
|
||||||
|
const targetParent = this.layers.value.find(
|
||||||
|
(layer) => layer.id === this.toParentId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!sourceParent || !targetParent) {
|
||||||
|
throw new Error("找不到源父图层或目标父图层");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源父图层中移除
|
||||||
|
const childIndex = sourceParent.children.findIndex(
|
||||||
|
(child) => child.id === draggedLayer.id
|
||||||
|
);
|
||||||
|
if (childIndex !== -1) {
|
||||||
|
sourceParent.children.splice(childIndex, 1);
|
||||||
|
console.log(
|
||||||
|
`🗑️ 从源父图层 ${this.fromParentId} 中移除图层 ${draggedLayer.id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 从源父组的fabricObject中移除
|
||||||
|
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
|
||||||
|
this.safeRemoveFromContainer(
|
||||||
|
draggedLayer.fabricObject,
|
||||||
|
sourceParent.fabricObject,
|
||||||
|
`源父组${this.fromParentId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图层的parentId
|
||||||
|
draggedLayer.parentId = this.toParentId;
|
||||||
|
console.log(
|
||||||
|
`🔗 更新图层 ${draggedLayer.id} 的parentId为 ${this.toParentId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 确保目标父图层有children数组
|
||||||
|
if (!targetParent.children) {
|
||||||
|
targetParent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将图层添加到目标组
|
||||||
|
targetParent.children.splice(this.newIndex, 0, draggedLayer);
|
||||||
|
console.log(
|
||||||
|
`📂 将图层 ${draggedLayer.id} 添加到目标父图层 ${this.toParentId} 的位置 ${this.newIndex}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 将图层的fabricObject添加到目标父组中
|
||||||
|
if (draggedLayer.fabricObject && targetParent.fabricObject) {
|
||||||
|
this.safeAddToContainer(
|
||||||
|
draggedLayer.fabricObject,
|
||||||
|
targetParent.fabricObject,
|
||||||
|
`目标父组${this.toParentId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 不同组间移动完成");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建跨层级移动命令的工厂函数
|
||||||
|
*/
|
||||||
|
export function createCrossLevelMoveCommand(options) {
|
||||||
|
return new CrossLevelMoveCommand(options);
|
||||||
|
}
|
||||||
@@ -46,35 +46,6 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
this.parentLayer = parent;
|
this.parentLayer = parent;
|
||||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
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) {
|
if (!this.layer) {
|
||||||
throw new Error(`图层 ${this.layerId} 不存在`);
|
throw new Error(`图层 ${this.layerId} 不存在`);
|
||||||
}
|
}
|
||||||
@@ -84,24 +55,42 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
throw new Error("背景图层和固定图层不能栅格化");
|
throw new Error("背景图层和固定图层不能栅格化");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成新图层ID
|
||||||
|
this.rasterizedLayerId = generateId("rasterized_layer_");
|
||||||
|
this.resterizedId = generateId("rasterized_");
|
||||||
|
this.rasterizedLayer = null;
|
||||||
|
|
||||||
|
// 要栅格化的图层和对象
|
||||||
|
this.layersToRasterize = [];
|
||||||
|
this.objectsToRasterize = [];
|
||||||
|
|
||||||
|
// 在构造函数中收集相关对象和保存状态
|
||||||
|
this._initializeCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
if (!this.layer) {
|
||||||
|
throw new Error(`图层 ${this.layerId} 不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.objectsToRasterize.length === 0) {
|
||||||
|
throw new Error("图层没有内容可栅格化");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 收集要栅格化的图层和对象
|
|
||||||
this._collectLayersAndObjects();
|
|
||||||
|
|
||||||
if (this.objectsToRasterize.length === 0) {
|
|
||||||
throw new Error("图层没有内容可栅格化");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存原始对象状态
|
|
||||||
this._saveOriginalObjectStates();
|
|
||||||
|
|
||||||
this.canvas.discardActiveObject();
|
this.canvas.discardActiveObject();
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
|
|
||||||
|
// 检查是否有遮罩对象
|
||||||
|
const maskObject = this._getMaskObject();
|
||||||
|
|
||||||
// 创建栅格化图像
|
// 创建栅格化图像
|
||||||
const rasterizedImage = await createRasterizedImage({
|
const rasterizedImage = await createRasterizedImage({
|
||||||
canvas: this.canvas,
|
canvas: this.canvas,
|
||||||
fabricObjects: this.objectsToRasterize,
|
fabricObjects: this.objectsToRasterize,
|
||||||
|
maskObject: maskObject,
|
||||||
|
preserveOriginalQuality: true, // 保持原始质量
|
||||||
|
isGroupWithMask: this.isGroupLayer && !!maskObject, // 标记是否为带遮罩的组
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建新的栅格化图层并替换原图层
|
// 创建新的栅格化图层并替换原图层
|
||||||
@@ -123,30 +112,36 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async undo() {
|
async undo() {
|
||||||
if (!this.originalLayers || !this.originalCanvasObjects) {
|
if (!this.originalObjectStates || this.originalObjectStates.size === 0) {
|
||||||
throw new Error("没有可恢复的原始数据");
|
throw new Error("没有可恢复的原始数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await optimizeCanvasRendering(this.canvas, async () => {
|
await optimizeCanvasRendering(this.canvas, async () => {
|
||||||
// 清空画布
|
// 移除栅格化后的图像对象
|
||||||
this.canvas.discardActiveObject();
|
if (this.rasterizedImage) {
|
||||||
this.canvas.clear();
|
removeCanvasObjectByObject(this.canvas, this.rasterizedImage);
|
||||||
|
}
|
||||||
|
|
||||||
// 恢复原始对象及其状态
|
// 恢复原始对象
|
||||||
this.originalCanvasObjects.forEach((obj) => {
|
this.originalObjectStates.forEach((originalState, objectId) => {
|
||||||
// 如果保存了该对象的原始状态,则恢复状态
|
const { object } = findObjectById(this.canvas, objectId);
|
||||||
if (this.originalObjectStates.has(obj.id)) {
|
if (object) {
|
||||||
const originalState = this.originalObjectStates.get(obj.id);
|
// 恢复对象状态
|
||||||
obj.set(originalState);
|
object.set(originalState);
|
||||||
|
object.setCoords();
|
||||||
|
} else {
|
||||||
|
// 如果对象不在画布上,重新添加
|
||||||
|
const originalObject = originalState._fabricObject;
|
||||||
|
if (originalObject) {
|
||||||
|
this.canvas.add(originalObject);
|
||||||
|
originalObject.setCoords();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvas.add(obj);
|
|
||||||
obj.setCoords();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 恢复原始图层结构
|
// 恢复原始图层结构
|
||||||
this.layers.value = [...this.originalLayers];
|
this.layers.value = [...this.originalLayerStructure];
|
||||||
|
|
||||||
// 恢复原活动图层
|
// 恢复原活动图层
|
||||||
this.activeLayerId.value = this.layerId;
|
this.activeLayerId.value = this.layerId;
|
||||||
@@ -163,6 +158,67 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化命令,收集相关图层和对象,保存原始状态
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initializeCommand() {
|
||||||
|
// 收集要栅格化的图层和对象
|
||||||
|
this._collectLayersAndObjects();
|
||||||
|
|
||||||
|
// 保存原始图层结构(深拷贝相关部分)
|
||||||
|
this._saveOriginalLayerStructure();
|
||||||
|
|
||||||
|
// 保存原始对象状态
|
||||||
|
this._saveOriginalObjectStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存原始图层结构
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_saveOriginalLayerStructure() {
|
||||||
|
// 只保存相关的图层结构,而不是整个图层数组
|
||||||
|
this.originalLayerStructure = JSON.parse(JSON.stringify(this.layers.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存原始对象状态
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_saveOriginalObjectStates() {
|
||||||
|
this.originalObjectStates = new Map();
|
||||||
|
|
||||||
|
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,
|
||||||
|
// 保存对象引用以便撤销时重新添加
|
||||||
|
_fabricObject: object,
|
||||||
|
};
|
||||||
|
this.originalObjectStates.set(object.id, originalState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`💾 保存了 ${this.originalObjectStates.size} 个对象的原始状态`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收集要栅格化的图层和对象
|
* 收集要栅格化的图层和对象
|
||||||
* @private
|
* @private
|
||||||
@@ -239,42 +295,15 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
return result;
|
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 栅格化后的图像
|
* @param {fabric.Image} rasterizedImage 栅格化后的图像
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _createRasterizedLayer(rasterizedImage) {
|
async _createRasterizedLayer(rasterizedImage) {
|
||||||
|
// 保存栅格化图像的引用用于撤销
|
||||||
|
this.rasterizedImage = rasterizedImage;
|
||||||
|
|
||||||
// 从画布中移除原有对象
|
// 从画布中移除原有对象
|
||||||
this.objectsToRasterize.forEach((obj) => {
|
this.objectsToRasterize.forEach((obj) => {
|
||||||
removeCanvasObjectByObject(this.canvas, obj);
|
removeCanvasObjectByObject(this.canvas, obj);
|
||||||
@@ -304,6 +333,21 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 在适当位置添加新的栅格化图层
|
// 在适当位置添加新的栅格化图层
|
||||||
|
this._replaceLayerInStructure();
|
||||||
|
|
||||||
|
// 设置为活动图层
|
||||||
|
this.activeLayerId.value = this.rasterizedLayerId;
|
||||||
|
|
||||||
|
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||||
|
|
||||||
|
console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在图层结构中替换原图层
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_replaceLayerInStructure() {
|
||||||
// 1.当前如果是子图层,则插入到子图层的位置
|
// 1.当前如果是子图层,则插入到子图层的位置
|
||||||
const { layer, parent } = findLayerRecursively(
|
const { layer, parent } = findLayerRecursively(
|
||||||
this.layers.value,
|
this.layers.value,
|
||||||
@@ -334,35 +378,37 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
1,
|
1,
|
||||||
this.rasterizedLayer
|
this.rasterizedLayer
|
||||||
);
|
);
|
||||||
} else this.layers.value.splice(insertIndex, 1, this.rasterizedLayer);
|
} else {
|
||||||
|
this.layers.value.splice(insertIndex, 1, this.rasterizedLayer);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 2.如果没有找到父图层,则添加到顶层
|
// 2.如果没有找到父图层,则添加到顶层
|
||||||
this.layers.value.unshift(this.rasterizedLayer);
|
this.layers.value.unshift(this.rasterizedLayer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // 替换图层结构
|
/**
|
||||||
// if (this.isGroupLayer) {
|
* 获取遮罩对象
|
||||||
// // 组图层:移除所有相关图层
|
* @returns {Object|null} 遮罩对象或null
|
||||||
// const layerIdsToRemove = this.layersToRasterize.map((layer) => layer.id);
|
* @private
|
||||||
// this.layers.value = this.layers.value.filter(
|
*/
|
||||||
// (layer) => !layerIdsToRemove.includes(layer.id)
|
_getMaskObject() {
|
||||||
// );
|
// 如果图层有clippingMask,获取对应的fabric对象
|
||||||
// } else {
|
if (this.layer?.clippingMask?.id) {
|
||||||
// // 普通图层:移除原图层
|
const { object: maskObject } = findObjectById(
|
||||||
// const layerIndex = this.layers.value.findIndex(
|
this.canvas,
|
||||||
// (l) => l.id === this.layerId
|
this.layer.clippingMask.id
|
||||||
// );
|
);
|
||||||
// if (layerIndex !== -1) {
|
if (maskObject) {
|
||||||
// this.layers.value.splice(layerIndex, 1);
|
console.log(
|
||||||
// }
|
`📎 找到遮罩对象: ${maskObject.id}, 类型: ${maskObject.type}`
|
||||||
// }
|
);
|
||||||
|
return maskObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置为活动图层
|
console.log("📎 未找到遮罩对象");
|
||||||
this.activeLayerId.value = this.rasterizedLayerId;
|
return null;
|
||||||
|
|
||||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
|
||||||
|
|
||||||
console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo() {
|
getInfo() {
|
||||||
@@ -387,11 +433,11 @@ export class ExportLayerToImageCommand extends Command {
|
|||||||
constructor(options) {
|
constructor(options) {
|
||||||
super({
|
super({
|
||||||
name: "导出图层",
|
name: "导出图层",
|
||||||
saveState: true,
|
saveState: false, // 导出不需要保存状态
|
||||||
});
|
});
|
||||||
this.canvas = options.canvas;
|
this.canvas = options.canvas;
|
||||||
this.layers = options.layers;
|
this.layers = options.layers;
|
||||||
this.layerId = options.layerId; // 指定要栅格化的图层ID
|
this.layerId = options.layerId; // 指定要导出的图层ID
|
||||||
// 是否包含锁定对象
|
// 是否包含锁定对象
|
||||||
this.hasLocked = options.hasLocked || true;
|
this.hasLocked = options.hasLocked || true;
|
||||||
// 是否包含隐藏对象
|
// 是否包含隐藏对象
|
||||||
@@ -409,48 +455,29 @@ export class ExportLayerToImageCommand extends Command {
|
|||||||
this.parentLayer = parent;
|
this.parentLayer = parent;
|
||||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
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) {
|
if (!this.layer) {
|
||||||
throw new Error(`图层 ${this.layerId} 不存在`);
|
throw new Error(`图层 ${this.layerId} 不存在`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 要导出的图层和对象
|
||||||
|
this.layersToRasterize = [];
|
||||||
|
this.objectsToRasterize = [];
|
||||||
|
|
||||||
|
// 收集相关对象
|
||||||
|
this._collectLayersAndObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
if (!this.layer) {
|
||||||
|
throw new Error(`图层 ${this.layerId} 不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.objectsToRasterize.length === 0) {
|
||||||
|
message.error("图层没有内容可导出");
|
||||||
|
throw new Error("图层没有内容可导出");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 收集要栅格化的图层和对象
|
|
||||||
this._collectLayersAndObjects();
|
|
||||||
|
|
||||||
if (this.objectsToRasterize.length === 0) {
|
|
||||||
message.error("图层没有内容可导出");
|
|
||||||
throw new Error("图层没有内容可导出");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存原始对象状态
|
// 保存原始对象状态
|
||||||
this.canvas.discardActiveObject();
|
this.canvas.discardActiveObject();
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
@@ -563,8 +590,6 @@ export class ExportLayerToImageCommand extends Command {
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
originalLayerId: this.layerId,
|
originalLayerId: this.layerId,
|
||||||
originalLayerName: this.layer?.name,
|
originalLayerName: this.layer?.name,
|
||||||
rasterizedLayerId: this.rasterizedLayerId,
|
|
||||||
rasterizedLayerName: this.rasterizedLayer?.name,
|
|
||||||
isGroupLayer: this.isGroupLayer,
|
isGroupLayer: this.isGroupLayer,
|
||||||
objectCount: this.objectsToRasterize?.length || 0,
|
objectCount: this.objectsToRasterize?.length || 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ function findParentLayerId() {
|
|||||||
'fixed-layer': layer.isBackground || layer.isFixed,
|
'fixed-layer': layer.isBackground || layer.isFixed,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
:data-layer-id="layer.id"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
@dblclick="handleDoubleClick"
|
@dblclick="handleDoubleClick"
|
||||||
@touchstart="handleTouchStart"
|
@touchstart="handleTouchStart"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const emit = defineEmits([
|
|||||||
"update:editing-name",
|
"update:editing-name",
|
||||||
"root-layers-sort",
|
"root-layers-sort",
|
||||||
"child-layers-sort",
|
"child-layers-sort",
|
||||||
|
"cross-level-move", // 新增:跨层级移动事件
|
||||||
"select-child-layer",
|
"select-child-layer",
|
||||||
"start-child-layer-edit",
|
"start-child-layer-edit",
|
||||||
"child-context-menu",
|
"child-context-menu",
|
||||||
@@ -73,6 +74,79 @@ const forwardEvent = (eventName, ...args) => {
|
|||||||
|
|
||||||
// 处理根级图层拖拽排序
|
// 处理根级图层拖拽排序
|
||||||
const handleRootLayersSort = (event) => {
|
const handleRootLayersSort = (event) => {
|
||||||
|
console.log("🔄 拖拽结束事件:", event);
|
||||||
|
console.log("📍 当前组件信息:", {
|
||||||
|
isChild: props.isChild,
|
||||||
|
parentLayerId: props.parentLayerId,
|
||||||
|
groupName: props.groupName,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理拖拽样式
|
||||||
|
const sortableContainers = document.querySelectorAll(".sortable-layers");
|
||||||
|
sortableContainers.forEach((container) => {
|
||||||
|
container.classList.remove("drag-mode", "drop-target", "drop-invalid");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否为跨层级拖拽
|
||||||
|
const { from, to, oldIndex, newIndex, item } = event;
|
||||||
|
const isFromDifferentContainer = from !== to;
|
||||||
|
|
||||||
|
if (isFromDifferentContainer) {
|
||||||
|
console.log("🔀 检测到跨层级拖拽");
|
||||||
|
|
||||||
|
// 获取被拖拽的图层ID
|
||||||
|
const draggedLayerId =
|
||||||
|
item.getAttribute("data-layer-id") ||
|
||||||
|
props.sortableRootLayers[oldIndex]?.id;
|
||||||
|
|
||||||
|
if (!draggedLayerId) {
|
||||||
|
console.error("❌ 无法获取被拖拽的图层ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取容器类型信息
|
||||||
|
const fromContainerType = from.getAttribute("data-container-type");
|
||||||
|
const toContainerType = to.getAttribute("data-container-type");
|
||||||
|
const fromParentId = from.getAttribute("data-parent-id");
|
||||||
|
const toParentId = to.getAttribute("data-parent-id");
|
||||||
|
|
||||||
|
console.log("📦 拖拽信息:", {
|
||||||
|
fromContainerType,
|
||||||
|
toContainerType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
draggedLayerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证拖拽的有效性
|
||||||
|
if (
|
||||||
|
!validateCrossLevelMove(
|
||||||
|
draggedLayerId,
|
||||||
|
fromContainerType,
|
||||||
|
toContainerType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.warn("⚠️ 无效的跨层级移动,操作被取消");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送跨层级移动事件
|
||||||
|
emit("cross-level-move", {
|
||||||
|
layerId: draggedLayerId,
|
||||||
|
fromContainerType,
|
||||||
|
toContainerType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex,
|
||||||
|
oldIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通的同层级拖拽排序
|
||||||
if (props.isChild) {
|
if (props.isChild) {
|
||||||
// 子图层事件处理
|
// 子图层事件处理
|
||||||
// 确保排序只影响当前组图层的children,而不是全局layers
|
// 确保排序只影响当前组图层的children,而不是全局layers
|
||||||
@@ -87,6 +161,92 @@ const handleRootLayersSort = (event) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 验证跨层级移动的有效性
|
||||||
|
const validateCrossLevelMove = (
|
||||||
|
layerId,
|
||||||
|
fromType,
|
||||||
|
toType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId
|
||||||
|
) => {
|
||||||
|
// 查找图层
|
||||||
|
const layer = findLayerInHierarchy(layerId, fromType, fromParentId);
|
||||||
|
|
||||||
|
if (!layer) {
|
||||||
|
console.error("找不到要移动的图层:", layerId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为不可移动的图层
|
||||||
|
if (layer.isBackground || layer.isFixed) {
|
||||||
|
console.warn("背景层和固定层不能移动");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否试图将组图层移动到自己的子层级中(防止循环引用)
|
||||||
|
if (toType === "child" && toParentId && isDescendantOf(toParentId, layerId)) {
|
||||||
|
console.warn("不能将图层移动到自己的子层级中");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 在层级结构中查找图层
|
||||||
|
const findLayerInHierarchy = (layerId, containerType, parentId) => {
|
||||||
|
if (containerType === "root") {
|
||||||
|
return props.layers.find((layer) => layer.id === layerId);
|
||||||
|
} else if (containerType === "child" && parentId) {
|
||||||
|
const parent = props.layers.find((layer) => layer.id === parentId);
|
||||||
|
return parent?.children?.find((child) => child.id === layerId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否为子层级关系(防止循环引用)
|
||||||
|
const isDescendantOf = (ancestorId, layerId) => {
|
||||||
|
const checkChildren = (children) => {
|
||||||
|
if (!children) return false;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.id === layerId) return true;
|
||||||
|
if (child.children && checkChildren(child.children)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ancestor = props.layers.find((layer) => layer.id === ancestorId);
|
||||||
|
return ancestor ? checkChildren(ancestor.children) : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 拖拽开始事件
|
||||||
|
const handleDragStart = (event) => {
|
||||||
|
console.log("🎯 开始拖拽:", event);
|
||||||
|
const { item } = event;
|
||||||
|
|
||||||
|
// 为拖拽元素添加数据属性
|
||||||
|
if (item) {
|
||||||
|
item.setAttribute("data-dragging", "true");
|
||||||
|
|
||||||
|
// 添加拖拽相关的样式类
|
||||||
|
const sortableContainers = document.querySelectorAll(".sortable-layers");
|
||||||
|
sortableContainers.forEach((container) => {
|
||||||
|
container.classList.add("drag-mode");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 拖拽添加事件(当元素被拖拽到新容器时)
|
||||||
|
const handleDragAdd = (event) => {
|
||||||
|
console.log("➕ 拖拽添加:", event);
|
||||||
|
// 这个事件在跨容器拖拽时会触发
|
||||||
|
};
|
||||||
|
|
||||||
|
// 拖拽移除事件(当元素被从容器中移除时)
|
||||||
|
const handleDragRemove = (event) => {
|
||||||
|
console.log("➖ 拖拽移除:", event);
|
||||||
|
// 这个事件在跨容器拖拽时会触发
|
||||||
|
};
|
||||||
|
|
||||||
const canDeleteComputed = computed(() => {
|
const canDeleteComputed = computed(() => {
|
||||||
// 如果是子图层,检查父图层是否可以删除
|
// 如果是子图层,检查父图层是否可以删除
|
||||||
if (props.isChild) {
|
if (props.isChild) {
|
||||||
@@ -106,18 +266,33 @@ const canDeleteComputed = computed(() => {
|
|||||||
<VueDraggable
|
<VueDraggable
|
||||||
:model-value="sortableRootLayers"
|
:model-value="sortableRootLayers"
|
||||||
@end="handleRootLayersSort"
|
@end="handleRootLayersSort"
|
||||||
|
@add="handleDragAdd"
|
||||||
|
@remove="handleDragRemove"
|
||||||
|
@start="handleDragStart"
|
||||||
class="sortable-layers"
|
class="sortable-layers"
|
||||||
:animation="200"
|
:data-container-type="isChild ? 'child' : 'root'"
|
||||||
|
:data-parent-id="isChild ? parentLayerId : null"
|
||||||
|
:animation="250"
|
||||||
:disabled="false"
|
:disabled="false"
|
||||||
handle=".layer-drag-handle"
|
handle=".layer-drag-handle"
|
||||||
ghost-class="ghost"
|
ghost-class="ghost"
|
||||||
chosen-class="chosen"
|
chosen-class="chosen"
|
||||||
drag-class="drag"
|
drag-class="drag"
|
||||||
:group="groupName"
|
:group="{
|
||||||
|
name: groupName,
|
||||||
|
pull: true,
|
||||||
|
put: true,
|
||||||
|
}"
|
||||||
|
:swap-threshold="0.5"
|
||||||
|
:empty-insert-threshold="5"
|
||||||
|
:force-fallback="false"
|
||||||
|
:fallback-tolerance="3"
|
||||||
|
:scroll-sensitivity="100"
|
||||||
|
:scroll-speed="10"
|
||||||
>
|
>
|
||||||
<!-- 遍历可排序的根级图层 -->
|
<!-- 遍历可排序的根级图层 -->
|
||||||
<template v-for="(layer, index) in sortableRootLayers" :key="layer.id">
|
<template v-for="(layer, index) in sortableRootLayers" :key="layer.id">
|
||||||
<div class="layer-group">
|
<div class="layer-group" :data-layer-id="layer.id">
|
||||||
<!-- 使用 LayerItem 子组件 -->
|
<!-- 使用 LayerItem 子组件 -->
|
||||||
<!-- :thumbnail-url="getLayerThumbnail(layer.id)" -->
|
<!-- :thumbnail-url="getLayerThumbnail(layer.id)" -->
|
||||||
|
|
||||||
@@ -193,7 +368,7 @@ const canDeleteComputed = computed(() => {
|
|||||||
:expanded-group-ids="expandedGroupIds"
|
:expanded-group-ids="expandedGroupIds"
|
||||||
:isChild="true"
|
:isChild="true"
|
||||||
:parentLayerId="layer.id"
|
:parentLayerId="layer.id"
|
||||||
group-name="layers-child"
|
:group-name="groupName"
|
||||||
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
|
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
|
||||||
@layer-double-click="
|
@layer-double-click="
|
||||||
(...args) => forwardEvent('layer-double-click', ...args)
|
(...args) => forwardEvent('layer-double-click', ...args)
|
||||||
@@ -222,6 +397,9 @@ const canDeleteComputed = computed(() => {
|
|||||||
@child-layers-sort="
|
@child-layers-sort="
|
||||||
(...args) => forwardEvent('child-layers-sort', ...args)
|
(...args) => forwardEvent('child-layers-sort', ...args)
|
||||||
"
|
"
|
||||||
|
@cross-level-move="
|
||||||
|
(...args) => forwardEvent('cross-level-move', ...args)
|
||||||
|
"
|
||||||
@select-child-layer="
|
@select-child-layer="
|
||||||
(...args) => forwardEvent('select-child-layer', ...args)
|
(...args) => forwardEvent('select-child-layer', ...args)
|
||||||
"
|
"
|
||||||
@@ -296,18 +474,81 @@ const canDeleteComputed = computed(() => {
|
|||||||
border-top: 1px solid #e0e0e0;
|
border-top: 1px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拖拽模式样式
|
||||||
|
.drag-mode {
|
||||||
|
.layer-item {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:not(.chosen):not(.ghost) {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 拖拽状态样式
|
// 拖拽状态样式
|
||||||
.ghost {
|
.ghost {
|
||||||
opacity: 0.5;
|
opacity: 0.4;
|
||||||
background: #f0f0f0;
|
background: linear-gradient(90deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
border: 2px dashed #1890ff;
|
||||||
|
transform: rotate(2deg);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chosen {
|
.chosen {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 999;
|
||||||
|
background: linear-gradient(90deg, #fff2e8 0%, #ffd8bf 100%);
|
||||||
|
border: 2px solid #fa8c16;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag {
|
.drag {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
transform: rotate(-1deg);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跨层级拖拽目标区域高亮
|
||||||
|
.sortable-layers {
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-height: 40px; // 确保空组也有足够的拖拽区域
|
||||||
|
|
||||||
|
&.drop-target {
|
||||||
|
background-color: rgba(82, 196, 26, 0.1);
|
||||||
|
border: 2px dashed #52c41a;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "拖拽到此处";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drop-invalid {
|
||||||
|
background-color: rgba(255, 77, 79, 0.1);
|
||||||
|
border: 2px dashed #ff4d4f;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "无法拖拽到此处";
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { findLayerRecursively, isGroupLayer } from "../../utils/layerHelper";
|
|||||||
import ContextMenu from "./ContextMenu.vue";
|
import ContextMenu from "./ContextMenu.vue";
|
||||||
import LayerItem from "./LayerItem.vue";
|
import LayerItem from "./LayerItem.vue";
|
||||||
import LayersList from "./LayersList.vue"; // 引入 LayersList 组件
|
import LayersList from "./LayersList.vue"; // 引入 LayersList 组件
|
||||||
|
import { createCrossLevelMoveCommand } from "../../commands/CrossLevelMoveCommands";
|
||||||
// // 导入命令类
|
// // 导入命令类
|
||||||
// import {
|
// import {
|
||||||
// ReorderLayersCommand,
|
// ReorderLayersCommand,
|
||||||
@@ -605,6 +606,7 @@ function handleLayerClick(layer, event) {
|
|||||||
} else {
|
} else {
|
||||||
// 否则直接设置当前图层为活动图层
|
// 否则直接设置当前图层为活动图层
|
||||||
setActiveLayer(layer.id);
|
setActiveLayer(layer.id);
|
||||||
|
layerManager?.updateLayersObjectsInteractivity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastSelectedIndex.value = sortableRootLayers.value.findIndex(
|
lastSelectedIndex.value = sortableRootLayers.value.findIndex(
|
||||||
@@ -1215,6 +1217,316 @@ function moveLayerToBottom(layerId) {
|
|||||||
const forwardEvent = (eventName, ...args) => {
|
const forwardEvent = (eventName, ...args) => {
|
||||||
emit(eventName, ...args);
|
emit(eventName, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理跨层级移动
|
||||||
|
async function handleCrossLevelMove(moveData) {
|
||||||
|
console.log("🔀 处理跨层级移动:", moveData);
|
||||||
|
|
||||||
|
const {
|
||||||
|
layerId,
|
||||||
|
fromContainerType,
|
||||||
|
toContainerType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex,
|
||||||
|
oldIndex,
|
||||||
|
} = moveData;
|
||||||
|
|
||||||
|
// 基本验证
|
||||||
|
if (!layerId) {
|
||||||
|
console.error("❌ 缺少图层ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 如果有命令管理器,使用命令模式
|
||||||
|
if (commandManager) {
|
||||||
|
console.log("📝 使用命令模式执行跨层级移动");
|
||||||
|
|
||||||
|
const command = createCrossLevelMoveCommand({
|
||||||
|
layers,
|
||||||
|
layerManager,
|
||||||
|
canvas: layerManager?.canvas,
|
||||||
|
layerId,
|
||||||
|
fromContainerType,
|
||||||
|
toContainerType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex,
|
||||||
|
oldIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
const result = await commandManager.executeCommand(command);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
console.log("✅ 跨层级移动命令执行成功");
|
||||||
|
// 清除选择状态
|
||||||
|
clearSelection();
|
||||||
|
} else {
|
||||||
|
console.warn("⚠️ 跨层级移动命令执行失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
// 回退方案:直接执行移动逻辑
|
||||||
|
console.log("📝 使用回退方案执行跨层级移动");
|
||||||
|
return await executeDirectMove(moveData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 跨层级移动失败:", error);
|
||||||
|
|
||||||
|
// 可以在这里添加用户友好的错误提示
|
||||||
|
const errorMessages = {
|
||||||
|
找不到要移动的图层: "无法找到要移动的图层,请刷新页面后重试",
|
||||||
|
背景层和固定层不能移动: "背景层和固定层无法移动",
|
||||||
|
不能将图层移动到自己的子层级中: "无法将图层移动到自己的子层级中",
|
||||||
|
目标图层不是组图层: "只能将图层移动到组图层中",
|
||||||
|
};
|
||||||
|
|
||||||
|
const userMessage =
|
||||||
|
errorMessages[error.message] || `移动失败: ${error.message}`;
|
||||||
|
|
||||||
|
// 这里可以触发一个全局的错误提示组件
|
||||||
|
// 暂时使用console.warn,实际项目中应该替换为适当的提示方式
|
||||||
|
console.warn("用户提示:", userMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接执行移动的回退方案(保持原有逻辑)
|
||||||
|
async function executeDirectMove(moveData) {
|
||||||
|
const {
|
||||||
|
layerId,
|
||||||
|
fromContainerType,
|
||||||
|
toContainerType,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex,
|
||||||
|
oldIndex,
|
||||||
|
} = moveData;
|
||||||
|
|
||||||
|
// 查找被拖拽的图层
|
||||||
|
let draggedLayer = null;
|
||||||
|
let sourceParent = null;
|
||||||
|
|
||||||
|
// 根据源容器类型查找图层
|
||||||
|
if (fromContainerType === "root") {
|
||||||
|
draggedLayer = layers.value.find((layer) => layer.id === layerId);
|
||||||
|
} else if (fromContainerType === "child" && fromParentId) {
|
||||||
|
sourceParent = layers.value.find((layer) => layer.id === fromParentId);
|
||||||
|
if (sourceParent && sourceParent.children) {
|
||||||
|
draggedLayer = sourceParent.children.find(
|
||||||
|
(child) => child.id === layerId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!draggedLayer) {
|
||||||
|
throw new Error("找不到要移动的图层");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否允许移动(背景层和固定层不能移动)
|
||||||
|
if (draggedLayer.isBackground || draggedLayer.isFixed) {
|
||||||
|
throw new Error("背景层和固定层不能移动");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况1: 从顶级图层移动到组图层内
|
||||||
|
if (fromContainerType === "root" && toContainerType === "child") {
|
||||||
|
await moveRootToGroup(draggedLayer, toParentId, newIndex);
|
||||||
|
}
|
||||||
|
// 情况2: 从组图层内移动到顶级图层
|
||||||
|
else if (fromContainerType === "child" && toContainerType === "root") {
|
||||||
|
await moveGroupToRoot(draggedLayer, fromParentId, newIndex);
|
||||||
|
}
|
||||||
|
// 情况3: 在不同组之间移动
|
||||||
|
else if (
|
||||||
|
fromContainerType === "child" &&
|
||||||
|
toContainerType === "child" &&
|
||||||
|
fromParentId !== toParentId
|
||||||
|
) {
|
||||||
|
await moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新画布渲染
|
||||||
|
if (layerManager?.canvas) {
|
||||||
|
layerManager.canvas.renderAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除选择状态
|
||||||
|
clearSelection();
|
||||||
|
|
||||||
|
console.log("✅ 跨层级移动完成");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从顶级图层移动到组图层内
|
||||||
|
async function moveRootToGroup(draggedLayer, toParentId, newIndex) {
|
||||||
|
console.log("📥 顶级图层移动到组内:", {
|
||||||
|
layerId: draggedLayer.id,
|
||||||
|
toParentId,
|
||||||
|
newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 找到目标父图层
|
||||||
|
const targetParent = layers.value.find((layer) => layer.id === toParentId);
|
||||||
|
if (!targetParent) {
|
||||||
|
throw new Error(`找不到目标父图层: ${toParentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目标父图层是否为组
|
||||||
|
if (!isGroupLayerType(targetParent)) {
|
||||||
|
throw new Error("目标图层不是组图层");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保父图层有children数组
|
||||||
|
if (!targetParent.children) {
|
||||||
|
targetParent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从顶级图层数组中移除
|
||||||
|
const rootIndex = layers.value.findIndex(
|
||||||
|
(layer) => layer.id === draggedLayer.id
|
||||||
|
);
|
||||||
|
if (rootIndex !== -1) {
|
||||||
|
layers.value.splice(rootIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 layerManager 的方法移动图层到组内
|
||||||
|
if (layerManager?.moveLayerToGroup) {
|
||||||
|
await layerManager.moveLayerToGroup(draggedLayer.id, toParentId, newIndex);
|
||||||
|
} else {
|
||||||
|
// 回退方案:手动更新图层关系
|
||||||
|
draggedLayer.parentId = toParentId;
|
||||||
|
targetParent.children.splice(newIndex, 0, draggedLayer);
|
||||||
|
|
||||||
|
// 处理 fabricObject 的层级关系
|
||||||
|
if (draggedLayer.fabricObject && targetParent.fabricObject) {
|
||||||
|
if (layerManager?.canvas) {
|
||||||
|
layerManager.canvas.remove(draggedLayer.fabricObject);
|
||||||
|
}
|
||||||
|
targetParent.fabricObject.add(draggedLayer.fabricObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 成功将图层移动到组内");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从组图层内移动到顶级图层
|
||||||
|
async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
|
||||||
|
console.log("📤 组内图层移动到顶级:", {
|
||||||
|
layerId: draggedLayer.id,
|
||||||
|
fromParentId,
|
||||||
|
newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 找到源父图层
|
||||||
|
const sourceParent = layers.value.find((layer) => layer.id === fromParentId);
|
||||||
|
if (!sourceParent || !sourceParent.children) {
|
||||||
|
throw new Error(`找不到源父图层或其children数组: ${fromParentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 layerManager 的方法移动图层到顶级
|
||||||
|
if (layerManager?.moveLayerToRoot) {
|
||||||
|
await layerManager.moveLayerToRoot(draggedLayer.id, newIndex);
|
||||||
|
} else {
|
||||||
|
// 回退方案:手动更新图层关系
|
||||||
|
// 从源父图层的children中移除
|
||||||
|
const childIndex = sourceParent.children.findIndex(
|
||||||
|
(child) => child.id === draggedLayer.id
|
||||||
|
);
|
||||||
|
if (childIndex !== -1) {
|
||||||
|
sourceParent.children.splice(childIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 fabricObject 的层级关系
|
||||||
|
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
|
||||||
|
sourceParent.fabricObject.remove(draggedLayer.fabricObject);
|
||||||
|
if (layerManager?.canvas) {
|
||||||
|
layerManager.canvas.add(draggedLayer.fabricObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除图层的parentId
|
||||||
|
delete draggedLayer.parentId;
|
||||||
|
|
||||||
|
// 计算在顶级图层中的插入位置
|
||||||
|
const targetIndex = Math.min(newIndex, layers.value.length);
|
||||||
|
|
||||||
|
// 将图层添加到顶级图层的指定位置
|
||||||
|
layers.value.splice(targetIndex, 0, draggedLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 成功将图层移动到顶级");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在不同组之间移动
|
||||||
|
async function moveGroupToGroup(
|
||||||
|
draggedLayer,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex
|
||||||
|
) {
|
||||||
|
console.log("🔄 在不同组间移动:", {
|
||||||
|
layerId: draggedLayer.id,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 找到源父图层和目标父图层
|
||||||
|
const sourceParent = layers.value.find((layer) => layer.id === fromParentId);
|
||||||
|
const targetParent = layers.value.find((layer) => layer.id === toParentId);
|
||||||
|
|
||||||
|
if (!sourceParent || !targetParent) {
|
||||||
|
throw new Error("找不到源父图层或目标父图层");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGroupLayerType(targetParent)) {
|
||||||
|
throw new Error("目标图层不是组图层");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 layerManager 的方法在组间移动
|
||||||
|
if (layerManager?.moveLayerBetweenGroups) {
|
||||||
|
await layerManager.moveLayerBetweenGroups(
|
||||||
|
draggedLayer.id,
|
||||||
|
fromParentId,
|
||||||
|
toParentId,
|
||||||
|
newIndex
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 回退方案:手动更新图层关系
|
||||||
|
// 从源父图层中移除
|
||||||
|
const childIndex = sourceParent.children.findIndex(
|
||||||
|
(child) => child.id === draggedLayer.id
|
||||||
|
);
|
||||||
|
if (childIndex !== -1) {
|
||||||
|
sourceParent.children.splice(childIndex, 1);
|
||||||
|
|
||||||
|
// 从源父组的fabricObject中移除
|
||||||
|
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
|
||||||
|
sourceParent.fabricObject.remove(draggedLayer.fabricObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图层的parentId
|
||||||
|
draggedLayer.parentId = toParentId;
|
||||||
|
|
||||||
|
// 确保目标父图层有children数组
|
||||||
|
if (!targetParent.children) {
|
||||||
|
targetParent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将图层添加到目标组
|
||||||
|
targetParent.children.splice(newIndex, 0, draggedLayer);
|
||||||
|
|
||||||
|
// 将图层的fabricObject添加到目标父组中
|
||||||
|
if (draggedLayer.fabricObject && targetParent.fabricObject) {
|
||||||
|
targetParent.fabricObject.add(draggedLayer.fabricObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ 成功在不同组间移动图层");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -1327,6 +1639,7 @@ const forwardEvent = (eventName, ...args) => {
|
|||||||
@update:editing-name="editingLayerName = $event"
|
@update:editing-name="editingLayerName = $event"
|
||||||
@root-layers-sort="handleRootLayersSort"
|
@root-layers-sort="handleRootLayersSort"
|
||||||
@child-layers-sort="handleChildLayersSort"
|
@child-layers-sort="handleChildLayersSort"
|
||||||
|
@cross-level-move="handleCrossLevelMove"
|
||||||
@select-child-layer="selectChildLayer"
|
@select-child-layer="selectChildLayer"
|
||||||
@start-child-layer-edit="startChildLayerEdit"
|
@start-child-layer-edit="startChildLayerEdit"
|
||||||
@child-context-menu="showChildLayerContextMenu"
|
@child-context-menu="showChildLayerContextMenu"
|
||||||
|
|||||||
@@ -335,6 +335,11 @@ export class LayerManager {
|
|||||||
// 设置裁剪对象
|
// 设置裁剪对象
|
||||||
layers.forEach((layer) => {
|
layers.forEach((layer) => {
|
||||||
if (layer.clippingMask) {
|
if (layer.clippingMask) {
|
||||||
|
const activeObject = this.canvas.getActiveObject();
|
||||||
|
if (activeObject?._objects?.length > 1) {
|
||||||
|
console.log(activeObject?._objects?.length);
|
||||||
|
return false; // 如果是多选对象,则不设置裁剪路径
|
||||||
|
}
|
||||||
// 如果是组图层 则给所有子对象设置裁剪对象
|
// 如果是组图层 则给所有子对象设置裁剪对象
|
||||||
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
||||||
layer.children.forEach((childLayer) => {
|
layer.children.forEach((childLayer) => {
|
||||||
@@ -344,8 +349,10 @@ export class LayerManager {
|
|||||||
);
|
);
|
||||||
if (object) {
|
if (object) {
|
||||||
const tempClipPath = fabric.util.object.clone(object);
|
const tempClipPath = fabric.util.object.clone(object);
|
||||||
|
tempClipPath.clipPath = null;
|
||||||
tempClipPath.set({
|
tempClipPath.set({
|
||||||
// 设置绝对定位
|
// 设置绝对定位
|
||||||
|
// ...layer.clippingMask, // 恢复原定位
|
||||||
absolutePositioned: true,
|
absolutePositioned: true,
|
||||||
});
|
});
|
||||||
const childObj = this.canvas
|
const childObj = this.canvas
|
||||||
@@ -362,7 +369,14 @@ export class LayerManager {
|
|||||||
layer.clippingMask?.id
|
layer.clippingMask?.id
|
||||||
);
|
);
|
||||||
if (object) {
|
if (object) {
|
||||||
obj.clipPath = object;
|
const tempClipPath = fabric.util.object.clone(object);
|
||||||
|
tempClipPath.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
|
tempClipPath.set({
|
||||||
|
// 设置绝对定位
|
||||||
|
// ...layer.clippingMask, // 恢复原定位
|
||||||
|
absolutePositioned: true,
|
||||||
|
});
|
||||||
|
obj.clipPath = tempClipPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -918,13 +932,17 @@ export class LayerManager {
|
|||||||
// 设置激活当前图层下画布中的所有对象,并变成选择组
|
// 设置激活当前图层下画布中的所有对象,并变成选择组
|
||||||
setAllActiveGroupLayerCanvasObject(layer) {
|
setAllActiveGroupLayerCanvasObject(layer) {
|
||||||
// 获取当前图层下所有元素
|
// 获取当前图层下所有元素
|
||||||
|
let layerMask = null;
|
||||||
// 选择当前组下所有画布元素
|
// 选择当前组下所有画布元素
|
||||||
const allObjects = layer.children.reduce((acc, child) => {
|
const allObjects = layer.children.reduce((acc, child) => {
|
||||||
// 如果子图层有fabricObjects,则添加到结果数组
|
// 如果子图层有fabricObjects,则添加到结果数组
|
||||||
child?.fabricObjects?.forEach((obj) => {
|
child?.fabricObjects?.forEach((obj) => {
|
||||||
const { object } = findObjectById(this.canvas, obj.id);
|
const { object } = findObjectById(this.canvas, obj.id);
|
||||||
if (object) {
|
if (object) {
|
||||||
|
if (!layerMask) {
|
||||||
|
layerMask = fabric.util.object.clone(object.clipPath);
|
||||||
|
}
|
||||||
|
object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
acc.push(object);
|
acc.push(object);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -932,6 +950,10 @@ export class LayerManager {
|
|||||||
if (child?.fabricObject) {
|
if (child?.fabricObject) {
|
||||||
const { object } = findObjectById(this.canvas, child?.fabricObject.id);
|
const { object } = findObjectById(this.canvas, child?.fabricObject.id);
|
||||||
if (object) {
|
if (object) {
|
||||||
|
if (!layerMask) {
|
||||||
|
layerMask = fabric.util.object.clone(object.clipPath);
|
||||||
|
}
|
||||||
|
object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
acc.push(object);
|
acc.push(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -951,6 +973,8 @@ export class LayerManager {
|
|||||||
canvas: this.canvas,
|
canvas: this.canvas,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
activeSelection.clipPath = layerMask; // 保留第一个对象的裁剪路径
|
||||||
|
|
||||||
// 设置活动选择组的属性
|
// 设置活动选择组的属性
|
||||||
this.canvas.setActiveObject(activeSelection);
|
this.canvas.setActiveObject(activeSelection);
|
||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
|
|||||||
@@ -18,9 +18,15 @@ export const createRasterizedImage = async ({
|
|||||||
scaleFactor = 2, // 高清倍数 - 默认是画布的高清倍数
|
scaleFactor = 2, // 高清倍数 - 默认是画布的高清倍数
|
||||||
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
|
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
|
||||||
isThumbnail = false, // 是否为缩略图
|
isThumbnail = false, // 是否为缩略图
|
||||||
|
preserveOriginalQuality = true, // 是否保持原始质量
|
||||||
|
isGroupWithMask = false, // 是否为带遮罩的组图层
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
try {
|
try {
|
||||||
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`);
|
console.log(
|
||||||
|
`📊 开始栅格化 ${fabricObjects.length} 个对象${
|
||||||
|
maskObject ? "(带遮罩)" : ""
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
// 确保有对象需要栅格化
|
// 确保有对象需要栅格化
|
||||||
if (fabricObjects.length === 0) {
|
if (fabricObjects.length === 0) {
|
||||||
@@ -39,7 +45,50 @@ export const createRasterizedImage = async ({
|
|||||||
|
|
||||||
console.log(`高清倍数: ${scaleFactor}, 当前缩放: ${currentZoom}`);
|
console.log(`高清倍数: ${scaleFactor}, 当前缩放: ${currentZoom}`);
|
||||||
|
|
||||||
// 使用组对象方式创建栅格化图像
|
// 如果有遮罩且保持原始质量,使用高质量的遮罩处理方法
|
||||||
|
if (maskObject && preserveOriginalQuality) {
|
||||||
|
const rasterizedImage = await createRasterizedImageWithMask({
|
||||||
|
canvas,
|
||||||
|
objects: fabricObjects,
|
||||||
|
maskObject,
|
||||||
|
scaleFactor,
|
||||||
|
quality,
|
||||||
|
format,
|
||||||
|
currentZoom,
|
||||||
|
isReturenDataURL,
|
||||||
|
isGroupWithMask,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rasterizedImage) {
|
||||||
|
console.warn("⚠️ 带遮罩的栅格化图像创建失败,返回空图像");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReturenDataURL) {
|
||||||
|
console.log("✅ 带遮罩的栅格化图像创建成功,返回DataURL");
|
||||||
|
return rasterizedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置栅格化图像的属性
|
||||||
|
rasterizedImage.set({
|
||||||
|
selectable: true,
|
||||||
|
evented: true,
|
||||||
|
hasControls: true,
|
||||||
|
hasBorders: true,
|
||||||
|
custom: {
|
||||||
|
type: "rasterized",
|
||||||
|
rasterizedAt: new Date().toISOString(),
|
||||||
|
objectCount: fabricObjects.length,
|
||||||
|
originalZoom: currentZoom,
|
||||||
|
hasMask: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ 带遮罩的栅格化图像创建完成`);
|
||||||
|
return rasterizedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用原有的组对象方式创建栅格化图像
|
||||||
const rasterizedImage = await createRasterizedImageWithGroup({
|
const rasterizedImage = await createRasterizedImageWithGroup({
|
||||||
canvas,
|
canvas,
|
||||||
objects: fabricObjects,
|
objects: fabricObjects,
|
||||||
@@ -60,7 +109,7 @@ export const createRasterizedImage = async ({
|
|||||||
|
|
||||||
if (isReturenDataURL) {
|
if (isReturenDataURL) {
|
||||||
console.log("✅ 栅格化图像创建成功,返回DataURL");
|
console.log("✅ 栅格化图像创建成功,返回DataURL");
|
||||||
return rasterizedImage; // 返回DataURL
|
return rasterizedImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置栅格化图像的属性
|
// 设置栅格化图像的属性
|
||||||
@@ -117,6 +166,7 @@ const createRasterizedImageWithGroup = async ({
|
|||||||
select: false,
|
select: false,
|
||||||
evented: false,
|
evented: false,
|
||||||
hasControls: false,
|
hasControls: false,
|
||||||
|
clipPath: null, // 确保克隆对象没有clipPath
|
||||||
});
|
});
|
||||||
clonedObjects.push(clonedObj);
|
clonedObjects.push(clonedObj);
|
||||||
}
|
}
|
||||||
@@ -207,6 +257,144 @@ const createRasterizedImageWithGroup = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用遮罩创建栅格化图像 - 专门处理带遮罩的组图层
|
||||||
|
* 基于遮罩的位置和大小来裁剪内容,确保正确的定位
|
||||||
|
* @param {Object} options 渲染选项
|
||||||
|
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
|
||||||
|
*/
|
||||||
|
const createRasterizedImageWithMask = async ({
|
||||||
|
canvas,
|
||||||
|
objects,
|
||||||
|
maskObject,
|
||||||
|
scaleFactor,
|
||||||
|
quality,
|
||||||
|
format,
|
||||||
|
currentZoom,
|
||||||
|
isReturenDataURL,
|
||||||
|
isGroupWithMask,
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
console.log("🎭 使用遮罩创建栅格化图像");
|
||||||
|
|
||||||
|
// 获取遮罩的边界框,这将作为最终图像的边界
|
||||||
|
const maskBounds = maskObject.getBoundingRect(true, true);
|
||||||
|
console.log("📏 遮罩边界框:", maskBounds);
|
||||||
|
|
||||||
|
// 克隆所有对象,并清除它们的遮罩,避免重复应用
|
||||||
|
const clonedObjects = [];
|
||||||
|
for (const obj of objects) {
|
||||||
|
const clonedObj = await cloneObjectAsync(obj);
|
||||||
|
clonedObj.set({
|
||||||
|
select: false,
|
||||||
|
evented: false,
|
||||||
|
hasControls: false,
|
||||||
|
clipPath: null, // 清除clipPath,避免重复应用遮罩
|
||||||
|
});
|
||||||
|
clonedObjects.push(clonedObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 克隆遮罩对象用于裁剪
|
||||||
|
const clonedMask = await cloneObjectAsync(maskObject);
|
||||||
|
clonedMask.set({
|
||||||
|
select: false,
|
||||||
|
evented: false,
|
||||||
|
hasControls: false,
|
||||||
|
absolutePositioned: false, // 设置为绝对定位
|
||||||
|
// fill: "#ffffff", // 遮罩使用白色
|
||||||
|
// stroke: "", // 确保没有描边
|
||||||
|
// strokeWidth: 0,
|
||||||
|
});
|
||||||
|
clonedMask.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
|
|
||||||
|
// 创建离屏画布,使用遮罩的边界大小
|
||||||
|
const offscreenCanvas = createStaticCanvas();
|
||||||
|
const canvasWidth = Math.ceil(maskBounds.width * scaleFactor);
|
||||||
|
const canvasHeight = Math.ceil(maskBounds.height * scaleFactor);
|
||||||
|
|
||||||
|
offscreenCanvas.setDimensions({
|
||||||
|
width: canvasWidth,
|
||||||
|
height: canvasHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 调整对象位置,相对于遮罩边界重新定位
|
||||||
|
clonedObjects.forEach((obj) => {
|
||||||
|
obj.set({
|
||||||
|
left: (obj.left - maskBounds.left) * scaleFactor,
|
||||||
|
top: (obj.top - maskBounds.top) * scaleFactor,
|
||||||
|
scaleX: (obj.scaleX || 1) * scaleFactor,
|
||||||
|
scaleY: (obj.scaleY || 1) * scaleFactor,
|
||||||
|
});
|
||||||
|
obj.setCoords();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调整遮罩位置
|
||||||
|
clonedMask.set({
|
||||||
|
left: 0, // 遮罩从画布左上角开始
|
||||||
|
top: 0,
|
||||||
|
scaleX: (clonedMask.scaleX || 1) * scaleFactor,
|
||||||
|
scaleY: (clonedMask.scaleY || 1) * scaleFactor,
|
||||||
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
|
absolutePositioned: true, // 设置为绝对定位
|
||||||
|
});
|
||||||
|
clonedMask.setCoords();
|
||||||
|
|
||||||
|
// 添加所有对象到离屏画布
|
||||||
|
clonedObjects.forEach((obj) => {
|
||||||
|
offscreenCanvas.add(obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用遮罩作为画布的clipPath来裁剪内容
|
||||||
|
offscreenCanvas.clipPath = clonedMask;
|
||||||
|
|
||||||
|
// 渲染离屏画布
|
||||||
|
offscreenCanvas.renderAll();
|
||||||
|
|
||||||
|
// 生成图像数据
|
||||||
|
const dataURL = offscreenCanvas.toDataURL({
|
||||||
|
format,
|
||||||
|
quality,
|
||||||
|
multiplier: 1, // 已经通过画布尺寸和对象缩放处理了高清倍数
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isReturenDataURL) {
|
||||||
|
// 清理离屏画布
|
||||||
|
offscreenCanvas.dispose();
|
||||||
|
return dataURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理离屏画布
|
||||||
|
offscreenCanvas.dispose();
|
||||||
|
|
||||||
|
// 创建fabric.Image对象
|
||||||
|
const fabricImage = await createFabricImageFromDataURL(dataURL);
|
||||||
|
|
||||||
|
// 设置图像的位置,使其与原始遮罩的位置匹配
|
||||||
|
fabricImage.set({
|
||||||
|
scaleX: 1 / scaleFactor, // 由于我们生成的图像是高清版本,需要缩放回原始大小
|
||||||
|
scaleY: 1 / scaleFactor,
|
||||||
|
left: maskBounds.left + maskBounds.width / 2, // 设置为遮罩中心点
|
||||||
|
top: maskBounds.top + maskBounds.height / 2, // 设置为遮罩中心点
|
||||||
|
originX: "center",
|
||||||
|
originY: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确保图像位置正确
|
||||||
|
fabricImage.setCoords();
|
||||||
|
|
||||||
|
console.log("✅ 带遮罩的栅格化图像创建完成");
|
||||||
|
return fabricImage;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("带遮罩的栅格化失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步克隆fabric对象
|
* 异步克隆fabric对象
|
||||||
* @param {fabric.Object} obj 要克隆的对象
|
* @param {fabric.Object} obj 要克隆的对象
|
||||||
@@ -248,9 +436,40 @@ const createFabricImageFromDataURL = (dataURL) => {
|
|||||||
* @param {Object} bounds 边界框
|
* @param {Object} bounds 边界框
|
||||||
*/
|
*/
|
||||||
const applyMaskToCanvas = async (canvas, maskObject, bounds) => {
|
const applyMaskToCanvas = async (canvas, maskObject, bounds) => {
|
||||||
// 这里可以实现遮罩逻辑
|
if (!maskObject) {
|
||||||
// 例如使用canvas的clipPath或其他遮罩技术
|
console.log("没有遮罩对象,跳过遮罩应用");
|
||||||
console.log("应用遮罩功能待实现");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("🎭 应用遮罩到画布");
|
||||||
|
|
||||||
|
// 克隆遮罩对象,避免影响原对象
|
||||||
|
const clonedMask = await cloneObjectAsync(maskObject);
|
||||||
|
|
||||||
|
// 设置遮罩属性
|
||||||
|
clonedMask.set({
|
||||||
|
fill: "#ffffff", // 遮罩使用白色
|
||||||
|
stroke: "", // 确保没有描边
|
||||||
|
strokeWidth: 0,
|
||||||
|
selectable: false,
|
||||||
|
evented: false,
|
||||||
|
absolutePositioned: true, // 设置为绝对定位
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调整遮罩位置,相对于画布边界重新定位
|
||||||
|
clonedMask.set({
|
||||||
|
left: clonedMask.left - bounds.left,
|
||||||
|
top: clonedMask.top - bounds.top,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将遮罩设置为画布的clipPath
|
||||||
|
canvas.clipPath = clonedMask;
|
||||||
|
|
||||||
|
console.log("✅ 遮罩应用完成");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("应用遮罩失败:", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user