2025-07-10 01:01:46 +08:00
|
|
|
|
import { generateId } from "./helper";
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 图层类型枚举
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const LayerType = {
|
|
|
|
|
|
EMPTY: "empty", // 空图层
|
|
|
|
|
|
BITMAP: "bitmap", // 位图图层
|
|
|
|
|
|
VECTOR: "vector", // 矢量图层
|
|
|
|
|
|
TEXT: "text", // 文字图层
|
|
|
|
|
|
GROUP: "group", // 组图层
|
|
|
|
|
|
ADJUSTMENT: "adjustment", // 调整图层
|
|
|
|
|
|
SMART_OBJECT: "smartObject", // 智能对象
|
|
|
|
|
|
SHAPE: "shape", // 形状图层
|
|
|
|
|
|
VIDEO: "video", // 视频图层 (预留)
|
|
|
|
|
|
AUDIO: "audio", // 音频图层 (预留)
|
|
|
|
|
|
FIXED: "fixed", // 固定图层 - 位于背景图层之上,普通图层之下
|
2025-06-18 11:05:23 +08:00
|
|
|
|
BACKGROUND: "background", // 背景图层 - 位于固定图层之、普通图层之下
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 画布操作模式枚举:draw(绘画)、select(选择)、pan(拖拽)....
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const OperationType = {
|
|
|
|
|
|
// 编辑器模式
|
|
|
|
|
|
DRAW: "draw", // 绘画模式
|
|
|
|
|
|
ERASER: "eraser", // 橡皮擦模式
|
|
|
|
|
|
SELECT: "select", // 选择模式
|
|
|
|
|
|
PAN: "pan", // 拖拽模式
|
|
|
|
|
|
EYEDROPPER: "eyedropper", // 吸色器模式
|
|
|
|
|
|
|
|
|
|
|
|
// 套索工具
|
|
|
|
|
|
LASSO: "lasso", // 套索工具模式 - 自由套索模式
|
|
|
|
|
|
LASSO_RECTANGLE: "lasso_rectangle", // 套索工具模式 - 矩形模式
|
|
|
|
|
|
LASSO_ELLIPSE: "lasso_ellipse", // 套索工具模式 - 椭圆
|
|
|
|
|
|
|
|
|
|
|
|
// 创建临时选区工具模式 - 类似于临时图层 在这个区域的操作不会影响其他图层
|
|
|
|
|
|
AREA_RECTANGLE: "area_rectangle", // 矩形选区模式
|
|
|
|
|
|
|
|
|
|
|
|
// 材质笔刷工具模式
|
|
|
|
|
|
TEXTURE: "texture", // 选择材质笔刷工具模式 - // 选择材质笔刷后会切换到绘画模式 笔刷固定到材质笔刷
|
|
|
|
|
|
|
|
|
|
|
|
// 液化工具
|
|
|
|
|
|
LIQUIFY: "liquify", // 液化工具模式
|
|
|
|
|
|
|
|
|
|
|
|
// 矢量工具
|
|
|
|
|
|
// VECTOR: "vector", // 矢量工具模式
|
|
|
|
|
|
// 矢量工具模式 - 自由绘制
|
|
|
|
|
|
// VECTOR_FREE: "vector_free",
|
|
|
|
|
|
|
|
|
|
|
|
TEXT: "text", // 文字工具模式
|
|
|
|
|
|
|
|
|
|
|
|
// 红绿图模式
|
|
|
|
|
|
RED_GREEN: "red_green", // 红绿图模式 - 只有红色和绿色笔刷还有橡皮擦 不支持添加其他图片 特殊模式
|
|
|
|
|
|
RED_BRUSH: "red_brush", // 红色笔刷
|
|
|
|
|
|
GREEN_BRUSH: "green_brush", // 绿色笔刷
|
|
|
|
|
|
|
|
|
|
|
|
// SHAPE: "shape", // 形状模式
|
|
|
|
|
|
// 可以根据需要添加更多工具
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 所有操作模式类型列表
|
|
|
|
|
|
export const OperationTypes = Object.values(OperationType);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 混合模式枚举
|
|
|
|
|
|
* 与 fabricjs 和 CSS3 的 globalCompositeOperation 对应
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const BlendMode = {
|
|
|
|
|
|
NORMAL: "source-over", // 正常模式
|
|
|
|
|
|
MULTIPLY: "multiply", // 正片叠底
|
|
|
|
|
|
SCREEN: "screen", // 滤色
|
|
|
|
|
|
OVERLAY: "overlay", // 叠加
|
|
|
|
|
|
DARKEN: "darken", // 变暗
|
|
|
|
|
|
LIGHTEN: "lighten", // 变亮
|
|
|
|
|
|
COLOR_DODGE: "color-dodge", // 颜色减淡
|
|
|
|
|
|
COLOR_BURN: "color-burn", // 颜色加深
|
|
|
|
|
|
HARD_LIGHT: "hard-light", // 强光
|
|
|
|
|
|
SOFT_LIGHT: "soft-light", // 柔光
|
|
|
|
|
|
DIFFERENCE: "difference", // 差值
|
|
|
|
|
|
EXCLUSION: "exclusion", // 排除
|
|
|
|
|
|
HUE: "hue", // 色相
|
|
|
|
|
|
SATURATION: "saturation", // 饱和度
|
|
|
|
|
|
COLOR: "color", // 颜色
|
|
|
|
|
|
LUMINOSITY: "luminosity", // 明度
|
|
|
|
|
|
DESTINATION_IN: "destination-in", // 目标内
|
|
|
|
|
|
DESTINATION_OUT: "destination-out", // 目标外
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断图层是否为组图层
|
|
|
|
|
|
* @param {Object} layer 要检查的图层
|
|
|
|
|
|
* @returns {boolean} 是否为组图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function isGroupLayer(layer) {
|
|
|
|
|
|
if (!layer) return false;
|
|
|
|
|
|
return (
|
|
|
|
|
|
layer.type === LayerType.GROUP ||
|
|
|
|
|
|
(Array.isArray(layer.children) && layer.children.length > 0)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从fabric对象创建图层
|
|
|
|
|
|
* @param {Object} fabricObject fabric对象
|
|
|
|
|
|
* @param {String} layerType 图层类型
|
|
|
|
|
|
* @param {Object} options 其他选项
|
|
|
|
|
|
* @returns {Object} 创建的图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createLayerFromFabricObject(
|
|
|
|
|
|
fabricObject,
|
|
|
|
|
|
layerType = "bitmap",
|
|
|
|
|
|
options = {}
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (!fabricObject) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 确定图层类型
|
|
|
|
|
|
let type = layerType;
|
|
|
|
|
|
if (fabricObject.type === "textbox" || fabricObject.type === "text") {
|
|
|
|
|
|
type = LayerType.TEXT;
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
fabricObject.type === "rect" ||
|
|
|
|
|
|
fabricObject.type === "circle" ||
|
|
|
|
|
|
fabricObject.type === "polygon" ||
|
|
|
|
|
|
fabricObject.type === "polyline"
|
|
|
|
|
|
) {
|
|
|
|
|
|
type = LayerType.SHAPE;
|
|
|
|
|
|
} else if (fabricObject.type === "path" || fabricObject.type === "line") {
|
|
|
|
|
|
type = LayerType.VECTOR;
|
|
|
|
|
|
} else if (fabricObject.type === "image") {
|
|
|
|
|
|
type = LayerType.BITMAP;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建基础图层
|
|
|
|
|
|
let layer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: type,
|
|
|
|
|
|
name:
|
|
|
|
|
|
options.name ||
|
|
|
|
|
|
`${
|
|
|
|
|
|
fabricObject.type.charAt(0).toUpperCase() + fabricObject.type.slice(1)
|
|
|
|
|
|
} 图层`,
|
|
|
|
|
|
parentId: options.parentId || null,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加对象到图层
|
|
|
|
|
|
if (Array.isArray(layer.fabricObjects)) {
|
|
|
|
|
|
layer.fabricObjects.push(fabricObject);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
layer.fabricObjects = [fabricObject];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果对象有自己的ID,将其与图层关联
|
|
|
|
|
|
if (fabricObject.id) {
|
|
|
|
|
|
fabricObject.layerId = layer.id;
|
|
|
|
|
|
fabricObject.layerName = layer.name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return layer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建标准图层对象
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createLayer(options = {}) {
|
|
|
|
|
|
const id =
|
2025-06-22 13:52:28 +08:00
|
|
|
|
options.id ||
|
|
|
|
|
|
generateId("layer_") ||
|
|
|
|
|
|
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return {
|
|
|
|
|
|
id: id,
|
|
|
|
|
|
// 图层基本属性
|
|
|
|
|
|
name: options.name || `图层 ${id.substring(id.lastIndexOf("_") + 1)}`,
|
|
|
|
|
|
type: options.type || LayerType.EMPTY,
|
|
|
|
|
|
visible: options.visible !== undefined ? options.visible : true,
|
|
|
|
|
|
locked: options.locked !== undefined ? options.locked : false,
|
|
|
|
|
|
opacity: options.opacity !== undefined ? options.opacity : 1.0,
|
|
|
|
|
|
blendMode: options.blendMode || BlendMode.NORMAL,
|
|
|
|
|
|
|
|
|
|
|
|
// 确保不是背景图层
|
|
|
|
|
|
isBackground: false,
|
|
|
|
|
|
|
|
|
|
|
|
// Fabric.js 对象列表
|
|
|
|
|
|
fabricObjects: options.fabricObjects || [],
|
|
|
|
|
|
|
|
|
|
|
|
// 嵌套结构 - 适用于组图层
|
|
|
|
|
|
children: options.children || [],
|
|
|
|
|
|
|
|
|
|
|
|
// 剪切蒙版
|
|
|
|
|
|
clippingMask: options.clippingMask || null,
|
|
|
|
|
|
|
|
|
|
|
|
// 位置和大小信息(可选)
|
|
|
|
|
|
bounds: options.bounds || null,
|
|
|
|
|
|
|
|
|
|
|
|
// 图层特定属性
|
|
|
|
|
|
layerProperties: options.layerProperties || {},
|
|
|
|
|
|
|
|
|
|
|
|
// 元数据 - 可用于存储任意数据
|
|
|
|
|
|
metadata: options.metadata || {},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建背景图层
|
|
|
|
|
|
* @param {Object} options 背景图层选项
|
|
|
|
|
|
* @returns {Object} 背景图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createBackgroundLayer(options = {}) {
|
|
|
|
|
|
const id =
|
|
|
|
|
|
options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: id,
|
|
|
|
|
|
// 图层基本属性
|
|
|
|
|
|
name: options.name || "背景",
|
|
|
|
|
|
type: LayerType.BITMAP,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: true, // 背景图层默认锁定
|
|
|
|
|
|
opacity: 1.0, // 背景图层始终不透明
|
|
|
|
|
|
blendMode: BlendMode.NORMAL, // 背景图层始终使用正常混合模式
|
|
|
|
|
|
|
|
|
|
|
|
// 标记为背景图层
|
|
|
|
|
|
isBackground: true,
|
|
|
|
|
|
|
|
|
|
|
|
// 画布尺寸
|
|
|
|
|
|
canvasWidth: options.canvasWidth || 800,
|
|
|
|
|
|
canvasHeight: options.canvasHeight || 600,
|
|
|
|
|
|
backgroundColor: options.backgroundColor || "#ffffff",
|
|
|
|
|
|
|
|
|
|
|
|
// Fabric.js 背景对象 (单个矩形对象)
|
|
|
|
|
|
fabricObject: null, // 创建后设置
|
|
|
|
|
|
// Fabric.js 对象列表
|
|
|
|
|
|
fabricObjects: [], // 创建后设置
|
|
|
|
|
|
|
|
|
|
|
|
// 无子图层
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
|
|
|
|
|
|
// 元数据 - 可用于存储任意数据
|
|
|
|
|
|
metadata: options.metadata || {},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建位图图层
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 位图图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createBitmapLayer(options = {}) {
|
|
|
|
|
|
const baseLayer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: LayerType.BITMAP,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加位图特定属性
|
|
|
|
|
|
baseLayer.layerProperties = {
|
|
|
|
|
|
...baseLayer.layerProperties,
|
|
|
|
|
|
filters: options.filters || [], // 滤镜数组
|
|
|
|
|
|
imageUrl: options.imageUrl || null, // 图片URL
|
|
|
|
|
|
imageElement: options.imageElement || null, // 图片元素
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return baseLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建文本图层
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 文本图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createTextLayer(options = {}) {
|
|
|
|
|
|
const baseLayer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: LayerType.TEXT,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加文字特定属性
|
|
|
|
|
|
baseLayer.layerProperties = {
|
|
|
|
|
|
...baseLayer.layerProperties,
|
|
|
|
|
|
text: options.text || "新文本",
|
|
|
|
|
|
fontFamily: options.fontFamily || "Arial",
|
|
|
|
|
|
fontSize: options.fontSize || 24,
|
|
|
|
|
|
fontWeight: options.fontWeight || "normal",
|
|
|
|
|
|
fontStyle: options.fontStyle || "normal",
|
|
|
|
|
|
textAlign: options.textAlign || "left",
|
|
|
|
|
|
underline: options.underline || false,
|
|
|
|
|
|
overline: options.overline || false,
|
|
|
|
|
|
linethrough: options.linethrough || false,
|
|
|
|
|
|
textBackgroundColor: options.textBackgroundColor || "transparent",
|
|
|
|
|
|
lineHeight: options.lineHeight || 1.16,
|
|
|
|
|
|
charSpacing: options.charSpacing || 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return baseLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建矢量图层
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 矢量图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createVectorLayer(options = {}) {
|
|
|
|
|
|
const baseLayer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: LayerType.VECTOR,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加矢量特定属性
|
|
|
|
|
|
baseLayer.layerProperties = {
|
|
|
|
|
|
...baseLayer.layerProperties,
|
|
|
|
|
|
vectorType: options.vectorType || "path", // path, polygon, polyline等
|
|
|
|
|
|
strokeWidth: options.strokeWidth !== undefined ? options.strokeWidth : 1,
|
|
|
|
|
|
strokeColor: options.strokeColor || "#000000",
|
|
|
|
|
|
fillColor: options.fillColor || "transparent",
|
|
|
|
|
|
fillRule: options.fillRule || "nonzero",
|
|
|
|
|
|
strokeLineCap: options.strokeLineCap || "butt",
|
|
|
|
|
|
strokeLineJoin: options.strokeLineJoin || "miter",
|
|
|
|
|
|
strokeDashArray: options.strokeDashArray || null,
|
|
|
|
|
|
strokeDashOffset: options.strokeDashOffset || 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return baseLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建形状图层
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 形状图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createShapeLayer(options = {}) {
|
|
|
|
|
|
const baseLayer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: LayerType.SHAPE,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加形状特定属性
|
|
|
|
|
|
baseLayer.layerProperties = {
|
|
|
|
|
|
...baseLayer.layerProperties,
|
|
|
|
|
|
shapeType: options.shapeType || "rect", // rect, circle, ellipse等
|
|
|
|
|
|
strokeWidth: options.strokeWidth !== undefined ? options.strokeWidth : 1,
|
|
|
|
|
|
strokeColor: options.strokeColor || "#000000",
|
|
|
|
|
|
fillColor: options.fillColor || "#ffffff",
|
|
|
|
|
|
rx: options.rx || 0, // 矩形圆角
|
|
|
|
|
|
ry: options.ry || 0, // 矩形圆角
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return baseLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建调整图层
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 调整图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createAdjustmentLayer(options = {}) {
|
|
|
|
|
|
const baseLayer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: LayerType.ADJUSTMENT,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加调整图层特定属性
|
|
|
|
|
|
baseLayer.layerProperties = {
|
|
|
|
|
|
...baseLayer.layerProperties,
|
|
|
|
|
|
adjustmentType: options.adjustmentType || "brightness", // brightness, contrast, hue, saturation等
|
|
|
|
|
|
value: options.value !== undefined ? options.value : 0,
|
|
|
|
|
|
affectedLayerIds: options.affectedLayerIds || [], // 受影响的图层ID列表
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return baseLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建智能对象图层
|
|
|
|
|
|
* @param {Object} options 图层选项
|
|
|
|
|
|
* @returns {Object} 智能对象图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createSmartObjectLayer(options = {}) {
|
|
|
|
|
|
const baseLayer = createLayer({
|
|
|
|
|
|
...options,
|
|
|
|
|
|
type: LayerType.SMART_OBJECT,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加智能对象特定属性
|
|
|
|
|
|
baseLayer.layerProperties = {
|
|
|
|
|
|
...baseLayer.layerProperties,
|
|
|
|
|
|
sourceType: options.sourceType || "image", // image, vector, embedded等
|
|
|
|
|
|
sourceUrl: options.sourceUrl || null,
|
|
|
|
|
|
sourceData: options.sourceData || null,
|
|
|
|
|
|
originalWidth: options.originalWidth || 0,
|
|
|
|
|
|
originalHeight: options.originalHeight || 0,
|
|
|
|
|
|
embedded: options.embedded !== undefined ? options.embedded : true,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return baseLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建固定图层 - 位于背景图层之上,普通图层之下
|
|
|
|
|
|
* @param {Object} options 固定图层选项
|
|
|
|
|
|
* @returns {Object} 固定图层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createFixedLayer(options = {}) {
|
|
|
|
|
|
const id =
|
|
|
|
|
|
options.id ||
|
|
|
|
|
|
`fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: id,
|
|
|
|
|
|
// 图层基本属性
|
|
|
|
|
|
name: options.name || "固定图层",
|
|
|
|
|
|
type: LayerType.FIXED,
|
|
|
|
|
|
visible: true, // 固定图层始终可见
|
|
|
|
|
|
locked: true, // 固定图层默认锁定
|
|
|
|
|
|
opacity: options.opacity !== undefined ? options.opacity : 1.0,
|
|
|
|
|
|
blendMode: options.blendMode || BlendMode.NORMAL,
|
|
|
|
|
|
|
|
|
|
|
|
// 标记为固定图层
|
|
|
|
|
|
isFixed: true,
|
|
|
|
|
|
isBackground: false,
|
|
|
|
|
|
|
|
|
|
|
|
// Fabric.js 对象列表
|
|
|
|
|
|
fabricObjects: options.fabricObjects || [],
|
|
|
|
|
|
|
|
|
|
|
|
// 无子图层
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
|
|
|
|
|
|
// 元数据 - 可用于存储任意数据
|
|
|
|
|
|
metadata: options.metadata || {},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 深拷贝图层对象
|
|
|
|
|
|
* @param {Object} layer 要拷贝的图层
|
|
|
|
|
|
* @returns {Object} 拷贝后的图层
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function cloneLayer(layer) {
|
|
|
|
|
|
if (!layer) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 基本属性深拷贝
|
|
|
|
|
|
const clonedLayer = {
|
|
|
|
|
|
...JSON.parse(JSON.stringify(layer)), // 深拷贝基本属性
|
|
|
|
|
|
fabricObjects: [], // 重置,后面处理
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 复制 fabric 对象 (如果存在)
|
|
|
|
|
|
if (Array.isArray(layer.fabricObjects)) {
|
|
|
|
|
|
clonedLayer.fabricObjects = layer.fabricObjects.map((obj) => {
|
|
|
|
|
|
return obj && typeof obj.clone === "function"
|
|
|
|
|
|
? obj.clone()
|
|
|
|
|
|
: JSON.parse(JSON.stringify(obj));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复制背景对象 (如果存在)
|
|
|
|
|
|
if (layer.isBackground && layer.fabricObject) {
|
|
|
|
|
|
clonedLayer.fabricObject =
|
|
|
|
|
|
typeof layer.fabricObject.clone === "function"
|
|
|
|
|
|
? layer.fabricObject.clone()
|
|
|
|
|
|
: JSON.parse(JSON.stringify(layer.fabricObject));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归复制子图层
|
|
|
|
|
|
if (Array.isArray(layer.children) && layer.children.length > 0) {
|
|
|
|
|
|
clonedLayer.children = layer.children.map((child) => cloneLayer(child));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return clonedLayer;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 递归查找图层(包括子图层)
|
|
|
|
|
|
* @param {Array} layers 图层数组
|
|
|
|
|
|
* @param {string} layerId 要查找的图层ID
|
|
|
|
|
|
* @param {Object} parent 父图层(可选,用于内部递归)
|
|
|
|
|
|
* @returns {Object|null} 包含layer和parent的对象,如果未找到返回null
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function findLayerRecursively(layers, layerId, parent = null) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
try {
|
|
|
|
|
|
if (!layers || !Array.isArray(layers) || !layerId) {
|
2025-07-04 03:16:18 +08:00
|
|
|
|
return {
|
|
|
|
|
|
layer: null,
|
|
|
|
|
|
parent: null,
|
|
|
|
|
|
};
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 在当前图层列表中查找
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-22 13:52:28 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`查找图层 ${layerId} 时出错:`, error);
|
2025-07-04 03:16:18 +08:00
|
|
|
|
return {
|
|
|
|
|
|
layer: null,
|
|
|
|
|
|
parent: null,
|
|
|
|
|
|
};
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
console.warn(`图层 ${layerId} 未找到`);
|
2025-07-04 03:16:18 +08:00
|
|
|
|
return {
|
|
|
|
|
|
layer: null,
|
|
|
|
|
|
parent: null,
|
|
|
|
|
|
};
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 在子图层中递归查找
|
|
|
|
|
|
* @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) {
|
2025-07-09 00:22:03 +08:00
|
|
|
|
return {
|
|
|
|
|
|
layer: null,
|
|
|
|
|
|
parent: null,
|
|
|
|
|
|
};
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2025-07-09 00:22:03 +08:00
|
|
|
|
if (result && result.layer) {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|