很多很多
17
src/assets/icons/CBrush2.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="201.000000pt" height="200.000000pt" viewBox="0 0 201.000000 200.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1654 1990 c-26 -8 -153 -110 -162 -129 -1 -3 81 -89 183 -191 l185
|
||||
-185 56 55 c30 30 62 67 70 82 22 41 18 123 -8 176 -29 57 -126 158 -169 176
|
||||
-47 20 -118 27 -155 16z"/>
|
||||
<path d="M868 1243 c-525 -527 -498 -492 -568 -725 -54 -179 -59 -219 -30
|
||||
-248 19 -19 29 -21 64 -16 113 18 361 105 431 152 28 18 251 235 498 481 l447
|
||||
448 -188 188 -187 187 -467 -467z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 799 B |
17
src/assets/icons/CEraser2.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M757 1273 c-214 -214 -402 -406 -418 -427 -32 -43 -37 -82 -15 -124
|
||||
23 -44 327 -338 376 -363 l45 -24 463 -3 462 -3 0 46 0 45 -342 0 -343 0 342
|
||||
343 c187 188 346 352 352 364 16 32 13 74 -7 105 -9 14 -110 116 -224 227
|
||||
l-207 201 -47 0 -48 0 -389 -387z m207 -342 l209 -209 -128 -131 c-76 -78
|
||||
-144 -138 -169 -151 -51 -24 -106 -26 -149 -4 -41 20 -337 314 -337 334 0 13
|
||||
347 370 360 370 3 0 99 -94 214 -209z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 847 B |
20
src/assets/icons/CMarquee.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M250 1545 l0 -205 80 0 80 0 0 -340 0 -340 -80 0 -80 0 0 -205 0
|
||||
-205 205 0 205 0 0 80 0 80 340 0 340 0 0 -80 0 -80 205 0 205 0 0 205 0 205
|
||||
-80 0 -80 0 0 340 0 340 80 0 80 0 0 205 0 205 -205 0 -205 0 0 -80 0 -80
|
||||
-340 0 -340 0 0 80 0 80 -205 0 -205 0 0 -205z m320 0 l0 -125 -120 0 -120 0
|
||||
0 125 0 125 120 0 120 0 0 -125z m1100 0 l0 -125 -120 0 -120 0 0 125 0 125
|
||||
120 0 120 0 0 -125z m-330 -125 l0 -80 85 0 85 0 0 -340 0 -340 -85 0 -85 0 0
|
||||
-80 0 -80 -340 0 -340 0 0 80 0 80 -85 0 -85 0 0 340 0 340 85 0 85 0 0 80 0
|
||||
80 340 0 340 0 0 -80z m-770 -965 l0 -125 -120 0 -120 0 0 125 0 125 120 0
|
||||
120 0 0 -125z m1100 0 l0 -125 -120 0 -120 0 0 125 0 125 120 0 120 0 0 -125z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/canvas/add.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/assets/images/canvas/remove.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/images/canvas/shubiao-l.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/assets/images/canvas/shubiao-r.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
@@ -15,6 +15,20 @@
|
||||
{{ t("Canvas.GarmentPartSelector") }}
|
||||
</div>
|
||||
<!-- 移除关闭按钮,完全通过工具切换控制显示隐藏 -->
|
||||
<div class="tip">
|
||||
<div>
|
||||
<img
|
||||
src="/src/assets/images/canvas/shubiao-l.png"
|
||||
/>
|
||||
<span>Left Click: Add</span>
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
src="/src/assets/images/canvas/shubiao-r.png"
|
||||
/>
|
||||
<span>Right Click: Remove</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-types">
|
||||
@@ -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)"
|
||||
>
|
||||
<svg-icon :name="item.icon" :size="item.size" />
|
||||
<span>{{ item.label }}</span>
|
||||
@@ -37,24 +51,18 @@
|
||||
|
||||
<!-- 底部选区操作工具栏 -->
|
||||
<div class="tool-actions">
|
||||
<div class="action-btn" @click="copySelectionToNewLayer">
|
||||
<div class="action-btn" @click="onCreate">
|
||||
<svg-icon name="CPaste" size="16" />
|
||||
<span class="btn-text">{{
|
||||
$t("Canvas.creation")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="action-btn" @click="cutSelectionToNewLayer">
|
||||
<div class="action-btn" @click="onCopyCreate">
|
||||
<svg-icon name="CCut" size="26" />
|
||||
<span class="btn-text">{{
|
||||
$t("Canvas.CreateAndCopy")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="action-btn" @click="clearSelectionContent">
|
||||
<svg-icon name="CClear" size="18" />
|
||||
<span class="btn-text">{{
|
||||
$t("Canvas.TheClearlySelectedContent")
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -290,6 +308,28 @@
|
||||
// border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 8px 8px 0 0;
|
||||
position: relative;
|
||||
> .tip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
> img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
> span {
|
||||
font-size: 10px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@@ -370,9 +410,9 @@
|
||||
|
||||
.tool-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 5px;
|
||||
padding: 0 10px;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
/* 平板适配 - 每行4个按钮 */
|
||||
@@ -436,5 +476,4 @@
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -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() {
|
||||
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) {
|
||||
|
||||
@@ -3,6 +3,8 @@ 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";
|
||||
|
||||
/**
|
||||
* 部件选择管理器
|
||||
@@ -13,41 +15,28 @@ export class PartManager {
|
||||
* @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.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.partObject = null; // 当前选区对象
|
||||
this.partId = "part_selector";
|
||||
this.defaultCursor = "default";
|
||||
|
||||
// 绘制状态
|
||||
this.drawingObject = null;
|
||||
this.startPoint = null;
|
||||
this.selectionPath = null; // 存储选区路径数据
|
||||
this.partPath = null; // 存储选区路径数据
|
||||
|
||||
// 自由选区相关状态
|
||||
this.drawingPoints = null;
|
||||
this.currentPathString = null;
|
||||
|
||||
// 不再直接绑定事件处理函数
|
||||
this._mouseDownHandler = null;
|
||||
@@ -64,13 +53,10 @@ export class PartManager {
|
||||
];
|
||||
|
||||
// 当前工具
|
||||
this.currentTool = OperationType.SELECT;
|
||||
this.activeTool = this.toolManager.activeTool;
|
||||
|
||||
// 选区状态变化回调
|
||||
this.onSelectionChanged = null;
|
||||
|
||||
// 不再自动初始化事件,改为手动控制
|
||||
// this.initEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,8 +64,6 @@ export class PartManager {
|
||||
* @param {String} toolId 工具ID
|
||||
*/
|
||||
setCurrentTool(toolId) {
|
||||
this.currentTool = toolId;
|
||||
|
||||
// 检查是否为选区工具
|
||||
const wasActive = this.isActive;
|
||||
this.isActive = this.tools.includes(toolId);
|
||||
@@ -93,11 +77,6 @@ export class PartManager {
|
||||
this.cleanupEvents();
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
// 根据工具类型设置选区类型
|
||||
if (this.isActive) {
|
||||
this.selectionType = toolId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +84,7 @@ export class PartManager {
|
||||
*/
|
||||
initEvents() {
|
||||
if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化
|
||||
this.defaultCursor = this.canvas.defaultCursor;
|
||||
|
||||
// 保存实例引用,用于事件处理函数中
|
||||
const self = this;
|
||||
@@ -113,30 +93,23 @@ export class PartManager {
|
||||
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.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;
|
||||
|
||||
// 根据选区类型执行不同的起始操作
|
||||
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);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -144,21 +117,24 @@ export class PartManager {
|
||||
// 鼠标移动事件处理
|
||||
this._mouseMoveHandler = (options) => {
|
||||
// 如果选区功能未激活或没有正在绘制的对象,不处理事件
|
||||
if (!this.isActive || !this.drawingObject) return;
|
||||
|
||||
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;
|
||||
|
||||
// 根据选区类型执行不同的绘制操作
|
||||
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);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -166,36 +142,27 @@ export class PartManager {
|
||||
// 鼠标抬起事件处理
|
||||
this._mouseUpHandler = (options) => {
|
||||
// 如果选区功能未激活或没有正在绘制的对象,不处理事件
|
||||
if (!this.isActive || !this.drawingObject) return;
|
||||
|
||||
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;
|
||||
|
||||
// 根据选区类型执行不同的完成操作
|
||||
switch (this.selectionType) {
|
||||
case OperationType.LASSO_RECTANGLE:
|
||||
this.endRectangleSelection();
|
||||
default:
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,27 +170,6 @@ export class PartManager {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 添加事件监听
|
||||
@@ -260,138 +206,75 @@ export class PartManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选区对象
|
||||
* @returns {Object} 选区对象
|
||||
*/
|
||||
getSelectionObject() {
|
||||
return this.selectionObject;
|
||||
// 点选工具模式下点击事件处理
|
||||
_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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选区路径
|
||||
* @returns {Array|String} 选区路径数据
|
||||
*/
|
||||
getSelectionPath() {
|
||||
return this.selectionPath;
|
||||
|
||||
// 框选工具模式下点击事件处理
|
||||
_rectangleDownHandler(options) {
|
||||
}
|
||||
// 框选工具模式下移动事件处理
|
||||
_rectangleMoveHandler(options) {
|
||||
|
||||
}
|
||||
// 框选工具模式下抬起事件处理
|
||||
_rectangleUpHandler(options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取羽化值
|
||||
* @returns {Number} 羽化值
|
||||
*/
|
||||
getFeatherAmount() {
|
||||
return this.featherAmount;
|
||||
|
||||
// 绘制工具模式下点击事件处理
|
||||
_brushDownHandler(options) {
|
||||
}
|
||||
// 绘制工具模式下移动事件处理
|
||||
_brushMoveHandler(options) {
|
||||
|
||||
}
|
||||
// 绘制工具模式下抬起事件处理
|
||||
_brushUpHandler(options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置羽化值
|
||||
* @param {Number} amount 羽化值
|
||||
*/
|
||||
setFeatherAmount(amount) {
|
||||
this.featherAmount = amount;
|
||||
return this.updateSelectionAppearance();
|
||||
|
||||
// 擦除工具模式下抬起事件处理
|
||||
_eraseUpHandler(options) {
|
||||
}
|
||||
// 擦除工具模式下点击事件处理
|
||||
_eraseDownHandler(options) {
|
||||
}
|
||||
// 擦除工具模式下移动事件处理
|
||||
_eraseMoveHandler(options) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选区对象
|
||||
* @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.removeSelectionFromCanvas();
|
||||
|
||||
// 重置选区状态
|
||||
this.selectionObject = null;
|
||||
this.selectionPath = null;
|
||||
this.selectionId = null;
|
||||
this.featherAmount = 0;
|
||||
this.partObject = null;
|
||||
this.partPath = null;
|
||||
|
||||
// 触发选区变化回调
|
||||
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
|
||||
@@ -401,533 +284,6 @@ export class PartManager {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
|
||||
@@ -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,7 +705,6 @@ export class ToolManager {
|
||||
*/
|
||||
setupPartTool() {
|
||||
if (!this.canvas) return;
|
||||
if (this.checkToolCanOperateSelectedObject()) return;
|
||||
this.canvas.isDrawingMode = false;
|
||||
this.canvas.selection = false;
|
||||
|
||||
@@ -713,6 +712,40 @@ export class ToolManager {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置波浪工具
|
||||
|
||||
@@ -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)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -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)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -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)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
</div>
|
||||
</div> -->
|
||||
<overall-canvas-demo />
|
||||
<div style="width: 100px; height: 100px;position: relative;"><CanvasEditor /></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import CanvasEditor from "./CanvasEditor/index.vue";
|
||||
import OverallCanvasDemo from "./OverallCanvas/demo.vue";
|
||||
const imageUrl = "/src/assets/images/canvas/xiangaofenge.png";
|
||||
const testRef = ref(null);
|
||||
|
||||