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") }}
+
@@ -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 @@
@@ -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 @@
-->