合并画布代码
This commit is contained in:
820
src/component/Canvas/CanvasEditor/utils/LayerSort.js
Normal file
820
src/component/Canvas/CanvasEditor/utils/LayerSort.js
Normal file
@@ -0,0 +1,820 @@
|
||||
import { ReorderChildLayersCommand } from "../commands/LayerCommands";
|
||||
import { optimizeCanvasRendering } from "./helper";
|
||||
import { findLayerRecursively, LayerType } from "./layerHelper";
|
||||
|
||||
/**
|
||||
* 图层排序工具类
|
||||
* 提供图层排序、重新排列画布对象等功能
|
||||
* 基于fabric.js 5.3.1版本开发
|
||||
*/
|
||||
export class LayerSort {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} canvas fabric.js画布实例
|
||||
* @param {Object} layers 图层数组响应式引用
|
||||
*/
|
||||
constructor(canvas, layers, options = {}) {
|
||||
this.canvas = canvas;
|
||||
this.layers = layers;
|
||||
this.commandManager = options.commandManager || null; // 命令管理器(可选)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新排列画布上的对象以匹配图层顺序
|
||||
* 使用 fabric.js 的 moveTo 方法直接调整对象层级,无需清空画布
|
||||
*/
|
||||
async rearrangeObjects() {
|
||||
if (!this.canvas) return;
|
||||
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
if (canvasObjects.length === 0) return;
|
||||
|
||||
// 使用画布渲染优化
|
||||
await optimizeCanvasRendering(this.canvas, () => {
|
||||
// 计算每个对象应该在的 z-index 位置
|
||||
const objectZIndexMap = this.calculateObjectZIndexes();
|
||||
|
||||
// 按照新的 z-index 排序对象
|
||||
const sortedObjects = canvasObjects
|
||||
.map((obj) => ({
|
||||
object: obj,
|
||||
targetZIndex: objectZIndexMap.get(obj.id) ?? -1,
|
||||
}))
|
||||
.filter((item) => item.targetZIndex >= 0) // 过滤掉无效对象
|
||||
.sort((a, b) => a.targetZIndex - b.targetZIndex);
|
||||
|
||||
// 使用 fabric.js 的 moveTo 方法重新排序
|
||||
sortedObjects.forEach((item, index) => {
|
||||
const currentIndex = this.canvas.getObjects().indexOf(item.object);
|
||||
if (currentIndex !== index && currentIndex !== -1) {
|
||||
// 将对象移动到正确的位置
|
||||
this.canvas.moveTo(item.object, index);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算每个对象在画布中应该的 z-index
|
||||
* 考虑图层类型、组结构和子图层
|
||||
* @returns {Map} 对象ID到z-index的映射
|
||||
*/
|
||||
calculateObjectZIndexes() {
|
||||
const zIndexMap = new Map();
|
||||
let currentZIndex = 0;
|
||||
|
||||
// 按照图层在数组中的顺序从后往前遍历(数组末尾 = 画布底层)
|
||||
for (let i = this.layers.value.length - 1; i >= 0; i--) {
|
||||
const layer = this.layers.value[i];
|
||||
|
||||
// 跳过不可见图层
|
||||
if (!layer.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理不同类型的图层
|
||||
if (layer.isBackground && layer.fabricObject) {
|
||||
// 背景图层对象放在最底层
|
||||
zIndexMap.set(layer.fabricObject.id, currentZIndex++);
|
||||
} else if (layer.isFixed && layer.fabricObjects) {
|
||||
// 固定图层对象
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
if (obj?.id) {
|
||||
zIndexMap.set(obj.id, currentZIndex++);
|
||||
}
|
||||
});
|
||||
} else if (!layer.isBackground && !layer.isFixed) {
|
||||
// 普通图层
|
||||
currentZIndex = this.processLayerObjects(
|
||||
layer,
|
||||
currentZIndex,
|
||||
zIndexMap
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return zIndexMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图层对象,包括组和子图层的情况
|
||||
* @param {Object} layer 图层对象
|
||||
* @param {number} currentZIndex 当前z-index
|
||||
* @param {Map} zIndexMap z-index映射表
|
||||
* @returns {number} 更新后的z-index
|
||||
*/
|
||||
processLayerObjects(layer, currentZIndex, zIndexMap) {
|
||||
// 检查是否有子图层(组图层)
|
||||
if (layer.children?.length > 0) {
|
||||
// 处理每个子图层
|
||||
// 按照图层在数组中的顺序从后往前遍历(数组末尾 = 画布底层)
|
||||
for (let i = layer.children.length - 1; i >= 0; i--) {
|
||||
const childLayer = layer.children[i];
|
||||
|
||||
// 跳过不可见图层
|
||||
if (!childLayer.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = childLayer.fabricObjects.length - 1; j >= 0; j--) {
|
||||
const obj = childLayer.fabricObjects[j];
|
||||
if (obj?.id) {
|
||||
zIndexMap.set(obj.id, currentZIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理图层本身的对象
|
||||
if (Array.isArray(layer.fabricObjects)) {
|
||||
for (let j = layer.fabricObjects.length - 1; j >= 0; j--) {
|
||||
const obj = layer.fabricObjects[j];
|
||||
if (obj?.id) {
|
||||
zIndexMap.set(obj.id, currentZIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentZIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定图层的子图层,按照正确顺序排列
|
||||
* @param {string} parentLayerId 父图层ID
|
||||
* @returns {Array} 子图层数组
|
||||
*/
|
||||
getChildLayersInOrder(parentLayerId) {
|
||||
// 获取所有子图层
|
||||
const childLayers =
|
||||
this.layers.value.filter((layer) => layer.id === parentLayerId)
|
||||
?.children || [];
|
||||
|
||||
return childLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取图层
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {Object|null} 图层对象或null
|
||||
*/
|
||||
getLayerById(layerId) {
|
||||
if (!layerId || !this.layers.value) return null;
|
||||
return this.layers.value.find((layer) => layer.id === layerId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量重新排列对象(异步版本,适用于大量对象)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async rearrangeObjectsAsync() {
|
||||
if (!this.canvas) return;
|
||||
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
if (canvasObjects.length === 0) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// 使用 requestAnimationFrame 进行异步处理
|
||||
requestAnimationFrame(() => {
|
||||
this.canvas.renderOnAddRemove = false;
|
||||
|
||||
try {
|
||||
const objectZIndexMap = this.calculateObjectZIndexes();
|
||||
|
||||
const sortedObjects = canvasObjects
|
||||
.map((obj) => ({
|
||||
object: obj,
|
||||
targetZIndex: objectZIndexMap.get(obj.id) ?? -1,
|
||||
}))
|
||||
.filter((item) => item.targetZIndex >= 0)
|
||||
.sort((a, b) => a.targetZIndex - b.targetZIndex);
|
||||
|
||||
// 分批处理,避免一次性处理太多对象
|
||||
const batchSize = LayerSortConstants.BATCH_SIZE;
|
||||
let currentBatch = 0;
|
||||
|
||||
const processBatch = () => {
|
||||
const start = currentBatch * batchSize;
|
||||
const end = Math.min(start + batchSize, sortedObjects.length);
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
const item = sortedObjects[i];
|
||||
const currentIndex = this.canvas
|
||||
.getObjects()
|
||||
.indexOf(item.object);
|
||||
if (currentIndex !== i && currentIndex !== -1) {
|
||||
this.canvas.moveTo(item.object, i);
|
||||
}
|
||||
}
|
||||
|
||||
currentBatch++;
|
||||
|
||||
if (end < sortedObjects.length) {
|
||||
// 继续处理下一批
|
||||
requestAnimationFrame(processBatch);
|
||||
} else {
|
||||
// 所有批次处理完成
|
||||
this.canvas.renderOnAddRemove = true;
|
||||
this.canvas.renderAll();
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
processBatch();
|
||||
} catch (error) {
|
||||
this.canvas.renderOnAddRemove = true;
|
||||
this.canvas.renderAll();
|
||||
console.error("重新排列对象时出错:", error);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证对象顺序是否正确
|
||||
* @returns {boolean} 顺序是否正确
|
||||
*/
|
||||
validateObjectOrder() {
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
const objectZIndexMap = this.calculateObjectZIndexes();
|
||||
|
||||
for (let i = 0; i < canvasObjects.length - 1; i++) {
|
||||
const currentObj = canvasObjects[i];
|
||||
const nextObj = canvasObjects[i + 1];
|
||||
|
||||
const currentZIndex = objectZIndexMap.get(currentObj.id);
|
||||
const nextZIndex = objectZIndexMap.get(nextObj.id);
|
||||
|
||||
if (currentZIndex !== undefined && nextZIndex !== undefined) {
|
||||
if (currentZIndex > nextZIndex) {
|
||||
return false; // 顺序不正确
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true; // 顺序正确
|
||||
}
|
||||
|
||||
/**
|
||||
* 图层排序规则:背景图层 > 固定图层 > 普通图层
|
||||
* @param {Array} layers 图层数组
|
||||
* @returns {Array} 排序后的图层数组
|
||||
*/
|
||||
sortLayers(layers = null) {
|
||||
const targetLayers = layers ?? this.layers.value;
|
||||
|
||||
return [...targetLayers].sort((a, b) => {
|
||||
// 如果a是背景图层,它应该排在后面(最底层)
|
||||
if (a.isBackground) return 1;
|
||||
// 如果b是背景图层,它应该排在后面(最底层)
|
||||
if (b.isBackground) return -1;
|
||||
|
||||
// 如果a是固定图层而b不是固定图层,a应该排在后面(固定图层在普通图层下方)
|
||||
if (a.isFixed && !b.isFixed) return 1;
|
||||
// 如果b是固定图层而a不是固定图层,b应该排在后面(固定图层在普通图层下方)
|
||||
if (b.isFixed && !a.isFixed) return -1;
|
||||
|
||||
// 其他情况保持原有顺序
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层在排序后的正确插入位置
|
||||
* @param {Object} newLayer 新图层
|
||||
* @param {string} targetLayerId 目标图层ID(插入到该图层之前)
|
||||
* @returns {number} 插入位置索引
|
||||
*/
|
||||
getInsertIndex(newLayer, targetLayerId = null) {
|
||||
if (!targetLayerId) {
|
||||
// 如果没有指定目标图层,根据图层类型决定插入位置
|
||||
if (newLayer.isBackground) {
|
||||
return this.layers.value.length; // 背景图层插入到最后
|
||||
} else if (newLayer.isFixed) {
|
||||
// 固定图层插入到背景图层之前
|
||||
const bgIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
return bgIndex !== -1 ? bgIndex : this.layers.value.length;
|
||||
} else {
|
||||
// 普通图层插入到固定图层之前
|
||||
const fixedIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isFixed
|
||||
);
|
||||
return fixedIndex !== -1 ? fixedIndex : this.layers.value.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果指定了目标图层,插入到目标图层之前
|
||||
const targetIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === targetLayerId
|
||||
);
|
||||
return targetIndex !== -1 ? targetIndex : this.layers.value.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动图层到指定位置
|
||||
* @param {string} layerId 要移动的图层ID
|
||||
* @param {number} newIndex 新位置索引
|
||||
* @returns {boolean} 是否移动成功
|
||||
*/
|
||||
async moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
|
||||
// 检查父图层是否存在
|
||||
// const parentLayer = this.getLayerById(parentId);
|
||||
const { layer: childLayer, parent: parentLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
layerId,
|
||||
parentId
|
||||
);
|
||||
|
||||
if (!parentLayer) {
|
||||
console.warn(`父图层 ${parentId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取所有子图层
|
||||
const childLayers = parentLayer?.children || [];
|
||||
|
||||
// 检查索引有效性
|
||||
if (
|
||||
oldIndex < 0 ||
|
||||
newIndex < 0 ||
|
||||
oldIndex >= childLayers.length ||
|
||||
newIndex >= childLayers.length
|
||||
) {
|
||||
console.warn("子图层排序索引无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是同一位置
|
||||
if (oldIndex === newIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证图层ID
|
||||
const layer = childLayers[oldIndex];
|
||||
if (!layer || layer.id !== layerId) {
|
||||
console.warn("子图层ID与索引不匹配");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新父图层的children数组 - 执行命令
|
||||
const command = new ReorderChildLayersCommand({
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId,
|
||||
layers: this.layers,
|
||||
canvas: this.canvas,
|
||||
layerSort: this, // 传入当前实例
|
||||
});
|
||||
|
||||
if (this.commandManager) {
|
||||
await this.commandManager?.execute(command);
|
||||
} else {
|
||||
await command?.execute?.();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层的有效移动范围
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {Object} 包含最小和最大索引的对象
|
||||
*/
|
||||
getLayerMoveRange(layerId) {
|
||||
const layer = this.getLayerById(layerId);
|
||||
if (!layer || layer.isBackground || layer.isFixed) {
|
||||
return { minIndex: -1, maxIndex: -1 };
|
||||
}
|
||||
|
||||
// 普通图层只能在普通图层范围内移动
|
||||
const normalLayers = this.layers.value
|
||||
.map((layer, index) => ({ layer, index }))
|
||||
.filter((item) => !item.layer.isBackground && !item.layer.isFixed);
|
||||
|
||||
if (normalLayers.length === 0) {
|
||||
return { minIndex: -1, maxIndex: -1 };
|
||||
}
|
||||
|
||||
return {
|
||||
minIndex: normalLayers[0].index,
|
||||
maxIndex: normalLayers[normalLayers.length - 1].index,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 拖拽排序图层
|
||||
* @param {number} oldIndex 原索引
|
||||
* @param {number} newIndex 新索引
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
async reorderLayers(oldIndex, newIndex, layerId) {
|
||||
// 检查索引有效性
|
||||
if (
|
||||
oldIndex < 0 ||
|
||||
newIndex < 0 ||
|
||||
oldIndex >= this.layers.value.length ||
|
||||
newIndex >= this.layers.value.length
|
||||
) {
|
||||
console.warn("图层排序索引无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是同一位置
|
||||
if (oldIndex === newIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取要移动的图层
|
||||
const layer = this.layers.value[oldIndex];
|
||||
if (!layer || layer.id !== layerId) {
|
||||
console.warn("图层ID与索引不匹配");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是背景层或固定层(不允许排序)
|
||||
if (layer.isBackground || layer.isFixed) {
|
||||
console.warn("背景层和固定层不能参与排序");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查目标位置是否合法(不能移到背景层或固定层的位置)
|
||||
const targetLayer = this.layers.value[newIndex];
|
||||
if (targetLayer && (targetLayer.isBackground || targetLayer.isFixed)) {
|
||||
console.warn("不能移动到背景层或固定层的位置");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 执行排序
|
||||
const layersArray = [...this.layers.value];
|
||||
const [movedLayer] = layersArray.splice(oldIndex, 1);
|
||||
layersArray.splice(newIndex, 0, movedLayer);
|
||||
|
||||
this.layers.value = layersArray;
|
||||
|
||||
// 重新排列画布对象
|
||||
await this.rearrangeObjects();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子图层排序
|
||||
* @param {string} parentId 父图层ID
|
||||
* @param {number} oldIndex 原索引
|
||||
* @param {number} newIndex 新索引
|
||||
* @param {string} layerId 子图层ID
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
async reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
||||
// 检查父图层是否存在
|
||||
// const parentLayer = this.getLayerById(parentId);
|
||||
const { layer: childLayer, parent: parentLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
layerId,
|
||||
parentId
|
||||
);
|
||||
|
||||
if (!parentLayer) {
|
||||
console.warn(`父图层 ${parentId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取所有子图层
|
||||
const childLayers = parentLayer?.children || [];
|
||||
|
||||
// 检查索引有效性
|
||||
if (
|
||||
oldIndex < 0 ||
|
||||
newIndex < 0 ||
|
||||
oldIndex >= childLayers.length ||
|
||||
newIndex >= childLayers.length
|
||||
) {
|
||||
console.warn("子图层排序索引无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是同一位置
|
||||
if (oldIndex === newIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证图层ID
|
||||
const layer = childLayers[oldIndex];
|
||||
if (!layer || layer.id !== layerId) {
|
||||
console.warn("子图层ID与索引不匹配");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新父图层的children数组 - 执行命令
|
||||
const command = ReorderChildLayersCommand({
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId,
|
||||
layers: this.layers,
|
||||
canvas: this.canvas,
|
||||
layerSort: this, // 传入当前实例
|
||||
});
|
||||
|
||||
if (this.commandManager) {
|
||||
await this.commandManager?.execute(command);
|
||||
} else {
|
||||
await command?.execute?.();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能排序 - 根据对象类型和位置自动调整图层顺序
|
||||
* @param {Array} targetLayerIds 要排序的图层ID数组
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
async smartSort(targetLayerIds = null) {
|
||||
const layersToSort = targetLayerIds
|
||||
? this.layers.value.filter((layer) => targetLayerIds.includes(layer.id))
|
||||
: this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
|
||||
if (layersToSort.length <= 1) return true;
|
||||
|
||||
// 按照对象类型和位置进行智能排序
|
||||
layersToSort.sort((a, b) => {
|
||||
const aWeight = this.getLayerSortWeight(a);
|
||||
const bWeight = this.getLayerSortWeight(b);
|
||||
|
||||
if (aWeight !== bWeight) {
|
||||
return bWeight - aWeight; // 权重高的在上层
|
||||
}
|
||||
|
||||
// 权重相同时,按照Y坐标排序(Y值小的在上层)
|
||||
const aY = this.getLayerAverageY(a);
|
||||
const bY = this.getLayerAverageY(b);
|
||||
|
||||
return aY - bY;
|
||||
});
|
||||
|
||||
// 更新图层顺序
|
||||
const sortedLayerIds = layersToSort.map((layer) => layer.id);
|
||||
const otherLayers = this.layers.value.filter(
|
||||
(layer) => !sortedLayerIds.includes(layer.id)
|
||||
);
|
||||
|
||||
// 重新组织图层数组:保持背景层和固定层的位置
|
||||
const newLayers = [];
|
||||
|
||||
// 添加普通图层(已排序)
|
||||
newLayers.push(...layersToSort);
|
||||
|
||||
// 添加其他普通图层
|
||||
otherLayers.forEach((layer) => {
|
||||
if (!layer.isBackground && !layer.isFixed) {
|
||||
newLayers.push(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加固定图层
|
||||
otherLayers.forEach((layer) => {
|
||||
if (layer.isFixed) {
|
||||
newLayers.push(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加背景图层
|
||||
otherLayers.forEach((layer) => {
|
||||
if (layer.isBackground) {
|
||||
newLayers.push(layer);
|
||||
}
|
||||
});
|
||||
|
||||
this.layers.value = newLayers;
|
||||
await this.rearrangeObjects();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层的排序权重
|
||||
* @param {Object} layer 图层对象
|
||||
* @returns {number} 排序权重
|
||||
*/
|
||||
getLayerSortWeight(layer) {
|
||||
const weightMap = LayerSortConstants.LAYER_PRIORITY;
|
||||
|
||||
if (layer.isBackground) return weightMap[LayerType.BACKGROUND];
|
||||
if (layer.isFixed) return weightMap[LayerType.FIXED];
|
||||
if (layer.children?.length > 0) return weightMap[LayerType.GROUP];
|
||||
|
||||
// 根据对象类型调整权重
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
const firstObj = layer.fabricObjects[0];
|
||||
if (firstObj.type === "text") return weightMap[LayerType.NORMAL] + 10;
|
||||
if (firstObj.type === "image") return weightMap[LayerType.NORMAL] + 5;
|
||||
}
|
||||
|
||||
return weightMap[LayerType.NORMAL];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层对象的平均Y坐标
|
||||
* @param {Object} layer 图层对象
|
||||
* @returns {number} 平均Y坐标
|
||||
*/
|
||||
getLayerAverageY(layer) {
|
||||
let totalY = 0;
|
||||
let count = 0;
|
||||
|
||||
if (layer.fabricObject) {
|
||||
totalY += layer.fabricObject.top || 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
totalY += obj.top || 0;
|
||||
count++;
|
||||
});
|
||||
}
|
||||
|
||||
return count > 0 ? totalY / count : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化图层结构 - 清理空图层、合并相邻相似图层等
|
||||
* @returns {Object} 优化结果统计
|
||||
*/
|
||||
async optimizeLayerStructure() {
|
||||
const stats = {
|
||||
removedEmptyLayers: 0,
|
||||
mergedLayers: 0,
|
||||
reorderedLayers: 0,
|
||||
};
|
||||
|
||||
// 清理空图层
|
||||
const emptyLayers = this.layers.value.filter(
|
||||
(layer) =>
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed &&
|
||||
(!layer.fabricObjects || layer.fabricObjects.length === 0) &&
|
||||
(!layer.children || layer.children.length === 0)
|
||||
);
|
||||
|
||||
emptyLayers.forEach((layer) => {
|
||||
const index = this.layers.value.findIndex((l) => l.id === layer.id);
|
||||
if (index !== -1) {
|
||||
this.layers.value.splice(index, 1);
|
||||
stats.removedEmptyLayers++;
|
||||
}
|
||||
});
|
||||
|
||||
// 重新排序以确保正确的层级关系
|
||||
const wasReordered = this.sortLayers();
|
||||
if (wasReordered) {
|
||||
stats.reorderedLayers = this.layers.value.length;
|
||||
await this.rearrangeObjects();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图层排序工具实例
|
||||
* @param {Object} canvas fabric.js画布实例
|
||||
* @param {Object} layers 图层数组响应式引用
|
||||
* @returns {LayerSort} 图层排序工具实例
|
||||
*/
|
||||
export const createLayerSort = (canvas, layers, options = {}) => {
|
||||
return new LayerSort(canvas, layers, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* 图层排序相关的常量
|
||||
*/
|
||||
export const LayerSortConstants = {
|
||||
// 图层类型优先级(数值越大优先级越高,在画布中越靠上)
|
||||
LAYER_PRIORITY: {
|
||||
[LayerType.BACKGROUND]: 0,
|
||||
[LayerType.FIXED]: 1,
|
||||
[LayerType.NORMAL]: 2,
|
||||
[LayerType.GROUP]: 2,
|
||||
},
|
||||
|
||||
// 批处理大小
|
||||
BATCH_SIZE: 50,
|
||||
|
||||
// 性能阈值
|
||||
PERFORMANCE_THRESHOLD: {
|
||||
OBJECTS_COUNT: 100, // 超过此数量使用异步处理
|
||||
LAYERS_COUNT: 20, // 超过此数量使用分批处理
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 图层排序辅助函数
|
||||
*/
|
||||
export const LayerSortUtils = {
|
||||
/**
|
||||
* 检查是否需要异步处理
|
||||
* @param {number} objectsCount 对象数量
|
||||
* @param {number} layersCount 图层数量
|
||||
* @returns {boolean} 是否需要异步处理
|
||||
*/
|
||||
shouldUseAsyncProcessing(objectsCount, layersCount) {
|
||||
return (
|
||||
objectsCount > LayerSortConstants.PERFORMANCE_THRESHOLD.OBJECTS_COUNT ||
|
||||
layersCount > LayerSortConstants.PERFORMANCE_THRESHOLD.LAYERS_COUNT
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取图层的排序权重
|
||||
* @param {Object} layer 图层对象
|
||||
* @returns {number} 排序权重
|
||||
*/
|
||||
getLayerSortWeight(layer) {
|
||||
if (layer.isBackground)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
|
||||
if (layer.isFixed)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
|
||||
if (layer.children?.length > 0)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.NORMAL];
|
||||
},
|
||||
|
||||
/**
|
||||
* 比较两个图层的排序优先级
|
||||
* @param {Object} layerA 图层A
|
||||
* @param {Object} layerB 图层B
|
||||
* @returns {number} 比较结果
|
||||
*/
|
||||
compareLayerPriority(layerA, layerB) {
|
||||
const weightA = this.getLayerSortWeight(layerA);
|
||||
const weightB = this.getLayerSortWeight(layerB);
|
||||
return weightB - weightA; // 权重高的排在前面(画布上层)
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 图层排序混入方法 - 用于在 LayerManager 中使用
|
||||
*/
|
||||
export const LayerSortMixin = {
|
||||
/**
|
||||
* 初始化图层排序工具
|
||||
*/
|
||||
initLayerSort() {
|
||||
this.layerSort = new LayerSort(this.canvas, this.layers);
|
||||
},
|
||||
|
||||
/**
|
||||
* 重新排列画布对象
|
||||
*/
|
||||
rearrangeCanvasObjects() {
|
||||
if (this.layerSort) {
|
||||
// 检查是否需要异步处理
|
||||
const objectsCount = this.canvas?.getObjects()?.length || 0;
|
||||
const layersCount = this.layers?.value?.length || 0;
|
||||
|
||||
if (LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount)) {
|
||||
return this.layerSort.rearrangeObjectsAsync();
|
||||
} else {
|
||||
this.layerSort.rearrangeObjects();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 排序图层
|
||||
*/
|
||||
sortLayersWithTool() {
|
||||
if (this.layerSort) {
|
||||
this.layers.value = this.layerSort.sortLayers();
|
||||
return this.layerSort.rearrangeObjects();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 智能排序图层
|
||||
*/
|
||||
smartSortLayers(targetLayerIds = null) {
|
||||
if (this.layerSort) {
|
||||
return this.layerSort.smartSort(targetLayerIds);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 优化图层结构
|
||||
*/
|
||||
optimizeLayerStructure() {
|
||||
if (this.layerSort) {
|
||||
return this.layerSort.optimizeLayerStructure();
|
||||
}
|
||||
return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
//import { fabric } from "fabric-with-all";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { canvasConfig } from "../config/canvasConfig";
|
||||
|
||||
/**
|
||||
|
||||
@@ -434,25 +434,324 @@ export function createCancellablePromise(executor) {
|
||||
};
|
||||
}
|
||||
|
||||
// 导出所有工具函数
|
||||
export default {
|
||||
deepCompare,
|
||||
deepClone,
|
||||
applyDiff,
|
||||
throttle,
|
||||
debounce,
|
||||
generateId,
|
||||
formatFileSize,
|
||||
formatDuration,
|
||||
isValidCommand,
|
||||
isPromise,
|
||||
safeJSONParse,
|
||||
safeJSONStringify,
|
||||
getObjectDepth,
|
||||
getObjectSize,
|
||||
checkBrowserSupport,
|
||||
delay,
|
||||
retry,
|
||||
batchProcess,
|
||||
createCancellablePromise,
|
||||
/**
|
||||
* 增强版检查对象是否在画布中(包括组内对象)
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {fabric.Object} targetObj 目标对象
|
||||
* @returns {Object} { flag: boolean, object: fabric.Object, parent: fabric.Group|null }
|
||||
*/
|
||||
export function objectIsInCanvas(canvas, targetObj) {
|
||||
if (!canvas || !targetObj) {
|
||||
return { flag: false, object: null, parent: null };
|
||||
}
|
||||
|
||||
const targetId = targetObj.id;
|
||||
if (!targetId) {
|
||||
return { flag: false, object: null, parent: null };
|
||||
}
|
||||
|
||||
// 首先检查顶层对象
|
||||
const topLevelObjects = canvas.getObjects();
|
||||
|
||||
// 直接在顶层查找
|
||||
const directMatch = topLevelObjects.find((obj) => obj.id === targetId);
|
||||
if (directMatch) {
|
||||
return { flag: true, object: directMatch, parent: null };
|
||||
}
|
||||
|
||||
// 递归检查组内对象
|
||||
for (const obj of topLevelObjects) {
|
||||
if (obj.type === "group") {
|
||||
const result = findObjectInGroup(obj, targetId);
|
||||
if (result.found) {
|
||||
return { flag: true, object: result.object, parent: obj };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { flag: false, object: null, parent: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* 在组中递归查找对象
|
||||
* @param {fabric.Group} group 组对象
|
||||
* @param {string} targetId 目标对象ID
|
||||
* @returns {Object} { found: boolean, object: fabric.Object|null }
|
||||
*/
|
||||
function findObjectInGroup(group, targetId) {
|
||||
if (!group || group.type !== "group" || !group.getObjects) {
|
||||
return { found: false, object: null };
|
||||
}
|
||||
|
||||
const groupObjects = group.getObjects();
|
||||
|
||||
for (const obj of groupObjects) {
|
||||
if (obj.id === targetId) {
|
||||
return { found: true, object: obj };
|
||||
}
|
||||
|
||||
// 递归检查嵌套组
|
||||
if (obj.type === "group") {
|
||||
const nestedResult = findObjectInGroup(obj, targetId);
|
||||
if (nestedResult.found) {
|
||||
return nestedResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { found: false, object: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过ID查找对象(增强版)
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {string} objectId 对象ID
|
||||
* @returns {Object} { object: fabric.Object|null, parent: fabric.Group|null }
|
||||
*/
|
||||
export function findObjectById(canvas, objectId) {
|
||||
if (!canvas || !objectId) {
|
||||
return { object: null, parent: null };
|
||||
}
|
||||
|
||||
const result = objectIsInCanvas(canvas, { id: objectId });
|
||||
return { object: result.object, parent: result.parent };
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全移除画布对象(包括组内对象)
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {fabric.Object} targetObj 目标对象
|
||||
* @returns {boolean} 是否成功移除
|
||||
*/
|
||||
export function removeCanvasObjectByObject(canvas, targetObj) {
|
||||
if (!canvas || !targetObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = objectIsInCanvas(canvas, targetObj);
|
||||
|
||||
if (!result.flag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (result.parent) {
|
||||
// 对象在组中,从组中移除
|
||||
result.parent.removeWithUpdate(result.object);
|
||||
} else {
|
||||
// 对象在顶层,直接从画布移除
|
||||
canvas.remove(result.object);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("移除对象时发生错误:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化画布渲染 统一渲染完成后才执行
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {Function} callback 渲染执行函数
|
||||
*/
|
||||
export const optimizeCanvasRendering = async (canvas, callback) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!canvas || typeof callback !== "function") {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// 暂停渲染以提高性能
|
||||
canvas.skipTargetFind = true;
|
||||
// 开始渲染
|
||||
// 暂停实时渲染和对象查找
|
||||
const wasRenderOnAddRemove = canvas.renderOnAddRemove;
|
||||
canvas.renderOnAddRemove = false; // 禁用自动渲染
|
||||
|
||||
// 等待下一帧渲染完成
|
||||
requestAnimationFrame(async () => {
|
||||
// 恢复渲染设置
|
||||
canvas.skipTargetFind = false;
|
||||
canvas.renderOnAddRemove = wasRenderOnAddRemove;
|
||||
if (isPromise(callback)) {
|
||||
await callback?.();
|
||||
} else {
|
||||
callback?.();
|
||||
}
|
||||
canvas.renderAll(); // 确保画布重新渲染 - 同步渲染
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取对象在画布中的z-index位置
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {fabric.Object} targetObj 目标对象
|
||||
* @returns {number} z-index位置,-1表示未找到
|
||||
*/
|
||||
export function getObjectZIndex(canvas, targetObj) {
|
||||
if (!canvas || !targetObj) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const result = objectIsInCanvas(canvas, targetObj);
|
||||
if (!result.flag) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (result.parent) {
|
||||
// 对象在组中,返回组在画布中的位置
|
||||
const allObjects = canvas.getObjects();
|
||||
return allObjects.indexOf(result.parent);
|
||||
} else {
|
||||
// 对象在顶层,直接返回在画布中的位置
|
||||
const allObjects = canvas.getObjects();
|
||||
return allObjects.indexOf(result.object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的z-index位置插入对象
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {fabric.Object} object 要插入的对象
|
||||
* @param {number} zIndex z-index位置
|
||||
* @param {boolean} renderAll 是否立即渲染,默认true
|
||||
* @returns {boolean} 是否成功插入
|
||||
*/
|
||||
export function insertObjectAtZIndex(canvas, object, zIndex, renderAll = true) {
|
||||
if (!canvas || !object || zIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保z-index不超过当前对象数量
|
||||
const maxIndex = canvas.getObjects().length;
|
||||
const safeZIndex = Math.min(zIndex, maxIndex);
|
||||
|
||||
canvas.insertAt(object, safeZIndex, false);
|
||||
|
||||
if (renderAll) {
|
||||
canvas.renderAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("插入对象到指定z-index位置失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动对象到指定的z-index位置
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {fabric.Object} object 要移动的对象
|
||||
* @param {number} zIndex 目标z-index位置
|
||||
* @param {boolean} renderAll 是否立即渲染,默认true
|
||||
* @returns {boolean} 是否成功移动
|
||||
*/
|
||||
export function moveObjectToZIndex(canvas, object, zIndex, renderAll = true) {
|
||||
if (!canvas || !object || zIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = objectIsInCanvas(canvas, object);
|
||||
if (!result.flag) {
|
||||
console.warn("对象不在画布中,无法移动");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保z-index不超过当前对象数量-1(因为当前对象也在其中)
|
||||
const maxIndex = canvas.getObjects().length - 1;
|
||||
const safeZIndex = Math.min(zIndex, maxIndex);
|
||||
|
||||
result.object.moveTo(safeZIndex);
|
||||
|
||||
if (renderAll) {
|
||||
canvas.renderAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("移动对象到指定z-index位置失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换两个对象的z-index位置
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {fabric.Object} obj1 第一个对象
|
||||
* @param {fabric.Object} obj2 第二个对象
|
||||
* @param {boolean} renderAll 是否立即渲染,默认true
|
||||
* @returns {boolean} 是否成功交换
|
||||
*/
|
||||
export function swapObjectsZIndex(canvas, obj1, obj2, renderAll = true) {
|
||||
if (!canvas || !obj1 || !obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const zIndex1 = getObjectZIndex(canvas, obj1);
|
||||
const zIndex2 = getObjectZIndex(canvas, obj2);
|
||||
|
||||
if (zIndex1 === -1 || zIndex2 === -1) {
|
||||
console.warn("其中一个或两个对象不在画布中");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 先移动z-index较大的对象,避免索引冲突
|
||||
if (zIndex1 > zIndex2) {
|
||||
moveObjectToZIndex(canvas, obj2, zIndex1, false);
|
||||
moveObjectToZIndex(canvas, obj1, zIndex2, false);
|
||||
} else {
|
||||
moveObjectToZIndex(canvas, obj1, zIndex2, false);
|
||||
moveObjectToZIndex(canvas, obj2, zIndex1, false);
|
||||
}
|
||||
|
||||
if (renderAll) {
|
||||
canvas.renderAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("交换对象z-index位置失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取画布中所有对象的z-index信息
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @returns {Array} 包含对象ID和z-index的数组
|
||||
*/
|
||||
export function getAllObjectsZIndex(canvas) {
|
||||
if (!canvas) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const objects = canvas.getObjects();
|
||||
return objects.map((obj, index) => ({
|
||||
id: obj.id || `unnamed_${index}`,
|
||||
zIndex: index,
|
||||
type: obj.type,
|
||||
layerId: obj.layerId,
|
||||
object: obj,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按图层ID获取对象的z-index信息
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {Array} 属于指定图层的对象z-index信息
|
||||
*/
|
||||
export function getLayerObjectsZIndex(canvas, layerId) {
|
||||
if (!canvas || !layerId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allInfo = getAllObjectsZIndex(canvas);
|
||||
return allInfo.filter((info) => info.layerId === layerId);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//import { fabric } from "fabric-with-all";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
|
||||
// 导入新的复合命令
|
||||
import { CreateImageLayerCommand } from "../commands/LayerCommands";
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ChangeFixedImageCommand,
|
||||
AddImageToLayerCommand,
|
||||
} from "../commands/LayerCommands";
|
||||
import { generateId } from "./helper";
|
||||
|
||||
/**
|
||||
* 加载并处理图片
|
||||
@@ -44,6 +45,7 @@ export function loadImage(imageSource, options = {}) {
|
||||
// 设置图片位置 - 默认居中
|
||||
if (options.centerOnCanvas !== false) {
|
||||
fabricImage.set({
|
||||
id: generateId("fabricImage"),
|
||||
left: (options.canvasWidth || 800) / 2,
|
||||
top: (options.canvasHeight || 600) / 2,
|
||||
originX: "center",
|
||||
|
||||
@@ -13,6 +13,7 @@ export const LayerType = {
|
||||
VIDEO: "video", // 视频图层 (预留)
|
||||
AUDIO: "audio", // 音频图层 (预留)
|
||||
FIXED: "fixed", // 固定图层 - 位于背景图层之上,普通图层之下
|
||||
BACKGROUND: "background", // 背景图层 - 位于固定图层之、普通图层之下
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -461,3 +462,161 @@ export function cloneLayer(layer) {
|
||||
|
||||
return clonedLayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找图层(包括子图层)
|
||||
* @param {Array} layers 图层数组
|
||||
* @param {string} layerId 要查找的图层ID
|
||||
* @param {Object} parent 父图层(可选,用于内部递归)
|
||||
* @returns {Object|null} 包含layer和parent的对象,如果未找到返回null
|
||||
*/
|
||||
export function findLayerRecursively(layers, layerId, parent = null) {
|
||||
if (!layers || !Array.isArray(layers) || !layerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 在当前图层列表中查找
|
||||
for (const layer of layers) {
|
||||
if (layer && layer.id === layerId) {
|
||||
return { layer, parent };
|
||||
}
|
||||
|
||||
// 如果是组图层,递归查找子图层
|
||||
if (
|
||||
layer &&
|
||||
(layer.type === "group" ||
|
||||
layer.type === LayerType.GROUP ||
|
||||
(layer.children && Array.isArray(layer.children)))
|
||||
) {
|
||||
const result = findInChildLayers(layer.children, layerId, layer);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在子图层中递归查找
|
||||
* @param {Array} children 子图层数组
|
||||
* @param {string} layerId 要查找的图层ID
|
||||
* @param {Object} parent 父图层
|
||||
* @returns {Object|null} 包含layer和parent的对象,如果未找到返回null
|
||||
*/
|
||||
export function findInChildLayers(children, layerId, parent) {
|
||||
if (!children || !Array.isArray(children) || !layerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const child of children) {
|
||||
if (child && child.id === layerId) {
|
||||
return { layer: child, parent };
|
||||
}
|
||||
|
||||
// 如果子图层也是组,继续递归查找
|
||||
if (
|
||||
child &&
|
||||
(child.type === "group" || child.type === LayerType.GROUP) &&
|
||||
child.children &&
|
||||
Array.isArray(child.children)
|
||||
) {
|
||||
const result = findInChildLayers(child.children, layerId, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单查找图层(仅在顶级图层中查找,不递归子图层)
|
||||
* @param {Array} layers 图层数组
|
||||
* @param {string} layerId 要查找的图层ID
|
||||
* @returns {Object|null} 找到的图层对象,如果未找到返回null
|
||||
*/
|
||||
export function findLayer(layers, layerId) {
|
||||
if (!layers || !Array.isArray(layers) || !layerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return layers.find((layer) => layer && layer.id === layerId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据图层名称查找图层
|
||||
* @param {Array} layers 图层数组
|
||||
* @param {string} layerName 要查找的图层名称
|
||||
* @param {boolean} recursive 是否递归查找子图层,默认false
|
||||
* @returns {Object|null} 找到的图层对象,如果未找到返回null
|
||||
*/
|
||||
export function findLayerByName(layers, layerName, recursive = false) {
|
||||
if (!layers || !Array.isArray(layers) || !layerName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const layer of layers) {
|
||||
if (layer && layer.name === layerName) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
// 如果需要递归查找且是组图层
|
||||
if (
|
||||
recursive &&
|
||||
layer &&
|
||||
(layer.type === "group" ||
|
||||
layer.type === LayerType.GROUP ||
|
||||
(layer.children && Array.isArray(layer.children)))
|
||||
) {
|
||||
const found = findLayerByName(layer.children, layerName, true);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层的完整路径(包含父图层信息)
|
||||
* @param {Array} layers 图层数组
|
||||
* @param {string} layerId 要查找的图层ID
|
||||
* @returns {Array} 图层路径数组,从根图层到目标图层
|
||||
*/
|
||||
export function getLayerPath(layers, layerId) {
|
||||
if (!layers || !Array.isArray(layers) || !layerId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function findPath(currentLayers, targetId, currentPath = []) {
|
||||
for (const layer of currentLayers) {
|
||||
if (!layer) continue;
|
||||
|
||||
const newPath = [...currentPath, layer];
|
||||
|
||||
if (layer.id === targetId) {
|
||||
return newPath;
|
||||
}
|
||||
|
||||
// 如果是组图层,递归查找
|
||||
if (
|
||||
layer.type === "group" ||
|
||||
layer.type === LayerType.GROUP ||
|
||||
(layer.children && Array.isArray(layer.children))
|
||||
) {
|
||||
const foundPath = findPath(layer.children, targetId, newPath);
|
||||
if (foundPath.length > 0) {
|
||||
return foundPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return findPath(layers, layerId);
|
||||
}
|
||||
|
||||
204
src/component/Canvas/CanvasEditor/utils/layerUtils.js
Normal file
204
src/component/Canvas/CanvasEditor/utils/layerUtils.js
Normal file
@@ -0,0 +1,204 @@
|
||||
import { isArray } from "lodash-es";
|
||||
|
||||
/**
|
||||
* 图层关联工具类
|
||||
* 提供图层与画布对象关联管理的通用方法
|
||||
*/
|
||||
|
||||
/**
|
||||
* 构建单个图层与画布对象的关联关系
|
||||
* @param {Object} layer 图层对象
|
||||
* @param {Array} canvasObjects 画布对象数组
|
||||
*/
|
||||
export function buildLayerAssociations(layer, canvasObjects) {
|
||||
if (!layer || !canvasObjects || !isArray(canvasObjects)) return;
|
||||
// 处理单个fabricObject关联
|
||||
if (layer.fabricObject) {
|
||||
// 如果图层已经有关联的fabricObject,确保它的layerId和layerName正确
|
||||
layer.fabricObject =
|
||||
canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
|
||||
}
|
||||
|
||||
if (layer.clippingMask) {
|
||||
// clippingMask 可能是一个fabricObject或组
|
||||
layer.clippingMask =
|
||||
canvasObjects.find((obj) => obj.id === layer.clippingMask.id) || null;
|
||||
}
|
||||
|
||||
// 处理多个fabricObjects关联
|
||||
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
|
||||
layer.fabricObjects = layer.fabricObjects
|
||||
.map((fabricObject) => {
|
||||
// 确保每个fabricObject的layerId和layerName正确
|
||||
const obj = canvasObjects.find((obj) => obj.id === fabricObject.id);
|
||||
if (obj) {
|
||||
obj.layerId = layer.id; // 确保对象的layerId正确
|
||||
obj.layerName = layer.name; // 确保对象的layerName正确
|
||||
return obj;
|
||||
}
|
||||
return null; // 如果没有找到对象,返回null
|
||||
})
|
||||
.filter((obj) => obj !== null); // 过滤掉null值
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复对象与图层的关联关系
|
||||
* @param {Object} layerManager 图层管理器实例
|
||||
* @param {Array} canvasObjects 画布对象数组
|
||||
*/
|
||||
export function restoreObjectLayerAssociations(layers, canvasObjects) {
|
||||
if (!layers || !canvasObjects || !isArray(canvasObjects)) return;
|
||||
layers.forEach((layer) => {
|
||||
buildLayerAssociations(layer, canvasObjects);
|
||||
// 处理子图层
|
||||
if (layer?.children?.length) {
|
||||
restoreObjectLayerAssociations(layer.children, canvasObjects);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 为画布对象设置图层信息
|
||||
* @param {Object} fabricObject 画布对象
|
||||
* @param {Object} layer 图层对象
|
||||
*/
|
||||
export function setObjectLayerInfo(fabricObject, layer) {
|
||||
if (!fabricObject || !layer) return;
|
||||
|
||||
fabricObject.layerId = layer.id;
|
||||
fabricObject.layerName = layer.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除画布对象的图层信息
|
||||
* @param {Object} fabricObject 画布对象
|
||||
*/
|
||||
export function clearObjectLayerInfo(fabricObject) {
|
||||
if (!fabricObject) return;
|
||||
|
||||
delete fabricObject.layerId;
|
||||
delete fabricObject.layerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证图层关联关系的完整性
|
||||
* @param {Object} layerManager 图层管理器实例
|
||||
* @param {fabric.Canvas} canvas 画布实例
|
||||
* @returns {Object} 验证结果 { valid: boolean, issues: Array }
|
||||
*/
|
||||
export function validateLayerAssociations(layers, canvasObjects) {
|
||||
const issues = [];
|
||||
|
||||
// 检查画布对象是否都有对应的图层
|
||||
canvasObjects.forEach((obj) => {
|
||||
if (obj.layerId) {
|
||||
const layer = layers.find((l) => l.id === obj.layerId);
|
||||
if (!layer) {
|
||||
issues.push({
|
||||
type: "orphaned_object",
|
||||
objectId: obj.id,
|
||||
layerId: obj.layerId,
|
||||
message: `对象 ${obj.id} 关联的图层 ${obj.layerId} 不存在`,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
issues.push({
|
||||
type: "missing_layer_id",
|
||||
objectId: obj.id,
|
||||
message: `对象 ${obj.id} 缺少图层ID关联`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 检查图层是否都有对应的画布对象
|
||||
layers.forEach((layer) => {
|
||||
if (layer.fabricObject && layer.fabricObject.id) {
|
||||
const obj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
|
||||
if (!obj) {
|
||||
issues.push({
|
||||
type: "missing_object",
|
||||
layerId: layer.id,
|
||||
objectId: layer.fabricObject.id,
|
||||
message: `图层 ${layer.id} 关联的对象 ${layer.fabricObject.id} 不存在于画布中`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
|
||||
layer.fabricObjects.forEach((fabricObj) => {
|
||||
if (fabricObj.id) {
|
||||
const obj = canvasObjects.find((o) => o.id === fabricObj.id);
|
||||
if (!obj) {
|
||||
issues.push({
|
||||
type: "missing_object",
|
||||
layerId: layer.id,
|
||||
objectId: fabricObj.id,
|
||||
message: `图层 ${layer.id} 关联的对象 ${fabricObj.id} 不存在于画布中`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
valid: issues.length === 0,
|
||||
issues,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化layers对象属性,只保留必要的属性
|
||||
* @param {Array} layers 图层数组 simplifyLayers(JSON.parse(JSON.stringify(this.layers.value)))
|
||||
|
||||
*/
|
||||
|
||||
export function simplifyLayers(layers) {
|
||||
if (!layers || !isArray(layers)) {
|
||||
console.warn("simplifyLayers 请传入有效的图层数组:", layers);
|
||||
return [];
|
||||
}
|
||||
|
||||
layers.forEach((layer) => {
|
||||
// 处理图层遮罩
|
||||
// 如果clippingMask是一个fabricObject或组,确保它的id正确 // 因为是fabric对象,所以没办法直接获取id,只能通过序列化获取
|
||||
if (layer.clippingMask) {
|
||||
layer.clippingMask = layer.clippingMask?.id || null;
|
||||
}
|
||||
|
||||
// 处理单个fabricObject
|
||||
if (layer.fabricObject) {
|
||||
layer.fabricObject = layer.fabricObject?.id;
|
||||
}
|
||||
// 处理多个fabricObjects
|
||||
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
|
||||
layer.fabricObjects = layer.fabricObjects
|
||||
.map((fabricObject) => {
|
||||
return fabricObject?.id || null; // 确保每个fabricObject都能转换为对象
|
||||
})
|
||||
.filter((obj) => obj !== null);
|
||||
}
|
||||
// 处理子图层
|
||||
if (layer.children && isArray(layer.children)) {
|
||||
layer.children = simplifyLayers(layer.children);
|
||||
}
|
||||
|
||||
// 只保留必要的属性
|
||||
layer = {
|
||||
id: layer.id,
|
||||
name: layer.name,
|
||||
visible: layer.visible,
|
||||
locked: layer.locked,
|
||||
opacity: layer.opacity,
|
||||
clippingMask: layer.clippingMask || null,
|
||||
fabricObject: layer.fabricObject || null,
|
||||
fabricObjects: layer.fabricObjects || [],
|
||||
children: layer.children || [],
|
||||
isBackground: layer.isBackground || false,
|
||||
ifFixed: layer.ifFixed || false,
|
||||
};
|
||||
});
|
||||
|
||||
return layers;
|
||||
}
|
||||
Reference in New Issue
Block a user