Merge branch 'dev_vite' of ssh://18.167.251.121:10002/aidlab/aida_front into dev_vite

This commit is contained in:
2026-01-16 14:30:53 +08:00
65 changed files with 5028 additions and 1750 deletions

260
package-lock.json generated
View File

@@ -34,6 +34,7 @@
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^9.6.1",
"vue-router": "^4.0.3",
"vue3-moveable": "^0.28.0",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0",
"x-sender": "^1.1.6"
@@ -232,6 +233,15 @@
"node": ">=6.9.0"
}
},
"node_modules/@cfcs/core": {
"version": "0.0.6",
"resolved": "https://registry.npmmirror.com/@cfcs/core/-/core-0.0.6.tgz",
"integrity": "sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==",
"license": "MIT",
"dependencies": {
"@egjs/component": "^3.0.2"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
@@ -240,6 +250,39 @@
"node": ">=10"
}
},
"node_modules/@daybrush/utils": {
"version": "1.13.0",
"resolved": "https://registry.npmmirror.com/@daybrush/utils/-/utils-1.13.0.tgz",
"integrity": "sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==",
"license": "MIT"
},
"node_modules/@egjs/agent": {
"version": "2.4.4",
"resolved": "https://registry.npmmirror.com/@egjs/agent/-/agent-2.4.4.tgz",
"integrity": "sha512-cvAPSlUILhBBOakn2krdPnOGv5hAZq92f1YHxYcfu0p7uarix2C6Ia3AVizpS1SGRZGiEkIS5E+IVTLg1I2Iog==",
"license": "MIT"
},
"node_modules/@egjs/children-differ": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@egjs/children-differ/-/children-differ-1.0.1.tgz",
"integrity": "sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==",
"license": "MIT",
"dependencies": {
"@egjs/list-differ": "^1.0.0"
}
},
"node_modules/@egjs/component": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/@egjs/component/-/component-3.0.5.tgz",
"integrity": "sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==",
"license": "MIT"
},
"node_modules/@egjs/list-differ": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@egjs/list-differ/-/list-differ-1.0.1.tgz",
"integrity": "sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==",
"license": "MIT"
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
@@ -1224,6 +1267,34 @@
"win32"
]
},
"node_modules/@scena/dragscroll": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/@scena/dragscroll/-/dragscroll-1.4.0.tgz",
"integrity": "sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.6.0",
"@scena/event-emitter": "^1.0.2"
}
},
"node_modules/@scena/event-emitter": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/@scena/event-emitter/-/event-emitter-1.0.5.tgz",
"integrity": "sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.1.1"
}
},
"node_modules/@scena/matrix": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@scena/matrix/-/matrix-1.1.1.tgz",
"integrity": "sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.4.0"
}
},
"node_modules/@simonwep/pickr": {
"version": "1.8.2",
"resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz",
@@ -2904,6 +2975,52 @@
"node": ">= 0.10"
}
},
"node_modules/croact": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/croact/-/croact-1.0.4.tgz",
"integrity": "sha512-9GhvyzTY/IVUrMQ2iz/mzgZ8+NcjczmIo/t4FkC1CU0CEcau6v6VsEih4jkTa4ZmRgYTF0qXEZLObCzdDFplpw==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@egjs/list-differ": "^1.0.0"
}
},
"node_modules/croact-css-styled": {
"version": "1.1.9",
"resolved": "https://registry.npmmirror.com/croact-css-styled/-/croact-css-styled-1.1.9.tgz",
"integrity": "sha512-G7yvRiVJ3Eoj0ov2h2xR4312hpOzATay2dGS9clK8yJQothjH1sBXIyvOeRP5wBKD9mPcKcoUXPCPsl0tQog4w==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"css-styled": "~1.0.8",
"framework-utils": "^1.1.0"
}
},
"node_modules/croact-moveable": {
"version": "0.9.0",
"resolved": "https://registry.npmmirror.com/croact-moveable/-/croact-moveable-0.9.0.tgz",
"integrity": "sha512-fc3bieV6CdqqZFtzsSLi9KmvUMFW3oakUfhPCls1BxKjOfUfn8rktteGED2341A/Qghy8tI3Hm6SdocIc68IKg==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@egjs/agent": "^2.2.1",
"@egjs/children-differ": "^1.0.1",
"@egjs/list-differ": "^1.0.0",
"@scena/dragscroll": "^1.4.0",
"@scena/event-emitter": "^1.0.5",
"@scena/matrix": "^1.1.1",
"croact-css-styled": "^1.1.9",
"css-to-mat": "^1.1.1",
"framework-utils": "^1.1.0",
"gesto": "^1.19.3",
"overlap-area": "^1.1.0",
"react-css-styled": "^1.1.9",
"react-moveable": "~0.56.0"
},
"peerDependencies": {
"croact": "^1.0.4"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3014,6 +3131,25 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/css-styled": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/css-styled/-/css-styled-1.0.8.tgz",
"integrity": "sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0"
}
},
"node_modules/css-to-mat": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/css-to-mat/-/css-to-mat-1.1.1.tgz",
"integrity": "sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@scena/matrix": "^1.0.0"
}
},
"node_modules/css-tree": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz",
@@ -4354,6 +4490,12 @@
"node": ">=0.10.0"
}
},
"node_modules/framework-utils": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/framework-utils/-/framework-utils-1.1.0.tgz",
"integrity": "sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==",
"license": "MIT"
},
"node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -4485,6 +4627,16 @@
"node": ">=10"
}
},
"node_modules/gesto": {
"version": "1.19.4",
"resolved": "https://registry.npmmirror.com/gesto/-/gesto-1.19.4.tgz",
"integrity": "sha512-hfr/0dWwh0Bnbb88s3QVJd1ZRJeOWcgHPPwmiH6NnafDYvhTsxg+SLYu+q/oPNh9JS3V+nlr6fNs8kvPAtcRDQ==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@scena/event-emitter": "^1.0.2"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -5695,6 +5847,24 @@
"setimmediate": "^1.0.5"
}
},
"node_modules/keycode": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/keycode/-/keycode-2.2.1.tgz",
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==",
"license": "MIT"
},
"node_modules/keycon": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/keycon/-/keycon-1.4.0.tgz",
"integrity": "sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==",
"license": "MIT",
"dependencies": {
"@cfcs/core": "^0.0.6",
"@daybrush/utils": "^1.7.1",
"@scena/event-emitter": "^1.0.2",
"keycode": "^2.2.0"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
@@ -6212,6 +6382,19 @@
"pathe": "^2.0.1"
}
},
"node_modules/moveable": {
"version": "0.53.0",
"resolved": "https://registry.npmmirror.com/moveable/-/moveable-0.53.0.tgz",
"integrity": "sha512-71jS9zIoQzMhnNvduhg4tUEdm23+fO/40FN7muVMbZvVwbTku2MIxxLhnU4qFvxI4oVxn75l79SbtgjuA+s7Pw==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@scena/event-emitter": "^1.0.5",
"croact": "^1.0.4",
"croact-moveable": "~0.9.0",
"react-moveable": "~0.56.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
@@ -6650,6 +6833,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/overlap-area": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/overlap-area/-/overlap-area-1.1.0.tgz",
"integrity": "sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.7.1"
}
},
"node_modules/own-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz",
@@ -7037,6 +7229,46 @@
"safe-buffer": "^5.1.0"
}
},
"node_modules/react-css-styled": {
"version": "1.1.9",
"resolved": "https://registry.npmmirror.com/react-css-styled/-/react-css-styled-1.1.9.tgz",
"integrity": "sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==",
"license": "MIT",
"dependencies": {
"css-styled": "~1.0.8",
"framework-utils": "^1.1.0"
}
},
"node_modules/react-moveable": {
"version": "0.56.0",
"resolved": "https://registry.npmmirror.com/react-moveable/-/react-moveable-0.56.0.tgz",
"integrity": "sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@egjs/agent": "^2.2.1",
"@egjs/children-differ": "^1.0.1",
"@egjs/list-differ": "^1.0.0",
"@scena/dragscroll": "^1.4.0",
"@scena/event-emitter": "^1.0.5",
"@scena/matrix": "^1.1.1",
"css-to-mat": "^1.1.1",
"framework-utils": "^1.1.0",
"gesto": "^1.19.3",
"overlap-area": "^1.1.0",
"react-css-styled": "^1.1.9",
"react-selecto": "^1.25.0"
}
},
"node_modules/react-selecto": {
"version": "1.26.3",
"resolved": "https://registry.npmmirror.com/react-selecto/-/react-selecto-1.26.3.tgz",
"integrity": "sha512-Ubik7kWSnZyQEBNro+1k38hZaI1tJarE+5aD/qsqCOA1uUBSjgKVBy3EWRzGIbdmVex7DcxznFZLec/6KZNvwQ==",
"license": "MIT",
"dependencies": {
"selecto": "~1.26.3"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -7507,6 +7739,24 @@
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
"dev": true
},
"node_modules/selecto": {
"version": "1.26.3",
"resolved": "https://registry.npmmirror.com/selecto/-/selecto-1.26.3.tgz",
"integrity": "sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==",
"license": "MIT",
"dependencies": {
"@daybrush/utils": "^1.13.0",
"@egjs/children-differ": "^1.0.1",
"@scena/dragscroll": "^1.4.0",
"@scena/event-emitter": "^1.0.5",
"css-styled": "^1.0.8",
"css-to-mat": "^1.1.1",
"framework-utils": "^1.1.0",
"gesto": "^1.19.4",
"keycon": "^1.2.0",
"overlap-area": "^1.1.0"
}
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
@@ -9765,6 +10015,16 @@
"vue": "^3.0.0"
}
},
"node_modules/vue3-moveable": {
"version": "0.28.0",
"resolved": "https://registry.npmmirror.com/vue3-moveable/-/vue3-moveable-0.28.0.tgz",
"integrity": "sha512-vplQO0XkxVEtXMDh2/lZE+c5kMycGXAfYFMvbwFKi8UVYzVk8MTgVHr4fxO9Z+4i4Rb+U/IEIgkhHRMAbx8FJg==",
"license": "MIT",
"dependencies": {
"framework-utils": "^1.1.0",
"moveable": "~0.53.0"
}
},
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",

View File

@@ -40,6 +40,7 @@
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^9.6.1",
"vue-router": "^4.0.3",
"vue3-moveable": "^0.28.0",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0",
"x-sender": "^1.1.6"

View 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

View 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

View 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

View File

