kjDXY2;
z$5*+<2-d`h6-DD)6OboA@i*YhD=!=7ctT+S^xvMhKg~$61?=ttEavE_dDZGb;guEc
z4NC1U%${Q`5u}m6J8=8A-Q@e&8BAw7RJ!qIlNyRGm{{lLea)h`TA<73bw>IeGh-?%
z_YLMhsR`A3I5$2MQ=;aok)^lVP+huGth|=z&;-kqRAi0HY8izO|ydg!O#CQua!{I2n3yg?YidYyh8lzz-R+
z8rLz~r%(XCG_RzUA-(ITHK`ko1+?ttYQG76x<@T3X{rUDtUhSel57`@ydI?Pt5$F>
z%zroc1jmi@_xFFdKxV0mu#qp`1*Z0e!5L=~K1WHFo
z+T+24qIvIEd{ig%%JO>gnNVCnc=(0#?Q>4u8i$DrI*9lNN1!Ts=w)Jtb}kj=4OVz8
zhjm6w1Qpo|?VBpMWAgmdrPn7NXvslhvbP*13D^U#+6`6rGJ)8#piEE;6D@l|av!UJ
zV?QGWSsH+g7m#5g2PrtjxN3Hc|S?(KWN~q(Is*hm2xfK*VbX0L6%1C
zyzLv1*ye7PX)z=R@*G!MWh|VysX*CawY{IdiL~i;Nx2rMcHO0OVqVKy(rt~TWgirD
z^jW>3ZY!*(U&{RoyBw&l+wL*9`Af4TxO$hfo0iU@AV?r&pKc4n&JN__^*LT-n_4sz^%#!s1vRaySkI1{^WP4+iBwqQ!P$
z18^oPuZ
z5@L7`UOaiNIi$YgIv)W0B)(px5;VEyRGtZ~U*;ZPpVxi8gr7W~!e2hkm!h=mJO5Tu
zr%{zLgyQ0=5E@uqTwLf{6sdTcPYel)d|EQy3(Qr1k8>b-eG-`xc|tYv-1yvqOD~v?E?3-db&QQ
z3oTMGPP}Ppb^LDfMKaI4Ha>jD%Ii4Y)G&nx{;du~cf>t*5ql-_N0HFBr{hT{17R@eua?I}#_+Ye-G?b!HENHqT&TD(H(Xv<
zxl&dLg@_iML8e1Cf4;f+US(N#@Vj2A?jguWKk&hTfPUpeSE~zMBTe6#5ba2zXHXFA
zvj?DEwYa){w$bY}ETTPUC!s6wXyY8kMJ!uoEt33CDoo(r+n+VqCQSdvAzb6(&%(#^-VoQa^=e{O!2jvRciqh2z4Y)$HL{pJboq
z8TmwWGFiW_#LUP}OkNcoH}*a-VMR{X!~Fy{h`*+$fyBQO)L}gX$NNxemMi+R)Kt09
zUkwhd@TdCTa2Nb*rSN^$ZRIY-m{(5N=A{@v@`m;@?CIDa((lDRr|kK9pNKa7
zOWL~)!hs?PV06-!NJZl2{t2&tsj<+8T;N?B8=KS7^0Gytx(B6SZH^=uZoo~V1>)pU
zx=C-p*a85+^nZ5&nz1#+FkFZz`a-G>eX~H!qjW$i*S-IkybrPU=pBje9H@~Ou6?z*
zx>^$5)N&%mh5!;vN=m{Y`vKF&7M1?5F2{Wt8-qQ_Fm6uppF>xHSGK&!%dD8Dh3=uu
zEK*zM@)ql(S6!A%Ai-=x#b<)d0l#)y=6bxWs_L1D@_b^xu!9^VtYcf)JWBKJUX{{!#f=jr_t7RlckRK5;|M20M>yFMRRqf~HM98WQ@`
z{DdjcDvz0ib6WG6i3~>->tR*Ty?bF>dL?v%mT5rJn@uYJT>3RXnpTwp
zGg~Jg0qf&5U*^%&BGl=qC)a#dt&KsydgA%m6yLCoP%xFg^r^Jl%G94^S=V^RQ&~C8
zr;a1SP5HfB&zN57Z_cSj9iZ7ib^CYNi}SyoxbG>hiU@d5oX<%7Vc;k5@uZ+%c5X-8
z=FT45W)?rMq)4XtbZ5-A*pArz`+#i=FCOa&Q9q%^Pt#dBOKb<;;VHpSt*sGW>sl&`Bf~nZJW)2shDcJ&Ml2pkGULwD2Q=
z6iK^0!~I_%POb?SvY1O~{P*343L+CwF^b5=0DV!smZK0*kIlN!HwGi92xpNdBzfiD
zQ_gFzvmAx)1iREfudCbTl({<5(1$!$#3dnOjB0aPc9BejubFx+uH?(b
zq>~Z_pujNwR0TNRIk_iBD#b#`CEiBcpN0A!@Q;DHo!!D%QlICDvwsKm#y8ByK`{wz
z`gbS#tWPCA3vIx@6n@o5Y5y-kjd?rOVuTuUr4{8+mK0A*rl!mz!m>-6fmE$Hqm~q=
z5Vlr+=fh`Xe9=-)@>##4uvyh}dba&7H;b8{&5r&N?B`o5o(pM@+7{dMxG$|eti4V;
z*4Uh)EzoW+(zshpdM0X=1CJzAe>ygiKZtc~zEP-1D`XiHSD0Kl@Mn5M<<^hYwpuU#
z7rt8nJTIOvsxTr9GD|=5X>|5TVm0@9fC_s8S(^<}CoNCO0)9SnWWACOvj>_KLg}UL
z$h7o$`9XuYP8XzAwtxR2!;#v>oMf82ylUZgOjah|
zq~(ZoAS)-NIhL%E@15XfJlV4j(BI+Yo63uei6JjGa)ahyeWC(L_SVt$dN3Z75dd1-
K2w9_PkNY3wjLWJ3
literal 0
HcmV?d00001
diff --git a/src/assets/images/canvas/shubiao-r.png b/src/assets/images/canvas/shubiao-r.png
new file mode 100644
index 0000000000000000000000000000000000000000..df70c79d138f37d4780b9550d74c70f65678fab0
GIT binary patch
literal 5384
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
literal 0
HcmV?d00001
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 @@
@@ -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 @@
-->
+