diff --git a/.env.dev b/.env.dev
index 139c8201..a866a5c6 100644
--- a/.env.dev
+++ b/.env.dev
@@ -7,5 +7,7 @@ VITE_APP_BASE_URL = 'https://develop.api.aida.com.hk'
# VITE_APP_BASE_URL = 'https://www.api.aida.com.hk'
# 徐佩
# VITE_APP_BASE_URL = 'http://192.168.31.118:5567'
+# 李天祥
+# VITE_APP_BASE_URL = 'http://192.168.31.82:5567'
# 海波
# VITE_APP_BASE_URL = 'http://192.168.31.34:5567'
diff --git a/.gitignore b/.gitignore
index 61bc8e64..97c229d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ dist.rar
*.sw?
.eslintrc-auto-import.json
components.d.ts
+.cursor
\ No newline at end of file
diff --git a/index.html b/index.html
index 10aa4bac..fd90fbd6 100644
--- a/index.html
+++ b/index.html
@@ -17,6 +17,7 @@
+
diff --git a/package-lock.json b/package-lock.json
index 0e42a09e..c319c9a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 962ae9e0..d83dfe55 100644
--- a/package.json
+++ b/package.json
@@ -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"
diff --git a/public/css/fonts/ARIAL.ttf b/public/css/fonts/ARIAL.ttf
new file mode 100644
index 00000000..8682d946
Binary files /dev/null and b/public/css/fonts/ARIAL.ttf differ
diff --git a/public/css/fonts/ARIALBD.ttf b/public/css/fonts/ARIALBD.ttf
new file mode 100644
index 00000000..a6037e68
Binary files /dev/null and b/public/css/fonts/ARIALBD.ttf differ
diff --git a/public/css/fonts/ArialMdm.ttf b/public/css/fonts/ArialMdm.ttf
new file mode 100644
index 00000000..3222b81e
Binary files /dev/null and b/public/css/fonts/ArialMdm.ttf differ
diff --git a/public/css/fonts/Poppins-Medium.ttf b/public/css/fonts/Poppins-Medium.ttf
new file mode 100644
index 00000000..6bcdcc27
Binary files /dev/null and b/public/css/fonts/Poppins-Medium.ttf differ
diff --git a/public/css/fonts/Poppins-Regular.ttf b/public/css/fonts/Poppins-Regular.ttf
new file mode 100644
index 00000000..9f0c71b7
Binary files /dev/null and b/public/css/fonts/Poppins-Regular.ttf differ
diff --git a/public/css/fonts/Poppins-SemiBold.ttf b/public/css/fonts/Poppins-SemiBold.ttf
new file mode 100644
index 00000000..74c726e3
Binary files /dev/null and b/public/css/fonts/Poppins-SemiBold.ttf differ
diff --git a/public/css/fonts/fontFamily.css b/public/css/fonts/fontFamily.css
new file mode 100644
index 00000000..a2f7501e
--- /dev/null
+++ b/public/css/fonts/fontFamily.css
@@ -0,0 +1,31 @@
+/* 字体定义 */
+@font-face {
+ font-family: 'Arial';
+ src: url('./fonts/ARIAL.ttf') format('ttf');
+}
+
+@font-face {
+ font-family: 'ArialBold';
+ src: url('./fonts/ARIALBD.ttf') format('ttf');
+}
+
+@font-face {
+ font-family: 'ArialMedium';
+ src: url('./fonts/ArialMdm.ttf') format('ttf');
+}
+
+@font-face {
+ font-family: 'Poppins';
+ src: url('./fonts/Poppins-Regular.ttf') format('ttf');
+ font-weight: normal;
+}
+
+@font-face {
+ font-family: 'PoppinsMedium';
+ src: url('./fonts/Poppins-Medium.ttf') format('ttf');
+}
+
+@font-face {
+ font-family: 'PoppinsBold';
+ src: url('./fonts/Poppins-SemiBold.ttf') format('ttf');
+}
diff --git a/src/assets/iconfont2/demo_index.html b/src/assets/iconfont2/demo_index.html
index 86fd4fdc..8f030666 100644
--- a/src/assets/iconfont2/demo_index.html
+++ b/src/assets/iconfont2/demo_index.html
@@ -54,6 +54,24 @@
@@ -96,17 +77,17 @@ import { defineComponent, ref, createVNode, computed, reactive, toRefs, onMounte
import { formatTime } from "@/tool/util";
import { useStore } from "vuex";
import { Https } from "@/tool/https";
+import SelectUser from '@/component/common/SelectUser.vue'
export default defineComponent({
components: {
+ SelectUser
},
setup() {
const store:any = useStore()
let filter:any = reactive({
dataList:[],
tableLoading:false,
- allUserList: computed(()=>{
- return store.state.adminPage.allUserList
- }),
+
allCountry:[]
})
let filterData:any = reactive({
diff --git a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js
index 60fd70f9..35b3759b 100644
--- a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js
@@ -207,7 +207,8 @@ export class BackgroundSizeCommand extends Command {
this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
// 记录原尺寸
- this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
+ this.bgId = this.bgLayer.fabricObject?.id || this.bgLayer.fabricObjects?.[0]?.id;
+ this.backgroundObject = findObjectById(this.canvas, this.bgId).object;
this.oldWidth = this.backgroundObject.width;
this.oldHeight = this.backgroundObject.height;
diff --git a/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js b/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js
index 98eb683a..edd061ac 100644
--- a/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js
@@ -50,7 +50,6 @@ export class FillGroupLayerBackgroundCommand extends Command {
);
this.layer = layer;
this.parent = parent;
- console.log("==========",layer);
if (!layer) return false;
if(!isUndo){
@@ -69,7 +68,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
layer.clippingMask,
this.canvas
);
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({ absolutePositioned: true });
this.newFill = new fabric.Rect({
width: clippingMaskFabricObject.width,
@@ -117,7 +116,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
this.parent.clippingMask,
this.canvas
);
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({ absolutePositioned: true });
this.newFill = new fabric.Rect({
width: clippingMaskFabricObject.width,
@@ -222,7 +221,7 @@ export class FillGroupLayerBackgroundCommand extends Command {
this.parent?.clippingMask,
this.canvas
);
- clipPath.clipPath = null;
+ // clipPath.clipPath = null;
clipPath.set({ absolutePositioned: true });
this.group.clipPath = clipPath;
}
@@ -343,10 +342,6 @@ export class FillGroupLayerBackgroundCommand extends Command {
minTop = Infinity,
maxRight = -Infinity,
maxBottom = -Infinity;
- console.log(
- "计算当前所有对象的边界信息:===>",
- this.originalfabricObjects.length
- );
this.originalfabricObjects?.forEach((obj) => {
const { object } = findObjectById(this.canvas, obj.id) || {};
if (object) {
diff --git a/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js b/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js
index 2ff386fd..9f1daeb2 100644
--- a/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js
@@ -56,7 +56,7 @@ export class FillLayerBackgroundCommand extends Command {
layer.clippingMask,
this.canvas
);
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
// 设置绝对定位
diff --git a/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js b/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js
new file mode 100644
index 00000000..85aa2dd6
--- /dev/null
+++ b/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js
@@ -0,0 +1,331 @@
+import { Command } from "./Command";
+import { findLayerRecursively } from "../utils/layerHelper";
+import { fabric } from "fabric-with-all";
+import {
+ findObjectById,
+ generateId,
+ insertObjectAtZIndex,
+ removeCanvasObjectByObject,
+ createPatternTransform,
+ imageAddGapToCanvas,
+} from "../utils/helper";
+import { restoreFabricObject } from "../utils/objectHelper";
+
+const scale = 0.3;// 默认缩放比例
+
+export const FillSourceToBase64 = (source) => {
+ if (source?.toDataURL) {
+ return source.toDataURL?.();
+ } else if (source?.src) {
+ return source.src;
+ }
+ return source;
+}
+
+/**
+ * 填充图案平铺命令
+ * 填充重复属性:repeat | repeat-x | repeat-y | no-repeat
+ * 默认缩放比例:0.3
+ * 默认偏移量:50%
+ */
+export class FillRepeatCommand extends Command {
+ constructor(options) {
+ super({ name: "填充图案平铺", saveState: true });
+ this.canvas = options.canvas;
+ this.layers = options.layers;
+ this.canvasManager = options.canvasManager;
+ this.layerManager = options.layerManager;
+ this.layerId = options.layerId;
+ this.fillRepeat = options.fillRepeat;
+ this.oldObjects = null;
+ this.oldLocked = null;
+ this.oldIsDisableUnlock = null;
+ }
+
+ async execute() {
+ const { layer } = findLayerRecursively(this.layers.value, this.layerId);
+ if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
+ console.warn("图层不存在或没有 fabric 对象");
+ return false;
+ }
+ const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
+ if (!object || (object.type !== "rect" && object.type !== "image")) {
+ console.warn("当前对象不能平铺", object.type);
+ return false;
+ }
+ this.oldObjects = object;
+ if (this.fillRepeat === "no-repeat") {
+ const fill_ = object.fill_;
+ const image = await new Promise((resolve, reject) => {
+ fabric.Image.fromURL(
+ fill_.source,
+ v => resolve(v),
+ { crossOrigin: "anonymous" }
+ );
+ });
+ image.set({
+ id: object.id,
+ layerId: object.layerId,
+ layerName: object.layerName,
+ ...(fill_.originalInfo || {
+ top: object.top,
+ left: object.left,
+ })
+ });
+ layer.fabricObjects = [image.toObject(["id", "layerId", "layerName"])];
+ this.oldLocked = layer.locked;
+ layer.locked = false;
+
+ this.canvas.add(image);
+ this.canvas.remove(object);
+ } else {
+ const img = await new Promise((resolve, reject) => {
+ if (object.type === "rect") {
+ let source = object.fill.source;
+ resolve(source);
+ } else if (object.type === "image") {
+ const imgElement = object.getElement();
+ // 创建透明 Canvas
+ const tcanvas = document.createElement('canvas');
+ tcanvas.width = imgElement.width;
+ tcanvas.height = imgElement.height;
+ const ctx = tcanvas.getContext('2d');
+ ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
+ ctx.drawImage(imgElement, 0, 0);
+ resolve(tcanvas);
+ }
+ });
+ const fill_ = object.fill_ || {
+ source: FillSourceToBase64(img),
+ gapX: 0,
+ gapY: 0,
+ width: img.width,
+ height: img.height,
+ originalInfo: {
+ top: object.top,
+ left: object.left,
+ scaleX: object.scaleX,
+ scaleY: object.scaleY,
+ width: object.width,
+ height: object.height,
+ }
+ };
+ const fdObject = this.canvasManager.getFixedLayerObject();
+ const bgObject = this.canvasManager.getBackgroundLayerObject();
+ const tObject = fdObject || bgObject;
+ const pattern = new fabric.Pattern({
+ source: img,
+ repeat: this.fillRepeat,
+ patternTransform: object.fill?.hasOwnProperty("patternTransform") ? object.fill.patternTransform : createPatternTransform(scale, 0),
+ offsetX: object.fill?.hasOwnProperty("offsetX") ? object.fill.offsetX : tObject.width / 2, // 水平偏移
+ offsetY: object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : tObject.height / 2, // 垂直偏移
+ });
+ const rect = new fabric.Rect({
+ id: object.id,
+ layerId: object.layerId,
+ layerName: object.layerName,
+ fill_,
+ });
+ layer.fabricObjects = [rect.toObject(["id", "layerId", "layerName"])];
+ this.oldLocked = layer.locked;
+ // this.oldIsDisableUnlock = layer.isDisableUnlock;
+ // layer.isDisableUnlock = true;
+ if (this.oldObjects.type === "rect") {
+ rect.set({
+ width: object.width,
+ height: object.height,
+ top: object.top,
+ left: object.left,
+ originX: object.originX,
+ originY: object.originY,
+ angle: object.angle,
+ scaleX: object.scaleX,
+ scaleY: object.scaleY,
+ flipX: object.flipX,
+ flipY: object.flipY,
+ });
+ } else {
+ let scaleX = tObject.scaleX || 1;
+ let scaleY = tObject.scaleY || 1;
+ rect.set({
+ width: tObject.width,
+ height: tObject.height,
+ top: tObject.top - tObject.height * scaleY / 2,
+ left: tObject.left - tObject.width * scaleX / 2,
+ scaleX,
+ scaleY,
+ });
+ layer.locked = true;
+ }
+ rect.set("fill", pattern);
+ this.canvas.add(rect);
+ this.canvas.remove(object);
+ }
+ await this.layerManager?.updateLayersObjectsInteractivity();
+ await this.layerManager?.sortLayersWithTool?.();
+ await this.canvasManager.thumbnailManager?.generateLayerThumbnail(
+ this.layerId
+ );
+ await this.layerManager.selectLayerObjects(this.layerId);
+ return true;
+ }
+
+ async undo() {
+ if (!this.oldObjects) {
+ console.warn("没有旧对象可恢复");
+ return false;
+ }
+ const { layer } = findLayerRecursively(this.layers.value, this.oldObjects.layerId);
+ if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
+ console.warn("图层不存在或没有 fabric 对象");
+ return false;
+ }
+ const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
+ this.canvas.remove(object);
+ this.canvas.add(this.oldObjects);
+ layer.fabricObjects = [this.oldObjects.toObject(["id", "layerId", "layerName"])];
+ layer.locked = this.oldLocked;
+ // layer.isDisableUnlock = this.oldIsDisableUnlock;
+ await this.layerManager?.updateLayersObjectsInteractivity();
+ await this.layerManager?.sortLayersWithTool?.();
+ this.canvas.renderAll();
+ this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layerId);
+ return true;
+ }
+}
+
+
+/**
+ * 填充图案更改参数
+ */
+export class FillRepeatChangeCommand extends Command {
+ constructor(options) {
+ super({ name: "填充图案更改参数", saveState: true });
+ this.canvas = options.canvas;
+ this.layers = options.layers;
+ this.canvasManager = options.canvasManager;
+ this.layerManager = options.layerManager;
+ this.layerId = options.layerId;
+ this.newPattern = options.newPattern;
+ this.oldPattern = null;
+ }
+
+ async execute() {
+ const { layer } = findLayerRecursively(this.layers.value, this.layerId);
+ if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
+ console.warn("图层不存在或没有 fabric 对象");
+ return false;
+ }
+ const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
+ if (!object || object.type !== "rect") {
+ console.warn("当前对象不是矩形", object);
+ return false;
+ }
+ this.oldPattern = object.oldPattern || object.get("fill");
+ delete object.oldPattern;
+ const oldPattern = { ...this.oldPattern };
+ delete oldPattern.id;
+ const pattern = new fabric.Pattern({
+ ...oldPattern,
+ ...this.newPattern,
+ });
+ object.set("fill", pattern);
+ this.canvas.renderAll();
+ return true;
+ }
+
+ async undo() {
+ if (!this.oldPattern) {
+ console.warn("没有旧图案可恢复");
+ return false;
+ }
+ const { layer } = findLayerRecursively(this.layers.value, this.layerId);
+ if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
+ console.warn("图层不存在或没有 fabric 对象");
+ return false;
+ }
+ const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
+ if (!object || object.type !== "rect") {
+ console.warn("当前对象不是矩形", object);
+ return false;
+ }
+ const pattern = new fabric.Pattern({
+ ...this.oldPattern
+ });
+ object.set("fill", pattern);
+ this.canvas.renderAll();
+ return true;
+ }
+}
+
+/**
+ * 填充图案更改间隙
+ */
+export class FillRepeatGapChangeCommand extends Command {
+ constructor(options) {
+ super({ name: "填充图案更改间隙", saveState: true });
+ this.canvas = options.canvas;
+ this.layers = options.layers;
+ this.canvasManager = options.canvasManager;
+ this.layerManager = options.layerManager;
+ this.layerId = options.layerId;
+ this.newGapX = options.newGapX;
+ this.newGapY = options.newGapY;
+ this.record = !!options.record;
+ this.oldGapX = null;
+ this.oldGapY = null;
+ }
+
+ async execute(isUndo = false) {
+ const { layer } = findLayerRecursively(this.layers.value, this.layerId);
+ if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
+ console.warn("图层不存在或没有 fabric 对象");
+ return false;
+ }
+ const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
+ if (!object || object.type !== "rect") {
+ console.warn("当前对象不是矩形", object);
+ return false;
+ }
+ if (!object.fill_) {
+ object.fill_ = {
+ source: FillSourceToBase64(object.fill.source),
+ gapX: 0,
+ gapY: 0,
+ }
+ }
+ if (isUndo) {
+ object.fill_.gapX = this.oldGapX;
+ object.fill_.gapY = this.oldGapY;
+ } else {
+ if (!object.oldFill_ && this.record) {
+ object.oldFill_ = { ...object.fill_ };
+ }
+ this.oldGapX = object.fill_.gapX;
+ this.oldGapY = object.fill_.gapY;
+ object.fill_.gapX = this.newGapX;
+ object.fill_.gapY = this.newGapY;
+ }
+ const image = new Image();
+ image.crossOrigin = "anonymous";
+ image.src = object.fill_.source;
+ await image.decode();
+ object.fill_.width = image.width;
+ object.fill_.height = image.height;
+ const fill = object.get("fill");
+ fill.source = imageAddGapToCanvas(image, object.fill_.gapX, object.fill_.gapY);
+ object.set("fill", new fabric.Pattern(fill));
+ this.canvas.renderAll();
+ return true;
+ }
+
+ async undo() {
+ if (this.oldGapX === null || this.oldGapY === null) {
+ console.warn("没有旧间隙可恢复");
+ return false;
+ }
+ await this.execute(true);
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
index cec13748..339fe361 100644
--- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
@@ -10,6 +10,7 @@ import { AddObjectToLayerCommand } from "./ObjectLayerCommands";
import { ToolCommand } from "./ToolCommands";
import {
findObjectById,
+ findObjectByLayerId,
generateId,
getObjectZIndex,
insertObjectAtZIndex,
@@ -19,7 +20,7 @@ import {
} from "../utils/helper";
import { fabric } from "fabric-with-all";
import { restoreFabricObject } from "../utils/objectHelper";
-
+import EventManager from "../utils/event.js";
/**
* 添加图层命令
*/
@@ -36,7 +37,7 @@ export class AddLayerCommand extends Command {
this.insertIndex = options.insertIndex;
this.oldActiveLayerId = null;
- this.beforeLayers = [...this.layers.value]; // 备份原图层列表
+ this.beforeLayers = JSON.stringify(this.layers.value); // 备份原图层列表
this.options = options.options || {};
}
@@ -70,7 +71,7 @@ export class AddLayerCommand extends Command {
undo() {
// 从图层列表删除该图层
- this.layers.value = [...this.beforeLayers];
+ this.layers.value = JSON.parse(this.beforeLayers);
// 恢复原活动图层
this.activeLayerId.value = this.oldActiveLayerId;
@@ -143,7 +144,7 @@ export class AddLayerCommand extends Command {
// 先在一级图层中查找
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
-
+ if (layer.isPrintTrimsGroup) continue;
if (layer.id === layerId) {
return {
layer: layer,
@@ -251,12 +252,12 @@ export class PasteLayerCommand extends Command {
(await restoreFabricObject(groupLayer?.clippingMask, this.canvas)) ||
null;
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
absolutePositioned: true,
});
- clippingMaskFabricObject.dirty = true;
+ // clippingMaskFabricObject.dirty = true;
clippingMaskFabricObject.setCoords();
// 添加所有对象到画布
allObjects.forEach((obj) => {
@@ -523,6 +524,7 @@ export class RemoveLayerCommand extends Command {
this.layerId = options.layerId;
this.activeLayerId = options.activeLayerId;
this.layerManager = options.layerManager || null;
+ this.IsOnlyLayer = this.layers.value.filter((v => !v.isFixed && !v.isFixedOther && !v.isBackground)).length <= 1
// 查找要删除的图层
this.layerIndex = this.layers.value.findIndex(
@@ -599,7 +601,9 @@ export class RemoveLayerCommand extends Command {
);
// 从图层列表中删除
this.layers.value.splice(this.layerIndex, 1);
-
+ if(this.IsOnlyLayer){
+ this.addCmd = await this.layerManager?.createLayer?.(null, LayerType.EMPTY, {}, false);
+ }
// 如果删除的是当前活动图层,需要更新活动图层
if (this.isActiveLayer) {
// 查找最近的非背景层作为新的活动图层
@@ -632,6 +636,9 @@ export class RemoveLayerCommand extends Command {
async undo() {
// 恢复图层到原位置
if (this.layerIndex !== -1 && this.removedLayer) {
+ if(this.IsOnlyLayer && this.addCmd){
+ this.addCmd?.undo?.();
+ }
this.layers.value.splice(this.layerIndex, 0, this.removedLayer);
// 使用优化渲染批处理恢复真实对象到画布
@@ -649,7 +656,6 @@ export class RemoveLayerCommand extends Command {
}
});
});
-
await this.layerManager?.updateLayersObjectsInteractivity?.();
this.canvas.renderAll();
@@ -802,15 +808,23 @@ export class ToggleLayerVisibilityCommand extends Command {
// 切换可见性
this.layer.visible = !this.layer.visible;
+ const ids = [this.layerId];
+ const childLayers = this.layer?.children || [];
+ childLayers.forEach((childLayer) => {
+ childLayer.visible = this.layer.visible;
+ ids.push(childLayer.id);
+ });
// 更新画布上图层对象的可见性
if (this.canvas) {
- const layerObjects = this.canvas
- .getObjects()
- .filter((obj) => obj.layerId === this.layerId);
- layerObjects.forEach((obj) => {
- obj.visible = this.layer.visible;
- });
+ this.canvas.getObjects().forEach((obj) => {
+ if (ids.includes(obj.layerId)) {
+ obj.getObjects?.()?.forEach((item) => {
+ item.visible = this.layer.visible;
+ });
+ obj.visible = this.layer.visible;
+ }
+ });
}
// 更新画布上对象的可选择状态
await this.layerManager?.updateLayersObjectsInteractivity();
@@ -858,23 +872,24 @@ 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) {
- const layerObjects = this.canvas
- .getObjects()
- .filter((obj) => obj.layerId === this.layerId);
-
- layerObjects.forEach((obj) => {
- obj.visible = this.childLayer.visible;
- });
+ this.canvas.getObjects().forEach((obj) => {
+ if (obj.layerId === this.layerId) {
+ obj.getObjects?.()?.forEach((item) => {
+ item.visible = this.childLayer.visible;
+ });
+ obj.visible = this.childLayer.visible;
+ }
+ });
}
// 更新画布上对象的可选择状态
@@ -1007,9 +1022,8 @@ export class LayerLockCommand extends Command {
// 如果是组图层,递归更新所有子图层
if (
- layer.type === "group" &&
layer.children &&
- Array.isArray(layer.children)
+ Array.isArray(layer.children) && layer.children.length > 0
) {
layer.children.forEach((child) => {
this._updateLayerLockState(child, locked);
@@ -1108,7 +1122,7 @@ export class SetLayerOpacityCommand extends Command {
this.canvas.renderAll();
}
-
+ EventManager.emit("object:opacity:execute", this.layerId, this.opacity);
return true;
}
@@ -1130,6 +1144,7 @@ export class SetLayerOpacityCommand extends Command {
this.canvas.renderAll();
}
}
+ EventManager.emit("object:opacity:undo", this.layerId, this.opacity);
}
getInfo() {
@@ -1371,7 +1386,7 @@ export class GroupLayersCommand extends Command {
// 备份原图层
this.originalLayers = [...this.layers.value];
// 新组ID
- this.groupId =
+ this.groupId = options.id ||
generateId("group_layer_") ||
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
@@ -3669,7 +3684,7 @@ export class ChangeFixedImageCommand extends Command {
opacity: currentObj.opacity,
};
- console.log(`保存渲染顺序: z-index = ${this.previousZIndex}`);
+ // console.log(`保存渲染顺序: z-index = ${this.previousZIndex}`);
}
}
@@ -3779,7 +3794,7 @@ export class ChangeFixedImageCommand extends Command {
false
);
if (insertSuccess) {
- console.log(`新图像插入到z-index位置: ${this.previousZIndex}`);
+ // console.log(`新图像插入到z-index位置: ${this.previousZIndex}`);
} else {
// 如果插入失败,回退到普通添加
this.canvas.add(newImage);
@@ -4276,24 +4291,28 @@ export class RemoveChildLayerCommand extends Command {
}
// 恢复子图层到原位置
this.parentLayer.children.splice(this.childIndex, 0, this.removedChild);
- optimizeCanvasRendering(this.canvas, async () => {
- this.originalObjects.forEach((obj) => {
- // 恢复对象到画布
- this.canvas.add(obj);
- // 恢复对象的图层信息
- obj.layerId = this.layerId;
- obj.layerName = this.removedChild.name;
- obj.setCoords(); // 更新坐标
- });
+ await new Promise((resolve) => {
+ optimizeCanvasRendering(this.canvas, async () => {
+ this.originalObjects.forEach((obj) => {
+ // 恢复对象到画布
+ this.canvas.add(obj);
+ // 恢复对象的图层信息
+ obj.layerId = this.layerId;
+ obj.layerName = this.removedChild.name;
+ obj.setCoords(); // 更新坐标
+ });
- // 如果是原活动图层,恢复活动图层
- if (this.isActiveLayer) {
- this.activeLayerId.value = this.layerId;
- }
+ // 如果是原活动图层,恢复活动图层
+ if (this.isActiveLayer) {
+ this.activeLayerId.value = this.layerId;
+ }
- // 重新渲染画布
- await this.layerManager?.updateLayersObjectsInteractivity(false);
+ // 重新渲染画布
+ await this.layerManager?.updateLayersObjectsInteractivity(false);
+ resolve(true);
+ });
});
+ return true;
}
getInfo() {
@@ -4434,3 +4453,90 @@ export class ChildLayerLockCommand extends Command {
};
}
}
+/**
+ * 设置图层混合模式
+ */
+export class SetLayerCompositeCommand extends Command {
+ constructor(options) {
+ super({
+ name: "设置图层混合模式",
+ saveState: false,
+ });
+ this.canvas = options.canvas;
+ this.layers = options.layers;
+ this.layerManager = options.layerManager;
+ this.layerId = options.layerId;
+ this.newValue = options.newValue;
+ this.oldValue = options.oldValue;
+ }
+
+ execute(isUndo = false) {
+ const { layer } = findLayerRecursively(this.layers.value, this.layerId);
+ const { object } = findObjectByLayerId(this.canvas, this.layerId);
+ if (!layer || !object) {
+ console.error(`图层${this.layerId}不存在`);
+ return false;
+ }
+ // console.log("==========", this.newValue, this.oldValue);
+ const value = isUndo ? this.oldValue : this.newValue;
+ layer.blendMode = value;
+ object.set("globalCompositeOperation", value);
+ this.canvas.renderAll();
+ const event = isUndo ? "object:composite:undo" : "object:composite:execute";
+ EventManager.emit(event, object);
+ return true;
+ }
+
+ undo() {
+ return this.execute(true);
+ }
+}
+
+
+/**
+ * 设置颜色图层颜色
+ */
+export class SetColorLayerFillCommand extends Command {
+ constructor(options) {
+ super({
+ name: "设置颜色图层颜色",
+ saveState: false,
+ });
+ this.canvas = options.canvas;
+ this.layerManager = options.layerManager;
+ this.object = options.object;
+ this.layer = this.layerManager?.getLayerById(this.object.layerId);
+ this.newFill = options.newFill;
+ this.oldFill = JSON.parse(JSON.stringify(this.object.fill));
+ this.layer.blendMode = "multiply";
+ this.object.set("globalCompositeOperation", "multiply");
+ this.object.set("originColor", options.originColor);
+ }
+
+ async execute(isUndo = false) {
+ if (!this.object) {
+ console.error(`颜色图层不存在`);
+ return false;
+ }
+ const isVisible = this.layer?.visible;
+ if(!isVisible && this.layer) this.layer.visible = true;
+ const gradient = new fabric.Gradient({
+ type: "linear",
+ gradientUnits: "percentage",
+ ...(isUndo ? this.oldFill : this.newFill),
+ });
+ this.object.setFill(gradient);
+ this.canvas.renderAll();
+ await this.canvas?.thumbnailManager?.generateLayerThumbnail?.(
+ this.object.id
+ );
+ if(!isVisible && this.layer) this.layer.visible = false;
+ this.layerManager?.updateLayersObjectsInteractivity();
+ return true;
+ }
+
+ undo() {
+ this.execute(true);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/component/Canvas/CanvasEditor/commands/ObjectCommands.js b/src/component/Canvas/CanvasEditor/commands/ObjectCommands.js
new file mode 100644
index 00000000..a8e5c33a
--- /dev/null
+++ b/src/component/Canvas/CanvasEditor/commands/ObjectCommands.js
@@ -0,0 +1,50 @@
+import { Command } from "./Command.js";
+
+/**
+ * 对象移动命令
+ * 轻量级命令,只记录对象的移动属性变化(位置)
+ */
+export class ObjectMoveCommand extends Command {
+ constructor(options) {
+ super({
+ name: options.name || "对象移动",
+ description: options.description || "移动对象",
+ saveState: false, // 自己管理状态,避免递归
+ });
+
+ this.canvas = options.canvas;
+ this.initPos = options.initPos;
+ this.finalPos = options.finalPos;
+ }
+
+ /**
+ * 执行命令
+ */
+ async execute() {
+ this.setObjectsPos(this.finalPos);
+ return true;
+ }
+ /**
+ * 撤销命令
+ * 应用初始状态
+ */
+ async undo() {
+ this.setObjectsPos(this.initPos);
+ return true;
+ }
+
+ async setObjectsPos(pos) {
+ const objects = this.canvas.getObjects();
+ const arr = typeof pos === "object" ? [pos] : pos;
+ arr.forEach((item) => {
+ const obj = objects.find((o) => o.id === item.id);
+ if(obj) {
+ obj.set({
+ left: item.left,
+ top: item.top,
+ });
+ }
+ });
+ this.canvas.renderAll();
+ }
+}
diff --git a/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js b/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js
index 1243295a..c32ff8c3 100644
--- a/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js
@@ -234,7 +234,7 @@ export class AddObjectToLayerCommand extends Command {
parent.clippingMask,
this.canvas
);
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({ absolutePositioned: true });
this.fabricObject.clipPath = clippingMaskFabricObject;
// 标记为脏对象
@@ -600,7 +600,7 @@ export class ChangeFixedImageCommand extends Command {
opacity: currentObj.opacity,
};
- console.log(`保存渲染顺序: z-index = ${this.previousZIndex}`);
+ // console.log(`保存渲染顺序: z-index = ${this.previousZIndex}`);
}
}
@@ -716,7 +716,7 @@ export class ChangeFixedImageCommand extends Command {
false
);
if (insertSuccess) {
- console.log(`新图像插入到z-index位置: ${this.previousZIndex}`);
+ // console.log(`新图像插入到z-index位置: ${this.previousZIndex}`);
} else {
// 如果插入失败,回退到普通添加
this.canvas.add(newImage);
diff --git a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js
index 54c6285b..5b6f3192 100644
--- a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js
@@ -46,13 +46,13 @@ export class RasterizeLayerCommand extends Command {
this.layerId
);
this.layer = layer;
- this.parentLayer = parent;
+ // this.parentLayer = parent;
- // 新增:如果有父图层,则栅格化父图层及其所有子图层
- if (this.parentLayer) {
- this.layer = this.parentLayer;
- this.layerId = this.parentLayer.id;
- }
+ // // 新增:如果有父图层,则栅格化父图层及其所有子图层
+ // if (this.parentLayer) {
+ // this.layer = this.parentLayer;
+ // this.layerId = this.parentLayer.id;
+ // }
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
@@ -153,7 +153,7 @@ export class RasterizeLayerCommand extends Command {
});
// 恢复原始图层结构
- this.layers.value = [...this.originalLayerStructure];
+ this.layers.value = JSON.parse(this.originalLayerStructure);
// 恢复原活动图层
this.activeLayerId.value = this.layerId;
@@ -191,7 +191,7 @@ export class RasterizeLayerCommand extends Command {
*/
_saveOriginalLayerStructure() {
// 只保存相关的图层结构,而不是整个图层数组
- this.originalLayerStructure = JSON.parse(JSON.stringify(this.layers.value));
+ this.originalLayerStructure = JSON.stringify(this.layers.value);
}
/**
@@ -285,17 +285,15 @@ export class RasterizeLayerCommand extends Command {
// 提取排序后的对象
this.objectsToRasterize = objectsWithZIndex.map((item) => item.object);
- console.log(
- `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合`
- );
- console.log(
- "🔢 对象z-index顺序:",
- objectsWithZIndex.map((item) => ({
- id: item.object.id,
- type: item.object.type,
- zIndex: item.zIndex,
- }))
- );
+ // console.log(`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合` );
+ // console.log(
+ // "🔢 对象z-index顺序:",
+ // objectsWithZIndex.map((item) => ({
+ // id: item.object.id,
+ // type: item.object.type,
+ // zIndex: item.zIndex,
+ // }))
+ // );
}
/**
@@ -517,12 +515,12 @@ export class ExportLayerToImageCommand extends Command {
this.canvas
);
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
absolutePositioned: true,
});
- clippingMaskFabricObject.dirty = true;
+ // clippingMaskFabricObject.dirty = true;
clippingMaskFabricObject.setCoords();
}
@@ -611,17 +609,17 @@ export class ExportLayerToImageCommand extends Command {
// 提取排序后的对象
this.objectsToRasterize = objectsWithZIndex.map((item) => item.object);
- console.log(
- `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合`
- );
- console.log(
- "🔢 对象z-index顺序:",
- objectsWithZIndex.map((item) => ({
- id: item.object.id,
- type: item.object.type,
- zIndex: item.zIndex,
- }))
- );
+ // console.log(
+ // `📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合`
+ // );
+ // console.log(
+ // "🔢 对象z-index顺序:",
+ // objectsWithZIndex.map((item) => ({
+ // id: item.object.id,
+ // type: item.object.type,
+ // zIndex: item.zIndex,
+ // }))
+ // );
}
/**
diff --git a/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js b/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js
index 9b3ffaa6..56638c2f 100644
--- a/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js
@@ -159,6 +159,8 @@ export class BatchInitializeRedGreenModeCommand extends Command {
absolutePositioned: true,
opacity: 0.01, // 设置为几乎透明
id: generateId("redGreenImageMask_"),
+ rx: 15,
+ ry: 15,
});
// this.canvas.add(this.redGreenImageMask);
this.canvas.clipPath = this.redGreenImageMask;
diff --git a/src/component/Canvas/CanvasEditor/commands/StateCommands.js b/src/component/Canvas/CanvasEditor/commands/StateCommands.js
index 03ba2b41..40db92ac 100644
--- a/src/component/Canvas/CanvasEditor/commands/StateCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/StateCommands.js
@@ -2,6 +2,7 @@ import { findObjectById } from "../utils/helper";
import { findLayerRecursively } from "../utils/layerHelper";
import { restoreFabricObject } from "../utils/objectHelper";
import { Command } from "./Command";
+import EventManager from "../utils/event.js";
/**
* 对象变换命令
@@ -75,7 +76,7 @@ export class TransformCommand extends Command {
// 触发画布更新
this.canvas.renderAll();
-
+ EventManager.emit("object:modified:execute", targetObject);
return true;
}
@@ -113,7 +114,7 @@ export class TransformCommand extends Command {
}, 300);
// 触发画布更新
this.canvas.renderAll();
-
+ EventManager.emit("object:modified:undo", targetObject);
return true;
}
@@ -167,7 +168,7 @@ export class TransformCommand extends Command {
);
if (clippingMaskFabricObject) {
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
absolutePositioned: true,
});
diff --git a/src/component/Canvas/CanvasEditor/commands/TextCommands.js b/src/component/Canvas/CanvasEditor/commands/TextCommands.js
index 8e6c80c0..a9e5699f 100644
--- a/src/component/Canvas/CanvasEditor/commands/TextCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/TextCommands.js
@@ -493,7 +493,7 @@ export class CreateTextCommand extends Command {
// 先在一级图层中查找
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
-
+ if (layer.isPrintTrimsGroup) continue;
if (layer.id === layerId) {
return {
layer: layer,
diff --git a/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js b/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js
index cf6114a3..cd114d6c 100644
--- a/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/UpdateGroupMaskPositionCommand.js
@@ -233,7 +233,7 @@ export class UpdateGroupMaskPositionCommand extends Command {
return;
}
- clippingMaskFabricObject.clipPath = null;
+ // clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
absolutePositioned: true,
});
diff --git a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue
index 310b6a60..0a8efeb4 100644
--- a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue
@@ -154,6 +154,8 @@ const isVisible = computed(() => {
OperationType.ERASER,
OperationType.RED_BRUSH,
OperationType.GREEN_BRUSH,
+ OperationType.PART_BRUSH,
+ OperationType.PART_ERASER,
].includes(props.activeTool);
});
diff --git a/src/component/Canvas/CanvasEditor/components/CropImage.vue b/src/component/Canvas/CanvasEditor/components/CropImage.vue
index 6eb5d7b7..eb49c6a7 100644
--- a/src/component/Canvas/CanvasEditor/components/CropImage.vue
+++ b/src/component/Canvas/CanvasEditor/components/CropImage.vue
@@ -1,5 +1,4 @@
-
@@ -1298,9 +1364,11 @@ defineExpose({
-
-
+
+
+
+
+
@@ -1399,6 +1468,7 @@ defineExpose({
/* background-color: #f8f8f8; */
:deep(.canvas-container) {
position: absolute !important;
+ filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
}
}
@@ -1409,35 +1479,36 @@ defineExpose({
top: 0;
bottom: 0;
}
+.app-container >.loading{
+ position: absolute;
+}
-.background-grid {
- --offsetX: 0px;
- --offsetY: 0px;
- --size: 8px;
- --color: #dedcdc;
+.canvas-container {
+ --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);
diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js
index 5ba9c81d..0fc4f7cd 100644
--- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js
@@ -9,7 +9,13 @@ import {
isGroupLayer,
OperationType,
OperationTypes,
+ findLayer,
+ createLayer,
+ LayerType,
+ SpecialLayerId,
+ BlendMode,
} from "../utils/layerHelper";
+import { ObjectMoveCommand } from "../commands/ObjectCommands";
import { AnimationManager } from "./animation/AnimationManager";
import { createCanvas } from "../utils/canvasFactory";
import { CanvasEventManager } from "./events/CanvasEventManager";
@@ -21,6 +27,15 @@ import {
findObjectById,
generateId,
optimizeCanvasRendering,
+ palletToFill,
+ fillToCssStyle,
+ calculateRotatedTopLeftDeg,
+ calculateCenterPoint,
+ calculateTopLeftPoint,
+ createPatternTransform,
+ getTransformScaleAngle,
+ base64ToCanvas,
+ imageAddGapToCanvas,
} from "../utils/helper";
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
import { isFunction } from "lodash-es";
@@ -30,6 +45,11 @@ import {
validateLayerAssociations,
} from "../utils/layerUtils";
import { imageModeHandler } from "../utils/imageHelper";
+import { getObjectAlphaToCanvas } from "../utils/objectHelper";
+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;
export class CanvasManager {
constructor(canvasElement, options) {
@@ -50,6 +70,10 @@ export class CanvasManager {
this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层
this.eraserStateManager = null; // 橡皮擦状态管理器引用
this.handleCanvasInit = null; // 画布初始化回调函数
+ this.partManager = options.partManager || null;
+ this.props = options.props || {};
+ this.emit = options.emit || (() => {});
+ this.awaitCanvasRun = null;
// 初始化画布
this.initializeCanvas();
}
@@ -83,10 +107,10 @@ export class CanvasManager {
this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布
- // // 设置画布辅助线
- // initAligningGuidelines(this.canvas);
+ // 设置画布辅助线
+ initAligningGuidelines(this.canvas);
- // // 设置画布中心线
+ // 设置画布中心线
// initCenteringGuidelines(this.canvas);
// 初始化画布事件监听器
@@ -152,10 +176,16 @@ export class CanvasManager {
_initCanvasEvents() {
// 添加笔刷图像转换处理回调
this.canvas.onBrushImageConverted = async (fabricImage) => {
- await this.addImageToLayer({ fabricImage, targetLayerId: null });
+ const activeTool = this.toolManager?.activeTool?.value;
+ if(activeTool === OperationType.PART_BRUSH){
+ this.partManager?.addPartImage(fabricImage);
+ }else{
+ await this.addImageToLayer({ fabricImage, targetLayerId: null });
+ }
// 返回false表示使用默认行为(直接添加到画布)
return false;
};
+
this.eraserStateManager = new EraserStateManager(
this.canvas,
@@ -283,6 +313,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;
@@ -408,12 +451,41 @@ export class CanvasManager {
}
// 居中所有画布元素,包括背景层和其他元素
- this.centerAllObjects();
+ await this.centerAllObjects();
// // 重新渲染画布使变更生效
// this.canvas.renderAll();
}
-
+ // 重置画布大小参照固定图层
+ async resetCanvasSizeByFixedLayer(){
+ // 重置画布大小为固定图层的大小
+ const fixedLayerObj = this.getFixedLayerObject();
+ const backgroundObject = this.getBackgroundLayerObject();
+ if (!fixedLayerObj || !backgroundObject) return
+ const fwidth = fixedLayerObj.width * fixedLayerObj.scaleX
+ const fheight = fixedLayerObj.height * fixedLayerObj.scaleY
+ const bwidth = backgroundObject.width * backgroundObject.scaleX
+ const bheight = backgroundObject.height * backgroundObject.scaleY
+ console.log(fixedLayerObj.width,
+ fixedLayerObj.scaleX,
+ fixedLayerObj.height,
+ fixedLayerObj.scaleY,
+ backgroundObject.width,
+backgroundObject.scaleX,
+backgroundObject.height,
+backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
+ if(Math.abs(fwidth/bwidth - fheight/bheight) < 0.1) return;
+ this.canvasWidth.value = fwidth
+ this.canvasHeight.value = fheight
+ backgroundObject.set({
+ width: this.canvasWidth.value,
+ height: this.canvasHeight.value,
+ })
+ this.canvas?.clipPath?.set?.({
+ width: this.canvasWidth.value,
+ height: this.canvasHeight.value,
+ })
+}
/**
* 重置视图变换,使元素回到原始位置
* @private
@@ -431,9 +503,8 @@ export class CanvasManager {
* 以背景层为参照,计算背景层的偏移量并应用到所有对象上
* 这样可以保持对象间的相对位置关系不变
*/
- centerAllObjects() {
+ async centerAllObjects() {
if (!this.canvas) return;
-
// 获取所有可见对象(不是背景元素的对象)
const allObjects = this.canvas.getObjects();
if (allObjects.length === 0) return;
@@ -448,9 +519,6 @@ export class CanvasManager {
// 获取背景对象
const backgroundObject = visibleObjects.find((obj) => obj.isBackground);
- !this.canvas?.clipPath &&
- this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
-
this.canvas?.clipPath?.set?.({
left: this.width / 2,
top: this.height / 2,
@@ -496,7 +564,6 @@ export class CanvasManager {
// 计算背景层的偏移量
const deltaX = backgroundObject.left - backgroundOldLeft;
const deltaY = backgroundObject.top - backgroundOldTop;
-
// 将相同的偏移量应用到所有其他对象上
const otherObjects = visibleObjects.filter(
(obj) => obj !== backgroundObject
@@ -543,14 +610,29 @@ export class CanvasManager {
});
});
}
+
+ !this.canvas?.clipPath &&
+ this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
// 如果有背景层,更新蒙层位置
if (backgroundObject && CanvasConfig.isCropBackground) {
this.updateMaskPosition(backgroundObject);
}
+ // 更新颜色层信息
+ // const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
+ // if(colorObject){
+ // await this.setObjecCliptInfo(colorObject);
+ // }
+ const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
+ if(groupLayer){
+ const groupRect = new fabric.Rect({});
+ await this.setObjecCliptInfo(groupRect);
+ groupLayer.clippingMask = groupRect.toObject();
+ }
+
// 重新渲染画布
- // this.canvas.renderAll();
+ this.canvas.renderAll();
}
/**
@@ -600,7 +682,7 @@ export class CanvasManager {
* @param {Number} canvasWidth 画布宽度
* @param {Number} canvasHeight 画布高度
*/
- centerBackgroundLayer(canvasWidth, canvasHeight) {
+ async centerBackgroundLayer(canvasWidth, canvasHeight) {
const backgroundLayerObject = this.getBackgroundLayer();
if (!backgroundLayerObject) return false;
@@ -646,6 +728,11 @@ export class CanvasManager {
if (this.maskLayer) {
this.canvas.remove(this.maskLayer);
}
+ this.canvas.getObjects().forEach((obj) => {
+ if (obj.id === "canvasMaskLayer") {
+ this.canvas.remove(obj);
+ }
+ })
// 创建蒙层 - 使用透明矩形作为裁剪区域
this.maskLayer = new fabric.Rect({
@@ -679,6 +766,8 @@ export class CanvasManager {
originX: backgroundLayerObject.originX || "left",
originY: backgroundLayerObject.originY || "top",
absolutePositioned: true,
+ rx: 15,
+ ry: 15,
});
}
getBackgroundLayer() {
@@ -706,6 +795,82 @@ export class CanvasManager {
return backgroundLayerByBgLayer;
}
+ getFixedLayerObject() {
+ if (!this.canvas) return null;
+ const fixedLayer = this.canvas.getObjects().find((obj) => {
+ return obj.isFixed;
+ });
+
+ if (fixedLayer) return fixedLayer;
+
+ // 如果没有找到固定层,则根据图层ID查找
+ const fixedLayerId = this.layers.value.find((layer) => {
+ return layer.isFixed;
+ })?.id;
+
+ const fixedLayerByFixedLayer = this.canvas.getObjects().find((obj) => {
+ return obj.isFixed || obj.id === fixedLayerId;
+ });
+ if (!fixedLayerByFixedLayer) {
+ console.warn(
+ "CanvasManager.js = >getFixedLayerObject 方法没有找到固定层"
+ );
+ }
+
+ return fixedLayerByFixedLayer;
+ }
+ getBackgroundLayerObject() {
+ if (!this.canvas) return null;
+ const backgroundLayer = this.canvas.getObjects().find((obj) => {
+ return obj.isBackground;
+ });
+
+ if (backgroundLayer) return backgroundLayer;
+
+ // 如果没有找到背景层,则根据图层ID查找
+ const backgroundLayerId = this.layers.value.find((layer) => {
+ return layer.isBackground;
+ })?.id;
+
+ const backgroundLayerByBgLayer = this.canvas.getObjects().find((obj) => {
+ return obj.isBackground || obj.id === backgroundLayerId;
+ });
+ if (!backgroundLayerByBgLayer) {
+ console.warn(
+ "CanvasManager.js = >getBackgroundLayerObject 方法没有找到背景层"
+ );
+ }
+
+ return backgroundLayerByBgLayer;
+ }
+ getLayerObjectById(layerId) {
+ if (!this.canvas) return null;
+
+ const layerObject = this.canvas.getObjects().find((obj) => {
+ return obj.id === layerId;
+ });
+
+ if (layerObject) return layerObject;
+
+ // 如果没有找到图层对象,则根据图层ID查找
+ const layerObjectByLayerId = this.canvas.getObjects().find((obj) => {
+ return obj.id === layerId;
+ });
+ if (!layerObjectByLayerId) {
+ console.warn(
+ "CanvasManager.js = >getLayerObjectById 方法没有找到图层对象"
+ );
+ }
+
+ return layerObjectByLayerId;
+ }
+ getObjectsByIds(ids){
+ const objects = this.canvas.getObjects().filter((obj) => {
+ return ids.includes(obj.id);
+ });
+ return objects;
+ }
+
/**
* 更新蒙层位置
* @param {Object} backgroundLayerObject 背景层对象
@@ -713,7 +878,6 @@ export class CanvasManager {
updateMaskPosition(backgroundLayerObject) {
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
return;
-
const left = backgroundLayerObject.left;
const top = backgroundLayerObject.top;
@@ -798,7 +962,7 @@ export class CanvasManager {
// 如果找到了图层,则生成缩略图
findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id);
-
+ this.layerManager?.sortLayers?.();
return result;
}
@@ -807,11 +971,16 @@ export class CanvasManager {
* @param {Object} options 导出选项
* @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)
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} options.isEnhanceImg 是否是增强图片
+ * @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
* @returns {String} 导出的图片数据URL
*/
async exportImage(options = {}) {
@@ -826,12 +995,16 @@ export class CanvasManager {
// this.canvas.renderAll(); // 重新渲染画布
// 自动设置红绿图模式相关参数
const enhancedOptions = {
+ isPrintTrimsNoRepeat: true,
+ isPrintTrimsRepeat: true,
+ isContainNormalLayer: true,
...options,
// 如果没有明确指定,则根据当前模式自动设置
restoreOpacityInRedGreen:
options.restoreOpacityInRedGreen !== undefined
? options.restoreOpacityInRedGreen
: false, // 默认在红绿图模式下恢复透明度
+ // excludedLayers: [SpecialLayerId.SPECIAL_GROUP], // 导出时排除的图层ID数组
};
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
@@ -846,7 +1019,7 @@ export class CanvasManager {
const normalLayerIds =
this.layers?.value
?.filter(
- (layer) => !layer.isBackground && !layer.isFixed && layer.visible
+ (layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible
)
?.map((layer) => layer.id) || [];
@@ -855,13 +1028,217 @@ 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.error("CanvasManager导出图片失败:", error);
+ console.warn("CanvasManager导出图片失败:", error);
throw error;
}
}
+ /**
+ * 导出印花元素颜色信息
+ * @returns {Object}
+ */
+ async exportExtraInfo() {
+ // 导出颜色图层信息
+ const color = await this.exportColorLayer().catch(() => (null));
+ // 导出印花和元素图层信息
+ const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null}));
+
+ const obj = {
+ color,
+ ...printTrimsData,
+ };
+ console.log("==========exportExtraInfo:", obj);
+ return obj;
+ }
+
+ /**
+ * 导出颜色图层
+ * @returns {Object} 导出的颜色图层数据URL
+ */
+ async exportColorLayer() {
+ if (!this.exportManager) {
+ console.warn("导出管理器未初始化,请确保已设置图层管理器");
+ return Promise.reject("颜色图层不存在");
+ }
+ const object = this.getLayerObjectById(SpecialLayerId.COLOR);
+ if(!object){
+ console.warn("颜色图层不存在,请确保已添加颜色图层");
+ return Promise.reject("颜色图层不存在");
+ }
+ const css = fillToCssStyle(object.fill)
+ const canvas = new fabric.StaticCanvas();
+ canvas.setDimensions({
+ width: object.width,
+ height: object.height,
+ backgroundColor: null,
+ imageSmoothingEnabled: true,
+ });
+ const cloneObject = await new Promise((resolve, reject) => {
+ object.clone(resolve);
+ });
+ cloneObject.set({
+ left: canvas.width / 2,
+ top: canvas.height / 2,
+ scaleX: 1,
+ scaleY: 1,
+ visible: true,
+ clipPath: null,
+ });
+ canvas.add(cloneObject);
+ canvas.renderAll();
+ const base64 = canvas.toDataURL({
+ format: "png",
+ quality: 1,
+ });
+ canvas.clear();
+ const color = object.originColor;
+ return {css, base64, color};
+ }
+
+ /**
+ * 导出印花和元素图层
+ */
+ async exportPrintTrimsLayers() {
+ const glayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
+ if(!glayer) return Promise.reject("印花和元素图层组不存在");
+ const ids = glayer.children.map((v) => v.id);
+ const objects = this.getObjectsByIds(ids);
+ const fixedLayerObj = this.getFixedLayerObject();
+ if(!fixedLayerObj) return Promise.reject("固定图层不存在");
+ const flWidth = fixedLayerObj.width
+ const flHeight = fixedLayerObj.height
+ const flTop = fixedLayerObj.top
+ const flLeft = fixedLayerObj.left
+ const flScaleX = fixedLayerObj.scaleX
+ const flScaleY = fixedLayerObj.scaleY
+ const prints = [];
+ const trims = [];
+ objects.forEach((v) => {
+ const sourceData = glayer.children.find((v_) => v_.id === v.id)?.metadata?.sourceData;
+ if(!sourceData) return;
+ const obj = {
+ ifSingle: typeof v.fill === "string",
+ level2Type: sourceData.level2Type,
+ designType: sourceData.designType,
+ path: sourceData.path,
+ minIOPath: sourceData.minIOPath,
+ location: [0, 0],
+ scale: [0, 0],
+ angle: v.angle,
+ name: sourceData.name,
+ priority: sourceData.priority,
+ object:{
+ top: 0,
+ left: 0,
+ scaleX: 0,//对象的缩放比例
+ scaleY: 0,//对象的缩放比例
+ opacity: v.opacity,
+ angle: v.angle,
+ flipX: v.flipX,
+ flipY: v.flipY,
+ blendMode: v.globalCompositeOperation,
+ gapX: 0,// 平铺模式下的间距
+ gapY: 0,// 平铺模式下的间距
+ fill_repeat: "",
+ }
+ }
+ let left = (v.left - (flLeft - flWidth * flScaleX / 2));
+ let top = (v.top - (flTop - flHeight * flScaleY / 2));
+ let width = (v.width * v.scaleX);
+ let height = (v.height * v.scaleY);
+ if(v.originX === "center" && v.originY === "center") {
+ let {x:cx, y:cy} = calculateTopLeftPoint(width, height, left, top, v.angle);
+ left = cx;
+ top = cy;
+ }
+ let oX = left / flScaleX;
+ let oY = top / flScaleY;
+ let oScaleX = (v.width * v.scaleX) / (flWidth * flScaleX);
+ let oScaleY = (v.height * v.scaleY) / (flHeight * flScaleY);
+ obj.object.top = oY;
+ obj.object.left = oX;
+ obj.object.scaleX = oScaleX;
+ obj.object.scaleY = oScaleY;
+ if(obj.ifSingle){
+ // 单个的是从中心计算的
+ let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle);
+ let oX = (cx-width/2) / flScaleX;
+ let oY = (cy-height/2) / flScaleY;
+ obj.location = [oX, oY];
+ obj.scale = [oScaleX, oScaleY];
+ }else{
+ let fill = v.fill;
+ let fill_ = v.fill_;
+ if(!fill || !fill_) return console.warn("印花元素不存在fill或fill_属性");
+ let {scale, angle} = getTransformScaleAngle(fill.patternTransform);
+ let scaleX = scale * 5 * v.fill_.width / flWidth;
+ let scaleY = scale * 5 * v.fill_.height / flHeight;
+ let scaleXY = flWidth > flHeight ? scaleX : scaleY;
+
+ let left = fill.offsetX - v.fill_.width * scale / 2;
+ let top = fill.offsetY - v.fill_.height * scale / 2;
+
+ obj.scale = [scaleXY, scaleXY];
+ obj.angle = angle;
+ obj.location = [left, top];
+ obj.object.gapX = fill_.gapX;
+ obj.object.gapY = fill_.gapY;
+ obj.object.fill_repeat = fill.repeat;
+ }
+ if(sourceData.type === "print"){
+ prints.push(obj);
+ }else if(sourceData.type === "trims"){
+ trims.push(obj);
+ }
+ })
+ // prints.sort((a, b) => a.ifSingle ? 1 : -1);
+ prints.forEach((v, i) => v.priority = i + 1);
+ trims.forEach((v, i) => v.priority = i + 1);
+ return {prints, trims};
+ }
+
+
dispose() {
// 释放导出管理器资源
if (this.exportManager) {
@@ -892,109 +1269,67 @@ export class CanvasManager {
}
getJSON() {
- // // 简化图层数据,在loadJSON时要根据id恢复引用
- // const simplifyLayers = (layers) => {
- // return layers.map((layer) => {
- // if (layer?.children?.length) {
- // layer.children = layer.children.map((child) => {
- // return {
- // id: child.id,
- // type: child.type,
- // layerId: child.layerId,
- // layerName: child.layerName,
- // isBackground: child.isBackground,
- // isLocked: child.isLocked,
- // isVisible: child.isVisible,
- // isFixed: child.isFixed,
- // parentId: child.parentId,
- // fabricObject: child.fabricObject
- // ? {
- // id: child.fabricObject.id,
- // type: child.fabricObject.type,
- // layerId: child.fabricObject.layerId,
- // layerName: child.fabricObject.layerName,
- // }
- // : {},
- // fabricObjects:
- // child.fabricObjects?.map((obj) => ({
- // id: obj.id,
- // type: obj.type,
- // layerId: obj.layerId,
- // layerName: obj.layerName,
- // })) || [],
- // };
- // });
- // }
- // return {
- // id: layer.id,
- // type: layer.type,
- // layerId: layer.layerId,
- // layerName: layer.layerName,
- // isBackground: layer.isBackground,
- // isLocked: layer.isLocked,
- // isVisible: layer.isVisible,
- // isFixed: layer.isFixed,
- // parentId: layer.parentId,
- // fabricObject: child.fabricObject
- // ? {
- // id: child.fabricObject.id,
- // type: child.fabricObject.type,
- // layerId: child.fabricObject.layerId,
- // layerName: child.fabricObject.layerName,
- // }
- // : {},
- // fabricObjects:
- // child.fabricObjects?.map((obj) => ({
- // id: obj.id,
- // type: obj.type,
- // layerId: obj.layerId,
- // layerName: obj.layerName,
- // })) || [],
- // children: layer.children,
- // };
- // });
- // };
try {
// 清除画布中选中状态
- this.canvas.discardActiveObject();
+ // this.canvas.discardActiveObject();
this.canvas.renderAll();
+
+ // 排除颜色图层和特殊组图层
+ const excludedLayers = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP];
+ this.layers.value.forEach((layer) => {
+ if(excludedLayers.includes(layer.id)){
+ excludedLayers.push(...layer.children?.map((child) => child.id));
+ }
+ })
+
+ const canvas = this.canvas.toJSON([
+ "id",
+ "type",
+ "layerId",
+ "layerName",
+ "isBackground",
+ "isLocked",
+ "isVisible",
+ "isFixed",
+ "parentId",
+ "eraser",
+ "eraserable",
+ "erasable",
+ "customType",
+ "fill_",
+ "scaleX",
+ "scaleY",
+ "top",
+ "left",
+ "width",
+ "height",
+ ]);
+ canvas.objects = canvas.objects.filter((v) => !excludedLayers.includes(v.layerId));
+
const simplifyLayersData = simplifyLayers(
- JSON.parse(JSON.stringify(this.layers.value))
+ JSON.parse(JSON.stringify(this.layers.value)),
+ excludedLayers
);
- console.log("获取画布JSON数据...", simplifyLayersData);
- return JSON.stringify({
- canvas: this.canvas.toJSON([
- "id",
- "type",
- "layerId",
- "layerName",
- "isBackground",
- "isLocked",
- "isVisible",
- "isFixed",
- "parentId",
- "eraser",
- "eraserable",
- "erasable",
- "customType",
- ]),
+ const data = {
+ canvas,
layers: simplifyLayersData, // 简化图层数据
- // layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据
version: "1.0", // 添加版本信息
timestamp: new Date().toISOString(), // 添加时间戳
canvasWidth: this.canvasWidth.value,
canvasHeight: this.canvasHeight.value,
canvasColor: this.canvasColor.value,
activeLayerId: this.layerManager?.activeLayerId?.value,
- });
+ };
+ this.FixJsonIdLoss(data);
+ console.log("获取画布JSON数据...", data);
+ return JSON.stringify(data);
} catch (error) {
console.error("获取画布JSON失败:", error);
throw new Error("获取画布JSON失败");
}
}
loadJSON(json, calllBack) {
- console.log("加载画布JSON数据:", json);
// 确保传入的json是字符串格式
if (typeof json === "object") {
@@ -1005,11 +1340,12 @@ export class CanvasManager {
// 解析JSON字符串
try {
const parsedJson = window.testCanvasJson || JSON.parse(json);
+ console.log("加载画布JSON数据:", parsedJson);
this.FixJsonIdLoss(parsedJson);
this.canvasWidth.value = parsedJson.canvasWidth || this.width;
this.canvasHeight.value = parsedJson.canvasHeight || this.height;
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
-
+
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const tempLayers = parsedJson?.layers || [];
@@ -1031,7 +1367,7 @@ export class CanvasManager {
// this.canvasHeight.value = parsedJson.canvasHeight || this.height;
// this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
- console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode);
+ // console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode);
// 重置视图变换以确保元素位置正确
this._resetViewportTransform(1);
@@ -1043,7 +1379,7 @@ export class CanvasManager {
// 清除当前画布内容
// this.canvas.clear(); // 清除画布内容 可以先去掉 这样加载闪动的情况就比较少 如果有问题 可以再打开
- console.log("清除当前画布内容", canvasData);
+ // console.log("清除当前画布内容", canvasData);
delete canvasData.clipPath; // 删除当前裁剪路径
// 加载画布数据
this.canvas.loadFromJSON(canvasData, async () => {
@@ -1070,8 +1406,9 @@ export class CanvasManager {
// }
try {
// 重置画布数据
- this.setCanvasSize(this.canvas.width, this.canvas.height);
- this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
+ await this.setCanvasSize(this.canvas.width, this.canvas.height);
+ await this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
+ await this.resetCanvasSizeByFixedLayer();
// 重新构建对象关系
// restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
// 验证图层关联关系 - 稳定后可以注释
@@ -1096,14 +1433,11 @@ export class CanvasManager {
// }
// 重载代码后支持回调中操作一些内容
- await calllBack?.();
// 确保所有对象的交互性正确设置
- await this.layerManager?.updateLayersObjectsInteractivity?.(
- false
- );
- console.log(this.layerManager.layers.value);
+ await this.layerManager?.updateLayersObjectsInteractivity?.();
+ await calllBack?.();
// 更新所有缩略图
setTimeout(() => {
this.updateAllThumbnails();
@@ -1147,6 +1481,388 @@ export class CanvasManager {
})
}
+
+ /**
+ * 创建其他图层:印花、颜色、元素...
+ * @param {Object} otherData - 其他图层数据
+ */
+ async createOtherLayers(otherData, isUpdate = false) {
+ if (!otherData) return console.warn("otherData 为空不需要添加");
+ this.canvas.loading.value = true;
+ let resolve = ()=>{};
+ this.awaitCanvasRun = ()=>(new Promise((v) => resolve = v))
+ const otherData_ = JSON.parse(JSON.stringify(otherData));
+ console.log("==========创建其他图层", otherData_);
+
+ const updateColor = !!otherData_.color;
+ const updateSpecialGroup = !!otherData_.printObject || !!otherData_.trims;
+ // 删除颜色图层和特殊组图层
+ const ids = [];
+ if(isUpdate){
+ updateColor && ids.push(SpecialLayerId.COLOR)
+ updateSpecialGroup && ids.push(SpecialLayerId.SPECIAL_GROUP)
+ }else{
+ ids.push(SpecialLayerId.COLOR)
+ ids.push(SpecialLayerId.SPECIAL_GROUP)
+ }
+ this.layers.value = this.layers.value.filter((layer) => {
+ if(ids.includes(layer.id)){
+ ids.push(...layer.children?.map((child) => child.id));
+ return false;
+ }
+ return true;
+ })
+ this.canvas.getObjects().forEach((v) => ids.includes(v.id) && this.canvas.remove(v))
+
+
+ // 创建颜色图层
+ otherData_.color && await this.createColorLayer(otherData_.color);
+
+ const printTrimsLayers = [];// 印花和元素图层
+ const singleLayers = [];// 平铺图层
+ otherData_.printObject?.prints?.forEach((print, index) => {// 印花
+ print.name = t("Canvas.Print") + (index + 1);
+ print.type = "print";
+ if(print.ifSingle){
+ printTrimsLayers.unshift({...print});
+ }else{
+ singleLayers.unshift({...print});
+ }
+ })
+ otherData_.trims?.prints?.forEach((trims, index) => {// 元素
+ trims.name = t("Canvas.Elements") + (index + 1);
+ trims.type = "trims";
+ printTrimsLayers.unshift({...trims});
+ })
+ if(isUpdate ? updateSpecialGroup : true){
+ await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
+ }
+ await this.changeCanvas();
+ console.log("==========创建其他图层成功");
+ resolve();
+ this.awaitCanvasRun = null;
+ this.canvas.loading.value = false;
+ }
+
+ // 设置画布对象的裁剪信息
+ async setObjecCliptInfo(tagObject, data){
+ const fixedLayerObj = this.getFixedLayerObject();
+ if(!fixedLayerObj) return console.warn("固定图层为空");
+ tagObject.set({
+ top: fixedLayerObj.top,
+ left: fixedLayerObj.left,
+ width: fixedLayerObj.width,
+ height: fixedLayerObj.height,
+ originX: fixedLayerObj.originX,
+ originY: fixedLayerObj.originY,
+ scaleX: fixedLayerObj.scaleX,
+ scaleY: fixedLayerObj.scaleY,
+ });
+ var object = fixedLayerObj;
+ const imageUrl = this.props.clothingImageUrl2;
+ if(imageUrl){
+ object = await new Promise((resolve, reject) => {
+ fabric.Image.fromURL(imageUrl, (imgObject) => {
+ tagObject.set({
+ width: imgObject.width,
+ height: imgObject.height,
+ });
+ resolve(imgObject);
+ }, { crossOrigin: "anonymous" });
+ });
+ }
+ const canvas = getObjectAlphaToCanvas(object, data);
+ const transparentMask = new fabric.Image(canvas, {
+ top: 0,
+ left: 0,
+ originX: fixedLayerObj.originX,
+ originY: fixedLayerObj.originY,
+ });
+ tagObject.set('clipPath', transparentMask);
+ }
+ async createColorLayer(color_){
+ const color = color_ || {r:0,g:0,b:0,a:0};
+ // if(findLayer(this.layers.value, SpecialLayerId.COLOR)) {
+ // return console.warn("画布中已存在颜色图层");
+ // }
+ console.log("==========添加颜色图层", color, this.layers.value.length)
+ // 创建颜色图层对象
+ const colorRect = new fabric.Rect({
+ id: SpecialLayerId.COLOR,
+ layerId: SpecialLayerId.COLOR,
+ layerName: t("Canvas.color"),
+ isVisible: true,
+ isLocked: true,
+ selectable: false,
+ hasControls: false,
+ hasBorders: false,
+ globalCompositeOperation: BlendMode.MULTIPLY,
+ originColor: color,
+ });
+ // await this.setObjecCliptInfo(colorRect);
+ const gradientObj = palletToFill(color);
+ const gradient = new fabric.Gradient({
+ type: 'linear',
+ gradientUnits: 'percentage',
+ ...gradientObj,
+ })
+ colorRect.set('fill', gradient);
+ this.canvas.add(colorRect);
+ // 创建颜色图层
+ const colorLayer = createLayer({
+ id: colorRect.layerId,
+ name: colorRect.layerName,
+ type: LayerType.SHAPE,
+ visible: colorRect.isVisible,
+ locked: colorRect.isLocked,
+ opacity: 1.0,
+ isFixedOther: true,
+ blendMode: BlendMode.MULTIPLY,
+ fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])],
+ })
+ const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground);
+ this.layers.value.splice(groupIndex, 0, colorLayer);
+ }
+
+ // 创建印花和元素图层
+ async createPrintTrimsLayers(printTrimsLayers, singleLayers){
+ // if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)) {
+ // return console.warn("画布中已存在印花和元素组图层");
+ // }
+ console.log("==========添加印花和元素图层组", printTrimsLayers, singleLayers)
+ const fixedLayerObj = this.getFixedLayerObject();
+ const flWidth = fixedLayerObj.width
+ const flHeight = fixedLayerObj.height
+ const flTop = fixedLayerObj.top
+ const flLeft = fixedLayerObj.left
+ const flScaleX = fixedLayerObj.scaleX
+ const flScaleY = fixedLayerObj.scaleY
+ const children = [];
+ // 添加印花和元素图层
+ for(let index = 0; index < printTrimsLayers.length; index++){
+ let item = printTrimsLayers[index];
+ let id = generateId("layer_image_");
+ let name = item.name;
+ let image = await new Promise(resolve => {
+ fabric.Image.fromURL(item.path, (fabricImage)=>{
+ resolve(fabricImage);
+ }, { crossOrigin: "anonymous" });
+ })
+ let left = flLeft - flWidth * flScaleX / 2 + (item.location?.[0] || 0) * flScaleX
+ let top = flTop - flHeight * flScaleY / 2 + (item.location?.[1] || 0) * flScaleY
+ let scaleX = flWidth * (item.scale?.[0] || 1) / image.width * flScaleX
+ let scaleY = flHeight * (item.scale?.[1] || 1) / image.height * flScaleY
+ let {x, y} = calculateRotatedTopLeftDeg(
+ image.width * scaleX,
+ image.height * scaleY,
+ left,
+ top,
+ 0,
+ item.angle || 0
+ )
+ let angle = item.angle || 0
+
+ let opacity = 1
+ let flipX = false;
+ let flipY = false;
+ let blendMode = BlendMode.MULTIPLY;
+ if(item.type === "trims") blendMode = BlendMode.NORMAL;// 元素正常
+ if(item.object){
+ opacity = item.object.opacity
+ flipX = item.object.flipX
+ flipY = item.object.flipY
+ if(item.object.blendMode) blendMode = item.object.blendMode;
+ }
+ image.set({
+ left: x,
+ top: y,
+ scaleX: scaleX,
+ scaleY: scaleY,
+ angle: angle,
+ opacity: opacity,
+ flipX: flipX,
+ flipY: flipY,
+ globalCompositeOperation: blendMode,
+ id: id,
+ layerId: id,
+ layerName: name,
+ selectable: true,
+ hasControls: true,
+ hasBorders: true,
+ isPrintTrims: true,
+ });
+ this.canvas.add(image);
+ let layer = createLayer({
+ id: id,
+ name: name,
+ type: LayerType.BITMAP,
+ visible: true,
+ locked: false,
+ opacity: opacity,
+ isPrintTrims: true,
+ blendMode: blendMode,
+ fabricObjects: [image.toObject(["id", "layerId", "layerName"])],
+ metadata: {sourceData: item, level2Type: item.level2Type},
+ })
+ children.push(layer);
+ };
+ // 添加平铺图层
+ for(let index = 0; index < singleLayers.length; index++){
+ let item = singleLayers[index];
+ let id = generateId("layer_image_");
+ let name = item.name;
+ let image = await new Promise(resolve => {
+ fabric.Image.fromURL(item.path, (fabricImage)=>{
+ const imgElement = fabricImage.getElement();
+ const tcanvas = document.createElement('canvas');
+ tcanvas.width = imgElement.width;
+ tcanvas.height = imgElement.height;
+ const ctx = tcanvas.getContext('2d');
+ ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
+ ctx.drawImage(imgElement, 0, 0);
+ resolve(tcanvas);
+ }, { crossOrigin: "anonymous" });
+ })
+ let scaleX_ = flWidth / image.width * (item.scale?.[0] || 1) / 5;
+ let scaleY_ = flHeight / image.height * (item.scale?.[1] || 1) / 5;
+ let scale = flWidth > flHeight ? scaleX_ : scaleY_;
+ let offsetX = (item.location?.[0] || 0) + image.width * scale / 2
+ let offsetY = (item.location?.[1] || 0) + image.height * scale / 2
+ let top = flTop - flHeight * flScaleY / 2
+ let left = flLeft - flWidth * flScaleX / 2
+ let scaleX = flScaleX
+ let scaleY = flScaleY
+ let opacity = 1
+ let angle = 0
+ let gapX = 0
+ let gapY = 0
+ let fillSource = image
+ let flipX = false;
+ let flipY = false;
+ let blendMode = BlendMode.MULTIPLY;
+ let fill_repeat = "repeat"
+ if(item.object){
+ top += item.object.top * flScaleY
+ left += item.object.left * flScaleX
+ scaleX *= item.object.scaleX
+ scaleY *= item.object.scaleY
+ opacity = item.object.opacity
+ angle = item.object.angle
+ flipX = item.object.flipX
+ flipY = item.object.flipY
+ blendMode = item.object.blendMode || BlendMode.MULTIPLY;
+ gapX = item.object.gapX
+ gapY = item.object.gapY
+ fillSource = imageAddGapToCanvas(image, gapX, gapY);
+ if(item.object.fill_repeat) fill_repeat = item.object.fill_repeat;
+ }
+ let rect = new fabric.Rect({
+ id: id,
+ layerId: id,
+ layerName: name,
+ width: flWidth,
+ height: flHeight,
+ top: top,
+ left: left,
+ scaleX: scaleX,
+ scaleY: scaleY,
+ opacity: opacity,
+ angle: angle,
+ flipX: flipX,
+ flipY: flipY,
+ globalCompositeOperation: blendMode,
+ fill: new fabric.Pattern({
+ source: fillSource,
+ repeat: fill_repeat,
+ patternTransform: createPatternTransform(scale, item.angle || 0),
+ offsetX: offsetX, // 水平偏移
+ offsetY: offsetY, // 垂直偏移
+ }),
+ fill_ : {
+ source: item.path,
+ gapX: gapX,
+ gapY: gapY,
+ width: image.width,
+ height: image.height,
+ },
+ isPrintTrims: true,
+ });
+ this.canvas.add(rect);
+ let layer = createLayer({
+ id: id,
+ name: name,
+ type: LayerType.BITMAP,
+ visible: true,
+ locked: true,
+ opacity: opacity,
+ isPrintTrims: true,
+ blendMode: BlendMode.MULTIPLY,
+ fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
+ metadata: {sourceData: item},
+ })
+ children.push(layer);
+ };
+ // if(children.length === 0){
+ // let layer = createLayer({
+ // id: generateId("layer_image_"),
+ // name: t("Canvas.EmptyLayer"),
+ // type: LayerType.BITMAP,
+ // visible: true,
+ // locked: false,
+ // opacity: 1.0,
+ // fabricObjects: [],
+ // })
+ // children.push(layer);
+ // }
+ if(children.length === 0) return;
+ const groupRect = new fabric.Rect({});
+ await this.setObjecCliptInfo(groupRect);
+ // 插入组图层
+ const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground);
+ const groupLayer = createLayer({
+ id: SpecialLayerId.SPECIAL_GROUP,
+ name: t("Canvas.PrintAndElementsGroup"),
+ type: LayerType.GROUP,
+ visible: true,
+ locked: false,
+ opacity: 1.0,
+ fabricObjects: [],
+ children: children,
+ clippingMask: groupRect.toObject(),
+ isPrintTrimsGroup: true,
+ });
+ this.layers.value.splice(groupIndex, 0, groupLayer);
+ }
+
+ /**
+ * 画布事件变更后
+ */
+ async changeCanvas(){
+ const fixedLayerObj = this.getFixedLayerObject();
+ if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj)
+ const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
+ if(colorObject){
+ const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP);
+ if(ids.length === 0){
+ ids.unshift(SpecialLayerId.SPECIAL_GROUP);
+ await this.setObjecCliptInfo(colorObject);
+ this.canvas.renderAll();
+ return;
+ }
+ const base64 = await this.exportManager.exportImage({layerIdArray2: ids, isEnhanceImg: true});
+ if(!base64) return console.warn("导出图片失败", base64)
+ const canvas = await base64ToCanvas(base64, fixedLayerObj.scaleX * 2, true);
+ const ctx = canvas.getContext('2d');
+ const width = fixedLayerObj.width;
+ const height = fixedLayerObj.height;
+ const x = (canvas.width - width) / 2;
+ const y = (canvas.height - height) / 2;
+ const data = ctx.getImageData(x, y, width, height);
+ await this.setObjecCliptInfo(colorObject, data);
+ this.canvas.renderAll();
+ }
+ }
+
/**
* 缩放红绿图模式内容以适应当前画布大小
* 确保衣服底图和红绿图永远在画布内可见
@@ -1270,6 +1986,7 @@ export class CanvasManager {
return fixedLayer.fabricObject || null;
}
+
/**
* 获取所有普通图层对象(包括红绿图)
* @returns {Array} 普通图层对象数组
@@ -1336,4 +2053,46 @@ export class CanvasManager {
return sizeMatch && positionMatch;
}
+
+ /**
+ * 键盘移动激活对象
+ * @param {String} direction 移动方向(up, down, left, right)
+ * @param {