@@ -0,0 +1,68 @@
<?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="103.000000pt" height="92.000000pt" viewBox="0 0 103.000000 92.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,92.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M365 895 c-5 -2 -36 -6 -67 -10 -45 -5 -58 -10 -59 -23 0 -11 -2 -12
-6 -4 -7 17 -32 15 -40 -4 -4 -11 -8 -12 -13 -4 -5 8 -13 9 -21 4 -7 -4 -22
-9 -34 -10 -11 -1 -41 -11 -66 -21 l-47 -18 20 -63 c11 -38 23 -60 30 -56 6 4
8 -1 3 -15 -3 -11 -3 -21 2 -21 4 0 9 -12 9 -26 1 -14 5 -28 8 -32 4 -3 33 3
66 15 33 11 61 19 62 18 2 -2 1 -139 -2 -304 l-5 -301 309 2 309 3 -2 108 c-2
77 2 113 11 125 10 12 10 14 1 8 -7 -4 -13 -2 -13 4 0 6 10 8 23 5 18 -5 26
-1 36 17 9 17 10 18 6 3 -5 -16 -4 -18 6 -7 18 17 -1 34 -40 37 l-32 2 -2 151
c-1 82 0 147 3 144 9 -12 44 -11 52 1 5 8 8 7 8 -4 0 -14 26 -28 52 -29 11 0
39 70 33 80 -3 5 1 11 7 13 10 4 9 8 -2 16 -13 10 -8 15 12 12 8 -1 38 91 32
98 -2 2 -10 -2 -18 -8 -9 -7 -17 -8 -20 -2 -3 5 0 11 6 14 7 2 -18 14 -56 26
-38 12 -71 19 -74 16 -3 -3 -11 0 -18 6 -8 6 -20 9 -28 6 -8 -3 -16 -2 -18 3
-5 15 -154 30 -286 29 -70 0 -131 -2 -137 -4z m21 -30 c-6 -18 3 -47 14 -40 4
2 18 -7 30 -20 27 -29 17 -34 -14 -7 -20 16 -20 16 -7 -1 23 -29 66 -47 112
-47 34 0 40 3 35 16 -5 14 -4 15 9 4 13 -11 19 -9 36 6 12 11 29 35 38 54 15
31 20 35 58 35 24 0 43 -2 43 -5 0 -11 115 -27 121 -18 3 5 9 2 13 -7 4 -13
14 -16 34 -12 15 2 36 1 47 -4 16 -7 14 -8 -10 -5 l-30 5 32 -14 c39 -18 39
-19 14 -83 -15 -38 -17 -52 -8 -61 9 -9 8 -11 -7 -5 -16 6 -18 4 -12 -19 3
-14 2 -29 -4 -32 -6 -3 -7 1 -4 9 6 16 -9 20 -73 19 -12 0 -20 4 -17 8 3 5 -4
9 -15 9 -20 0 -21 -5 -21 -151 l0 -151 -27 8 c-16 4 -38 7 -50 7 -19 -1 -21 2
-13 17 15 28 12 57 -6 57 -11 0 -15 -8 -12 -27 4 -24 1 -27 -23 -26 -15 1 -25
4 -22 9 2 4 -2 7 -10 7 -9 0 -14 -10 -14 -25 0 -13 3 -22 8 -19 5 3 6 -1 3 -9
-2 -7 0 -25 5 -40 8 -20 7 -25 -3 -21 -7 3 -13 -2 -13 -11 0 -12 7 -15 26 -11
14 3 21 3 14 0 -7 -3 -9 -12 -6 -20 3 -7 10 -11 16 -7 5 3 7 1 4 -4 -9 -14 3
-74 12 -68 4 2 7 -6 7 -18 -1 -32 32 -34 44 -3 5 14 7 34 5 46 -3 13 0 18 7
14 8 -5 9 -1 5 10 -4 9 -3 15 2 12 5 -3 12 1 15 10 3 8 2 12 -4 9 -6 -3 -10
-1 -10 4 0 13 3 13 24 5 13 -5 16 -24 16 -103 0 -63 4 -102 13 -111 10 -12 9
-12 -4 -2 -13 10 -88 12 -300 10 l-284 -3 3 303 3 302 -26 0 c-14 0 -25 -4
-25 -10 0 -5 -7 -6 -17 -3 -9 4 -14 2 -10 -3 4 -6 -6 -9 -24 -6 -28 4 -41 -11
-19 -21 6 -3 5 -4 -2 -3 -7 2 -13 11 -13 22 -1 32 -25 104 -35 104 -5 0 -6 7
-3 17 4 10 2 14 -5 9 -7 -4 -10 2 -8 17 3 29 14 47 29 47 7 0 3 -8 -8 -17
l-20 -16 20 8 c11 4 28 10 37 12 10 3 18 9 18 13 0 4 6 7 12 7 22 -2 158 26
158 32 0 3 20 6 45 5 25 0 45 3 45 8 0 4 3 8 6 8 3 0 4 -7 0 -15z m237 -5 c-3
-9 1 -8 11 4 9 11 16 15 16 9 0 -6 -7 -16 -15 -23 -8 -7 -15 -9 -15 -4 0 10
-29 -28 -30 -40 0 -4 8 -5 17 -2 15 6 15 4 -2 -14 -22 -24 -37 -26 -28 -4 5
14 3 15 -15 5 -26 -14 -77 -14 -103 -1 -10 6 -27 27 -38 48 l-19 37 113 0 c95
0 112 -2 108 -15z"/>
<path d="M346 641 c-3 -5 1 -12 10 -15 23 -9 36 -7 29 4 -3 6 1 7 9 4 9 -3 16
-1 16 5 0 13 -56 15 -64 2z"/>
<path d="M440 640 c0 -16 33 -26 38 -12 2 7 8 10 13 6 5 -3 9 0 9 5 0 6 -13
11 -30 11 -16 0 -30 -5 -30 -10z"/>
<path d="M530 641 c0 -12 37 -24 50 -16 20 12 10 25 -20 25 -16 0 -30 -4 -30
-9z"/>
<path d="M620 641 c0 -12 37 -24 50 -16 20 12 10 25 -20 25 -16 0 -30 -4 -30
-9z"/>
<path d="M310 593 c0 -20 5 -30 16 -30 10 0 14 8 12 25 -4 37 -28 40 -28 5z"/>
<path d="M697 613 c-13 -13 -7 -50 8 -50 10 0 15 10 15 29 0 27 -9 35 -23 21z"/>
<path d="M317 534 c-4 -4 -7 -20 -7 -36 0 -35 23 -34 28 1 4 25 -10 46 -21 35z"/>
<path d="M692 503 c4 -39 28 -42 28 -4 0 21 -5 31 -16 31 -11 0 -14 -8 -12
-27z"/>
<path d="M312 415 c4 -33 22 -33 26 0 2 18 -1 25 -13 25 -12 0 -15 -7 -13 -25z"/>
<path d="M312 329 c2 -19 8 -33 13 -31 15 3 12 55 -3 60 -10 3 -13 -5 -10 -29z"/>
<path d="M342 278 c3 -7 19 -14 37 -16 24 -3 32 0 29 10 -3 7 -19 14 -37 16
-24 3 -32 0 -29 -10z"/>
<path d="M443 275 c0 -10 10 -15 29 -15 18 0 28 5 28 15 0 10 -10 15 -28 15
-19 0 -29 -5 -29 -15z"/>
<path d="M530 275 c0 -10 10 -15 33 -15 22 0 28 3 18 9 -11 7 -11 9 0 14 8 3
-1 6 -18 6 -23 1 -33 -4 -33 -14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,31 @@
<?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="96.000000pt" height="96.000000pt" viewBox="0 0 96.000000 96.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,96.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M346 936 c-21 -13 -49 -41 -62 -62 -41 -67 -27 -180 27 -218 47 -32
53 -12 12 40 -71 94 -2 229 116 229 37 0 58 -7 87 -28 32 -22 40 -24 42 -12
14 64 -142 100 -222 51z"/>
<path d="M368 877 c-33 -28 -48 -57 -48 -96 0 -39 9 -61 26 -61 10 0 14 13 14
46 0 57 12 79 50 93 61 21 110 -22 110 -96 0 -48 14 -56 31 -19 31 67 -35 156
-114 156 -29 0 -50 -7 -69 -23z"/>
<path d="M580 794 c0 -58 -9 -84 -43 -121 -19 -21 -19 -23 -2 -29 34 -13 85
75 85 148 0 37 -10 58 -26 58 -10 0 -14 -15 -14 -56z"/>
<path d="M410 749 c-13 -6 -28 -15 -32 -22 -4 -7 -8 -106 -8 -222 l-1 -210
-27 34 c-62 80 -89 101 -126 101 -27 0 -39 -6 -52 -25 -15 -24 -15 -28 0 -68
21 -53 78 -123 94 -113 18 11 16 17 -28 75 -44 58 -48 72 -25 91 21 18 54 -6
93 -68 31 -47 74 -78 93 -67 5 4 9 101 9 224 0 225 3 241 42 241 10 0 19 -1
19 -2 1 -2 5 -86 8 -188 5 -165 8 -185 24 -188 15 -3 17 5 17 61 0 72 17 100
45 77 10 -9 15 -32 15 -77 0 -56 2 -64 18 -61 12 2 16 11 14 34 -5 48 14 86
41 82 20 -3 22 -9 25 -66 3 -53 6 -63 20 -60 12 2 19 16 22 43 4 32 10 41 27
43 36 5 46 -36 39 -166 -6 -127 -19 -160 -75 -195 -48 -29 -176 -34 -246 -10
-48 18 -56 25 -130 128 -16 22 -25 26 -37 19 -15 -8 -11 -18 31 -75 78 -105
94 -113 236 -117 119 -3 121 -3 167 27 69 44 82 74 86 214 6 179 -8 221 -72
216 -22 -2 -39 4 -55 20 -17 17 -32 22 -55 19 -19 -2 -38 2 -45 9 -6 7 -25 13
-40 13 l-29 0 -4 100 c-3 103 -11 122 -53 133 -11 3 -31 1 -45 -4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="24" height="24" fill="url(#pattern0_2641_12790)"/>
<defs>
<pattern id="pattern0_2641_12790" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_2641_12790" transform="scale(0.0078125)"/>
</pattern>
<image id="image0_2641_12790" width="128" height="128" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAACIlJREFUeJztnWlsFVUUgL9OyxNwoVUoCBZFVNxQUaNWo0aiica4SxTUGJe4x+0PBJc/rlH8oSYucQvgQkARixpCECNqgtGKiiigssbK5sLeCvT54/TR+mz75s7cuXfeeL7k/Gpn3jlzZ+5y7rnngKIo/18qfCuQcfYGBgF922QzsA5YD6zxqJeSEBXAWcAEoBHYBeS7kFXAROAaoLcPZRV75IAbgYV03eDdye/AY8BA14or8akHFhGt4YtlKzAWqHRqgRKJAHiC7rv5qPIZsL87UxRT9gCmYL/hO8py4HBXBinhyQFzSbbxC7IBONKNWUpYXsRN43fsCfo6sUwpya24bfyCzEZ9Nt7pD2zEzwuQB65N3kSlOybhr/HziOewj22jdL0ZjiOQsd+0G94MvAe8grxAHwM/A7VAjeG99gK2AJ8aXqdYYAJmX+tO4HGguov7VQBjgCbD+y5D/A+KQ3oAawnfSFuBc0PeeyCwwODeeeAcCzZ1yjHAm8hY43OsK3cZZfjcB2LeE0SV34A3gOHFSowBWlLw8Mpdphc/2JBc5VjPFmB04cePQRvflpxENAJghWNdW4DhATAOcW8q8WgCvox4bSsw06IuYcgB4wJgpOMfzipLkS8rKottKWLAyICulyqKGZtjXr/RihZmVBfGHiU+tTGvH2BFCzOWB8BUDz+cRYYTL7bvFFuKGDANxL/8M/5n0FmQ3UsrQ2qQIcSlrj8B+xQUGIK5R0rlv7KEaCuqpxzr2Yi0+b82NyqBC5CwZo1HaycHXGTw/68gEcJhOR9oILyPvxHZE4jCb8iGVAOy9FRC0ojZF/Yy4XqC0cA2g/u2AodYskkx4DbMu9mliIu9eGIYIBO+hgj3nJuQfUoJqoE/iDbebgPmI3EB85BuOOrYfWnShipdcyd+JpYF+RSNC/RKFfAdfhp/J3Bs8iYqpTgWCcty/QKMdWGcEo5LkNm4q8afinb9qeMukjkTWCwfAr0c2aQYcimwneQafxISj6ikmFOBH7Hb8FuQHka7/TKhJ/AwdnqDD4CDnGqvWKMfMltfjVmjtyATvZPdq6zdTBJUAscjm2qnA4ORl6MvsAlJELUW+ArZmJlH/GgiRVEURVEURQlPGlYBVciRqhOR2XItkmJ1AzJjXo1shf7iS0HFPgESZ/c+4aNhVwLPoVmzypoAuIl4YeitwCykx1DKiKHAR9jzme9C0rbs5dIIJRqXYRb9aiKLEG+bklLuIPm98ybgOFcGKeG5iWQbvqNsoO20i5IOTgWacfcC5IEf6HDeTfFHDf6STU12YJ9Sgmfx0/gFOTN5E5WuOBrYgd8X4Gs0oaI3XsO8wf4CngROQ1zBOaAOybk3nWhh2GGTNSoW2QfzgxNTgP1K3PckZC/A5L5v2zMruxRvBvVCXKxR89XUA/cY/P/Tbf+fD/G/fYFPCL8X8DdwHTIcKcIaJBRte/EfKoEHcZumZA7m2cqH4udoVpZkE/BAx2dfAbzlWIlddJKvNiQPpeAhZkHepG0EcJ2nNk+8nPd1uD2bl2UZEwA3mz1/K8yJce1q/GTVzCI3B8AIDz+8Mub1q6xooYzw5SyJ+7tpCGXLAvkAyQ/omjrP1yvCggB43sMPxyl7MhgtqWqLF0C60zdwO/vchRSqiMLDjnXNqrxOh6G0ErgfcRK4UmAu5o6gQ5GiTL4fXjnLRmB84dkXT6Z6Iq7gqKliTV3BzwB3tylWCnUFxyNPuyu4OakfibIZNJXSxZHrkfy4JvedZs8sxYRXidYtTUDO0/dHtoMHA1cAM9Dt4LLiKPwHhDSiASFeeRp/jd8KnJG8iUp31BAvKXIcmejAPiUE9bgPC/8G2NOFcUo4bsBd469DU6ylkutJflL4K5pNO9VcTHKHQxcCB7gzRYnKwUggiK2G34GsNnTMLyMqkCGhUGc3irQiKVV9BLIolgiQEnUNhN+AWo4cORvmQd/MkobImipkA+pE5FRQLbKnsL5NVgKfIS+AoiiKoiiKoihxScMqIGtUASfQXjCiDol46kd7wYh1SGjWXKRgxCYvmipWqSVeyZh69yorNugJPIadre9ZiMtcKRNOA5Zgd3NrKxJZrcNzyrmcZAtHTkaCY5UUci9uchTMBno7skkJyWW4TVDxDjocpIbj8JOjaLwL45TuqQK+w33j55HDtFogwzOFdHa+5HMSGApMT+f+X6kG3kXW/KZsQ04qNSIewB5IUSxT6oDvkYzoimNux/yLXQxcyX9n8RVI5tMZEe75cUL2KSX4GrOGeolwa/hRmOU7aEVyJFij45hSiZRyOws5oasIOeS5hOUlpFJKWM4DZhJ+OG5EjspHYS2yAdWATCx3MwQ5VuVzkpMFWUw0792TjvVcQIfyOtWYZ+JW6VyuJBo1uM3TnEdqN/Yp5AYy6eKUztkG3Ei0lDTNwPG4rYq6L7AjQDY1lPgsRF6CqHxhSxEDRgXoiVpbrI15/RorWpgxJEDKtSjxiVuuro8VLcz4M0Dq+SrxGUY8V62P7KcfgRRuaMH/DDoLcrLR428nAFY41rUZqfAGwGj0JbAhM4jG1Y71bKaTJetwJGdwk6eHlxW5ovjBlmAQ7hJqNSE5gnd/+UppeiAz/LAPeSvi3g3DIMw9sGdbsEkxZAJmjbQTce/WdHG/AOn2Tb/8ZVhOgKmxZuE4AliE+fPagsT6z0fW+TXAYcCFwIER9LgPeDTCdYoFJuJ3brGG+L4GJQb9EaeZrxfgmuRNVEpxC34afxY6XKeGF3Db+MsoXU9BcUgOu/kOu5N1yARUSRk5kq+3vBxNiZdqAuBxZM1vu/HnAQPcmaLEYQTmUcNdyRYkyYRWOykzckiFsm+J1vDrgUfQrz4TnAk8AXxJ98PDCuA14Cqglw9FQdeWSbMnksq+H7KU24TM7AuiKIrikX8A+4ThOTuVZbQAAAAASUVORK5CYII="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -7,6 +7,7 @@ import {
insertObjectAtZIndex,
removeCanvasObjectByObject,
createPatternTransform,
imageAddGapToCanvas,
} from "../utils/helper";
import { restoreFabricObject } from "../utils/objectHelper";
@@ -308,16 +309,8 @@ export class FillRepeatGapChangeCommand extends Command {
await image.decode();
object.fill_.width = image.width;
object.fill_.height = image.height;
// 创建透明 Canvas
const tcanvas = document.createElement('canvas');
tcanvas.width = image.width + object.fill_.gapX;
tcanvas.height = image.height + object.fill_.gapY;
const ctx = tcanvas.getContext('2d');
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
const fill = object.get("fill");
fill.source = tcanvas;
fill.source = imageAddGapToCanvas(image, object.fill_.gapX, object.fill_.gapY);
object.set("fill", new fabric.Pattern(fill));
this.canvas.renderAll();
return true;

View File

@@ -872,13 +872,13 @@ export class ToggleChildLayerVisibilityCommand extends Command {
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
}
async execute() {
async execute(visible) {
if (!this.childLayer) {
throw new Error("找不到要切换可见性的子图层");
}
// 切换可见性
this.childLayer.visible = !this.childLayer.visible;
this.childLayer.visible = typeof visible === "boolean" ? visible : !this.childLayer.visible;
// 更新画布上图层对象的可见性
if (this.canvas) {

View File

@@ -287,7 +287,7 @@ const canDeleteComputed = computed(() => {
:is-child="isChild"
:is-active="layer.id === activeLayerId"
:is-selected="isLayerSelected(layer.id)"
:is-multi-select-mode="isMultiSelectMode && !layer.specialType"
:is-multi-select-mode="isMultiSelectMode && !(layer.isPrintTrims || layer.isPrintTrimsGroup)"
:is-editing="editingLayerId === layer.id"
:editing-name="editingLayerName"
:can-delete="
@@ -296,7 +296,7 @@ const canDeleteComputed = computed(() => {
:expanded-group-ids="expandedGroupIds"
@click="(...args) => forwardEvent('layer-click', ...args)"
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
@context-menu="(...args) => !layer.specialType && forwardEvent('context-menu', ...args)"
@context-menu="(...args) => !(layer.isPrintTrims || layer.isPrintTrimsGroup) && forwardEvent('context-menu', ...args)"
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
@@ -337,7 +337,7 @@ const canDeleteComputed = computed(() => {
:expanded-group-ids="expandedGroupIds"
:isChild="true"
:parentLayerId="layer.id"
:group-name="layer.specialType || groupName"
:group-name="groupName"
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
@layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
@context-menu="(...args) => forwardEvent('context-menu', ...args)"

View File

@@ -1242,7 +1242,7 @@ async function handleCrossLevelMove(moveData) {
try {
const layer = findLayerRecursively(layers.value, layerId).layer;
const toLayer = findLayerRecursively(layers.value, toParentId).layer;
if(layer?.specialType || toLayer?.specialType) {
if(layer?.isPrintTrims || layer?.isPrintTrimsGroup || toLayer?.isPrintTrims || toLayer?.isPrintTrimsGroup) {
console.warn("当前图层不可移动到外部");
return;
}

View File

@@ -0,0 +1,479 @@
<template>
<transition name="fade">
<div
class="part-selector-toolbar"
v-if="visible"
:class="{ active: !closePanel }"
>
<div class="btn" @click="setClosePanel">
<i class="fi fi-br-angle-left"></i>
</div>
<!-- 顶部选区类型工具栏 -->
<div class="toolbar-section">
<div class="toolbar-header">
<div class="header-title">
{{ 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">
<div
v-for="item in toolList"
:key="item.type"
:class="[
'tool-btn',
{ active: toolType === item.type },
]"
@click="setPartType(item.type)"
>
<svg-icon :name="item.icon" :size="item.size" />
<span>{{ item.label }}</span>
</div>
</div>
<!-- 分割线 -->
<div class="toolbar-divider"></div>
<!-- 底部选区操作工具栏 -->
<div class="tool-actions">
<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="onCopyCreate">
<svg-icon name="CCut" size="26" />
<span class="btn-text">{{
$t("Canvas.CreateAndCopy")
}}</span>
</div>
</div>
</div>
</div>
</transition>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { useI18n } from "vue-i18n";
import {
CreateSelectionCommand,
InvertSelectionCommand,
FeatherSelectionCommand,
FillSelectionCommand,
} from "../commands/SelectionCommands";
import { ToolCommand } from "../commands/ToolCommands";
import {
LassoCutoutCommand,
ClearSelectionCommand,
// CutSelectionToNewLayerCommand,
} from "../commands/LassoCutoutCommand";
import { OperationType } from "../utils/layerHelper";
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
const props = defineProps({
canvas: {
type: Object,
required: true,
},
commandManager: {
type: Object,
required: true,
},
partManager: {
type: Object,
required: true,
},
partManager: {
type: Object,
required: true,
},
layerManager: {
type: Object,
required: true,
},
toolManager: {
type: Object,
required: true,
},
activeTool: {
type: String,
required: false,
default: null,
},
});
// 响应式数据
const visible = ref(false);
const toolType = ref(OperationType.PART);
//打开隐藏操作面板
const closePanel = ref(false);
const setClosePanel = () => {
closePanel.value = !closePanel.value;
};
const toolList = [
{
type: OperationType.PART,
label: "Point Selection",
icon: "CPoint",
size: "20",
},
{
type: OperationType.PART_RECTANGLE,
label: "Marquee Selection",
icon: "CMarquee",
size: "20",
},
{
type: OperationType.PART_BRUSH,
label: "Brush Selection",
icon: "CBrush2",
size: "16",
},
{
type: OperationType.PART_ERASER,
label: "Erase",
icon: "CEraser2",
size: "22",
},
];
// 国际化
const { t } = useI18n();
onMounted(() => {});
// 监听 activeTool 变化
watch(
() => props.activeTool,
(newTool) => {
// 当工具为LASSO或AREA类型时显示选区面板
const selectionTools = [
OperationType.PART,
OperationType.PART_RECTANGLE,
OperationType.PART_BRUSH,
OperationType.PART_ERASER,
];
if (selectionTools.includes(newTool)) {
show();
// 根据工具类型设置选区类型
toolType.value = newTool;
// 更新选区管理器的选区类型
// if (props.partManager) {
// props.partManager.setPartType(toolType.value);
// props.partManager.setupPartEvents();
// }
} else {
close();
}
},
{ immediate: true }
);
/**
* 显示面板
*/
function show() {
visible.value = true;
closePanel.value = true;
}
/**
* 关闭面板
*/
function close() {
visible.value = false;
}
/**
* 设置选区类型
*/
function setPartType(type) {
toolType.value = type;
// 通过 ToolManager 切换工具,这会自动通知 partManager
if (props.toolManager) {
props.toolManager.setToolWithCommand(type);
}
// // 备用方案:如果没有 toolManager直接更新 partManager
// else if (props.partManager) {
// props.partManager.setPartType(type);
// props.partManager.setupPartEvents();
// }
}
// 创建
function onCreate() {
}
// 复制并创建
function onCopyCreate() {
}
</script>
<style scoped lang="less">
.part-selector-toolbar {
position: absolute;
bottom: 22px;
left: 20px;
right: 20px;
max-width: min(90vw, 700px);
margin: 0 auto;
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
border-radius: 8px;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
z-index: 1000;
color: #333;
border: 1px solid rgba(0, 0, 0, 0.05);
user-select: none;
&.active {
transform: translateY(100%);
> .btn {
> i {
transform: rotate(90deg);
}
}
}
> .btn {
width: 100%;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
height: 22px;
> i {
font-size: 1.4rem;
transform: rotate(270deg);
}
}
}
/* 平板和手机适配 */
@media screen and (max-width: 768px) {
.part-selector-toolbar {
bottom: 15px;
left: 15px;
right: 15px;
max-width: calc(100vw - 30px);
border-radius: 6px;
}
}
@media screen and (max-width: 480px) {
.part-selector-toolbar {
bottom: 10px;
left: 10px;
right: 10px;
max-width: calc(100vw - 20px);
}
}
.part-selector-toolbar.is-active {
transform: translateY(0);
}
.toolbar-header {
// display: flex;
// justify-content: center;
// align-items: center;
padding: 8px 0;
// 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 {
font-size: 13px;
font-weight: 500;
color: #333;
text-align: left;
}
.header-btn {
background: none;
border: none;
color: #333;
font-size: 12px;
cursor: pointer;
padding: 3px 0;
border-radius: 3px;
transition: background-color 0.2s ease;
min-width: 32px;
}
.header-btn:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.close-btn {
color: #666;
}
.toolbar-section {
padding: 0 3rem 1.2rem;
}
.tool-types {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
padding: 10px 0;
}
.tool-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.05);
border: none;
border-radius: 6px;
padding: 6px;
color: #333;
cursor: pointer;
transition: all 0.2s;
}
.tool-btn span {
margin-top: 0;
font-size: 12px;
}
.tool-btn svg {
width: 24px;
height: 24px;
}
.tool-btn:hover {
background-color: rgba(0, 0, 0, 0.08);
}
.tool-btn.active {
background-color: #007aff;
color: white;
}
.toolbar-divider {
height: 1px;
background-color: rgba(0, 0, 0, 0.05);
margin-bottom: 5px;
}
.tool-actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 5px;
padding: 0 30px;
}
/* 平板适配 - 每行4个按钮 */
@media screen and (max-width: 768px) {
.tool-actions {
grid-template-columns: repeat(3, 1fr);
gap: 8px 6px;
padding: 0 8px;
}
}
/* 手机适配 - 每行3个按钮 */
@media screen and (max-width: 480px) {
.tool-actions {
grid-template-columns: repeat(3, 1fr);
gap: 6px 4px;
padding: 0 6px;
}
.header-btn {
font-size: 11px;
padding: 2px 4px;
min-width: 28px;
}
}
.action-btn {
display: flex;
// flex-direction: column;
flex-direction: row;
align-items: center;
justify-content: center;
background: none;
border: none;
color: #333;
cursor: pointer;
padding: 0;
gap: 4px;
.c-svg {
width: auto;
}
}
.action-btn svg {
width: 22px;
height: 22px;
margin-bottom: 8px;
}
.btn-text {
display: block;
font-size: 12px;
text-align: center;
}
.action-btn:hover {
color: #007aff;
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

View File

@@ -1,14 +1,15 @@
<template>
<div class="repeat-setting">
<div class="title">{{ t("Canvas.repeatSetting") }}</div>
<div class="repeat-setting-item">
<span class="label">{{ t("Canvas.angle") }}</span>
<angle-tool
:angle="angle"
@input="(e) => emit('inputFillAngle', e)"
@change="(e) => emit('changeFillAngle', e)"
style-type="2"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">{{ t("Canvas.scale") }}</span>
<slider
@@ -22,7 +23,6 @@
@change="changeFillScale"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">Gap X</span>
<slider
@@ -36,7 +36,6 @@
@change="(e) => emit('changeFill_Gap', e, gapY)"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">Gap Y</span>
<slider
@@ -50,14 +49,23 @@
@change="(e) => emit('changeFill_Gap', gapX, e)"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">{{ t("Canvas.offset") }}</span>
<offset-tool
:top="(props.object.fill?.offsetY / props.object.height) * 100"
:left="(props.object.fill?.offsetX / props.object.width) * 100"
:left="offsetX"
:top="offsetY"
@input="(e) => emit('inputFillOffset', e)"
@change="(e) => emit('changeFillOffset', e)"
:show-dish="false"
/>
</div>
<div class="repeat-setting-item offset">
<offset-tool
:left="offsetX"
:top="offsetY"
@input="(e) => emit('inputFillOffset', e)"
@change="(e) => emit('changeFillOffset', e)"
:show-input="false"
/>
</div>
</div>
@@ -88,6 +96,12 @@
});
const gapX = computed(() => props.object.fill_?.gapX || 0);
const gapY = computed(() => props.object.fill_?.gapY || 0);
const offsetX = computed(
() => (props.object.fill?.offsetX / props.object.width) * 100
);
const offsetY = computed(
() => (props.object.fill?.offsetY / props.object.height) * 100
);
const emit = defineEmits([
"inputFillAngle",
"changeFillAngle",
@@ -111,23 +125,36 @@
<style scoped lang="less">
.repeat-setting {
user-select: none;
width: 228px;
> .title {
line-height: 35px;
font-size: 14px;
text-align: center;
margin-top: -12px;
margin-bottom: 3px;
}
> .repeat-setting-item {
display: flex;
align-items: center;
//虚线
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
&.offset {
justify-content: center;
}
> .label {
min-width: 50px;
font-size: 14px;
min-width: 68px;
font-size: 12px;
}
> .angle-tool {
&:not(.offset) > div {
width: 120px;
flex: 1;
}
> .slider {
--slider-thumb-color1: #000;
--slider-thumb-color2: #eee;
}
}
> p {
margin: 10px 0;
width: 100%;
height: 0;
border-bottom: 1px dashed #e5e5e5;
}
}
</style>

View File

@@ -132,7 +132,6 @@
v-if="v.type === 'rect'"
trigger="click"
destroyTooltipOnHide
:title="t('Canvas.repeatSetting')"
>
<template #content>
<repeat-setting
@@ -164,7 +163,7 @@
/>
</template>
<div class="btn">
<i class="iconfont icon-gengduo"></i>
<SvgIcon name="overallMore" size="18" />
</div>
</a-popover>
</div>
@@ -179,11 +178,7 @@
import { ref, onMounted, watch, onUnmounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import {
OperationType,
SpecialLayerId,
SpecialType,
} from "../../utils/layerHelper";
import { OperationType, SpecialLayerId } from "../../utils/layerHelper";
import { loadImageUrlToLayer } from "../../utils/imageHelper";
import {
calculateRotatedTopLeftDeg,
@@ -284,9 +279,6 @@
const getActiveObject = (e) => {
console.log("==========切换激活对象", e, activeObjects);
activeObjects.value = [...e.selected];
// .filter((v) =>
// v.specialType ? v.specialType === SpecialType.REPEAT_O : true
// );// 过滤出印花对象
activeObjects.value.forEach((v) => {
v.layer = props.layerManager.getLayerById(v.layerId);
});
@@ -441,7 +433,7 @@
};
changeFill(obj, pattern);
};
// 改变填充便宜
// 改变填充偏移
const inputFillOffset = (value, obj) => {
if (!obj.oldPattern) obj.oldPattern = obj.get("fill");
const pattern = new fabric.Pattern({
@@ -784,6 +776,7 @@
}
> .list {
display: flex;
> div {
display: flex;
align-items: center;

View File

@@ -29,8 +29,12 @@ const props = defineProps({
type: Boolean,
default: false,
},
clothingMinIOPath: {
type: String,
default: "", // 衣服底图URL-线稿
},
});
console.log(props.clothingMinIOPath)
const commandManager = inject("commandManager");
const layerManager = inject("layerManager"); // 图层管理器
@@ -166,6 +170,19 @@ const normalToolsList = ref([
icon: { name: "CFont", size: "20" },
class: "text-btn",
},
{
id: OperationType.PART,
title: t("Canvas.GarmentPartSelector"),
action: () => selectTool(OperationType.PART),
icon: { name: "CPart", size: "28" },
class: "part-btn",
activeList: [
OperationType.PART,
OperationType.PART_RECTANGLE,
OperationType.PART_BRUSH,
OperationType.PART_ERASER,
],
},
{
id: "help",
title: t("Canvas.help"),
@@ -236,7 +253,13 @@ const redGreenToolsList = ref([
// 根据模式选择工具列表
const toolsList = computed(() => {
return props.isRedGreenMode ? redGreenToolsList.value : normalToolsList.value;
const list = props.isRedGreenMode ? redGreenToolsList.value : normalToolsList.value;
return list.filter(tool => {
if(tool.id === OperationType.PART){
return !!props.clothingMinIOPath;
}
return true;
});
});
function selectTool(tool, isRedGreenMode = false) {

View File

@@ -1,32 +1,53 @@
<template>
<div class="angle-tool" :disabled="disabled">
<div
ref="dishRef"
class="dish"
@mousedown.stop="mousedown"
@touchmove.stop="mousedown"
>
<div class="pointer" :style="{ transform: `rotate(${angle}deg)` }">
<span></span>
<template v-if="styleType === '1'">
<div
ref="dishRef"
class="dish"
@mousedown.stop="mousedown"
@touchmove.stop="mousedown"
>
<div
class="pointer"
:style="{ transform: `rotate(${angle}deg)` }"
>
<span></span>
</div>
</div>
</div>
<div class="input">
<input
type="number"
v-model="angle"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
<div class="input">
<input
type="number"
v-model="angle"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
</template>
<my-input
v-if="styleType === '2'"
v-model="angle"
@input="onInput"
@change="onChange"
:disabled="disabled"
type="number"
after="°"
icon="icon-angle"
/>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
import { calculateAngle } from "../../utils/helper";
import MyInput from "./MyInput.vue";
// Props
const props = defineProps({
styleType: {
type: String,
default: "1",
},
angle: {
type: Number,
default: 0,
@@ -139,5 +160,8 @@
outline: none;
}
}
> .my-input {
flex: 1;
}
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="my-input">
<span class="decorate"></span>
<span v-show="icon" :class="['iconfont', icon]"></span>
<span v-show="before" class="before">{{ before }}</span>
<input v-bind="$attrs" :value="modelValue" @input="onInput" />
<span v-show="after" class="after">{{ after }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
const props = defineProps({
modelValue: { type: Number, default: 0 },
icon: { default: "", type: String },
before: { default: "", type: String },
after: { default: "", type: String },
});
const emit = defineEmits(["update:modelValue", "input"]);
const onInput = (e) => {
const value = e.target.value;
emit("update:modelValue", value);
emit("input", value);
};
</script>
<style scoped lang="less">
.my-input {
display: flex;
align-items: center;
width: 100%;
border: 1px solid rgba(230, 230, 231, 1);
border-radius: 3px;
height: 20px;
padding: 0 4px 0 2px;
> .decorate {
width: 2px;
background-color: rgba(230, 230, 231, 1);
border-radius: 3px;
height: 85%;
margin-right: 4px;
}
> .iconfont {
font-size: 10px;
color: #000;
margin-right: 2px;
}
> .before {
font-size: 12px;
color: #000;
margin-right: 2px;
}
> .after {
font-size: 12px;
color: #000;
}
> input {
font-size: 12px;
width: 0;
flex: 1;
text-align: right;
outline: none;
border: none;
background-color: transparent;
padding: 0;
}
}
</style>

View File

@@ -1,84 +1,100 @@
<template>
<div class="offset-tool">
<div class="input" v-show="showInput">
<my-input
v-model="left"
@input="onInput"
@change="onChange"
type="number"
before="X"
after="%"
:min="-100"
:max="100"
/>
<my-input
v-model="top"
@input="onInput"
@change="onChange"
type="number"
before="Y"
after="%"
:min="-100"
:max="100"
/>
</div>
<div
class="dish"
@mousedown="mousedown"
@touchstart="mousedown"
ref="dishRef"
v-show="showDish"
>
<span
:style="{ top: data.top + '%', left: data.left + '%' }"
></span>
<img src="/src/assets/images/icon/xyz.png" />
<span class="ball" :style="ballStyle"></span>
<span class="tip x">X: {{ left }}%</span>
<span class="tip y">Y: {{ top }}%</span>
<span class="line x"></span>
<span class="line y"></span>
<span class="line z" :style="lineZStyle"></span>
</div>
<input
class="top"
type="range"
:min="0"
:max="100"
:step="0.1"
v-model="data.top"
@input="onInput"
@change="onChange"
/>
<input
class="left"
type="range"
:min="0"
:max="100"
:step="0.1"
v-model="data.left"
@input="onInput"
@change="onChange"
/>
<span class="tip"
>x:{{ tofix(data.left) }}% y:{{ tofix(data.top) }}%</span
>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
import { ref, defineProps, defineEmits, watch, computed } from "vue";
import MyInput from "./MyInput.vue";
const props = defineProps({
top: {
type: Number,
default: 50,
},
left: {
type: Number,
default: 50,
default: 0,
},
top: {
type: Number,
default: 0,
},
showInput: {
type: Boolean,
default: true,
},
showDish: {
type: Boolean,
default: true,
},
});
const tofix = (v: number | string) => Number(Number(v).toFixed(1));
const emit = defineEmits(["change", "input"]);
const data = reactive({
top: tofix(props.top),
left: tofix(props.left),
});
watch(
() => props.top,
(v) => (data.top = tofix(v))
);
// 工具的实际坐标 -100 ~ 100
const top = ref(Math.round(props.top));
const left = ref(Math.round(props.left));
// 原点的坐标 0 ~ 100
const ballStyle = computed(() => ({
top: 50 + Number(top.value) / 2 + "%",
left: 50 + Number(left.value) / 2 + "%",
}));
watch(
() => props.left,
(v) => (data.left = tofix(v))
(v) => (left.value = Math.round(v))
);
watch(
() => props.top,
(v) => (top.value = Math.round(v))
);
const dishRef = ref<HTMLDivElement>();
const mousedown = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const { left, top, width, height } =
dishRef.value.getBoundingClientRect();
const rect = dishRef.value.getBoundingClientRect();
const X = e.clientX || (e as TouchEvent).touches[0].clientX;
const Y = e.clientY || (e as TouchEvent).touches[0].clientY;
var x = ((X - left) / width) * 100;
var y = ((Y - top) / height) * 100;
var x = ((X - rect.left) / rect.width) * 100;
var y = ((Y - rect.top) / rect.height) * 100;
if (x < 0) x = 0;
if (x > 100) x = 100;
if (y < 0) y = 0;
if (y > 100) y = 100;
data.left = tofix(x);
data.top = tofix(y);
left.value = Math.round((x - 50) * 2);
top.value = Math.round((y - 50) * 2);
onInput();
};
mousemove(e);
@@ -94,96 +110,125 @@
document.addEventListener("mouseup", mouseup);
document.addEventListener("touchend", mouseup);
};
const onInput = () => emit("input", { ...data });
const onInput = () => {
emit("input", { left: left.value, top: top.value });
};
var changeTime: any = null;
const onChange = () => {
clearTimeout(changeTime);
changeTime = setTimeout(() => emit("change", { ...data }), 500);
changeTime = setTimeout(() => {
emit("change", {
left: left.value,
top: top.value,
});
}, 500);
};
// var offsetTime = null;
// watch(data, (v) => {
// const obj = { ...v };
// emit("input", obj);
// clearTimeout(offsetTime);
// offsetTime = setTimeout(() => emit("change", obj), 50);
// });
// defineExpose({
// open,
// close,
// });
const lineZStyle = computed(() => ({
"--rotateZ": calculateAngle(0, 0, left.value, top.value) + "deg",
width: calculateDistance(0, 0, left.value, top.value) / 2 + "%",
}));
// 计算角度
function calculateAngle(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1;
const deltaY = y1 - y2;
let angle = Math.atan2(deltaX, deltaY) * (180 / Math.PI) - 90;
return angle;
}
// 计算距离
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1;
const deltaY = y2 - y1;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return distance;
}
</script>
<style scoped lang="less">
.offset-tool {
width: 125px;
height: 125px;
display: flex;
position: relative;
overflow: hidden;
--gap: 15px;
> .input {
display: flex;
align-items: center;
justify-content: center;
> * {
flex: 1;
margin-right: 12px;
&:last-child {
margin-right: 0;
}
}
}
> .dish {
margin: var(--gap) 0 0 var(--gap);
flex: 1;
border: 1px solid #000;
border-radius: 5px;
width: 135px;
height: 135px;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;
position: relative;
background-color: #fff;
> span {
background-color: #f6f6f6;
margin-top: 24px;
> * {
position: absolute;
pointer-events: none;
user-select: none;
position: absolute;
top: 0%;
left: 0%;
}
> img {
width: 15px;
height: 15px;
bottom: 4px;
right: 4px;
}
> .ball {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
background-color: #000;
width: 10px;
height: 10px;
border: 1px solid #fff;
background-color: #333;
border-radius: 50%;
box-shadow: 0px 0.68px 1.7px 0px rgba(0, 0, 0, 0.26);
}
}
> .tip {
position: absolute;
right: 4px;
bottom: 0;
font-size: 10px;
pointer-events: none;
user-select: none;
color: #666;
}
> input.left {
right: 0;
}
> input.top {
bottom: 0;
left: 0;
transform-origin: left bottom;
transform: rotate(90deg) translateX(-100%);
}
> input {
position: absolute;
width: calc(100% - var(--gap));
-webkit-appearance: none;
appearance: none;
height: 8px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.1); /* 更柔和的颜色 */
// outline: none;
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4285f4; /* 蓝色滑块 */
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
> .tip {
font-size: 10px;
color: #000;
line-height: 24px;
&.x {
top: 50%;
right: 0%;
transform: translate(100%, -50%);
padding-left: 6px;
}
&.y {
top: 0%;
left: 50%;
transform: translate(-50%, -100%);
}
}
&::-webkit-slider-thumb:hover {
background: #3b77db;
transform: scale(1.1);
> .line {
border-color: #d9d9d9;
border-style: dashed;
border-width: 0;
width: 0;
height: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
&.x {
width: 100%;
border-top-width: 1px;
}
&.y {
height: 100%;
border-left-width: 1px;
}
&.z {
width: 50%;
border-top-width: 1px;
border-color: #454754;
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
transform-origin: left center;
}
}
}
}

View File

@@ -1,13 +1,12 @@
<template>
<div class="slider" :disabled="disabled">
<div class="input-range">
<span
class="tip"
:style="{
'--progress': (value - props.min) / (props.max - props.min),
}"
>{{ props.tipFormatter(value) }}</span
>
<div
class="input-range"
:style="{
'--progress': (value - props.min) / (props.max - props.min),
}"
>
<span class="tip">{{ props.tipFormatter(value) }}</span>
<input
type="range"
v-model="value"
@@ -20,8 +19,7 @@
/>
</div>
<div class="input" v-show="isInput">
<input
type="number"
<my-input
v-model="value"
:min="props.min"
:max="props.max"
@@ -29,6 +27,7 @@
@input="onInput"
@change="onChange"
:disabled="disabled"
type="number"
/>
</div>
</div>
@@ -36,6 +35,7 @@
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
import MyInput from "./MyInput.vue";
const props = defineProps({
disabled: {
type: Boolean,
@@ -86,9 +86,10 @@
position: relative;
display: flex;
align-items: center;
--input-thumb-size: 12px;
width: 150px;
// &:focus-within,
--input-thumb-size: 10px;
--backcolor1: var(--slider-thumb-color1, #4285f4);
--backcolor2: var(--slider-thumb-color2, rgba(0, 0, 0, 0.1));
&:hover {
> .input-range > .tip {
display: block;
@@ -103,21 +104,26 @@
appearance: none;
height: 5px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.1); /* 更柔和的颜色 */
outline: none;
background: linear-gradient(
to right,
var(--backcolor1) 0%,
var(--backcolor1) calc(var(--progress) * 100%),
var(--backcolor2) calc(var(--progress) * 100%),
var(--backcolor2) 100%
);
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: var(--input-thumb-size);
height: var(--input-thumb-size);
border-radius: 50%;
background: #4285f4; /* 蓝色滑块 */
background: var(--backcolor1); /* 蓝色滑块 */
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
&::-webkit-slider-thumb:hover {
background: #3b77db;
transform: scale(1.1);
}
}

View File

@@ -17,6 +17,7 @@ import { KeyboardManager } from "./managers/events/KeyboardManager.js";
import CanvasConfig from "./config/canvasConfig.js";
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
import { SelectionManager } from "./managers/selection/SelectionManager";
import { PartManager } from "./managers/PartManager";
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
import texturePresetManager from "./managers/brushes/TexturePresetManager";
import { BrushStore } from "./store/BrushStore";
@@ -38,6 +39,7 @@ import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑
import PalletPanel from "./components/PalletPanel/index.vue";
import SelectMenuPanel from "./components/SelectMenuPanel/index.vue"; // 引入选择工具菜单组件
import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板
import PartSelectorPanel from "./components/PartSelectorPanel.vue"; // 引入部件选取面板
import { LayerType, OperationType } from "./utils/layerHelper.js";
import { ToolManager } from "./managers/ToolManager.js";
import { fabric } from "fabric-with-all";
@@ -45,6 +47,7 @@ import {
uploadImageAndCreateLayer,
loadImageUrlToLayer,
loadImage,
resizeImage,
} from "./utils/imageHelper.js";
import { optimizeCanvasRendering } from "./utils/helper";
// import MinimapPanel from "./components/MinimapPanel.vue";
@@ -84,6 +87,10 @@ const props = defineProps({
type: Boolean,
default: false, // 是否启用红绿图模式
},
clothingMinIOPath: {
type: String,
default: "", // 衣服底图URL-线稿
},
clothingImageUrl: {
type: String,
default: "", // 衣服底图URL-线稿
@@ -184,6 +191,7 @@ let keyboardManager = null;
let toolManager = null;
let liquifyManager = null;
let selectionManager = null;
let partManager = null;
let redGreenModeManager = null;
// 快捷键帮助模态框状态
@@ -225,6 +233,7 @@ function handleCanvasInit(isLoadJson = false) {
keyboardManager,
liquifyManager,
selectionManager,
partManager,
redGreenModeManager,
});
}
@@ -374,9 +383,20 @@ onMounted(async () => {
selectionManager = new SelectionManager({
canvas: canvasManager.canvas,
layerManager,
props,
});
canvasManager.setSelectionManager(selectionManager);
// 初始化部件选择管理器
partManager = new PartManager({
canvas: canvasManager.canvas,
layerManager,
canvasManager,
toolManager,
props,
});
canvasManager.setPartManager(partManager);
if (props.canvasJSON) {
// 如果传入了初始JSON数据加载到画布上
if (typeof props.canvasJSON === "string") {
@@ -536,6 +556,7 @@ onBeforeUnmount(async () => {
toolManager?.dispose?.();
liquifyManager?.dispose?.();
selectionManager?.dispose?.();
partManager?.dispose?.();
redGreenModeManager?.dispose?.();
// minimapManager?.dispose?.();
canvasManager = null;
@@ -545,6 +566,7 @@ onBeforeUnmount(async () => {
toolManager = null;
liquifyManager = null;
selectionManager = null;
partManager = null;
redGreenModeManager = null;
// fabric.Object.prototype.controls.deleteControl = undefined;
@@ -709,8 +731,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) {
@@ -995,26 +1022,38 @@ defineExpose({
);
},
// 导出图片
exportImage: ({
exportImage: async ({
isContainBg = false, // 是否包含背景图层
isContainFixed = false, // 是否包含固定图层
isContainFixedOther = false, // 是否包含其他固定图层
isContainFixedOther = true, // 是否包含其他固定图层--颜色图层
isPrintTrimsNoRepeat = true, // 是否包含印花图层的不平铺
isPrintTrimsRepeat = true, // 是否包含印花图层的平铺
isContainNormalLayer = true, // 是否包含普通图层
isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true则导出时裁剪到背景图层大小
layerId = "", // 导出具体图层ID
layerIdArray = [], // 导出多个图层ID数组
expPicType = "png", // 导出图片类型 JPG 或 PNG ,SVG
isEnhanceImg, // 是否是增强图片
width = 0,// 导出的图片宽度
height = 0, // 导出的图片高度
} = {}) => {
return canvasManager.exportImage({
var base64 = await canvasManager.exportImage({
isContainBg,
isContainFixed,
isContainFixedOther,
isPrintTrimsNoRepeat,
isPrintTrimsRepeat,
isContainNormalLayer,
isCropByBg,
layerId,
layerIdArray,
expPicType,
isEnhanceImg,
});
if(width > 0 && height > 0){
base64 = await resizeImage(base64, width, height);
}
return base64;
},
// 导出颜色图层
exportColorLayer: () => {
@@ -1177,6 +1216,7 @@ defineExpose({
v-if="canvasManagerLoaded"
:activeTool="activeTool"
:isRedGreenMode="isRedGreenMode"
:clothingMinIOPath="props.clothingMinIOPath"
@tool-selected="handleToolSelect"
@red-green-tool-selected="handleRedGreenToolSelect"
@toggle-red-green-mode="toggleRedGreenMode"
@@ -1248,6 +1288,19 @@ defineExpose({
:activeTool="activeTool"
/>
<!-- 部件选取面板 -->
<PartSelectorPanel
v-if="canvasManagerLoaded && !enabledRedGreenMode"
:canvas="canvasManager && canvasManager.canvas"
:commandManager="commandManager"
:selectionManager="selectionManager"
:partManager="partManager"
:layerManager="layerManager"
:canvasManager="canvasManager"
:toolManager="toolManager"
:activeTool="activeTool"
/>
<!-- 文本编辑面板 -->
<TextEditorPanel
v-if="canvasManagerLoaded && !enabledRedGreenMode"
@@ -1401,6 +1454,7 @@ defineExpose({
/* background-color: #f8f8f8; */
:deep(.canvas-container) {
position: absolute !important;
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
}
}
@@ -1413,33 +1467,31 @@ defineExpose({
}
.background-grid {
--offsetX: 0px;
--offsetY: 0px;
--size: 8px;
--color: #dedcdc;
--offsetX: 50%;
--offsetY: 50%;
--size: 10px;
--color: rgba(229, 229,229,0.5);
background-image: -webkit-linear-gradient(
45deg,
var(--color) 25%,
90deg,
var(--color) 1px,
transparent 0,
transparent 75%,
var(--color) 0
),
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent
75%, var(--color) 0);
background-image: linear-gradient(
45deg,
var(--color) 25%,
-webkit-linear-gradient(
0,
var(--color) 1px,
transparent 0,
);
background-image:linear-gradient(
90deg,
var(--color) 1px,
transparent 0,
transparent 75%,
var(--color) 0
),
linear-gradient(
45deg,
var(--color) 25%,
0,
var(--color) 1px,
transparent 0,
transparent 75%,
var(--color) 0
);
background-color: #fafafa;
background-position: var(--offsetX) var(--offsetY),
calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY));
background-size: calc(var(--size) * 2) calc(var(--size) * 2);

View File

@@ -13,7 +13,6 @@ import {
createLayer,
LayerType,
SpecialLayerId,
SpecialType,
BlendMode,
} from "../utils/layerHelper";
import { ObjectMoveCommand } from "../commands/ObjectCommands";
@@ -35,6 +34,7 @@ import {
createPatternTransform,
getTransformScaleAngle,
base64ToCanvas,
imageAddGapToCanvas,
} from "../utils/helper";
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
import { isFunction } from "lodash-es";
@@ -45,7 +45,7 @@ import {
} from "../utils/layerUtils";
import { imageModeHandler } from "../utils/imageHelper";
import { getObjectAlphaToCanvas } from "../utils/objectHelper";
import { AddLayerCommand, RemoveLayerCommand } from "../commands/LayerCommands";
import { AddLayerCommand, RemoveLayerCommand, ToggleChildLayerVisibilityCommand } from "../commands/LayerCommands";
import { fa, id } from "element-plus/es/locales.mjs";
import i18n from "@/lang/index.ts";
const {t} = i18n.global;
@@ -305,6 +305,19 @@ export class CanvasManager {
}
}
/**
* 设置部件选择管理器
* @param {Object} partManager 部件选择管理器实例
*/
setPartManager(partManager) {
this.partManager = partManager;
// 如果已创建事件管理器,更新它的部件选择管理器引用
if (this.eventManager) {
this.eventManager.partManager = this.partManager;
}
}
// 设置红绿图模式管理器
setRedGreenModeManager(redGreenModeManager) {
this.redGreenModeManager = redGreenModeManager;
@@ -717,6 +730,8 @@ export class CanvasManager {
originX: backgroundLayerObject.originX || "left",
originY: backgroundLayerObject.originY || "top",
absolutePositioned: true,
rx: 15,
ry: 15,
});
}
getBackgroundLayer() {
@@ -827,7 +842,7 @@ export class CanvasManager {
updateMaskPosition(backgroundLayerObject) {
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
return;
console.log("backgroundLayerObject");
const left = backgroundLayerObject.left;
const top = backgroundLayerObject.top;
@@ -922,6 +937,9 @@ export class CanvasManager {
* @param {Boolean} options.isContainBg 是否包含背景图层
* @param {Boolean} options.isContainFixed 是否包含固定图层
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
* @param {Boolean} options.isPrintTrimsNoRepeat 是否包含印花图层的不平铺
* @param {Boolean} options.isPrintTrimsRepeat 是否包含印花图层的平铺
* @param {Boolean} options.isContainNormalLayer 是否包含普通图层
* @param {String} options.layerId 导出具体图层ID
* @param {Array} options.layerIdArray 导出多个图层ID数组
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
@@ -942,6 +960,9 @@ export class CanvasManager {
// this.canvas.renderAll(); // 重新渲染画布
// 自动设置红绿图模式相关参数
const enhancedOptions = {
isPrintTrimsNoRepeat: true,
isPrintTrimsRepeat: true,
isContainNormalLayer: true,
...options,
// 如果没有明确指定,则根据当前模式自动设置
restoreOpacityInRedGreen:
@@ -972,7 +993,46 @@ export class CanvasManager {
console.log("红绿图模式导出图层:", normalLayerIds);
}
}
return await this.exportManager.exportImage(enhancedOptions);
// 处理特殊图层的显示状态
const ptlids = [];
if(!enhancedOptions.isPrintTrimsNoRepeat || !enhancedOptions.isPrintTrimsRepeat){
let layers = this.layers?.value?.find((layer) => layer.isPrintTrimsGroup)?.children || [];
for(let layer of layers){
if(!layer.visible) continue;
let repeat = layer.fabricObjects?.[0]?.fill?.repeat || "no-repeat";
if(typeof repeat !== "string") repeat = "no-repeat";
if(repeat === "no-repeat"){
if(enhancedOptions.isPrintTrimsNoRepeat) continue;
}else{
if(enhancedOptions.isPrintTrimsRepeat) continue;
}
ptlids.push(layer.id);
const command = new ToggleChildLayerVisibilityCommand({
canvas: this.canvas,
layers: this.layers,
layerId: layer.id,
layerManager: this.layerManager,
});
await command.execute(false);
}
await this.changeCanvas();
}
const res = await this.exportManager.exportImage(enhancedOptions);
// 恢复特殊图层的显示状态
if(ptlids.length > 0){
for(let id of ptlids){
const command = new ToggleChildLayerVisibilityCommand({
canvas: this.canvas,
layers: this.layers,
layerId: id,
layerManager: this.layerManager,
});
await command.execute(true);
}
await this.changeCanvas();
}
return res;
} catch (error) {
console.warn("CanvasManager导出图片失败:", error);
throw error;
@@ -989,10 +1049,12 @@ export class CanvasManager {
// 导出印花和元素图层信息
const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null}));
return {
const obj = {
color,
...printTrimsData,
};
console.log("==========exportExtraInfo:", obj);
return obj;
}
/**
@@ -1533,7 +1595,7 @@ export class CanvasManager {
selectable: true,
hasControls: true,
hasBorders: true,
specialType: SpecialType.PRINT_TRIMS_O,
isPrintTrims: true,
globalCompositeOperation: BlendMode.MULTIPLY,
});
resolve(fabricImage);
@@ -1547,7 +1609,7 @@ export class CanvasManager {
visible: true,
locked: false,
opacity: 1.0,
specialType: SpecialType.PRINT_TRIMS_L,
isPrintTrims: true,
blendMode: BlendMode.MULTIPLY,
fabricObjects: [image.toObject(["id", "layerId", "layerName"])],
metadata: {sourceData: item},
@@ -1571,37 +1633,57 @@ export class CanvasManager {
resolve(tcanvas);
}, { crossOrigin: "anonymous" });
})
let scaleX = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5;
let scaleY = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5;
let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX : scaleY;
let left = (item.location?.[0] || 0) - image.width * scale / 2
let top = (item.location?.[1] || 0) - image.height * scale / 2
let scaleX_ = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5;
let scaleY_ = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5;
let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX_ : scaleY_;
let offsetX = (item.location?.[0] || 0) - image.width * scale / 2
let offsetY = (item.location?.[1] || 0) - image.height * scale / 2
let top = fixedLayerObj.top - fixedLayerObj.height * fixedLayerObj.scaleY / 2
let left = fixedLayerObj.left - fixedLayerObj.width * fixedLayerObj.scaleX / 2
let scaleX = fixedLayerObj.scaleX
let scaleY = fixedLayerObj.scaleY
let opacity = 1
let angle = 0
let gapX = 0
let gapY = 0
let fillSource = image
if(item.object){
top += item.object.top * fixedLayerObj.scaleY
left += item.object.left * fixedLayerObj.scaleX
scaleX *= item.object.scaleX
scaleY *= item.object.scaleY
opacity = item.object.opacity
angle = item.object.angle
gapX = item.object.gapX
gapY = item.object.gapY
fillSource = imageAddGapToCanvas(image, gapX, gapY);
}
let rect = new fabric.Rect({
id: id,
layerId: id,
layerName: name,
width: fixedLayerObj.width,
height: fixedLayerObj.height,
top: fixedLayerObj.top - fixedLayerObj.height * fixedLayerObj.scaleY / 2,
left: fixedLayerObj.left - fixedLayerObj.width * fixedLayerObj.scaleX / 2,
scaleX: fixedLayerObj.scaleX,
scaleY: fixedLayerObj.scaleY,
top: top,
left: left,
scaleX: scaleX,
scaleY: scaleY,
globalCompositeOperation: BlendMode.MULTIPLY,
fill: new fabric.Pattern({
source: image,
source: fillSource,
repeat: "repeat",
patternTransform: createPatternTransform(scale, item.angle || 0),
offsetX: left, // 水平偏移
offsetY: top, // 垂直偏移
offsetX: offsetX, // 水平偏移
offsetY: offsetY, // 垂直偏移
}),
fill_ : {
source: item.path,
gapX: 0,
gapY: 0,
gapX: gapX,
gapY: gapY,
width: image.width,
height: image.height,
},
specialType: SpecialType.REPEAT_O,
isPrintTrims: true,
});
this.canvas.add(rect);
let layer = createLayer({
@@ -1611,7 +1693,7 @@ export class CanvasManager {
visible: true,
locked: true,
opacity: 1,
specialType: SpecialType.REPEAT_L,
isPrintTrims: true,
blendMode: BlendMode.MULTIPLY,
fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
metadata: {sourceData: item},
@@ -1630,7 +1712,7 @@ export class CanvasManager {
// })
// children.push(layer);
// }
// if(children.length === 0) return;
if(children.length === 0) return;
const groupRect = new fabric.Rect({});
await this.setObjecCliptInfo(groupRect);
// 插入组图层
@@ -1646,11 +1728,8 @@ export class CanvasManager {
children: children,
clippingMask: groupRect.toObject(),
isPrintTrimsGroup: true,
specialType: SpecialType.PRINT_TRIMS_G,
});
this.layers.value.splice(groupIndex, 0, groupLayer);
console.log("==========layers", [...this.layers.value]);
}
/**

View File

@@ -20,6 +20,7 @@ export class ExportManager {
* @param {Boolean} options.isContainBg 是否包含背景图层
* @param {Boolean} options.isContainFixed 是否包含固定图层
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
* @param {Boolean} options.isContainNormalLayer 是否包含普通图层
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
* @param {String} options.layerId 导出具体图层ID
* @param {Array} options.layerIdArray 导出多个图层ID数组
@@ -35,6 +36,7 @@ export class ExportManager {
isContainBg = false,
isContainFixed = false,
isContainFixedOther = false, // 是否包含其他固定图层
isContainNormalLayer = true, // 是否包含普通图层
isCropByBg = false, // 是否使用背景大小裁剪
layerId = "",
layerIdArray = [],
@@ -68,6 +70,7 @@ export class ExportManager {
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg,
@@ -81,6 +84,7 @@ export class ExportManager {
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg,
@@ -160,6 +164,7 @@ export class ExportManager {
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
@@ -173,6 +178,7 @@ export class ExportManager {
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer = true, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
@@ -188,6 +194,7 @@ export class ExportManager {
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
);
if (objectsToExport.length === 0) {
@@ -220,6 +227,7 @@ export class ExportManager {
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
@@ -233,6 +241,7 @@ export class ExportManager {
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
@@ -246,6 +255,7 @@ export class ExportManager {
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
excludedLayers,
);
@@ -405,11 +415,12 @@ export class ExportManager {
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @param {Array} excludedLayers 排除的图层ID数组
* @returns {Array} 按正确顺序排列的真实对象数组
* @private
*/
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, excludedLayers) {
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer, excludedLayers) {
const objectsToExport = [];
const allLayers = this._getAllLayersFlattened(excludedLayers); // 获取扁平化的图层列表
@@ -421,7 +432,7 @@ export class ExportManager {
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
// 检查图层类型过滤条件
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther))
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer))
continue;
if (layer.visible) {
@@ -1040,10 +1051,11 @@ export class ExportManager {
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @returns {Boolean} 是否应该包含
* @private
*/
_shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther) {
_shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer) {
if (!layer) return false;
// 检查背景图层
@@ -1061,7 +1073,12 @@ export class ExportManager {
return isContainFixedOther;
}
// 普通图层总是包含
return true;
// 印花图层始终导出
if (layer.isPrintTrims || layer.isPrintTrimsGroup) {
return true;
}
// 普通图层
return isContainNormalLayer;
}
}

View File

@@ -0,0 +1,325 @@
import { fabric } from "fabric-with-all";
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";
import { Https } from "@/tool/https";
import store from "@/store";
/**
* 部件选择管理器
*/
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.props = options.props;
// 状态
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 fixedObject = this.canvasManager.getFixedLayerObject();
if (!fixedObject) return console.warn("未找到固定图层");
const { x, y } = options.pointer;
const width = fixedObject.width * fixedObject.scaleX;
const height = fixedObject.height * fixedObject.scaleY;
const X = x - (fixedObject.left - width / 2);
const Y = y - (fixedObject.top - height / 2);
this.getSegAnythingImage({
image_path: "aida-users/24299/sketch/70bb39cc-63e0-44a9-a627-3542d0f9cd70.png",
type: "point",
points: [[X, Y], [X - 10, Y - 10]],
labels: [1, 0],
});
}
// 框选工具模式下点击事件处理
_rectangleDownHandler(options) {
}
// 框选工具模式下移动事件处理
_rectangleMoveHandler(options) {
}
// 框选工具模式下抬起事件处理
_rectangleUpHandler(options) {
}
// 绘制工具模式下点击事件处理
_brushDownHandler(options) {
}
// 绘制工具模式下移动事件处理
_brushMoveHandler(options) {
}
// 绘制工具模式下抬起事件处理
_brushUpHandler(options) {
}
// 擦除工具模式下抬起事件处理
_eraseUpHandler(options) {
}
// 擦除工具模式下点击事件处理
_eraseDownHandler(options) {
}
// 擦除工具模式下移动事件处理
_eraseMoveHandler(options) {
}
// 获取分隔后图片
async getSegAnythingImage(obj) {
const user_id = store.state.UserHabit.userDetail.userId;
const data = {
user_id,
...obj,
}
Https.axiosPost(Https.httpUrls.segAnything, data)
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
}
/**
* 清除选区
*/
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;
}
}

View File

@@ -37,6 +37,7 @@ export class ToolManager {
this.activeTool = options.activeTool || {
value: OperationType.SELECT,
};
this.props = options.props;
// 红绿图模式状态
this.isRedGreenMode = false;
@@ -67,6 +68,12 @@ export class ToolManager {
// 工具列表 - 与OperationType保持一致
this.tools = {
// 禁用工具
[OperationType.DISABLED]: {
name: "禁用工具",
icon: "disabled",
cursor: "not-allowed",
},
// 基础工具
[OperationType.SELECT]: {
name: "选择工具",
@@ -83,6 +90,7 @@ export class ToolManager {
shortcut: "B",
setup: this.setupBrushTool.bind(this),
allowedInRedGreen: false,
specialLayerDisabled: true,
},
[OperationType.ERASER]: {
name: "橡皮擦",
@@ -91,6 +99,7 @@ export class ToolManager {
shortcut: "E",
setup: this.setupEraserTool.bind(this),
allowedInRedGreen: true, // 红绿图模式允许橡皮擦
specialLayerDisabled: true,
},
[OperationType.EYEDROPPER]: {
name: "吸色工具",
@@ -117,6 +126,7 @@ export class ToolManager {
shortcut: "L",
setup: this.setupLassoTool.bind(this),
allowedInRedGreen: false,
specialLayerDisabled: true,
},
[OperationType.LASSO_RECTANGLE]: {
name: "矩形套索工具",
@@ -126,6 +136,7 @@ export class ToolManager {
altKey: true,
setup: this.setupRectangleLassoTool.bind(this),
allowedInRedGreen: false,
specialLayerDisabled: true,
},
[OperationType.LASSO_ELLIPSE]: {
name: "椭圆形套索工具",
@@ -135,6 +146,7 @@ export class ToolManager {
altKey: true,
setup: this.setupEllipseLassoTool.bind(this),
allowedInRedGreen: false,
specialLayerDisabled: true,
},
// 选区工具 - 只需要矩形选区
@@ -164,6 +176,7 @@ export class ToolManager {
shortcut: "J",
setup: this.setupLiquifyTool.bind(this),
allowedInRedGreen: false, // 红绿图模式不允许液化
specialLayerDisabled: true,
},
[OperationType.TEXT]: {
name: "文本工具",
@@ -174,6 +187,32 @@ export class ToolManager {
allowedInRedGreen: false, // 红绿图模式不允许文本
},
// 部件选取工具
[OperationType.PART]: {
name: "部件选取工具",
icon: "part",
cursor: "default",
setup: this.setupPartTool.bind(this),
},
[OperationType.PART_RECTANGLE]: {
name: "部件选取工具-矩形",
icon: "part",
cursor: "default",
setup: this.setupPartRectangleTool.bind(this),
},
[OperationType.PART_BRUSH]: {
name: "部件选取工具-画笔",
icon: "part",
cursor: "default",
setup: this.setupPartBrushTool.bind(this),
},
[OperationType.PART_ERASER]: {
name: "部件选取工具-橡皮擦",
icon: "part",
cursor: "default",
setup: this.setupPartEraserTool.bind(this),
},
// 红绿图模式专用工具
[OperationType.RED_BRUSH]: {
name: "红色笔刷",
@@ -331,8 +370,9 @@ export class ToolManager {
* @param {String} toolId 工具ID
*/
setTool(toolId) {
const tool = this.tools[toolId];
// 检查工具是否存在
if (!this.tools[toolId]) {
if (!tool) {
console.error(`工具 '${toolId}' 不存在`);
return;
}
@@ -348,15 +388,20 @@ export class ToolManager {
console.warn(`工具 '${toolId}' 只能在红绿图模式下使用`);
return;
}
if(tool?.specialLayerDisabled && this.checkToolCanOperateSelectedObject()){
console.warn(`工具 '${toolId}' 不能在当前选中对象上操作`);
toolId = OperationType.DISABLED;
}
// 保存先前的工具
// 保存先前的工具
this.previousTool = this.activeTool.value;
// 取消画布的选中状态
this.canvas?.discardActiveObject();
this.canvasManager?.layerManager?.updateLayersObjectsInteractivity?.();
this.canvas?.renderAll();
if(toolId !== OperationType.DISABLED){
this.canvas?.discardActiveObject();
this.canvasManager?.layerManager?.updateLayersObjectsInteractivity?.();
this.canvas?.renderAll();
}
// 隐藏文本编辑面板
this.hideTextEditor();
@@ -374,7 +419,6 @@ export class ToolManager {
}
// 设置工具特定的状态
const tool = this.tools[toolId];
if (tool && typeof tool.setup === "function") {
console.log(`画布切换工具:${tool.name}(${toolId})`)
this.canvas.toolId = toolId;
@@ -424,7 +468,7 @@ export class ToolManager {
const currentTool = this.activeTool.value;
const tool = this.tools[currentTool];
if(tool?.specialLayerDisabled && this.checkToolCanOperateSelectedObject()) return;
// 根据当前工具设置selection状态
if (currentTool === OperationType.SELECT) {
this.canvas.selection = true;
@@ -455,24 +499,19 @@ export class ToolManager {
if (!this.canvas) return;
this.canvas.isDrawingMode = false;
this.canvas.selection = true;
}
/**
* 检查当前工具是否禁止操作当前选中的对象
* @param {Boolean} isBrushTool 是否为画笔工具
* @returns {Boolean} 是否可以切换
*/
checkToolCanOperateSelectedObject(isBrushTool = false) {
checkToolCanOperateSelectedObject() {
const layer = this.layerManager?.getActiveLayer();
const isSpecialLayer = !!layer?.specialType;
const isSpecialLayer = !!layer?.isPrintTrims || !!layer?.isPrintTrimsGroup;
if (isSpecialLayer) {
if(isBrushTool){
this._disableBrushIndicator();
}
this._disableBrushIndicator();
this.canvas.defaultCursor = "not-allowed";
}
console.log("===========",isSpecialLayer, this.canvas.defaultCursor);
return isSpecialLayer;
}
@@ -482,7 +521,7 @@ export class ToolManager {
*/
setupBrushTool() {
if (!this.canvas) return;
if (this.checkToolCanOperateSelectedObject(true)) return;
if (this.checkToolCanOperateSelectedObject()) return;
this.canvas.isDrawingMode = true;
this.canvas.selection = false;
@@ -526,7 +565,7 @@ export class ToolManager {
*/
setupEraserTool() {
if (!this.canvas) return;
if (this.checkToolCanOperateSelectedObject(true)) return;
if (this.checkToolCanOperateSelectedObject()) return;
this.canvas.isDrawingMode = true;
this.canvas.selection = false;
@@ -580,6 +619,7 @@ export class ToolManager {
*/
setupLassoTool() {
if (!this.canvas) return;
if (this.checkToolCanOperateSelectedObject()) return;
this.canvas.isDrawingMode = false;
this.canvas.selection = false;
@@ -661,6 +701,53 @@ export class ToolManager {
}
}
/**
* 设置部件选取工具
*/
setupPartTool() {
if (!this.canvas) 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);
}
}
/**
* 设置波浪工具
*/
@@ -676,7 +763,7 @@ export class ToolManager {
*/
setupLiquifyTool() {
if (!this.canvas || !this.layerManager) return;
if (this.checkToolCanOperateSelectedObject(true)) return;
if (this.checkToolCanOperateSelectedObject()) return;
this.canvas.isDrawingMode = false;
this.canvas.selection = false;

View File

@@ -1005,3 +1005,110 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) {
image.onerror = reject;
});
}
/**
* 图片边界跟踪算法(透明底)
* @param {HTMLCanvasElement} canvas - canvas元素
* @returns {Array} 边界点数组 [{x, y}, ...]
*/
export function traceImageContour(canvas) {
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
// 查找起始点(第一个不透明像素)
let startX = -1;
let startY = -1;
outer: for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
if (data[index + 3] > 0) {
startX = x;
startY = y;
break outer;
}
}
}
if (startX === -1) return []; // 没有不透明像素
// Moore-Neighbor边界跟踪算法
const contour = [];
const visited = new Set();
const directions = [
[-1, 0],
[-1, -1],
[0, -1],
[1, -1],
[1, 0],
[1, 1],
[0, 1],
[-1, 1],
];
let currentX = startX;
let currentY = startY;
let backtrackDir = 4; // 起始方向:右
do {
const pointKey = `${currentX},${currentY}`;
if (!visited.has(pointKey)) {
contour.push({ x: currentX, y: currentY });
visited.add(pointKey);
}
// 从右方向开始顺时针查找
let found = false;
for (let i = 0; i < 8; i++) {
const dir = (backtrackDir + i) % 8;
const dx = directions[dir][0];
const dy = directions[dir][1];
const checkX = currentX + dx;
const checkY = currentY + dy;
if (
checkX >= 0 &&
checkX < width &&
checkY >= 0 &&
checkY < height
) {
const index = (checkY * width + checkX) * 4;
if (data[index + 3] > 0) {
currentX = checkX;
currentY = checkY;
backtrackDir = (dir + 5) % 8; // 下一个开始查找的方向
found = true;
break;
}
}
}
if (!found) break;
} while (
!(currentX === startX && currentY === startY) &&
visited.size < width * height
);
return contour;
}
/**
* 图片添加gap转换
* @param {HTMLCanvasElement} image - img元素
* @param {Number} gapX - 水平gap
* @param {Number} gapY - 垂直gap
* @returns {HTMLCanvasElement} 转换后的canvas元素
*/
export function imageAddGapToCanvas(image, gapX, gapY) {
// 创建透明 Canvas
const tcanvas = document.createElement('canvas');
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
const ctx = tcanvas.getContext('2d');
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
return tcanvas;
}

View File

@@ -2191,3 +2191,27 @@ export const imageModeHandler = ({ imageMode, newImage, canvasWidth, canvasHeigh
}
}
};
/**
* 调整图像大小
* @param {string} base64 - 原始base64字符串
* @param {number} width - 目标宽度
* @param {number} height - 目标高度
* @returns {Promise<string>} 处理后的base64字符串
*/
export const resizeImage = async (base64, width, height) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = base64;
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
resolve(canvas.toDataURL());
};
img.onerror = reject;
});
};

View File

@@ -25,18 +25,6 @@ export const SpecialLayerId = {
SPECIAL_GROUP: "group_special", // 特殊组
COLOR: "special_color", // 颜色图层
}
/**
* 特殊类型
*/
export const SpecialType = {
PRINT_TRIMS_G: "print_trims_group", // 印花和元素图层组
PRINT_TRIMS_L: "print_trims_layer", // 印花和元素图层
PRINT_TRIMS_O: "print_trims_object", // 印花和元素图层对象
REPEAT_L: "repeat_layer",// 平铺图层
REPEAT_O: "repeat_object",// 平铺图层对象
}
/**
@@ -44,6 +32,7 @@ export const SpecialType = {
*/
export const OperationType = {
// 编辑器模式
DISABLED: "disabled", // 禁用
DRAW: "draw", // 绘画模式
ERASER: "eraser", // 橡皮擦模式
SELECT: "select", // 选择模式
@@ -76,6 +65,12 @@ export const OperationType = {
RED_BRUSH: "red_brush", // 红色笔刷
GREEN_BRUSH: "green_brush", // 绿色笔刷
// 部件选取工具
PART: "part", // 部件选取工具模式 - 点选模式
PART_RECTANGLE: "part_rectangle", // 部件选取工具模式 - 矩形模式
PART_BRUSH: "part_brush", // 部件选取工具模式 - 笔刷模式
PART_ERASER: "part_eraser", // 部件选取工具模式 - 橡皮擦模式
// SHAPE: "shape", // 形状模式
// 可以根据需要添加更多工具
};

View File

@@ -2,72 +2,122 @@
<div class="demo">
<div
class="control"
:class="{ active: item.id === activeId }"
:class="{ active: item.token === activeToken }"
v-for="(item, index) in list"
:key="item.id"
@click="onSelect(item.id)"
:key="item.token"
@click="onSelect(item.token)"
>
<div>
<b>{{ item.name }}</b>
<button
v-if="index !== 0"
@click="onMove(item.id, list[index - 1].id)"
@click="onMove(item.token, list[index - 1].token)"
>
</button>
<button
v-if="index !== list.length - 1"
@click="onMove(item.id, list[index + 1].id)"
@click="onMove(item.token, list[index + 1].token)"
>
</button>
<button @click.stop="onDelete(item.id)">删除</button>
<button @click.stop="onDelete(item.token)">删除</button>
</div>
<div>
<span>偏移X</span>
<input type="number" v-model="item.location[0]" />
<input
type="number"
v-model="item.location[0]"
@input="updateList(item, 'location[0]', item.location[0])"
/>
</div>
<div>
<span>偏移Y</span>
<input type="number" v-model="item.location[1]" />
<input
type="number"
v-model="item.location[1]"
@input="updateList(item, 'location[1]', item.location[1])"
/>
</div>
<div>
<span>角度</span>
<input type="number" v-model="item.angle" />
<input
type="number"
v-model="item.angle"
@input="updateList(item, 'angle', item.angle)"
/>
</div>
<div>
<span>缩放</span>
<input type="number" v-model="item.scale[0]" />
<input
type="number"
v-model="item.scale[0]"
@input="updateList(item, 'scale[0]', item.scale[0])"
/>
</div>
<div>
<span>水平间距</span>
<input type="number" v-model="item.object.gapX" />
<input
type="number"
v-model="item.object.gapX"
@input="updateList(item, 'object.gapX', item.object.gapX)"
/>
</div>
<div>
<span>垂直间距</span>
<input type="number" v-model="item.object.gapY" />
<input
type="number"
v-model="item.object.gapY"
@input="updateList(item, 'object.gapY', item.object.gapY)"
/>
</div>
<hr />
<hr />
<div>
<span>缩放X</span>
<input type="number" v-model="item.object.scaleX" step="0.1" />
<input
type="number"
v-model="item.object.scaleX"
step="0.1"
@input="
updateList(item, 'object.scaleX', item.object.scaleX)
"
/>
</div>
<div>
<span>缩放Y</span>
<input type="number" v-model="item.object.scaleY" step="0.1" />
<input
type="number"
v-model="item.object.scaleY"
step="0.1"
@input="
updateList(item, 'object.scaleY', item.object.scaleY)
"
/>
</div>
<div>
<span>X</span>
<input type="number" v-model="item.object.left" />
<input
type="number"
v-model="item.object.left"
@input="updateList(item, 'object.left', item.object.left)"
/>
</div>
<div>
<span>Y</span>
<input type="number" v-model="item.object.top" />
<input
type="number"
v-model="item.object.top"
@input="updateList(item, 'object.top', item.object.top)"
/>
</div>
<div>
<span>角度</span>
<input type="number" v-model="item.object.angle" />
<input
type="number"
v-model="item.object.angle"
@input="updateList(item, 'object.angle', item.object.angle)"
/>
</div>
<div>
@@ -78,6 +128,9 @@
step="0.1"
min="0"
max="1"
@input="
updateList(item, 'object.opacity', item.object.opacity)
"
/>
</div>
</div>
@@ -86,6 +139,8 @@
<pingpu
:list="list"
ref="pingpuRef"
:width="600"
:height="900"
@change-canvas="updateCanvas"
/>
</div>
@@ -105,10 +160,10 @@
const convertDotNotationToBracket = (str) =>
str.replace(/(?:^|\.)(\d+)(?=\.|$)/g, "[#$1]").replace(/\[#/g, "[");
const activeId = ref("1");
const activeToken = ref("1");
const list = ref([
{
id: "1",
token: "1",
ifSingle: false,
level2Type: "Pattern",
designType: "Library",
@@ -133,7 +188,7 @@
},
},
{
id: "2",
token: "2",
ifSingle: false,
level2Type: "Pattern",
designType: "Library",
@@ -144,12 +199,12 @@
name: "Print2",
priority: 1,
object: {
top: 150,
left: 250,
top: 450,
left: 300,
scaleX: 0.5,
scaleY: 0.5,
opacity: 1,
angle: 45,
angle: 0,
flipX: false,
flipY: false,
blendMode: "multiply",
@@ -163,42 +218,37 @@
const oldList = ref(deepCopy(list.value));
const pingpuRef = ref(null);
const updateCanvas = (arr) => {
console.log(arr);
oldList.value = deepCopy(list.value);
arr.forEach((item) => {
list.value.forEach((v) => {
const obj = list.value.find((v) => v.token === item.token);
if (item.action === ACTIONS.UPDATE) {
if (item.action === ACTIONS.UPDATE) {
if (v.id === item.id) {
if (item.action === ACTIONS.UPDATE) {
try {
const key = item.key;
const str = /^\[/.test(item.key)
? "v" + key
: "v." + key;
eval(`${str} = item.value`);
} catch (error) {
console.error(error);
}
}
try {
const key = item.key;
const str = /^\[/.test(item.key)
? "obj" + key
: "obj." + key;
eval(`${str} = item.value`);
} catch (error) {
console.error(error);
}
} else if (item.action === ACTIONS.SELECT) {
activeId.value = item.id;
}
});
} 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);
}
});
};
const onSelect = (id) => {
activeId.value = id;
pingpuRef.value.updataList([
{
id: id,
action: ACTIONS.SELECT,
},
]);
const onSelect = (token) => {
activeToken.value = token;
pingpuRef.value.updataList([{ token, action: ACTIONS.SELECT }]);
};
const onMove = (id, id2) => {
const obj1 = list.value.find((v) => v.id === id);
const obj2 = list.value.find((v) => v.id === id2);
const onMove = (token1, token2) => {
const obj1 = list.value.find((v) => v.token === token1);
const obj2 = list.value.find((v) => v.token === token2);
const index1 = list.value.indexOf(obj1);
const index2 = list.value.indexOf(obj2);
if (index1 < index2) {
@@ -206,26 +256,16 @@
} else {
list.value.splice(index1, 0, list.value.splice(index2, 1)[0]);
}
const ids = list.value.map((v) => v.id);
pingpuRef.value.updataList([
{
action: ACTIONS.SORT,
ids,
},
]);
const tokens = list.value.map((v) => v.token);
pingpuRef.value.updataList([{ action: ACTIONS.SORT, tokens }]);
};
const onDelete = (id) => {
list.value = list.value.filter((v) => v.id !== id);
pingpuRef.value.updataList([
{
id: id,
action: ACTIONS.DELETE,
},
]);
const onDelete = (token) => {
list.value = list.value.filter((v) => v.token !== token);
pingpuRef.value.updataList([{ token, action: ACTIONS.DELETE }]);
};
const onAdd = () => {
const obj = {
id: Date.now().toString(),
token: Date.now().toString(),
ifSingle: false,
level2Type: "Pattern",
designType: "Library",
@@ -250,59 +290,20 @@
},
};
list.value.push(obj);
pingpuRef.value.updataList([{ action: ACTIONS.ADD, data: obj }]);
};
// 监听列表变化属性变更
const updateList = (item, key, value) => {
if (key === "scale[0]") item.scale[1] = value;
pingpuRef.value.updataList([
{
action: ACTIONS.ADD,
data: obj,
token: item.token,
action: ACTIONS.UPDATE,
key,
value,
},
]);
};
watch(
() => list.value,
() => updateList(),
{ deep: true }
);
// 监听列表变化属性变更
const updateList = () => {
const changeList = [];
oldList.value.forEach((oldItem) => {
const newItem = list.value.find((v) => v.id === oldItem.id);
if (newItem) {
const arr = findDiffProps(oldItem, newItem);
arr.forEach((item) => {
changeList.push({
...item,
id: oldItem.id,
action: ACTIONS.UPDATE,
key: convertDotNotationToBracket(item.key),
});
});
} else {
changeList.push({
id: oldItem.id,
action: ACTIONS.DELETE,
});
}
});
oldList.value = deepCopy(list.value);
if (changeList.length) {
pingpuRef.value.updataList(changeList);
}
};
// 递归查找不同的属性
const findDiffProps = (obj1, obj2, diffProps = [], path = "") => {
for (const key in obj1) {
if (typeof obj1[key] === "object") {
findDiffProps(obj1[key], obj2[key], diffProps, `${path}${key}.`);
} else if (obj1[key] !== obj2[key]) {
diffProps.push({
key: `${path}${key}`,
value: obj2[key],
});
}
}
return diffProps;
};
</script>
<style lang='less' scoped>

View File

@@ -1,5 +1,5 @@
<template>
<div class="pingpu" ref="el"><canvas ref="canvasRef"></canvas></div>
<div class="overall-canvas" ref="el"><canvas ref="canvasRef"></canvas></div>
</template>
<script setup>
@@ -30,9 +30,11 @@
DELETE: "delete",
SORT: "sort",
};
const emit = defineEmits(["change-canvas"]);
const emit = defineEmits(["change-canvas", "init-canvas"]);
const props = defineProps({
list: { type: Array, default: () => [] },
width: { type: Number, required: true },
height: { type: Number, required: true },
});
const el = ref(null);
const canvasRef = ref(null);
@@ -41,7 +43,6 @@
onMounted(async () => {
initCanvas();
await setCanvasData();
let throttleTimeout = null;
let lastRunTime = 0;
let trailingTimeout = null;
@@ -63,6 +64,7 @@
}
});
observer.value.observe(el.value);
emit("init-canvas", props.list);
});
onBeforeUnmount(() => {
observer.value.disconnect();
@@ -100,23 +102,23 @@
const object = e.target;
const action = e.action;
const list = [];
const id = object.id;
const token = object.token;
if (action === "drag" || action === "rotate") {
list.push({
id: id,
token,
action: ACTIONS.UPDATE,
key: KEYS.O_TOP,
value: object.top,
value: (props.height / canvas.height) * object.top,
});
list.push({
id: id,
token,
action: ACTIONS.UPDATE,
key: KEYS.O_LEFT,
value: object.left,
value: (props.width / canvas.width) * object.left,
});
if (action === "rotate") {
list.push({
id: id,
token,
action: ACTIONS.UPDATE,
key: KEYS.O_ANGLE,
value: object.angle,
@@ -124,13 +126,13 @@
}
} else if (action === "scale") {
list.push({
id: id,
token,
action: ACTIONS.UPDATE,
key: KEYS.O_SCALE_X,
value: object.scaleX,
});
list.push({
id: id,
token,
action: ACTIONS.UPDATE,
key: KEYS.O_SCALE_Y,
value: object.scaleY,
@@ -140,15 +142,17 @@
};
// 对象选中
const onObjectSelected = (e) => {
const id = e.selected[0].id;
const list = [
{
id: id,
action: ACTIONS.SELECT,
},
];
const token = e.selected[0].token;
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,17 +185,19 @@
const cheight = canvas.height;
let pattern = await setFill(item);
let rect = new fabric.Rect({
id: item.id,
token: item.token,
width: cwidth,
height: cheight,
fill: pattern,
...item.object,
top: item.object.top / (props.height / canvas.height),
left: item.object.left / (props.width / canvas.width),
onDelete: (v) => onDeleteItem(v),
});
canvas.add(rect);
};
const setFill = async (item) => {
if (!item) return null;
console.log(item.scale);
const cwidth = canvas.width;
const cheight = canvas.height;
let image = await urlToCanvas(item.path);
@@ -225,19 +231,20 @@
return pattern;
};
const updataList = async (list) => {
console.log(list);
const objects = canvas.getObjects();
// list.forEach((item) => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
let object = objects.find((o) => o.id === item.id);
let object = objects.find((o) => o.token === item.token);
if (item.action === ACTIONS.UPDATE) {
if (!object) continue;
let value = item.value;
switch (item.key) {
case KEYS.O_TOP:
object.set("top", value);
object.set("top", value / (props.height / canvas.height));
break;
case KEYS.O_LEFT:
object.set("left", value);
object.set("left", value / (props.width / canvas.width));
break;
case KEYS.O_OPACITY:
object.set("opacity", value);
@@ -268,24 +275,23 @@
case KEYS.FILL_GAPX:
case KEYS.FILL_GAPY:
let pattern = await setFill(
props.list.find((v) => v.id === item.id)
props.list.find((v) => v.token === item.token)
);
object.set("fill", pattern);
break;
}
} else if (item.action === ACTIONS.SELECT) {
canvas.setActiveObject(object);
if (object) canvas.setActiveObject(object);
} else if (item.action === ACTIONS.SORT) {
let ids = item.ids;
let tokens = item.tokens;
canvas.clear();
for (let j = 0; j < ids.length; j++) {
let id = ids[j];
let object = objects.find((o) => o.id === id);
canvas.add(object);
for (let j = 0; j < tokens.length; j++) {
let object = objects.find((o) => o.token === tokens[j]);
if (object) canvas.add(object);
}
canvas.renderAll();
} else if (item.action === ACTIONS.DELETE) {
canvas.remove(object);
if (object) canvas.remove(object);
} else if (item.action === ACTIONS.ADD) {
await addObject(item.data);
}
@@ -298,7 +304,7 @@
</script>
<style lang='less' scoped>
.pingpu {
.overall-canvas {
width: 100%;
height: 100%;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,18 @@
<template>
<div class="test" ref="testRef">
<!-- <div class="canvas-container">
<!-- <div class="test" ref="testRef">
<div class="canvas-container">
<canvas id="canvas"></canvas>
</div> -->
</div>
</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);
@@ -35,53 +39,104 @@
canvas1.height = height;
const ctx1 = canvas1.getContext("2d");
ctx1.drawImage(image, 0, 0, width, height);
const data = ctx1.getImageData(0, 0, width, height);
testRef.value.appendChild(canvas1);
const testData = test(data);
const canvas2 = document.createElement("canvas");
canvas2.width = width;
canvas2.height = height;
const ctx2 = canvas2.getContext("2d");
ctx2.putImageData(testData, 0, 0);
testRef.value.appendChild(canvas2);
const arr = traceImageContour(canvas1);
const str = arr.map((v) => `${v.x} ${v.y}`).join(" L ");
const path = new fabric.Path(`M ${str} z`);
path.set({
fill: "rgba(127, 255, 127, 0.3)",
stroke: "#2AA81B",
strokeWidth: 2,
strokeDashArray: [8, 4],
strokeLineCap: "round",// 折线端点样式
strokeLineJoin: "bevel", // 折线连接样式
strokeUniform: true, // 保持描边宽度不随缩放改变
});
canvas.add(path);
};
});
// 获取图片轮廓点位
function test(data) {
// 找过的点位
const visited = [];
// 轮廓点位
const contours = [];
const { width, height } = data;
function cd(x, y) {
const arr = [
[x, y], // 当前
[x, y - 1], // 上
[x + 1, y], // 右
[x, y + 1], // 下
[x - 1, y], // 左
];
for (let i = 0; i < arr.length; i++) {
let [x1, y1] = arr[i];
if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height) continue;
let key = `${x1},${y1}`;
if (visited.includes(key)) continue;
visited.push(key);
let index = (y1 * width + x1) * 4;
let r = data.data[index];
let g = data.data[index + 1];
let b = data.data[index + 2];
let a = data.data[index + 3];
if ((r || g || b) && a) {
contours.push({ x: x1, y: y1 });
} else {
if (i > 0) cd(x1, y1);
// 边界追踪
function traceImageContour(canvas) {
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
// 查找起始点(第一个不透明像素)
let startX = -1;
let startY = -1;
outer: for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
if (data[index + 3] > 0) {
startX = x;
startY = y;
break outer;
}
}
}
cd(0, 0);
console.log(contours);
return data;
if (startX === -1) return []; // 没有不透明像素
// Moore-Neighbor边界跟踪算法
const contour = [];
const visited = new Set();
const directions = [
[-1, 0],
[-1, -1],
[0, -1],
[1, -1],
[1, 0],
[1, 1],
[0, 1],
[-1, 1],
];
let currentX = startX;
let currentY = startY;
let backtrackDir = 4; // 起始方向:右
do {
const pointKey = `${currentX},${currentY}`;
if (!visited.has(pointKey)) {
contour.push({ x: currentX, y: currentY });
visited.add(pointKey);
}
// 从右方向开始顺时针查找
let found = false;
for (let i = 0; i < 8; i++) {
const dir = (backtrackDir + i) % 8;
const dx = directions[dir][0];
const dy = directions[dir][1];
const checkX = currentX + dx;
const checkY = currentY + dy;
if (
checkX >= 0 &&
checkX < width &&
checkY >= 0 &&
checkY < height
) {
const index = (checkY * width + checkX) * 4;
if (data[index + 3] > 0) {
currentX = checkX;
currentY = checkY;
backtrackDir = (dir + 5) % 8; // 下一个开始查找的方向
found = true;
break;
}
}
}
if (!found) break;
} while (
!(currentX === startX && currentY === startY) &&
visited.size < width * height
);
return contour;
}
</script>

View File

@@ -50,7 +50,7 @@
</div>
</div>
<div class="item detailLeft" :class="{isEditPattern:isEditPattern.value}">
<detailLeft v-if="currentDetailType" ref="detailLeft"></detailLeft>
<detailLeft v-if="currentDetailType" ref="detailLeft" :detailLeftColorKey="detailLeftColorKey"></detailLeft>
<!-- <detailLeft v-if="selectDetail && selectDetail.id && currentDetailType"></detailLeft> -->
<div class="btn" style="margin: 0;" v-show="currentDetailType == 'color'">
<div class="gallery_btn" @click="previwe">{{$t('DesignPrintOperation.Preview')}}</div>
@@ -61,13 +61,10 @@
<model
ref="model"
:key="positionKey"
@addDetail="addDetail"
@canvasReload="canvasReload"
@detailEdit="detailEdit"
@addSketch="()=>isEditPattern.value = ''"
@revocation="revocation"
@oppositeRevocation="oppositeRevocation"
@modelOnLoad="modelOnLoad"
@sketchSysToLibrary="sketchSysToLibrary"
></model>
<div class="btn">
@@ -88,7 +85,7 @@
" alt="">
</div>
<div class="contentRight" v-else-if="currentDetailType && !isEditPattern.value">
<detailRight ref="detailRight"></detailRight>
<detailRight ref="detailRight" :sketchSize="sketchSize"></detailRight>
<div class="btn"
v-show="
currentDetailType !== 'color' &&
@@ -103,15 +100,14 @@
<div class="gallery_btn" @click="previwe">{{$t('DesignPrintOperation.Preview')}}</div>
</div>
</div>
<div class="contentRight" v-if="selectDetail && selectDetail.id && currentDetailType && isEditPattern.value">
<canvasBox ref="canvasBox" :key="canvasKey || isEditPattern.value" :isEditPattern="isEditPattern.value"></canvasBox>
<div class="contentRight canvas" v-if="selectDetail && selectDetail.id && currentDetailType" :class="{'active': isEditPattern.value}">
<canvasBox ref="canvasBox" :key="canvasKey" :sketchSize="sketchSize" @setSloganData="setSloganData" :isEditPattern="isEditPattern.value" :updateOtherLayers="updateOtherLayers"></canvasBox>
</div>
<!-- 画布 -->
<!-- <div class="content" v-else-if="selectDetail && selectDetail.id">
</div> -->
</div>
</div>
<addDetails ref="addDetails" @setSloganData="setSloganData"></addDetails>
</a-modal>
<div class="mark_loading" v-show="loadingShow">
<a-spin size="large" />
@@ -130,14 +126,13 @@ import canvasBox from './canvas/index.vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { Https } from "@/tool/https";
import { Modal,message } from 'ant-design-vue';
import {getUploadUrl,isMoible,setGradual} from '@/tool/util'
import {getUploadUrl,segmentImage,setGradual,rgbToHsv,rgbaToHex} from '@/tool/util'
import { useStore } from "vuex";
import { openGuide,driverObj__ } from "@/tool/guide";
import { useI18n } from 'vue-i18n'
import addDetails from '@/component/Detail/addDetails.vue'
export default defineComponent({
components:{
detailLeft,model,detailRight,canvasBox,addDetails
detailLeft,model,detailRight,canvasBox
},
emits:['destroy'],
setup(props,{emit}) {
@@ -148,7 +143,6 @@ export default defineComponent({
canvasBox,
detailRight,
detailLeft:null as any,
addDetails:null as any,
})
const userDetail = computed(()=>{
return store.state.UserHabit.userDetail
@@ -157,10 +151,12 @@ export default defineComponent({
selectObject:computed(()=>store.state.Workspace.probjects) as any,//选择的项目
designDetail:computed(()=>store.state.DesignDetail.designDetail),
currentDetailType:computed(()=>store.state.DesignDetail.currentDetailType),
frontBack:computed(()=>store.state.DesignDetail.frontBack),
selectDetail:computed(()=>store.state.DesignDetail.selectDetail),
designDetailShow:false,
loadingShow:false,
oppositeRevocationShow:-1,
imgDomIndex:-1,
revocationShow:-1,
isEditPattern:{
value:'' as any,
@@ -173,10 +169,30 @@ export default defineComponent({
fun:null,
},
positionKey:0,
isUndividedLayerWithSinglePrint:false,
detailLeftColorKey:0,
sketchSize:{
width:0,
height:0,
}
})
const getSketchSize:any = async ()=>{
let img = new Image();
img.src = detailData.selectDetail.path;
await new Promise((resolve, reject) => {
img.onload = () => {
detailData.sketchSize.width = img.width
detailData.sketchSize.height = img.height
resolve([img.width, img.height]);
}
img.onerror = reject;
});
}
watch(()=>detailData.selectDetail,async (newValue,oldValue)=>{
detailData.imgDomIndex = detailData.frontBack.front.findIndex((item:any)=>item.id == newValue.id)
if(newValue)await getSketchSize()
detailData.canvasKey += 1
// privewDetail(oldValue)
},{immediate: true})
provide('getCanvasIfEdit',detailData.getCanvasIfEdit)
provide('singleOveral',detailData.singleOveral)
provide('isEditPattern',detailData.isEditPattern)
@@ -195,6 +211,11 @@ export default defineComponent({
detailData.loadingShow = true
Https.axiosGet(url).then(
async (rv: any) => {
//清除画布JSON数据
sessionStorage.removeItem('canvasList');
sessionStorage.removeItem('revocation');
sessionStorage.removeItem('oppositeRevocation');
sessionStorage.setItem('key', 'value');
store.commit('DesignDetail/setDesignDetail',rv)
rv.clothes.forEach((item:any)=>{
let a
@@ -228,25 +249,17 @@ export default defineComponent({
element.designType = 'Library'
}
});
if(!item.layersObject[0].undividedLayerWithSinglePrint){
detailData.isUndividedLayerWithSinglePrint = true
}
})
detailData.singleOveral.value = rv.singleOverall
detailData.designDetailShow = true
// this.deleteShow = false
initialize()
setRevocation()
if(rv.singleOverall == "single"){
store.commit('DesignDetail/setDesignColthes',rv.clothes[0].id)
modelOnLoad()
}
if(detailData.isUndividedLayerWithSinglePrint){
}else{
detailData.loadingShow = false
}
detailData.loadingShow = false
resolve(rv)
}
).catch(rv=>{
@@ -256,12 +269,6 @@ export default defineComponent({
})
}
const initialize = ()=>{//design后初始化
sessionStorage.removeItem('oppositeRevocation')
sessionStorage.removeItem('revocation')
}
//撤回
const setRevocation = ()=>{//设置撤销
let itemDetail = JSON.parse(JSON.stringify(detailData.designDetail))
@@ -320,13 +327,15 @@ export default defineComponent({
const setCurrentDetail = (str:string)=>{
store.commit('DesignDetail/setCurrentDetailType',str)
}
const setClothes = async (list:any)=>{
const setClothes = async (list:any,str:string)=>{
let clothesList:any = []
await nextTick()
if(detailData.isEditPattern.value == 'editSketch')await detailDom.canvasBox.submitBase64Data().then((rv)=>{
detailData.selectDetail.sketchString = rv
})
for(let i = 0;i<list.length;i++){
detailData.selectDetail
let {scale,offset,priority,maskUrl,maskMinioUrl} = await (detailDom.model as any).getSubmitData(list[i],detailData.isUndividedLayerWithSinglePrint)
if(detailDom.canvasBox?.privewDetail)await (detailDom.canvasBox as any).privewDetail()
let {scale,offset,priority,transpose,rotate,maskUrl,maskMinioUrl} = await (detailDom.model as any).getSubmitData(list[i])
if(detailDom.detailRight?.privewDetail)await (detailDom.detailRight as any).privewDetail()
let gradient = null
let newData = list[i]?.newDetail?.[detailData.currentDetailType]
@@ -349,6 +358,32 @@ export default defineComponent({
detailData.selectDetail.maskUrl = ''
detailData.selectDetail.maskMinioUrl = ''
}
function isNetworkPath(str) {
if (typeof str !== 'string' || str.trim() === '') {
return false;
}
const urlPatterns = [
/^https?:\/\//i, // http:// 或 https://
/^ftp:\/\//i, // ftp://
/^ws:\/\//i, // ws://
/^wss:\/\//i, // wss://
/^\/\//, // 协议相对路径 //example.com
/^data:/i, // data:image/png;base64,...这是Base64不是网络路径
];
// 排除data:URL这实际上是Base64
if (str.startsWith('data:')) {
return false;
}
return urlPatterns.some(pattern => pattern.test(str));
}
const printObjectToJSON = (list:any)=>{
if(list?.length > 0){
list.forEach((item:any)=>{
if(item.object)item.object = JSON.stringify(item.object)
})
}
}
let data:any = {
changed:false,
color,
@@ -356,13 +391,15 @@ export default defineComponent({
id:(newData && detailData.currentDetailType == 'sketch' && isCurrent && !detailData.isEditPattern.value)?newData.id:list[i].id,
maskMinioUrl:((newData && detailData.currentDetailType == 'sketch') || list[i].sketchString)?'':list[i]?.maskMinioUrl,
// maskUrl:'',
maskUrl:((newData && detailData.currentDetailType == 'sketch') || list[i].sketchString)?'':list[i]?.maskUrl,
maskUrl:list[i]?.maskUrl || '',
// offset:[
// -233.13985,
// 406.90964
// ],
offset,
partialDesign:list[i].partialDesign || {},
transpose,
rotate,
partialDesign:list[i].partialDesign,
// partialDesign:detailData.isEditPattern.value?list[i].partialDesign:{},
path:(newData && detailData.currentDetailType == 'sketch' && isCurrent && !detailData.isEditPattern.value)?newData.minIOPath:list[i].minIOPath,
printObject:(newData && detailData.currentDetailType == 'print' && isCurrent && !detailData.isEditPattern.value)?{prints:newData}:list[i].printObject?list[i].printObject:{prints:[]},
@@ -377,6 +414,8 @@ export default defineComponent({
trims:(newData && detailData.currentDetailType == 'element' && isCurrent && !detailData.isEditPattern.value)?{prints:newData}:list[i].trims?.prints?list[i].trims:{prints:[]},
accessory:(newData && detailData.currentDetailType == 'accessory' && isCurrent && !detailData.isEditPattern.value)?{prints:newData}:list[i].trims?.prints?list[i].trims:{prints:[]},
}
data.printObject.prints = printObjectToJSON(data.printObject.prints)
data.trims.prints = printObjectToJSON(data.trims.prints)
if((detailData.isEditPattern.value && list[i].color?.gradient) || (!detailData.isEditPattern.value && (list[i].newDetail?.color?.gradient || list[i].color?.gradient))){
gradient = list[i].newDetail?.color?.gradient || list[i].color.gradient
gradient.colorImg = await setGradual(gradient,320,700)
@@ -391,10 +430,10 @@ export default defineComponent({
let workspace = store.state.Workspace.probjects
if(!detailData?.selectDetail?.path && !detailData?.selectDetail?.newDetail?.sketch?.minIOPath)return
let clothes:any
if(detailData.currentDetailType == 'models' || detailData.isUndividedLayerWithSinglePrint){
clothes = await setClothes(detailData.designDetail.clothes)
if(detailData.currentDetailType == 'models'){
clothes = await setClothes(detailData.designDetail.clothes,str)
}else{
clothes = await setClothes([detailData.selectDetail])
clothes = await setClothes([detailData.selectDetail],str)
}
let data = {
designItemId:detailData.designDetail.designItemId,
@@ -405,6 +444,7 @@ export default defineComponent({
sketchString:'',
modelId:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.id:detailData.designDetail.oldModel?detailData.designDetail.oldModel.id:'',
modelType:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.type:detailData.designDetail.oldModel?detailData.designDetail.oldModel.type:'',
designType:detailData.selectDetail.id?'merage':'default',
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
processId:userDetail.value.userId,
probjectId:store.state.Workspace.probjects.id,
@@ -417,10 +457,10 @@ export default defineComponent({
fun:setRevocation
}
if(detailData?.designDetail?.newModel)detailData.designDetail.oldModel = JSON.parse(JSON.stringify(detailData.designDetail.newModel))
delete detailData.designDetail.newModel
// delete detailData.designDetail.newModel
detailData.selectDetail.sketchString = null
store.commit('DesignDetail/setPraeview',value)
detailData.loadingShow = false
detailData.isUndividedLayerWithSinglePrint = false
canvasReload()
// setRevocation()
}).catch(res=>{
@@ -429,7 +469,7 @@ export default defineComponent({
}
const submit = async ()=>{
let workspace = store.state.Workspace.probjects
let clothes:any = await setClothes(detailData.designDetail.clothes)
let clothes:any = await setClothes(detailData.designDetail.clothes,'sub')
let data = {
designItemId:detailData.designDetail.designItemId,
designSingleItemDTOList:clothes,
@@ -439,13 +479,21 @@ export default defineComponent({
sketchString:'',
modelId:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.id:detailData.designDetail.oldModel?detailData.designDetail.oldModel.id:'',
modelType:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.type:detailData.designDetail.oldModel?detailData.designDetail.oldModel.type:'',
designType:detailData.selectDetail.id?'merage':'default',
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
processId:userDetail.value.userId,
probjectId:store.state.Workspace.probjects.id,
}
detailData.loadingShow = true
Https.axiosPost(Https.httpUrls.designSingle, data).then((rv)=>{
Https.axiosPost(Https.httpUrls.designSingle, data).then(async (rv)=>{
saveCanvasJSONToSession()
// store.commit('DesignDetail/setPraeview',rv)
const sessionCanvasList = sessionStorage.getItem('canvasList');
const canvasList = sessionCanvasList ? JSON.parse(sessionCanvasList) : []
for (let i = 0; i < canvasList.length; i++) {
const index = detailData.designDetail.clothes.findIndex(item => item.id === canvasList[i].id);
if(index>-1)await detailDom.canvasBox.saveCanvas(canvasList[i])
}
let designCollectionList = store.state.HomeStoreModule.designCollectionList
let likeDesignCollectionList = store.state.HomeStoreModule.likeDesignCollectionList
designCollectionList.forEach((item:any) => {
@@ -466,45 +514,191 @@ export default defineComponent({
detailData.loadingShow = false
});
}
const previwe = ()=>{
let data = getSubmitData('preview')
store.dispatch('DesignDetail/setSubmit',data)
}
const modelOnLoad = ()=>{
if(!detailData.isUndividedLayerWithSinglePrint)return
setTimeout(()=>{
let data = getSubmitData('sub')
const previwe = async ()=>{
if((detailData.currentDetailType == 'sketch' && !detailData.isEditPattern.value) || detailData.isEditPattern.value == 'editSketch'){
let data = getSubmitData('preview')
store.dispatch('DesignDetail/setSubmit',data)
}else{
//走画布合成图片并且直接分割
if(detailData.isEditPattern.value !== 'canvasEditor'){
if(detailDom.detailRight?.privewDetail)await (detailDom.detailRight as any).privewDetail()
let otherData = await updateOtherLayers('single')
await detailDom.canvasBox.updateOtherLayers(otherData)
}
await detailDom.canvasBox.privewDetail()
let img = new Image()
img.onload = ()=>{
let partialDesign = detailData.selectDetail.partialDesign.partialDesignBase64 || detailData.selectDetail.partialDesign.partialDesignPath
let size = {
width:img.width,
height:img.height,
}
segmentImage(detailData.selectDetail.maskUrl,partialDesign,size).then(async (rv)=>{
let front = detailData.frontBack.front[detailData.imgDomIndex]
let back = detailData.frontBack.back[detailData.imgDomIndex]
if(!front?.oldImageUrl)front.oldImageUrl = front.imageUrl
if(!front?.oldMaskUrl)front.oldMaskUrl = front.maskUrl
if(!back?.oldImageUrl)back.oldImageUrl = back.imageUrl
if(!front?.oldMaskUrl)store.commit('DesignDetail/updataDetailItem',{maskUrl:front.oldMaskUrl})
front.imageUrl = rv.targetFrontUrl
back.imageUrl = rv.targetBackUrl
store.commit('DesignDetail/canvasPreviewUpdata',{type:detailData.isEditPattern.value?'all':detailData.currentDetailType,callBack:setRevocation})
})
}
img.src = detailData.selectDetail.path
saveCanvasJSONToSession()
}
}
const saveCanvasJSONToSession = ()=>{
let canvasJSON = detailDom.canvasBox.getCanvasJSON()
const sessionCanvasList = sessionStorage.getItem('canvasList');
const list = sessionCanvasList ? JSON.parse(sessionCanvasList) : []
let index = -1
list.forEach((item:any,i:number)=>{
if(item.id == detailData.selectDetail.id){
index = i
}
})
if(index < 0){
list.push({id:detailData.selectDetail.id,canvasJSON:canvasJSON})
}else{
list[index].canvasJSON = canvasJSON
}
sessionStorage.setItem('canvasList', JSON.stringify(list));
}
const detailEdit = async (str:any)=>{
if(str){
if(detailData.isEditPattern.value && detailData.isEditPattern.value == str){
await detailDom.canvasBox.saveCanvas()
// await detailDom.canvasBox.saveCanvas()
await (detailDom.canvasBox as any).privewDetail()
if(detailData.isEditPattern.value == 'canvasEditor')await uploadElement()
detailData.isEditPattern.value = ''
}else{
if(detailData.isEditPattern.value){
detailDom.canvasBox.editFront(str)
}
// if(detailData.isEditPattern.value && (str == 'canvasEditor' || str == 'redGreenExample')){
// detailDom.canvasBox.editFront(str)
// }
detailDom.canvasBox.editFront(str)
let otherData = await updateOtherLayers('single')
await detailDom.canvasBox.updateOtherLayers(otherData)
detailData.isEditPattern.value = str
}
}else{
detailData.isEditPattern.value = ''
}
}
const getColorName = (color:any)=>{
let rgb:any = [color.r, color.g, color.b];
let hsv = rgbToHsv(rgb);
let data = [{
h: hsv[0],
s: hsv[1],
v: hsv[2],
}]
return new Promise((resolve, reject) => {
Https.axiosPost(Https.httpUrls.getRgbByHsvBatch, data)
.then((rv) => {
if (rv) {
resolve(rv[0])
}
})
.catch((res) => {
resolve({name:'--',tcx:'--'})
});
})
}
const updateOtherLayers = async (str:any='all')=>{//更新到画布图层
let otherData:any = {}
if(detailDom.detailRight?.privewDetail)await (detailDom.detailRight as any).privewDetail()
if(str == 'all'){
otherData = {
color: detailData.selectDetail.newDetail?.color.r?detailData.selectDetail.newDetail?.color:detailData.selectDetail.color,
printObject: detailData.selectDetail.newDetail?.print?.length>0?{prints:detailData.selectDetail.newDetail?.print}:detailData.selectDetail.printObject || null,
trims: detailData.selectDetail.newDetail?.element?.length>0?detailData.selectDetail.newDetail?.element:detailData.selectDetail.trims || null,
}
}else if(str == 'single'){
otherData = {
color: detailData.selectDetail.color,
printObject: detailData.selectDetail.printObject || null,
trims: detailData.selectDetail.trims || null,
}
if(detailData.currentDetailType == 'color'){
let color = detailData.selectDetail.newDetail?.color
// let colorData:any = await getColorName(color?.rgba)
if(detailData.selectDetail.newDetail?.color){
if(color.r){
color.rgba = {r:color.r,g:color.g,b:color.b,a:color.a}
}else{
color.rbga = {}
}
otherData.color = color
}
}
if(detailData.currentDetailType == 'print'){
otherData.printObject = {prints:detailData.selectDetail.newDetail?.print}
}
if(detailData.currentDetailType == 'element'){
otherData.trims = detailData.selectDetail.newDetail?.element
}
}
console.log(JSON.parse(JSON.stringify(otherData)),'=======',JSON.parse(JSON.stringify(detailData.selectDetail)))
return otherData
}
const uploadElement = async ()=>{//取出画布数据更新到detail
if(detailData.isEditPattern.value == 'canvasEditor'){
// await detailDom.canvasBox.saveCanvas()
const allInfo = await (detailDom.canvasBox as any).getCanvasElement()
console.log(allInfo,'allInfo')
if(allInfo.trims?.length > 0){
// detailData.selectDetail.trims.prints = allInfo.trims
let value = {
data:allInfo.trims,
str:'element'
}
store.commit('DesignDetail/setNewDetail',value)
}
if(allInfo.prints?.length > 0){
// detailData.selectDetail.printObject.prints = allInfo.prints
let value = {
data:allInfo.prints,
str:'print'
}
store.commit('DesignDetail/setNewDetail',value)
}
if(allInfo.color?.color?.rgba){
let canvasColor = allInfo.color.color
let colorData:any = await getColorName(allInfo.color.color?.rgba)
let value:any = {
data:{
hsv:{
h:colorData.h,
s:colorData.s,
v:colorData.v,
},
name:colorData.name,
tcx:colorData.tcx,
rgba:canvasColor.rgba,
hex:rgbaToHex([canvasColor.rgba.r,canvasColor.rgba.g,canvasColor.rgba.b]),
},
str:'color'
}
if(canvasColor.gradient){
value.data.gradient = canvasColor.gradient
}
store.commit('DesignDetail/setNewDetail',value)
if(allInfo.color.color.gradient)detailData.selectDetail.color.gradient = allInfo.color.color.gradient
if(detailData.currentDetailType == 'color'){
detailData.detailLeftColorKey++
}
}
}
}
const canvasReload = async ()=>{
if(detailData.isEditPattern.value){
await detailDom.canvasBox.saveCanvas()
}
detailData.canvasKey += 1
}
let time = null as any
const handleResize = ()=>{
clearTimeout(time)
time = setTimeout(()=>{
store.commit('DesignDetail/setDesignDetail',detailData.designDetail)
},1000)
}
const sketchSysToLibrary = ()=>{//系统sketch添加到library更新library
coverRevocation()
detailDom.detailLeft.sketchSysToLibrary()
@@ -516,11 +710,6 @@ export default defineComponent({
sessionStorage.setItem('revocation', JSON.stringify(revocation));
sessionStorage.setItem('oppositeRevocation',JSON.stringify([]));
}
const addDetail = () =>{
let addDetails:any = detailDom.addDetails
addDetails.init(detailData.selectDetail,'')
}
const setSloganData = (data:any)=>{
detailData.selectDetail.sketchString = data
if(detailData.currentDetailType == 'sketch' && detailData.selectDetail?.newDetail?.sketch){
@@ -528,14 +717,12 @@ export default defineComponent({
}
}
onMounted(()=>{
window.addEventListener('resize', handleResize);
})
onBeforeUnmount(()=>{
sessionStorage.removeItem('oppositeRevocation')
sessionStorage.removeItem('revocation')
store.commit('DesignDetail/clearDesignDetail')
window.removeEventListener('resize', handleResize);
})
return{
@@ -551,10 +738,9 @@ export default defineComponent({
oppositeRevocation,
detailEdit,
canvasReload,
modelOnLoad,
sketchSysToLibrary,
addDetail,
setSloganData,
updateOtherLayers,//更新到画布图层 再canvasInit中执行
}
},
@@ -680,6 +866,24 @@ export default defineComponent({
flex: 1;
flex-direction: column;
overflow: hidden;
&.canvas{
opacity: 0;
position: absolute;
flex: auto;
width: 35vw;
height: 80vh;
pointer-events: none;
transform: translate(100vw,100vh);
}
&.active{
position: relative;
opacity: 1;
flex: 1;
width: auto;
height: auto;
pointer-events: auto;
transform: translate(0,0);
}
}
}
.btn{

View File

@@ -1,98 +0,0 @@
<template>
<div class="addDetailsModal generalModel" ref="addDetailsModal"></div>
<a-modal
class="addDetails_modal generalModel fullScreen"
v-model:visible="addDetails"
:footer="null"
:get-container="() => $refs.addDetailsModal"
width="100%"
height="100%"
:maskClosable="false"
:centered="true"
:closable="false"
:destroyOnClose="true"
wrapClassName="#app"
:keyboard="false"
:mask="false"
>
<div class="generalModel_btn">
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
<svg width="100%" height="100%" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="23" cy="23" r="23" fill="white" fill-opacity="0.3"/>
<rect x="32.5063" y="12" width="3" height="29" rx="1.5" transform="rotate(45 32.5063 12)" fill="#000"/>
<rect x="34.6274" y="32.5059" width="3" height="29" rx="1.5" transform="rotate(135 34.6274 32.5059)" fill="#000"/>
</svg>
</div>
</div>
<div class="addDetails_center">
<generalMiniCanvas :imgUrl="imgUrl" @submitBase64Data="submitBase64Data"></generalMiniCanvas>
</div>
<div></div>
</a-modal>
</template>
<script>
import { defineComponent, ref, reactive, watch, onMounted, nextTick, toRefs } from "vue";
import generalMiniCanvas from "../modules/generalMiniCanvas.vue";
export default defineComponent({
components: {
generalMiniCanvas,
},
emits: ['setSloganData'],
setup(props,{emit}) {
let addDetail = reactive({
imgUrl:''
});
let addDetails = ref(false);
let init = (data,index)=>{
addDetails.value = true
addDetail.imgUrl = data.sketchString || data.path
}
let submitBase64Data = (data)=>{
emit('setSloganData',data)
cancelDsign()
}
let cancelDsign = ()=>{
addDetails.value = false
}
return {
...toRefs(addDetail),
addDetails,
init,
submitBase64Data,
cancelDsign,
};
},
data() {
return {
};
},
mounted() {},
methods: {
},
});
</script>
<style lang="less" scoped>
.addDetailsModal{
width: 0;
height: 0;
}
.addDetails_modal {
.closeIcon {
z-index: 2;
}
.addDetails_center{
position: relative;
height: 100%;
display: flex;
flex-direction: column;
margin: 0 auto;
}
.exportCanvasBox_submit{
// margin-top: 2.4rem;
// text-align: center;
}
}
</style>

View File

@@ -5,17 +5,16 @@
<div class="canvasContent" ref="canvasContent">
<div class="content-bottom" ref="canvasContent">
<div class="contet">
<!-- :clothingImageUrl="selectDetail?.undividedLayerWithSinglePrint || selectDetail.undividedLayer || selectDetail.path" -->
<div class="canvas" v-if="currentView === 'canvasEditor'" @click.stop>
<div class="canvas" :class="{'active': currentView === 'canvasEditor'}"@click.stop>
<editCanvas v-if="canvasLoad" :config="canvasConfig"
@canvasInit="canvasInit"
@changeCanvas="changeCanvas"
@canvasInit="editSketchCanvasInit"
is-edit
:clothingMinIOPath="selectDetail.minIOPath"
:clothingImageUrl="selectDetail.path"
:clothingImageUrl2="selectDetail.undividedLayer"
:clothingImageUrl2="selectDetail.layersObject[0].maskUrl"
showFixedLayer
:canvasJSON="canvasJSON"
:otherData="otherData"
@canvasLoadJsonSuccess="canvasLoadJsonSuccess"
:clothing-image-opts="{
imageMode:'contains',
}"
@@ -25,14 +24,8 @@
<!-- <canvasContent ref="canvasContent"></canvasContent> -->
</div>
<div class="editFrontBack" v-if="currentView === 'redGreenExample'" @click.stop>
<!-- <editFrontBack
:patchData="frontBack"
:imgDomIndex="imgDomIndex"
ref="editFrontBack">
</editFrontBack> -->
<editCanvas v-if="canvasLoad" :config="canvasConfig"
@canvasInit="canvasInit"
@canvasInit="editFrontBackCanvasInit"
:enabledRedGreenMode="true"
:clothingImageUrl="selectDetail.path"
:redGreenImageUrl="frontBack.front[imgDomIndex].maskUrl"
@@ -45,6 +38,9 @@
ref="editCanvasBackFront">
</editCanvas>
</div>
<div class="editSketch" v-if="currentView === 'editSketch'" @click.stop>
<generalMiniCanvas ref="generalMiniCanvas" :btnShow="false" :imgUrl="selectDetail.sketchString || selectDetail.path"></generalMiniCanvas>
</div>
</div>
<!-- <div class="Finish">
@@ -69,16 +65,25 @@ import { useI18n } from 'vue-i18n'
import editCanvas from "@/component/Canvas/CanvasEditor/index.vue";
import { formatTime,segmentImage,getMinioUrl } from "@/tool/util";
import { useRouter, useRoute } from 'vue-router'
import generalMiniCanvas from "../../modules/generalMiniCanvas.vue";
export default defineComponent({
components:{
editCanvas
editCanvas,generalMiniCanvas
},
props:{
isEditPattern:{
type:String,
default:''
}
},
updateOtherLayers:{
type:Function,
default:()=>{}
},
sketchSize:{
type:Object,
default:()=>{}
},
},
setup(props,{emit}) {
const store = useStore();
@@ -90,6 +95,7 @@ export default defineComponent({
editCanvas:null as any,
editCanvasBackFront:null as any,
canvasContent:null as any,
generalMiniCanvas:null as any,
})
const userDetail = computed(()=>{
return store.state.UserHabit.userDetail
@@ -130,14 +136,14 @@ export default defineComponent({
if(detailData.currentView === 'canvasEditor'){
sessionStorage.setItem('sketchEdit',detailDom.editCanvas.getJSON())
canvasJSON = sessionStorage.getItem('frontBackEdit');
}else{
}else if(detailData.currentView === 'redGreenExample'){
sessionStorage.setItem('frontBackEdit',detailDom.editCanvasBackFront.getJSON())
canvasJSON = sessionStorage.getItem('sketchEdit');
}
detailData.canvasLoad = false
// detailData.canvasLoad = false
detailData.currentView = str
if(canvasJSON){
detailData.canvasLoad = true
// detailData.canvasLoad = true
nextTick(()=>{
if(detailData.currentView === 'redGreenExample'){
detailDom.editCanvas.loadJSON(canvasJSON)
@@ -148,29 +154,51 @@ export default defineComponent({
}else{
if(detailData.currentView === 'redGreenExample'){
nextTick(()=>{
setCanvas(detailData.selectDetail?.undividedLayerWithSinglePrint || detailData.selectDetail.undividedLayer || detailData.selectDetail.path).then(()=>{
detailData.canvasLoad = true
setCanvas(detailData.selectDetail.path).then(()=>{
// detailData.canvasLoad = true
})
})
}else{
nextTick(()=>{
setCanvas(detailData.frontBack.front[detailData.imgDomIndex].maskUrl).then(()=>{
detailData.canvasLoad = true
// detailData.canvasLoad = true
})
})
}
}
}
const privewDetail = async (oldSelectDetail = detailData.selectDetail)=>{
const updateOtherLayers = (obj:any)=>{
if(!detailDom.editCanvas)return
return new Promise((res,reject)=>{
detailDom.editCanvas.exportImage({isContainBg:false,isContainFixed:false}).then((rv)=>{
if(oldSelectDetail?.partialDesign)oldSelectDetail.partialDesign.partialDesignBase64 = rv
res('')
})
return new Promise(async (res,reject)=>{
await detailDom?.editCanvas.updateOtherLayers(obj)
res('')
})
}
const privewDetail = async (oldSelectDetail = detailData.selectDetail)=>{
// if(!detailDom.editCanvas)return
return new Promise(async (res,reject)=>{
await detailDom.editCanvas.exportImage({
isContainFixed:true,
width:props.sketchSize.width,
height:props.sketchSize.height,
}).then((rv)=>{
if(oldSelectDetail?.partialDesign)oldSelectDetail.partialDesign.partialDesignBase64 = rv
})
//包含平铺图层 single+overall模式的图
// await detailDom.editCanvas.exportImage({isContainFixed:true,isPrintTrimsNoRepeat:true,isPrintTrimsRepeat:true,isContainNormalLayer:false}).then((rv)=>{
// oldSelectDetail.undividedLayerWithSinglePrint = rv
// })
//不包含平铺图层overall模式的图
// await detailDom.editCanvas.exportImage({isContainFixed:true,isPrintTrimsNoRepeat:false,isPrintTrimsRepeat:true,isContainNormalLayer:false}).then((rv)=>{
// oldSelectDetail.undividedLayer = rv
// })
await setUndivideLayer()
res('')
})
}
const getCanvasElement = ()=>{
if(!detailDom?.editCanvas)return ''
return detailDom?.editCanvas.exportExtraInfo();
}
const setFrontBackColor = (data:any)=>{
detailDom.editFrontBack.setBackground(data)
@@ -180,11 +208,14 @@ export default defineComponent({
let img = new Image()
img.onload = ()=>{
let wH = [1,1]
let domHeight = detailDom.canvasContent.offsetHeight - 200
let domHeight = detailDom.canvasContent?.offsetHeight - 200
let imgHeight = img.height
wH = [1,domHeight/imgHeight]
detailData.canvasConfig.width = img.width * wH[1]
detailData.canvasConfig.height = domHeight
// detailData.canvasConfig.width = img.width * wH[1]
// detailData.canvasConfig.height = domHeight
console.log(detailData.canvasConfig,'===============')
detailData.canvasConfig.width = img.width
detailData.canvasConfig.height = img.height
res('')
}
@@ -234,7 +265,7 @@ export default defineComponent({
const frontBackChange = (value:any)=>{
let full = detailData.frontBack.front[detailData.imgDomIndex]?.undividedLayerWithSinglePrint || detailData.frontBack.front[detailData.imgDomIndex].undividedLayer || detailData.selectDetail.path
let full = detailData.selectDetail.partialDesign.partialDesignBase64 || detailData.selectDetail.path
let size = {
...detailData.canvasConfig,
}
@@ -255,28 +286,34 @@ export default defineComponent({
})
}
const canvasInit = (value:any)=>{
const editSketchCanvasInit = (value:any)=>{
detailData.canvasInstance = value
detailData.getCanvasIfEdit.fun = getCanvasLength
detailData.isShowMark = false
console.log('初始化完成')
}
const editFrontBackCanvasInit = (value:any)=>{
detailData.canvasInstance = value
detailData.getCanvasIfEdit.fun = getCanvasLength
detailData.isShowMark = false
console.log('初始化完成')
}
const getCanvasLength = ()=>{
return detailData.canvasInstance?.commandManager?.undoStack?.length
}
const saveCanvas = async (type:string = '')=>{
if(type !== 'auto'){
detailData.isShowMark = true
}
const index = detailData.designDetail.clothes.findIndex(item => item.id === detailData.selectDetail.id);
const getCanvasJSON = ()=>{
if(!detailDom?.editCanvas)return ''
return detailDom?.editCanvas?.getJSON()
}
const saveCanvas = async (canvasData:any)=>{
const index = detailData.designDetail.clothes.findIndex(item => item.id === canvasData.id);
await new Promise<void>((resolve, reject) => {
if(!detailDom?.editCanvas)return resolve()
let canvasJSON = detailDom?.editCanvas?.getJSON()
let canvasData = JSON.parse(canvasJSON)
if(!canvasData)return resolve()
canvasData.canvas.objects.forEach((objectsItem:any) => {
if(objectsItem.type == 'image')objectsItem.minioUrl = getMinioUrl(objectsItem.src)
});
let blob = new Blob([JSON.stringify(canvasData)], { type: "application/json" });
let canvasJSON = JSON.parse(canvasData.canvasJSON)
if(!canvasJSON)return resolve()
// canvasData.canvas.objects.forEach((objectsItem:any) => {
// if(objectsItem.type == 'image')objectsItem.minioUrl = getMinioUrl(objectsItem.src)
// });
let blob = new Blob([JSON.stringify(canvasJSON)], { type: "application/json" });
let formData = new FormData();
formData.append("file", blob, "data.json");
formData.append("designItemDetailId", detailData.selectDetail.id);
@@ -297,12 +334,52 @@ export default defineComponent({
});
})
}
let time = null as any
const changeCanvas = ()=>{
clearTimeout(time)
time = setTimeout(()=>{
saveCanvas('auto')
},3000)
// let time = null as any
// const changeCanvas = ()=>{
// clearTimeout(time)
// time = setTimeout(()=>{
// saveCanvas('auto')
// },3000)
// }
const canvasLoadJsonSuccess = async ()=>{
let otherData = await props.updateOtherLayers()
await updateOtherLayers(otherData)
await setUndivideLayer()
}
const setUndivideLayer = async ()=>{
await new Promise<void>(async (resolve, reject) => {
// if(!detailData.selectDetail.undividedLayerColor){
await detailDom.editCanvas.exportImage({
isContainFixed:true,
isPrintTrimsRepeat:false,
isPrintTrimsNoRepeat:false,
isContainNormalLayer:false,
width:props.sketchSize.width,
height:props.sketchSize.height}).then((rv)=>{
detailData.selectDetail.undividedLayerColor = rv
})
// }
// if(!detailData.selectDetail.undividedLayer){
await detailDom.editCanvas.exportImage({
isContainFixed:true,
isPrintTrimsNoRepeat:false,
isPrintTrimsRepeat:true,
isContainNormalLayer:false,
width:props.sketchSize.width,
height:props.sketchSize.height,
}).then((rv)=>{
console.log(rv,'======')
detailData.selectDetail.undividedLayer = rv
})
// }
resolve()
})
}
const submitBase64Data = ()=>{
return detailDom.generalMiniCanvas.submitBase64Data()
}
onBeforeUnmount(()=>{
@@ -322,6 +399,12 @@ export default defineComponent({
onMounted(()=>{
nextTick(async ()=>{
// detailData.currentView = 'canvasEditor'
const sessionCanvasList = sessionStorage.getItem('canvasList');
const canvasList = sessionCanvasList ? JSON.parse(sessionCanvasList) : []
let canvasIndex = canvasList.findIndex(item => item.id === detailData.selectDetail.id);
if(canvasIndex>1){
detailData.canvasJSON = canvasList[canvasIndex].canvasJSON
}
if(detailData.selectDetail.canvasId){
detailData.isShowMark = true
await new Promise((resolve, reject) => {
@@ -339,7 +422,7 @@ export default defineComponent({
});
})
}
setCanvas(detailData.selectDetail?.undividedLayerWithSinglePrint || detailData.selectDetail.undividedLayer || detailData.selectDetail.path).then(()=>{
setCanvas(detailData.selectDetail.path).then(()=>{
detailData.canvasLoad = true
})
})
@@ -351,9 +434,14 @@ export default defineComponent({
privewDetail,
setFrontBackColor,
frontBackChange,
canvasInit,
editSketchCanvasInit,
editFrontBackCanvasInit,
saveCanvas,
changeCanvas,
getCanvasElement,
updateOtherLayers,
canvasLoadJsonSuccess,
submitBase64Data,
getCanvasJSON,
}
},
provide() {
@@ -434,7 +522,15 @@ export default defineComponent({
flex: 1;
overflow: hidden;
position: relative;
.canvas,.editFrontBack{
.canvas{
opacity: 0;
position: absolute;
&.active{
opacity: 1;
position: relative;
}
}
.canvas,.editFrontBack,.editSketch{
width: 100%;
height: 100%;
}

View File

@@ -61,6 +61,7 @@ import SelectImages from '@/component/common/SelectImages.vue'
import upload from './upload.vue'
import pallet from './pallet.vue'
import { select } from 'three/tsl';
export default defineComponent({
components:{
upload,pallet,SelectImages
@@ -104,8 +105,12 @@ export default defineComponent({
newVal.name = data.name
newVal.tcx = data.tcx
colorData.colorList.list[colorData.selectDetail.id][colorData.colorList.index] = newVal
data.rgba = newVal.rgba
if(newVal.gradient){
data.gradient = newVal.gradient
}
let value = {
data:newVal,
data:data,
str:'color'
}
store.commit('DesignDetail/setNewDetail',value)
@@ -132,16 +137,14 @@ export default defineComponent({
item = colorData.allBoardData.colorBoards[index]
let color = colorData.allBoardData.colorBoards?.[index]
if(!color?.rgba && color?.rgbValue)color.rgba = color.rgbValue
if(
colorData.allBoardData.colorBoards?.[index] &&
colorData.selectDetail.color.rgba?.r == color?.rgba?.r &&
colorData.selectDetail.color.rgba?.g == color?.rgba?.g &&
colorData.selectDetail.color.rgba?.b == color?.rgba?.b &&
JSON.stringify(colorData.selectDetail.color.gradient) == JSON.stringify(color?.gradient)
colorData.selectDetail.color.rgba?.b == color?.rgba?.b ||
(JSON.stringify(colorData.selectDetail.color.gradient) == JSON.stringify(color?.gradient) && JSON.stringify(colorData.selectDetail.color.gradient))
&& colorData.selectDetail.color.rgba?.r
){
console.log('---',index)
isNoSelect = true
colorData.selectColor = item
colorData.colorList.index = index
@@ -190,52 +193,11 @@ export default defineComponent({
colorData.colorList.index = num
colorData.colorList.list[newVal][num] = item
}
// for (let index = 0; index < 9; index++) {
// colorData.allBoardData.colorBoards
// let item:any = {}
// item = colorData.allBoardData.colorBoards[index]
// if(
// colorData.allBoardData.colorBoards?.[index] &&
// colorData.selectDetail.color.rgba?.r == colorData.allBoardData.colorBoards?.[index]?.rgba?.r &&
// colorData.selectDetail.color.rgba?.g == colorData.allBoardData.colorBoards?.[index]?.rgba?.g &&
// colorData.selectDetail.color.rgba?.b == colorData.allBoardData.colorBoards?.[index]?.rgba?.b &&
// JSON.stringify(colorData.selectDetail.color.gradient) == JSON.stringify(colorData.allBoardData.colorBoards?.[index]?.gradient)
// && colorData.selectDetail.color.rgba?.r
// ){
// console.log(13212)
// if(!isOneChecked){
// isOneChecked = true
// if(colorData.allBoardData.colorBoards?.[index].gradient){
// item.gradient = colorData.allBoardData.colorBoards?.[index].gradient
// }
// colorData.selectColor = item
// colorData.colorList.index = index
// }
// }else if(colorData.selectDetail.color.rgba?.r){
// if(!isNoSelect){
// item = {
// hex:rgbaToHex([colorData.selectDetail.color.rgba.r,colorData.selectDetail.color.rgba.g,colorData.selectDetail.color.rgba.b]),
// id:colorData.selectDetail.color.id,
// rgba:{
// r:colorData.selectDetail.color.rgba.r,
// g:colorData.selectDetail.color.rgba.g,
// b:colorData.selectDetail.color.rgba.b,
// },
// tcx:colorData.selectDetail.color.tcx,
// name:colorData.selectDetail.color.name,
// }
// if(colorData.selectDetail.color.gradient){
// item.gradient = colorData.selectDetail.color.gradient
// }
// colorData.colorList.index = index
// colorData.selectColor = item
// isNoSelect = true
// }else{
// item = {}
// }
// }
// colorData.colorList.list[newVal].push(item)
// let newData = colorData.selectDetail?.newDetail?.color
// console.log(newData,colorData.selectColor)
// if(newData){
// newData.hex = rgbaToHex([newData.rgba.r,newData.rgba.g,newData.rgba.b])
// colorData.selectColor = newData
// }
},{immediate: true})
const selectImgItem = ()=>{
@@ -281,7 +243,6 @@ export default defineComponent({
})
}
const handleShowListChange=(val:boolean)=>{
console.log('val',val)
showLibrary.value = !val
}

View File

@@ -2,7 +2,7 @@
<div class="detailLeft">
<sketch v-show="currentDetailType == 'sketch'" ref="sketch" @addDetail="addDetail"></sketch>
<print v-show="currentDetailType == 'print'"></print>
<color v-if="currentDetailType == 'color'"></color>
<color v-if="currentDetailType == 'color'" :key="detailLeftColorKey"></color>
<element v-show="currentDetailType == 'element'"></element>
<accessory v-show="currentDetailType == 'accessory'"></accessory>
<models v-show="currentDetailType == 'models'"></models>
@@ -26,6 +26,12 @@ export default defineComponent({
components:{
sketch,print,color,element,models,accessory
},
props:{
detailLeftColorKey:{
type:Number,
default:0,
},
},
emit:['addDetail'],
setup(props,{emit}) {
const store = useStore();

View File

@@ -69,7 +69,6 @@ export default defineComponent({
file.designType = file?.resData?.designType
}
}
// store.commit('DesignDetail/setNewDetail',file.resData)
emit('selectImgItem',file)
}

View File

@@ -98,7 +98,6 @@ export default defineComponent({
})
const selectImgItem = (file:any)=>{
// let data = JSON.parse(JSON.stringify(file))
// store.commit('DesignDetail/setNewDetail',file)
emit('selectImgItem',file)
}
const upFileUploadChange = (data:any)=>{

View File

@@ -88,7 +88,6 @@ export default defineComponent({
})
const selectImgItem = (file:any)=>{
// let data = JSON.parse(JSON.stringify(file))
// store.commit('DesignDetail/setNewDetail',file)
emit('selectImgItem',file)
}
const setUploadImgList = (list:any)=>{

View File

@@ -16,20 +16,21 @@
</div>
</div>
<div class="printElementListBorder" :class="{'single':stateOverallSingle == 'single'}">
<div class="printElementList" ref="printElementList" :style="{height:stateOverallSingle == 'overall'?'14rem':'10rem'}">
<div class="itemList" :style="{width:stateOverallSingle == 'single'?(prentWidth || 0) : '100%'}">
<div class="printElementListBorder single">
<div class="printElementList" ref="printElementList" :style="{height:'10rem'}">
<div class="itemList" :style="{width:(prentWidth || 0)}">
<div class="imgItem"
v-if="stateOverallSingle == 'single'"
v-for="item in printStyleList[type][stateOverallSingle]"
v-for="item,index in printStyleList[type][stateOverallSingle]"
:key="item"
:class="{active:stateOverallSingle == 'overall' && index == imgDomIndex}"
@mousedown.stop="designMousedown(getMousePosition($event,false),item.uniqueId,'disLike')"
@touchstart.passive="designMousedown(getMousePosition($event,true),item.uniqueId,'disLike')"
@click.stop="overallSetIndex(item)"
>
<img :src="item.path" alt="">
<i class="fi fi-rr-trash" @click="navDelete(item)"></i>
<i class="fi fi-rr-trash" @click.stop="navDelete(item)"></i>
</div>
<div class="imgItem"
<!-- <div class="imgItem"
v-if="stateOverallSingle == 'overall'"
v-for="item in printStyleList[type][stateOverallSingle]"
:key="item"
@@ -37,31 +38,49 @@
>
<img :src="item.path" alt="">
<i class="fi fi-rr-trash" @click="navDelete(item)"></i>
</div>
</div> -->
</div>
</div>
</div>
<div v-show="stateOverallSingle != 'single'" class="habit_System_Designer">
<div class="habit_System_Designer_text">{{ $t('DesignPrintOperation.Scale') }}</div>
<a-slider id="system_silder"
<div v-if="imgDomIndex >= 0" v-show="stateOverallSingle != 'single'" class="habit_System_Designer">
<!-- <a-slider id="system_silder"
class="system_silder"
:min="20"
:max="1000"
v-model:value="systemDesignerPercentage"
:tip-formatter="formatter"
>
</a-slider>
</a-slider> -->
<a-popover
trigger="click"
destroyTooltipOnHide
:title="t('Canvas.repeatSetting')"
>
<template #content>
<repeat-setting
:object="printStyleList[type].overall[imgDomIndex]"
@inputFillAngle="inputFillAngle"
@inputFillOffset="inputFillOffset"
@inputFillScale="inputFillScale"
:sketchPath="selectDetail.path"
@inputFill_Gap="
(x, y) => inputFill_Gap(x, y)"
/>
</template>
<div class="btn">
<SvgIcon name="overallMore" size="20" />
</div>
</a-popover>
</div>
<div class="editPrintElementBox">
<div class="designOpenrtion_centent" id="designOpenrtionCentent">
<!-- <div class="designOpenrtion_imgMask" :style="sketch"> -->
<div class="designOpenrtion_imgMask">
<div class="designOpenrtion_print">
<div class="designOpenrtion_print" v-if="stateOverallSingle == 'single'">
<div
v-for="item,index in printStyleList[type][stateOverallSingle]"
:key="item"
v-if="stateOverallSingle == 'single'"
:style="item?.pattern?.style"
:style="{...item?.pattern?.style,opacity:item?.object?.opacity || 1}"
@mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))"
class="modal_imgItem"
ref="content"
@@ -70,9 +89,10 @@
</div>
</div>
<!-- <img :src="selectDetail.path" alt="" class="designOpenrtion_sketch" ref="sketchImg"> -->
<img :src="selectDetail?.undividedLayer?selectDetail.undividedLayer:selectDetail.path" alt="" class="designOpenrtion_sketch" ref="sketchImg" @load="()=>isSketchLoad = true">
<div class="designOpenrtion_btn">
<ul v-if="stateOverallSingle == 'single'" v-for="item,index in printStyleList[type][stateOverallSingle]" :key="item" :class="{active:item?.pattern.designOpenrtionBtn?item?.pattern.designOpenrtionBtn:false}" class="designOpenrtion_Mousingle" :style="item?.pattern.style" @mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))">
<img :src="stateOverallSingle == 'single'?(selectDetail.undividedLayer||selectDetail.path):(selectDetail.undividedLayerColor || selectDetail.path)" alt="" class="designOpenrtion_sketch" ref="sketchImg" @load="()=>isSketchLoad = true">
<img :src="selectDetail.sketchMask" alt="" class="designOpenrtion_sketchMask" ref="sketchMask">
<div class="designOpenrtion_btn" v-if="stateOverallSingle == 'single'" >
<ul v-for="item,index in printStyleList[type][stateOverallSingle]" :key="item" :class="{active:item?.pattern.designOpenrtionBtn?item?.pattern.designOpenrtionBtn:false}" class="designOpenrtion_Mousingle" :style="item?.pattern.style" @mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))">
<li class="designOpenrtion_btn_top" @mousedown.stop="itemSizeMousedown('top',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('top',getMousePosition($event,true))"></li>
<li class="designOpenrtion_btn_bottom" @mousedown.stop="itemSizeMousedown('bottom',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('bottom',getMousePosition($event,true))"></li>
<li class="designOpenrtion_btn_left" @mousedown.stop="itemSizeMousedown('left',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('left',getMousePosition($event,true))"></li>
@@ -82,12 +102,15 @@
<img src="../../../assets/images/homePage/cuowu.svg" alt="">
</li>
</ul>
<div v-show="stateOverallSingle != 'single'"></div>
<!-- <div v-show="stateOverallSingle != 'single'"></div>
<ul v-if="stateOverallSingle != 'single' && printStyleList[type][stateOverallSingle][0]" class="designOpenrtion_Mouoverall active" :style="'left:'+printStyleList[type][stateOverallSingle][0]?.pattern?.style?.left+';top:'+printStyleList[type][stateOverallSingle][0]?.pattern?.style?.top+';'" @mousedown.stop="itemMoveMousedown(0,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(0,getMousePosition($event,true))">
<i class="fi fi-rr-arrows animtion1"></i>
<i class="fi fi-rr-arrows animtion2"></i>
<li class="designOpenrtion_rotote" v-rotote.stop="[0,printStyleList[type][stateOverallSingle][0]?.pattern?.transform,type]"></li>
</ul>
</ul> -->
</div>
<div class="designOpenrtion_pingpu" v-else>
<pingpu :list="printStyleList[type].overall" :width="sketchSize.width" :height="sketchSize.height" ref="pingpuRef" @change-canvas="updateCanvas"></pingpu>
</div>
</div>
</div>
@@ -106,20 +129,31 @@ import { defineComponent,computed,ref,onMounted,nextTick,watch,toRefs, reactive,
// import setDesignItem from '@/component/Detail/setDesignItem2.vue'
import { useStore } from "vuex";
import { getMousePosition } from "@/tool/mdEvent";
import { sketchToMask } from "@/tool/util";
import pingpu from '@/component/Canvas/OverallCanvas/index.vue'
import RepeatSetting from "./overallSetting/RepeatSetting.vue";
import { useI18n } from 'vue-i18n'
export default defineComponent({
components:{
pingpu,RepeatSetting
},
props: {
type: {
type: String,
default: 'print',
}
},
sketchSize:{
type:Object,
default:()=>{}
},
},
setup(props,{emit}) {
const { t } = useI18n()
const store = useStore();
const editPrintElementDom = reactive({
imgDom:null as any,
sketchImg:null as any,
pingpuRef:null as any,
})
const editPrintElementData = reactive({
selectDetail:computed(()=>store.state.DesignDetail.selectDetail),
@@ -135,9 +169,9 @@ export default defineComponent({
single:[],
}
} as any,
stateOverallSingle:'single',
stateOverallSingle:'single' as any,
type:props.type,
imgDomIndex:0,
imgDomIndex:-1,
direction:'',//判断点的那条边
printZIndex:2,//印花优先级
sketchWH:{
@@ -149,6 +183,13 @@ export default defineComponent({
printElementList:null as any,
isSketchLoad:false,
})
const ACTIONS = {
ADD: "add",
UPDATE: "update",
DELETE: "delete",
SELECT: "select",
SORT: "sort",
};
const collItemSize = reactive({
collValue:18,
num:1,
@@ -168,13 +209,14 @@ export default defineComponent({
})
const setOveralSingle = ()=>{
setItemPosition()
}
const formatter = (value:any)=>{
return `${value}%`;
}
const deletePrint = ()=>{
editPrintElementData.printStyleList[props.type][editPrintElementData.stateOverallSingle].splice(editPrintElementData.imgDomIndex,1)
if(editPrintElementData.imgDomIndex>-1)editPrintElementData.printStyleList[props.type][editPrintElementData.stateOverallSingle].splice(editPrintElementData.imgDomIndex,1)
}
const setScaleLocation:any = (item:any)=>{
return new Promise((resolve, reject) => {
@@ -215,6 +257,7 @@ export default defineComponent({
path:data.url,
priority:editPrintElementData.printZIndex,
scale,
globalCompositeOperation:'',
}
getItemPosition(item)
setItemPosition()
@@ -249,7 +292,9 @@ export default defineComponent({
path:item.path,
minIOPath:item.minIOPath,
ifSingle:!!item.ifSingle,
globalCompositeOperation:'',
}
if(item.object)value.object = item.object
return value
}
if(editPrintElementData.printStyleList[props.type].single.length>0){
@@ -282,7 +327,6 @@ export default defineComponent({
}
const getItemPosition = (item:any)=>{
let left,top
if(item.ifSingle){
//single
left = item.location[0] / editPrintElementData.sketchWH.scale[0]
@@ -312,13 +356,40 @@ export default defineComponent({
}
editPrintElementData.printZIndex++
item.pattern = pattern
if(item.object){
}else{
item.object = {
top: 0,
left: 0,
scaleX: 1,
scaleY: 1,
opacity: 1,
angle: 0,
flipX: false,
flipY: false,
blendMode: "multiply",
gapX: 0,
gapY: 0,
}
}
if(item.ifSingle){
editPrintElementData.printStyleList[props.type].single.push(item)
}else{
editPrintElementData.printStyleList[props.type].overall = []
item.token = Date.now().toString() + (editPrintElementData.printStyleList[props.type].overall.length + '')
// editPrintElementData.printStyleList[props.type].overall = []
editPrintElementData.printStyleList[props.type].overall.push(item)
}
setTimeout(()=>{
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.ADD,
data: item,
},
]);
})
}
}
const setPosition = ()=>{
nextTick(()=>{
@@ -337,16 +408,16 @@ export default defineComponent({
if(!editPrintElementData.selectDetail.printObject.prints)return
let state = true
// editPrintElementData.stateOverallSingle = 'single'
let arr:any = editPrintElementData.selectDetail.printObject.prints
let arr:any = editPrintElementData.selectDetail.newDetail?.print || editPrintElementData.selectDetail.printObject.prints
if(props.type == 'element'){
arr = editPrintElementData.selectDetail.trims.prints
arr = editPrintElementData.selectDetail.newDetail?.element || editPrintElementData.selectDetail.trims.prints
}
if(editPrintElementData.selectDetail.newDetail?.[editPrintElementData.currentDetailType]){
arr = editPrintElementData.selectDetail.newDetail[editPrintElementData.currentDetailType]
}
if(arr && arr.length > 0){
arr.forEach((item:any)=>{
if(!item.ifSingle && arr.length == 1){
if(!item.ifSingle){
editPrintElementData.stateOverallSingle = 'overall',
state = false
}
@@ -377,15 +448,42 @@ export default defineComponent({
addPrintELement(newVal)
}
})
watch(()=>((editPrintElementData.selectDetail?.undividedLayer || editPrintElementData.selectDetail?.id)),(newVal)=>{
watch(()=>((editPrintElementData.selectDetail?.id)),(newVal)=>{
if(!newVal)return
editPrintElementData.isSketchLoad = false,
editPrintElementData.printStyleList[props.type] = {
single:[],
overall:[],
}
if(!editPrintElementData.selectDetail.sketchMask){
sketchToMask(editPrintElementData.selectDetail?.layersObject[0].maskUrl).then((res:any)=>{
editPrintElementData.selectDetail.sketchMask = res
})
}
setPosition()
},{immediate: true,})
watch(()=>editPrintElementData.stateOverallSingle,(newVal)=>{
let arr = editPrintElementData.printStyleList[props.type][newVal]
if(arr.length > 0){
editPrintElementData.imgDomIndex = 0
if(newVal == 'overall'){
arr.forEach((item:any,index:number) => {
item.id_ = index
});
}
}else{
editPrintElementData.imgDomIndex = -1
}
})
watch(()=>editPrintElementData.printStyleList[props.type][editPrintElementData.stateOverallSingle].length,(newVal)=>{
if(newVal > 0){
if(editPrintElementData.imgDomIndex < 0)editPrintElementData.imgDomIndex = 0
}
})
const addOverallToCanvas = ()=>{
editPrintElementDom.pingpuRef
}
//设置移动
const itemMoveMousedown = (index:number,event:any)=>{
if (event.target.tagName === 'IMG' || event.target.nodeName === 'IMG')return
@@ -649,7 +747,17 @@ export default defineComponent({
}
//排序 从大到小
const sortDesignCollection = ()=> {
if(editPrintElementData.stateOverallSingle == 'overall'){
let arr:any = editPrintElementData.printStyleList[props.type][editPrintElementData.stateOverallSingle]
let tokenList = arr.map((item)=>({token:item.token,srot:item.pattern.style.zIndex}))
let tokens = tokenList.sort((a:any,b:any)=>a.srot - b.srot).map((item:any)=>item.token)
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.SORT,
tokens: tokens,
},
]);
}
}
const reRange = (item:any, x:number, y:number,str?:string)=>{
let elList:any = collItemSize.elList
@@ -692,6 +800,14 @@ export default defineComponent({
const navDelete = (item:any)=>{
let arr:any = editPrintElementData.printStyleList[props.type][editPrintElementData.stateOverallSingle]
let index = arr.findIndex((arrItem:any)=>arrItem.uniqueId == item.uniqueId)
if(editPrintElementData.stateOverallSingle == 'overall'){
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.DELETE,
token: arr[index].token,
},
]);
}
arr.splice(index,1)
setItemPosition()
}
@@ -738,6 +854,106 @@ export default defineComponent({
collItemSize.prentWidth = (collItemSize.padding + remValue) * elArr.length + 'px'
moveItem()
}
const updateCanvas = (arr)=>{//画布操作更新对应的参数
let list = editPrintElementData.printStyleList[props.type].overall
arr.forEach((item)=>{
const obj = list.find((v) => v.token === item.token);
if (item.action === ACTIONS.UPDATE) {
if (item.action === ACTIONS.UPDATE) {
try {
const key = item.key;
const str = /^\[/.test(item.key)
? "obj" + key
: "obj." + key;
eval(`${str} = item.value`);
} catch (error) {
console.error(error);
}
}
} else if (item.action === ACTIONS.SELECT) {
overallSetIndex(obj)
}
})
}
const inputFillAngle = (angle:any)=>{
let arr = editPrintElementData.printStyleList[props.type].overall
arr[editPrintElementData.imgDomIndex].angle = angle
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token,
key: 'angle',
value: angle,
},
]);
}
const inputFillOffset = (offset:any)=>{
let arr = editPrintElementData.printStyleList[props.type].overall
arr[editPrintElementData.imgDomIndex].location = [offset.left * offset.size[0] / 100,offset.top * offset.size[1] / 100]
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token,
key: 'location[0]',
value: offset.left,
},
]);
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token,
key: 'location[1]',
value: offset.top,
},
]);
// editPrintElementData.overallDetail.offsetX = offset.left
// editPrintElementData.overallDetail.offsetY = offset.top
}
const inputFillScale = (scale:any)=>{
let arr = editPrintElementData.printStyleList[props.type].overall
arr[editPrintElementData.imgDomIndex].scale = [scale,scale]
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token,
key: 'scale[0]',
value: scale,
},
]);
}
const inputFill_Gap = (x:any,y:any)=>{
let arr = editPrintElementData.printStyleList[props.type].overall
arr[editPrintElementData.imgDomIndex].object.gapX = x
arr[editPrintElementData.imgDomIndex].object.gapY = y
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token,
key: "object.gapX",
value: x,
},
]);
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token,
key: "object.gapY",
value: y,
},
]);
}
const overallSetIndex = (item)=>{
if(editPrintElementData.stateOverallSingle !== 'overall')return
let arr = editPrintElementData.printStyleList[props.type].overall
let index = arr.findIndex((arrItem:any)=>arrItem.token == item.token)
editPrintElementData.imgDomIndex = index
editPrintElementDom.pingpuRef.updataList([
{
action: ACTIONS.SELECT,
token: arr[index].token,
},
]);
}
onMounted(()=>{
if(props.type == 'element'){
editPrintElementData.stateOverallSingle = 'single'
@@ -747,6 +963,7 @@ export default defineComponent({
previewDetailPrintData()
})
return{
t,
getMousePosition,
...toRefs(editPrintElementDom),
...toRefs(editPrintElementData),
@@ -760,6 +977,12 @@ export default defineComponent({
clearOverall,
designMousedown,
navDelete,
updateCanvas,
inputFillAngle,
inputFillOffset,
inputFillScale,
inputFill_Gap,
overallSetIndex,
}
},
directives:{
@@ -876,7 +1099,10 @@ export default defineComponent({
.habit_System_Designer {
align-items: center;
display: flex;
justify-content: center;
justify-content: flex-end;
margin-top: 1.8rem;
margin-right: .8rem;
.ant-slider-track,
.ant-slider-rail {
background-color: #e1e1e1;
@@ -924,6 +1150,9 @@ export default defineComponent({
cursor: pointer;
border-radius: 1rem;
overflow: hidden;
&.active{
border: 2px solid #B4B4B4;
}
> img{
width: 100%;
height: 100%;
@@ -977,13 +1206,23 @@ export default defineComponent({
overflow-x: hidden;
// max-height: 80%;
position: relative;
&::-webkit-scrollbar {
display: none;
}
>img{
z-index: 2;
position: relative;
// height: 100%;
max-width: 100%;
// width: 100%;
&.designOpenrtion_sketchMask{
position: absolute;
z-index: 3;
top: 0;
left: 0;
pointer-events: none;
height: 100%;
}
}
.designOpenrtion_sketch_mask{
z-index: 2;
@@ -1013,8 +1252,13 @@ export default defineComponent({
}
}
}
.designOpenrtion_pingpu{
width: 100%;
height: 100%;
z-index: 2;
}
.designOpenrtion_btn{
z-index: 3;
z-index: 99;
>div{
width: 100%;
height: 100%;

View File

@@ -6,8 +6,8 @@
</div> -->
<sketchType v-if="currentDetailType === 'sketch'"></sketchType>
<!-- <printType v-if="currentDetailType === 'print'"></printType> -->
<editPrintElement ref="editPrintElement" v-if="currentDetailType === 'print'" type="print"></editPrintElement>
<editPrintElement ref="editPrintElement" v-if="currentDetailType === 'element'" type="element"></editPrintElement>
<editPrintElement ref="editPrintElement" v-if="currentDetailType === 'print'" type="print" :sketchSize="sketchSize"></editPrintElement>
<editPrintElement ref="editPrintElement" v-if="currentDetailType === 'element'" type="element" :sketchSize="sketchSize"></editPrintElement>
<modelsType ref="modelsType" v-if="currentDetailType === 'models'"></modelsType>
</div>
@@ -24,6 +24,12 @@ export default defineComponent({
components:{
sketchType,printType,editPrintElement,modelsType
},
props:{
sketchSize:{
type:Object,
default:()=>{}
},
},
setup(props,{emit}) {
const store = useStore();
const detailData = reactive({

View File

@@ -0,0 +1,166 @@
<template>
<div class="repeat-setting">
{{ }}
<div class="repeat-setting-item">
<span class="label">{{ t("Canvas.angle") }}</span>
<angle-tool
:angle="angle"
styleType="2"
@input="(e) => emit('inputFillAngle', e)"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">{{ t("Canvas.scale") }}</span>
<slider
:min="1"
:max="1000"
:step="1"
is-input
:tipFormatter="(v) => `${scale}%`"
:value="scale"
@input="inputFillScale"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">Gap X</span>
<slider
:min="0"
:max="1000"
:step="1"
is-input
:tipFormatter="(v) => `${v}px`"
:value="gapX"
@input="(e) => emit('inputFill_Gap', e, gapY)"
@change="(e) => emit('changeFill_Gap', e, gapY)"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">Gap Y</span>
<slider
:min="0"
:max="1000"
:step="1"
is-input
:tipFormatter="(v) => `${v}px`"
:value="gapY"
@input="(e) => emit('inputFill_Gap', gapX, e)"
@change="(e) => emit('changeFill_Gap', gapX, e)"
/>
</div>
<p></p>
<div class="repeat-setting-item">
<span class="label">{{ t("Canvas.offset") }}</span>
<offset-tool
:top="offset[1]"
:left="offset[0]"
@input="inputOffset"
@change="(e) => emit('changeFillOffset', e)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, computed } from "vue";
import AngleTool from "./tools/AngleTool.vue";
import OffsetTool from "./tools/OffsetTool.vue";
import Slider from "./tools/Slider.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
object: {
required: true,
type: Object,
},
sketchPath: {
required: true,
type: String,
},
});
const angle = computed(() => props.object?.angle || 0);
const scale = computed(() => {
// let scaleValue = props.object?.scale/10;
// return props.object?.scale/10;
return props.object?.scale[0] * 100;
});
const offset = ref([0,0])
const sketchSize:any = async ()=>{
let img = new Image();
let size = [0,0];
img.src = props.sketchPath;
await new Promise((resolve, reject) => {
img.onload = () => {
size = [img.width, img.height]
resolve([img.width, img.height]);
}
img.onerror = reject;
});
return size
}
watch (() => props.object.path || props.object.location, async () => {
let size = await sketchSize();
offset.value[0] = props.object.location[0] / size[0] * 100;
offset.value[1] = props.object.location[1] / size[1] * 100;
},{immediate: true})
const gapX = computed(() => props.object.object?.gapX || 0);
const gapY = computed(() => props.object.object?.gapY || 0);
const emit = defineEmits([
"inputFillAngle",
"changeFillAngle",
"inputFillOffset",
"changeFillOffset",
"inputFillScale",
"changeFillScale",
"inputFill_Gap",
"changeFill_Gap",
]);
const inputFillScale = (e) => {
const scale = e / 100;
console.log(scale)
emit("inputFillScale", scale);
};
const inputOffset = async (e:any)=>{
emit('inputFillOffset', {...e,size: await sketchSize()})
}
</script>
<style scoped lang="less">
.repeat-setting {
user-select: none;
width: 228px;
> .title {
line-height: 35px;
font-size: 14px;
text-align: center;
margin-top: -12px;
margin-bottom: 3px;
}
> .repeat-setting-item {
display: flex;
align-items: center;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
&.offset {
justify-content: center;
}
> .label {
min-width: 68px;
font-size: 12px;
}
&:not(.offset) > div {
width: 120px;
flex: 1;
}
> .slider {
--slider-thumb-color1: #000;
--slider-thumb-color2: #eee;
}
}
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<div class="angle-tool" :disabled="disabled">
<template v-if="styleType === '1'">
<div
ref="dishRef"
class="dish"
@mousedown.stop="mousedown"
@touchmove.stop="mousedown"
>
<div
class="pointer"
:style="{ transform: `rotate(${angle}deg)` }"
>
<span></span>
</div>
</div>
<div class="input">
<input
type="number"
v-model="angle"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
</template>
<my-input
v-if="styleType === '2'"
v-model="angle"
@input="onInput"
@change="onChange"
:disabled="disabled"
type="number"
after="°"
icon="icon-angle"
/>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
import { calculateAngle } from "@/component/Canvas/CanvasEditor/utils/helper";
import MyInput from "./MyInput.vue";
// Props
const props = defineProps({
styleType: {
type: String,
default: "1",
},
angle: {
type: Number,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["change", "input"]);
const angle = ref(props.angle);
watch(
() => props.angle,
(value) => {
angle.value = value;
}
);
const dishRef = ref<HTMLDivElement>();
const mousedown = (e: MouseEvent | TouchEvent) => {
if (props.disabled) return;
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const { left, top, width, height } =
dishRef.value.getBoundingClientRect();
const centerX = left + width / 2;
const centerY = top + height / 2;
const { clientX, clientY } = e?.touches?.[0] || e;
angle.value = calculateAngle(centerX, centerY, clientX, clientY, true);
onInput();
};
mousemove(e);
const mouseup = () => {
onChange();
document.removeEventListener("mousemove", mousemove);
document.removeEventListener("touchmove", mousemove);
document.removeEventListener("mouseup", mouseup);
document.removeEventListener("touchend", mouseup);
};
document.addEventListener("mousemove", mousemove);
document.addEventListener("touchmove", mousemove);
document.addEventListener("mouseup", mouseup);
document.addEventListener("touchend", mouseup);
};
const onInput = () => !props.disabled && emit("input", angle.value);
var changeTime: any = null;
const onChange = () => {
if (props.disabled) return;
clearTimeout(changeTime);
changeTime = setTimeout(() => emit("change", angle.value), 500);
};
// var angleTime = null;
// watch(angle, (value) => {
// emit("input", value);
// clearTimeout(angleTime);
// angleTime = setTimeout(() => emit("change", value), 50);
// });
// defineExpose({
// open,
// close,
// });
</script>
<style scoped lang="less">
.angle-tool {
display: flex;
align-items: center;
width: 100%;
--color: #000;
&[disabled="true"] {
--color: #b2b2b2;
> .dish {
cursor: not-allowed;
}
}
> .dish {
width: 24px;
height: 24px;
border: 1px solid var(--color);
border-radius: 50%;
cursor: pointer;
> .pointer {
pointer-events: none;
user-select: none;
position: relative;
width: 100%;
height: 100%;
> span {
position: absolute;
top: 10%;
left: 50%;
transform: translate(-50%, 0);
width: 35%;
height: 35%;
background-color: var(--color);
border-radius: 50%;
}
}
}
> .input {
margin-left: 5px;
font-size: 14px;
color: var(--color);
flex: 1;
// min-width: 45px;
// max-width: 80px;
// width: 50px;
> input {
width: 100%;
border-radius: 3px;
outline: none;
}
}
> .my-input {
flex: 1;
}
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="my-input">
<span class="decorate"></span>
<span v-show="icon" :class="['iconfont', icon]"></span>
<span v-show="before" class="before">{{ before }}</span>
<input v-bind="$attrs" :value="modelValue" @input="onInput" />
<span v-show="after" class="after">{{ after }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
const props = defineProps({
modelValue: { type: Number, default: 0 },
icon: { default: "", type: String },
before: { default: "", type: String },
after: { default: "", type: String },
});
const emit = defineEmits(["update:modelValue", "input"]);
const onInput = (e) => {
const value = e.target.value;
emit("update:modelValue", value);
emit("input", value);
};
</script>
<style scoped lang="less">
.my-input {
display: flex;
align-items: center;
width: 100%;
border: 1px solid rgba(230, 230, 231, 1);
border-radius: 3px;
height: 20px;
padding: 0 4px 0 2px;
> .decorate {
width: 2px;
background-color: rgba(230, 230, 231, 1);
border-radius: 3px;
height: 85%;
margin-right: 4px;
}
> .iconfont {
font-size: 10px;
color: #000;
margin-right: 2px;
}
> .before {
font-size: 12px;
color: #000;
margin-right: 2px;
}
> .after {
font-size: 12px;
color: #000;
}
> input {
font-size: 12px;
width: 0;
flex: 1;
text-align: right;
outline: none;
border: none;
background-color: transparent;
padding: 0;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<a-select
class="my-select"
:size="size"
@change="change"
:defaultValue="defaultValue"
@dropdownVisibleChange="dropdownVisibleChange"
:disabled="disabled"
>
<a-select-option
v-for="v in list"
:key="v.value"
:value="v.value"
:title="v.tip"
@mouseover.stop.prevent="mouseover(v)"
@mouseleave="mouseleave(v)"
>{{ v.label }}</a-select-option
>
</a-select>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
defaultValue: {
default: "",
},
list: {
type: Array,
default: () => [],
},
size: {
type: String,
default: "small",
},
});
const emit = defineEmits(["change", "active"]);
const isChange = ref(false);
const initValue = ref(props.defaultValue);
const activeValue = ref(props.defaultValue);
const timeout = ref(null);
const mouseover = (v) => {
clearTimeout(timeout.value);
if (v.value === activeValue.value) return;
emit("active", v.value, activeValue.value);
activeValue.value = v.value;
};
const mouseleave = () => {
clearTimeout(timeout.value);
timeout.value = setTimeout(() => {
dropdownVisibleChange(false);
}, 100);
};
const change = (v) => {
isChange.value = true;
emit("change", v, initValue.value);
};
const dropdownVisibleChange = (v) => {
if (v) {
isChange.value = false;
initValue.value = props.defaultValue;
} else if (!isChange.value) {
emit("active", initValue.value, activeValue.value);
activeValue.value = initValue.value;
}
};
</script>

View File

@@ -0,0 +1,235 @@
<template>
<div class="offset-tool">
<div class="input" v-show="showInput">
<my-input
v-model="left"
@input="onInput"
@change="onChange"
type="number"
before="X"
after="%"
:min="-100"
:max="100"
/>
<my-input
v-model="top"
@input="onInput"
@change="onChange"
type="number"
before="Y"
after="%"
:min="-100"
:max="100"
/>
</div>
<div
class="dish"
@mousedown="mousedown"
@touchstart="mousedown"
ref="dishRef"
v-show="showDish"
>
<img src="/src/assets/images/icon/xyz.png" />
<span class="ball" :style="ballStyle"></span>
<span class="tip x">X: {{ left }}%</span>
<span class="tip y">Y: {{ top }}%</span>
<span class="line x"></span>
<span class="line y"></span>
<span class="line z" :style="lineZStyle"></span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch, computed } from "vue";
import MyInput from "./MyInput.vue";
const props = defineProps({
left: {
type: Number,
default: 0,
},
top: {
type: Number,
default: 0,
},
showInput: {
type: Boolean,
default: true,
},
showDish: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["change", "input"]);
// 工具的实际坐标 -100 ~ 100
const top = ref(Math.round(props.top));
const left = ref(Math.round(props.left));
// 原点的坐标 0 ~ 100
const ballStyle = computed(() => ({
top: 50 + Number(top.value) / 2 + "%",
left: 50 + Number(left.value) / 2 + "%",
}));
watch(
() => props.left,
(v) => (left.value = Math.round(v))
);
watch(
() => props.top,
(v) => (top.value = Math.round(v))
);
const dishRef = ref<HTMLDivElement>();
const mousedown = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const rect = dishRef.value.getBoundingClientRect();
const X = e.clientX || (e as TouchEvent).touches[0].clientX;
const Y = e.clientY || (e as TouchEvent).touches[0].clientY;
var x = ((X - rect.left) / rect.width) * 100;
var y = ((Y - rect.top) / rect.height) * 100;
if (x < 0) x = 0;
if (x > 100) x = 100;
if (y < 0) y = 0;
if (y > 100) y = 100;
left.value = Math.round((x - 50) * 2);
top.value = Math.round((y - 50) * 2);
onInput();
};
mousemove(e);
const mouseup = () => {
onChange();
document.removeEventListener("mousemove", mousemove);
document.removeEventListener("touchmove", mousemove);
document.removeEventListener("mouseup", mouseup);
document.removeEventListener("touchend", mouseup);
};
document.addEventListener("mousemove", mousemove);
document.addEventListener("touchmove", mousemove);
document.addEventListener("mouseup", mouseup);
document.addEventListener("touchend", mouseup);
};
const onInput = () => {
emit("input", { left: left.value, top: top.value });
};
var changeTime: any = null;
const onChange = () => {
clearTimeout(changeTime);
changeTime = setTimeout(() => {
emit("change", {
left: left.value,
top: top.value,
});
}, 500);
};
const lineZStyle = computed(() => ({
"--rotateZ": calculateAngle(0, 0, left.value, top.value) + "deg",
width: calculateDistance(0, 0, left.value, top.value) / 2 + "%",
}));
// 计算角度
function calculateAngle(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1;
const deltaY = y1 - y2;
let angle = Math.atan2(deltaX, deltaY) * (180 / Math.PI) - 90;
return angle;
}
// 计算距离
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1;
const deltaY = y2 - y1;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return distance;
}
</script>
<style scoped lang="less">
.offset-tool {
position: relative;
> .input {
display: flex;
align-items: center;
justify-content: center;
> * {
flex: 1;
margin-right: 12px;
&:last-child {
margin-right: 0;
}
}
}
> .dish {
width: 135px;
height: 135px;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;
position: relative;
background-color: #f6f6f6;
margin-top: 24px;
> * {
position: absolute;
pointer-events: none;
user-select: none;
}
> img {
width: 15px;
height: 15px;
bottom: 4px;
right: 4px;
}
> .ball {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 10px;
border: 1px solid #fff;
background-color: #333;
border-radius: 50%;
box-shadow: 0px 0.68px 1.7px 0px rgba(0, 0, 0, 0.26);
}
> .tip {
font-size: 10px;
color: #000;
line-height: 24px;
&.x {
top: 50%;
right: 0%;
transform: translate(100%, -50%);
padding-left: 6px;
}
&.y {
top: 0%;
left: 50%;
transform: translate(-50%, -100%);
}
}
> .line {
border-color: #d9d9d9;
border-style: dashed;
border-width: 0;
width: 0;
height: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
&.x {
width: 100%;
border-top-width: 1px;
}
&.y {
height: 100%;
border-left-width: 1px;
}
&.z {
width: 50%;
border-top-width: 1px;
border-color: #454754;
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
transform-origin: left center;
}
}
}
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<div class="slider" :disabled="disabled">
<div
class="input-range"
:style="{
'--progress': (value - props.min) / (props.max - props.min),
}"
>
<span class="tip">{{ props.tipFormatter(value) }}</span>
<input
type="range"
v-model="value"
:min="props.min"
:max="props.max"
:step="props.step"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
<div class="input" v-show="isInput">
<my-input
v-model="value"
:min="props.min"
:max="props.max"
:step="props.step"
@input="onInput"
@change="onChange"
:disabled="disabled"
type="number"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
import MyInput from "./MyInput.vue";
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
value: {
type: Number,
default: 0,
},
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 100,
},
step: {
type: Number,
default: 1,
},
tipFormatter: {
type: Function,
default: (v) => v,
},
isInput: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["change", "input"]);
const value = ref(props.value);
watch(
() => props.value,
(v) => (value.value = v)
);
const onInput = () => !props.disabled && emit("input", Number(value.value));
var changeTime: any = null;
const onChange = () => {
if (props.disabled) return;
clearTimeout(changeTime);
changeTime = setTimeout(() => emit("change", Number(value.value)), 500);
};
</script>
<style scoped lang="less">
.slider {
position: relative;
display: flex;
align-items: center;
width: 150px;
--input-thumb-size: 10px;
--backcolor1: var(--slider-thumb-color1, #4285f4);
--backcolor2: var(--slider-thumb-color2, rgba(0, 0, 0, 0.1));
&:hover {
> .input-range > .tip {
display: block;
}
}
> .input-range {
position: relative;
flex: 2;
> input {
width: 100%;
-webkit-appearance: none;
appearance: none;
height: 5px;
border-radius: 5px;
outline: none;
background: linear-gradient(
to right,
var(--backcolor1) 0%,
var(--backcolor1) calc(var(--progress) * 100%),
var(--backcolor2) calc(var(--progress) * 100%),
var(--backcolor2) 100%
);
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: var(--input-thumb-size);
height: var(--input-thumb-size);
border-radius: 50%;
background: var(--backcolor1); /* 蓝色滑块 */
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
&::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
}
> .tip {
position: absolute;
font-size: 10px;
pointer-events: none;
user-select: none;
color: #666;
top: 0;
left: calc(
(100% - var(--input-thumb-size)) * var(--progress) +
var(--input-thumb-size) / 2
);
transform: translate(-50%, -100%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 0.4rem 0.8rem;
border-radius: 0.4rem;
font-size: 1.2rem;
white-space: nowrap;
pointer-events: none;
display: none;
&::after {
content: "";
position: absolute;
top: 97%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid rgba(0, 0, 0, 0.8);
}
}
}
> .input {
flex: 1;
margin-left: 10px;
> input {
border-radius: 3px;
width: 100%;
}
}
}
</style>

View File

@@ -4,9 +4,9 @@
<div class="back" v-show="isEditPattern.value">
<i class="fi fi-br-angle-left" @click="setBack"></i>
</div>
<modelNav @canvasReload="()=>$emit('canvasReload')" @addSketch="()=>$emit('addSketch')" @sketchSysToLibrary="()=>$emit('sketchSysToLibrary')" @deleteItem="deleteItem"></modelNav>
<modelNav ref="modelNav" @addSketch="()=>$emit('addSketch')" @sketchSysToLibrary="()=>$emit('sketchSysToLibrary')" @deleteItem="deleteItem"></modelNav>
</div>
<div class="modelindex_right">
<div class="modelindex_right" ref="modelindexRight">
<div class="detail_btn">
<!-- 全屏 -->
<!-- <i class="fi fi-bs-expand-arrows-alt" @click="showDesignImgDetail('2')"></i> -->
@@ -14,7 +14,7 @@
<i class="icon iconfont icon-chehui" @click="revocation"></i>
<i class="icon iconfont icon-fanchehui" @click="oppositeRevocation"></i>
<!-- 编辑 -->
<i class="fi fi-rs-pencil-paintbrush" :title="$t('DesignDetail.editSketchTitle')" :class="{'pointerEventsNone':!selectDetail?.id}" @click="()=>$emit('addDetail')"></i>
<i class="fi fi-rs-pencil-paintbrush" :title="$t('DesignDetail.editSketchTitle')" :class="{'pointerEventsNone':!selectDetail?.id,active:isEditPattern.value == 'editSketch'}" @click="showDesignImgDetail('editSketch')"></i>
<i class="fi fi-rr-edit" :title="$t('DesignDetail.editTitle')" :class="{active:isEditPattern.value == 'canvasEditor','pointerEventsNone':!selectDetail?.id}" @click="showDesignImgDetail('canvasEditor')"></i>
<i class="icon iconfont icon-clothes" :title="$t('Canvas.editFrontBack')" style="font-size: 3.2rem;" @click="showDesignImgDetail('redGreenExample')" :class="{active:isEditPattern.value == 'redGreenExample','pointerEventsNone':!selectDetail?.id}"></i>
@@ -32,7 +32,7 @@
</i>
</div>
<position ref="position" @modelOnLoad="()=>$emit('modelOnLoad')" @canvasReload="()=>$emit('canvasReload')" @addSketch="()=>$emit('addSketch')" :imgDesignImg=imgDesignImg></position>
<position ref="position" @addSketch="()=>$emit('addSketch')" :imgDesignImg=imgDesignImg></position>
</div>
</div>
@@ -51,7 +51,7 @@ export default defineComponent({
components:{
position,modelNav
},
emits:['detailEdit','canvasReload','addSketch','revocation','oppositeRevocation','modelOnLoad','sketchSysToLibrary','addDetail'],
emits:['detailEdit','addSketch','revocation','oppositeRevocation','sketchSysToLibrary'],
setup(props,{emit}) {
const {t} = useI18n()
const store = useStore();
@@ -68,36 +68,40 @@ export default defineComponent({
const getDetailListDom = reactive({
libraryList:null as any,
position:null as any,
modelindexRight:null as any,
modelNav:null as any,
})
const getSubmitData = (value:any,boolean)=>{
return getDetailListDom.position.getSubmitData(value,boolean)
const getSubmitData = (value:any)=>{
return getDetailListDom.position.getSubmitData(value,)
}
const showDesignImgDetail = (str:any)=>{
new Promise((resolve, reject) => {
if(
getDetailListData.isEditPattern.value&&
detailData?.getCanvasIfEdit?.fun&&detailData?.getCanvasIfEdit?.fun() > 0
){
Modal.confirm({
title: t('collectionModal.jsContent2'),
icon: createVNode(ExclamationCircleOutlined),
okText: 'Yes',
cancelText: 'No',
mask:false,
centered:true,
onOk() {
resolve(true)
emit('detailEdit',str)
},
onCancel(){
resolve(false)
}
});
}else{
resolve(true)
emit('detailEdit',str)
}
})
emit('detailEdit',str)
// new Promise((resolve, reject) => {
// if(
// getDetailListData.isEditPattern.value&&
// detailData?.getCanvasIfEdit?.fun&&detailData?.getCanvasIfEdit?.fun() > 0
// ){
// Modal.confirm({
// title: t('collectionModal.jsContent2'),
// icon: createVNode(ExclamationCircleOutlined),
// okText: 'Yes',
// cancelText: 'No',
// mask:false,
// centered:true,
// onOk() {
// resolve(true)
// emit('detailEdit',str)
// },
// onCancel(){
// resolve(false)
// }
// });
// }else{
// resolve(true)
// emit('detailEdit',str)
// }
// })
}
const deleteItem = ()=>{
setRevocation()
@@ -121,18 +125,25 @@ export default defineComponent({
clearTimeout(time)
time = setTimeout(()=>{
store.commit('DesignDetail/setDesignDetail',getDetailListData.designDetail)
getDetailListDom.position.updataPosition()
},1000)
getDetailListDom.position?.updataPosition?.()
getDetailListDom.modelNav?.setItemPosition?.()
getDetailListDom.position?.updateRect?.()
},500)
}
const setBack = ()=>{
emit('detailEdit')
showDesignImgDetail(getDetailListData.isEditPattern.value)
}
let observers = null as any
onMounted(()=>{
window.addEventListener('resize', handleResize);
observers = new ResizeObserver(entries => {
for (let entry of entries) {
handleResize()
}
});
observers.observe(getDetailListDom.modelindexRight);
})
onBeforeUnmount(()=>{
window.removeEventListener('resize', handleResize);
observers.disconnect();
})
return{
...toRefs(detailData),

View File

@@ -34,7 +34,7 @@ export default defineComponent({
components:{
position,
},
emits:['canvasReload','addSketch','deleteItem','sketchSysToLibrary'],
emits:['addSketch','deleteItem','sketchSysToLibrary'],
props:{
},
setup(props,{emit}) {
@@ -79,25 +79,25 @@ export default defineComponent({
detailData.selectDetail?.id &&
detailData?.getCanvasIfEdit?.fun&&detailData?.getCanvasIfEdit?.fun() > 0
){
Modal.confirm({
title: t('collectionModal.jsContent6'),
icon: createVNode(ExclamationCircleOutlined),
okText: 'Yes',
cancelText: 'No',
mask:false,
centered:true,
onOk() {
resolve(true)
emit('canvasReload')
},
onCancel(){
resolve(false)
}
});
resolve()
// Modal.confirm({
// title: t('collectionModal.jsContent6'),
// icon: createVNode(ExclamationCircleOutlined),
// okText: 'Yes',
// cancelText: 'No',
// mask:false,
// centered:true,
// onOk() {
// resolve(true)
// emit('canvasReload')
// },
// onCancel(){
// resolve(false)
// }
// });
}else{
resolve(true)
if(detailData.selectDetail.id !== item.id){
emit('canvasReload')
}
}
}).then((rv)=>{
@@ -117,7 +117,6 @@ export default defineComponent({
const maxObj = detailData.designDetail.clothes.find(item => item.priority === maxValue);
store.commit('DesignDetail/setDesignColthes',maxObj.id)
}
emit('canvasReload')
emit('deleteItem')
}
const addSketch = ()=>{
@@ -268,17 +267,8 @@ export default defineComponent({
let allPostition = store.state.Workspace.workspaceAllPosition
return allPostition.find(item => item.value === type)?.name
}
let observers = null as any
onMounted(()=>{
observers = new ResizeObserver(entries => {
for (let entry of entries) {
setItemPosition()
}
});
observers.observe(detailData.modelNavBox);
})
onBeforeUnmount(()=>{
observers.disconnect();
})
return{
...toRefs(detailData),

View File

@@ -1,21 +1,17 @@
<template>
<div class="molepositon" :class="{active:!imgDesignImg}">
<div class="designOpenrtion_imgMask" v-if="frontBack?.body?.path" :style="frontBack?.body?.style">
<div class="designOpenrtion_print" v-for="item,index in frontBack.back" @mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))" @click="setpitch(item,index)" :style="frontBack.front[index].style">
<div class="designOpenrtion_print" v-for="item,index in frontBack.back" :style="frontBack.front[index].style">
<img :style="item.imageUrl?'':'display:none;'" :src="item.imageUrl" alt="">
</div>
<img class="perview_img" @load="setPrintSize()" ref="detailBody" :key="designDetail.designItemId" :src="frontBack?.body?.path" :style="'width:'+ frontBack?.body?.layersObject?.[0].imageSize?.[0] +';height:' + frontBack?.body?.layersObject?.[0].imageSize?.[0] +';'">
<div class="detail_modal_item_front" v-for="item,index in frontBack.front" @mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))" @click="setpitch(item,index)" :style="item.style">
<!-- <div class="detail_modal_item_front" ref="target" v-for="item,index in frontBack.front" @mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))" @click="setpitch(item,index)" :style="item.style">
<img :src="item.imageUrl" alt="">
</div> -->
<div class="detail_modal_item_front" :ref="el => { setElementRef(el, index) }" v-for="item,index in frontBack.front" :class="{'active':item.id == selectDetail?.id}" :style="item.style">
<img :src="item.imageUrl" alt="">
</div>
<div class="designOpenrtion_btnBox">
<ul v-for="item,index in frontBack.front" :key="item" :class="{active:item.designOpenrtionBtn}" class="designOpenrtion_btn" :style="item.style" @mousedown.stop="itemMoveMousedown(index,getMousePosition($event,false))" @touchstart.passive="itemMoveMousedown(index,getMousePosition($event,true))">
<li class="designOpenrtion_btn_top" @mousedown.stop="itemSizeMousedown('top',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('top',getMousePosition($event,true))"></li>
<li class="designOpenrtion_btn_bottom" @mousedown.stop="itemSizeMousedown('bottom',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('bottom',getMousePosition($event,true))"></li>
<li class="designOpenrtion_btn_left" @mousedown.stop="itemSizeMousedown('left',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('left',getMousePosition($event,true))"></li>
<li class="designOpenrtion_btn_right" @mousedown.stop="itemSizeMousedown('right',getMousePosition($event,false))" @touchstart.passive="itemSizeMousedown('right',getMousePosition($event,true))"></li>
</ul>
</div>
<div ref="moveableContainer" class="moveableContainer"></div>
</div>
<div class="designOpenrtion_imgMask" v-if="!frontBack?.body?.path">
<img :src="selectDetail?.undividedLayerWithSinglePrint || selectDetail?.undividedLayer || selectDetail?.path" style="object-fit: cover;" alt="">
@@ -40,8 +36,10 @@ import { Https } from "@/tool/https";
import { useStore } from "vuex";
import { useI18n } from 'vue-i18n'
import { getMousePosition } from "@/tool/mdEvent";
import { Modal,message } from 'ant-design-vue';
import newFollowVue from '@/component/Account/message/newFollow.vue';
import Vue3Moveable from 'vue3-moveable';
import Moveable from 'moveable';
import { parse } from 'vue/compiler-sfc';
import { scale } from 'echarts/types/src/scale/helper.js';
export default defineComponent({
components:{
},
@@ -51,7 +49,7 @@ export default defineComponent({
type:Boolean,
}
},
emits:['canvasReload','addSketch','modelOnLoad'],
emits:['addSketch'],
setup(props,{emit}) {
const {t} = useI18n()
const store = useStore();
@@ -75,10 +73,7 @@ export default defineComponent({
imgDom:null as any,
direction:'',
})
watch(()=>selectItem.selectDetail,(newValue,oldValue)=>{
if(!newValue && newValue?.id == oldValue?.id)return
selectItem.imgDomIndex = detailData.frontBack.front.findIndex((item:any)=>item.id == newValue.id)
},{immediate: true,})
watch(()=>detailData.frontBack?.body?.path,(newVal)=>{
setPrintSize()
})
@@ -99,7 +94,6 @@ export default defineComponent({
if(entries[0].contentRect.width == 0)return
detailData.observerWH.width = Math.floor(entries[0].contentRect.width)
detailData.observerWH.height = Math.floor(entries[0].contentRect.height)
console.log(detailData.observerWH)
})
detailData.observer.observe(dom)
@@ -131,6 +125,7 @@ export default defineComponent({
}
for (const key in detailData.frontBack.back[index].style) {
if(key == 'zIndex')return
if(key == 'transform')return
let value = detailData.frontBack.back[index].style[key]
if(typeof value !== 'number'){
value = value.replace('px','')
@@ -142,7 +137,7 @@ export default defineComponent({
}
});
setTimeout(() => {
emit('modelOnLoad')
initMoveableForSelected()
},500);
};
img.src = detailData.frontBack?.body?.path;
@@ -150,203 +145,280 @@ export default defineComponent({
}
const getDetailListDom = reactive({
libraryList:null as any,
moveableContainer:null as any,//控件层
})
//设置尺寸
const itemSizeMousedown = (direction:any,event:any)=>{
selectItem.direction = direction
selectItem.imgDom = document.getElementsByClassName('molepositon')[0].getElementsByClassName("detail_modal_item_front")[selectItem.imgDomIndex]
detailData.frontBack.front[selectItem.imgDomIndex].designOpenrtionBtn = true
let imgDomWH = selectItem.imgDom.getBoundingClientRect()
let li = (document.getElementsByClassName('molepositon')[0].getElementsByClassName("designOpenrtion_btn_top")[0] as any).offsetWidth/2
if(selectItem.direction == 'right' || selectItem.direction == 'bottom'){
detailData.frontBack.front[selectItem.imgDomIndex].centers.left = imgDomWH.x+event.offsetX-li
detailData.frontBack.front[selectItem.imgDomIndex].centers.top = imgDomWH.y+event.offsetY-li
}else{
detailData.frontBack.front[selectItem.imgDomIndex].centers.left = imgDomWH.x+event.offsetX+imgDomWH.width-li
detailData.frontBack.front[selectItem.imgDomIndex].centers.top = imgDomWH.y+event.offsetY+imgDomWH.height-li
const elementRefs = ref([]) as any;
const moveableInstance = ref(null) as any;
const setElementRef = (el, index) => {
elementRefs.value[index] = el;
};
const updateRect = ()=>{
setTimeout(() => {
moveableInstance.value.updateRect()
}, 500);
}
const initMoveableForSelected = () => {
// 销毁旧的实例
if(selectItem.imgDomIndex == -1)return
if (moveableInstance.value) {
moveableInstance.value.destroy();
}
document.addEventListener('mousemove', sizeMouseMove);
document.addEventListener('touchmove', sizeTouchmove);
document.addEventListener('mouseup', sizeMouseup);
document.addEventListener('touchend', sizeMouseup);
}
const sizeMouseMove = (event:any)=>{
let e = getMousePosition(event,false)
sizeMouseMoveOperation(e)
}
const sizeTouchmove = (event:any)=>{
let e = getMousePosition(event,true)
sizeMouseMoveOperation(e)
}
const sizeMouseup = (e:any)=>{
detailData.frontBack.front[selectItem.imgDomIndex].style={
right:'auto',
left:selectItem.imgDom.offsetLeft+'px',
bottom:'auto',
top:selectItem.imgDom.offsetTop+'px',
height:selectItem.imgDom.offsetHeight+'px',
width:selectItem.imgDom.offsetWidth+'px',
zIndex:selectItem.imgDom.style.zIndex,
// zIndex:selectItem.printZIndex
const selectedEl = elementRefs.value[selectItem.imgDomIndex];
if (!selectedEl) return;
if(!selectedEl.style.left)return
moveableInstance.value = new Moveable(getDetailListDom.moveableContainer, {
target: selectedEl,
draggable: true,
scalable: true,
rotatable: true,
keepRatio: false, // 等比缩放
snappable: true,
snapThreshold: 5,
edge: false,
});
let startPosition = {//记录初始位置
left: 0,
top: 0,
}
// detailData.frontBack.back[selectItem.imgDomIndex].style.zIndex = selectItem.printZIndex
document.removeEventListener('mousemove',sizeMouseMove)
document.removeEventListener('touchmove',sizeTouchmove)
document.removeEventListener('mouseup',sizeMouseup)
document.removeEventListener('touchend',sizeMouseup)
//鼠标抬起
setRevocation()
}
let isMove = false//表示是否移动,是否需要在鼠标抬起的时候保存数据
moveableInstance.value.on('scaleStart', ({ target, direction }) => {
const frontStyle = detailData.frontBack.front[selectItem.imgDomIndex];
if (!frontStyle.mirror){
let scaleX = frontStyle.style.transform.match(/scaleX\(([-\d.]+)\)/);
let scaleY = frontStyle.style.transform.match(/scaleY\(([-\d.]+)\)/);
frontStyle.mirror = { x: scaleX[1] == '-1' ? true : false, y: scaleY[1] == '-1' ? true : false };
}
});
moveableInstance.value.on('rotateStart', ({ target, direction }) => {
const frontStyle = detailData.frontBack.front[selectItem.imgDomIndex];
if (!frontStyle.mirror){
let scaleX = frontStyle.style.transform.match(/scaleX\(([-\d.]+)\)/);
let scaleY = frontStyle.style.transform.match(/scaleY\(([-\d.]+)\)/);
frontStyle.mirror = { x: scaleX[1] == '-1' ? true : false, y: scaleY[1] == '-1' ? true : false };
}
});
moveableInstance.value.on('dragStart', ({ target, clientX, clientY }) => {
startPosition = {
left:parseFloat(selectedEl.style.left.replace('px','')) || 0,
top:parseFloat(selectedEl.style.top.replace('px','')) || 0,
}
});
// 拖拽
moveableInstance.value.on('drag', ({ target, beforeTranslate }) => {
let x = startPosition.left + beforeTranslate[0]
let y = startPosition.top + beforeTranslate[1]
detailData.frontBack.front[selectItem.imgDomIndex].style.left = x + 'px'
detailData.frontBack.front[selectItem.imgDomIndex].style.top = y + 'px'
});
const updateElementTransform = (element, rotateDeg = null) => {
const currentTransform = element.style?.transform || '';
// 1. 提取当前的所有rotate
const currentRotates = (currentTransform.match(/rotate[XYZ]?\([^)]+\)/g) || []);
// 2. 获取除镜像和rotate外的其他所有变换
let otherTransforms = currentTransform
.replace(/scaleX\(-1\)|scaleY\(-1\)/g, '') // 移除镜像
.replace(/rotate[XYZ]?\([^)]+\)/g, '') // 移除rotate
.replace(/\s+/g, ' ')
.trim();
// 3. 构建新transform
const transforms = [];
// 镜像部分
if (element.mirror.x) transforms.push('scaleX(-1)');
if (element.mirror.y) transforms.push('scaleY(-1)');
if (otherTransforms) transforms.push(otherTransforms);
if (rotateDeg !== null) {
transforms.push(`rotate(${rotateDeg}deg)`);
} else if (currentRotates.length > 0) {
transforms.push(...currentRotates);
}
element.style.transform = transforms.join(' ').trim();
};
moveableInstance.value.on('scale', ({ target, delta, direction }) => {
const frontStyle = detailData.frontBack.front[selectItem.imgDomIndex];
if (!frontStyle.mirror) frontStyle.mirror = { x: false, y: false };
const width = parseFloat(frontStyle.style.width);
const height = parseFloat(frontStyle.style.height);
let left = parseFloat(frontStyle.style.left) || 0;
let top = parseFloat(frontStyle.style.top) || 0;
let rotation = 0;
// 获取原始比例
const originalRatio = width / height;
if (frontStyle.style.transform) {
const transform = frontStyle.style.transform;
const match = transform.match(/rotate\(([-\d.]+)deg\)/);
if (match) {
rotation = parseFloat(match[1]);
}
}
// 根据旋转角度重新计算控制点的方向
function getAdjustedCorner(originalCorner, rotationAngle) {
const angleRad = rotationAngle * (Math.PI / 180);
const cosA = Math.cos(angleRad);
const sinA = Math.sin(angleRad);
const newX = originalCorner.x * cosA - originalCorner.y * sinA;
const newY = originalCorner.x * sinA + originalCorner.y * cosA;
const threshold = 0.5;
return {
x: Math.abs(newX) > threshold ? (newX > 0 ? 1 : -1) : 0,
y: Math.abs(newY) > threshold ? (newY > 0 ? 1 : -1) : 0
};
}
if (rotation !== 0) {
direction = getAdjustedCorner({x: direction[0], y: direction[1]}, rotation);
direction = [direction.x, direction.y];
}
// 判断是否是对角线方向(需要等比缩放)
const isDiagonal = Math.abs(direction[0]) === 1 && Math.abs(direction[1]) === 1;
// 处理轴缩放,包含镜像翻转逻辑
const processAxis = (axis, val, deltaVal, dir, originalPosition, originalSize, keepRatio = false, otherAxisResult = null) => {
let newVal = val * deltaVal;
const mirrorKey = axis === 'width' ? 'x' : 'y';
const isWidth = axis === 'width';
// 检查是否需要镜像翻转当值小于等于0时
if (newVal <= 0) {
frontStyle.mirror[mirrorKey] = !frontStyle.mirror[mirrorKey];
newVal = Math.abs(newVal);
updateElementTransform(frontStyle);
// 镜像翻转后,位置需要根据原始锚点调整
if (dir === -1) {
// 从左上/右上缩放时,位置保持不变
return {
newVal,
adjustPos: originalPosition,
shouldFlip: true
};
} else {
// 从左下/右下缩放时,位置需要调整
const newPosition = originalPosition + (originalSize - newVal) * (frontStyle.mirror[mirrorKey] ? -1 : 1);
return {
newVal,
adjustPos: newPosition,
shouldFlip: true
};
}
}
const shouldMove = (!frontStyle.mirror[mirrorKey] && dir === -1) ||
(frontStyle.mirror[mirrorKey] && dir === 1);
if (keepRatio && otherAxisResult) {
newVal = isWidth ?
otherAxisResult.newVal * originalRatio :
otherAxisResult.newVal / originalRatio;
}
let adjustPos;
if (shouldMove) {
adjustPos = originalPosition - (newVal - originalSize);
} else {
adjustPos = originalPosition;
}
return {
newVal,
adjustPos,
shouldFlip: false
};
};
// 自由缩放
const widthResult = processAxis('width', width, delta[0], direction[0], left, width);
const heightResult = processAxis('height', height, delta[1], direction[1], top, height);
frontStyle.style.left = widthResult.adjustPos + 'px';
frontStyle.style.top = heightResult.adjustPos + 'px';
frontStyle.style.width = widthResult.newVal + 'px';
frontStyle.style.height = heightResult.newVal + 'px';
// }
});
// 旋转
moveableInstance.value.on('rotate', ({ target, beforeRotate }) => {
let frontStyle = detailData.frontBack.front[selectItem.imgDomIndex];
// 确保镜像状态存在
if (!frontStyle.mirror) frontStyle.mirror = { x: false, y: false };
const { x: isMirroredX, y: isMirroredY } = frontStyle.mirror;
// 计算实际旋转角度
let actualRotate = beforeRotate;
// 关键逻辑当镜像状态不同时一个true一个false旋转方向反转
if (isMirroredX !== isMirroredY) {
actualRotate = -beforeRotate;
}
// 确保角度在 0-360 度范围内
actualRotate = actualRotate % 360;
// 如果角度为负数,转换为正数
if (actualRotate < 0) {
actualRotate += 360;
}
// 确保角度在 [0, 360) 范围内
actualRotate = ((actualRotate % 360) + 360) % 360;
updateElementTransform(frontStyle, actualRotate.toFixed(2));
});
// 调整大小
moveableInstance.value.on('resize', ({ target, width, height }) => {
// console.log(width, height)
// detailData.frontBack.front[selectItem.imgDomIndex].style.width = width
// detailData.frontBack.front[selectItem.imgDomIndex].style.height = height
});
moveableInstance.value.on('dragEnd', ({ target, clientX, clientY }) => {
startPosition = {
left:0,
top:0,
}
upDataDetail()
});
moveableInstance.value.on('scaleEnd', () => {
upDataDetail()
});
moveableInstance.value.on('rotateEnd', () => {
upDataDetail()
});
};
watch(()=>selectItem.selectDetail,(newValue,oldValue)=>{
if(!newValue && newValue?.id == oldValue?.id)return
selectItem.imgDomIndex = detailData.frontBack.front.findIndex((item:any)=>item.id == newValue.id)
initMoveableForSelected()
},{immediate: true,})
const setRevocation = ()=>{
if(!isMove)return
isMove = false
let frontBack = JSON.parse(JSON.stringify(detailData.frontBack))
console.log(frontBack)
let revocation:any = JSON.parse((sessionStorage.getItem("revocation") as any))
revocation.push({designData:null,position:frontBack})
sessionStorage.setItem('revocation', JSON.stringify(revocation));
}
const sizeMouseMoveOperation = (e:any)=> {
isMove = true
let imgDomWH = selectItem.imgDom.getBoundingClientRect()
let parentNode =selectItem.imgDom.parentNode
let width = imgDomWH.width
let height = imgDomWH.height
let w,h
let num = height/width
//判断移动四个边
if(selectItem.direction == 'right'){
w = (e.clientX - detailData.frontBack.front[selectItem.imgDomIndex].centers.left)
h = (e.clientX - detailData.frontBack.front[selectItem.imgDomIndex].centers.left)*num
width = w+'px'
// height = w*num+'px'
}else if(selectItem.direction == 'top'){
num = width/height
detailData.frontBack.front[selectItem.imgDomIndex].style.top = 'auto'
// this.printStyleList[selectItem.imgDomIndex].style.left = 'auto'
detailData.frontBack.front[selectItem.imgDomIndex].style.bottom = parentNode.offsetHeight -imgDomWH.height - selectItem.imgDom.offsetTop+'px'
w = (e.clientX - detailData.frontBack.front[selectItem.imgDomIndex].centers.left)*num
h = (detailData.frontBack.front[selectItem.imgDomIndex].centers.top - e.clientY)
height = h+'px'
// width = h*num+'px'
}else if(selectItem.direction == 'bottom'){
num = width/height
h = (e.clientY - detailData.frontBack.front[selectItem.imgDomIndex].centers.top)
height = h+'px'
// width = h*num+'px'
}else if(selectItem.direction == 'left'){
detailData.frontBack.front[selectItem.imgDomIndex].style.left = 'auto'
detailData.frontBack.front[selectItem.imgDomIndex].style.right = parentNode.offsetWidth -imgDomWH.width - selectItem.imgDom.offsetLeft+'px'
w = (detailData.frontBack.front[selectItem.imgDomIndex].centers.left - e.clientX)
width = w+'px'
// height = w*num+'px'
}
//判断尺寸是否到边
detailData.frontBack.front[selectItem.imgDomIndex].style.width = width
detailData.frontBack.front[selectItem.imgDomIndex].style.height = height
}
// 设置移动
const mouseMove = (event:any)=>{
let e = getMousePosition(event,false)
mouseMoveOperation(e)
}
const touchmove=(event:any)=>{
let e = getMousePosition(event,true)
mouseMoveOperation(e)
}
const mouseup = (e:any)=> {
document.removeEventListener('mousemove',mouseMove)
document.removeEventListener('touchmove',touchmove)
document.removeEventListener('mouseup',mouseup)
document.removeEventListener('touchend',mouseup)
///鼠标抬起
const upDataDetail = async ()=>{
//同步到selectDetail数据中
// getDetailListData.designDetail
let {scale,offset,priority,transpose,rotate,position,imageSize} = await getSubmitData(selectItem.selectDetail)
selectItem.selectDetail.layersObject[0].scale = scale
selectItem.selectDetail.layersObject[1].scale = scale
selectItem.selectDetail.layersObject[0].offset = offset
selectItem.selectDetail.layersObject[1].offset = offset
selectItem.selectDetail.layersObject[0].priority = priority
selectItem.selectDetail.layersObject[1].priority = priority
selectItem.selectDetail.layersObject[0].rotate = rotate
selectItem.selectDetail.layersObject[1].rotate = rotate
selectItem.selectDetail.layersObject[0].transpose = transpose
selectItem.selectDetail.layersObject[1].transpose = transpose
selectItem.selectDetail.layersObject[0].position = position
selectItem.selectDetail.layersObject[1].position = position
selectItem.selectDetail.layersObject[0].imageSize = imageSize
selectItem.selectDetail.layersObject[1].imageSize = imageSize
setRevocation()
}
const mouseMoveOperation = (e:any)=>{
isMove = true
let imgDomWH = selectItem.imgDom.getBoundingClientRect()
let parentNode = document.getElementsByClassName('molepositon')[0].getElementsByClassName("designOpenrtion_imgMask")[0].getBoundingClientRect()
let x = (e.clientX - detailData.frontBack.front[selectItem.imgDomIndex].centers.left)+'px'
let y = ( e.clientY - detailData.frontBack.front[selectItem.imgDomIndex].centers.top)+'px'
detailData.frontBack.front[selectItem.imgDomIndex].style.left = x
detailData.frontBack.front[selectItem.imgDomIndex].style.top = y
}
const clothesOpenActive = (index:number)=>{
// this.designItemDetail.clothes.forEach((item)=>{
// item.clothesOpenItem = false
// })
// if(index != -1){
// this.designItemDetail.clothes[index].clothesOpenItem = true
// }
}
const itemMoveMousedown = async (index:any,e:any)=>{
if(detailData.selectDetail.id != detailData.frontBack.front[index].id)return
let isModal = false
await new Promise((resolve, reject) => {
// if(
// detailData.isEditPattern.value &&
// selectItem.selectDetail?.id &&
// (detailData.frontBack.front[index].id != selectItem.selectDetail.id)
// ){
// isModal = true
// Modal.confirm({
// title: t('collectionModal.jsContent2'),
// icon: createVNode(ExclamationCircleOutlined),
// okText: 'Yes',
// cancelText: 'No',
// mask:false,
// centered:true,
// onOk() {
// resolve(true)
// isOpen = true
// },
// onCancel(){
// resolve(false)
// isOpen = false
// }
// });
// }else{
// if(detailData.frontBack.front[index].id != selectItem.selectDetail.id){
// isOpen = true
// }
// resolve(true)
// isModal = false
// }
resolve(true)
}).then((rv)=>{
})
// emit('canvasReload')
// store.commit('DesignDetail/setDesignColthes',detailData.frontBack.front[index].id)
if(isModal)return
store.commit('DesignDetail/setDesignColthes',detailData.frontBack.front[index].id)
selectItem.imgDomIndex = index
detailData.frontBack.front.forEach((v:any)=>{
v.designOpenrtionBtn = false
})
clothesOpenActive(index)
let event = e||window.event
selectItem.imgDom = document.getElementsByClassName('molepositon')[0].getElementsByClassName("detail_modal_item_front")[selectItem.imgDomIndex]
detailData.frontBack.front[index].designOpenrtionBtn = true
// detailData.frontBack.front[index].style.zIndex = selectItem.printZIndex++
// detailData.frontBack.back[index].style.zIndex = selectItem.printZIndex
let imgDomWH = selectItem.imgDom.getBoundingClientRect()
let left = Number(detailData.frontBack.front[index].style.left.replace(/px/g,''))
let top = Number(detailData.frontBack.front[index].style.top.replace(/px/g,''))
detailData.frontBack.front[index].centers.left = imgDomWH.x+event.offsetX-left
detailData.frontBack.front[index].centers.top = imgDomWH.y+event.offsetY-top
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchmove', touchmove);
document.addEventListener('mouseup', mouseup);
document.addEventListener('touchend', mouseup);
}
const sort = (arr:any)=>{
arr.sort((a:any, b:any) => {
var a_num = a.style.zIndex;
@@ -355,9 +427,9 @@ export default defineComponent({
});
return arr
}
const getSubmitData = (value:any,isNoComputed)=>{
const getSubmitData = async (value:any)=>{
let parentNode = document.getElementsByClassName('molepositon')[0].getElementsByClassName("designOpenrtion_imgMask")[0].getBoundingClientRect()
if(!detailData.frontBack?.body?.layersObject?.[0]?.imageSize || isNoComputed){
if(!detailData.frontBack?.body?.layersObject?.[0]?.imageSize){
return{
scale:value.layersObject[0].scale,
offset:value.layersObject[0].offset,
@@ -365,6 +437,17 @@ export default defineComponent({
}
}
let ratio = detailData.frontBack.body.layersObject[0].imageSize[0]/parentNode.width
let scale = 0
let dom:any = document.querySelector('.molepositon .perview_img')
const img = new Image();
img.src = detailData.frontBack?.body?.path;
await new Promise<void>((resolve, reject) => {
img.onload = () => {
scale = dom.parentNode.offsetWidth / img.width
resolve()
};
})
// let arr:any = sort(detailData.frontBack.front)
let arr:any = sort(JSON.parse(JSON.stringify(detailData.frontBack.front)))
let num = 10
@@ -377,6 +460,8 @@ export default defineComponent({
priority:'',
maskUrl:'',
maskMinioUrl:'',
position:null,
imageSize:null,
}
let state = false
for (let index = 0; index < arr.length; index++) {
@@ -384,13 +469,27 @@ export default defineComponent({
state = true
let y = ((arr[index]?.style?.top.replace(/px/g,'')*ratio).toFixed(0) as any - arr[index]?.position[0])
let x = ((arr[index]?.style?.left.replace(/px/g,'')*ratio).toFixed(0) as any - arr[index]?.position[1])
let positionX = Number((arr[index]?.style?.left.replace(/px/g,'')/scale)).toFixed(2)
let positionY = Number((arr[index]?.style?.top.replace(/px/g,'')/scale)).toFixed(2)
let imageSizeW = Number((arr[index]?.style?.width.replace(/px/g,'')/scale)).toFixed(2)
let imageSizeH = Number((arr[index]?.style?.height.replace(/px/g,'')/scale)).toFixed(2)
let scaleWidth = arr[index]?.imageSize?Number(((arr[index]?.style?.width.replace(/px/g,'')*ratio)/(arr[index]?.imageSize[0]/arr[index].scale[0])).toFixed(2)):1
let scaleHeight = arr[index]?.imageSize?Number(((arr[index]?.style?.height.replace(/px/g,'')*ratio)/(arr[index]?.imageSize[1]/arr[index].scale[1])).toFixed(2)):1
// let widthScale = (arr[index].style.width.replace(/px/g,'')/arr[index].style.height.replace(/px/g,'')).toFixed(2)
let transformStr = arr[index]?.style?.transform
let scaleX = transformStr.match(/scaleX\(([-\d.]+)\)/)
let scaleY = transformStr.match(/scaleY\(([-\d.]+)\)/)
let rotate = transformStr.match(/rotate\(([-\d.]+)deg\)/);
data.transpose = [parseFloat(scaleX?.[1] || 1),parseFloat(scaleY?.[1] || 1)]
data.rotate = parseFloat(rotate?.[1] || 0)
data.scale = [scaleWidth,scaleHeight]
let top = y == 0 ? value.layersObject[0].offset[1]:y+value.layersObject[0].offset[1]
let left = x == 0 ? value.layersObject[0].offset[0]:x+value.layersObject[0].offset[0]
data.offset = [left?left:0,top?top:0]
data.position = [positionY,positionX]
data.imageSize = [imageSizeW,imageSizeH]
// data.offset = [left?left:0,top?top:0]
data.maskUrl = arr[index].maskUrl
data.maskMinioUrl = arr[index].maskMinioUrl
@@ -410,15 +509,6 @@ export default defineComponent({
}
const deleteNav = ()=>{
}
const setpitch = (item:any,index:any)=>{
detailData.frontBack.front.forEach((v:any)=>{
v.designOpenrtionBtn = false
})
detailData.frontBack.front[index].designOpenrtionBtn = true
// detailData.frontBack.front[index].style.zIndex = selectItem.printZIndex++
// detailData.frontBack.back[index].style.zIndex = selectItem.printZIndex
clothesOpenActive(index)
}
const updataPosition = ()=>{
let url = detailData.frontBack?.body?.path
@@ -431,10 +521,12 @@ export default defineComponent({
detailData.frontBack.front.forEach((item:any,index:number) => {
for (const key in item.style) {
if(key == 'zIndex')return
if(key == 'transform')return
item.style[key] = item.style[key]*sacle+'px'
}
for (const key in detailData.frontBack.back[index].style) {
if(key == 'zIndex')return
if(key == 'transform')return
detailData.frontBack.back[index].style[key] = detailData.frontBack.back[index].style[key]*sacle+'px'
}
});
@@ -445,20 +537,22 @@ export default defineComponent({
if (detailData.observer) {
detailData.observer.disconnect()
}
if (moveableInstance.value) {
moveableInstance.value.destroy();
}
})
return{
...toRefs(detailData),
...toRefs(selectItem),
...toRefs(getDetailListDom),
setElementRef,
setPrintSize,
itemSizeMousedown,
itemMoveMousedown,
deleteNav,
setpitch,
getSubmitData,
getMousePosition,
updataPosition,
updateRect,
}
},
@@ -541,6 +635,17 @@ export default defineComponent({
}
}
}
.moveableContainer{
:deep(.moveable-origin){
opacity: 0;
}
:deep(.moveable-control){
border-radius: 0;
}
:deep(.moveable-rotation-control){
border-radius: 50%;
}
}
> .designOpenrtion_imgMask{
width: auto;
height: auto;
@@ -561,10 +666,15 @@ export default defineComponent({
position: absolute;
top: 0;
}
.detail_modal_item_front,.designOpenrtion_print{
z-index: 2;
height: 100%;
width: 100%;
pointer-events: none;
&.active{
pointer-events: auto;
}
img{
width: 100%;
// height: ;
@@ -583,94 +693,6 @@ export default defineComponent({
.designOpenrtion_print{
z-index: 1 !important;
}
> .designOpenrtion_btnBox{
z-index: 99;
width: 100%;
height: 100%;
left: 0;
ul{
list-style: none;
// width: 100%;
// height: 100%;
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
border: 2px solid rgb(20, 188, 255);
padding: 0;
-webkit-user-drag: none;
user-select:none;
opacity: 0;
margin: 0;
li{
cursor: pointer;
// border-radius: 50%;
width: calc(2rem*1.2);
height: calc(2rem*1.2);
background-color: rgb(20, 188, 255);
position: absolute;
pointer-events: none;
}
&.active{
opacity: 1;
z-index: 999 !important;
li{
pointer-events: auto;
}
}
.designOpenrtion_btn_top,.designOpenrtion_btn_bottom{
left: 50%;
transform: translate(-50%,-50%) ;
cursor: n-resize;
}
.designOpenrtion_btn_top{
top: 0;
}
.designOpenrtion_btn_bottom{
top: 100%;
}
.designOpenrtion_btn_left,.designOpenrtion_btn_right{
top: 50%;
transform: translate(-50%,-50%) ;
cursor: e-resize;
}
.designOpenrtion_btn_left{
left: 0;
}
.designOpenrtion_btn_right{
left: 100%;
}
.designOpenrtion_rotote{
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 0;
height: 0;
}
.designOpenrtion_rotote::after{
position: absolute;
content: "";
background-color: #14bcff;
width: 2px;
height: 30px;
left: 50%;
bottom: 0;
transform: translateX(-50%);
}
.designOpenrtion_rotote::before{
position: absolute;
content: "";
background-color: #14bcff;
top: calc(50% - 30px);
left: 50%;
transform: translate(-50%,-50%) ;
width: calc(1.5rem*1.2);
height: calc(1.5rem*1.2);
border-radius: 50%;
}
}
}
}
}
</style>

View File

@@ -1074,10 +1074,10 @@ export default defineComponent({
const scrollTop = textarea.scrollTop
// 2. 计算单行高度
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 20 // 默认20px
const lineHeight:any = parseInt(getComputedStyle(textarea).lineHeight) || 20 // 默认20px
// 3. 重置高度为1行
textarea.style.height = lineHeight + 'px'
textarea.style.height = (parseInt(lineHeight)+4) + 'px'
// 4. 计算实际需要的高度
const newHeight = Math.max(lineHeight, textarea.scrollHeight)

View File

@@ -476,9 +476,9 @@ export default defineComponent({
if(!textarea)return
const scrollTop = textarea.scrollTop;
// 2. 计算单行高度
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 20; // 默认20px
const lineHeight:any = parseInt(getComputedStyle(textarea).lineHeight) || 20; // 默认20px
// 3. 重置高度为1行
textarea.style.height = lineHeight + 'px';
textarea.style.height = (parseInt(lineHeight)+4) + 'px'
// 4. 计算实际需要的高度
const newHeight = Math.max(lineHeight, textarea.scrollHeight);
textarea.style.height = newHeight + 'px';

View File

@@ -132,7 +132,25 @@ export default defineComponent({
url:rv.url,
designType:props.item.resData.designType || props.item.designType,
}
props.list.unshift(rv)
rv.type_ = {
type1: "material",
type2: props.level1Type
}
if(props.level1Type == 'Printboard'){
let list = props.list.filter((v:any)=> v.type_?.type1 == "material")
list.unshift(rv)
store.commit("setPrintboardMaterialFiles", list);
}else if(props.level1Type == 'Sketchboard'){
let list = props.list.filter((v:any)=> v.type_?.type1 == "material")
list.unshift(rv)
store.commit("setSketchboardMaterialFiles", list);
}else if(props.level1Type == 'Moodleboard'){
let list = props.list.filter((v:any)=> v.type_?.type1 == "material")
list.unshift(rv)
store.commit("setMoodleboardMaterialFiles", list);
}else{
props.list.unshift(rv)
}
}
}
).catch(res=>{

View File

@@ -9,7 +9,7 @@
:isBackgroundChangeable="false"
ref="editCanvas"></editCanvas>
</div>
<div class="btn">
<div class="btn" v-if="btnShow">
<div class="gallery_btn" @click="canvasSave" style="width: min-content;margin-top: auto;">{{ $t('exportModel.Save') }}</div>
<div class="gallery_btn" @click="exportElement">{{ $t('exportModel.Export') }}</div>
</div>
@@ -46,7 +46,11 @@ export default defineComponent({
isSubmitCanvasJSON:{
type:Boolean,
default:false
}
},
btnShow:{
type:Boolean,
default:true
},
},
emits:['submitBase64Data','canvasChangeGetJSON'],
setup(props,{emit}) {
@@ -99,6 +103,9 @@ export default defineComponent({
}
}
const submitBase64Data = (base64Data)=>{
return dataDom.editCanvas.exportImage({isContainBg:true,isContainFixed:true,isCropByBg:true})
}
const exportElement = ()=>{
dataDom.editCanvas.exportImage({isContainBg:true,isContainFixed:false,isCropByBg:true}).then((rv)=>{
downloadBase64Image(rv,'canvas')
@@ -174,6 +181,7 @@ export default defineComponent({
canvasInit,
exportElement,
changeCanvas,
submitBase64Data,
};
},
data(prop) {

View File

@@ -1497,6 +1497,7 @@ export default {
CompositeColorTip: '颜色:保留原图像饱和度,改变新图像颜色',
CompositeLuminosity: '亮度',
CompositeLuminosityTip: '亮度:保留原图像颜色,改变新图像亮度',
GarmentPartSelector: '服装部件选取',
},
speedList: {
High: '高级',

View File

@@ -1547,7 +1547,8 @@ export default {
'Color: Preserve the original image saturation and change the color of the new image',
CompositeLuminosity: 'Luminosity',
CompositeLuminosityTip:
'Luminosity: Preserve the original image color and change the luminosity of the new image'
'Luminosity: Preserve the original image color and change the luminosity of the new image',
GarmentPartSelector: 'Garment Part Selector',
},
speedList: {
High: 'High',

View File

@@ -1,6 +1,7 @@
import {Module} from 'vuex'
import {RootState} from '../index'
import { forEach } from 'jszip'
import { transform } from 'typescript'
interface DesignDetail{
designDetail:any,
@@ -53,12 +54,32 @@ const DesignDetail : Module<DesignDetail,RootState> = {
data.clothes.forEach((v:any,index:any)=>{
// if(!v?.partialDesignDTO){
// }
function isJSONString(str) {
try {
JSON.parse(str);
return true; // 解析成功是有效的JSON字符串
} catch (e) {
return false; // 解析失败不是有效的JSON字符串
}
}
v?.printObject?.prints?.forEach((item:any) => {
console.log(item.object)
if(isJSONString(item.object)){
item.object = JSON.parse(item.object)
}
});
v?.trims?.prints?.forEach((item:any) => {
if(isJSONString(item.object)){
item.object = JSON.parse(item.object)
}
});
for (let i = v.layersObject.length-1; i >= 0; i--) {
v.layersObject[i].style = {
top:v.layersObject[i].position?.[0],
left:v.layersObject[i].position?.[1],
width:v.layersObject[i].imageSize?.[0],
height:v.layersObject[i].imageSize?.[1],
transform:`rotate(${v.layersObject[i]?.rotate || 0}deg) scaleX(${v.layersObject[i].transpose?.[0] || 1}) scaleY(${v.layersObject[i].transpose?.[1] || 1})`,
}
v.layersObject[i].centers={
left:0,
@@ -69,16 +90,12 @@ const DesignDetail : Module<DesignDetail,RootState> = {
v.layersObject[i].designOpenrtionBtn = false
if(v.layersObject[i].imageCategory.indexOf("back") == -1){
front[index] = v.layersObject[i]
// front[index].style.zIndex = v.priority
front[index].style.zIndex = v.priority + 10
front[index].id = v.id
front[index].undividedLayer = v.undividedLayer
front[index].undividedLayerWithSinglePrint = v?.undividedLayerWithSinglePrint
}else{
back[index] = v.layersObject[i]
back[index].style.zIndex = v.priority
back[index].id = v.id
back[index].undividedLayer = v.undividedLayer
back[index].undividedLayerWithSinglePrint = v?.undividedLayerWithSinglePrint
// back[index].style.zIndex = backIndex==0?v.layersObject[i]:backIndex++
}
if(state.printZIndex < v.priority){
@@ -198,8 +215,8 @@ const DesignDetail : Module<DesignDetail,RootState> = {
printObject:null,
trims:null,
type:null,
undividedLayer:null,
undividedLayerWithSinglePrint:null,
transpose:[1,1],
rotate:0,
}
if(!state.currentDetailType)state.currentDetailType = 'sketch'
state.designDetail.clothes.forEach((item:any) => {
@@ -209,6 +226,28 @@ const DesignDetail : Module<DesignDetail,RootState> = {
});
state.selectDetail = data
},
canvasPreviewUpdata(state,{type,callBack}){
console.log(state.selectDetail,type)
// state.selectDetail.newDetail?.print?.forEach((item:any) => {
// state.selectDetail.printObject.prints = []
// state.selectDetail.printObject.push({
// })
// });
if(type == 'print' || type == 'all')state.selectDetail.printObject.prints = state.selectDetail.newDetail?.print
if(type == 'color' || type == 'all')state.selectDetail.color = {
...state.selectDetail.newDetail?.color,
...state.selectDetail.newDetail?.color?.rgba,
...state.selectDetail.newDetail?.color?.hsv,
}
console.log(state.selectDetail,type,state.selectDetail.newDetail)
if(type == 'element' || type == 'all')state.selectDetail.trims.prints = state.selectDetail.newDetail?.element
if(type == 'all'){
state.selectDetail.newDetail = {}
}else{
state.selectDetail.newDetail[type] = null
}
callBack()
},
async setPraeview(state,value){//preview
let data = value?.rv || value
let currentType = value?.currentType
@@ -228,12 +267,9 @@ const DesignDetail : Module<DesignDetail,RootState> = {
}else{
id_ = item.id
}
console.log(id_)
let el:any = document.querySelector('.molepositon .perview_img')
await new Promise((resolve, reject) => {
if(!state.frontBack?.body?.path){
state.frontBack.front[0].undividedLayer = value.rv.clothes[0].undividedLayer
state.frontBack.front[0].undividedLayerWithSinglePrint = value.rv.clothes[0]?.undividedLayerWithSinglePrint
resolve('')
}
const img = new Image();
@@ -252,7 +288,8 @@ const DesignDetail : Module<DesignDetail,RootState> = {
left:item.layersObject[i].position?.[1] * scale + 'px',
width:item.layersObject[i].imageSize?.[0] * scale + 'px',
height:item.layersObject[i].imageSize?.[1] * scale + 'px',
zIndex:v?.style?.zIndex?v.style.zIndex:v.priority?v.priority:state.frontBack.front.length
zIndex:v?.style?.zIndex?v.style.zIndex:v.priority?v.priority:state.frontBack.front.length,
transform:`rotate(${item.layersObject?.[i]?.rotate || 0}deg) scaleX(${item.layersObject[i].transpose?.[0] || 1}) scaleY(${item.layersObject[i].transpose?.[1] || 1})`,
}
item.layersObject[i].centers={
left:0,
@@ -263,14 +300,10 @@ const DesignDetail : Module<DesignDetail,RootState> = {
state.frontBack.front[index] = item.layersObject[i]
// state.frontBack.front[index].style.zIndex = v.priority
state.frontBack.front[index].id = item.id
state.frontBack.front[index].undividedLayer = item.undividedLayer
state.frontBack.front[index].undividedLayerWithSinglePrint = item?.undividedLayerWithSinglePrint
}else{
state.frontBack.back[index] = item.layersObject[i]
// state.frontBack.back[index].style.zIndex = v.priority
state.frontBack.back[index].id = item.id
state.frontBack.back[index].undividedLayer = item.undividedLayer
state.frontBack.back[index].undividedLayerWithSinglePrint = item?.undividedLayerWithSinglePrint
}
}
}
@@ -293,6 +326,8 @@ const DesignDetail : Module<DesignDetail,RootState> = {
detailItem.minIOPath = item.minIOPath
detailItem.scale = [1,1]
detailItem.offset = [0,0]
detailItem.transpose = item.transpose || [1,1]
detailItem.rotate = item.rotate || 0
detailItem.printObject = item.printObject
detailItem.trims = item.trims
detailItem.type = item.type
@@ -307,8 +342,6 @@ const DesignDetail : Module<DesignDetail,RootState> = {
},
gradient:item.gradient,
}
detailItem.undividedLayer = item.undividedLayer
detailItem.undividedLayerWithSinglePrint = item?.undividedLayerWithSinglePrint
if(detailItem.newDetail?.sketch?.id && !detailItem.id){
state.designDetail.clothes.push(detailItem)
}
@@ -354,8 +387,6 @@ const DesignDetail : Module<DesignDetail,RootState> = {
// },
// gradient:item.gradient,
// }
// state.selectDetail.undividedLayer = item.undividedLayer
// state.selectDetail.undividedLayerWithSinglePrint = item?.undividedLayerWithSinglePrint
// if(state.selectDetail.newDetail?.sketch?.id && !state.selectDetail.id){
// state.designDetail.clothes.push(state.selectDetail)
// }

View File

@@ -440,7 +440,10 @@ export const Https = {
getHistoryNotification: `/api/message/getHistoryNotification`, //获取历史消息
oneClickRead: `/api/message/oneClickRead`, //全部设为已读
personalHomepage: `/api/account/personalHomepage`, //获取个人主页信息
refreshMinioUrl: `/api/third/party/refreshMinioUrl` //获取可以使用的minio地址
refreshMinioUrl: `/api/third/party/refreshMinioUrl`, //获取可以使用的minio地址
// 画布
segAnything: `/api/python/segAnything`,//分割Anything
},
axiosGet(url, config) {

View File

@@ -618,6 +618,60 @@ function segmentImage(markerImage,fullImage,size){
}
})
}
/**
* 处理PNG图片透明度转白色其他颜色转透明
* @param {string} sketchImage - 原始图片
* @returns {Promise} 处理后的ase64
*/
function sketchToMask(sketchImage) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function() {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
if (a > 0) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
data[i + 3] = 0;
} else {
// 完全透明的像素 -> 纯白色
data[i] = 255;
data[i + 1] = 255;
data[i + 2] = 255;
data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
const base64 = canvas.toDataURL('image/png');
resolve(base64);
} catch (error) {
reject(error);
}
};
img.onerror = function() {
reject(new Error('图片加载失败'));
};
img.src = sketchImage;
});
}
export {
isEmail,
getUploadUrl,
@@ -640,5 +694,6 @@ export {
setGradual,
calculateGradientCoordinate,
segmentImage,
UrlToFile
UrlToFile,
sketchToMask
}