feat: 添加对图层操作的支持,优化图层粘贴和变换命令,增强组图层遮罩位置更新逻辑
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
removeCanvasObjectByObject,
|
||||
} from "../utils/helper";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { restoreFabricObject } from "../utils/objectHelper";
|
||||
|
||||
/**
|
||||
* 添加图层命令
|
||||
@@ -188,12 +189,13 @@ export class PasteLayerCommand extends Command {
|
||||
this.clipboardData = options.clipboardData;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 新图层相关属性
|
||||
this.newLayer = null;
|
||||
this.newLayerId = null;
|
||||
this.newLayerId = generateId("layer_");
|
||||
this.insertIndex = null;
|
||||
this.oldActiveLayerId = null;
|
||||
this.createdObjects = [];
|
||||
this.isGroupLayer = false;
|
||||
this.childLayerIdMap = new Map();
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -201,27 +203,9 @@ export class PasteLayerCommand extends Command {
|
||||
console.error("剪贴板中没有图层数据");
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = this.clipboardData;
|
||||
const fabric = window.fabric;
|
||||
|
||||
if (!fabric) {
|
||||
console.error("未找到fabric库");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成新的图层ID
|
||||
this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
// 创建新图层
|
||||
this.newLayer = {
|
||||
...data,
|
||||
id: this.newLayerId,
|
||||
name: `${data.name} 副本`,
|
||||
fabricObjects: [],
|
||||
isCut: undefined,
|
||||
serializedObjects: undefined,
|
||||
};
|
||||
const data = JSON.parse(JSON.stringify(this.clipboardData));
|
||||
// 判断是否为组图层
|
||||
this.isGroupLayer = data.type === "group" || !!data?.children?.length;
|
||||
|
||||
// 保存当前活动图层ID
|
||||
this.oldActiveLayerId = this.activeLayerId.value;
|
||||
@@ -229,144 +213,263 @@ export class PasteLayerCommand extends Command {
|
||||
// 计算插入位置
|
||||
this.insertIndex = this.layerManager._getInsertIndexAboveActiveLayer();
|
||||
|
||||
// 执行添加图层操作
|
||||
if (this.insertIndex !== undefined && this.insertIndex !== null) {
|
||||
this.layers.value.splice(this.insertIndex, 0, this.newLayer);
|
||||
} else {
|
||||
this.layers.value.push(this.newLayer);
|
||||
}
|
||||
if (this.isGroupLayer) {
|
||||
// 粘贴为新的组图层,递归生成子图层ID
|
||||
const { groupLayer, allObjects } = await this._createGroupLayerFromClipboard(data);
|
||||
if (groupLayer?.clippingMask) {
|
||||
// 给遮罩蒙层也添加上偏移
|
||||
groupLayer.clippingMask.left += 30;
|
||||
groupLayer.clippingMask.top += 30;
|
||||
}
|
||||
this.newLayer = groupLayer;
|
||||
this.newLayer.id = this.newLayerId;
|
||||
|
||||
// 更新活动图层
|
||||
if (!this.newLayer.isBackground) {
|
||||
// 插入组图层
|
||||
if (this.insertIndex !== undefined && this.insertIndex !== null) {
|
||||
this.layers.value.splice(this.insertIndex, 0, this.newLayer);
|
||||
} else {
|
||||
this.layers.value.push(this.newLayer);
|
||||
}
|
||||
// 重新创建遮罩对象
|
||||
const clippingMaskFabricObject =
|
||||
(await restoreFabricObject(groupLayer?.clippingMask, this.canvas)) || null;
|
||||
|
||||
clippingMaskFabricObject.clipPath = null;
|
||||
clippingMaskFabricObject.set({
|
||||
absolutePositioned: true,
|
||||
});
|
||||
|
||||
clippingMaskFabricObject.dirty = true;
|
||||
clippingMaskFabricObject.setCoords();
|
||||
// 添加所有对象到画布
|
||||
allObjects.forEach((obj) => {
|
||||
obj.clipPath = clippingMaskFabricObject;
|
||||
obj.dirty = true;
|
||||
obj.setCoords();
|
||||
this.canvas.add(obj);
|
||||
});
|
||||
this.createdObjects = allObjects;
|
||||
|
||||
// 设置活动图层为新组图层
|
||||
this.activeLayerId.value = this.newLayer.id;
|
||||
}
|
||||
|
||||
// 如果有序列化的对象,异步恢复它们
|
||||
if (
|
||||
data.serializedObjects &&
|
||||
Array.isArray(data.serializedObjects) &&
|
||||
data.serializedObjects.length > 0
|
||||
) {
|
||||
await this._restoreObjectsAsync(data);
|
||||
} else {
|
||||
this._onObjectsRestored(data);
|
||||
// 普通图层粘贴逻辑
|
||||
this.newLayer = {
|
||||
...data,
|
||||
id: this.newLayerId,
|
||||
name: `${data.name} 副本`,
|
||||
fabricObjects: [],
|
||||
isCut: undefined,
|
||||
serializedObjects: undefined,
|
||||
};
|
||||
|
||||
if (this.insertIndex !== undefined && this.insertIndex !== null) {
|
||||
this.layers.value.splice(this.insertIndex, 0, this.newLayer);
|
||||
} else {
|
||||
this.layers.value.push(this.newLayer);
|
||||
}
|
||||
|
||||
if (!this.newLayer.isBackground) {
|
||||
this.activeLayerId.value = this.newLayer.id;
|
||||
}
|
||||
|
||||
if (
|
||||
data.serializedObjects &&
|
||||
Array.isArray(data.serializedObjects) &&
|
||||
data.serializedObjects.length > 0
|
||||
) {
|
||||
await this._restoreObjectsAsync(data);
|
||||
} else {
|
||||
this._onObjectsRestored(data);
|
||||
}
|
||||
}
|
||||
// 取消画布激活对象
|
||||
|
||||
this.canvas.discardActiveObject(); // 取消当前活动对象
|
||||
// 重新排序
|
||||
await this.layerManager?.sortLayersWithTool?.();
|
||||
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||
|
||||
return this.newLayerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步恢复序列化的对象
|
||||
* @param {Object} data 剪贴板数据
|
||||
* @private
|
||||
* 递归创建组图层及其所有子图层和对象
|
||||
*/
|
||||
async _restoreObjectsAsync(data) {
|
||||
async _createGroupLayerFromClipboard(data) {
|
||||
const groupId = generateId("group_layer_");
|
||||
const childLayerIdMap = new Map();
|
||||
const allObjects = [];
|
||||
|
||||
// 递归处理子图层
|
||||
const processChildren = async (children) => {
|
||||
const result = [];
|
||||
for (const child of children) {
|
||||
const newChildId = generateId("layer_");
|
||||
childLayerIdMap.set(child.id, newChildId);
|
||||
|
||||
// 递归处理子组
|
||||
let childLayer;
|
||||
if (child.type === "group" && Array.isArray(child.children)) {
|
||||
const { groupLayer, allObjects: childObjs } =
|
||||
await this._createGroupLayerFromClipboard(child);
|
||||
childLayer = groupLayer;
|
||||
allObjects.push(...childObjs);
|
||||
} else {
|
||||
// 普通子图层
|
||||
childLayer = {
|
||||
...child,
|
||||
id: newChildId,
|
||||
name: `${child.name} 副本`,
|
||||
fabricObjects: [],
|
||||
isCut: undefined,
|
||||
serializedObjects: undefined,
|
||||
};
|
||||
// 恢复对象
|
||||
if (
|
||||
child.serializedObjects &&
|
||||
Array.isArray(child.serializedObjects) &&
|
||||
child.serializedObjects.length > 0
|
||||
) {
|
||||
const objs = await this._enlivenObjects(
|
||||
child.serializedObjects,
|
||||
newChildId,
|
||||
`${child.name} 副本`
|
||||
);
|
||||
childLayer.fabricObjects = objs.map((obj) =>
|
||||
obj.toObject(["id", "layerId", "layerName"])
|
||||
);
|
||||
allObjects.push(...objs);
|
||||
}
|
||||
}
|
||||
result.push(childLayer);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 处理当前组的子图层
|
||||
const children = await processChildren(data.children);
|
||||
|
||||
// 创建组图层对象
|
||||
const groupLayer = {
|
||||
...data,
|
||||
id: groupId,
|
||||
name: `${data.name} 副本`,
|
||||
type: "group",
|
||||
children,
|
||||
fabricObjects: [],
|
||||
isCut: undefined,
|
||||
serializedObjects: undefined,
|
||||
};
|
||||
|
||||
this.childLayerIdMap = childLayerIdMap;
|
||||
|
||||
// 取消画布激活对象
|
||||
|
||||
this.canvas.discardActiveObject(); // 取消当前活动对象
|
||||
return { groupLayer, allObjects };
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复对象(用于普通图层和组图层的子图层)
|
||||
*/
|
||||
async _enlivenObjects(serializedObjects, layerId, layerName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.util.enlivenObjects(data.serializedObjects, (objects) => {
|
||||
fabric.util.enlivenObjects(serializedObjects, (objects) => {
|
||||
try {
|
||||
objects.forEach((obj) => {
|
||||
// 生成新的对象ID
|
||||
const newObjId = `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
obj.id = newObjId;
|
||||
obj.layerId = this.newLayerId;
|
||||
obj.layerName = this.newLayer.name;
|
||||
|
||||
// 如果是复制操作,给对象添加偏移量
|
||||
if (!data.isCut) {
|
||||
const offset = 10;
|
||||
if (obj.left !== undefined) obj.left += offset;
|
||||
if (obj.top !== undefined) obj.top += offset;
|
||||
}
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(obj);
|
||||
|
||||
// 添加到图层
|
||||
this.newLayer.fabricObjects.push(obj);
|
||||
|
||||
// 记录创建的对象,用于撤销
|
||||
this.createdObjects.push(obj);
|
||||
obj.id = generateId("obj_");
|
||||
obj.layerId = layerId;
|
||||
obj.layerName = layerName;
|
||||
// 添加偏移
|
||||
const offset = 30;
|
||||
if (obj.left !== undefined) obj.left += offset;
|
||||
if (obj.top !== undefined) obj.top += offset;
|
||||
});
|
||||
|
||||
this._onObjectsRestored(data);
|
||||
resolve();
|
||||
resolve(objects);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _restoreObjectsAsync(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.util.enlivenObjects(data.serializedObjects, (objects) => {
|
||||
try {
|
||||
objects.forEach((obj) => {
|
||||
const newObjId = generateId("obj_");
|
||||
obj.id = newObjId;
|
||||
obj.layerId = this.newLayerId;
|
||||
obj.layerName = this.newLayer.name;
|
||||
if (!data.isCut) {
|
||||
const offset = 30;
|
||||
if (obj.left !== undefined) obj.left += offset;
|
||||
if (obj.top !== undefined) obj.top += offset;
|
||||
}
|
||||
this.canvas.add(obj);
|
||||
this.newLayer.fabricObjects.push(obj.toObject(["id", "layerId", "layerName"]));
|
||||
this.createdObjects.push(obj);
|
||||
});
|
||||
this._onObjectsRestored(data);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("恢复对象时发生错误:", error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象恢复完成后的处理
|
||||
* @param {Object} data 剪贴板数据
|
||||
* @private
|
||||
*/
|
||||
_onObjectsRestored(data) {
|
||||
// 更新对象交互性
|
||||
this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||
|
||||
// 重新排列对象
|
||||
this.layerManager?._rearrangeObjects?.();
|
||||
|
||||
// 判断如果是剪切操作,粘贴完后需要删除剪贴板数据
|
||||
if (data.isCut && this.layerManager) {
|
||||
this.layerManager.clipboardData = null;
|
||||
console.log(`已粘贴图层:${this.newLayer.name}(剪切)`);
|
||||
} else {
|
||||
console.log(`已粘贴图层:${this.newLayer.name}(复制)`);
|
||||
}
|
||||
|
||||
// 重新渲染画布
|
||||
if (this.canvas) {
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
undo() {
|
||||
async undo() {
|
||||
if (!this.newLayer || !this.newLayerId) return;
|
||||
|
||||
// 从图层列表删除该图层
|
||||
const index = this.layers.value.findIndex((layer) => layer.id === this.newLayerId);
|
||||
if (index !== -1) {
|
||||
this.layers.value.splice(index, 1);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.newLayerId);
|
||||
if (!layer) {
|
||||
console.error(`图层 ${this.newLayerId} 不存在, 无法撤销`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 恢复原活动图层
|
||||
this.activeLayerId.value = this.oldActiveLayerId;
|
||||
if (parent) {
|
||||
// 如果是子图层,直接从父图层中删除
|
||||
const index = parent.children.findIndex((child) => child.id === this.newLayerId);
|
||||
if (index !== -1) {
|
||||
parent.children.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
// 如果是顶级图层,直接从图层列表中删除
|
||||
const index = this.layers.value.findIndex((l) => l.id === this.newLayerId);
|
||||
if (index !== -1) {
|
||||
this.layers.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 从画布移除所有创建的对象
|
||||
this.createdObjects.forEach((obj) => {
|
||||
this.canvas.remove(obj);
|
||||
this.createdObjects?.forEach((obj) => {
|
||||
removeCanvasObjectByObject(this.canvas, obj);
|
||||
});
|
||||
|
||||
// 如果图层有其他fabric对象,也要移除
|
||||
if (this.newLayer.fabricObjects && this.newLayer.fabricObjects.length > 0) {
|
||||
this.newLayer.fabricObjects.forEach((obj) => {
|
||||
if (!this.createdObjects.includes(obj)) {
|
||||
this.canvas.remove(obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.activeLayerId.value = this.oldActiveLayerId;
|
||||
|
||||
// 如果是剪切操作的撤销,需要恢复剪贴板数据
|
||||
if (this.clipboardData && this.clipboardData.isCut && this.layerManager) {
|
||||
this.layerManager.clipboardData = this.clipboardData;
|
||||
}
|
||||
// 取消激活对象
|
||||
|
||||
// 重新渲染画布
|
||||
if (this.canvas) {
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
// 更新对象交互性
|
||||
if (
|
||||
this.layerManager &&
|
||||
typeof this.layerManager.updateLayersObjectsInteractivity === "function"
|
||||
) {
|
||||
this.layerManager.updateLayersObjectsInteractivity();
|
||||
}
|
||||
this.canvas.discardActiveObject(); // 取消当前活动对象
|
||||
// 重新排序
|
||||
await this.layerManager?.sortLayersWithTool?.();
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
@@ -375,6 +478,7 @@ export class PasteLayerCommand extends Command {
|
||||
layerName: this.newLayer?.name || "未知图层",
|
||||
layerId: this.newLayerId,
|
||||
objectCount: this.createdObjects.length,
|
||||
isGroupLayer: this.isGroupLayer,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { findObjectById } from "../utils/helper";
|
||||
import { findLayerRecursively } from "../utils/layerHelper";
|
||||
import { restoreFabricObject } from "../utils/objectHelper";
|
||||
import { Command } from "./Command";
|
||||
|
||||
/**
|
||||
@@ -18,6 +21,18 @@ export class TransformCommand extends Command {
|
||||
this.initialState = options.initialState || null;
|
||||
this.finalState = options.finalState || null;
|
||||
this.objectType = options.objectType || "object";
|
||||
this.layerManager = options.layerManager;
|
||||
this.layers = options.layers || null;
|
||||
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
|
||||
|
||||
const targetObject = findObjectById(this.canvas, this.objectId)?.object || null;
|
||||
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, targetObject?.layerId);
|
||||
|
||||
this.layer = layer;
|
||||
this.parent = parent;
|
||||
|
||||
this.isSginleObject = parent?.id === this.lastSelectLayerId?.value; // 是否需要记录遮罩的变换位置 如果是组图层且组下只有一个图层有对象 且 用户最后点击的是父图层 则记录遮罩变更
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +40,7 @@ export class TransformCommand extends Command {
|
||||
* 如果是首次执行,记录初始和最终状态
|
||||
* 如果是重做,应用最终状态
|
||||
*/
|
||||
execute() {
|
||||
async execute() {
|
||||
if (!this.finalState) {
|
||||
console.warn("没有最终状态可应用");
|
||||
return false;
|
||||
@@ -39,7 +54,7 @@ export class TransformCommand extends Command {
|
||||
}
|
||||
|
||||
// 应用最终变换状态
|
||||
this._applyTransform(targetObject, this.finalState);
|
||||
await this._applyTransform(targetObject, this.finalState);
|
||||
|
||||
// 触发画布更新
|
||||
this.canvas.renderAll();
|
||||
@@ -51,7 +66,7 @@ export class TransformCommand extends Command {
|
||||
* 撤销命令
|
||||
* 应用初始状态
|
||||
*/
|
||||
undo() {
|
||||
async undo() {
|
||||
if (!this.initialState) {
|
||||
console.warn("没有初始状态可恢复");
|
||||
return false;
|
||||
@@ -65,7 +80,7 @@ export class TransformCommand extends Command {
|
||||
}
|
||||
|
||||
// 应用初始变换状态
|
||||
this._applyTransform(targetObject, this.initialState);
|
||||
await this._applyTransform(targetObject, this.initialState);
|
||||
|
||||
// 触发画布更新
|
||||
this.canvas.renderAll();
|
||||
@@ -86,8 +101,41 @@ export class TransformCommand extends Command {
|
||||
* 应用变换状态到对象
|
||||
* @private
|
||||
*/
|
||||
_applyTransform(object, transformState) {
|
||||
async _applyTransform(object, transformState) {
|
||||
if (!object || !transformState) return;
|
||||
// 变换遮罩层 - 如果当前图层是组图层,且有遮罩并且组下只有一个图层有对象 则应用遮罩转换
|
||||
if (
|
||||
this.parent &&
|
||||
this.parent?.clippingMask &&
|
||||
this.parent?.children?.length === 1 &&
|
||||
this.isSginleObject
|
||||
) {
|
||||
// 计算对象的变换位置
|
||||
const moveLeft = object.left - transformState.left; // 计算移动的水平距离
|
||||
const moveTop = object.top - transformState.top; // 计算移动的垂直距离
|
||||
|
||||
this.parent.clippingMask.left -= moveLeft;
|
||||
this.parent.clippingMask.top -= moveTop;
|
||||
|
||||
// 重新创建遮罩对象
|
||||
const clippingMaskFabricObject = await restoreFabricObject(
|
||||
this.parent.clippingMask,
|
||||
this.canvas
|
||||
);
|
||||
|
||||
if (clippingMaskFabricObject) {
|
||||
clippingMaskFabricObject.clipPath = null;
|
||||
clippingMaskFabricObject.set({
|
||||
absolutePositioned: true,
|
||||
});
|
||||
|
||||
clippingMaskFabricObject.dirty = true;
|
||||
clippingMaskFabricObject.setCoords();
|
||||
|
||||
const clippingMask = this.parent.clippingMask;
|
||||
object.clipPath = clippingMask;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用变换属性,只设置真正变化的值
|
||||
Object.entries(transformState).forEach(([key, value]) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { findLayerRecursively } from "../utils/layerHelper";
|
||||
import { findLayerRecursively, isGroupLayer } from "../utils/layerHelper";
|
||||
import { restoreFabricObject } from "../utils/objectHelper";
|
||||
import { Command } from "./Command";
|
||||
|
||||
@@ -23,6 +23,8 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
this.maskInitialTop = options.maskInitialTop || 0;
|
||||
this.isExecuteRealtime = options.isExecuteRealtime || false;
|
||||
this.activeSelection = this.canvas.getActiveObject() || {};
|
||||
this.isSginleObject = options.isSginleObject || false; // 组内是否单个对象移动
|
||||
this.target = options.target || null; // 当前操作的目标对象
|
||||
|
||||
this.isFristExecute = true;
|
||||
|
||||
@@ -42,17 +44,18 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
};
|
||||
|
||||
// 收集当前选择的所有对象位置
|
||||
this.originalObjectsPostion = this.activeSelection.getObjects().map((obj) => {
|
||||
return {
|
||||
left: obj.left || 0,
|
||||
top: obj.top || 0,
|
||||
id: obj.id,
|
||||
};
|
||||
});
|
||||
this.originalObjectsPostion =
|
||||
this.activeSelection.getObjects?.()?.map((obj) => {
|
||||
return {
|
||||
left: obj.left || 0,
|
||||
top: obj.top || 0,
|
||||
id: obj.id,
|
||||
};
|
||||
}) || this.target?.getBoundingRect?.(true, true);
|
||||
|
||||
this.originalSelectionPosition = {
|
||||
left: this.activeSelection.left || 0,
|
||||
top: this.activeSelection.top || 0,
|
||||
left: this.isSginleObject ? this.target.left : this.activeSelection.left || 0,
|
||||
top: this.isSginleObject ? this.target.top : this.activeSelection.top || 0,
|
||||
};
|
||||
|
||||
console.log(
|
||||
@@ -147,6 +150,10 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
// 更新所有使用此遮罩的子图层对象(不需要等待)
|
||||
this._updateChildObjectsClipPath(layer);
|
||||
|
||||
this.target.set({
|
||||
isSginleObject: this.isSginleObject,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("实时更新组图层遮罩位置失败:", error);
|
||||
@@ -217,21 +224,41 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
}
|
||||
|
||||
if (isUndo) {
|
||||
this.activeSelection.set({
|
||||
left: this.originalSelectionPosition.left - this.deltaX,
|
||||
top: this.originalSelectionPosition.top - this.deltaY,
|
||||
});
|
||||
this.activeSelection.dirty = true;
|
||||
this.activeSelection.setCoords();
|
||||
if (this.isSginleObject) {
|
||||
// 如果是单个对象移动,直接更新目标对象位置
|
||||
// this.target.set({
|
||||
// left: this.target.left - this.deltaX,
|
||||
// top: this.target.top - this.deltaY,
|
||||
// });
|
||||
// this.target.dirty = true;
|
||||
// this.target.setCoords();
|
||||
} else {
|
||||
this.activeSelection.set({
|
||||
left: this.originalSelectionPosition.left - this.deltaX,
|
||||
top: this.originalSelectionPosition.top - this.deltaY,
|
||||
});
|
||||
this.activeSelection.dirty = true;
|
||||
this.activeSelection.setCoords();
|
||||
}
|
||||
}
|
||||
|
||||
if (isExecute && !this.isFristExecute) {
|
||||
this.activeSelection.set({
|
||||
left: this.activeSelection.left + this.deltaX,
|
||||
top: this.activeSelection.top + this.deltaY,
|
||||
});
|
||||
this.activeSelection.dirty = true;
|
||||
this.activeSelection.setCoords();
|
||||
if (this.isSginleObject) {
|
||||
// 如果是单个对象移动,直接更新目标对象位置
|
||||
// this.target.set({
|
||||
// left: this.target.left + this.deltaX,
|
||||
// top: this.target.top + this.deltaY,
|
||||
// });
|
||||
// this.target.dirty = true;
|
||||
// this.target.setCoords();
|
||||
} else {
|
||||
this.activeSelection.set({
|
||||
left: this.activeSelection.left + this.deltaX,
|
||||
top: this.activeSelection.top + this.deltaY,
|
||||
});
|
||||
this.activeSelection.dirty = true;
|
||||
this.activeSelection.setCoords();
|
||||
}
|
||||
}
|
||||
|
||||
// 触发画布重新渲染
|
||||
|
||||
@@ -40,6 +40,7 @@ const emit = defineEmits([
|
||||
]);
|
||||
|
||||
const layers = inject("layers", []);
|
||||
const lastSelectLayerId = inject("lastSelectLayerId", null);
|
||||
|
||||
const layerManager = inject("layerManager", {});
|
||||
|
||||
@@ -573,6 +574,7 @@ function handleLayerClick(layer, event) {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
|
||||
toggleLayerSelection(layer, event);
|
||||
} else {
|
||||
lastSelectLayerId.value = layer.id; // 更新最后选中的图层ID
|
||||
// 普通点击:进入单选模式
|
||||
// selectedLayerIds.value = [layer.id];
|
||||
// isMultiSelectMode.value = false;
|
||||
|
||||
@@ -33,7 +33,7 @@ import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑
|
||||
import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板
|
||||
import { LayerType, OperationType } from "./utils/layerHelper.js";
|
||||
import { ToolManager } from "./managers/toolManager.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
// import { fabric } from "fabric-with-all";
|
||||
import { uploadImageAndCreateLayer, loadImageUrlToLayer, loadImage } from "./utils/imageHelper.js";
|
||||
// import MinimapPanel from "./components/MinimapPanel.vue";
|
||||
const KeyboardShortcutHelp = defineAsyncComponent(
|
||||
@@ -124,6 +124,7 @@ provide("isShowLayerPanel", isShowLayerPanel); // 提供红绿图模式状态给
|
||||
const layers = ref([]);
|
||||
const activeLayerId = ref(null);
|
||||
const activeElementId = ref(null);
|
||||
const lastSelectLayerId = ref(null); // 最后选择的图层ID
|
||||
|
||||
// 当前选择的工具
|
||||
const activeTool = ref(CanvasConfig.defaultTool); // 默认工具
|
||||
@@ -199,6 +200,7 @@ onMounted(async () => {
|
||||
// backgroundColor: canvasColor.value,
|
||||
currentZoom,
|
||||
layers,
|
||||
lastSelectLayerId,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
canvasColor,
|
||||
@@ -275,6 +277,8 @@ onMounted(async () => {
|
||||
provide("liquifyManager", () => liquifyManager); // 提供液化管理器
|
||||
provide("texturePresetManager", texturePresetManager); // 提供纹理预设管理器
|
||||
provide("layers", layers); // 提供图层数据
|
||||
provide("lastSelectLayerId", lastSelectLayerId); // 提供最后选择的图层ID
|
||||
|
||||
// 初始化网格设置
|
||||
// toggleGridVisibility(gridEnabled.value);
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export class CanvasManager {
|
||||
this.maskLayer = null; // 添加蒙层引用
|
||||
this.editorMode = CanvasConfig.defaultTool; // 默认编辑器模式
|
||||
this.layers = options.layers || null; // 图层引用
|
||||
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
|
||||
this.canvasWidth = options.canvasWidth || this.width; // 画布宽度
|
||||
this.canvasHeight = options.canvasHeight || this.height; // 画布高度
|
||||
this.canvasColor = options.canvasColor || "#ffffff"; // 画布背景颜色
|
||||
@@ -282,6 +283,7 @@ export class CanvasManager {
|
||||
activeElementId: activeElementId,
|
||||
layerManager: layerManager,
|
||||
layers: this.layers,
|
||||
lastSelectLayerId: this.lastSelectLayerId,
|
||||
});
|
||||
|
||||
// 设置动画交互效果
|
||||
|
||||
@@ -971,7 +971,6 @@ export class LayerManager {
|
||||
// allObjects.push(object);
|
||||
// }
|
||||
// }
|
||||
|
||||
if (allObjects.length) {
|
||||
// 切换到选择模式
|
||||
this?.toolManager?.setTool(OperationType.SELECT);
|
||||
@@ -1525,13 +1524,32 @@ export class LayerManager {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前图层序列化对象
|
||||
* @param {Object} layer 图层对象
|
||||
* @returns {Array} 图层中的序列化对象列表
|
||||
*/
|
||||
getCurrLayerSerializedObjects(layer) {
|
||||
if (!layer || layer?.fabricObjects?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
// 序列化图层中的对象
|
||||
return layer.fabricObjects
|
||||
.map((obj) => {
|
||||
const { object } = findObjectById(this.canvas, obj.id);
|
||||
if (object) return object.toObject(["id", "layerId", "layerName"]);
|
||||
return false;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制图层数据到剪贴板
|
||||
* @param {string} layerId 要复制的图层ID
|
||||
* @returns {Object} 复制的图层数据
|
||||
*/
|
||||
copyLayer(layerId) {
|
||||
const layer = this.layers.value.find((l) => l.id === layerId);
|
||||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||||
if (!layer) {
|
||||
console.error(`图层 ${layerId} 不存在`);
|
||||
return null;
|
||||
@@ -1548,11 +1566,18 @@ export class LayerManager {
|
||||
|
||||
// 序列化fabricObjects数组
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layerCopy.serializedObjects = layer.fabricObjects
|
||||
.map((obj) =>
|
||||
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
||||
)
|
||||
.filter(Boolean);
|
||||
layerCopy.serializedObjects = this.getCurrLayerSerializedObjects(layer);
|
||||
}
|
||||
|
||||
// 处理子图层
|
||||
if (layer?.children?.length) {
|
||||
layerCopy.children = layer.children.map((child) => {
|
||||
const childCopy = JSON.parse(JSON.stringify(child));
|
||||
if (child.fabricObjects && child.fabricObjects.length > 0) {
|
||||
childCopy.serializedObjects = this.getCurrLayerSerializedObjects(child);
|
||||
}
|
||||
return childCopy;
|
||||
});
|
||||
}
|
||||
|
||||
// 存储到剪贴板
|
||||
@@ -1653,7 +1678,7 @@ export class LayerManager {
|
||||
* 粘贴图层
|
||||
* @returns {string} 新创建的图层ID
|
||||
*/
|
||||
pasteLayer() {
|
||||
async pasteLayer() {
|
||||
if (!this.clipboardData) {
|
||||
console.error("剪贴板中没有图层数据");
|
||||
return null;
|
||||
@@ -1668,8 +1693,20 @@ export class LayerManager {
|
||||
layerManager: this, // 传递LayerManager实例
|
||||
});
|
||||
|
||||
// 设置命令为可撤销
|
||||
command.undoable = true;
|
||||
if (this.commandManager) {
|
||||
// 使用命令管理器执行命令
|
||||
const result = await this.commandManager.execute(command);
|
||||
this.clipboardData = null; // 清空剪贴板数据 复制一次
|
||||
// 执行命令
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = await command.execute();
|
||||
this.clipboardData = null; // 清空剪贴板数据 复制一次就清空,避免重复粘贴 出现 错误后也清空 总之就是清空 不用给自己找麻烦
|
||||
// 执行命令
|
||||
return command.execute();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2893,10 +2930,11 @@ export class LayerManager {
|
||||
let lastUpdateTime = 0;
|
||||
let hasMoved = false; // 追踪是否实际发生了移动
|
||||
const UPDATE_THRESHOLD = 32; // 约60fps
|
||||
|
||||
// 移动开始事件处理
|
||||
const handleMovingStart = (e) => {
|
||||
if (e.target === activeSelection) {
|
||||
// 判断活动对象是否只有一个
|
||||
const isSginleObject = e.target === activeSelection?._objects?.[0];
|
||||
if (e.target === activeSelection || isSginleObject) {
|
||||
hasMoved = false; // 重置移动状态
|
||||
console.log("🎯 开始移动组选择对象");
|
||||
// 记录遮罩初始位置
|
||||
@@ -2905,8 +2943,8 @@ export class LayerManager {
|
||||
`${layer.clippingMask.left || 0}, ${layer.clippingMask.top || 0}`
|
||||
);
|
||||
// 记录初始位置
|
||||
initialLeft = activeSelection.left;
|
||||
initialTop = activeSelection.top;
|
||||
initialLeft = isSginleObject ? e.target.left : activeSelection.left;
|
||||
initialTop = isSginleObject ? e.target.top : activeSelection.top;
|
||||
|
||||
maskInitialLeft = layer.clippingMask.left || 0;
|
||||
maskInitialTop = layer.clippingMask.top || 0;
|
||||
@@ -2914,8 +2952,10 @@ export class LayerManager {
|
||||
};
|
||||
// 移动中事件处理函数(带节流)
|
||||
const handleMoving = (e) => {
|
||||
// 判断活动对象是否只有一个
|
||||
const isSginleObject = e.target === activeSelection?._objects?.[0];
|
||||
const target = e.target;
|
||||
if (target === activeSelection) {
|
||||
if (target === activeSelection || isSginleObject) {
|
||||
hasMoved = true; // 标记发生了移动
|
||||
const now = Date.now();
|
||||
|
||||
@@ -2949,6 +2989,8 @@ export class LayerManager {
|
||||
maskInitialLeft: maskInitialLeft,
|
||||
maskInitialTop: maskInitialTop,
|
||||
isExecuteRealtime: true,
|
||||
isSginleObject,
|
||||
target,
|
||||
});
|
||||
|
||||
// 执行实时更新
|
||||
@@ -2963,7 +3005,15 @@ export class LayerManager {
|
||||
// 修改事件处理函数 - 使用 object:modified 替代 object:moved
|
||||
const handleModified = (e) => {
|
||||
const target = e.target;
|
||||
if (target === activeSelection && hasMoved) {
|
||||
// 判断活动对象是否只有一个
|
||||
const isSginleObject = e.target === activeSelection?._objects?.[0];
|
||||
if (isSginleObject) {
|
||||
// 如果是单个对象,不处理
|
||||
console.log("🚫 单个对象不处理移动完成");
|
||||
hasMoved = false; // 重置移动状态
|
||||
return;
|
||||
}
|
||||
if ((target === activeSelection || isSginleObject) && hasMoved) {
|
||||
console.log("✅ 组选择对象移动完成");
|
||||
|
||||
// 计算最终移动距离
|
||||
@@ -2982,8 +3032,12 @@ export class LayerManager {
|
||||
maskInitialLeft: maskInitialLeft,
|
||||
maskInitialTop: maskInitialTop,
|
||||
activeSelection,
|
||||
isSginleObject,
|
||||
target,
|
||||
});
|
||||
|
||||
command.undoable = isSginleObject ? false : true; // 设置为可撤销的命令
|
||||
|
||||
// 执行可撤销的命令
|
||||
if (this.commandManager) {
|
||||
this.commandManager.execute(command);
|
||||
|
||||
@@ -13,6 +13,7 @@ export class CanvasEventManager {
|
||||
this.activeElementId = options.activeElementId || { value: null };
|
||||
this.layerManager = options.layerManager || null;
|
||||
this.layers = options.layers || null;
|
||||
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
|
||||
|
||||
// 事件处理的内部状态 - 优化设备检测
|
||||
this.deviceInfo = this._detectDeviceType();
|
||||
@@ -410,6 +411,9 @@ export class CanvasEventManager {
|
||||
finalState: TransformCommand.captureTransformState(activeObj),
|
||||
objectType: activeObj.type,
|
||||
name: `变换 ${activeObj.type || "对象"}`,
|
||||
layerManager: this.layerManager,
|
||||
layers: this.layers,
|
||||
lastSelectLayerId: this.lastSelectLayerId,
|
||||
});
|
||||
|
||||
// 执行并将命令添加到历史栈
|
||||
|
||||
Reference in New Issue
Block a user