diff --git a/src/assets/icons/CBrush2.svg b/src/assets/icons/CBrush2.svg new file mode 100644 index 00000000..d9e38702 --- /dev/null +++ b/src/assets/icons/CBrush2.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/assets/icons/CEraser2.svg b/src/assets/icons/CEraser2.svg new file mode 100644 index 00000000..ea475fa7 --- /dev/null +++ b/src/assets/icons/CEraser2.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/src/assets/icons/CMarquee.svg b/src/assets/icons/CMarquee.svg new file mode 100644 index 00000000..517b2810 --- /dev/null +++ b/src/assets/icons/CMarquee.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/src/assets/images/canvas/add.png b/src/assets/images/canvas/add.png new file mode 100644 index 00000000..3fb5a603 Binary files /dev/null and b/src/assets/images/canvas/add.png differ diff --git a/src/assets/images/canvas/remove.png b/src/assets/images/canvas/remove.png new file mode 100644 index 00000000..9fd798cc Binary files /dev/null and b/src/assets/images/canvas/remove.png differ diff --git a/src/assets/images/canvas/shubiao-l.png b/src/assets/images/canvas/shubiao-l.png new file mode 100644 index 00000000..76155843 Binary files /dev/null and b/src/assets/images/canvas/shubiao-l.png differ diff --git a/src/assets/images/canvas/shubiao-r.png b/src/assets/images/canvas/shubiao-r.png new file mode 100644 index 00000000..df70c79d Binary files /dev/null and b/src/assets/images/canvas/shubiao-r.png differ diff --git a/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue b/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue index a7f01588..c5ec0856 100644 --- a/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue @@ -15,6 +15,20 @@ {{ t("Canvas.GarmentPartSelector") }} +
+
+ + Left Click: Add +
+
+ + Right Click: Remove +
+
@@ -23,9 +37,9 @@ :key="item.type" :class="[ 'tool-btn', - { active: selectionType === item.type }, + { active: toolType === item.type }, ]" - @click="setSelectionType(item.type)" + @click="setPartType(item.type)" > {{ item.label }} @@ -37,24 +51,18 @@
-
+
{{ $t("Canvas.creation") }}
-
+
{{ $t("Canvas.CreateAndCopy") }}
-
- - {{ - $t("Canvas.TheClearlySelectedContent") - }} -
@@ -90,7 +98,7 @@ type: Object, required: true, }, - selectionManager: { + partManager: { type: Object, required: true, }, @@ -115,7 +123,7 @@ // 响应式数据 const visible = ref(false); - const selectionType = ref("rectangle"); + const toolType = ref(OperationType.PART); //打开隐藏操作面板 const closePanel = ref(false); const setClosePanel = () => { @@ -132,20 +140,20 @@ { type: OperationType.PART_RECTANGLE, label: "Marquee Selection", - icon: "CRectangle", - size: "26", + icon: "CMarquee", + size: "20", }, { type: OperationType.PART_BRUSH, label: "Brush Selection", - icon: "CBrush", - size: "24", + icon: "CBrush2", + size: "16", }, { type: OperationType.PART_ERASER, label: "Erase", - icon: "CEraser", - size: "24", + icon: "CEraser2", + size: "22", }, ]; @@ -169,13 +177,13 @@ if (selectionTools.includes(newTool)) { show(); // 根据工具类型设置选区类型 - selectionType.value = newTool; + toolType.value = newTool; // 更新选区管理器的选区类型 - if (props.selectionManager) { - props.selectionManager.setSelectionType(selectionType.value); - props.selectionManager.setupSelectionEvents(); - } + // if (props.partManager) { + // props.partManager.setPartType(toolType.value); + // props.partManager.setupPartEvents(); + // } } else { close(); } @@ -201,20 +209,30 @@ /** * 设置选区类型 */ - function setSelectionType(type) { - selectionType.value = type; + function setPartType(type) { + toolType.value = type; - // 通过 ToolManager 切换工具,这会自动通知 SelectionManager + // 通过 ToolManager 切换工具,这会自动通知 partManager if (props.toolManager) { props.toolManager.setToolWithCommand(type); } - // 备用方案:如果没有 toolManager,直接更新 selectionManager - else if (props.selectionManager) { - props.selectionManager.setSelectionType(type); - props.selectionManager.setupSelectionEvents(); - } + // // 备用方案:如果没有 toolManager,直接更新 partManager + // else if (props.partManager) { + // props.partManager.setPartType(type); + // props.partManager.setupPartEvents(); + // } } + + // 创建 + function onCreate() { + + } + // 复制并创建 + function onCopyCreate() { + + } + diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index d927f742..a5b371eb 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -385,6 +385,8 @@ onMounted(async () => { partManager = new PartManager({ canvas: canvasManager.canvas, layerManager, + canvasManager, + toolManager, }); canvasManager.setPartManager(partManager); @@ -722,8 +724,13 @@ function addRemoveBtn(fun) { }); } -function deleteFun() { - removeLayer(layerManager.activeLayerId.value); +function deleteFun(e, control) { + const target = control.target; + if(target.onDelete){ + target.onDelete(target); + }else if(target.id){ + removeLayer(layerManager.activeLayerId.value); + } } function removeLayer(layerId) { diff --git a/src/component/Canvas/CanvasEditor/managers/PartManager.js b/src/component/Canvas/CanvasEditor/managers/PartManager.js index 4758af83..645d1c8e 100644 --- a/src/component/Canvas/CanvasEditor/managers/PartManager.js +++ b/src/component/Canvas/CanvasEditor/managers/PartManager.js @@ -3,939 +3,295 @@ import { generateId } from "../utils/helper"; import { OperationType } from "../utils/layerHelper"; import { CreateSelectionCommand } from "../commands/SelectionCommands"; import { ClearSelectionCommand } from "../commands/LassoCutoutCommand"; +import addIcon from "@/assets/images/canvas/add.png"; +import removeIcon from "@/assets/images/canvas/remove.png"; /** * 部件选择管理器 */ export class PartManager { - /** - * 创建部件选择管理器 - * @param {Object} options 配置选项 - * @param {Object} options.canvas fabric.js画布实例 - * @param {Object} options.commandManager 命令管理器实例 - * @param {Object} options.layerManager 图层管理实例 - */ - constructor(options = {}) { - this.canvas = options.canvas; - this.commandManager = options.commandManager; - this.layerManager = options.layerManager; - - // 选区状态 - this.isActive = false; - this.selectionType = OperationType.LASSO_RECTANGLE; // 使用常量而不是字符串 - this.selectionObject = null; // 当前选区对象 - this.selectionId = "selection_" + Date.now(); - this.featherAmount = 0; // 羽化值 - - // 选区样式配置 - this.selectionStyle = { - stroke: "#0096ff", - strokeWidth: 1, - strokeDashArray: [5, 5], - fill: "rgba(0, 150, 255, 0.1)", - selectable: false, - evented: false, - excludeFromExport: true, - hoverCursor: "default", - moveCursor: "default", - }; - - // 绘制状态 - this.drawingObject = null; - this.startPoint = null; - this.selectionPath = null; // 存储选区路径数据 - - // 自由选区相关状态 - this.drawingPoints = null; - this.currentPathString = null; - - // 不再直接绑定事件处理函数 - this._mouseDownHandler = null; - this._mouseMoveHandler = null; - this._mouseUpHandler = null; - this._keyDownHandler = null; - - // 选区相关的工具类型 - this.tools = [ - OperationType.PART, - OperationType.PART_RECTANGLE, - OperationType.PART_BRUSH, - OperationType.PART_ERASER, - ]; - - // 当前工具 - this.currentTool = OperationType.SELECT; - - // 选区状态变化回调 - this.onSelectionChanged = null; - - // 不再自动初始化事件,改为手动控制 - // this.initEvents(); - } - - /** - * 设置当前工具 - * @param {String} toolId 工具ID - */ - setCurrentTool(toolId) { - this.currentTool = toolId; - - // 检查是否为选区工具 - const wasActive = this.isActive; - this.isActive = this.tools.includes(toolId); - - // 如果从非选区工具切换到选区工具,初始化事件 - if (!wasActive && this.isActive) { - this.initEvents(); - } - // 如果从选区工具切换到非选区工具,清理事件和选区 - else if (wasActive && !this.isActive) { - this.cleanupEvents(); - this.clearSelection(); - } - - // 根据工具类型设置选区类型 - if (this.isActive) { - this.selectionType = toolId; - } - } - - /** - * 初始化选区相关事件 - */ - initEvents() { - if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化 - - // 保存实例引用,用于事件处理函数中 - const self = this; - - // 鼠标按下事件处理 - this._mouseDownHandler = (options) => { - // 如果选区功能未激活,不处理事件 - if (!this.isActive) return; - - // 如果点击的是已有对象且不是选区对象,则不处理 - if ( - options.target && - options.target.id !== this.selectionId && - options.target.selectable !== false && - options.target.type !== "selection" - ) { - return; - } - - // 阻止事件冒泡,避免与 CanvasEventManager 冲突 - options.e.stopPropagation(); - - // 根据选区类型执行不同的起始操作 - switch (this.selectionType) { - case OperationType.LASSO: - this.startFreeSelection(options); - break; - case OperationType.LASSO_ELLIPSE: - this.startEllipseSelection(options); - break; - case OperationType.LASSO_RECTANGLE: - this.startRectangleSelection(options); - break; - } - }; - - // 鼠标移动事件处理 - this._mouseMoveHandler = (options) => { - // 如果选区功能未激活或没有正在绘制的对象,不处理事件 - if (!this.isActive || !this.drawingObject) return; - - // 阻止事件冒泡 - options.e.stopPropagation(); - - // 根据选区类型执行不同的绘制操作 - switch (this.selectionType) { - case OperationType.LASSO_RECTANGLE: - this.drawRectangleSelection(options); - break; - case OperationType.LASSO_ELLIPSE: - this.drawEllipseSelection(options); - break; - case OperationType.LASSO: - this.drawFreeSelection(options); - break; - } - }; - - // 鼠标抬起事件处理 - this._mouseUpHandler = (options) => { - // 如果选区功能未激活或没有正在绘制的对象,不处理事件 - if (!this.isActive || !this.drawingObject) return; - - // 阻止事件冒泡 - if (options && options.e) { - options.e.stopPropagation(); - } - - // 根据选区类型执行不同的完成操作 - switch (this.selectionType) { - case OperationType.LASSO_RECTANGLE: - this.endRectangleSelection(); - break; - case OperationType.LASSO_ELLIPSE: - this.endEllipseSelection(); - break; - case OperationType.LASSO: - this.endFreeSelection(); - break; - } - - // 如果有命令管理器,使用命令模式记录选区创建 - if (this.commandManager && this.selectionObject) { - this.commandManager.execute( - new CreateSelectionCommand({ - canvas: this.canvas, - selectionManager: this, - selectionObject: this.selectionObject, - selectionType: this.selectionType, - }) - ); - } - }; - - // 键盘事件处理 - this._keyDownHandler = (event) => { - // 只在选区功能激活时处理键盘事件 - if (!this.isActive) return; - - if (event.key === "Escape") { - // ESC键取消当前选区操作 - if (this.drawingObject) { - this.canvas.remove(this.drawingObject); - this.drawingObject = null; - this.startPoint = null; - } - // 清除已有选区 - else if (this.selectionObject) { - if (this.commandManager) { - this.commandManager.execute( - new ClearSelectionCommand({ - selectionManager: this, - }) - ); - } else { - this.clearSelection(); - } - } - } - }; - - // 添加事件监听 - this.canvas.on("mouse:down", this._mouseDownHandler); - this.canvas.on("mouse:move", this._mouseMoveHandler); - this.canvas.on("mouse:up", this._mouseUpHandler); - - // 添加键盘事件监听 - document.addEventListener("keydown", this._keyDownHandler); - } - - /** - * 清理事件监听 - */ - cleanupEvents() { - if (!this.canvas) return; - - // 移除事件监听 - if (this._mouseDownHandler) { - this.canvas.off("mouse:down", this._mouseDownHandler); - this._mouseDownHandler = null; - } - if (this._mouseMoveHandler) { - this.canvas.off("mouse:move", this._mouseMoveHandler); - this._mouseMoveHandler = null; - } - if (this._mouseUpHandler) { - this.canvas.off("mouse:up", this._mouseUpHandler); - this._mouseUpHandler = null; - } - if (this._keyDownHandler) { - document.removeEventListener("keydown", this._keyDownHandler); - this._keyDownHandler = null; - } - } - - /** - * 获取选区对象 - * @returns {Object} 选区对象 - */ - getSelectionObject() { - return this.selectionObject; - } - - /** - * 获取选区路径 - * @returns {Array|String} 选区路径数据 - */ - getSelectionPath() { - return this.selectionPath; - } - - /** - * 获取羽化值 - * @returns {Number} 羽化值 - */ - getFeatherAmount() { - return this.featherAmount; - } - - /** - * 设置羽化值 - * @param {Number} amount 羽化值 - */ - setFeatherAmount(amount) { - this.featherAmount = amount; - return this.updateSelectionAppearance(); - } - - /** - * 设置选区对象 - * @param {Object} object 选区对象 - */ - setSelectionObject(object) { - // 如果已存在选区,先移除 - if (this.selectionObject) { - this.removeSelectionFromCanvas(); - } - - // 更新选区对象 - this.selectionObject = object; - this.selectionPath = object.path; - this.selectionId = object.id || generateId(); - - // 更新外观 - this.updateSelectionAppearance(); - - // 添加到画布(确保在顶层) - if (this.canvas && this.selectionObject) { - this.canvas.add(this.selectionObject); - this.canvas.bringToFront(this.selectionObject); - this.canvas.renderAll(); - } - - // 触发选区变化回调 - if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") { - this.onSelectionChanged(); - } - - return true; - } - - /** - * 从路径数据设置选区 - * @param {Array|String} path 选区路径数据 - */ - setSelectionFromPath(path) { - if (!path) return false; - - // 创建选区对象 - const selectionObj = new fabric.Path(path, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - }); - - // 设置选区 - return this.setSelectionObject(selectionObj); - } - - /** - * 更新选区外观 - */ - updateSelectionAppearance() { - if (!this.selectionObject) return false; - - // 应用基本样式 - Object.assign(this.selectionObject, this.selectionStyle); - - // 应用羽化效果 - if (this.featherAmount > 0) { - this.selectionObject.shadow = new fabric.Shadow({ - color: "rgba(0, 150, 255, 0.5)", - blur: this.featherAmount, - offsetX: 0, - offsetY: 0, - }); - } else { - this.selectionObject.shadow = null; - } - - // 更新画布 - this.canvas.renderAll(); - return true; - } - - /** - * 移除选区 - */ - removeSelectionFromCanvas() { - if (this.canvas && this.selectionObject) { - this.canvas.remove(this.selectionObject); - this.canvas.renderAll(); - } - } - - /** - * 清除选区 - */ - clearSelection() { - // 移除选区对象 - this.removeSelectionFromCanvas(); - - // 重置选区状态 - this.selectionObject = null; - this.selectionPath = null; - this.selectionId = null; - this.featherAmount = 0; - - // 触发选区变化回调 - if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") { - this.onSelectionChanged(); - } - - return true; - } - - /** - * 反转选区 - */ - async invertSelection() { - if (!this.canvas || !this.selectionObject) return false; - - // 获取画布范围 - const canvasRect = new fabric.Rect({ - left: 0, - top: 0, - width: this.canvas.width, - height: this.canvas.height, - selectable: false, - }); - - // 创建反选路径 - let invertedPath; - try { - invertedPath = canvasRect.subtractPathFromRect(this.selectionObject.path); - } catch (error) { - console.error("无法反转选区:", error); - return false; - } - - // 设置新的选区 - const newSelection = new fabric.Path(invertedPath.path, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - }); - - return this.setSelectionObject(newSelection); - } - - /** - * 添加到选区 - * @param {Object} newSelection 要添加的选区对象 - */ - async addToSelection(newSelection) { - if (!this.canvas) return false; - - // 如果当前没有选区,直接使用新选区 - if (!this.selectionObject) { - return this.setSelectionObject(newSelection); - } - - // 合并选区 - let combinedPath; - try { - combinedPath = this.selectionObject.union(newSelection); - } catch (error) { - console.error("无法添加到选区:", error); - return false; - } - - // 设置新的选区 - const combinedSelection = new fabric.Path(combinedPath.path, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - }); - - return this.setSelectionObject(combinedSelection); - } - - /** - * 从选区中移除 - * @param {Object} removeSelection 要移除的选区对象 - */ - async removeFromSelection(removeSelection) { - if (!this.canvas || !this.selectionObject) return false; - - // 从当前选区中减去新选区 - let resultPath; - try { - resultPath = this.selectionObject.subtract(removeSelection); - } catch (error) { - console.error("无法从选区中移除:", error); - return false; - } - - // 设置新的选区 - const newSelection = new fabric.Path(resultPath.path, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - }); - - return this.setSelectionObject(newSelection); - } - - /** - * 应用羽化效果 - * @param {Number} amount 羽化值 - */ - async featherSelection(amount) { - if (!this.selectionObject) return false; - - // 更新羽化值 - this.featherAmount = amount; - - // 更新选区外观 - return this.updateSelectionAppearance(); - } - - /** - * 检查对象是否在选区内 - * @param {Object} object 要检查的对象 - * @returns {Boolean} 是否在选区内 - */ - isObjectInSelection(object) { - if (!this.selectionObject || !object) return false; - - // 获取对象的边界框 - const bounds = object.getBoundingRect(); - const { left, top, width, height } = bounds; - - // 检查对象的中心点和四个角是否在选区内 - const centerX = left + width / 2; - const centerY = top + height / 2; - - // 检查中心点 - if (this.isPointInSelection(centerX, centerY)) return true; - - // 检查四个角 - if (this.isPointInSelection(left, top)) return true; - if (this.isPointInSelection(left + width, top)) return true; - if (this.isPointInSelection(left, top + height)) return true; - if (this.isPointInSelection(left + width, top + height)) return true; - - return false; - } - - /** - * 检查点是否在选区内 - * @param {Number} x X坐标 - * @param {Number} y Y坐标 - * @returns {Boolean} 是否在选区内 - */ - isPointInSelection(x, y) { - if (!this.selectionObject) return false; - - // 使用fabric.js的containsPoint方法判断点是否在选区内 - return this.selectionObject.containsPoint({ x, y }); - } - - /** - * 开始自由选区 - * @param {Object} options 事件对象 - */ - startFreeSelection(options) { - if (!this.canvas || !this.isActive) return; - - // 获取鼠标位置 - const pointer = this.canvas.getPointer(options.e); - this.startPoint = pointer; - - // 创建用于绘制轨迹的点数组 - this.drawingPoints = [pointer]; - - // 初始化SVG路径字符串 - this.currentPathString = `M ${pointer.x} ${pointer.y}`; - - // 创建临时路径对象用于实时显示 - this.drawingObject = new fabric.Path(this.currentPathString, { - stroke: this.selectionStyle.stroke, - strokeWidth: this.selectionStyle.strokeWidth, - strokeDashArray: this.selectionStyle.strokeDashArray, - fill: "transparent", - selectable: false, - evented: false, - strokeLineCap: "round", - strokeLineJoin: "round", - }); - - // 添加到画布 - this.canvas.add(this.drawingObject); - this.canvas.renderAll(); - } - - /** - * 绘制自由选区 - * @param {Object} options 事件对象 - */ - drawFreeSelection(options) { - if (!this.drawingObject || !this.drawingPoints || !this.isActive) return; - - // 获取鼠标位置 - const pointer = this.canvas.getPointer(options.e); - - // 添加新的点,但避免添加过于密集的点 - const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; - const distance = Math.sqrt( - Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.y - lastPoint.y, 2) - ); - - // 只有当距离大于2像素时才添加新点,避免路径过于复杂 - if (distance > 2) { - this.drawingPoints.push(pointer); - - // 更新路径字符串 - this.currentPathString += ` L ${pointer.x} ${pointer.y}`; - - // 移除旧的绘制对象 - this.canvas.remove(this.drawingObject); - - // 创建新的路径对象 - this.drawingObject = new fabric.Path(this.currentPathString, { - stroke: this.selectionStyle.stroke, - strokeWidth: this.selectionStyle.strokeWidth, - strokeDashArray: this.selectionStyle.strokeDashArray, - fill: "transparent", - selectable: false, - evented: false, - strokeLineCap: "round", - strokeLineJoin: "round", - }); - - // 重新添加到画布 - this.canvas.add(this.drawingObject); - this.canvas.renderAll(); - } - } - - /** - * 结束自由选区 - */ - endFreeSelection() { - if (!this.drawingObject || !this.drawingPoints || !this.isActive) return; - - // 检查是否有足够的点来形成选区 - if (this.drawingPoints.length < 3) { - // 点太少,清除绘制对象 - this.canvas.remove(this.drawingObject); - this.drawingObject = null; - this.drawingPoints = null; - this.startPoint = null; - this.currentPathString = null; - return; - } - - // 自动闭合路径 - 连接最后一点到第一点 - const firstPoint = this.drawingPoints[0]; - const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; - const closingDistance = Math.sqrt( - Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2) - ); - - // 如果首尾距离较大,自动添加闭合线段 - let finalPathString = this.currentPathString; - if (closingDistance > 10) { - finalPathString += ` L ${firstPoint.x} ${firstPoint.y}`; - } - finalPathString += " Z"; // 闭合路径 - - // 创建最终选区对象 - const selectionObj = new fabric.Path(finalPathString, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - fill: this.selectionStyle.fill, // 恢复填充 - }); - - // 移除绘制中的临时对象 - this.canvas.remove(this.drawingObject); - - // 重置绘制状态 - this.drawingObject = null; - this.drawingPoints = null; - this.startPoint = null; - this.currentPathString = null; - - // 设置选区 - this.setSelectionObject(selectionObj); - } - - /** - * 开始矩形选区 - * @param {Object} options 事件对象 - */ - startRectangleSelection(options) { - if (!this.canvas || !this.isActive) return; - - // 获取鼠标位置 - const pointer = this.canvas.getPointer(options.e); - this.startPoint = pointer; - - // 创建矩形对象 - this.drawingObject = new fabric.Rect({ - left: pointer.x, - top: pointer.y, - width: 0, - height: 0, - ...this.selectionStyle, - fill: "transparent", // 在绘制过程中不显示填充 - }); - - // 添加到画布 - this.canvas.add(this.drawingObject); - this.canvas.renderAll(); - } - - /** - * 绘制矩形选区 - * @param {Object} options 事件对象 - */ - drawRectangleSelection(options) { - if (!this.drawingObject || !this.startPoint || !this.isActive) return; - - // 获取鼠标位置 - const pointer = this.canvas.getPointer(options.e); - - // 计算宽度和高度 - const width = Math.abs(pointer.x - this.startPoint.x); - const height = Math.abs(pointer.y - this.startPoint.y); - - // 确定左上角坐标 - const left = Math.min(this.startPoint.x, pointer.x); - const top = Math.min(this.startPoint.y, pointer.y); - - // 更新矩形 - this.drawingObject.set({ - left: left, - top: top, - width: width, - height: height, - }); - - this.canvas.renderAll(); - } - - /** - * 结束矩形选区 - */ - endRectangleSelection() { - if (!this.drawingObject || !this.startPoint || !this.isActive) return; - - // 将矩形转换为路径 - const left = this.drawingObject.left; - const top = this.drawingObject.top; - const width = this.drawingObject.width; - const height = this.drawingObject.height; - - // 如果矩形太小,忽略 - if (width < 5 || height < 5) { - this.canvas.remove(this.drawingObject); - this.drawingObject = null; - this.startPoint = null; - return; - } - - // 创建矩形路径字符串 - const pathString = `M ${left} ${top} L ${left + width} ${top} L ${ - left + width - } ${top + height} L ${left} ${top + height} Z`; - - // 创建最终选区对象 - const selectionObj = new fabric.Path(pathString, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - fill: this.selectionStyle.fill, // 恢复填充 - }); - - // 移除绘制中的临时对象 - this.canvas.remove(this.drawingObject); - - // 重置绘制状态 - this.drawingObject = null; - this.startPoint = null; - - // 设置选区 - this.setSelectionObject(selectionObj); - } - - /** - * 开始椭圆选区 - * @param {Object} options 事件对象 - */ - startEllipseSelection(options) { - if (!this.canvas || !this.isActive) return; - - // 获取鼠标位置 - const pointer = this.canvas.getPointer(options.e); - this.startPoint = pointer; - - // 创建椭圆对象 - this.drawingObject = new fabric.Ellipse({ - left: pointer.x, - top: pointer.y, - rx: 0, - ry: 0, - ...this.selectionStyle, - fill: "transparent", // 在绘制过程中不显示填充 - // originX: "left", - // originY: "top", - originX: "center", - originY: "center", - }); - - // 添加到画布 - this.canvas.add(this.drawingObject); - this.canvas.renderAll(); - } - - /** - * 绘制椭圆选区 - * @param {Object} options 事件对象 - */ - drawEllipseSelection(options) { - if (!this.drawingObject || !this.startPoint || !this.isActive) return; - - // 获取鼠标位置 - const pointer = this.canvas.getPointer(options.e); - - // 计算半径 - const rx = Math.abs(pointer.x - this.startPoint.x) / 2; - const ry = Math.abs(pointer.y - this.startPoint.y) / 2; - - // 确定中心坐标 - const left = Math.min(this.startPoint.x, pointer.x); - const top = Math.min(this.startPoint.y, pointer.y); - - // 更新椭圆 - this.drawingObject.set({ - left: left, - top: top, - rx: rx, - ry: ry, - originX: "left", - originY: "top", - }); - - this.canvas.renderAll(); - } - - /** - * 结束椭圆选区 - */ - endEllipseSelection() { - if (!this.drawingObject || !this.startPoint || !this.isActive) return; - - // 获取椭圆参数 - const { left, top, rx, ry } = this.drawingObject; - - // 如果椭圆太小,忽略 - if (rx < 2 || ry < 2) { - this.canvas.remove(this.drawingObject); - this.drawingObject = null; - this.startPoint = null; - return; - } - - // 计算中心点 - const cx = left + rx; - const cy = top + ry; - - // 将椭圆转换为路径字符串 - const pathString = this.ellipseToSVGPath(cx, cy, rx, ry); - - // 创建最终选区对象 - const selectionObj = new fabric.Path(pathString, { - ...this.selectionStyle, - id: `selection_${Date.now()}`, - name: "selection", - fill: this.selectionStyle.fill, // 恢复填充 - }); - - // 移除绘制中的临时对象 - this.canvas.remove(this.drawingObject); - - // 重置绘制状态 - this.drawingObject = null; - this.startPoint = null; - - // 设置选区 - this.setSelectionObject(selectionObj); - } - - /** - * 将椭圆转换为SVG路径字符串 - * @param {Number} cx 中心点X坐标 - * @param {Number} cy 中心点Y坐标 - * @param {Number} rx X半径 - * @param {Number} ry Y半径 - * @returns {String} SVG路径字符串 - */ - ellipseToSVGPath(cx, cy, rx, ry) { - // 使用椭圆弧命令创建完整椭圆 - return `M ${cx - rx} ${cy} A ${rx} ${ry} 0 1 0 ${ - cx + rx - } ${cy} A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`; - } - - /** - * 设置选区工具 - * @param {string} type 选区类型:OperationType.LASSO, OperationType.LASSO_RECTANGLE, OperationType.LASSO_ELLIPSE - */ - setSelectionType(type) { - this.selectionType = type; - - // 如果正在绘制,清除临时对象 - if (this.drawingObject) { - this.canvas.remove(this.drawingObject); - this.drawingObject = null; - this.startPoint = null; - } - } - - /** - * 设置选区工具的鼠标事件 - */ - setupSelectionEvents() { - // 选区事件现在通过 setCurrentTool 方法管理 - // 这个方法现在主要用于刷新或重置事件监听 - if (!this.canvas || !this.isActive) return; - - // 确保选区处于激活状态 - if (this.tools.includes(this.currentTool)) { - this.isActive = true; - // 如果事件还没有初始化,初始化它们 - if (!this._mouseDownHandler) { - this.initEvents(); - } - } - } - - /** - * 清理资源 - */ - dispose() { - this.cleanupEvents(); - this.clearSelection(); - this.canvas = null; - this.commandManager = null; - this.layerManager = null; - } + /** + * 创建部件选择管理器 + * @param {Object} options 配置选项 + * @param {Object} options.canvas fabric.js画布实例 + * @param {Object} options.commandManager 命令管理器实例 + * @param {Object} options.canvasManager 画布管理实例 + * @param {Object} options.layerManager 图层管理实例 + * @param {Object} options.toolManager 工具管理实例 + */ + constructor(options = {}) { + this.canvas = options.canvas; + this.commandManager = options.commandManager; + this.layerManager = options.layerManager; + this.canvasManager = options.canvasManager; + this.toolManager = options.toolManager; + + // 状态 + this.isActive = false; + this.partObject = null; // 当前选区对象 + this.partId = "part_selector"; + this.defaultCursor = "default"; + + // 绘制状态 + this.drawingObject = null; + this.startPoint = null; + this.partPath = null; // 存储选区路径数据 + + + // 不再直接绑定事件处理函数 + this._mouseDownHandler = null; + this._mouseMoveHandler = null; + this._mouseUpHandler = null; + this._keyDownHandler = null; + + // 选区相关的工具类型 + this.tools = [ + OperationType.PART, + OperationType.PART_RECTANGLE, + OperationType.PART_BRUSH, + OperationType.PART_ERASER, + ]; + + // 当前工具 + this.activeTool = this.toolManager.activeTool; + + // 选区状态变化回调 + this.onSelectionChanged = null; + } + + /** + * 设置当前工具 + * @param {String} toolId 工具ID + */ + setCurrentTool(toolId) { + // 检查是否为选区工具 + const wasActive = this.isActive; + this.isActive = this.tools.includes(toolId); + + // 如果从非选区工具切换到选区工具,初始化事件 + if (!wasActive && this.isActive) { + this.initEvents(); + } + // 如果从选区工具切换到非选区工具,清理事件和选区 + else if (wasActive && !this.isActive) { + this.cleanupEvents(); + this.clearSelection(); + } + } + + /** + * 初始化选区相关事件 + */ + initEvents() { + if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化 + this.defaultCursor = this.canvas.defaultCursor; + + // 保存实例引用,用于事件处理函数中 + const self = this; + + // 鼠标按下事件处理 + this._mouseDownHandler = (options) => { + // 如果选区功能未激活,不处理事件 + if (!this.isActive) return; + // 阻止事件冒泡,避免与 CanvasEventManager 冲突 + options.e.stopPropagation(); + switch (this.activeTool.value) { + case OperationType.PART: + this._pointDownkHandler(options); + break; + case OperationType.PART_RECTANGLE: + this._rectangleDownHandler(options); + break; + case OperationType.PART_BRUSH: + this._brushDownHandler(options); + break; + case OperationType.PART_ERASER: + this._eraseDownHandler(options); + break; + + default: + break; + } + }; + + // 鼠标移动事件处理 + this._mouseMoveHandler = (options) => { + // 如果选区功能未激活或没有正在绘制的对象,不处理事件 + if (!this.isActive) return; + // 阻止事件冒泡 + options.e.stopPropagation(); + switch (this.activeTool.value) { + case OperationType.PART: + this._pointMoveHandler(options); + break; + case OperationType.PART_RECTANGLE: + this._rectangleMoveHandler(options); + break; + case OperationType.PART_BRUSH: + this._brushMoveHandler(options); + break; + case OperationType.PART_ERASER: + this._eraseMoveHandler(options); + break; + + default: + break; + } + }; + + // 鼠标抬起事件处理 + this._mouseUpHandler = (options) => { + // 如果选区功能未激活或没有正在绘制的对象,不处理事件 + if (!this.isActive) return; + // 阻止事件冒泡 + if (options && options.e) { + options.e.stopPropagation(); + } + switch (this.activeTool.value) { + case OperationType.PART: + this._pointUpHandler(options); + break; + case OperationType.PART_RECTANGLE: + this._rectangleUpHandler(options); + break; + case OperationType.PART_BRUSH: + this._brushUpHandler(options); + break; + case OperationType.PART_ERASER: + this._eraseUpHandler(options); + break; + + default: + break; + } + }; + + // 键盘事件处理 + this._keyDownHandler = (event) => { + // 只在选区功能激活时处理键盘事件 + if (!this.isActive) return; + }; + + // 添加事件监听 + this.canvas.on("mouse:down", this._mouseDownHandler); + this.canvas.on("mouse:move", this._mouseMoveHandler); + this.canvas.on("mouse:up", this._mouseUpHandler); + + // 添加键盘事件监听 + document.addEventListener("keydown", this._keyDownHandler); + } + + /** + * 清理事件监听 + */ + cleanupEvents() { + if (!this.canvas) return; + + // 移除事件监听 + if (this._mouseDownHandler) { + this.canvas.off("mouse:down", this._mouseDownHandler); + this._mouseDownHandler = null; + } + if (this._mouseMoveHandler) { + this.canvas.off("mouse:move", this._mouseMoveHandler); + this._mouseMoveHandler = null; + } + if (this._mouseUpHandler) { + this.canvas.off("mouse:up", this._mouseUpHandler); + this._mouseUpHandler = null; + } + if (this._keyDownHandler) { + document.removeEventListener("keydown", this._keyDownHandler); + this._keyDownHandler = null; + } + } + + // 点选工具模式下点击事件处理 + _pointDownkHandler(options) { + const button = options.button; + const isLeft = button === 1;// 左键1(添加) 右键3(删除) + const icon = `url("${isLeft ? addIcon : removeIcon}") 16 16, default` + this.canvas.upperCanvasEl.style.cursor = icon; + } + // 点选工具模式下移动事件处理 + _pointMoveHandler(options) { + + } + // 点选工具模式下抬起事件处理 + _pointUpHandler(options) { + const button = options.button; + const isLeft = button === 1;// 左键1(添加) 右键3(删除) + this.canvas.upperCanvasEl.style.cursor = this.defaultCursor; + const { x, y } = options.pointer; + const fixedObject = this.canvasManager.getFixedLayerObject({ x, y }); + console.log("==========", fixedObject) + } + + + // 框选工具模式下点击事件处理 + _rectangleDownHandler(options) { + } + // 框选工具模式下移动事件处理 + _rectangleMoveHandler(options) { + + } + // 框选工具模式下抬起事件处理 + _rectangleUpHandler(options) { + } + + + // 绘制工具模式下点击事件处理 + _brushDownHandler(options) { + } + // 绘制工具模式下移动事件处理 + _brushMoveHandler(options) { + + } + // 绘制工具模式下抬起事件处理 + _brushUpHandler(options) { + } + + + // 擦除工具模式下抬起事件处理 + _eraseUpHandler(options) { + } + // 擦除工具模式下点击事件处理 + _eraseDownHandler(options) { + } + // 擦除工具模式下移动事件处理 + _eraseMoveHandler(options) { + + } + + + + /** + * 清除选区 + */ + clearSelection() { + // 移除选区对象 + // this.removeSelectionFromCanvas(); + + // 重置选区状态 + this.partObject = null; + this.partPath = null; + + // 触发选区变化回调 + if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") { + this.onSelectionChanged(); + } + + return true; + } + + /** + * 清理资源 + */ + dispose() { + this.cleanupEvents(); + this.clearSelection(); + this.canvas = null; + this.commandManager = null; + this.layerManager = null; + } } diff --git a/src/component/Canvas/CanvasEditor/managers/ToolManager.js b/src/component/Canvas/CanvasEditor/managers/ToolManager.js index db048368..3f8949f9 100644 --- a/src/component/Canvas/CanvasEditor/managers/ToolManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ToolManager.js @@ -197,19 +197,19 @@ export class ToolManager { name: "部件选取工具-矩形", icon: "part", cursor: "default", - setup: this.setupPartTool.bind(this), + setup: this.setupPartRectangleTool.bind(this), }, [OperationType.PART_BRUSH]: { name: "部件选取工具-画笔", icon: "part", cursor: "default", - setup: this.setupPartTool.bind(this), + setup: this.setupPartBrushTool.bind(this), }, [OperationType.PART_ERASER]: { name: "部件选取工具-橡皮擦", icon: "part", cursor: "default", - setup: this.setupPartTool.bind(this), + setup: this.setupPartEraserTool.bind(this), }, // 红绿图模式专用工具 @@ -705,14 +705,47 @@ export class ToolManager { */ setupPartTool() { if (!this.canvas) return; - if (this.checkToolCanOperateSelectedObject()) return; this.canvas.isDrawingMode = false; this.canvas.selection = false; - + if (this.canvasManager && this.canvasManager.partManager) { this.canvasManager.partManager.setCurrentTool(OperationType.PART); } } + /** + * 设置部件选取工具--矩形 + */ + setupPartRectangleTool() { + if (!this.canvas) return; + this.canvas.isDrawingMode = false; + this.canvas.selection = true; + if (this.canvasManager && this.canvasManager.partManager) { + this.canvasManager.partManager.setCurrentTool(OperationType.PART_RECTANGLE); + } + } + /** + * 设置部件选取工具--画笔 + */ + setupPartBrushTool() { + if (!this.canvas) return; + this.canvas.isDrawingMode = true; + this.canvas.selection = false; + if (this.canvasManager && this.canvasManager.partManager) { + this.canvasManager.partManager.setCurrentTool(OperationType.PART_BRUSH); + } + } + /** + * 设置部件选取工具--橡皮擦 + */ + setupPartEraserTool() { + if (!this.canvas) return; + this.canvas.isDrawingMode = false; + this.canvas.selection = false; + if (this.canvasManager && this.canvasManager.partManager) { + this.canvasManager.partManager.setCurrentTool(OperationType.PART_ERASER); + } + } + /** * 设置波浪工具 diff --git a/src/component/Canvas/OverallCanvas/demo.vue b/src/component/Canvas/OverallCanvas/demo.vue index dd5331e5..8c378d4b 100644 --- a/src/component/Canvas/OverallCanvas/demo.vue +++ b/src/component/Canvas/OverallCanvas/demo.vue @@ -79,7 +79,9 @@ type="number" v-model="item.object.scaleX" step="0.1" - @input="updateList(item, 'object.scaleX', item.object.scaleX)" + @input=" + updateList(item, 'object.scaleX', item.object.scaleX) + " />
@@ -88,7 +90,9 @@ type="number" v-model="item.object.scaleY" step="0.1" - @input="updateList(item, 'object.scaleY', item.object.scaleY)" + @input=" + updateList(item, 'object.scaleY', item.object.scaleY) + " />
@@ -124,7 +128,9 @@ step="0.1" min="0" max="1" - @input="updateList(item, 'object.opacity', item.object.opacity)" + @input=" + updateList(item, 'object.opacity', item.object.opacity) + " />
@@ -228,6 +234,8 @@ } } else if (item.action === ACTIONS.SELECT) { activeToken.value = item.token; + } else if (item.action === ACTIONS.DELETE) { + list.value = list.value.filter((v) => v.token !== item.token); } }); }; @@ -284,7 +292,7 @@ }; // 监听列表变化属性变更 const updateList = (item, key, value) => { - if(key === "scale[0]") item.scale[1] = value; + if (key === "scale[0]") item.scale[1] = value; pingpuRef.value.updataList([ { token: item.token, diff --git a/src/component/Canvas/OverallCanvas/index.vue b/src/component/Canvas/OverallCanvas/index.vue index 0bc44351..2feedef1 100644 --- a/src/component/Canvas/OverallCanvas/index.vue +++ b/src/component/Canvas/OverallCanvas/index.vue @@ -144,6 +144,13 @@ const list = [{ token, action: ACTIONS.SELECT }]; emit("change-canvas", list); }; + // 删除对象 + const onDeleteItem = (object) => { + const list = [{ token: object.token, action: ACTIONS.DELETE }]; + emit("change-canvas", list); + canvas.remove(object); + canvas.renderAll(); + }; const urlToCanvas = (url) => { return new Promise((resolve, reject) => { fabric.Image.fromURL( @@ -181,6 +188,7 @@ height: cheight, fill: pattern, ...item.object, + onDelete: (v) => onDeleteItem(v), }); canvas.add(rect); }; diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index 4d270e12..be04b46a 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -335,15 +335,15 @@ const otherData = { color: {rgba: {r:255,g:0,b:0,a:1}}, printObject: { prints: [ - { - ifSingle: false, - level2Type: "Pattern", - designType: "Library", - path: "/src/assets/images/canvas/yinhua1.jpg", - location: [250, 780], - scale: [0.3, 0.4], - angle: 0, - }, + // { + // ifSingle: false, + // level2Type: "Pattern", + // designType: "Library", + // path: "/src/assets/images/canvas/yinhua1.jpg", + // location: [250, 780], + // scale: [0.3, 0.4], + // angle: 0, + // }, { ifSingle: true, level2Type: "Pattern", diff --git a/src/component/Canvas/test.vue b/src/component/Canvas/test.vue index 097f2571..1433898e 100644 --- a/src/component/Canvas/test.vue +++ b/src/component/Canvas/test.vue @@ -5,11 +5,13 @@ --> +