很多很多
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() {
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置波浪工具
|
||||
|
||||
@@ -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);
|
||||
|
||||