diff --git a/.env.dev_build b/.env.dev_build
index da2a9a20..9de2530a 100644
--- a/.env.dev_build
+++ b/.env.dev_build
@@ -1,4 +1,4 @@
-VITE_USER_NODE_ENV = 'production'
+VITE_USER_NODE_ENV = 'development'
# VITE_APP_BASE_URL = 'https://aida.com.hk/test'
# VITE_APP_BASE_URL = 'http://18.167.251.121:10088'
# VITE_APP_BASE_URL = 'https://api.aida.com.hk'
diff --git a/.env.test b/.env.test
index ebe37860..cc8ff50b 100644
--- a/.env.test
+++ b/.env.test
@@ -1,12 +1,6 @@
-<<<<<<< HEAD
VITE_USER_NODE_ENV = 'development'
VITE_APP_BASE_URL = 'https://test.api.aida.com.hk'
# VITE_APP_BASE_URL = 'https://api.aida.com.hk'
-=======
-NODE_ENV = 'development'
-# VUE_APP_BASE_URL = 'https://api.aida.com.hk'
-VUE_APP_BASE_URL = 'https://test.api.aida.com.hk'
->>>>>>> 5d8304ce3ece21dd3200ffffb0c76e3ef55dd213
# VITE_APP_BASE_URL = 'http://18.167.251.121:10086'
# VITE_APP_BASE_URL = 'http://192.168.1.9:5567'
diff --git a/.gitignore b/.gitignore
index 118a6b88..61bc8e64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ dist.rar
*.sln
*.sw?
.eslintrc-auto-import.json
+components.d.ts
diff --git a/components.d.ts b/components.d.ts
deleted file mode 100644
index a74ec2fa..00000000
--- a/components.d.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint-disable */
-// @ts-nocheck
-// Generated by unplugin-vue-components
-// Read more: https://github.com/vuejs/core/pull/3399
-// biome-ignore lint: disable
-export {}
-
-/* prettier-ignore */
-declare module 'vue' {
- export interface GlobalComponents {
- ABadge: typeof import('ant-design-vue/es')['Badge']
- ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
- ADrawer: typeof import('ant-design-vue/es')['Drawer']
- AImage: typeof import('ant-design-vue/es')['Image']
- AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
- AModal: typeof import('ant-design-vue/es')['Modal']
- APagination: typeof import('ant-design-vue/es')['Pagination']
- APopover: typeof import('ant-design-vue/es')['Popover']
- ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
- ASelect: typeof import('ant-design-vue/es')['Select']
- ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
- ASlider: typeof import('ant-design-vue/es')['Slider']
- ASpin: typeof import('ant-design-vue/es')['Spin']
- ASwitch: typeof import('ant-design-vue/es')['Switch']
- ATable: typeof import('ant-design-vue/es')['Table']
- AUpload: typeof import('ant-design-vue/es')['Upload']
- ElCascader: typeof import('element-plus/es')['ElCascader']
- RouterLink: typeof import('vue-router')['RouterLink']
- RouterView: typeof import('vue-router')['RouterView']
- }
-}
diff --git a/dist.7z b/dist.7z
index 704ed151..471bee9f 100644
Binary files a/dist.7z and b/dist.7z differ
diff --git a/public/image/brush/PencilBrush-2.jpg b/public/image/brush/PencilBrush-2.jpg
deleted file mode 100644
index 4ae64cd3..00000000
Binary files a/public/image/brush/PencilBrush-2.jpg and /dev/null differ
diff --git a/public/image/brush/CrayonBrush.jpg b/public/image/brush/crayon.jpg
similarity index 100%
rename from public/image/brush/CrayonBrush.jpg
rename to public/image/brush/crayon.jpg
diff --git a/public/image/brush/fur.jpg b/public/image/brush/fur.jpg
new file mode 100644
index 00000000..6c4563c9
Binary files /dev/null and b/public/image/brush/fur.jpg differ
diff --git a/public/image/brush/InkBrush.jpg b/public/image/brush/ink.jpg
similarity index 100%
rename from public/image/brush/InkBrush.jpg
rename to public/image/brush/ink.jpg
diff --git a/public/image/brush/LongfurBrush.jpg b/public/image/brush/longFur.jpg
similarity index 100%
rename from public/image/brush/LongfurBrush.jpg
rename to public/image/brush/longFur.jpg
diff --git a/public/image/brush/MarkerBrush.jpg b/public/image/brush/marker.jpg
similarity index 100%
rename from public/image/brush/MarkerBrush.jpg
rename to public/image/brush/marker.jpg
diff --git a/public/image/brush/pen.jpg b/public/image/brush/pen.jpg
new file mode 100644
index 00000000..450979ad
Binary files /dev/null and b/public/image/brush/pen.jpg differ
diff --git a/public/image/brush/PencilBrush.jpg b/public/image/brush/pencil.jpg
similarity index 100%
rename from public/image/brush/PencilBrush.jpg
rename to public/image/brush/pencil.jpg
diff --git a/public/image/brush/RibbonBrush.jpg b/public/image/brush/ribbon.jpg
similarity index 100%
rename from public/image/brush/RibbonBrush.jpg
rename to public/image/brush/ribbon.jpg
diff --git a/public/image/brush/shaded.jpg b/public/image/brush/shaded.jpg
new file mode 100644
index 00000000..a60d35c0
Binary files /dev/null and b/public/image/brush/shaded.jpg differ
diff --git a/public/image/brush/spray.jpg b/public/image/brush/spray.jpg
new file mode 100644
index 00000000..a4ecd7e5
Binary files /dev/null and b/public/image/brush/spray.jpg differ
diff --git a/public/image/brush/imgBrush.webp b/public/image/brush/texture.jpg
similarity index 100%
rename from public/image/brush/imgBrush.webp
rename to public/image/brush/texture.jpg
diff --git a/public/image/brush/WritingBrush.jpg b/public/image/brush/writing.jpg
similarity index 100%
rename from public/image/brush/WritingBrush.jpg
rename to public/image/brush/writing.jpg
diff --git a/src/assets/iconfont2/iconfont.css b/src/assets/iconfont2/iconfont.css
index 1aba4c42..32375ef9 100644
--- a/src/assets/iconfont2/iconfont.css
+++ b/src/assets/iconfont2/iconfont.css
@@ -1,18 +1,22 @@
@font-face {
font-family: "iconfont"; /* Project id 4292253 */
- src: url('iconfont.woff2?t=1727415711578') format('woff2'),
- url('iconfont.woff?t=1727415711578') format('woff'),
- url('iconfont.ttf?t=1727415711578') format('truetype');
+ src: url('iconfont.woff2?t=1759888699816') format('woff2'),
+ url('iconfont.woff?t=1759888699816') format('woff'),
+ url('iconfont.ttf?t=1759888699816') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
- font-size: 1.8rem;
+ font-size: 1.6rem;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+.icon-clothes:before {
+ content: "\e8d1";
+}
+
.icon-caizhi:before {
content: "\e647";
}
diff --git a/src/assets/iconfont2/iconfont.json b/src/assets/iconfont2/iconfont.json
index 578a73da..21b485b7 100644
--- a/src/assets/iconfont2/iconfont.json
+++ b/src/assets/iconfont2/iconfont.json
@@ -1,170 +1,219 @@
{
- "id": "",
- "name": "",
+ "id": "4292253",
+ "name": "aida",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
- "icon_id": "124968799",
- "name": "外套_长款外套1@1x",
- "font_class": "a-waitao_changkuanwaitao11x",
- "unicode": "e66c",
- "unicode_decimal": 58988
+ "icon_id": "20183053",
+ "name": "clothes",
+ "font_class": "clothes",
+ "unicode": "e8d1",
+ "unicode_decimal": 59601
},
{
- "icon_id": "125198319",
- "name": "撤销 返回 撤回 上一步",
- "font_class": "fanchehui",
- "unicode": "e626",
- "unicode_decimal": 58918
+ "icon_id": "15739173",
+ "name": "材质",
+ "font_class": "caizhi",
+ "unicode": "e647",
+ "unicode_decimal": 58951
},
{
- "icon_id": "125198320",
- "name": "撤销 返回 撤回 上一步",
- "font_class": "chehui",
- "unicode": "e609",
- "unicode_decimal": 58889
+ "icon_id": "35023469",
+ "name": "IC-液化",
+ "font_class": "IC-yehua",
+ "unicode": "e61b",
+ "unicode_decimal": 58907
},
{
- "icon_id": "125524062",
- "name": "语言",
- "font_class": "yuyan",
- "unicode": "e85f",
- "unicode_decimal": 59487
+ "icon_id": "12096844",
+ "name": "上一层",
+ "font_class": "shangyiceng",
+ "unicode": "e751",
+ "unicode_decimal": 59217
},
{
- "icon_id": "126177191",
- "name": "标签",
- "font_class": "biaoqian",
- "unicode": "e603",
- "unicode_decimal": 58883
+ "icon_id": "16531912",
+ "name": "上一层",
+ "font_class": "shangyiceng1",
+ "unicode": "e604",
+ "unicode_decimal": 58884
},
{
- "icon_id": "126459101",
- "name": "并集",
- "font_class": "bingji",
- "unicode": "e620",
- "unicode_decimal": 58912
+ "icon_id": "24253227",
+ "name": "下一层",
+ "font_class": "xiayiceng",
+ "unicode": "e68a",
+ "unicode_decimal": 59018
},
{
- "icon_id": "126459102",
- "name": "并集",
- "font_class": "bingji1",
- "unicode": "e668",
- "unicode_decimal": 58984
+ "icon_id": "24253230",
+ "name": "上一层",
+ "font_class": "shangyiceng2",
+ "unicode": "e68b",
+ "unicode_decimal": 59019
},
{
- "icon_id": "126901286",
- "name": "点位",
- "font_class": "dianwei",
- "unicode": "e685",
- "unicode_decimal": 59013
- },
- {
- "icon_id": "130743908",
- "name": "编辑",
- "font_class": "bianji",
- "unicode": "e600",
- "unicode_decimal": 58880
- },
- {
- "icon_id": "130743909",
- "name": "圆形",
- "font_class": "circle",
- "unicode": "e64f",
- "unicode_decimal": 58959
- },
- {
- "icon_id": "130743910",
- "name": "三角形",
- "font_class": "sanjiaoxing",
- "unicode": "e615",
- "unicode_decimal": 58901
- },
- {
- "icon_id": "130743911",
- "name": "图层",
- "font_class": "tuceng",
- "unicode": "e632",
- "unicode_decimal": 58930
- },
- {
- "icon_id": "130743912",
- "name": "平移",
- "font_class": "move",
- "unicode": "e616",
- "unicode_decimal": 58902
- },
- {
- "icon_id": "130743913",
- "name": "橡皮",
- "font_class": "xiangpi_huaban1",
- "unicode": "e67b",
- "unicode_decimal": 59003
- },
- {
- "icon_id": "130743914",
- "name": "tx-fill-椭圆形",
- "font_class": "tx-fill-tuoyuanxing",
- "unicode": "e64c",
- "unicode_decimal": 58956
- },
- {
- "icon_id": "130743915",
- "name": "直线",
- "font_class": "zhixian",
- "unicode": "e602",
- "unicode_decimal": 58882
- },
- {
- "icon_id": "130743916",
- "name": "线",
- "font_class": "xian",
- "unicode": "ec5f",
- "unicode_decimal": 60511
- },
- {
- "icon_id": "130743917",
- "name": "正方形",
- "font_class": "checkbox-full",
- "unicode": "ea6f",
- "unicode_decimal": 60015
- },
- {
- "icon_id": "130743918",
- "name": "图层",
- "font_class": "tuceng1",
- "unicode": "e62d",
- "unicode_decimal": 58925
- },
- {
- "icon_id": "130751283",
+ "icon_id": "3663275",
"name": "审批",
"font_class": "shenpi",
"unicode": "e6a1",
"unicode_decimal": 59041
},
{
- "icon_id": "130751284",
+ "icon_id": "7638976",
"name": "用户",
"font_class": "yonghu",
"unicode": "e617",
"unicode_decimal": 58903
},
{
- "icon_id": "130751285",
+ "icon_id": "9775414",
"name": "使用次数",
"font_class": "usetime",
"unicode": "e601",
"unicode_decimal": 58881
},
{
- "icon_id": "130751286",
+ "icon_id": "16843615",
"name": "下拉",
"font_class": "xiala",
"unicode": "e634",
"unicode_decimal": 58932
+ },
+ {
+ "icon_id": "1264",
+ "name": "编辑",
+ "font_class": "bianji",
+ "unicode": "e600",
+ "unicode_decimal": 58880
+ },
+ {
+ "icon_id": "755612",
+ "name": "圆形",
+ "font_class": "circle",
+ "unicode": "e64f",
+ "unicode_decimal": 58959
+ },
+ {
+ "icon_id": "3101162",
+ "name": "三角形",
+ "font_class": "sanjiaoxing",
+ "unicode": "e615",
+ "unicode_decimal": 58901
+ },
+ {
+ "icon_id": "6774075",
+ "name": "图层",
+ "font_class": "tuceng",
+ "unicode": "e632",
+ "unicode_decimal": 58930
+ },
+ {
+ "icon_id": "10905244",
+ "name": "平移",
+ "font_class": "move",
+ "unicode": "e616",
+ "unicode_decimal": 58902
+ },
+ {
+ "icon_id": "14421659",
+ "name": "橡皮",
+ "font_class": "xiangpi_huaban1",
+ "unicode": "e67b",
+ "unicode_decimal": 59003
+ },
+ {
+ "icon_id": "14718690",
+ "name": "tx-fill-椭圆形",
+ "font_class": "tx-fill-tuoyuanxing",
+ "unicode": "e64c",
+ "unicode_decimal": 58956
+ },
+ {
+ "icon_id": "17521049",
+ "name": "直线",
+ "font_class": "zhixian",
+ "unicode": "e602",
+ "unicode_decimal": 58882
+ },
+ {
+ "icon_id": "17581689",
+ "name": "线",
+ "font_class": "xian",
+ "unicode": "ec5f",
+ "unicode_decimal": 60511
+ },
+ {
+ "icon_id": "18175800",
+ "name": "正方形",
+ "font_class": "checkbox-full",
+ "unicode": "ea6f",
+ "unicode_decimal": 60015
+ },
+ {
+ "icon_id": "26998795",
+ "name": "图层",
+ "font_class": "tuceng1",
+ "unicode": "e62d",
+ "unicode_decimal": 58925
+ },
+ {
+ "icon_id": "31762941",
+ "name": "点位",
+ "font_class": "dianwei",
+ "unicode": "e685",
+ "unicode_decimal": 59013
+ },
+ {
+ "icon_id": "8722601",
+ "name": "并集",
+ "font_class": "bingji",
+ "unicode": "e620",
+ "unicode_decimal": 58912
+ },
+ {
+ "icon_id": "15192904",
+ "name": "并集",
+ "font_class": "bingji1",
+ "unicode": "e668",
+ "unicode_decimal": 58984
+ },
+ {
+ "icon_id": "17863630",
+ "name": "标签",
+ "font_class": "biaoqian",
+ "unicode": "e603",
+ "unicode_decimal": 58883
+ },
+ {
+ "icon_id": "16399020",
+ "name": "语言",
+ "font_class": "yuyan",
+ "unicode": "e85f",
+ "unicode_decimal": 59487
+ },
+ {
+ "icon_id": "4240742",
+ "name": "撤销 返回 撤回 上一步",
+ "font_class": "fanchehui",
+ "unicode": "e626",
+ "unicode_decimal": 58918
+ },
+ {
+ "icon_id": "6126117",
+ "name": "撤销 返回 撤回 上一步",
+ "font_class": "chehui",
+ "unicode": "e609",
+ "unicode_decimal": 58889
+ },
+ {
+ "icon_id": "33174601",
+ "name": "外套_长款外套1@1x",
+ "font_class": "a-waitao_changkuanwaitao11x",
+ "unicode": "e66c",
+ "unicode_decimal": 58988
}
]
}
diff --git a/src/assets/iconfont2/iconfont.ttf b/src/assets/iconfont2/iconfont.ttf
index 905838aa..d708a6c9 100644
Binary files a/src/assets/iconfont2/iconfont.ttf and b/src/assets/iconfont2/iconfont.ttf differ
diff --git a/src/assets/iconfont2/iconfont.woff b/src/assets/iconfont2/iconfont.woff
index a1c35fd0..2f9fcd35 100644
Binary files a/src/assets/iconfont2/iconfont.woff and b/src/assets/iconfont2/iconfont.woff differ
diff --git a/src/assets/iconfont2/iconfont.woff2 b/src/assets/iconfont2/iconfont.woff2
index 3fc507c2..e48d0fb0 100644
Binary files a/src/assets/iconfont2/iconfont.woff2 and b/src/assets/iconfont2/iconfont.woff2 differ
diff --git a/src/assets/icons/CClothes.svg b/src/assets/icons/CClothes.svg
new file mode 100644
index 00000000..bcbd739a
--- /dev/null
+++ b/src/assets/icons/CClothes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/CHelp.svg b/src/assets/icons/CHelp.svg
new file mode 100644
index 00000000..f8b7726b
--- /dev/null
+++ b/src/assets/icons/CHelp.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/assets/icons/CLibrary.svg b/src/assets/icons/CLibrary.svg
new file mode 100644
index 00000000..ce0f94c5
--- /dev/null
+++ b/src/assets/icons/CLibrary.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/src/assets/icons/CSelect.svg b/src/assets/icons/CSelect.svg
index 717e534e..c118de07 100644
--- a/src/assets/icons/CSelect.svg
+++ b/src/assets/icons/CSelect.svg
@@ -1 +1,19 @@
-
\ No newline at end of file
+
+
+
diff --git a/src/assets/icons/DetailCompare.svg b/src/assets/icons/DetailCompare.svg
new file mode 100644
index 00000000..aeceaf6b
--- /dev/null
+++ b/src/assets/icons/DetailCompare.svg
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/icons/Folder.svg b/src/assets/icons/Folder.svg
new file mode 100644
index 00000000..abfd060d
--- /dev/null
+++ b/src/assets/icons/Folder.svg
@@ -0,0 +1,22 @@
+
+
+
diff --git a/src/assets/icons/editFrontBack.svg b/src/assets/icons/editFrontBack.svg
new file mode 100644
index 00000000..6977c448
--- /dev/null
+++ b/src/assets/icons/editFrontBack.svg
@@ -0,0 +1,60 @@
+
+
+
diff --git a/src/assets/images/icon/library.png b/src/assets/images/icon/library.png
new file mode 100644
index 00000000..60b8e7bf
Binary files /dev/null and b/src/assets/images/icon/library.png differ
diff --git a/src/assets/images/icon/selected.png b/src/assets/images/icon/selected.png
new file mode 100644
index 00000000..149bc06a
Binary files /dev/null and b/src/assets/images/icon/selected.png differ
diff --git a/src/assets/images/icon/upload_black.png b/src/assets/images/icon/upload_black.png
new file mode 100644
index 00000000..635249c7
Binary files /dev/null and b/src/assets/images/icon/upload_black.png differ
diff --git a/src/assets/images/icon/upload_gray.png b/src/assets/images/icon/upload_gray.png
new file mode 100644
index 00000000..4e109e29
Binary files /dev/null and b/src/assets/images/icon/upload_gray.png differ
diff --git a/src/assets/style/style.css b/src/assets/style/style.css
index 0ebbe619..96ae3887 100644
--- a/src/assets/style/style.css
+++ b/src/assets/style/style.css
@@ -62,6 +62,9 @@ li {
height: 100%;
margin: 0 auto;
}
+.ant-dropdown-menu {
+ border-radius: 1rem;
+}
.button_second {
width: 14rem;
text-align: center;
@@ -237,7 +240,7 @@ li {
color: #fff;
background-color: #000;
text-align: center;
- font-weight: 600;
+ font-weight: 500;
border: 2px solid #000;
cursor: pointer;
box-sizing: border-box;
@@ -2106,7 +2109,7 @@ textarea:focus {
.generalMenu_printModel_upload .input_border .input_box_btnBox .upload_item .upload_file_item,
.generate .input_border .input_box_btnBox .upload_item .upload_file_item {
position: absolute;
- left: 0;
+ left: 1rem;
top: 50%;
transform: translateY(-50%);
height: 4.7rem;
@@ -2207,7 +2210,7 @@ textarea:focus {
cursor: pointer;
transition: all 0.3s;
position: relative;
- width: 4rem;
+ width: 3rem;
display: flex;
min-height: 3rem;
justify-content: center;
diff --git a/src/assets/style/style.less b/src/assets/style/style.less
index db55a3e7..8e0a7922 100644
--- a/src/assets/style/style.less
+++ b/src/assets/style/style.less
@@ -63,6 +63,9 @@ input:focus{
height: 100%;
margin: 0 auto;
}
+.ant-dropdown-menu{
+ border-radius: 1rem;
+}
.button_second{
width: 14rem;
text-align: center;
@@ -241,7 +244,7 @@ input:focus{
color: #fff;
background-color: #000;
text-align: center;
- font-weight: 600;
+ font-weight: 500;
border: 2px solid #000;
cursor: pointer;
box-sizing: border-box;
@@ -2105,7 +2108,7 @@ textarea:focus{
width: 4.7rem;
.upload_file_item{
position: absolute;
- left: 0;
+ left: 1rem;
top: 50%;
transform: translateY(-50%);
height: 4.7rem;
@@ -2150,7 +2153,7 @@ textarea:focus{
cursor: pointer;
transition: all .3s;
position: relative;
- width: 4rem;
+ width: 3rem;
align-items: center;
justify-content: center;
diff --git a/src/assets/work/1.PNG b/src/assets/work/1.PNG
deleted file mode 100644
index c91dfdf7..00000000
Binary files a/src/assets/work/1.PNG and /dev/null differ
diff --git a/src/assets/work/2.PNG b/src/assets/work/2.PNG
deleted file mode 100644
index 81540426..00000000
Binary files a/src/assets/work/2.PNG and /dev/null differ
diff --git a/src/assets/work/3.PNG b/src/assets/work/3.PNG
deleted file mode 100644
index f877ece1..00000000
Binary files a/src/assets/work/3.PNG and /dev/null differ
diff --git a/src/assets/work/5.PNG b/src/assets/work/5.PNG
deleted file mode 100644
index f0d87430..00000000
Binary files a/src/assets/work/5.PNG and /dev/null differ
diff --git a/src/assets/work/6.PNG b/src/assets/work/6.PNG
deleted file mode 100644
index 9ccafd2a..00000000
Binary files a/src/assets/work/6.PNG and /dev/null differ
diff --git a/src/assets/work/IMG_0001.PNG b/src/assets/work/IMG_0001.PNG
deleted file mode 100644
index 139e4ba3..00000000
Binary files a/src/assets/work/IMG_0001.PNG and /dev/null differ
diff --git a/src/assets/work/IMG_0008.PNG b/src/assets/work/IMG_0008.PNG
deleted file mode 100644
index b999a075..00000000
Binary files a/src/assets/work/IMG_0008.PNG and /dev/null differ
diff --git a/src/assets/work/group_hide.PNG b/src/assets/work/group_hide.PNG
deleted file mode 100644
index 8ea506bb..00000000
Binary files a/src/assets/work/group_hide.PNG and /dev/null differ
diff --git a/src/assets/work/group_show.PNG b/src/assets/work/group_show.PNG
deleted file mode 100644
index 3d1e2d45..00000000
Binary files a/src/assets/work/group_show.PNG and /dev/null differ
diff --git a/src/assets/work/图片1.png b/src/assets/work/图片1.png
deleted file mode 100644
index 9f7d2b27..00000000
Binary files a/src/assets/work/图片1.png and /dev/null differ
diff --git a/src/assets/work/图片2.png b/src/assets/work/图片2.png
deleted file mode 100644
index 015e64f3..00000000
Binary files a/src/assets/work/图片2.png and /dev/null differ
diff --git a/src/assets/work/图片4.png b/src/assets/work/图片4.png
deleted file mode 100644
index afab67c2..00000000
Binary files a/src/assets/work/图片4.png and /dev/null differ
diff --git a/src/assets/work/图片5.png b/src/assets/work/图片5.png
deleted file mode 100644
index a43e8b87..00000000
Binary files a/src/assets/work/图片5.png and /dev/null differ
diff --git a/src/assets/work/图片6.png b/src/assets/work/图片6.png
deleted file mode 100644
index dea147d3..00000000
Binary files a/src/assets/work/图片6.png and /dev/null differ
diff --git a/src/assets/work/图片7.png b/src/assets/work/图片7.png
deleted file mode 100644
index 50276080..00000000
Binary files a/src/assets/work/图片7.png and /dev/null differ
diff --git a/src/assets/work/图片8.png b/src/assets/work/图片8.png
deleted file mode 100644
index 54914c87..00000000
Binary files a/src/assets/work/图片8.png and /dev/null differ
diff --git a/src/assets/work/图片9.png b/src/assets/work/图片9.png
deleted file mode 100644
index 6755cd89..00000000
Binary files a/src/assets/work/图片9.png and /dev/null differ
diff --git a/src/component/Account/frontPage/bindPage.vue b/src/component/Account/frontPage/bindPage.vue
index 7ec33f51..825781f4 100644
--- a/src/component/Account/frontPage/bindPage.vue
+++ b/src/component/Account/frontPage/bindPage.vue
@@ -111,28 +111,46 @@ export default defineComponent({
.catch((res) => {accountHomeData.loadingShow = false});
}
const ungroupWeiXinModel = ()=>{
- Https.axiosGet(Https.httpUrls.unbindWeChat,).then((rv)=>{
- message.success(t('frontPage.jsContent1'));
- let value = {
- accountExtendList:{
- WeChat:undefined,
- Google:accountHomeData.userDetail.accountExtendList?.Google
- }
+ Modal.confirm({
+ title: t('frontPage.UnbindTip'),
+ okText: t('Yes'),
+ cancelText: t('No'),
+ mask:false,
+ centered:true,
+ onOk() {
+ Https.axiosGet(Https.httpUrls.unbindWeChat,).then((rv)=>{
+ message.success(t('frontPage.jsContent1'));
+ let value = {
+ accountExtendList:{
+ WeChat:undefined,
+ Google:accountHomeData.userDetail.accountExtendList?.Google
+ }
+ }
+ store.commit("upUserDetail", value)
+ })
}
- store.commit("upUserDetail", value)
- })
+ });
}
const ungroupGoogleModel = ()=>{
- Https.axiosGet(Https.httpUrls.unbindGoogle,).then((rv)=>{
- let value = {
- accountExtendList:{
- WeChat:accountHomeData.userDetail.accountExtendList?.WeChat,
- Google:undefined,
- }
+ Modal.confirm({
+ title: t('frontPage.UnbindTip'),
+ okText: t('Yes'),
+ cancelText: t('No'),
+ mask:false,
+ centered:true,
+ onOk() {
+ Https.axiosGet(Https.httpUrls.unbindGoogle,).then((rv)=>{
+ let value = {
+ accountExtendList:{
+ WeChat:accountHomeData.userDetail.accountExtendList?.WeChat,
+ Google:undefined,
+ }
+ }
+ store.commit("upUserDetail", value)
+ message.success(t('frontPage.jsContent1'));
+ })
}
- store.commit("upUserDetail", value)
- message.success(t('frontPage.jsContent1'));
- })
+ });
}
const modifyEmail = ()=>{
bindPageDom.bindEmail.init('Modify')
diff --git a/src/component/Account/frontPage/mylnformation.vue b/src/component/Account/frontPage/mylnformation.vue
index 80175852..72da23b4 100644
--- a/src/component/Account/frontPage/mylnformation.vue
+++ b/src/component/Account/frontPage/mylnformation.vue
@@ -117,13 +117,12 @@ export default defineComponent({
let value = {
country:accountHomeData.Country,
title:accountHomeData.selectSex,
- userName:accountHomeData.editUserName,
surname:accountHomeData.surname,
givenName:accountHomeData.givenName,
}
store.commit('upUserDetail',value)
accountHomeData.loadingShow = false
- message.success(t('exportModel.jsContent7'))
+ message.success(t('account.jsContent13'))
}).catch((err:any)=>{
accountHomeData.loadingShow = false
})
diff --git a/src/component/Administrator/SE/designDetailList/index.vue b/src/component/Administrator/SE/designDetailList/index.vue
index 89ac71fd..cb046d96 100644
--- a/src/component/Administrator/SE/designDetailList/index.vue
+++ b/src/component/Administrator/SE/designDetailList/index.vue
@@ -1,12 +1,11 @@
-
+
-
-
{{ $t('admin.StartDate') }}:
+
{{ $t("admin.StartDate") }}:
- {{ $t('admin.StartTime') }}:
-
+ {{ $t("admin.StartTime") }}:
+
- {{ $t('admin.Email') }}:
-
-
+
{{ $t("admin.Email") }}:
+
+
-
{{ $t('admin.UserName') }}:
-
-
+
{{ $t("admin.UserName") }}:
+
+
+
+ Organization Name:
+
+
-
{{ $t('admin.search') }}
+
+ {{ $t("admin.search") }}
+
@@ -73,205 +93,207 @@
>
-
+
\ No newline at end of file
diff --git a/src/component/Administrator/TestClickData.vue b/src/component/Administrator/TestClickData.vue
index f5bf6b81..ea76f449 100644
--- a/src/component/Administrator/TestClickData.vue
+++ b/src/component/Administrator/TestClickData.vue
@@ -1,12 +1,11 @@
-
@@ -73,270 +89,272 @@
>
-
+
\ No newline at end of file
diff --git a/src/component/Administrator/organization/organization.vue b/src/component/Administrator/organization/organization.vue
index f3fb8d79..0488bed0 100644
--- a/src/component/Administrator/organization/organization.vue
+++ b/src/component/Administrator/organization/organization.vue
@@ -1,228 +1,305 @@
-
-
-
+
+
-
+
Create Time:
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js
index ad5addc5..62de515a 100644
--- a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js
@@ -10,6 +10,8 @@ import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js";
import { ClearSelectionCommand } from "./LassoCutoutCommand.js";
import { ClearSelectionContentCommand } from "./ClearSelectionContentCommand.js";
+import i18n from "@/lang/index.ts";
+const { t } = i18n.global;
/**
* 剪切选区到新图层命令
@@ -36,7 +38,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
this.groupId = options.groupId || generateId("lasso-copy-group-");
- this.groupName = options.groupName || `选区组`;
+ this.groupName = options.groupName || t(`Canvas.ConstituencyGroup`);
this.groupLayer = null; // 新增:保存组图层的引用
this.originalLayersLength = 0; // 新增:保存原始图层数量
@@ -179,7 +181,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
// 创建新的组图层
this.groupLayer = createLayer({
id: this.groupId,
- name: this.groupName || `选区组`,
+ name: this.groupName || t(`Canvas.ConstituencyGroup`),
type: LayerType.GROUP,
visible: true,
locked: false,
diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js
index 8897a6bd..5c4a50b2 100644
--- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js
+++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js
@@ -11,6 +11,10 @@ import {
} from "./LayerCommands.js";
import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js";
+import i18n from "@/lang/index.ts";
+const { t } = i18n.global;
+
+
/**
* 套索抠图命令
@@ -37,7 +41,7 @@ export class LassoCutoutCommand extends CompositeCommand {
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
this.groupId = options.groupId || generateId("lasso-group-");
- this.groupName = options.groupName || `选区组`;
+ this.groupName = options.groupName || t(`Canvas.ConstituencyGroup`);
this.clippingMaskId = generateId("clipping-mask-");
@@ -238,7 +242,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 创建新的组图层
this.groupLayer = createLayer({
id: this.groupId,
- name: this.groupName || `选区组`,
+ name: this.groupName || t(`Canvas.ConstituencyGroup`),
type: LayerType.GROUP,
visible: true,
locked: false,
diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js
index 06a3ade1..a3ad1eaf 100644
--- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js
@@ -13,6 +13,9 @@ import {
import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js";
import { ToolCommand } from "./ToolCommands.js";
+import i18n from "@/lang/index.ts";
+const { t } = i18n.global;
+
/**
* 套索抠图命令
@@ -39,7 +42,7 @@ export class LassoCutoutCommand extends CompositeCommand {
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
this.groupId = options.groupId || generateId("lasso-group-");
- this.groupName = options.groupName || `选区组`;
+ this.groupName = options.groupName || t(`Canvas.ConstituencyGroup`);
this.clippingMaskId = generateId("clipping-mask-");
@@ -220,16 +223,26 @@ export class LassoCutoutCommand extends CompositeCommand {
await clearSelectionCmd.execute();
this.executedCommands.push(clearSelectionCmd);
- const topLayerIndex = this.layerManager.layers.value.findIndex(
- (layer) => layer.id === this.originalLayer.id
- );
+ const layers = this.layerManager.layers.value;
+ var topLayerIndex = 0;
+ layers.forEach((layer, index) => {
+ if (layer.id === this.originalLayer.id) {
+ topLayerIndex = index;
+ }else if (layer.children.length > 0) {
+ layer.children.forEach((childLayer) => {
+ if (childLayer.id === this.originalLayer.id) {
+ topLayerIndex = index;
+ }
+ });
+ }
+ });
// const selectLayer = this.layerManager.layers.value[topLayerIndex];
// 创建新的组图层
this.groupLayer = createLayer({
id: this.groupId,
- name: this.groupName || `选区组`,
+ name: this.groupName || t(`Canvas.ConstituencyGroup`),
type: LayerType.GROUP,
visible: true,
locked: false,
@@ -244,7 +257,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// });
const selectLayer = createLayer({
- name: `选区空图层`,
+ name: t(`Canvas.ConstituencyEmptyLayer`),
type: LayerType.EMPTY,
visible: true,
locked: false,
@@ -284,6 +297,7 @@ export class LassoCutoutCommand extends CompositeCommand {
this.groupLayer.children.push(selectLayer);
// 插入新组图层
+ console.log("新增套索添加index", topLayerIndex);
this.layerManager.layers.value.splice(topLayerIndex, 0, this.groupLayer);
this.layerManager.activeLayerId.value = selectLayer.id; // 设置新组图层为活动图层
diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
index 97f551be..1dffba96 100644
--- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
@@ -116,7 +116,7 @@ export class AddLayerCommand extends Command {
parentLayer.children.splice(insertIndex, 0, newLayer);
console.log(
- `新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
+ `新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
);
} else {
// 当前激活图层是一级图层
@@ -2569,7 +2569,6 @@ export class CreateImageLayerCommand extends Command {
this.fabricImage = options.fabricImage;
this.toolManager = options.toolManager;
this.layerName = options.layerName || null;
-
this.imageId = generateId("image_");
// 存储执行过程中的结果
@@ -2617,6 +2616,18 @@ export class CreateImageLayerCommand extends Command {
this.commands.push(createLayerCmd);
this.executedCommands.push(createLayerCmd);
+ // 新加功能-选区套索内添加图片自动居中
+ const { parent } = findLayerRecursively(this.layerManager.layers.value, this.newLayerId);
+ if(parent && parent.selectObject){
+ let {top,left,width,height} = parent.selectObject;
+ const ltop = top + height / 2;
+ const lleft = left + width / 2;
+ this.fabricImage.set({
+ top: ltop,
+ left: lleft,
+ })
+ }
+
// 2. 添加图片对象到图层命令
const addObjectCmd = new AddObjectToLayerCommand({
canvas: this.layerManager.canvas,
@@ -4197,6 +4208,7 @@ export class RemoveChildLayerCommand extends Command {
this.isActiveLayer = this.layerId === this.activeLayerId.value;
this.originalObjects = this.canvas.getObjects().filter((obj) => {
+ obj.parentId = this.parentId;
return obj.layerId === this.layerId;
});
}
diff --git a/src/component/Canvas/CanvasEditor/commands/TextCommands.js b/src/component/Canvas/CanvasEditor/commands/TextCommands.js
index f4d52013..24c47c62 100644
--- a/src/component/Canvas/CanvasEditor/commands/TextCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/TextCommands.js
@@ -1,6 +1,8 @@
import { generateId, optimizeCanvasRendering } from "../utils/helper";
import { createLayer, LayerType, OperationType } from "../utils/layerHelper";
import { Command } from "./Command";
+import i18n from "@/lang/index.ts";
+const { t } = i18n.global;
/**
* 文本内容命令
@@ -329,7 +331,7 @@ export class CreateTextCommand extends Command {
// 默认文本属性
this.defaultOptions = {
- text: "双击编辑文本",
+ text: t('Canvas.DoubleClickText'),
fontFamily: "Arial",
fontSize: 24,
fontWeight: "normal",
diff --git a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue
index 3d08bb67..6ef70614 100644
--- a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue
@@ -362,6 +362,11 @@ watch(
setBrushSize(newSize);
}
);
+watch(()=>isVisible.value, (newVisible) => {
+ if (newVisible) {
+ setBrushSize(brushSize.value);
+ }
+})
// 监听brushOpacity的变化,更新到BrushStore
watch(
diff --git a/src/component/Canvas/CanvasEditor/components/BrushPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushPanel.vue
index 0688d390..b625358b 100644
--- a/src/component/Canvas/CanvasEditor/components/BrushPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/BrushPanel.vue
@@ -14,7 +14,8 @@
@click="setBrushTypeWithCommand(brush.id)"
:class="['brush-type-item', { active: brushStore.state.type === brush.id }]"
>
-
+
+
{{ brush.name }}
@@ -189,7 +190,7 @@
- {{ $t("上传的纹理") }}
+ {{ $t("Canvas.UploadedTexture") }}
@@ -200,7 +201,7 @@
+
-
{{ $t("上传纹理") }}
+
{{ $t("Canvas.UploadTexture") }}
×
@@ -534,6 +535,9 @@ import {
TextureUploadCommand,
} from "../commands/BrushCommands";
import { debounce } from "lodash-es";
+import { useI18n } from "vue-i18n";
+const { t } = useI18n();
+
// 从工具管理器获取可用笔刷类型
const toolManager = inject("toolManager");
@@ -872,7 +876,7 @@ function applyPresetWithCommand(presetIndex) {
// 保存当前设置为预设
function saveCurrentAsPreset() {
// 简单实现,可以后续优化为弹窗输入名称
- const name = prompt("请输入预设名称:", `预设 ${BrushStore.state.presets.length + 1}`);
+ const name = prompt(t('Canvas.presetNamePrompt'), `${t('Canvas.preset')} ${BrushStore.state.presets.length + 1}`);
if (name) {
const presetIndex = BrushStore.saveCurrentAsPreset(name);
// 应用新创建的预设(可选)
@@ -886,6 +890,7 @@ onMounted(() => {
const availableBrushes = toolManager.brushManager
.getBrushTypes()
?.filter((brush) => brush.id !== "eraser");
+ console.log(availableBrushes)
BrushStore.setAvailableBrushes(availableBrushes);
}
});
@@ -1178,6 +1183,8 @@ const brushStore = BrushStore;
margin-bottom: 8px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.02);
+ object-fit: contain;
+ background-color: #fff;
}
/* 保持笔刷预览内容样式一致 */
diff --git a/src/component/Canvas/CanvasEditor/components/KeyboardShortcutHelp.vue b/src/component/Canvas/CanvasEditor/components/KeyboardShortcutHelp.vue
index edba6ecc..e1d19484 100644
--- a/src/component/Canvas/CanvasEditor/components/KeyboardShortcutHelp.vue
+++ b/src/component/Canvas/CanvasEditor/components/KeyboardShortcutHelp.vue
@@ -136,7 +136,7 @@ function convertShortcuts(managerShortcuts) {
action: actionDisplay,
windows: shortcut.key.replace(/cmdOrCtrl\+/g, "Ctrl+"),
mac: shortcut.key.replace(/cmdOrCtrl\+/g, "⌘+"),
- touch: shortcut.touch || "触控界面点击对应工具",
+ touch: shortcut.touch || t('Canvas.touchTools'),
displayKey: shortcut.displayKey,
});
}
@@ -326,8 +326,8 @@ function getShortcutsByCategory(category) {
iPad
iOS
Android
- 其他
- (触控设备)
+ {{ $t('Canvas.other') }}
+ ({{ $t('Canvas.touchDevice') }})
@@ -421,14 +421,14 @@ function getShortcutsByCategory(category) {
-->
-
触控设备提示
+
{{ $t('Canvas.touchDevicePrompts') }}
- - 长按图层面板可访问更多选项
- - 双击元素可快速进入编辑模式
- - 双指拖动可平移画布
- - 双指捏合可缩放画布
- - 双指连按可显示元素变换控制点
- - 三指左右滑动可进行撤销/重做操作
+ - {{ $t('Canvas.touchDevicePrompts_1') }}
+ - {{ $t('Canvas.touchDevicePrompts_2') }}
+ - {{ $t('Canvas.touchDevicePrompts_3') }}
+ - {{ $t('Canvas.touchDevicePrompts_4') }}
+ - {{ $t('Canvas.touchDevicePrompts_5') }}
+ - {{ $t('Canvas.touchDevicePrompts_6') }}
diff --git a/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue b/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue
index c8946485..081bf615 100644
--- a/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue
@@ -258,6 +258,23 @@ const setClosePanel = ()=>{
closePanel.value = !closePanel.value
}
+// 工具管理器和画布管理器
+const toolManager = inject("toolManager");
+const canvasManager = inject("canvasManager");
+
+watch(size, (newSize, oldSize) => {
+ setBrushIndicatorSize(newSize)
+
+})
+// 设置笔刷指示器大小
+function setBrushIndicatorSize(size) {
+ // 如果工具管理器存在,立即应用此更改
+ console.log(`=========== ${size}`,toolManager);
+ if (toolManager) {
+ toolManager.updateBrushIndicatorSize(size);
+ }
+}
+
// 监听当前工具变化 - 参考 SelectionPanel 的实现方式
watch(
() => props.activeTool,
@@ -269,10 +286,11 @@ watch(
// 如果面板未显示且有合适的目标对象,则显示面板
if (!visible.value) {
visible.value = true;
- closePanel.value = true
+ closePanel.value = true
// 检查是否有可液化的对象
checkAndShowPanel();
}
+ setBrushIndicatorSize(size.value)
} else {
visible.value = false; // 切换到其他工具时隐藏面板
// 切换到其他工具,隐藏液化面板
@@ -444,16 +462,16 @@ function showPanel(event) {
if (detail.layerStatus && detail.layerStatus.message) {
console.log("液化操作提示:", detail.layerStatus.message);
Modal.error({
- title: "错误提示",
+ title: t('Canvas.ErrorMessage'),
content: detail.layerStatus.message,
- okText: "确定",
+ okText: t('Canvas.confirm'),
centered: true,
});
} else {
Modal.error({
- title: "错误提示",
- content: "未选择有效图像或图层不适合液化操作",
- okText: "确定",
+ title: t('Canvas.ErrorMessage'),
+ content: t('Canvas.LiquidationError'),
+ okText: t('Canvas.confirm'),
centered: true,
});
console.log("未选择有效图像或图层不适合液化操作");
@@ -1634,24 +1652,26 @@ function stopPressTimer() {
color: #333;
border: 1px solid rgba(0, 0, 0, 0.05);
padding-bottom: 12px;
- &.active{
- transform: translateY(100%);
- > .btn{
- > i{
- transform: rotate(90deg);
+ &.active{
+ transform: translateY(100%);
+ > .btn{
+ > i{
+ transform: rotate(90deg);
+ }
}
}
-}
> .btn{
- width: 100%;
- text-align: center;
- cursor: pointer;
-
- > i{
- font-size: 1.4rem;
- display: block;
- transform: rotate(270deg);
- }
+ width: 100%;
+ text-align: center;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ > i{
+ font-size: 1.4rem;
+ transform: rotate(270deg);
+ }
}
}
diff --git a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue
index 31bd74b6..2c2d0232 100644
--- a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue
@@ -145,10 +145,10 @@
@@ -159,7 +159,7 @@
diff --git a/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue b/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue
index 2198665a..f944678a 100644
--- a/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue
+++ b/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue
@@ -16,8 +16,9 @@ const emit = defineEmits([
"zoom-out",
"toggle-red-green-mode",
"undo-redo-status-changed",
+ "trigger-library"
]);
-const {t} = useI18n()
+const {t,locale} = useI18n()
const props = defineProps({
activeTool: String,
minimapEnabled: {
@@ -151,6 +152,13 @@ const normalToolsList = ref([
icon: { name: "CUpload", size: "26" },
class: "upload-btn",
},
+ {
+ id: "library",
+ title: t("LibraryPage.library"),
+ action: triggerLibrary,
+ icon: { name: "CLibrary", size: "26" },
+ class: "library-btn",
+ },
{
id: "addText",
title: t("Canvas.AddText"),
@@ -158,6 +166,17 @@ const normalToolsList = ref([
icon: { name: "CFont", size: "20" },
class: "text-btn",
},
+ {
+ id: "help",
+ title: t("Canvas.help"),
+ action: () => openTutorial(),
+ icon: { name: "CHelp", size: "30" },
+ class: "text-btn",
+ style: {
+ 'position': 'absolute',
+ 'bottom': '0',
+ },
+ },
]);
// 红绿图模式工具列表
@@ -228,10 +247,22 @@ function triggerImageUpload() {
emit("trigger-image-upload");
}
+function triggerLibrary() {
+ emit("trigger-library");
+}
+
function addText() {
emit("add-text");
}
+function openTutorial() {
+ if(locale.value == 'ENGLISH'){
+ window.open('https://aida-user-manual.super.site/specific-scenarios/freely-sketching-in-canvas', '_blank');
+ }else{
+ window.open('https://aida-user-manual-chinese.super.site/%e4%bd%bf%e7%94%a8%e7%94%bb%e5%b8%83%e8%bf%9b%e8%a1%8c%e7%bc%96%e8%be%91 ', '_blank');
+ }
+}
+
function undo() {
if (!canUndo.value) return;
undoFun();
@@ -327,19 +358,25 @@ const handleToolClick = (tool) => {
@change="fillColorChange"
style="width: 0; height: 0; opacity: 0"
/>
+
+
-
-
-
+
+
+
+
+
+
@@ -347,15 +384,25 @@ const handleToolClick = (tool) => {
.tools-sidebar {
display: flex;
flex-direction: column;
- gap: 1.0rem;
padding: 1.5rem 1.0rem;
border-right: .1rem solid #e0e0e0;
background-color: #ffffff;
user-select: none;
min-width: 5.8rem;
height: 100%;
+ padding-top: .5rem;
+ padding-bottom: 4rem;
+ /* overflow-y: auto; */
+ /* overflow-x: hidden; */
+}
+.tools-list{
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ flex: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
}
-
.red-green-mode {
background-color: #fff4f4;
}
diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue
index aa90942e..d46799e6 100644
--- a/src/component/Canvas/CanvasEditor/index.vue
+++ b/src/component/Canvas/CanvasEditor/index.vue
@@ -21,6 +21,9 @@ import { RedGreenModeManager } from "./managers/RedGreenModeManager";
import texturePresetManager from "./managers/brushes/TexturePresetManager";
import { BrushStore } from "./store/BrushStore";
import cuowuImg from '@/assets/images/homePage/cuowu.svg'
+import { Https } from "@/tool/https";
+import SelectImages from '@/component/common/SelectImages.vue'
+import { UrlToFile } from '@/tool/util'
// import { MinimapManager } from "./managers/minimap/MinimapManager";
@@ -52,6 +55,7 @@ const emit = defineEmits([
"trigger-red-green-mouseup", // 红绿图模式鼠标抬起事件
"changeCanvas", // 画布变更事件
"canvasInit", // 画布初始化事件
+ "trigger-library", // 触发打开Library选择图片事件
]);
const props = defineProps({
@@ -101,6 +105,14 @@ const props = defineProps({
type: Boolean,
default: false, // 是否显示固定图层
},
+ isGeneral: { // 从generalMiniCanvas来的
+ type: Boolean,
+ default:false
+ },
+ isEdit: { // 从design点击喜欢过的图片,再点击顶部的编辑图标
+ type: Boolean,
+ default: false
+ }
});
// 引用和状态
@@ -569,7 +581,6 @@ function zoomOut() {
x: canvasManager.canvas.width / 2,
y: canvasManager.canvas.height / 2,
};
-
canvasManager.animateZoom(centerPoint, newZoom);
}
@@ -601,7 +612,7 @@ async function addLayer() {
await layerManager.createLayer(t("Canvas.EmptyLayer"));
}
async function addTopLayer() {
- await layerManager.createLayer("空图层", LayerType.EMPTY, {
+ await layerManager.createLayer(t("Canvas.EmptyLayer"), LayerType.EMPTY, {
insertTop: true,
});
}
@@ -667,12 +678,20 @@ function deleteFun(){
function removeLayer(layerId) {
// Check if this is the last layer - prevent deletion
- if (layers.value.length <= 2) {
- console.warn(
+ var isChild = false;
+ var parentLength = 0;
+ layers.value.forEach((layer) => {
+ if(layer.children.some(v => v.id == layerId)){
+ isChild = true;
+ parentLength = layer.children.length;
+ }
+ })
+ if(isChild && parentLength == 1 || layers.value.length <= 3){
+ console.warn(
"Cannot delete the last layer. At least one layer must remain."
);
return;
- }
+ }
if (canvasManager && canvasManager.canvas) {
const layerToRemove = layers.value.find((l) => l.id === layerId);
@@ -727,6 +746,21 @@ function handleImageUpload(event) {
});
}
+const selectImages = ref(null);
+const handleImageSelect = (data) => {
+ UrlToFile(data.url,data.name).then((file)=>{
+ handleImageUpload({ target: { files: [file] } })
+ })
+}
+function triggerLibrary() {
+ // console.log('CanvasEditor', '打开收藏')
+ if (props.isGeneral || props.isEdit) {
+ selectImages.value.init()
+ } else {
+ emit("trigger-library");
+ }
+}
+
function handleAddText() {
if (toolManager && canvasManager && canvasManager.canvas) {
// 在画布中央创建文本
@@ -828,6 +862,7 @@ const changeCanvas = async (command) => {
setTimeout(async ()=>{
const imageData = await canvasManager.exportImage({
restoreOpacityInRedGreen: true, // 恢复红绿图模式下的透明度
+ isCropByBg:true,
});
emit("trigger-red-green-mouseup", imageData);
},100)
@@ -1066,10 +1101,14 @@ defineExpose({
@zoom-in="zoomIn"
@zoom-out="zoomOut"
@undo-redo-status-changed="changeCanvas"
+ @trigger-library="triggerLibrary"
>
+
+
+
-
-
+
+
@@ -1184,6 +1223,14 @@ defineExpose({
style="display: none"
@change="handleImageUpload"
/>
+
diff --git a/src/component/Canvas/CanvasEditor/managers/BrushIndicator.js b/src/component/Canvas/CanvasEditor/managers/BrushIndicator.js
index dac4c65b..11383dad 100644
--- a/src/component/Canvas/CanvasEditor/managers/BrushIndicator.js
+++ b/src/component/Canvas/CanvasEditor/managers/BrushIndicator.js
@@ -1,4 +1,5 @@
import { fabric } from "fabric-with-all";
+import { OperationType } from "../utils/layerHelper";
/**
* 笔刷指示器
@@ -104,9 +105,8 @@ export class BrushIndicator {
this.canvas.freeDrawingBrush &&
this.canvas.freeDrawingBrush.type === "eraser";
- if (!isBrushMode && !isEraserMode) {
- return;
- }
+ const isLiquifyMode = this.canvas.toolId === OperationType.LIQUIFY;// 检查是否在液化模式
+ if ([isBrushMode, isEraserMode, isLiquifyMode].every(v => !v)) return;
let hasChanges = false;
@@ -391,6 +391,9 @@ export class BrushIndicator {
this._mouseEnterHandler = (e) => {
if (this._shouldShowIndicator()) {
this.show(e.e);
+ const currentVpt = this.canvas.viewportTransform;
+ this.staticCanvas.setViewportTransform([...currentVpt]);
+ this.staticCanvas.renderAll();
}
};
@@ -471,9 +474,12 @@ export class BrushIndicator {
* @returns {Boolean} 是否显示
*/
_shouldShowIndicator() {
- // 检查画布是否在绘图模式
- if (!this.canvas.isDrawingMode) return false;
-
+ const isDrawingMode = this.canvas.isDrawingMode;// 检查画布是否在绘图模式
+ const isLiquifyMode = this.canvas.toolId === OperationType.LIQUIFY;// 检查是否在液化模式
+ // console.log(`笔刷指示器\n绘图模式:${isDrawingMode}\n液化模式:${isLiquifyMode}`)
+ // 检查画布是否在绘图模式OR液化模式
+ if ([isDrawingMode, isLiquifyMode].every(v => !v)) return false;
+
// 检查是否有笔刷
if (!this.canvas.freeDrawingBrush) return false;
diff --git a/src/component/Canvas/CanvasEditor/managers/ExportManager.js b/src/component/Canvas/CanvasEditor/managers/ExportManager.js
index 0b474d05..14bca2bd 100644
--- a/src/component/Canvas/CanvasEditor/managers/ExportManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/ExportManager.js
@@ -137,7 +137,9 @@ export class ExportManager {
return await this._exportWithCanvasSize(
objectsToExport,
expPicType,
- restoreOpacityInRedGreen
+ restoreOpacityInRedGreen,
+ isCropByBg, // 是否使用背景大小裁剪
+ isEnhanceImg, // 是否是增强图片
);
}
@@ -224,7 +226,7 @@ export class ExportManager {
const objectsToExport = this._collectObjectsByLayerOrder(
null, // 导出所有图层
isContainBg,
- isContainFixed
+ isContainFixed,
);
if (objectsToExport.length === 0) {
@@ -549,7 +551,7 @@ export class ExportManager {
return await this._exportWithCanvasSize(
objectsToExport,
expPicType,
- restoreOpacityInRedGreen
+ restoreOpacityInRedGreen,
);
}
diff --git a/src/component/Canvas/CanvasEditor/managers/ToolManager.js b/src/component/Canvas/CanvasEditor/managers/ToolManager.js
index 07ec3046..0a4d0f25 100644
--- a/src/component/Canvas/CanvasEditor/managers/ToolManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/ToolManager.js
@@ -373,6 +373,8 @@ export class ToolManager {
// 设置工具特定的状态
const tool = this.tools[toolId];
if (tool && typeof tool.setup === "function") {
+ console.log(`画布切换工具:${tool.name}(${toolId})`)
+ this.canvas.toolId = toolId;
tool.setup();
}
@@ -450,6 +452,7 @@ export class ToolManager {
if (!this.canvas) return;
this.canvas.isDrawingMode = false;
this.canvas.selection = true;
+
}
/**
@@ -750,6 +753,7 @@ export class ToolManager {
detail: panelDetail,
})
);
+ this._enableBrushIndicator();
}
/**
@@ -759,14 +763,14 @@ export class ToolManager {
* @private
*/
_showRasterizeConfirmModal(isGroup, layerId) {
- const title = "栅格化图层";
- const content = "需要先栅格化才能进行液化操作,是否立即栅格化?";
+ const title = this.t("Canvas.RasterizedLayer");
+ const content = this.t("Canvas.rasterizeImmediately");
Modal.confirm({
title,
content,
- okText: "确定栅格化",
- cancelText: "取消",
+ okText: this.t("Canvas.ConfirmRasterization"),
+ cancelText: this.t("Canvas.close"),
centered: true,
icon: h("span", { style: "color: #faad14;" }, "⚠️"),
onOk: () => {
@@ -800,8 +804,8 @@ export class ToolManager {
if (!this.commandManager || !this.layerManager) return;
// 显示加载Modal
const loadingModal = Modal.info({
- title: "正在栅格化",
- content: "正在栅格化图层,请稍候...",
+ title: this.t('Canvas.beingRasterized'),
+ content: this.t('Canvas.waitRasterizing'),
okButtonProps: { style: { display: "none" } },
centered: true,
closable: false,
@@ -823,16 +827,16 @@ export class ToolManager {
if (result) {
// 栅格化成功,启动液化
- message.success("图层已成功栅格化,可以进行液化操作");
+ message.success(this.t('Canvas.successRasterizing'));
this._startLiquify(result);
this.setTool(OperationType.LIQUIFY); // 切换到液化工具
} else {
// 栅格化失败
Modal.error({
- title: "栅格化失败",
- content: "栅格化失败,无法进行液化操作",
- okText: "确定",
+ title: this.t('Canvas.gridingFailed'),
+ content: this.t('Canvas.gridingFailedNoOperation'),
+ okText: this.t('Canvas.ok'),
centered: true,
});
}
@@ -840,9 +844,9 @@ export class ToolManager {
console.error("栅格化图层失败:", error);
Modal.error({
- title: "栅格化错误",
- content: `栅格化失败:${error.message}`,
- okText: "确定",
+ title: this.t('Canvas.gridingError'),
+ content: `${this.t('Canvas.gridingFailed')}:${error.message}`,
+ okText: this.t('Canvas.ok'),
centered: true,
});
} finally {
@@ -866,9 +870,9 @@ export class ToolManager {
);
if (!layer) {
Modal.error({
- title: "图层错误",
- content: "图层不存在",
- okText: "确定",
+ title: this.t('Canvas.LayerError'),
+ content: this.t('Canvas.LayerDoesNotExist'),
+ okText: this.t('Canvas.ok'),
centered: true,
});
return;
@@ -880,9 +884,9 @@ export class ToolManager {
// 背景图层使用 fabricObject (单数)
if (!layer.fabricObject) {
Modal.warning({
- title: "背景图层为空",
- content: "背景图层为空,无法进行液化操作",
- okText: "确定",
+ title: this.t('Canvas.backgroundEmpty'),
+ content: this.t('Canvas.backgroundEmptyNoLiquidation'),
+ okText: this.t('Canvas.ok'),
centered: true,
});
return;
@@ -892,9 +896,9 @@ export class ToolManager {
// 普通图层使用 fabricObjects (复数)
if (!layer.fabricObjects || layer.fabricObjects.length === 0) {
Modal.warning({
- title: "图层为空",
- content: "图层为空,无法进行液化操作",
- okText: "确定",
+ title: this.t('Canvas.layerEmpty'),
+ content: this.t('Canvas.layerEmptyNoLiquidation'),
+ okText: this.t('Canvas.ok'),
centered: true,
});
return;
@@ -906,9 +910,9 @@ export class ToolManager {
const liquifyManager = this.canvasManager?.liquifyManager;
if (!liquifyManager) {
Modal.error({
- title: "液化管理器错误",
- content: "液化管理器未初始化",
- okText: "确定",
+ title: this.t('Canvas.liqueficationManagerError'),
+ content: this.t('Canvas.liqueficationManagerErrorInitialized'),
+ okText: this.t('Canvas.ok'),
centered: true,
});
return;
@@ -917,8 +921,8 @@ export class ToolManager {
try {
// 显示准备中的Modal
const preparingModal = Modal.info({
- title: "准备液化环境",
- content: "正在准备液化环境,请稍候...",
+ title: this.t('Canvas.liquefactionEnvironment'),
+ content: this.t('Canvas.liquefactionEnvironmentLoading'),
okButtonProps: { style: { display: "none" } },
centered: true,
closable: false,
@@ -963,9 +967,9 @@ export class ToolManager {
} catch (error) {
console.error("启动液化工具失败:", error);
Modal.error({
- title: "液化工具启动失败",
- content: `启动液化工具失败:${error.message}`,
- okText: "确定",
+ title: this.t('Canvas.LiqueficationFailed'),
+ content: `${this.t('Canvas.LiqueficationFailed')}:${error.message}`,
+ okText: this.t('Canvas.ok'),
centered: true,
});
}
@@ -1041,7 +1045,7 @@ export class ToolManager {
_createTextDirect(x, y, options = {}) {
// 默认文本属性
const defaultOptions = {
- text: "双击编辑文本",
+ text: this.t('Canvas.DoubleClickText'),
fontFamily: "Arial",
fontSize: 24,
fontWeight: "normal",
@@ -1188,6 +1192,7 @@ export class ToolManager {
if (this.brushIndicator) {
this.brushIndicator.dispose();
this.brushIndicator = null;
+ console.log("笔刷指示器已清理");
}
// 移除文本编辑相关事件监听器
@@ -1381,6 +1386,7 @@ export class ToolManager {
if (!this.brushIndicator) return;
this.brushIndicator.updateSize(size);
+ console.log(`笔刷指示器大小已更新为: ${size}`);
}
/**
@@ -1391,6 +1397,7 @@ export class ToolManager {
if (!this.brushIndicator) return;
this.brushIndicator.updateColor(color);
+ console.log(`笔刷指示器颜色已更新为: ${color}`);
}
/**
@@ -1465,6 +1472,7 @@ export class ToolManager {
OperationType.ERASER,
OperationType.RED_BRUSH,
OperationType.GREEN_BRUSH,
+ OperationType.LIQUIFY,
];
return brushTools.includes(currentTool);
diff --git a/src/component/Canvas/CanvasEditor/managers/animation/AnimationManager.js b/src/component/Canvas/CanvasEditor/managers/animation/AnimationManager.js
index b8ed055d..c9250732 100644
--- a/src/component/Canvas/CanvasEditor/managers/animation/AnimationManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/animation/AnimationManager.js
@@ -173,7 +173,7 @@ export class AnimationManager {
this._zoomAnimation = null;
// 确保最终状态准确
- this._applyZoom(point, targetZoom, true);
+ // this._applyZoom(point, targetZoom, true);
},
};
diff --git a/src/component/Canvas/CanvasEditor/managers/brushes/brushManager.js b/src/component/Canvas/CanvasEditor/managers/brushes/brushManager.js
index 051659ab..15ae7463 100644
--- a/src/component/Canvas/CanvasEditor/managers/brushes/brushManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/brushes/brushManager.js
@@ -65,6 +65,7 @@ export class BrushManager {
description: "基础铅笔工具,适合精细线条绘制",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/pencil.jpg',
});
// 注册材质笔刷
@@ -73,6 +74,7 @@ export class BrushManager {
description: "使用纹理图片作为笔刷,支持缩放和透明度",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/texture.jpg',
});
// 注册集成的笔刷类型
@@ -81,54 +83,63 @@ export class BrushManager {
description: "使用纹理图片作为笔刷,支持缩放和透明度",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/crayon.jpg',
});
brushRegistry.register("fur", FurBrush, {
name: this.t("Canvas.Fur"),
description: "使用纹理图片作为笔刷,支持缩放和透明度",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/fur.jpg',
});
brushRegistry.register("ink", InkBrush, {
name: this.t("Canvas.Ink"),
description: "墨水笔刷,适合书写和绘图",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/ink.jpg',
});
brushRegistry.register("", LongfurBrush, {
name: this.t("Canvas.Longfur"),
description: "长毛发笔刷,适合绘制动物毛皮、草或头发",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/longFur.jpg',
});
brushRegistry.register("writing", WritingBrush, {
name: this.t("Canvas.Writing"),
description: "书法笔刷,模拟中国传统书法毛笔效果,具有笔锋和墨色变化",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/writing.jpg',
});
brushRegistry.register("marker", MarkerBrush, {
name: this.t("Canvas.Marker"),
description: "马克笔笔刷,适合粗线条和填充",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/marker.jpg',
});
brushRegistry.register("pen", CustomPenBrush, {
name: this.t("Canvas.Pen"),
description: "自定义钢笔笔刷,适合书写和绘图",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/pen.jpg',
});
brushRegistry.register("ribbon", RibbonBrush, {
name: this.t("Canvas.Ribbon"),
description: "丝带笔刷,适合创建流动的丝带效果",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/ribbon.jpg',
});
brushRegistry.register("shaded", ShadedBrush, {
name: this.t("Canvas.Shaded"),
description: "阴影笔刷,适合创建渐变和阴影效果",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/shaded.jpg',
});
brushRegistry.register("spray", SprayBrush, {
@@ -136,6 +147,7 @@ export class BrushManager {
description: "模拟喷枪效果,创建散点效果",
t:this.t,
category: this.t('Canvas.BasicBrushes'),
+ imgUrl:'./image/brush/spray.jpg',
});
// brushRegistry.register("sketchy", SketchyBrush);
@@ -365,6 +377,7 @@ export class BrushManager {
description: brushInfo.metadata.description || "",
category: brushInfo.metadata.category || "默认",
icon: brushInfo.metadata.icon || null,
+ imgUrl: brushInfo.metadata.imgUrl || null,
}));
}
diff --git a/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js b/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js
index ccdcece5..71ce6f17 100644
--- a/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js
+++ b/src/component/Canvas/CanvasEditor/managers/brushes/types/TextureBrush.js
@@ -1,6 +1,8 @@
import { BaseBrush } from "../BaseBrush";
import { fabric } from "fabric-with-all";
import texturePresetManager from "../TexturePresetManager";
+import i18n from "@/lang/index.ts";
+const {t} = i18n.global;
/**
* 纹理笔刷
@@ -468,12 +470,12 @@ export class TextureBrush extends BaseBrush {
const textureProperties = [
{
id: "textureSelector",
- name: "材质选择",
+ name: t('Canvas.TextureSelector'),
type: "texture-grid",
defaultValue: this.selectedTextureId,
options: textureOptions,
- description: "选择要使用的纹理",
- category: "纹理设置",
+ description: t('Canvas.selectTexture'),
+ category: t('Canvas.TextureSettings'),
order: 100,
hidden: allTextures.length === 0,
},
diff --git a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js
index 8306e125..e906d467 100644
--- a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js
@@ -706,7 +706,8 @@ export class CanvasEventManager {
if (e.target.id) {
// 如果该元素是分组图层的一部分,也更新分组图层的缩略图
if (e.target.parentId) {
- setTimeout(() => this.updateLayerThumbnail(e.target.parentId), 50);
+ // setTimeout(() => this.updateLayerThumbnail(e.target.parentId), 50);
+ this.thumbnailManager.generateLayerThumbnail(e.target.parentId);
}
}
}
diff --git a/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js b/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js
index 13a6015e..34e902dd 100644
--- a/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js
@@ -5,6 +5,8 @@
import { LiquifyWebGLManager } from "./LiquifyWebGLManager";
import { LiquifyCPUManager } from "./LiquifyCPUManager";
import { findLayerRecursively, LayerType } from "../../utils/layerHelper";
+import i18n from "@/lang/index.ts";
+const {t} = i18n.global;
export class EnhancedLiquifyManager {
/**
@@ -20,13 +22,13 @@ export class EnhancedLiquifyManager {
// 是否强制使用WebGL模式
forceWebGL: options.forceWebGL || false,
// 网格大小
- gridSize: options.gridSize || 15,
+ gridSize: options.gridSize || 8,
// 最大变形强度
- maxStrength: options.maxStrength || 100,
+ maxStrength: options.maxStrength || 200,
// 平滑迭代次数
- smoothingIterations: options.smoothingIterations || 2,
+ smoothingIterations: options.smoothingIterations || 1,
// 网格弹性因子
- relaxFactor: options.relaxFactor || 0.25,
+ relaxFactor: options.relaxFactor || 0.05,
// WebGL网格精度
meshResolution: options.meshResolution || 64,
};
@@ -599,7 +601,7 @@ export class EnhancedLiquifyManager {
if (objectsToCheck.length === 0) {
return {
valid: false,
- message: "图层为空,无法进行液化操作",
+ message: t('Canvas.layerEmptyNoLiquidation'),
needsRasterization: false,
isImage: false,
isEmpty: true,
diff --git a/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyCPUManager.js b/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyCPUManager.js
index f7455e10..cb91dcae 100644
--- a/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyCPUManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyCPUManager.js
@@ -3,1313 +3,1451 @@
* 修复版本 - 解决三角形网格失真问题,优化持续按压效果
*/
export class LiquifyCPUManager {
- constructor(options = {}) {
- this.config = {
- gridSize: options.gridSize || 16, // 稍微增大网格提高性能
- maxStrength: options.maxStrength || 200, // 适度降低最大强度
- smoothingIterations: options.smoothingIterations || 1, // 增加平滑处理
- relaxFactor: options.relaxFactor || 0.1, // 适度松弛
- };
-
- this.params = {
- size: 80, // 增大默认尺寸
- pressure: 0.8, // 增大默认压力
- distortion: 0,
- power: 0.8, // 增大默认动力
- };
-
- this.modes = {
- PUSH: "push",
- CLOCKWISE: "clockwise",
- COUNTERCLOCKWISE: "counterclockwise",
- PINCH: "pinch",
- EXPAND: "expand",
- CRYSTAL: "crystal",
- EDGE: "edge",
- RECONSTRUCT: "reconstruct",
- };
-
- this.currentMode = this.modes.PUSH;
- this.originalImageData = null;
- this.currentImageData = null;
- this.mesh = null;
- this.initialized = false;
- this.canvas = document.createElement("canvas");
- this.ctx = this.canvas.getContext("2d");
- this.deformHistory = [];
-
- // 性能优化相关
- this.lastUpdateTime = 0;
- this.updateThrottle = 16; // 限制更新频率约60fps
- this.isProcessing = false;
-
- // 鼠标位置跟踪(用于推拉模式)
- this.initialMouseX = 0; // 初始点击位置X
- this.initialMouseY = 0; // 初始点击位置Y
- this.currentMouseX = 0; // 当前鼠标位置X
- this.currentMouseY = 0; // 当前鼠标位置Y
- this.lastMouseX = 0;
- this.lastMouseY = 0;
- this.mouseMovementX = 0;
- this.mouseMovementY = 0;
- this.isFirstApply = true; // 标记是否是首次应用
- this.isDragging = false; // 标记是否正在拖拽
- this.dragDistance = 0; // 拖拽距离
- this.dragAngle = 0; // 拖拽角度
-
- // 新增:持续按压相关状态
- this.pressStartTime = 0; // 按压开始时间
- this.pressDuration = 0; // 按压持续时间
- this.accumulatedRotation = 0; // 累积旋转角度(用于顺时针/逆时针)
- this.accumulatedScale = 0; // 累积缩放量(用于捏合/展开)
- this.lastApplyTime = 0; // 上次应用时间
- this.continuousApplyInterval = 50; // 持续应用间隔(毫秒)
- this.isHolding = false; // 是否正在持续按压
- }
-
- initialize(imageSource) {
- try {
- if (imageSource instanceof ImageData) {
- this.originalImageData = new ImageData(
- new Uint8ClampedArray(imageSource.data),
- imageSource.width,
- imageSource.height
- );
- } else if (imageSource instanceof HTMLImageElement) {
- this.canvas.width = imageSource.width;
- this.canvas.height = imageSource.height;
- this.ctx.drawImage(imageSource, 0, 0);
- this.originalImageData = this.ctx.getImageData(0, 0, imageSource.width, imageSource.height);
- } else {
- throw new Error("不支持的图像类型");
- }
-
- this.currentImageData = new ImageData(
- new Uint8ClampedArray(this.originalImageData.data),
- this.originalImageData.width,
- this.originalImageData.height
- );
-
- this._initMesh(this.originalImageData.width, this.originalImageData.height);
- this.initialized = true;
- return true;
- } catch (error) {
- console.error("液化管理器初始化失败:", error);
- return false;
- }
- }
-
- _initMesh(width, height) {
- const gridSize = this.config.gridSize;
- const cols = Math.ceil(width / gridSize);
- const rows = Math.ceil(height / gridSize);
-
- this.mesh = {
- cols,
- rows,
- gridSize,
- width,
- height,
- originalPoints: [],
- deformedPoints: [],
- };
-
- for (let y = 0; y <= rows; y++) {
- for (let x = 0; x <= cols; x++) {
- const point = { x: x * gridSize, y: y * gridSize };
- this.mesh.originalPoints.push({ ...point });
- this.mesh.deformedPoints.push({ ...point });
- }
- }
- }
-
- setMode(mode) {
- if (Object.values(this.modes).includes(mode)) {
- this.currentMode = mode;
- return true;
- }
- return false;
- }
-
- setParam(param, value) {
- if (param in this.params) {
- this.params[param] = value;
- return true;
- }
- return false;
- }
-
- getParams() {
- return { ...this.params };
- }
-
- resetParams() {
- this.params = {
- size: 80, // 增大默认尺寸
- pressure: 0.8, // 增大默认压力
- distortion: 0,
- power: 0.8, // 增大默认动力
- };
- }
-
- /**
- * 开始液化操作(记录初始点)
- * @param {Number} x 初始X坐标
- * @param {Number} y 初始Y坐标
- */
- startDeformation(x, y) {
- this.initialMouseX = x;
- this.initialMouseY = y;
- this.currentMouseX = x;
- this.currentMouseY = y;
- this.lastMouseX = x;
- this.lastMouseY = y;
- this.isDragging = true;
- this.isFirstApply = true;
- this.dragDistance = 0;
- this.dragAngle = 0;
-
- // 新增:初始化持续按压状态
- this.pressStartTime = Date.now();
- this.pressDuration = 0;
- this.accumulatedRotation = 0;
- this.accumulatedScale = 0;
- this.lastApplyTime = this.pressStartTime;
- this.isHolding = true;
-
- // 启动持续效果定时器(对于所有模式都支持持续按压)
- this.startContinuousEffect();
-
- console.log(`开始液化操作,初始点: (${x}, ${y})`);
- }
-
- /**
- * 结束液化操作
- */
- endDeformation() {
- this.isDragging = false;
- this.isFirstApply = true;
- this.dragDistance = 0;
- this.dragAngle = 0;
-
- // 新增:重置持续按压状态
- this.isHolding = false;
- this.pressStartTime = 0;
- this.pressDuration = 0;
- this.accumulatedRotation = 0;
- this.accumulatedScale = 0;
- this.lastApplyTime = 0;
-
- // 停止持续效果定时器
- this.stopContinuousEffect();
-
- console.log("结束液化操作");
- }
-
- // 新增:启动持续效果
- startContinuousEffect() {
- this.stopContinuousEffect(); // 先停止已有的定时器
-
- this.continuousTimer = setInterval(() => {
- if (this.isHolding && this.initialized) {
- // 更新持续时间
- this.pressDuration = Date.now() - this.pressStartTime;
-
- // 所有模式都支持持续效果
- this.applyContinuousDeformation();
- }
- }, this.continuousApplyInterval);
- }
-
- // 新增:停止持续效果
- stopContinuousEffect() {
- if (this.continuousTimer) {
- clearInterval(this.continuousTimer);
- this.continuousTimer = null;
- }
- }
-
- /**
- * 稳定的旋转衰减函数 - 确保内圈快外圈慢,保持纹理连续性
- * @param {number} t 归一化距离 (0-1)
- * @returns {number} 衰减因子 (0-1)
- */
- _stableRotationFalloff(t) {
- if (t >= 1.0) return 0;
- if (t <= 0) return 1;
-
- // 使用反向二次函数:内圈(t=0)时值为1,外圈(t=1)时值为0
- // 这确保了内圈旋转最快,外圈旋转最慢
- const inverseFalloff = 1 - t;
-
- // 使用平滑的二次衰减,确保内圈效果强,外圈效果弱
- const quadraticFalloff = inverseFalloff * inverseFalloff;
-
- // 添加轻微的线性分量,确保过渡平滑
- const linearFalloff = inverseFalloff;
-
- // 混合二次和线性衰减,70%二次衰减 + 30%线性衰减
- return quadraticFalloff * 0.7 + linearFalloff * 0.3;
- }
-
- /**
- * 基于test-liquify-enhanced.html的旋转算法 - 像素级实现
- * @param {number} centerX 旋转中心X坐标
- * @param {number} centerY 旋转中心Y坐标
- * @param {number} radius 影响半径
- * @param {number} strength 强度
- * @param {boolean} isClockwise 是否顺时针旋转
- */
- _applyEnhancedRotationDeformation(centerX, centerY, radius, strength, isClockwise) {
- if (!this.currentImageData) return;
-
- const data = this.currentImageData.data;
- const width = this.currentImageData.width;
- const height = this.currentImageData.height;
- const tempData = new Uint8ClampedArray(data);
-
- // 计算旋转角度 - 基于test-liquify-enhanced.html的算法
- const { pressure, power } = this.params;
- const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
- const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度
- const rotationAngle =
- (isClockwise ? 1 : -1) * baseRotationSpeed * pressure * power * (1.0 + timeFactor * 0.5);
-
- // 累积旋转角度 - 关键:这确保了持续旋转效果
- this.accumulatedRotation += rotationAngle;
-
- const processRadius = Math.min(radius, Math.min(width, height) / 2);
- const minX = Math.max(0, Math.floor(centerX - processRadius));
- const maxX = Math.min(width, Math.ceil(centerX + processRadius));
- const minY = Math.max(0, Math.floor(centerY - processRadius));
- const maxY = Math.min(height, Math.ceil(centerY + processRadius));
-
- // 遍历影响区域内的每个像素
- for (let y = minY; y < maxY; y++) {
- for (let x = minX; x < maxX; x++) {
- const dx = x - centerX;
- const dy = y - centerY;
- const distance = Math.sqrt(dx * dx + dy * dy);
-
- if (distance < processRadius && distance > 0.1) {
- // 距离衰减:内圈快,外圈慢 - 与测试文件算法一致
- const normalizedDistance = distance / processRadius;
- const falloff = Math.pow(1 - normalizedDistance, 2); // 二次衰减
-
- // 计算旋转后的源位置 - 关键算法
- const angle = Math.atan2(dy, dx);
- const newAngle = angle + this.accumulatedRotation * falloff;
-
- const sourceX = centerX + Math.cos(newAngle) * distance;
- const sourceY = centerY + Math.sin(newAngle) * distance;
-
- // 双线性插值采样 - 确保像素连续性
- const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
-
- if (color) {
- const targetIdx = (y * width + x) * 4;
- data[targetIdx] = color[0];
- data[targetIdx + 1] = color[1];
- data[targetIdx + 2] = color[2];
- data[targetIdx + 3] = color[3];
- }
- }
- }
- }
-
- return true;
- }
-
- /**
- * 基于test-liquify-enhanced.html的捏合/展开算法
- * @param {number} centerX 中心X坐标
- * @param {number} centerY 中心Y坐标
- * @param {number} radius 影响半径
- * @param {number} strength 强度
- * @param {boolean} isPinch 是否为捏合模式
- */
- _applyEnhancedPinchDeformation(centerX, centerY, radius, strength, isPinch) {
- if (!this.currentImageData) return;
-
- const data = this.currentImageData.data;
- const width = this.currentImageData.width;
- const height = this.currentImageData.height;
- const tempData = new Uint8ClampedArray(data);
-
- // 计算时间相关的缩放因子 - 基于test-liquify-enhanced.html
- const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
- const baseScaleFactor = isPinch ? -0.01 : 0.01;
- const scaleFactor = baseScaleFactor * (1.0 + timeFactor * 0.5);
-
- this.accumulatedScale += scaleFactor;
-
- const processRadius = Math.min(radius, Math.min(width, height) / 2);
- const minX = Math.max(0, Math.floor(centerX - processRadius));
- const maxX = Math.min(width, Math.ceil(centerX + processRadius));
- const minY = Math.max(0, Math.floor(centerY - processRadius));
- const maxY = Math.min(height, Math.ceil(centerY + processRadius));
-
- for (let y = minY; y < maxY; y++) {
- for (let x = minX; x < maxX; x++) {
- const dx = x - centerX;
- const dy = y - centerY;
- const distance = Math.sqrt(dx * dx + dy * dy);
-
- if (distance < processRadius && distance > 0.1) {
- const normalizedDistance = distance / processRadius;
- const falloff = 1 - normalizedDistance * normalizedDistance;
-
- // 计算缩放后的位置
- const scale = 1 + this.accumulatedScale * falloff;
- const sourceX = centerX + dx * scale;
- const sourceY = centerY + dy * scale;
-
- // 双线性插值采样
- const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
-
- if (color) {
- const targetIdx = (y * width + x) * 4;
- data[targetIdx] = color[0];
- data[targetIdx + 1] = color[1];
- data[targetIdx + 2] = color[2];
- data[targetIdx + 3] = color[3];
- }
- }
- }
- }
-
- return true;
- }
-
- /**
- * 基于test-liquify-enhanced.html的推拉算法
- * @param {number} centerX 中心X坐标
- * @param {number} centerY 中心Y坐标
- * @param {number} radius 影响半径
- * @param {number} strength 强度
- */
- _applyEnhancedPushDeformation(centerX, centerY, radius, strength) {
- if (!this.currentImageData) return;
-
- const data = this.currentImageData.data;
- const width = this.currentImageData.width;
- const height = this.currentImageData.height;
- const tempData = new Uint8ClampedArray(data);
-
- // 计算推拉方向
- const deltaX = this.currentMouseX - this.initialMouseX;
- const deltaY = this.currentMouseY - this.initialMouseY;
- const dragLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
-
- const processRadius = Math.min(radius, Math.min(width, height) / 2);
- const minX = Math.max(0, Math.floor(centerX - processRadius));
- const maxX = Math.min(width, Math.ceil(centerX + processRadius));
- const minY = Math.max(0, Math.floor(centerY - processRadius));
- const maxY = Math.min(height, Math.ceil(centerY + processRadius));
-
- if (dragLength === 0) {
- // 如果没有拖拽,在持续按压时执行基础的外推效果
- if (this.isHolding) {
- const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
- const pushStrength = strength * timeFactor * 0.3;
-
- for (let y = minY; y < maxY; y++) {
- for (let x = minX; x < maxX; x++) {
- const dx = x - centerX;
- const dy = y - centerY;
- const distance = Math.sqrt(dx * dx + dy * dy);
-
- if (distance < processRadius && distance > 0.1) {
- const normalizedDistance = distance / processRadius;
- const falloff = 1 - normalizedDistance * normalizedDistance;
- const factor = falloff * pushStrength;
-
- // 径向外推效果
- const pushX = (dx / distance) * factor;
- const pushY = (dy / distance) * factor;
-
- const sourceX = x - pushX;
- const sourceY = y - pushY;
-
- const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
-
- if (color) {
- const targetIdx = (y * width + x) * 4;
- data[targetIdx] = color[0];
- data[targetIdx + 1] = color[1];
- data[targetIdx + 2] = color[2];
- data[targetIdx + 3] = color[3];
- }
- }
- }
- }
- }
- return true;
- }
-
- // 有拖拽时的推拉效果
- const dirX = deltaX / dragLength;
- const dirY = deltaY / dragLength;
-
- for (let y = minY; y < maxY; y++) {
- for (let x = minX; x < maxX; x++) {
- const dx = x - centerX;
- const dy = y - centerY;
- const distance = Math.sqrt(dx * dx + dy * dy);
-
- if (distance < processRadius && distance > 0.1) {
- const normalizedDistance = distance / processRadius;
- const falloff = 1 - normalizedDistance * normalizedDistance;
- const factor = falloff * strength;
-
- const offsetX = dirX * factor * Math.min(dragLength * 0.3, 15);
- const offsetY = dirY * factor * Math.min(dragLength * 0.3, 15);
-
- const sourceX = x - offsetX;
- const sourceY = y - offsetY;
-
- const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
-
- if (color) {
- const targetIdx = (y * width + x) * 4;
- data[targetIdx] = color[0];
- data[targetIdx + 1] = color[1];
- data[targetIdx + 2] = color[2];
- data[targetIdx + 3] = color[3];
- }
- }
- }
- }
-
- return true;
- }
-
- /**
- * 优化的持续变形效果处理 - 使用增强算法
- */
- applyContinuousDeformation() {
- if (!this.isHolding || !this.initialized || !this.currentImageData) return;
-
- const { size, pressure, power } = this.params;
- const mode = this.currentMode;
- const radius = size;
- const x = this.initialMouseX;
- const y = this.initialMouseY;
- const strength = pressure * power;
-
- // 根据模式使用相应的增强算法
- switch (mode) {
- case this.modes.CLOCKWISE:
- this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
- break;
-
- case this.modes.COUNTERCLOCKWISE:
- this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
- break;
-
- case this.modes.PINCH:
- this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
- break;
-
- case this.modes.EXPAND:
- this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
- break;
-
- case this.modes.PUSH:
- this._applyEnhancedPushDeformation(x, y, radius, strength);
- break;
-
- default: {
- // 对于其他模式,使用原有的网格算法
- if (!this.mesh) return;
-
- const baseStrength = (pressure * power * this.config.maxStrength) / 100;
- const timeFactor = Math.min(this.pressDuration / 1000, 4.0);
- const finalStrength = baseStrength * (1.0 + timeFactor * 0.5);
-
- this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
-
- if (this.config.smoothingIterations > 0) {
- this._lightSmoothing();
- }
-
- return this._applyMeshToImage();
- }
- }
-
- // 对于像素算法,直接返回当前图像数据
- return this.currentImageData;
- }
-
- /**
- * 应用液化变形 - 主要入口,集成增强算法
- */
- // applyDeformation(x, y) {
- // if (!this.initialized || !this.originalImageData) {
- // console.warn("液化管理器未初始化或缺少必要数据");
- // return this.currentImageData;
- // }
-
- // // 更新鼠标位置
- // this.currentMouseX = x;
- // this.currentMouseY = y;
-
- // // 计算拖拽参数
- // const deltaX = this.currentMouseX - this.initialMouseX;
- // const deltaY = this.currentMouseY - this.initialMouseY;
- // this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- // this.dragAngle = Math.atan2(deltaY, deltaX);
-
- // // 获取当前参数
- // const { size, pressure, power } = this.params;
- // const mode = this.currentMode;
- // const radius = size;
- // const strength = pressure * power;
-
- // // 根据模式选择算法
- // const pixelModes = [
- // this.modes.CLOCKWISE,
- // this.modes.COUNTERCLOCKWISE,
- // this.modes.PINCH,
- // this.modes.EXPAND,
- // this.modes.PUSH,
- // ];
-
- // if (pixelModes.includes(mode)) {
- // // 使用增强的像素算法
- // switch (mode) {
- // case this.modes.CLOCKWISE:
- // this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
- // break;
- // case this.modes.COUNTERCLOCKWISE:
- // this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
- // break;
- // case this.modes.PINCH:
- // this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
- // break;
- // case this.modes.EXPAND:
- // this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
- // break;
- // case this.modes.PUSH:
- // this._applyEnhancedPushDeformation(x, y, radius, strength);
- // break;
- // }
-
- // // 更新最后应用时间
- // this.lastApplyTime = Date.now();
- // this.isFirstApply = false;
-
- // return this.currentImageData;
- // } else {
- // // 使用原有的网格算法处理其他模式
- // if (!this.mesh) {
- // console.warn("网格未初始化");
- // return this.currentImageData;
- // }
-
- // const finalStrength = (strength * this.config.maxStrength) / 100;
-
- // // 应用变形
- // this._applyDeformation(
- // x,
- // y,
- // radius,
- // finalStrength,
- // mode,
- // this.params.distortion,
- // );
-
- // // 平滑处理
- // if (this.config.smoothingIterations > 0) {
- // this._smoothMesh();
- // }
-
- // // 更新图像数据
- // const result = this._applyMeshToImage();
-
- // // 更新最后应用时间
- // this.lastApplyTime = Date.now();
- // this.isFirstApply = false;
-
- // return result;
- // }
- // }
-
- /**
- * 双线性插值采样 - 用于像素级算法
- * @param {Uint8ClampedArray} data 图像数据
- * @param {number} width 图像宽度
- * @param {number} height 图像高度
- * @param {number} x X坐标
- * @param {number} y Y坐标
- * @returns {Array|null} RGBA颜色值数组或null
- */
- _bilinearSample(data, width, height, x, y) {
- if (x < 0 || x >= width - 1 || y < 0 || y >= height - 1) {
- return null;
- }
-
- const x1 = Math.floor(x);
- const y1 = Math.floor(y);
- const x2 = x1 + 1;
- const y2 = y1 + 1;
-
- const fx = x - x1;
- const fy = y - y1;
-
- const getPixel = (px, py) => {
- const idx = (py * width + px) * 4;
- return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
- };
-
- const p1 = getPixel(x1, y1);
- const p2 = getPixel(x2, y1);
- const p3 = getPixel(x1, y2);
- const p4 = getPixel(x2, y2);
-
- return [
- Math.round(
- (1 - fx) * (1 - fy) * p1[0] +
- fx * (1 - fy) * p2[0] +
- (1 - fx) * fy * p3[0] +
- fx * fy * p4[0]
- ),
- Math.round(
- (1 - fx) * (1 - fy) * p1[1] +
- fx * (1 - fy) * p2[1] +
- (1 - fx) * fy * p3[1] +
- fx * fy * p4[1]
- ),
- Math.round(
- (1 - fx) * (1 - fy) * p1[2] +
- fx * (1 - fy) * p2[2] +
- (1 - fx) * fy * p3[2] +
- fx * fy * p4[2]
- ),
- Math.round(
- (1 - fx) * (1 - fy) * p1[3] +
- fx * (1 - fy) * p2[3] +
- (1 - fx) * fy * p3[3] +
- fx * fy * p4[3]
- ),
- ];
- }
-
- /**
- * 应用变形到网格 - 原有的网格算法(用于其他模式)
- */
- _applyDeformation(x, y, radius, strength, mode, distortion) {
- if (!this.mesh) return;
-
- const points = this.mesh.deformedPoints;
- const originalPoints = this.mesh.originalPoints;
-
- // 性能优化:只计算影响范围内的网格点
- const affectedPoints = this._getAffectedPoints(x, y, radius);
-
- for (const pointInfo of affectedPoints) {
- const { index: i, point, originalPoint, distance } = pointInfo;
-
- if (distance > 0) {
- // 使用优化的衰减函数
- const normalizedDistance = distance / radius;
- const factor = this._optimizedFalloff(normalizedDistance) * strength;
-
- switch (mode) {
- case this.modes.CRYSTAL: {
- // 水晶模式
- const dx = point.x - x;
- const dy = point.y - y;
- const crystalAngle = Math.atan2(dy, dx);
- const crystalRadius = normalizedDistance;
-
- const baseDistortion = Math.max(distortion, 0.3);
- const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
- const timeEnhancedDistortion = baseDistortion * (1.0 + timeFactor * 0.3);
-
- const wave1 = Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
- const wave2 = Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
- const waveAngle = crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
-
- const radialMod =
- 1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
- const modDistance = distance * radialMod;
-
- const crystalX = x + Math.cos(waveAngle) * modDistance;
- const crystalY = y + Math.sin(waveAngle) * modDistance;
-
- const crystalFactor = factor * timeEnhancedDistortion * 0.7;
- point.x += (crystalX - point.x) * crystalFactor;
- point.y += (crystalY - point.y) * crystalFactor;
- break;
- }
-
- case this.modes.EDGE: {
- // 边缘模式
- const dx = point.x - x;
- const dy = point.y - y;
- const edgeAngle = Math.atan2(dy, dx);
- const edgeRadius = normalizedDistance;
-
- const baseEdgeDistortion = Math.max(distortion, 0.5);
- const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
- const timeEnhancedDistortion = baseEdgeDistortion * (1.0 + timeFactor * 0.4);
-
- const edgeWave =
- Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
- Math.cos(edgeAngle * 6 + this.pressDuration * 0.002);
- const perpAngle = edgeAngle + Math.PI / 2;
-
- const edgeFactor = edgeWave * factor * timeEnhancedDistortion * 0.5;
- const edgeOffsetX = Math.cos(perpAngle) * edgeFactor;
- const edgeOffsetY = Math.sin(perpAngle) * edgeFactor;
-
- point.x += edgeOffsetX;
- point.y += edgeOffsetY;
- break;
- }
-
- case this.modes.RECONSTRUCT: {
- // 重建模式
- const restoreFactor = factor * 0.2;
- point.x += (originalPoint.x - point.x) * restoreFactor;
- point.y += (originalPoint.y - point.y) * restoreFactor;
- break;
- }
- }
- }
- }
- }
-
- /**
- * 获取受影响的网格点(范围优化)
- */
- _getAffectedPoints(centerX, centerY, radius) {
- const { cols, rows, gridSize } = this.mesh;
- const points = this.mesh.deformedPoints;
- const originalPoints = this.mesh.originalPoints;
- const affectedPoints = [];
-
- // 计算影响范围的网格边界
- const minGridX = Math.max(0, Math.floor((centerX - radius) / gridSize));
- const maxGridX = Math.min(cols, Math.ceil((centerX + radius) / gridSize));
- const minGridY = Math.max(0, Math.floor((centerY - radius) / gridSize));
- const maxGridY = Math.min(rows, Math.ceil((centerY + radius) / gridSize));
-
- // 只遍历影响范围内的网格点
- for (let gridY = minGridY; gridY <= maxGridY; gridY++) {
- for (let gridX = minGridX; gridX <= maxGridX; gridX++) {
- const index = gridY * (cols + 1) + gridX;
- if (index < points.length) {
- const point = points[index];
- const originalPoint = originalPoints[index];
- const dx = point.x - centerX;
- const dy = point.y - centerY;
- const distance = Math.sqrt(dx * dx + dy * dy);
-
- // 只包含在影响半径内的点
- if (distance <= radius) {
- affectedPoints.push({
- index,
- point,
- originalPoint,
- distance,
- dx,
- dy,
- });
- }
- }
- }
- }
-
- return affectedPoints;
- }
-
- _smoothMesh() {
- const { rows, cols } = this.mesh;
- const points = this.mesh.deformedPoints;
- const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
-
- for (let iteration = 0; iteration < this.config.smoothingIterations; iteration++) {
- for (let y = 1; y < rows; y++) {
- for (let x = 1; x < cols; x++) {
- const idx = y * (cols + 1) + x;
- const left = points[y * (cols + 1) + (x - 1)];
- const right = points[y * (cols + 1) + (x + 1)];
- const top = points[(y - 1) * (cols + 1) + x];
- const bottom = points[(y + 1) * (cols + 1) + x];
-
- const centerX = (left.x + right.x + top.x + bottom.x) / 4;
- const centerY = (left.y + right.y + top.y + bottom.y) / 4;
-
- const relaxFactor = this.config.relaxFactor;
- tempPoints[idx].x += (centerX - points[idx].x) * relaxFactor;
- tempPoints[idx].y += (centerY - points[idx].y) * relaxFactor;
- }
- }
-
- for (let i = 0; i < points.length; i++) {
- points[i].x = tempPoints[i].x;
- points[i].y = tempPoints[i].y;
- }
- }
- }
-
- /**
- * 专门为旋转模式优化的网格平滑
- */
- _lightSmoothing() {
- const { rows, cols } = this.mesh;
- const points = this.mesh.deformedPoints;
- const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
-
- // 只进行一次轻微平滑
- for (let y = 1; y < rows; y++) {
- for (let x = 1; x < cols; x++) {
- const idx = y * (cols + 1) + x;
- const left = points[y * (cols + 1) + (x - 1)];
- const right = points[y * (cols + 1) + (x + 1)];
- const top = points[(y - 1) * (cols + 1) + x];
- const bottom = points[(y + 1) * (cols + 1) + x];
-
- const centerX = (left.x + right.x + top.x + bottom.x) / 4;
- const centerY = (left.y + right.y + top.y + bottom.y) / 4;
-
- // 使用更小的松弛因子
- const lightRelaxFactor = this.config.relaxFactor * 0.3;
- tempPoints[idx].x += (centerX - points[idx].x) * lightRelaxFactor;
- tempPoints[idx].y += (centerY - points[idx].y) * lightRelaxFactor;
- }
- }
-
- for (let i = 0; i < points.length; i++) {
- points[i].x = tempPoints[i].x;
- points[i].y = tempPoints[i].y;
- }
- }
-
- /**
- * 使用更优化的衰减函数
- * @param {number} t 归一化距离 (0-1)
- * @returns {number} 衰减因子 (0-1)
- */
- _optimizedFalloff(t) {
- if (t >= 1.0) return 0;
-
- // 对于旋转模式,使用专门的衰减函数
- if (
- this.currentMode === this.modes.CLOCKWISE ||
- this.currentMode === this.modes.COUNTERCLOCKWISE
- ) {
- return this._stableRotationFalloff(t); // 修复函数名
- }
-
- // 其他模式使用原来的衰减函数
- const smoothT = 1 - t;
-
- // 多项式衰减 + 指数衰减的组合
- const polynomial = smoothT * smoothT * (3 - 2 * smoothT); // 平滑阶梯函数
- const exponential = Math.exp(-t * 2); // 指数衰减
-
- // 组合两种衰减方式,在不同区域有不同特性
- const weight = Math.cos(t * Math.PI * 0.5); // 权重函数
-
- return polynomial * weight + exponential * (1 - weight);
- }
-
- _applyMeshToImage() {
- if (!this.mesh || !this.originalImageData) {
- return this.currentImageData;
- }
-
- const width = this.originalImageData.width;
- const height = this.originalImageData.height;
- const result = new ImageData(width, height);
- const srcData = this.originalImageData.data;
- const dstData = result.data;
-
- // 性能优化:使用步长采样减少计算量
- const step = width > 1000 || height > 1000 ? 2 : 1;
-
- for (let y = 0; y < height; y += step) {
- for (let x = 0; x < width; x += step) {
- const srcPos = this._mapPointBack(x, y);
-
- if (srcPos.x >= 0 && srcPos.x < width && srcPos.y >= 0 && srcPos.y < height) {
- const color = this._bilinearInterpolate(srcData, width, height, srcPos.x, srcPos.y);
-
- // 如果使用步长采样,需要填充相邻像素
- for (let dy = 0; dy < step && y + dy < height; dy++) {
- for (let dx = 0; dx < step && x + dx < width; dx++) {
- const dstIdx = ((y + dy) * width + (x + dx)) * 4;
- dstData[dstIdx] = color[0];
- dstData[dstIdx + 1] = color[1];
- dstData[dstIdx + 2] = color[2];
- dstData[dstIdx + 3] = color[3];
- }
- }
- }
- }
- }
-
- this.currentImageData = result;
- return result;
- }
-
- // 添加异步处理方法用于大图像
- async applyDeformationAsync(x, y) {
- return new Promise((resolve) => {
- setTimeout(() => {
- const result = this.applyDeformation(x, y);
- resolve(result);
- }, 0);
- });
- }
-
- // 批量处理方法
- applyDeformationBatch(positions) {
- if (!this.initialized || !this.mesh || positions.length === 0) {
- return this.currentImageData;
- }
-
- // 对于批量处理,模拟连续的拖拽操作
- if (positions.length > 0) {
- // 使用第一个位置作为初始点
- this.startDeformation(positions[0].x, positions[0].y);
-
- // 逐个应用每个位置的变形
- positions.forEach((pos, index) => {
- if (index === 0) return; // 跳过第一个,因为已经作为初始点
-
- // 更新当前位置并应用变形
- this.currentMouseX = pos.x;
- this.currentMouseY = pos.y;
-
- // 重新计算拖拽参数
- const deltaX = this.currentMouseX - this.initialMouseX;
- const deltaY = this.currentMouseY - this.initialMouseY;
- this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- this.dragAngle = Math.atan2(deltaY, deltaX);
-
- const { size, pressure, distortion, power } = this.params;
- const mode = this.currentMode;
- const radius = size * 0.8;
-
- // 根据推拉模式和拖拽距离动态调整强度
- let strength;
- if (mode === this.modes.PUSH) {
- const baseStrength = (pressure * power * this.config.maxStrength) / 100;
- const distanceFactor = Math.min(this.dragDistance / radius, 2.0);
- strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度
- } else {
- strength = (pressure * power * this.config.maxStrength) / 100;
- }
-
- this._applyDeformation(pos.x, pos.y, radius, strength, mode, distortion);
- });
-
- // 结束拖拽操作
- this.endDeformation();
- }
-
- if (this.config.smoothingIterations > 0) {
- this._smoothMesh();
- }
-
- return this._applyMeshToImage();
- }
-
- /**
- * 改进的网格映射算法 - 防止空白区域
- */
- _mapPointBack(x, y) {
- const { cols, rows, gridSize } = this.mesh;
- const gridX = x / gridSize;
- const gridY = y / gridSize;
-
- const x1 = Math.floor(gridX);
- const y1 = Math.floor(gridY);
- const x2 = Math.min(x1 + 1, cols);
- const y2 = Math.min(y1 + 1, rows);
-
- const fx = gridX - x1;
- const fy = gridY - y1;
-
- // 获取四个网格点的变形和原始坐标
- const deformed = [
- this.mesh.deformedPoints[y1 * (cols + 1) + x1],
- this.mesh.deformedPoints[y1 * (cols + 1) + x2],
- this.mesh.deformedPoints[y2 * (cols + 1) + x1],
- this.mesh.deformedPoints[y2 * (cols + 1) + x2],
- ];
-
- const original = [
- this.mesh.originalPoints[y1 * (cols + 1) + x1],
- this.mesh.originalPoints[y1 * (cols + 1) + x2],
- this.mesh.originalPoints[y2 * (cols + 1) + x1],
- this.mesh.originalPoints[y2 * (cols + 1) + x2],
- ];
-
- // 双线性插值计算变形后的位置
- const deformedX =
- (1 - fx) * (1 - fy) * deformed[0].x +
- fx * (1 - fy) * deformed[1].x +
- (1 - fx) * fy * deformed[2].x +
- fx * fy * deformed[3].x;
- const deformedY =
- (1 - fx) * (1 - fy) * deformed[0].y +
- fx * (1 - fy) * deformed[1].y +
- (1 - fx) * fy * deformed[2].y +
- fx * fy * deformed[3].y;
-
- // 计算原始网格位置
- const originalX = x1 * gridSize + fx * gridSize;
- const originalY = y1 * gridSize + fy * gridSize;
-
- // 计算偏移量并应用反向映射
- const offsetX = deformedX - originalX;
- const offsetY = deformedY - originalY;
-
- // 限制偏移量,防止过度扭曲
- const maxOffset = gridSize * 0.5;
- const limitedOffsetX = Math.max(-maxOffset, Math.min(maxOffset, offsetX));
- const limitedOffsetY = Math.max(-maxOffset, Math.min(maxOffset, offsetY));
-
- return {
- x: Math.max(0, Math.min(this.mesh.width - 1, x - limitedOffsetX)),
- y: Math.max(0, Math.min(this.mesh.height - 1, y - limitedOffsetY)),
- };
- }
-
- _bilinearInterpolate(data, width, height, x, y) {
- const x1 = Math.floor(x);
- const y1 = Math.floor(y);
- const x2 = Math.min(x1 + 1, width - 1);
- const y2 = Math.min(y1 + 1, height - 1);
-
- const fx = x - x1;
- const fy = y - y1;
-
- const getPixel = (px, py) => {
- const idx = (py * width + px) * 4;
- return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
- };
-
- const p1 = getPixel(x1, y1);
- const p2 = getPixel(x2, y1);
- const p3 = getPixel(x1, y2);
- const p4 = getPixel(x2, y2);
-
- return [
- Math.round(
- (1 - fx) * (1 - fy) * p1[0] +
- fx * (1 - fy) * p2[0] +
- (1 - fx) * fy * p3[0] +
- fx * fy * p4[0]
- ),
- Math.round(
- (1 - fx) * (1 - fy) * p1[1] +
- fx * (1 - fy) * p2[1] +
- (1 - fx) * fy * p3[1] +
- fx * fy * p4[1]
- ),
- Math.round(
- (1 - fx) * (1 - fy) * p1[2] +
- fx * (1 - fy) * p2[2] +
- (1 - fx) * fy * p3[2] +
- fx * fy * p4[2]
- ),
- Math.round(
- (1 - fx) * (1 - fy) * p1[3] +
- fx * (1 - fy) * p2[3] +
- (1 - fx) * fy * p3[3] +
- fx * fy * p4[3]
- ),
- ];
- }
-
- reset() {
- if (!this.mesh || !this.originalImageData) return false;
-
- for (let i = 0; i < this.mesh.deformedPoints.length; i++) {
- this.mesh.deformedPoints[i].x = this.mesh.originalPoints[i].x;
- this.mesh.deformedPoints[i].y = this.mesh.originalPoints[i].y;
- }
-
- this.currentImageData = new ImageData(
- new Uint8ClampedArray(this.originalImageData.data),
- this.originalImageData.width,
- this.originalImageData.height
- );
-
- // 重置拖拽状态
- this.initialMouseX = 0;
- this.initialMouseY = 0;
- this.currentMouseX = 0;
- this.currentMouseY = 0;
- this.lastMouseX = 0;
- this.lastMouseY = 0;
- this.mouseMovementX = 0;
- this.mouseMovementY = 0;
- this.isFirstApply = true;
- this.isDragging = false;
- this.dragDistance = 0;
- this.dragAngle = 0;
-
- // 新增:重置持续按压状态
- this.isHolding = false;
- this.pressStartTime = 0;
- this.pressDuration = 0;
- this.accumulatedRotation = 0;
- this.accumulatedScale = 0;
- this.lastApplyTime = 0;
-
- this.deformHistory = [];
- return true;
- }
-
- // 新增:获取持续按压状态信息
- getHoldingInfo() {
- return {
- isHolding: this.isHolding,
- pressDuration: this.pressDuration,
- accumulatedRotation: this.accumulatedRotation,
- accumulatedScale: this.accumulatedScale,
- pressStartTime: this.pressStartTime,
- };
- }
-
- /**
- * 应用液化变形 - 主要的公共接口方法
- * @param {Number} x X坐标
- * @param {Number} y Y坐标
- * @returns {ImageData} 变形后的图像数据
- */
- applyDeformation(x, y) {
- if (!this.initialized || !this.mesh || !this.originalImageData) {
- console.warn("液化管理器未初始化或缺少必要数据");
- return this.currentImageData;
- }
-
- // 更新鼠标位置
- this.currentMouseX = x;
- this.currentMouseY = y;
-
- // 计算拖拽参数
- const deltaX = this.currentMouseX - this.initialMouseX;
- const deltaY = this.currentMouseY - this.initialMouseY;
- this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- this.dragAngle = Math.atan2(deltaY, deltaX);
-
- // 获取当前参数
- const { size, pressure, power } = this.params;
- const mode = this.currentMode;
- const radius = size;
- const strength = pressure * power;
-
- // 根据模式选择算法
- const pixelModes = [
- this.modes.CLOCKWISE,
- this.modes.COUNTERCLOCKWISE,
- this.modes.PINCH,
- this.modes.EXPAND,
- this.modes.PUSH,
- ];
-
- if (pixelModes.includes(mode)) {
- // 使用增强的像素算法
- switch (mode) {
- case this.modes.CLOCKWISE:
- this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
- break;
- case this.modes.COUNTERCLOCKWISE:
- this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
- break;
- case this.modes.PINCH:
- this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
- break;
- case this.modes.EXPAND:
- this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
- break;
- case this.modes.PUSH:
- this._applyEnhancedPushDeformation(x, y, radius, strength);
- break;
- }
-
- // 更新最后应用时间
- this.lastApplyTime = Date.now();
- this.isFirstApply = false;
-
- return this.currentImageData;
- } else {
- // 使用原有的网格算法处理其他模式
- if (!this.mesh) {
- console.warn("网格未初始化");
- return this.currentImageData;
- }
-
- const finalStrength = (strength * this.config.maxStrength) / 100;
-
- // 应用变形
- this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
-
- // 平滑处理
- if (this.config.smoothingIterations > 0) {
- this._smoothMesh();
- }
-
- // 更新图像数据
- const result = this._applyMeshToImage();
-
- // 更新最后应用时间
- this.lastApplyTime = Date.now();
- this.isFirstApply = false;
-
- return result;
- }
- }
-
- getCurrentImageData() {
- return this.currentImageData;
- }
-
- destroy() {
- // 停止持续效果定时器
- this.stopContinuousEffect();
-
- this.originalImageData = null;
- this.currentImageData = null;
- this.mesh = null;
- this.deformHistory = [];
- this.initialized = false;
-
- // 清理拖拽状态
- this.initialMouseX = 0;
- this.initialMouseY = 0;
- this.currentMouseX = 0;
- this.currentMouseY = 0;
- this.lastMouseX = 0;
- this.lastMouseY = 0;
- this.mouseMovementX = 0;
- this.mouseMovementY = 0;
- this.isFirstApply = true;
- this.isDragging = false;
- this.dragDistance = 0;
- this.dragAngle = 0;
-
- // 新增:清理持续按压状态
- this.isHolding = false;
- this.pressStartTime = 0;
- this.pressDuration = 0;
- this.accumulatedRotation = 0;
- this.accumulatedScale = 0;
- this.lastApplyTime = 0;
- }
-
- /**
- * 释放资源 - 别名方法,与其他管理器保持一致
- */
- dispose() {
- this.destroy();
- }
+ constructor(options = {}) {
+ this.config = {
+ gridSize: 8, // 稍微增大网格提高性能
+ maxStrength: 200, // 适度降低最大强度
+ smoothingIterations: 1, // 增加平滑处理
+ relaxFactor: 0.05, // 适度松弛
+ sharpenAmount: 0.3, // 添加锐化强度参数
+ ...options,
+ };
+ console.log("CPU版本的液化管理器config",this.config);
+
+ this.params = {
+ size: 60, // 增大默认尺寸
+ pressure: 0.6, // 增大默认压力
+ distortion: 0,
+ power: 0.7, // 增大默认动力
+ };
+
+ this.modes = {
+ PUSH: "push",
+ CLOCKWISE: "clockwise",
+ COUNTERCLOCKWISE: "counterclockwise",
+ PINCH: "pinch",
+ EXPAND: "expand",
+ CRYSTAL: "crystal",
+ EDGE: "edge",
+ RECONSTRUCT: "reconstruct",
+ };
+
+ this.currentMode = this.modes.PUSH;
+ this.originalImageData = null;
+ this.currentImageData = null;
+ this.mesh = null;
+ this.initialized = false;
+ this.canvas = document.createElement("canvas");
+ this.ctx = this.canvas.getContext("2d");
+ this.deformHistory = [];
+
+ // 性能优化相关
+ this.lastUpdateTime = 0;
+ this.updateThrottle = 16; // 限制更新频率约60fps
+ this.isProcessing = false;
+
+ // 鼠标位置跟踪(用于推拉模式)
+ this.initialMouseX = 0; // 初始点击位置X
+ this.initialMouseY = 0; // 初始点击位置Y
+ this.currentMouseX = 0; // 当前鼠标位置X
+ this.currentMouseY = 0; // 当前鼠标位置Y
+ this.lastMouseX = 0;
+ this.lastMouseY = 0;
+ this.mouseMovementX = 0;
+ this.mouseMovementY = 0;
+ this.isFirstApply = true; // 标记是否是首次应用
+ this.isDragging = false; // 标记是否正在拖拽
+ this.dragDistance = 0; // 拖拽距离
+ this.dragAngle = 0; // 拖拽角度
+
+ // 新增:持续按压相关状态
+ this.pressStartTime = 0; // 按压开始时间
+ this.pressDuration = 0; // 按压持续时间
+ this.accumulatedRotation = 0; // 累积旋转角度(用于顺时针/逆时针)
+ this.accumulatedScale = 0; // 累积缩放量(用于捏合/展开)
+ this.lastApplyTime = 0; // 上次应用时间
+ this.continuousApplyInterval = 50; // 持续应用间隔(毫秒)
+ this.isHolding = false; // 是否正在持续按压
+ }
+
+ initialize(imageSource) {
+ try {
+ if (imageSource instanceof ImageData) {
+ this.originalImageData = new ImageData(
+ new Uint8ClampedArray(imageSource.data),
+ imageSource.width,
+ imageSource.height
+ );
+ } else if (imageSource instanceof HTMLImageElement) {
+ this.canvas.width = imageSource.width;
+ this.canvas.height = imageSource.height;
+ this.ctx.drawImage(imageSource, 0, 0);
+ this.originalImageData = this.ctx.getImageData(0, 0, imageSource.width, imageSource.height);
+ } else {
+ throw new Error("不支持的图像类型");
+ }
+
+ this.currentImageData = new ImageData(
+ new Uint8ClampedArray(this.originalImageData.data),
+ this.originalImageData.width,
+ this.originalImageData.height
+ );
+
+ this._initMesh(this.originalImageData.width, this.originalImageData.height);
+ this.initialized = true;
+ return true;
+ } catch (error) {
+ console.error("液化管理器初始化失败:", error);
+ return false;
+ }
+ }
+
+ _initMesh(width, height) {
+ const gridSize = this.config.gridSize;
+ const cols = Math.ceil(width / gridSize);
+ const rows = Math.ceil(height / gridSize);
+
+ this.mesh = {
+ cols,
+ rows,
+ gridSize,
+ width,
+ height,
+ originalPoints: [],
+ deformedPoints: [],
+ };
+
+ for (let y = 0; y <= rows; y++) {
+ for (let x = 0; x <= cols; x++) {
+ const point = { x: x * gridSize, y: y * gridSize };
+ this.mesh.originalPoints.push({ ...point });
+ this.mesh.deformedPoints.push({ ...point });
+ }
+ }
+ }
+
+ setMode(mode) {
+ if (Object.values(this.modes).includes(mode)) {
+ this.currentMode = mode;
+ return true;
+ }
+ return false;
+ }
+
+ setParam(param, value) {
+ if (param === 'sharpness') {
+ this.config.sharpenAmount = Math.max(0, Math.min(1, value));
+ return true;
+ }
+ if (param in this.params) {
+ this.params[param] = value;
+ return true;
+ }
+ return false;
+ }
+
+ getParams() {
+ return { ...this.params, sharpness: this.config.sharpenAmount, };
+ }
+ // 添加清晰度控制方法
+ setSharpness(amount) {
+ this.config.sharpenAmount = Math.max(0, Math.min(1, amount));
+ return this;
+ }
+ resetParams() {
+ this.params = {
+ size: 60, // 增大默认尺寸
+ pressure: 0.6, // 增大默认压力
+ distortion: 0,
+ power: 0.7, // 增大默认动力
+ };
+ }
+
+ /**
+ * 开始液化操作(记录初始点)
+ * @param {Number} x 初始X坐标
+ * @param {Number} y 初始Y坐标
+ */
+ startDeformation(x, y) {
+ this.initialMouseX = x;
+ this.initialMouseY = y;
+ this.currentMouseX = x;
+ this.currentMouseY = y;
+ this.lastMouseX = x;
+ this.lastMouseY = y;
+ this.isDragging = true;
+ this.isFirstApply = true;
+ this.dragDistance = 0;
+ this.dragAngle = 0;
+
+ // 新增:初始化持续按压状态
+ this.pressStartTime = Date.now();
+ this.pressDuration = 0;
+ this.accumulatedRotation = 0;
+ this.accumulatedScale = 0;
+ this.lastApplyTime = this.pressStartTime;
+ this.isHolding = true;
+
+ // 启动持续效果定时器(对于所有模式都支持持续按压)
+ this.startContinuousEffect();
+
+ console.log(`开始液化操作,初始点: (${x}, ${y})`);
+ }
+
+ /**
+ * 结束液化操作
+ */
+ endDeformation() {
+ this.isDragging = false;
+ this.isFirstApply = true;
+ this.dragDistance = 0;
+ this.dragAngle = 0;
+
+ // 新增:重置持续按压状态
+ this.isHolding = false;
+ this.pressStartTime = 0;
+ this.pressDuration = 0;
+ this.accumulatedRotation = 0;
+ this.accumulatedScale = 0;
+ this.lastApplyTime = 0;
+
+ // 停止持续效果定时器
+ this.stopContinuousEffect();
+
+ console.log("结束液化操作");
+ }
+
+ // 新增:启动持续效果
+ startContinuousEffect() {
+ this.stopContinuousEffect(); // 先停止已有的定时器
+
+ this.continuousTimer = setInterval(() => {
+ if (this.isHolding && this.initialized) {
+ // 更新持续时间
+ this.pressDuration = Date.now() - this.pressStartTime;
+
+ // 所有模式都支持持续效果
+ this.applyContinuousDeformation();
+ }
+ }, this.continuousApplyInterval);
+ }
+
+ // 新增:停止持续效果
+ stopContinuousEffect() {
+ if (this.continuousTimer) {
+ clearInterval(this.continuousTimer);
+ this.continuousTimer = null;
+ }
+ }
+
+ /**
+ * 稳定的旋转衰减函数 - 确保内圈快外圈慢,保持纹理连续性
+ * @param {number} t 归一化距离 (0-1)
+ * @returns {number} 衰减因子 (0-1)
+ */
+ _stableRotationFalloff(t) {
+ if (t >= 1.0) return 0;
+ if (t <= 0) return 1;
+
+ // 使用反向二次函数:内圈(t=0)时值为1,外圈(t=1)时值为0
+ // 这确保了内圈旋转最快,外圈旋转最慢
+ const inverseFalloff = 1 - t;
+
+ // 使用平滑的二次衰减,确保内圈效果强,外圈效果弱
+ const quadraticFalloff = inverseFalloff * inverseFalloff;
+
+ // 添加轻微的线性分量,确保过渡平滑
+ const linearFalloff = inverseFalloff;
+
+ // 混合二次和线性衰减,70%二次衰减 + 30%线性衰减
+ return quadraticFalloff * 0.7 + linearFalloff * 0.3;
+ }
+
+ /**
+ * 基于test-liquify-enhanced.html的旋转算法 - 像素级实现
+ * @param {number} centerX 旋转中心X坐标
+ * @param {number} centerY 旋转中心Y坐标
+ * @param {number} radius 影响半径
+ * @param {number} strength 强度
+ * @param {boolean} isClockwise 是否顺时针旋转
+ */
+ _applyEnhancedRotationDeformation(centerX, centerY, radius, strength, isClockwise) {
+ if (!this.currentImageData) return;
+
+ const data = this.currentImageData.data;
+ const width = this.currentImageData.width;
+ const height = this.currentImageData.height;
+ const tempData = new Uint8ClampedArray(data);
+
+ // 计算旋转角度 - 基于test-liquify-enhanced.html的算法
+ const { pressure, power } = this.params;
+ const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
+ const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度
+ const rotationAngle =
+ (isClockwise ? 1 : -1) * baseRotationSpeed * pressure * power * (1.0 + timeFactor * 0.5);
+
+ // 累积旋转角度 - 关键:这确保了持续旋转效果
+ this.accumulatedRotation += rotationAngle;
+
+ const processRadius = Math.min(radius, Math.min(width, height) / 2);
+ const minX = Math.max(0, Math.floor(centerX - processRadius));
+ const maxX = Math.min(width, Math.ceil(centerX + processRadius));
+ const minY = Math.max(0, Math.floor(centerY - processRadius));
+ const maxY = Math.min(height, Math.ceil(centerY + processRadius));
+
+ // 遍历影响区域内的每个像素
+ for (let y = minY; y < maxY; y++) {
+ for (let x = minX; x < maxX; x++) {
+ const dx = x - centerX;
+ const dy = y - centerY;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < processRadius && distance > 0.1) {
+ // 距离衰减:内圈快,外圈慢 - 与测试文件算法一致
+ const normalizedDistance = distance / processRadius;
+ const falloff = Math.pow(1 - normalizedDistance, 2); // 二次衰减
+
+ // 计算旋转后的源位置 - 关键算法
+ const angle = Math.atan2(dy, dx);
+ const newAngle = angle + this.accumulatedRotation * falloff;
+
+ const sourceX = centerX + Math.cos(newAngle) * distance;
+ const sourceY = centerY + Math.sin(newAngle) * distance;
+
+ // 双线性插值采样 - 确保像素连续性
+ const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
+
+ if (color) {
+ const targetIdx = (y * width + x) * 4;
+ data[targetIdx] = color[0];
+ data[targetIdx + 1] = color[1];
+ data[targetIdx + 2] = color[2];
+ data[targetIdx + 3] = color[3];
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 基于test-liquify-enhanced.html的捏合/展开算法
+ * @param {number} centerX 中心X坐标
+ * @param {number} centerY 中心Y坐标
+ * @param {number} radius 影响半径
+ * @param {number} strength 强度
+ * @param {boolean} isPinch 是否为捏合模式
+ */
+ _applyEnhancedPinchDeformation(centerX, centerY, radius, strength, isPinch) {
+ if (!this.currentImageData) return;
+
+ const data = this.currentImageData.data;
+ const width = this.currentImageData.width;
+ const height = this.currentImageData.height;
+ const tempData = new Uint8ClampedArray(data);
+
+ // 计算时间相关的缩放因子 - 基于test-liquify-enhanced.html
+ const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
+ const baseScaleFactor = isPinch ? -0.01 : 0.01;
+ const scaleFactor = baseScaleFactor * (1.0 + timeFactor * 0.5);
+
+ this.accumulatedScale += scaleFactor;
+
+ const processRadius = Math.min(radius, Math.min(width, height) / 2);
+ const minX = Math.max(0, Math.floor(centerX - processRadius));
+ const maxX = Math.min(width, Math.ceil(centerX + processRadius));
+ const minY = Math.max(0, Math.floor(centerY - processRadius));
+ const maxY = Math.min(height, Math.ceil(centerY + processRadius));
+
+ for (let y = minY; y < maxY; y++) {
+ for (let x = minX; x < maxX; x++) {
+ const dx = x - centerX;
+ const dy = y - centerY;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < processRadius && distance > 0.1) {
+ const normalizedDistance = distance / processRadius;
+ const falloff = 1 - normalizedDistance * normalizedDistance;
+
+ // 计算缩放后的位置
+ const scale = 1 + this.accumulatedScale * falloff;
+ const sourceX = centerX + dx * scale;
+ const sourceY = centerY + dy * scale;
+
+ // 双线性插值采样
+ const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
+
+ if (color) {
+ const targetIdx = (y * width + x) * 4;
+ data[targetIdx] = color[0];
+ data[targetIdx + 1] = color[1];
+ data[targetIdx + 2] = color[2];
+ data[targetIdx + 3] = color[3];
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 基于test-liquify-enhanced.html的推拉算法
+ * @param {number} centerX 中心X坐标
+ * @param {number} centerY 中心Y坐标
+ * @param {number} radius 影响半径
+ * @param {number} strength 强度
+ */
+ _applyEnhancedPushDeformation(centerX, centerY, radius, strength) {
+ if (!this.currentImageData) return;
+
+ const data = this.currentImageData.data;
+ const width = this.currentImageData.width;
+ const height = this.currentImageData.height;
+ const tempData = new Uint8ClampedArray(data);
+
+ // 计算推拉方向
+ const deltaX = this.currentMouseX - this.initialMouseX;
+ const deltaY = this.currentMouseY - this.initialMouseY;
+ const dragLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ const processRadius = Math.min(radius, Math.min(width, height) / 2);
+ const minX = Math.max(0, Math.floor(centerX - processRadius));
+ const maxX = Math.min(width, Math.ceil(centerX + processRadius));
+ const minY = Math.max(0, Math.floor(centerY - processRadius));
+ const maxY = Math.min(height, Math.ceil(centerY + processRadius));
+
+ if (dragLength === 0) {
+ // 如果没有拖拽,在持续按压时执行基础的外推效果
+ if (this.isHolding) {
+ const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
+ const pushStrength = strength * timeFactor * 0.3;
+
+ for (let y = minY; y < maxY; y++) {
+ for (let x = minX; x < maxX; x++) {
+ const dx = x - centerX;
+ const dy = y - centerY;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < processRadius && distance > 0.1) {
+ const normalizedDistance = distance / processRadius;
+ const falloff = 1 - normalizedDistance * normalizedDistance;
+ const factor = falloff * pushStrength;
+
+ // 径向外推效果
+ const pushX = (dx / distance) * factor;
+ const pushY = (dy / distance) * factor;
+
+ const sourceX = x - pushX;
+ const sourceY = y - pushY;
+
+ const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
+
+ if (color) {
+ const targetIdx = (y * width + x) * 4;
+ data[targetIdx] = color[0];
+ data[targetIdx + 1] = color[1];
+ data[targetIdx + 2] = color[2];
+ data[targetIdx + 3] = color[3];
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ // 有拖拽时的推拉效果
+ const dirX = deltaX / dragLength;
+ const dirY = deltaY / dragLength;
+
+ for (let y = minY; y < maxY; y++) {
+ for (let x = minX; x < maxX; x++) {
+ const dx = x - centerX;
+ const dy = y - centerY;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < processRadius && distance > 0.1) {
+ const normalizedDistance = distance / processRadius;
+ const falloff = 1 - normalizedDistance * normalizedDistance;
+ const factor = falloff * strength;
+
+ const offsetX = dirX * factor * Math.min(dragLength * 0.3, 15);
+ const offsetY = dirY * factor * Math.min(dragLength * 0.3, 15);
+
+ const sourceX = x - offsetX;
+ const sourceY = y - offsetY;
+
+ const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
+
+ if (color) {
+ const targetIdx = (y * width + x) * 4;
+ data[targetIdx] = color[0];
+ data[targetIdx + 1] = color[1];
+ data[targetIdx + 2] = color[2];
+ data[targetIdx + 3] = color[3];
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 优化的持续变形效果处理 - 使用增强算法
+ */
+ applyContinuousDeformation() {
+ if (!this.isHolding || !this.initialized || !this.currentImageData) return;
+
+ const { size, pressure, power } = this.params;
+ const mode = this.currentMode;
+ const radius = size;
+ const x = this.initialMouseX;
+ const y = this.initialMouseY;
+ const strength = pressure * power;
+
+ // 根据模式使用相应的增强算法
+ switch (mode) {
+ case this.modes.CLOCKWISE:
+ this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
+ break;
+
+ case this.modes.COUNTERCLOCKWISE:
+ this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
+ break;
+
+ case this.modes.PINCH:
+ this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
+ break;
+
+ case this.modes.EXPAND:
+ this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
+ break;
+
+ case this.modes.PUSH:
+ this._applyEnhancedPushDeformation(x, y, radius, strength);
+ break;
+
+ default: {
+ // 对于其他模式,使用原有的网格算法
+ if (!this.mesh) return;
+
+ const baseStrength = (pressure * power * this.config.maxStrength) / 100;
+ const timeFactor = Math.min(this.pressDuration / 1000, 4.0);
+ const finalStrength = baseStrength * (1.0 + timeFactor * 0.5);
+
+ this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
+
+ if (this.config.smoothingIterations > 0) {
+ this._lightSmoothing();
+ }
+
+ return this._applyMeshToImage();
+ }
+ }
+
+ // 对于像素算法,直接返回当前图像数据
+ return this.currentImageData;
+ }
+
+ /**
+ * 应用液化变形 - 主要入口,集成增强算法
+ */
+ // applyDeformation(x, y) {
+ // if (!this.initialized || !this.originalImageData) {
+ // console.warn("液化管理器未初始化或缺少必要数据");
+ // return this.currentImageData;
+ // }
+
+ // // 更新鼠标位置
+ // this.currentMouseX = x;
+ // this.currentMouseY = y;
+
+ // // 计算拖拽参数
+ // const deltaX = this.currentMouseX - this.initialMouseX;
+ // const deltaY = this.currentMouseY - this.initialMouseY;
+ // this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ // this.dragAngle = Math.atan2(deltaY, deltaX);
+
+ // // 获取当前参数
+ // const { size, pressure, power } = this.params;
+ // const mode = this.currentMode;
+ // const radius = size;
+ // const strength = pressure * power;
+
+ // // 根据模式选择算法
+ // const pixelModes = [
+ // this.modes.CLOCKWISE,
+ // this.modes.COUNTERCLOCKWISE,
+ // this.modes.PINCH,
+ // this.modes.EXPAND,
+ // this.modes.PUSH,
+ // ];
+
+ // if (pixelModes.includes(mode)) {
+ // // 使用增强的像素算法
+ // switch (mode) {
+ // case this.modes.CLOCKWISE:
+ // this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
+ // break;
+ // case this.modes.COUNTERCLOCKWISE:
+ // this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
+ // break;
+ // case this.modes.PINCH:
+ // this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
+ // break;
+ // case this.modes.EXPAND:
+ // this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
+ // break;
+ // case this.modes.PUSH:
+ // this._applyEnhancedPushDeformation(x, y, radius, strength);
+ // break;
+ // }
+
+ // // 更新最后应用时间
+ // this.lastApplyTime = Date.now();
+ // this.isFirstApply = false;
+
+ // return this.currentImageData;
+ // } else {
+ // // 使用原有的网格算法处理其他模式
+ // if (!this.mesh) {
+ // console.warn("网格未初始化");
+ // return this.currentImageData;
+ // }
+
+ // const finalStrength = (strength * this.config.maxStrength) / 100;
+
+ // // 应用变形
+ // this._applyDeformation(
+ // x,
+ // y,
+ // radius,
+ // finalStrength,
+ // mode,
+ // this.params.distortion,
+ // );
+
+ // // 平滑处理
+ // if (this.config.smoothingIterations > 0) {
+ // this._smoothMesh();
+ // }
+
+ // // 更新图像数据
+ // const result = this._applyMeshToImage();
+
+ // // 更新最后应用时间
+ // this.lastApplyTime = Date.now();
+ // this.isFirstApply = false;
+
+ // return result;
+ // }
+ // }
+
+ /**
+ * 双线性插值采样 - 用于像素级算法
+ * @param {Uint8ClampedArray} data 图像数据
+ * @param {number} width 图像宽度
+ * @param {number} height 图像高度
+ * @param {number} x X坐标
+ * @param {number} y Y坐标
+ * @returns {Array|null} RGBA颜色值数组或null
+ */
+ _bilinearSample(data, width, height, x, y) {
+ return this._bicubicInterpolate(data, width, height, x, y);
+ }
+ /**
+ * 双三次插值实现 - 确保正确处理Alpha通道
+ * @param {Uint8ClampedArray} data 图像数据
+ * @param {number} width 图像宽度
+ * @param {number} height 图像高度
+ * @param {number} x X坐标
+ * @param {number} y Y坐标
+ * @returns {Array|null} RGBA颜色值数组或null
+ */
+ _bicubicInterpolate(data, width, height, x, y) {
+ // 获取周围16个像素点
+ const x1 = Math.floor(x) - 1;
+ const y1 = Math.floor(y) - 1;
+
+ // 创建16个采样点的颜色数组
+ const pixels = [];
+ for (let ky = 0; ky < 4; ky++) {
+ for (let kx = 0; kx < 4; kx++) {
+ const px = Math.max(0, Math.min(width - 1, x1 + kx));
+ const py = Math.max(0, Math.min(height - 1, y1 + ky));
+ const idx = (py * width + px) * 4;
+ pixels[ky * 4 + kx] = [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
+ }
+ }
+
+ // 计算小数部分
+ const fx = x - (x1 + 1);
+ const fy = y - (y1 + 1);
+
+ // 计算行插值
+ const row0 = this._cubicInterpolateRow(pixels[0], pixels[1], pixels[2], pixels[3], fx);
+ const row1 = this._cubicInterpolateRow(pixels[4], pixels[5], pixels[6], pixels[7], fx);
+ const row2 = this._cubicInterpolateRow(pixels[8], pixels[9], pixels[10], pixels[11], fx);
+ const row3 = this._cubicInterpolateRow(pixels[12], pixels[13], pixels[14], pixels[15], fx);
+
+ // 计算最终结果
+ return this._cubicInterpolateRow(row0, row1, row2, row3, fy);
+ }
+ // 三次插值辅助方法 - 单行插值
+ _cubicInterpolateRow(p0, p1, p2, p3, t) {
+ // 使用三次多项式插值公式
+ const a = [0, 0, 0, 0];
+ const b = [0, 0, 0, 0];
+ const c = [0, 0, 0, 0];
+
+ // 为每个通道计算插值系数
+ for (let i = 0; i < 4; i++) {
+ a[i] = -0.5 * p0[i] + 1.5 * p1[i] - 1.5 * p2[i] + 0.5 * p3[i];
+ b[i] = p0[i] - 2.5 * p1[i] + 2 * p2[i] - 0.5 * p3[i];
+ c[i] = -0.5 * p0[i] + 0.5 * p2[i];
+ }
+
+ // 应用三次多项式
+ const t2 = t * t;
+ const t3 = t * t2;
+
+ return [
+ Math.round(a[0] * t3 + b[0] * t2 + c[0] * t + p1[0]),
+ Math.round(a[1] * t3 + b[1] * t2 + c[1] * t + p1[1]),
+ Math.round(a[2] * t3 + b[2] * t2 + c[2] * t + p1[2]),
+ Math.round(a[3] * t3 + b[3] * t2 + c[3] * t + p1[3]) // 确保Alpha通道也被正确插值
+ ];
+ }
+
+ /**
+ * 应用变形到网格 - 原有的网格算法(用于其他模式)
+ */
+ _applyDeformation(x, y, radius, strength, mode, distortion) {
+ if (!this.mesh) return;
+
+ const points = this.mesh.deformedPoints;
+ const originalPoints = this.mesh.originalPoints;
+
+ // 性能优化:只计算影响范围内的网格点
+ const affectedPoints = this._getAffectedPoints(x, y, radius);
+
+ for (const pointInfo of affectedPoints) {
+ const { index: i, point, originalPoint, distance } = pointInfo;
+
+ if (distance > 0) {
+ // 使用优化的衰减函数
+ const normalizedDistance = distance / radius;
+ const factor = this._optimizedFalloff(normalizedDistance) * strength;
+
+ switch (mode) {
+ case this.modes.CRYSTAL: {
+ // 水晶模式
+ const dx = point.x - x;
+ const dy = point.y - y;
+ const crystalAngle = Math.atan2(dy, dx);
+ const crystalRadius = normalizedDistance;
+
+ const baseDistortion = Math.max(distortion, 0.3);
+ const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
+ const timeEnhancedDistortion = baseDistortion * (1.0 + timeFactor * 0.3);
+
+ const wave1 = Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
+ const wave2 = Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
+ const waveAngle = crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
+
+ const radialMod =
+ 1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
+ const modDistance = distance * radialMod;
+
+ const crystalX = x + Math.cos(waveAngle) * modDistance;
+ const crystalY = y + Math.sin(waveAngle) * modDistance;
+
+ const crystalFactor = factor * timeEnhancedDistortion * 0.7;
+ point.x += (crystalX - point.x) * crystalFactor;
+ point.y += (crystalY - point.y) * crystalFactor;
+ break;
+ }
+
+ case this.modes.EDGE: {
+ // 边缘模式
+ const dx = point.x - x;
+ const dy = point.y - y;
+ const edgeAngle = Math.atan2(dy, dx);
+ const edgeRadius = normalizedDistance;
+
+ const baseEdgeDistortion = Math.max(distortion, 0.5);
+ const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
+ const timeEnhancedDistortion = baseEdgeDistortion * (1.0 + timeFactor * 0.4);
+
+ const edgeWave =
+ Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
+ Math.cos(edgeAngle * 6 + this.pressDuration * 0.002);
+ const perpAngle = edgeAngle + Math.PI / 2;
+
+ const edgeFactor = edgeWave * factor * timeEnhancedDistortion * 0.5;
+ const edgeOffsetX = Math.cos(perpAngle) * edgeFactor;
+ const edgeOffsetY = Math.sin(perpAngle) * edgeFactor;
+
+ point.x += edgeOffsetX;
+ point.y += edgeOffsetY;
+ break;
+ }
+
+ case this.modes.RECONSTRUCT: {
+ // 重建模式
+ const restoreFactor = factor * 0.2;
+ point.x += (originalPoint.x - point.x) * restoreFactor;
+ point.y += (originalPoint.y - point.y) * restoreFactor;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 获取受影响的网格点(范围优化)
+ */
+ _getAffectedPoints(centerX, centerY, radius) {
+ const { cols, rows, gridSize } = this.mesh;
+ const points = this.mesh.deformedPoints;
+ const originalPoints = this.mesh.originalPoints;
+ const affectedPoints = [];
+
+ // 计算影响范围的网格边界
+ const minGridX = Math.max(0, Math.floor((centerX - radius) / gridSize));
+ const maxGridX = Math.min(cols, Math.ceil((centerX + radius) / gridSize));
+ const minGridY = Math.max(0, Math.floor((centerY - radius) / gridSize));
+ const maxGridY = Math.min(rows, Math.ceil((centerY + radius) / gridSize));
+
+ // 只遍历影响范围内的网格点
+ for (let gridY = minGridY; gridY <= maxGridY; gridY++) {
+ for (let gridX = minGridX; gridX <= maxGridX; gridX++) {
+ const index = gridY * (cols + 1) + gridX;
+ if (index < points.length) {
+ const point = points[index];
+ const originalPoint = originalPoints[index];
+ const dx = point.x - centerX;
+ const dy = point.y - centerY;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ // 只包含在影响半径内的点
+ if (distance <= radius) {
+ affectedPoints.push({
+ index,
+ point,
+ originalPoint,
+ distance,
+ dx,
+ dy,
+ });
+ }
+ }
+ }
+ }
+
+ return affectedPoints;
+ }
+
+ _smoothMesh() {
+ const { rows, cols } = this.mesh;
+ const points = this.mesh.deformedPoints;
+ const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
+
+ for (let iteration = 0; iteration < this.config.smoothingIterations; iteration++) {
+ for (let y = 1; y < rows; y++) {
+ for (let x = 1; x < cols; x++) {
+ const idx = y * (cols + 1) + x;
+ const left = points[y * (cols + 1) + (x - 1)];
+ const right = points[y * (cols + 1) + (x + 1)];
+ const top = points[(y - 1) * (cols + 1) + x];
+ const bottom = points[(y + 1) * (cols + 1) + x];
+
+ const centerX = (left.x + right.x + top.x + bottom.x) / 4;
+ const centerY = (left.y + right.y + top.y + bottom.y) / 4;
+
+ const relaxFactor = this.config.relaxFactor;
+ tempPoints[idx].x += (centerX - points[idx].x) * relaxFactor;
+ tempPoints[idx].y += (centerY - points[idx].y) * relaxFactor;
+ }
+ }
+
+ for (let i = 0; i < points.length; i++) {
+ points[i].x = tempPoints[i].x;
+ points[i].y = tempPoints[i].y;
+ }
+ }
+ }
+
+ /**
+ * 专门为旋转模式优化的网格平滑
+ */
+ _lightSmoothing() {
+ const { rows, cols } = this.mesh;
+ const points = this.mesh.deformedPoints;
+ const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
+
+ // 只进行一次轻微平滑
+ for (let y = 1; y < rows; y++) {
+ for (let x = 1; x < cols; x++) {
+ const idx = y * (cols + 1) + x;
+ const left = points[y * (cols + 1) + (x - 1)];
+ const right = points[y * (cols + 1) + (x + 1)];
+ const top = points[(y - 1) * (cols + 1) + x];
+ const bottom = points[(y + 1) * (cols + 1) + x];
+
+ const centerX = (left.x + right.x + top.x + bottom.x) / 4;
+ const centerY = (left.y + right.y + top.y + bottom.y) / 4;
+
+ // 使用更小的松弛因子
+ const lightRelaxFactor = this.config.relaxFactor * 0.3;
+ tempPoints[idx].x += (centerX - points[idx].x) * lightRelaxFactor;
+ tempPoints[idx].y += (centerY - points[idx].y) * lightRelaxFactor;
+ }
+ }
+
+ for (let i = 0; i < points.length; i++) {
+ points[i].x = tempPoints[i].x;
+ points[i].y = tempPoints[i].y;
+ }
+ }
+
+ /**
+ * 使用更优化的衰减函数
+ * @param {number} t 归一化距离 (0-1)
+ * @returns {number} 衰减因子 (0-1)
+ */
+ _optimizedFalloff(t) {
+ if (t >= 1.0) return 0;
+
+ // 对于旋转模式,使用专门的衰减函数
+ if (
+ this.currentMode === this.modes.CLOCKWISE ||
+ this.currentMode === this.modes.COUNTERCLOCKWISE
+ ) {
+ return this._stableRotationFalloff(t); // 修复函数名
+ }
+
+ // 其他模式使用原来的衰减函数
+ const smoothT = 1 - t;
+
+ // 多项式衰减 + 指数衰减的组合
+ const polynomial = smoothT * smoothT * (3 - 2 * smoothT); // 平滑阶梯函数
+ const exponential = Math.exp(-t * 2); // 指数衰减
+
+ // 组合两种衰减方式,在不同区域有不同特性
+ const weight = Math.cos(t * Math.PI * 0.5); // 权重函数
+
+ return polynomial * weight + exponential * (1 - weight);
+ }
+
+ _applyMeshToImage() {
+ if (!this.mesh || !this.originalImageData) {
+ return this.currentImageData;
+ }
+
+ const width = this.originalImageData.width;
+ const height = this.originalImageData.height;
+ const result = new ImageData(width, height);
+ const srcData = this.originalImageData.data;
+ const dstData = result.data;
+
+ // 移除步长采样,始终使用1:1采样
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const srcPos = this._mapPointBack(x, y);
+ const dstIdx = (y * width + x) * 4;
+
+ if (srcPos.x >= 0 && srcPos.x < width && srcPos.y >= 0 && srcPos.y < height) {
+ // 使用双三次插值获取颜色
+ const color = this._bicubicInterpolate(srcData, width, height, srcPos.x, srcPos.y);
+ dstData[dstIdx] = color[0];
+ dstData[dstIdx + 1] = color[1];
+ dstData[dstIdx + 2] = color[2];
+ dstData[dstIdx + 3] = color[3]; // 确保Alpha通道值被正确设置
+ } else {
+ // 对于边界外的点,使用最近的有效像素或保持原Alpha通道
+ // 这里我们确保Alpha通道不为0,防止出现透明区域
+ const nearestX = Math.max(0, Math.min(width - 1, Math.round(srcPos.x)));
+ const nearestY = Math.max(0, Math.min(height - 1, Math.round(srcPos.y)));
+ const nearestIdx = (nearestY * width + nearestX) * 4;
+
+ // 复制最近像素的颜色,但保持Alpha通道为不透明
+ dstData[dstIdx] = srcData[nearestIdx];
+ dstData[dstIdx + 1] = srcData[nearestIdx + 1];
+ dstData[dstIdx + 2] = srcData[nearestIdx + 2];
+ dstData[dstIdx + 3] = 255; // 强制设置为完全不透明
+ }
+ }
+ }
+
+ this.currentImageData = result;
+ // 添加锐化处理
+ if (this.config.sharpenAmount > 0) {
+ this.currentImageData = this._sharpenImage(this.currentImageData, this.config.sharpenAmount);
+ }
+ return result;
+ }
+
+ // 添加异步处理方法用于大图像
+ async applyDeformationAsync(x, y) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const result = this.applyDeformation(x, y);
+ resolve(result);
+ }, 0);
+ });
+ }
+
+ // 批量处理方法
+ applyDeformationBatch(positions) {
+ if (!this.initialized || !this.mesh || positions.length === 0) {
+ return this.currentImageData;
+ }
+
+ // 对于批量处理,模拟连续的拖拽操作
+ if (positions.length > 0) {
+ // 使用第一个位置作为初始点
+ this.startDeformation(positions[0].x, positions[0].y);
+
+ // 逐个应用每个位置的变形
+ positions.forEach((pos, index) => {
+ if (index === 0) return; // 跳过第一个,因为已经作为初始点
+
+ // 更新当前位置并应用变形
+ this.currentMouseX = pos.x;
+ this.currentMouseY = pos.y;
+
+ // 重新计算拖拽参数
+ const deltaX = this.currentMouseX - this.initialMouseX;
+ const deltaY = this.currentMouseY - this.initialMouseY;
+ this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ this.dragAngle = Math.atan2(deltaY, deltaX);
+
+ const { size, pressure, distortion, power } = this.params;
+ const mode = this.currentMode;
+ const radius = size * 0.8;
+
+ // 根据推拉模式和拖拽距离动态调整强度
+ let strength;
+ if (mode === this.modes.PUSH) {
+ const baseStrength = (pressure * power * this.config.maxStrength) / 100;
+ const distanceFactor = Math.min(this.dragDistance / radius, 2.0);
+ strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度
+ } else {
+ strength = (pressure * power * this.config.maxStrength) / 100;
+ }
+
+ this._applyDeformation(pos.x, pos.y, radius, strength, mode, distortion);
+ });
+
+ // 结束拖拽操作
+ this.endDeformation();
+ }
+
+ if (this.config.smoothingIterations > 0) {
+ this._smoothMesh();
+ }
+
+ return this._applyMeshToImage();
+ }
+
+ /**
+ * 改进的网格映射算法 - 防止空白区域
+ */
+ _mapPointBack(x, y) {
+ const { cols, rows, gridSize } = this.mesh;
+ const gridX = x / gridSize;
+ const gridY = y / gridSize;
+
+ const x1 = Math.floor(gridX);
+ const y1 = Math.floor(gridY);
+ const x2 = Math.min(x1 + 1, cols);
+ const y2 = Math.min(y1 + 1, rows);
+
+ const fx = gridX - x1;
+ const fy = gridY - y1;
+
+ // 获取四个网格点的变形和原始坐标
+ const deformed = [
+ this.mesh.deformedPoints[y1 * (cols + 1) + x1],
+ this.mesh.deformedPoints[y1 * (cols + 1) + x2],
+ this.mesh.deformedPoints[y2 * (cols + 1) + x1],
+ this.mesh.deformedPoints[y2 * (cols + 1) + x2],
+ ];
+
+ const original = [
+ this.mesh.originalPoints[y1 * (cols + 1) + x1],
+ this.mesh.originalPoints[y1 * (cols + 1) + x2],
+ this.mesh.originalPoints[y2 * (cols + 1) + x1],
+ this.mesh.originalPoints[y2 * (cols + 1) + x2],
+ ];
+
+ // 双线性插值计算变形后的位置
+ const deformedX =
+ (1 - fx) * (1 - fy) * deformed[0].x +
+ fx * (1 - fy) * deformed[1].x +
+ (1 - fx) * fy * deformed[2].x +
+ fx * fy * deformed[3].x;
+ const deformedY =
+ (1 - fx) * (1 - fy) * deformed[0].y +
+ fx * (1 - fy) * deformed[1].y +
+ (1 - fx) * fy * deformed[2].y +
+ fx * fy * deformed[3].y;
+
+ // 计算原始网格位置
+ const originalX = x1 * gridSize + fx * gridSize;
+ const originalY = y1 * gridSize + fy * gridSize;
+
+ // 检查是否接近边缘,如果是则减少偏移量
+ const isNearEdge = this._isNearEdge(originalX, originalY);
+ const edgeProtectionFactor = isNearEdge ? 0.2 : 1.0; // 边缘区域减少变形量
+
+ // 计算偏移量并应用反向映射
+ const offsetX = (deformedX - originalX) * edgeProtectionFactor;
+ const offsetY = (deformedY - originalY) * edgeProtectionFactor;
+
+ return {
+ x: Math.max(0, Math.min(this.mesh.width - 1, x - offsetX)),
+ y: Math.max(0, Math.min(this.mesh.height - 1, y - offsetY)),
+ };
+ }
+ // 边缘检测辅助方法
+ _isNearEdge(x, y, threshold = 10) {
+ if (!this.originalImageData) return false;
+
+ const data = this.originalImageData.data;
+ const width = this.originalImageData.width;
+ const height = this.originalImageData.height;
+
+ // 检查像素是否在边缘
+ if (x <= 0 || x >= width - 1 || y <= 0 || y >= height - 1) return true;
+
+ // 简单的Sobel边缘检测
+ const getPixelBrightness = (px, py) => {
+ const idx = (py * width + px) * 4;
+ return (data[idx] + data[idx + 1] + data[idx + 2]) / 3;
+ };
+
+ const kernelX = [
+ [-1, 0, 1],
+ [-2, 0, 2],
+ [-1, 0, 1]
+ ];
+
+ const kernelY = [
+ [-1, -2, -1],
+ [0, 0, 0],
+ [1, 2, 1]
+ ];
+
+ let gradientX = 0;
+ let gradientY = 0;
+
+ for (let ky = -1; ky <= 1; ky++) {
+ for (let kx = -1; kx <= 1; kx++) {
+ const px = Math.min(Math.max(0, x + kx), width - 1);
+ const py = Math.min(Math.max(0, y + ky), height - 1);
+ const brightness = getPixelBrightness(px, py);
+ gradientX += brightness * kernelX[ky + 1][kx + 1];
+ gradientY += brightness * kernelY[ky + 1][kx + 1];
+ }
+ }
+
+ const gradientMagnitude = Math.sqrt(gradientX * gradientX + gradientY * gradientY);
+ return gradientMagnitude > threshold;
+ }
+ // 图像锐化方法
+ _sharpenImage(imageData, amount = 0.5) {
+ if (!imageData) return imageData;
+
+ const data = new Uint8ClampedArray(imageData.data);
+ const width = imageData.width;
+ const height = imageData.height;
+ const result = new ImageData(width, height);
+ const dstData = result.data;
+
+ // 锐化核 - 中心为5,周围为-1
+ const kernel = [
+ [0, -1, 0],
+ [-1, 5, -1],
+ [0, -1, 0]
+ ];
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ // 边缘像素不处理
+ if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
+ const idx = (y * width + x) * 4;
+ for (let c = 0; c < 4; c++) {
+ dstData[idx + c] = data[idx + c];
+ }
+ continue;
+ }
+
+ const sharpened = [0, 0, 0, 0];
+
+ // 应用锐化核
+ for (let ky = -1; ky <= 1; ky++) {
+ for (let kx = -1; kx <= 1; kx++) {
+ const px = x + kx;
+ const py = y + ky;
+ const idx = (py * width + px) * 4;
+ const weight = kernel[ky + 1][kx + 1];
+
+ for (let c = 0; c < 3; c++) { // 只锐化RGB通道
+ sharpened[c] += data[idx + c] * weight;
+ }
+ sharpened[3] = data[idx + 3]; // 保持Alpha通道不变
+ }
+ }
+
+ // 应用锐化强度并裁剪值范围
+ const idx = (y * width + x) * 4;
+ for (let c = 0; c < 3; c++) {
+ const original = data[idx + c];
+ const diff = sharpened[c] - original;
+ dstData[idx + c] = Math.max(0, Math.min(255, original + diff * amount));
+ }
+ dstData[idx + 3] = sharpened[3];
+ }
+ }
+
+ return result;
+ }
+ _bilinearInterpolate(data, width, height, x, y) {
+ const x1 = Math.floor(x);
+ const y1 = Math.floor(y);
+ const x2 = Math.min(x1 + 1, width - 1);
+ const y2 = Math.min(y1 + 1, height - 1);
+
+ const fx = x - x1;
+ const fy = y - y1;
+
+ const getPixel = (px, py) => {
+ const idx = (py * width + px) * 4;
+ return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
+ };
+
+ const p1 = getPixel(x1, y1);
+ const p2 = getPixel(x2, y1);
+ const p3 = getPixel(x1, y2);
+ const p4 = getPixel(x2, y2);
+
+ return [
+ Math.round(
+ (1 - fx) * (1 - fy) * p1[0] +
+ fx * (1 - fy) * p2[0] +
+ (1 - fx) * fy * p3[0] +
+ fx * fy * p4[0]
+ ),
+ Math.round(
+ (1 - fx) * (1 - fy) * p1[1] +
+ fx * (1 - fy) * p2[1] +
+ (1 - fx) * fy * p3[1] +
+ fx * fy * p4[1]
+ ),
+ Math.round(
+ (1 - fx) * (1 - fy) * p1[2] +
+ fx * (1 - fy) * p2[2] +
+ (1 - fx) * fy * p3[2] +
+ fx * fy * p4[2]
+ ),
+ Math.round(
+ (1 - fx) * (1 - fy) * p1[3] +
+ fx * (1 - fy) * p2[3] +
+ (1 - fx) * fy * p3[3] +
+ fx * fy * p4[3]
+ ),
+ ];
+ }
+
+ reset() {
+ if (!this.mesh || !this.originalImageData) return false;
+
+ for (let i = 0; i < this.mesh.deformedPoints.length; i++) {
+ this.mesh.deformedPoints[i].x = this.mesh.originalPoints[i].x;
+ this.mesh.deformedPoints[i].y = this.mesh.originalPoints[i].y;
+ }
+
+ this.currentImageData = new ImageData(
+ new Uint8ClampedArray(this.originalImageData.data),
+ this.originalImageData.width,
+ this.originalImageData.height
+ );
+
+ // 重置拖拽状态
+ this.initialMouseX = 0;
+ this.initialMouseY = 0;
+ this.currentMouseX = 0;
+ this.currentMouseY = 0;
+ this.lastMouseX = 0;
+ this.lastMouseY = 0;
+ this.mouseMovementX = 0;
+ this.mouseMovementY = 0;
+ this.isFirstApply = true;
+ this.isDragging = false;
+ this.dragDistance = 0;
+ this.dragAngle = 0;
+
+ // 新增:重置持续按压状态
+ this.isHolding = false;
+ this.pressStartTime = 0;
+ this.pressDuration = 0;
+ this.accumulatedRotation = 0;
+ this.accumulatedScale = 0;
+ this.lastApplyTime = 0;
+
+ this.deformHistory = [];
+ return true;
+ }
+
+ // 新增:获取持续按压状态信息
+ getHoldingInfo() {
+ return {
+ isHolding: this.isHolding,
+ pressDuration: this.pressDuration,
+ accumulatedRotation: this.accumulatedRotation,
+ accumulatedScale: this.accumulatedScale,
+ pressStartTime: this.pressStartTime,
+ };
+ }
+
+ /**
+ * 应用液化变形 - 主要的公共接口方法
+ * @param {Number} x X坐标
+ * @param {Number} y Y坐标
+ * @returns {ImageData} 变形后的图像数据
+ */
+ applyDeformation(x, y) {
+ if (!this.initialized || !this.mesh || !this.originalImageData) {
+ console.warn("液化管理器未初始化或缺少必要数据");
+ return this.currentImageData;
+ }
+
+ // 更新鼠标位置
+ this.currentMouseX = x;
+ this.currentMouseY = y;
+
+ // 计算拖拽参数
+ const deltaX = this.currentMouseX - this.initialMouseX;
+ const deltaY = this.currentMouseY - this.initialMouseY;
+ this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ this.dragAngle = Math.atan2(deltaY, deltaX);
+
+ // 获取当前参数
+ const { size, pressure, power } = this.params;
+ const mode = this.currentMode;
+ const radius = size;
+ const strength = pressure * power;
+
+ // 根据模式选择算法
+ const pixelModes = [
+ this.modes.CLOCKWISE,
+ this.modes.COUNTERCLOCKWISE,
+ this.modes.PINCH,
+ this.modes.EXPAND,
+ this.modes.PUSH,
+ ];
+
+ if (pixelModes.includes(mode)) {
+ // 使用增强的像素算法
+ switch (mode) {
+ case this.modes.CLOCKWISE:
+ this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
+ break;
+ case this.modes.COUNTERCLOCKWISE:
+ this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
+ break;
+ case this.modes.PINCH:
+ this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
+ break;
+ case this.modes.EXPAND:
+ this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
+ break;
+ case this.modes.PUSH:
+ this._applyEnhancedPushDeformation(x, y, radius, strength);
+ break;
+ }
+
+ // 更新最后应用时间
+ this.lastApplyTime = Date.now();
+ this.isFirstApply = false;
+
+ return this.currentImageData;
+ } else {
+ // 使用原有的网格算法处理其他模式
+ if (!this.mesh) {
+ console.warn("网格未初始化");
+ return this.currentImageData;
+ }
+
+ const finalStrength = (strength * this.config.maxStrength) / 100;
+
+ // 应用变形
+ this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
+
+ // 有条件地应用平滑处理,仅在特定模式下应用
+ const smoothingModes = [this.modes.CRYSTAL, this.modes.EDGE];
+ if (smoothingModes.includes(mode) && this.config.smoothingIterations > 0) {
+ this._smoothMesh();
+ }
+
+ // 更新图像数据
+ const result = this._applyMeshToImage();
+
+ // 更新最后应用时间
+ this.lastApplyTime = Date.now();
+ this.isFirstApply = false;
+
+ return result;
+ }
+ }
+
+ getCurrentImageData() {
+ return this.currentImageData;
+ }
+
+ destroy() {
+ // 停止持续效果定时器
+ this.stopContinuousEffect();
+
+ this.originalImageData = null;
+ this.currentImageData = null;
+ this.mesh = null;
+ this.deformHistory = [];
+ this.initialized = false;
+
+ // 清理拖拽状态
+ this.initialMouseX = 0;
+ this.initialMouseY = 0;
+ this.currentMouseX = 0;
+ this.currentMouseY = 0;
+ this.lastMouseX = 0;
+ this.lastMouseY = 0;
+ this.mouseMovementX = 0;
+ this.mouseMovementY = 0;
+ this.isFirstApply = true;
+ this.isDragging = false;
+ this.dragDistance = 0;
+ this.dragAngle = 0;
+
+ // 新增:清理持续按压状态
+ this.isHolding = false;
+ this.pressStartTime = 0;
+ this.pressDuration = 0;
+ this.accumulatedRotation = 0;
+ this.accumulatedScale = 0;
+ this.lastApplyTime = 0;
+ }
+
+ /**
+ * 释放资源 - 别名方法,与其他管理器保持一致
+ */
+ dispose() {
+ this.destroy();
+ }
}
diff --git a/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyManager.js b/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyManager.js
index 5475501b..ce033261 100644
--- a/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyManager.js
+++ b/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyManager.js
@@ -31,10 +31,10 @@ export class LiquifyManager {
// 创建增强版液化管理器实例
this.enhancedManager = new EnhancedLiquifyManager({
// 配置选项
- gridSize: options.gridSize || 15,
- maxStrength: options.maxStrength || 100,
- smoothingIterations: options.smoothingIterations || 2,
- relaxFactor: options.relaxFactor || 0.25,
+ gridSize: options.gridSize || 8,
+ maxStrength: options.maxStrength || 200,
+ smoothingIterations: options.smoothingIterations || 1,
+ relaxFactor: options.relaxFactor || 0.05,
meshResolution: options.meshResolution || 64,
// 根据环境选择合适的渲染模式
forceCPU: true, // 默认不强制使用CPU
diff --git a/src/component/Canvas/ExistsImageList/ToolButton.vue b/src/component/Canvas/ExistsImageList/ToolButton.vue
index df816c72..5c51ff9a 100644
--- a/src/component/Canvas/ExistsImageList/ToolButton.vue
+++ b/src/component/Canvas/ExistsImageList/ToolButton.vue
@@ -21,6 +21,10 @@ const props = defineProps({
type: Boolean,
default: true,
},
+ tipBody: {
+ type: Boolean,
+ default: false,
+ },
});
const emit = defineEmits(["click"]);
@@ -47,10 +51,45 @@ const handleClick = () => {
if (isDisabled.value) return;
emit("click", props.tool);
};
+
+const tipId = "tooltip-" + Math.random().toString(36).substring(2);
+const el = ref(null);
+// 鼠标移入获取位置信息
+const handleMouseEnter = (e) => {
+ const tooltip = el.value;
+ const rect = tooltip.getBoundingClientRect();
+ const left = rect.left + rect.width;
+ const top = rect.top + rect.height / 2;
+ const tip = document.getElementById(tipId);
+ tip.style.position = 'fixed';
+ tip.style.left = `${left}px`;
+ tip.style.top = `${top}px`;
+ tip.style.display = 'block';
+}
+// 鼠标移出隐藏提示
+const handleMouseLeave = () => {
+ const tip = document.getElementById(tipId);
+ tip.style.display = 'none';
+}
+onMounted(() => {
+ if(props.tipBody){
+ el.value.addEventListener('mouseenter', handleMouseEnter);
+ el.value.addEventListener('mouseleave', handleMouseLeave);
+ }
+})
+
+onUnmounted(() => {
+ if(props.tipBody && el.value){
+ el.value.removeEventListener('mouseenter', handleMouseEnter);
+ el.value.removeEventListener('mouseleave', handleMouseLeave);
+ }
+})
+
-
{{ t(tool.title) }}
+
+ {{ t(tool.title) }}
+
+
{{ t(tool.title) }}
@@ -82,6 +124,7 @@ const handleClick = () => {
font-size: 1.6rem;
color: #333;
transition: all 0.2s ease;
+ flex-shrink: 0;
}
.tool-btn:hover {
@@ -115,7 +158,7 @@ const handleClick = () => {
margin-left: .8rem;
white-space: nowrap;
font-size: 1.2rem;
- z-index: 10;
+ z-index: 9999;
}
.tool-tooltip:before {
diff --git a/src/component/Canvas/ExistsImageList/index.vue b/src/component/Canvas/ExistsImageList/index.vue
index 5730cbfe..d1aaab6c 100644
--- a/src/component/Canvas/ExistsImageList/index.vue
+++ b/src/component/Canvas/ExistsImageList/index.vue
@@ -4,7 +4,7 @@
@@ -56,6 +56,14 @@
{{ item.name || "未命名" }}
+
+
@@ -70,6 +78,7 @@
{{ $t("Canvas.general") }} {{ filteredImages.length }} {{ $t("Canvas.PicturesInTotal") }}
+ {{ $t("Canvas.confirm") }}
@@ -103,6 +112,7 @@ const emits = defineEmits(["select"]);
// 响应式数据
const showPanel = ref(false);
const selectedCategory = ref(t("Canvas.all"));
+const selectList = ref([])
// 计算属性:获取所有分类
const categories = computed(() => {
@@ -152,8 +162,12 @@ const filteredImages = computed(() => {
// 处理图片点击
const handleImageClick = (item) => {
- emits("select", item);
- showPanel.value = false;
+ // 已选中,取消选中
+ if(selectList.value.includes(item.url)){
+ selectList.value = selectList.value.filter(url => url !== item.url)
+ }else{
+ selectList.value.push(item.url)
+ }
};
// 处理图片加载错误
@@ -162,6 +176,13 @@ const handleImageError = (event) => {
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuWbvueJh+WKoOi9veWksei0pe+8jOivt+ajgOafpeWbvueJh+i3r+W+hDwvdGV4dD48L3N2Zz4=";
event.target.alt = "图片加载失败";
};
+
+//提交选中的T图片
+const confirm = ()=>{
+ emits("select", selectList.value);
+ selectList.value = []
+ showPanel.value = false;
+}
\ No newline at end of file
diff --git a/src/component/Detail/detailLeft/models.vue b/src/component/Detail/detailLeft/models.vue
index 403240bc..c239fa94 100644
--- a/src/component/Detail/detailLeft/models.vue
+++ b/src/component/Detail/detailLeft/models.vue
@@ -100,11 +100,12 @@ export default defineComponent({
padding: 1rem 0;
text-align: center;
border-radius: .5rem;
- // border: 1px dashed #202020;
- border: 1px dashed transparent;
+ border: 1px solid #000;
+ // border: 1px dashed transparent;
+ border-radius: 1.5rem;
position: relative;
background: linear-gradient(#fff, #fff) padding-box,repeating-linear-gradient(-45deg,#fff 0,#fff 0.3em, #000 0,#000 0.6em);
- margin-bottom: 3rem;
+ margin-bottom: 1.4rem;
> img{
width: 100%;
height: 100%;
diff --git a/src/component/Detail/detailLeft/module/currentList.vue b/src/component/Detail/detailLeft/module/currentList.vue
index 36094527..95143250 100644
--- a/src/component/Detail/detailLeft/module/currentList.vue
+++ b/src/component/Detail/detailLeft/module/currentList.vue
@@ -107,9 +107,9 @@ export default defineComponent({
align-content: flex-start;
&::-webkit-scrollbar{display: none;}
> .content_img_item{
+ width: calc((50% - 1rem));
> .content_img_item_block{
- width: calc((34rem - 2rem) / 2);
- height: calc((34rem - 2rem) / 2);
+ aspect-ratio: 1/1;
position: relative;
margin-bottom: 2rem;
@@ -121,16 +121,6 @@ export default defineComponent({
}
}
}
- > .material_content_list_loding{
- width: 100%;
- height: calc((34rem - 2rem) / 2);
- }
- > .upload_item{
- width: calc((34rem - 2rem) / 2);
- height: calc((34rem - 2rem) / 2);
- align-items: center;
- justify-content: center;
- }
}
}
diff --git a/src/component/Detail/detailLeft/module/libraryList.vue b/src/component/Detail/detailLeft/module/libraryList.vue
index 5b40f497..bd3459c0 100644
--- a/src/component/Detail/detailLeft/module/libraryList.vue
+++ b/src/component/Detail/detailLeft/module/libraryList.vue
@@ -6,7 +6,7 @@
v-model:value="designType"
:options="(designTypeList)"
@change="handleChange"
- style="width:100%"
+ style="width:100%; font-size: 1.4rem;"
size="large"
:fieldNames="{ label: 'name', value: 'value' }"
>
@@ -23,7 +23,7 @@
v-model:value="mannequinData.system"
:options="systemList"
@change="handleChange"
- style="width:100%"
+ style="width:100%; font-size: 1.4rem;"
size="large"
:fieldNames="{ label: 'name', value: 'value' }"
>
@@ -41,7 +41,7 @@
v-model:value="mannequinData.style"
:options="mannequinStyle"
@change="handleChange"
- style="width:100%"
+ style="width:100%; font-size: 1.4rem;"
size="large"
:fieldNames="{ label: 'name', value: 'value' }"
>
@@ -58,7 +58,7 @@
v-model:value="mannequinData.ageGroup"
:options="ageGroupList"
@change="handleChange"
- style="width:100%"
+ style="width:100%; font-size: 1.4rem;"
size="large"
:fieldNames="{ label: 'name', value: 'value' }"
>
@@ -75,7 +75,7 @@
v-model:value="mannequinData.sex"
:options="sexList"
@change="handleChange"
- style="width:100%"
+ style="width:100%; font-size: 1.4rem;"
size="large"
:fieldNames="{ label: 'name', value: 'value' }"
>
@@ -333,6 +333,9 @@ export default defineComponent({
justify-content: center;
cursor: pointer;
}
+ > .search_input{
+ font-size: 1.4rem;
+ }
}
> .generalModel_state_item:last-child{
// margin-top: 2rem;
@@ -347,11 +350,11 @@ export default defineComponent({
margin-top: 1rem;
justify-content: space-between;
align-content: flex-start;
- &::-webkit-scrollbar{display: none;}
+ // &::-webkit-scrollbar{display: none;}
> .content_img_item{
+ width: calc((50% - 1rem));
> .content_img_item_block{
- width: calc((34rem - 2rem) / 2);
- height: calc((34rem - 2rem) / 2);
+ aspect-ratio: 1/1;
position: relative;
margin-bottom: 2rem;
cursor: pointer;
@@ -364,7 +367,7 @@ export default defineComponent({
}
> .material_content_list_loding{
width: 100%;
- height: calc((34rem - 2rem) / 2);
+ aspect-ratio: 1/1;
overflow: hidden;
> img{
width: 100%;
diff --git a/src/component/Detail/detailLeft/module/selectList.vue b/src/component/Detail/detailLeft/module/selectList.vue
index 0bf32560..489df33f 100644
--- a/src/component/Detail/detailLeft/module/selectList.vue
+++ b/src/component/Detail/detailLeft/module/selectList.vue
@@ -1,6 +1,6 @@
-