diff --git a/.env.dev_build b/.env.dev_build index 9de2530a..2affe0c8 100644 --- a/.env.dev_build +++ b/.env.dev_build @@ -5,4 +5,3 @@ VITE_USER_NODE_ENV = 'development' VITE_APP_BASE_URL = 'https://develop.api.aida.com.hk' # VITE_APP_BASE_URL = 'http://localhost:22170' - diff --git a/public/css/fonts/InstrumentSans-Bold.ttf b/public/css/fonts/InstrumentSans-Bold.ttf new file mode 100644 index 00000000..f602dcef Binary files /dev/null and b/public/css/fonts/InstrumentSans-Bold.ttf differ diff --git a/public/css/fonts/InstrumentSans-Regular.ttf b/public/css/fonts/InstrumentSans-Regular.ttf new file mode 100644 index 00000000..14c6113c Binary files /dev/null and b/public/css/fonts/InstrumentSans-Regular.ttf differ diff --git a/public/css/fonts/fontFamily.css b/public/css/fonts/fontFamily.css index a2f7501e..e4da7066 100644 --- a/public/css/fonts/fontFamily.css +++ b/public/css/fonts/fontFamily.css @@ -1,31 +1,41 @@ /* 字体定义 */ @font-face { font-family: 'Arial'; - src: url('./fonts/ARIAL.ttf') format('ttf'); + src: url('./fonts/ARIAL.ttf') format('truetype'); } @font-face { font-family: 'ArialBold'; - src: url('./fonts/ARIALBD.ttf') format('ttf'); + src: url('./fonts/ARIALBD.ttf') format('truetype'); } @font-face { font-family: 'ArialMedium'; - src: url('./fonts/ArialMdm.ttf') format('ttf'); + src: url('./fonts/ArialMdm.ttf') format('truetype'); } @font-face { font-family: 'Poppins'; - src: url('./fonts/Poppins-Regular.ttf') format('ttf'); + src: url('./fonts/Poppins-Regular.ttf') format('truetype'); font-weight: normal; } @font-face { font-family: 'PoppinsMedium'; - src: url('./fonts/Poppins-Medium.ttf') format('ttf'); + src: url('./fonts/Poppins-Medium.ttf') format('truetype'); } @font-face { font-family: 'PoppinsBold'; - src: url('./fonts/Poppins-SemiBold.ttf') format('ttf'); + src: url('./fonts/Poppins-SemiBold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Instrument'; + src: url('./InstrumentSans-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'InstrumentBold'; + src: url('./InstrumentSans-Bold.ttf') format('truetype'); } diff --git a/public/image/events/award-poster.gif b/public/image/events/award-poster.gif new file mode 100644 index 00000000..4180343d Binary files /dev/null and b/public/image/events/award-poster.gif differ diff --git a/src/assets/icons/CFile.svg b/src/assets/icons/CFile.svg new file mode 100644 index 00000000..e824de6e --- /dev/null +++ b/src/assets/icons/CFile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/award/apply_bg.png b/src/assets/images/award/apply_bg.png index eb170088..f2f1a835 100644 Binary files a/src/assets/images/award/apply_bg.png and b/src/assets/images/award/apply_bg.png differ diff --git a/src/assets/images/award/banner.mp4 b/src/assets/images/award/banner.mp4 new file mode 100644 index 00000000..c1d7d04f Binary files /dev/null and b/src/assets/images/award/banner.mp4 differ diff --git a/src/assets/images/award/bloom_bg.png b/src/assets/images/award/bloom_bg.png index f40622f2..111b119d 100644 Binary files a/src/assets/images/award/bloom_bg.png and b/src/assets/images/award/bloom_bg.png differ diff --git a/src/assets/images/award/point.png b/src/assets/images/award/point.png index ade4dab4..b2120b47 100644 Binary files a/src/assets/images/award/point.png and b/src/assets/images/award/point.png differ diff --git a/src/assets/images/award/upload_video_icon.png b/src/assets/images/award/upload_video_icon.png new file mode 100644 index 00000000..dbc1ed68 Binary files /dev/null and b/src/assets/images/award/upload_video_icon.png differ diff --git a/src/assets/json/events.json b/src/assets/json/events.json index f7447995..647059e9 100644 --- a/src/assets/json/events.json +++ b/src/assets/json/events.json @@ -1,93 +1,129 @@ { - "eventsList": [ + "eventsList": [ + { + "id": 1, + "title": "Just post your design work, you could have the chance to come to Hong Kong and interact with industry leaders face-to-face!", + "imgUrl": "/image/events/workshop-En.jpg" + }, { - "id": 1, - "title":"Just post your design work, you could have the chance to come to Hong Kong and interact with industry leaders face-to-face!", - "imgUrl": "/image/events/workshop-En.jpg" - },{ "id": 2, - "title":"AiDA X SFT AI Fashion Award 2024", + "title": "AiDA X SFT AI Fashion Award 2024", "imgUrl": "/image/events/Fashion-Award-2024.png" + }, + { + "id": 3, + "title": "AiDA Global Design Awards 2026", + "imgUrl": "/image/events/award-poster.gif" } - ], - "eventsItem":[ - { - "id":1, - "title":"Just post your design work, you could have the chance to come to Hong Kong and interact with industry leaders face-to-face!", - "imgUrl": "/image/events/workshop-En.jpg", - "textList":[ - { - "paragraph":[ - { - "text":"🎨AiDA Workshop!" - } - ] - },{ - "paragraph":[ - { - "text":"The process is simple: use AiDA to post your design work on the 'Gallery', and the one with the most likes(at least 20 likes) will be invited to the AiDA Workshop offline event in Hong Kong on November 14th, to exchange ideas with the Royal College of Art (RCA), Jae Lim, co-founder of the renowned fashion brand BESFXXK, and outstanding designers! " - } - ] - },{ - "paragraph":[ - { - "text":"⚠️ATTENTION❗❗" - } - ] - },{ - "paragraph":[ - { - "text":"1. Add the tag in the work description #AiDAworkshop_2024" - },{ - "text":"2. One winner only" - } - ] - },{ - "paragraph":[ - { - "text":"🤩Code-Create will provide (Terms and conditions apply):" - } - ] - },{ - "paragraph":[ - { - "text":"✅Round-trip transportation fee (only within China)" - } - ] - },{ - "paragraph":[ - { - "text":"✅One night accommodation fee" - } - ] - },{ - "paragraph":[ - { - "text":"⌛️Deadline: October 31, 2024" - } - ] - } - ] - }, - { - "id":2, - "title":"AiDA X SFT AI Fashion Award 2024", - "imgUrl": "/image/events/Fashion-Award-2024.png", - "textList":[ - { - "paragraph":[ - { - "text":"With the aim of inspiring students to innovate in fashion design using AI, Code-Create and The Hong Kong Polytechnic University School of Fashion and Textiles (SFT) have jointly launched the 'AiDA X SFT AI Fashion Award 2024'. This competition provides students with valuable practical AiDA experience, laying the foundation for the future fashion design industry and positioning them as pioneers in AI fashion." - } - ] - },{ - "paragraph":[ - { - "text":"The competition is open to all SFT students, with the winners having the chance to win cash prizes (up to 20,000 HKD), internship opportunity at BESFXXK (will work with the renowned designer, Mr Jae Hyuk Lim, for the BESFXXK collection, that will be featured at NY Fashion Week and Paris Fashion Week) and more surprises! Scan the QR code to learn more." - } - ] - } - ] + ], + "eventsItem": [ + { + "id": 3, + "title": "AiDA Global Design Awards 2026", + "imgUrl": "/image/events/award-poster.gif", + "textList": [ + { + "paragraph": [ + { + "text": "Scan the QR code for more information and to join the competition! The AiDA Global Design Award 2026 is an international design competition hosted by Code‑Create, a globally leading AI fashion solutions provider, celebrating the future of creativity powered by artificial intelligence. Open to designers from Hong Kong, China, Singapore, South Korea, and beyond, the competition brings together global talent, empowering AI as a creative partner—pushing fashion beyond traditional boundaries and unlocking new possibilities where technology amplifies human imagination." + } + ] + }, + { + "paragraph": [ + { + "text": "Participants have the opportunity to compete for cash prizes totaling up to US$9,000, gain global media exposure showcased by top international platforms, and connect with designers and industry leaders worldwide. Finalists will also attend an exclusive award ceremony in Hong Kong, with travel support provided, allowing them to showcase their talent, network with professionals, and celebrate their achievements on an international stage." + } + ] + } + ] + }, + { + "id": 1, + "title": "Just post your design work, you could have the chance to come to Hong Kong and interact with industry leaders face-to-face!", + "imgUrl": "/image/events/workshop-En.jpg", + "textList": [ + { + "paragraph": [ + { + "text": "🎨AiDA Workshop!" + } + ] + }, + { + "paragraph": [ + { + "text": "The process is simple: use AiDA to post your design work on the 'Gallery', and the one with the most likes(at least 20 likes) will be invited to the AiDA Workshop offline event in Hong Kong on November 14th, to exchange ideas with the Royal College of Art (RCA), Jae Lim, co-founder of the renowned fashion brand BESFXXK, and outstanding designers! " + } + ] + }, + { + "paragraph": [ + { + "text": "⚠️ATTENTION❗❗" + } + ] + }, + { + "paragraph": [ + { + "text": "1. Add the tag in the work description #AiDAworkshop_2024" + }, + { + "text": "2. One winner only" + } + ] + }, + { + "paragraph": [ + { + "text": "🤩Code-Create will provide (Terms and conditions apply):" + } + ] + }, + { + "paragraph": [ + { + "text": "✅Round-trip transportation fee (only within China)" + } + ] + }, + { + "paragraph": [ + { + "text": "✅One night accommodation fee" + } + ] + }, + { + "paragraph": [ + { + "text": "⌛️Deadline: October 31, 2024" + } + ] + } + ] + }, + { + "id": 2, + "title": "AiDA X SFT AI Fashion Award 2024", + "imgUrl": "/image/events/Fashion-Award-2024.png", + "textList": [ + { + "paragraph": [ + { + "text": "With the aim of inspiring students to innovate in fashion design using AI, Code-Create and The Hong Kong Polytechnic University School of Fashion and Textiles (SFT) have jointly launched the 'AiDA X SFT AI Fashion Award 2024'. This competition provides students with valuable practical AiDA experience, laying the foundation for the future fashion design industry and positioning them as pioneers in AI fashion." + } + ] + }, + { + "paragraph": [ + { + "text": "The competition is open to all SFT students, with the winners having the chance to win cash prizes (up to 20,000 HKD), internship opportunity at BESFXXK (will work with the renowned designer, Mr Jae Hyuk Lim, for the BESFXXK collection, that will be featured at NY Fashion Week and Paris Fashion Week) and more surprises! Scan the QR code to learn more." + } + ] + } + ] } ] - } \ No newline at end of file +} diff --git a/src/assets/json/events_cn.json b/src/assets/json/events_cn.json index ce253a69..1a5ebc10 100644 --- a/src/assets/json/events_cn.json +++ b/src/assets/json/events_cn.json @@ -1,93 +1,129 @@ { - "eventsList": [ - { - "id": 1, - "title":"什么?只要发布设计作品就有机会来香港与大佬面对面交流?!", - "imgUrl": "/image/events/workshop-Cn.jpg" - },{ - "id": 2, - "title":"AiDA X SFT AI时尚设计比赛2024", - "imgUrl": "/image/events/Fashion-Award-2024.png" - } + "eventsList": [ + { + "id": 1, + "title": "什么?只要发布设计作品就有机会来香港与大佬面对面交流?!", + "imgUrl": "/image/events/workshop-Cn.jpg" + }, + { + "id": 2, + "title": "AiDA X SFT AI时尚设计比赛2024", + "imgUrl": "/image/events/Fashion-Award-2024.png" + }, + { + "id": 3, + "title": "AiDA全球设计奖 2026", + "imgUrl": "/image/events/award-poster.gif" + } ], - "eventsItem":[ - { - "id":1, - "title":"什么?只要发布设计作品就有机会来香港与大佬面对面交流?!", - "imgUrl": "/image/events/workshop-Cn.jpg", - "textList":[ - { - "paragraph":[ - { - "text":"🎨这是一趟艺术巅峰之旅!AiDA Workshop!" - } - ] - },{ - "paragraph":[ - { - "text":"参与过程很简单,利用AiDA 在 “Gallery广场 ”发布设计作品,最终获赞最高者(至少20个赞)将被邀请至11月14日 举办的AiDA Workshop香港线下活动,与英国皇家艺术学院(RCA)、韩国知名时尚品牌BESFXXK创始人JAE以及优秀设计师一同交流!(名额仅限1名)" - } - ] - },{ - "paragraph":[ - { - "text":"⚠️注意❗❗" - } - ] - },{ - "paragraph":[ - { - "text":"1. 作品描述添加tag: #AiDAworkshop_2024" - },{ - "text":"2. 一个冠军名额" - } - ] - },{ - "paragraph":[ - { - "text":"🤩Code-Create将提供(适用条款条规):" - } - ] - },{ - "paragraph":[ - { - "text":"✅往返机票/动车费用(仅限中国地区)" - } - ] - },{ - "paragraph":[ - { - "text":"✅一晚酒店住宿费用" - } - ] - },{ - "paragraph":[ - { - "text":"⌛️截止时间:2024.10.31" - } - ] - } - ] - }, - { - "id":2, - "title":"AiDA X SFT AI时尚设计比赛2024", - "imgUrl": "/image/events/Fashion-Award-2024.png", - "textList":[ - { - "paragraph":[ - { - "text":"秉承着激发学生使用AI进行时尚设计的创新能力的初衷,Code-Create和香港理工大学时装及纺织学院(SFT)共同举办了“AiDA X SFT AI时尚设计比赛2024”让学生们在比赛中获得宝贵的AiDA实践经验,为未来的时尚设计行业打下了坚实的基础,成为时尚界的AI先锋。" - } - ] - },{ - "paragraph":[ - { - "text":" 此次比赛面向全体SFT 学生,最终获奖者将赢取丰厚奖金(最高可达2万港币),获得在BESFXXK的实习机会(将与著名设计师Lim Jae Hyuk先生合作设计BESFXXK 系列,该系列将在纽约时装周和巴黎时装周上展出)及更多惊喜哦!扫描二维码获取更多比赛信息。" - } - ] - } - ] - } - ] -} \ No newline at end of file + "eventsItem": [ + { + "id": 3, + "title": "AiDA全球设计奖 2026", + "imgUrl": "/image/events/award-poster.gif", + "textList": [ + { + "paragraph": [ + { + "text": "秉承推动 AI 赋能创意设计的初衷,Code‑Create 举办了「AiDA 全球设计大奖 2026」,面向来自香港、中国、新加坡、韩国及全球的设计师,鼓励大家探索 AI 与时尚设计的无限可能,突破传统界限,释放科技与想象力的创新潜能。扫描二维码获取更多比赛信息,抓住成为 AI 时尚先锋的机会吧!" + } + ] + }, + { + "paragraph": [ + { + "text": "参赛者将有机会赢取总奖金 9,000 美元,作品还将获得国际媒体展示机会,并与全球设计师和行业领袖建立联系。入围决赛者将受邀参加在香港举办的 专属颁奖典礼,主办方提供差旅支持,让设计师在国际舞台展示才华、拓展人脉,并共同庆祝创意成果。" + } + ] + } + ] + }, + { + "id": 1, + "title": "什么?只要发布设计作品就有机会来香港与大佬面对面交流?!", + "imgUrl": "/image/events/workshop-Cn.jpg", + "textList": [ + { + "paragraph": [ + { + "text": "🎨这是一趟艺术巅峰之旅!AiDA Workshop!" + } + ] + }, + { + "paragraph": [ + { + "text": "参与过程很简单,利用AiDA 在 “Gallery广场 ”发布设计作品,最终获赞最高者(至少20个赞)将被邀请至11月14日 举办的AiDA Workshop香港线下活动,与英国皇家艺术学院(RCA)、韩国知名时尚品牌BESFXXK创始人JAE以及优秀设计师一同交流!(名额仅限1名)" + } + ] + }, + { + "paragraph": [ + { + "text": "⚠️注意❗❗" + } + ] + }, + { + "paragraph": [ + { + "text": "1. 作品描述添加tag: #AiDAworkshop_2024" + }, + { + "text": "2. 一个冠军名额" + } + ] + }, + { + "paragraph": [ + { + "text": "🤩Code-Create将提供(适用条款条规):" + } + ] + }, + { + "paragraph": [ + { + "text": "✅往返机票/动车费用(仅限中国地区)" + } + ] + }, + { + "paragraph": [ + { + "text": "✅一晚酒店住宿费用" + } + ] + }, + { + "paragraph": [ + { + "text": "⌛️截止时间:2024.10.31" + } + ] + } + ] + }, + { + "id": 2, + "title": "AiDA X SFT AI时尚设计比赛2024", + "imgUrl": "/image/events/Fashion-Award-2024.png", + "textList": [ + { + "paragraph": [ + { + "text": "秉承着激发学生使用AI进行时尚设计的创新能力的初衷,Code-Create和香港理工大学时装及纺织学院(SFT)共同举办了“AiDA X SFT AI时尚设计比赛2024”让学生们在比赛中获得宝贵的AiDA实践经验,为未来的时尚设计行业打下了坚实的基础,成为时尚界的AI先锋。" + } + ] + }, + { + "paragraph": [ + { + "text": " 此次比赛面向全体SFT 学生,最终获奖者将赢取丰厚奖金(最高可达2万港币),获得在BESFXXK的实习机会(将与著名设计师Lim Jae Hyuk先生合作设计BESFXXK 系列,该系列将在纽约时装周和巴黎时装周上展出)及更多惊喜哦!扫描二维码获取更多比赛信息。" + } + ] + } + ] + } + ] +} diff --git a/src/assets/style/style.css b/src/assets/style/style.css index df4233a9..8f96aae1 100644 --- a/src/assets/style/style.css +++ b/src/assets/style/style.css @@ -2506,4 +2506,7 @@ textarea:focus { } .justify-center { justify-content: center; +} +.flex-1{ + flex: 1; } \ No newline at end of file diff --git a/src/assets/style/style.less b/src/assets/style/style.less index e1c0a640..dc89d11c 100644 --- a/src/assets/style/style.less +++ b/src/assets/style/style.less @@ -2424,4 +2424,7 @@ textarea:focus{ } .justify-center { justify-content: center; +} +.flex-1{ + flex: 1; } \ No newline at end of file diff --git a/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js b/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js index 85aa2dd6..3c97e52b 100644 --- a/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js @@ -7,12 +7,11 @@ import { insertObjectAtZIndex, removeCanvasObjectByObject, createPatternTransform, + getTransformScaleAngle, imageAddGapToCanvas, } from "../utils/helper"; import { restoreFabricObject } from "../utils/objectHelper"; -const scale = 0.3;// 默认缩放比例 - export const FillSourceToBase64 = (source) => { if (source?.toDataURL) { return source.toDataURL?.(); @@ -39,7 +38,6 @@ export class FillRepeatCommand extends Command { this.fillRepeat = options.fillRepeat; this.oldObjects = null; this.oldLocked = null; - this.oldIsDisableUnlock = null; } async execute() { @@ -64,17 +62,15 @@ export class FillRepeatCommand extends Command { ); }); image.set({ - id: object.id, - layerId: object.layerId, - layerName: object.layerName, + ...this.copyObjectProperties(object), ...(fill_.originalInfo || { top: object.top, left: object.left, }) }); layer.fabricObjects = [image.toObject(["id", "layerId", "layerName"])]; - this.oldLocked = layer.locked; - layer.locked = false; + // this.oldLocked = layer.locked; + // layer.locked = false; this.canvas.add(image); this.canvas.remove(object); @@ -113,23 +109,32 @@ export class FillRepeatCommand extends Command { const fdObject = this.canvasManager.getFixedLayerObject(); const bgObject = this.canvasManager.getBackgroundLayerObject(); const tObject = fdObject || bgObject; + const tWidth = tObject.width; + const tHeight = tObject.height; + + // const offsetX = object.fill?.hasOwnProperty("offsetX") ? object.fill.offsetX : tObject.width / 2; + // const offsetY = object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : tObject.height / 2; + const scaleX_ = tWidth / img.width / 5; + const scaleY_ = tHeight / img.height / 5; + const scale_ = tWidth > tHeight ? scaleX_ : scaleY_; + + const patternTransform = object.fill?.hasOwnProperty("patternTransform") ? object.fill.patternTransform : createPatternTransform(scale_, 0); + const scale = getTransformScaleAngle(patternTransform).scale; + const offsetX = tWidth / 2 - img.width * scale / 2; + const offsetY = tHeight / 2 - img.height * scale / 2; const pattern = new fabric.Pattern({ source: img, repeat: this.fillRepeat, - patternTransform: object.fill?.hasOwnProperty("patternTransform") ? object.fill.patternTransform : createPatternTransform(scale, 0), - offsetX: object.fill?.hasOwnProperty("offsetX") ? object.fill.offsetX : tObject.width / 2, // 水平偏移 - offsetY: object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : tObject.height / 2, // 垂直偏移 + patternTransform, + offsetX, // 水平偏移 + offsetY, // 垂直偏移 }); const rect = new fabric.Rect({ - id: object.id, - layerId: object.layerId, - layerName: object.layerName, + ...this.copyObjectProperties(object), fill_, }); layer.fabricObjects = [rect.toObject(["id", "layerId", "layerName"])]; - this.oldLocked = layer.locked; - // this.oldIsDisableUnlock = layer.isDisableUnlock; - // layer.isDisableUnlock = true; + // this.oldLocked = layer.locked; if (this.oldObjects.type === "rect") { rect.set({ width: object.width, @@ -148,14 +153,14 @@ export class FillRepeatCommand extends Command { let scaleX = tObject.scaleX || 1; let scaleY = tObject.scaleY || 1; rect.set({ - width: tObject.width, - height: tObject.height, - top: tObject.top - tObject.height * scaleY / 2, - left: tObject.left - tObject.width * scaleX / 2, + width: tWidth, + height: tHeight, + top: tObject.top - tHeight * scaleY / 2, + left: tObject.left - tWidth * scaleX / 2, scaleX, scaleY, }); - layer.locked = true; + // layer.locked = true; } rect.set("fill", pattern); this.canvas.add(rect); @@ -184,14 +189,23 @@ export class FillRepeatCommand extends Command { this.canvas.remove(object); this.canvas.add(this.oldObjects); layer.fabricObjects = [this.oldObjects.toObject(["id", "layerId", "layerName"])]; - layer.locked = this.oldLocked; - // layer.isDisableUnlock = this.oldIsDisableUnlock; + // layer.locked = this.oldLocked; await this.layerManager?.updateLayersObjectsInteractivity(); await this.layerManager?.sortLayersWithTool?.(); this.canvas.renderAll(); this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layerId); return true; } + + // 复制原对象的属性 + copyObjectProperties(object) { + return { + id: object.id, + layerId: object.layerId, + layerName: object.layerName, + isPrintTrims: object.isPrintTrims, + } + } } @@ -230,6 +244,10 @@ export class FillRepeatChangeCommand extends Command { ...this.newPattern, }); object.set("fill", pattern); + if (object.globalCompositeOperation_) { + object.globalCompositeOperation = object.globalCompositeOperation_; + object.globalCompositeOperation_ = null; + } this.canvas.renderAll(); return true; } @@ -276,7 +294,7 @@ export class FillRepeatGapChangeCommand extends Command { this.oldGapY = null; } - async execute(isUndo = false) { + async execute(isCommand = true, isUndo = false) { const { layer } = findLayerRecursively(this.layers.value, this.layerId); if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) { console.warn("图层不存在或没有 fabric 对象"); @@ -315,6 +333,10 @@ export class FillRepeatGapChangeCommand extends Command { const fill = object.get("fill"); fill.source = imageAddGapToCanvas(image, object.fill_.gapX, object.fill_.gapY); object.set("fill", new fabric.Pattern(fill)); + if (isCommand && object.globalCompositeOperation_) { + object.globalCompositeOperation = object.globalCompositeOperation_; + object.globalCompositeOperation_ = null; + } this.canvas.renderAll(); return true; } @@ -324,7 +346,7 @@ export class FillRepeatGapChangeCommand extends Command { console.warn("没有旧间隙可恢复"); return false; } - await this.execute(true); + await this.execute(true, true); return true; } diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 339fe361..9c79d9ce 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -280,8 +280,13 @@ export class PasteLayerCommand extends Command { isCut: undefined, serializedObjects: undefined, }; - - if (this.insertIndex !== undefined && this.insertIndex !== null) { + if(this.newLayer.isPrintTrims){ + this.layers.value.forEach((layer) => { + if (layer.isPrintTrimsGroup) { + layer.children.unshift(this.newLayer); + } + }) + }else if (this.insertIndex !== undefined && this.insertIndex !== null) { this.layers.value.splice(this.insertIndex, 0, this.newLayer); } else { this.layers.value.push(this.newLayer); diff --git a/src/component/Canvas/CanvasEditor/commands/StateCommands.js b/src/component/Canvas/CanvasEditor/commands/StateCommands.js index 40db92ac..03c0bd8a 100644 --- a/src/component/Canvas/CanvasEditor/commands/StateCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/StateCommands.js @@ -25,7 +25,7 @@ export class TransformCommand extends Command { this.layerManager = options.layerManager; this.layers = options.layers || null; this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID - + this.isCommand = options.isCommand == undefined ? true : options.isCommand const targetObject = findObjectById(this.canvas, this.objectId)?.object || null; @@ -189,6 +189,11 @@ export class TransformCommand extends Command { object.set(key, value); }); + if(this.isCommand && object.globalCompositeOperation_){ + object.globalCompositeOperation = object.globalCompositeOperation_; + object.globalCompositeOperation_ = null; + } + // 确保对象更新 object.setCoords(); } diff --git a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue index 3e699e09..c8aad686 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue @@ -132,8 +132,8 @@ const offsetY = object.fill?.offsetY; const twidth = object.fill_?.width; const theight = object.fill_?.height; - const x = ((offsetX - (twidth * scale) / 2) * 100) / object.width; - const y = ((offsetY - (theight * scale) / 2) * 100) / object.height; + const x = ((offsetX + (twidth * scale) / 2) * 100) / object.width; + const y = ((offsetY + (theight * scale) / 2) * 100) / object.height; return { x, y }; }); const inputFillOffset = (e) => setFillOffset(e, true); @@ -143,8 +143,8 @@ const object = props.object; const patternTransform = object.fill?.patternTransform; const scale = getTransformScaleAngle(patternTransform).scale; - const x = (left / 100) * object.width + (object.fill_?.width * scale) / 2; - const y = (top / 100) * object.height + (object.fill_?.height * scale) / 2; + const x = (left / 100) * object.width - (object.fill_?.width * scale) / 2; + const y = (top / 100) * object.height - (object.fill_?.height * scale) / 2; emit(isInput ? "inputFillOffset" : "changeFillOffset", { x, y }); }; diff --git a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue index 6f8a54a4..26a16fa2 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue @@ -126,8 +126,8 @@ :options="selectOptions" @change="(e) => changeFillRepeat(e, v)" :disabled=" - v.layer?.metadata?.level2Type === - 'Embroidery' + v.layer?.metadata?.sourceData?.type === + 'trims' " /> @@ -315,6 +315,7 @@ layerManager: props.layerManager, layers: layers, lastSelectLayerId: lastSelectLayerId, + isCommand, }); if (isCommand) { props.commandManager.execute(cmd); @@ -336,6 +337,7 @@ const finalState = computeAngleState(angle, obj, initialState); transformObject(obj, initialState, finalState, false); if (!obj.hasOwnProperty("oldState")) obj.oldState = initialState; + props.canvasManager.beforeChangeCanvas([obj]); }; const changeAngle = (angle, obj) => { var initialState; @@ -428,6 +430,7 @@ }); obj.set("fill", pattern); props.canvas.renderAll(); + props.canvasManager.beforeChangeCanvas([obj]); }; const changeFillAngle = (angle, obj) => { const fill = obj.get("fill"); @@ -447,6 +450,7 @@ }); obj.set("fill", pattern); props.canvas.renderAll(); + props.canvasManager.beforeChangeCanvas([obj]); }; const changeFillOffset = (value, obj) => { const pattern = new fabric.Pattern({ @@ -466,6 +470,7 @@ }); obj.set("fill", pattern); props.canvas.renderAll(); + props.canvasManager.beforeChangeCanvas([obj]); }; const changeFillScale = (scale, obj) => { const fill = obj.get("fill"); @@ -498,7 +503,8 @@ newGapY: gapY, record: true, }); - cmd.execute(); + cmd.execute(false); + props.canvasManager.beforeChangeCanvas([obj]); }; const changeFillGap = (gapX, gapY, obj) => { if (obj.oldFill_) { diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index a80624c7..53f4455b 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -896,7 +896,7 @@ const changeCanvas = async (command) => { ...command, // 传递完整的命令数据 }; emit("changeCanvas", commandData); - canvasManager.changeCanvas(commandData); + canvasManager.changeCanvas(); if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) { setTimeout(async () => { try { @@ -991,7 +991,7 @@ defineExpose({ }, updateOtherLayers: async (otherData) => { await new Promise((resolve) => optimizeCanvasRendering(canvasManager.canvas, resolve)); - await canvasManager?.createOtherLayers?.(otherData, true); + await canvasManager?.createOtherLayers?.(otherData); layerManager.activeLayerId.value = "" layerManager?.sortLayers(); await layerManager?.updateLayersObjectsInteractivity?.(true); diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index abce88de..b7583264 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -74,6 +74,7 @@ export class CanvasManager { this.props = options.props || {}; this.emit = options.emit || (() => {}); this.awaitCanvasRun = null; + this.canvasChangeing = false; // 初始化画布 this.initializeCanvas(); } @@ -338,6 +339,7 @@ export class CanvasManager { setupCanvasEvents(activeElementId, layerManager) { // 创建画布事件管理器 this.eventManager = new CanvasEventManager(this.canvas, { + canvasManager: this, toolManager: this.toolManager, animationManager: this.animationManager, thumbnailManager: this.thumbnailManager, @@ -868,9 +870,9 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') return layerObjectByLayerId; } - getObjectsByIds(ids){ + getObjectsByIdOrLayerId(ids){ const objects = this.canvas.getObjects().filter((obj) => { - return ids.includes(obj.id); + return ids.includes(obj.id) || ids.includes(obj.layerId); }); return objects; } @@ -1147,7 +1149,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') const glayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); if(!glayer) return Promise.reject("印花和元素图层组不存在"); const ids = glayer.children.map((v) => v.id); - const objects = this.getObjectsByIds(ids); + const objects = this.getObjectsByIdOrLayerId(ids); const fixedLayerObj = this.getFixedLayerObject(); if(!fixedLayerObj) return Promise.reject("固定图层不存在"); const flWidth = fixedLayerObj.width @@ -1158,8 +1160,9 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') const flScaleY = fixedLayerObj.scaleY const prints = []; const trims = []; - objects.forEach((v) => { - const sourceData = glayer.children.find((v_) => v_.id === v.id)?.metadata?.sourceData; + objects.forEach((v, i) => { + const label = glayer.children.find((v_) => (v_.id === v.layerId || v_.id === v.id)); + const sourceData = label?.metadata?.sourceData; if(!sourceData) return; const obj = { ifSingle: typeof v.fill === "string", @@ -1171,7 +1174,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') scale: [0, 0], angle: v.angle, name: sourceData.name, - priority: sourceData.priority, + priority: i + 1, object:{ top: 0, left: 0, @@ -1220,8 +1223,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') let scaleY = scale * 5 * v.fill_.height / flHeight; let scaleXY = flWidth > flHeight ? scaleX : scaleY; - let left = fill.offsetX - v.fill_.width * scale / 2; - let top = fill.offsetY - v.fill_.height * scale / 2; + let left = fill.offsetX + v.fill_.width * scale / 2; + let top = fill.offsetY + v.fill_.height * scale / 2; obj.scale = [scaleXY, scaleXY]; obj.angle = angle; @@ -1237,8 +1240,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') } }) // prints.sort((a, b) => a.ifSingle ? 1 : -1); - prints.forEach((v, i) => v.priority = i + 1); - trims.forEach((v, i) => v.priority = i + 1); + // prints.forEach((v, i) => v.priority = i + 1); + // trims.forEach((v, i) => v.priority = i + 1); return {prints, trims}; } @@ -1465,9 +1468,12 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') } /** 修复JSON数据中的ID丢失问题 */ FixJsonIdLoss(json){ + const layerIds = []; const layers = json?.layers || []; const objects = json?.canvas?.objects || []; layers.forEach((layer) => { + layerIds.push(layer.id); + layer.children?.forEach((child) => layerIds.push(child.id)); if(!layer.fabricObjects?.[0]?.id && !layer.fabricObject?.id){ const obj = objects?.find((o) => o.layerId === layer.id); if(obj) { @@ -1483,6 +1489,17 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') if (a.isBackground) return -1; if (b.isBackground) return 1; }) + // 排除的对象id + const excludedObjects = [SpecialLayerId.PART_SELECTOR]; + json.canvas.objects = objects.filter((v) => { + // 指定ID排除 + if(excludedObjects.includes(v.id)) return false; + + if(v.isBackground || v.isFixed) return true; + // 当前图层不存在当前对象 + if(!layerIds.includes(v.layerId)) return false; + return true + }); } @@ -1490,7 +1507,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') * 创建其他图层:印花、颜色、元素... * @param {Object} otherData - 其他图层数据 */ - async createOtherLayers(otherData, isUpdate = false) { + async createOtherLayers(otherData) { if (!otherData) return console.warn("otherData 为空不需要添加"); this.canvas.loading.value = true; let resolve = ()=>{}; @@ -1498,17 +1515,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') const otherData_ = JSON.parse(JSON.stringify(otherData)); console.log("==========创建其他图层", otherData_); - const updateColor = !!otherData_.color; - const updateSpecialGroup = !!otherData_.printObject || !!otherData_.trims; // 删除颜色图层和特殊组图层 - const ids = []; - if(isUpdate){ - updateColor && ids.push(SpecialLayerId.COLOR) - updateSpecialGroup && ids.push(SpecialLayerId.SPECIAL_GROUP) - }else{ - ids.push(SpecialLayerId.COLOR) - ids.push(SpecialLayerId.SPECIAL_GROUP) - } + const ids = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP]; this.layers.value = this.layers.value.filter((layer) => { if(ids.includes(layer.id)){ ids.push(...layer.children?.map((child) => child.id)); @@ -1516,11 +1524,15 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') } return true; }) - this.canvas.getObjects().forEach((v) => ids.includes(v.id) && this.canvas.remove(v)) + this.canvas.getObjects().forEach((v) => { + if(ids.includes(v.id) || ids.includes(v.layerId)){ + this.canvas.remove(v) + } + }) // 创建颜色图层 - otherData_.color && await this.createColorLayer(otherData_.color); + await this.createColorLayer(otherData_.color); const printTrimsLayers = [];// 印花和元素图层 const singleLayers = [];// 平铺图层 @@ -1538,7 +1550,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') trims.type = "trims"; printTrimsLayers.unshift({...trims}); }) - if(isUpdate ? updateSpecialGroup : true){ + if(printTrimsLayers.length || singleLayers.length){ await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); } await this.changeCanvas(); @@ -1669,8 +1681,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') let opacity = 1 let flipX = false; let flipY = false; - let blendMode = BlendMode.MULTIPLY; - if(item.type === "trims") blendMode = BlendMode.NORMAL;// 元素正常 + let blendMode = BlendMode.NORMAL; + // if(item.type === "trims") blendMode = BlendMode.NORMAL;// 元素正常 if(item.object){ opacity = item.object.opacity flipX = item.object.flipX @@ -1695,7 +1707,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') hasBorders: true, isPrintTrims: true, }); - this.canvas.add(image); + // this.canvas.add(image); let layer = createLayer({ id: id, name: name, @@ -1706,7 +1718,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') isPrintTrims: true, blendMode: blendMode, fabricObjects: [image.toObject(["id", "layerId", "layerName"])], - metadata: {sourceData: item, level2Type: item.level2Type}, + metadata: {sourceData: item}, + object: image, }) children.push(layer); }; @@ -1730,8 +1743,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') let scaleX_ = flWidth / image.width * (item.scale?.[0] || 1) / 5; let scaleY_ = flHeight / image.height * (item.scale?.[1] || 1) / 5; let scale = flWidth > flHeight ? scaleX_ : scaleY_; - let offsetX = (item.location?.[0] || 0) + image.width * scale / 2 - let offsetY = (item.location?.[1] || 0) + image.height * scale / 2 + let offsetX = (item.location?.[0] || 0) - image.width * scale / 2 + let offsetY = (item.location?.[1] || 0) - image.height * scale / 2 let top = flTop - flHeight * flScaleY / 2 let left = flLeft - flWidth * flScaleX / 2 let scaleX = flScaleX @@ -1743,7 +1756,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') let fillSource = image let flipX = false; let flipY = false; - let blendMode = BlendMode.MULTIPLY; + let blendMode = BlendMode.NORMAL; let fill_repeat = "repeat" if(item.object){ top += item.object.top * flScaleY @@ -1754,7 +1767,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') angle = item.object.angle flipX = item.object.flipX flipY = item.object.flipY - blendMode = item.object.blendMode || BlendMode.MULTIPLY; + if(item.object.blendMode) blendMode = item.object.blendMode; gapX = item.object.gapX gapY = item.object.gapY fillSource = imageAddGapToCanvas(image, gapX, gapY); @@ -1791,18 +1804,19 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') }, isPrintTrims: true, }); - this.canvas.add(rect); + // this.canvas.add(rect); let layer = createLayer({ id: id, name: name, type: LayerType.BITMAP, visible: true, - locked: true, + locked: false, opacity: opacity, isPrintTrims: true, - blendMode: BlendMode.MULTIPLY, + blendMode: blendMode, fabricObjects: [rect.toObject(["id", "layerId", "layerName"])], metadata: {sourceData: item}, + object: rect, }) children.push(layer); }; @@ -1819,6 +1833,13 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') // children.push(layer); // } if(children.length === 0) return; + // 印花元素排序 + if(new Set(children.map(v => v.metadata.sourceData.priority)).size === children.length){ + children.sort((a, b) => b.metadata.sourceData.priority - a.metadata.sourceData.priority); + } + children.forEach(layer => { + this.canvas.add(layer.object); + }); const groupRect = new fabric.Rect({}); await this.setObjecCliptInfo(groupRect); // 插入组图层 @@ -1841,12 +1862,13 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') /** * 画布事件变更后 */ - async changeCanvas(){ + async changeCanvas(fids = [], isBeforeChange = false){ + if(!isBeforeChange) this.canvasChangeing = false; const fixedLayerObj = this.getFixedLayerObject(); if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj) const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR); if(colorObject){ - const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP); + const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP).filter(id => !fids.includes(id)); if(ids.length === 0){ ids.unshift(SpecialLayerId.SPECIAL_GROUP); await this.setObjecCliptInfo(colorObject); @@ -1866,6 +1888,24 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') this.canvas.renderAll(); } } + /** 画布变更之前 */ + async beforeChangeCanvas(objects){ + if(this.canvasChangeing) return; + const ids = objects.filter(v => { + return v.isPrintTrims && v.globalCompositeOperation && v.globalCompositeOperation !== BlendMode.NORMAL + }).map(v => v.layerId); + if(ids.length > 0){ + this.canvasChangeing = true; + this.canvas.getObjects().forEach(v => { + if(ids.includes(v.layerId)){ + v.globalCompositeOperation_ = v.globalCompositeOperation; + v.globalCompositeOperation = BlendMode.NORMAL; + } + }) + this.canvas.renderAll(); + await this.changeCanvas(ids, true); + } + } /** * 缩放红绿图模式内容以适应当前画布大小 diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index 4b23403f..b0935346 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -1750,7 +1750,7 @@ export class LayerManager { layer.serializedObjects = layer.fabricObjects .map((obj) => { if (typeof obj.toObject === "function") { - return obj.toObject(["id", "layerId", "layerName"]); + return obj.toObject(["id", "layerId", "layerName", "fill_"]); } return null; }) @@ -1763,7 +1763,7 @@ export class LayerManager { if (layer.fabricObject) { layer.serializedBackgroundObject = typeof layer.fabricObject.toObject === "function" - ? layer.fabricObject.toObject(["id", "layerId", "layerName"]) + ? layer.fabricObject.toObject(["id", "layerId", "layerName", "fill_"]) : null; delete layer.fabricObject; @@ -1793,7 +1793,7 @@ export class LayerManager { return layer.fabricObjects .map((obj) => { const { object } = findObjectById(this.canvas, obj.id); - if (object) return object.toObject(["id", "layerId", "layerName"]); + if (object) return object.toObject(["id", "layerId", "layerName", "fill_"]); return false; }) .filter(Boolean); @@ -1839,6 +1839,7 @@ export class LayerManager { // 存储到剪贴板 this.clipboardData = layerCopy; + console.log("复制图层:", layerCopy); const input = document.createElement("input"); input.value = "aida_copy_canvas_layer: " + layer.name; document.body.appendChild(input); @@ -1884,7 +1885,7 @@ export class LayerManager { layerCopy.serializedObjects = layer.fabricObjects .map((obj) => typeof obj.toObject === "function" - ? obj.toObject(["id", "layerId", "layerName"]) + ? obj.toObject(["id", "layerId", "layerName", "fill_"]) : null ) .filter(Boolean); @@ -1935,10 +1936,6 @@ export class LayerManager { return this.clipboardData; } - /** - * 粘贴图层 - * @returns {string} 新创建的图层ID - */ /** * 粘贴图层 * @returns {string} 新创建的图层ID diff --git a/src/component/Canvas/CanvasEditor/managers/PartManager.js b/src/component/Canvas/CanvasEditor/managers/PartManager.js index 3f68adcc..8e4208b7 100644 --- a/src/component/Canvas/CanvasEditor/managers/PartManager.js +++ b/src/component/Canvas/CanvasEditor/managers/PartManager.js @@ -1,6 +1,6 @@ import { fabric } from "fabric-with-all"; import { traceImageContour, imageToCanvas } from "../utils/helper"; -import { OperationType } from "../utils/layerHelper"; +import { OperationType, SpecialLayerId } from "../utils/layerHelper"; import { LassoCutoutCommand } from "../commands/LassoCutoutCommand"; import addIcon from "@/assets/images/canvas/add.png"; import removeIcon from "@/assets/images/canvas/remove.png"; @@ -72,7 +72,7 @@ export class PartManager { this.activeTool = this.toolManager.activeTool; this.rgba = { r: 0, g: 255, b: 0, a: 200 }; - this.partId = "part_selector"; + this.partId = SpecialLayerId.PART_SELECTOR; this.partGroup = null; // 当前选区对象 this.partCanvas = null;// 选区画布 this.rectangleObject = null; // 矩形对象 diff --git a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js index 8c318cf5..8aa4593c 100644 --- a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js +++ b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js @@ -6,6 +6,7 @@ import { OperationType, OperationTypes } from "../../utils/layerHelper"; export class CanvasEventManager { constructor(canvas, options = {}) { this.canvas = canvas; + this.canvasManager = options.canvasManager; this.toolManager = options.toolManager || null; this.animationManager = options.animationManager; this.thumbnailManager = options.thumbnailManager; @@ -691,7 +692,9 @@ export class CanvasEventManager { // 清除临时状态记录 delete activeObj._initialTransformState; } - } + }else{ + this.canvasManager.changeCanvas(); + } if (this.thumbnailManager && e.target) { if (e.target.id) { @@ -975,6 +978,13 @@ export class CanvasEventManager { // 添加调试日志(可选) // console.log(`捕获对象 ${obj.id} (${obj.type}) 的初始变换状态`); } + const arrs = []; + if (e.target._objects) { + e.target._objects.forEach((v) => arrs.push(v)); + } else { + arrs.push(e.target); + } + this.canvasManager.beforeChangeCanvas(arrs); } /** diff --git a/src/component/Canvas/CanvasEditor/utils/layerHelper.js b/src/component/Canvas/CanvasEditor/utils/layerHelper.js index 7a164fe0..33187625 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/layerHelper.js @@ -24,6 +24,7 @@ export const LayerType = { export const SpecialLayerId = { SPECIAL_GROUP: "group_special", // 特殊组 COLOR: "special_color", // 颜色图层 + PART_SELECTOR: "part_selector", // 部件选择器图层 } diff --git a/src/component/Canvas/CanvasEditor/utils/selectionToImage.js b/src/component/Canvas/CanvasEditor/utils/selectionToImage.js index 1b775b41..6c0d4ce3 100644 --- a/src/component/Canvas/CanvasEditor/utils/selectionToImage.js +++ b/src/component/Canvas/CanvasEditor/utils/selectionToImage.js @@ -184,16 +184,17 @@ const createClippedDataURLByCanvas = async ({ // console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL"); // 使用优化后的边界计算,确保包含描边区域 - // const optimizedBounds = calculateOptimizedBounds( - // clippingObject, - // fabricObjects - // ); - const optimizedBounds = { - left: clippingObject.left - clippingObject.width / 2, - top: clippingObject.top - clippingObject.height / 2, - width: clippingObject.width, - height: clippingObject.height, - } + const optimizedBounds = calculateOptimizedBounds( + clippingObject, + fabricObjects + ); + console.log("📐 优化后的选区边界框:", optimizedBounds); + // const optimizedBounds = { + // left: clippingObject.left - clippingObject.width / 2, + // top: clippingObject.top - clippingObject.height / 2, + // width: clippingObject.width, + // height: clippingObject.height, + // } // 使用高分辨率以保证质量 const pixelRatio = window.devicePixelRatio || 1; diff --git a/src/component/Canvas/OverallCanvas/index.vue b/src/component/Canvas/OverallCanvas/index.vue index 07a39d9b..7c304820 100644 --- a/src/component/Canvas/OverallCanvas/index.vue +++ b/src/component/Canvas/OverallCanvas/index.vue @@ -203,9 +203,9 @@ let scaleY = ((cheight / image.height) * item.scale[1]) / 5; let scale = cwidth > cheight ? scaleX : scaleY; let offsetX = - (item.location[0] * cwidth) / props.width + (image.width * scale) / 2; + (item.location[0] * cwidth) / props.width - (image.width * scale) / 2; let offsetY = - (item.location[1] * cheight) / props.height + + (item.location[1] * cheight) / props.height - (image.height * scale) / 2; let angle = item.angle; let gapX = item.object.gapX; diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index 84e86170..7a961666 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -342,32 +342,34 @@ level2Type: "Pattern", designType: "Library", path: "/src/assets/images/canvas/yinhua1.jpg", - location: [250, 780], - scale: [1.2, 1.6], + location: [800, 600], + scale: [1, 1], angle: 0, + priority: 1, object: { - top: 600, - left: 800, + top: 300, + left: 400, scaleX: 0.5, scaleY: 0.5, opacity: 1, - angle: 45, + angle: 0, flipX: false, flipY: false, - blendMode: "multiply", + // blendMode: "multiply", gapX: 0, gapY: 0, }, }, - // { - // ifSingle: true, - // level2Type: "Pattern", - // designType: "Library", - // path: "/src/assets/images/canvas/yinhua1.jpg", - // location: [550, 650], - // scale: [0.15, 0.2], - // angle: 0, - // }, + { + ifSingle: true, + level2Type: "Pattern", + designType: "Library", + path: "/src/assets/images/canvas/yinhua1.jpg", + location: [550, 650], + scale: [0.15, 0.2], + angle: 0, + priority: 2, + }, // { // ifSingle: true, // level2Type: "Pattern", @@ -376,6 +378,7 @@ // location: [700, 400], // scale: [0.1, 0.133], // angle: 0, + // priority: 3, // }, ], }, @@ -411,6 +414,7 @@ :clothingMinIOPath="clothingMinIOPath" :clothingImageUrl="clothingImageUrl" :clothingImageUrl2="clothingImageUrlInit" + @canvasLoadJsonSuccess="canvasLoadJsonSuccess" :config="editorConfig" :clothing-image-opts="{ imageMode: 'contains', // 设置底图包含在画布内 diff --git a/src/component/Detail/DesignDetail.vue b/src/component/Detail/DesignDetail.vue index a4f36f18..410f7bc5 100644 --- a/src/component/Detail/DesignDetail.vue +++ b/src/component/Detail/DesignDetail.vue @@ -369,6 +369,7 @@ export default defineComponent({ // }else if(isCurrent){ // } + console.log(JSON.parse(JSON.stringify(detailData.selectDetail.color)),'=====') color = list[i].color?.rgba?.r != null?`${list[i].color.rgba.r} ${list[i].color.rgba.g} ${list[i].color.rgba.b}`:'' gradient = list[i].gradient if((detailData.currentDetailType == 'sketch' && newData?.sketch) || detailData.isEditPattern.value == 'editSketch'){ @@ -588,6 +589,7 @@ export default defineComponent({ segmentImage(detailData.selectDetail.maskUrl,partialDesign,size).then(async (rv)=>{ let front = detailData.frontBack.front[detailData.imgDomIndex] let back = detailData.frontBack.back[detailData.imgDomIndex] + if(front?.oldImageUrl)front.oldImageUrl = '' if(front?.oldMaskUrl)front.oldMaskUrl = '' if(back?.oldImageUrl)back.oldImageUrl = '' @@ -778,7 +780,6 @@ export default defineComponent({ if(canvasColor?.gradient){ color.gradient = canvasColor.gradient } - console.log(color,'color') } if(detailData.isEditPattern.value == 'canvasEditor'){ diff --git a/src/component/Detail/canvas/index.vue b/src/component/Detail/canvas/index.vue index dde6d912..ac0c3544 100644 --- a/src/component/Detail/canvas/index.vue +++ b/src/component/Detail/canvas/index.vue @@ -12,6 +12,7 @@ is-edit :clothingImageUrl="selectDetail.path" :clothingImageUrl2="selectDetail.maskUrl || selectDetail.layersObject[0].maskUrl" + :clothingMinIOPath="selectDetail.minIOPath" showFixedLayer :canvasJSON="canvasJSON" @canvasLoadJsonSuccess="canvasLoadJsonSuccess" diff --git a/src/component/Detail/detailLeft/colorBox/index.vue b/src/component/Detail/detailLeft/colorBox/index.vue index 6300ddee..7d9517c7 100644 --- a/src/component/Detail/detailLeft/colorBox/index.vue +++ b/src/component/Detail/detailLeft/colorBox/index.vue @@ -101,7 +101,6 @@ export default defineComponent({ }) watch(()=>colorData.selectColor,async (newVal,oldVal)=>{ if((newVal.rgba && newVal.rgba?.r != null) || newVal.gradient != null){ - console.log('=======',123) let data :any = {} if(newVal.rgba?.r != null){ data = await getColorName(newVal.rgba) @@ -119,7 +118,6 @@ export default defineComponent({ } store.commit('DesignDetail/setNewDetail',value) }else{ - console.log('=======',123) let value = { data:{}, str:'color' @@ -144,14 +142,13 @@ export default defineComponent({ let color = colorData.allBoardData.colorBoards?.[index] if(!color?.rgba && color?.rgbValue)color.rgba = color.rgbValue if( - (colorData.allBoardData.colorBoards?.[index] && + (colorData.allBoardData.colorBoards?.[index] && color?.rgba && colorData.selectDetail.color.rgba?.r == color?.rgba?.r && colorData.selectDetail.color.rgba?.g == color?.rgba?.g && colorData.selectDetail.color.rgba?.b == color?.rgba?.b) || - (JSON.stringify(colorData.selectDetail.color.gradient) == JSON.stringify(color?.gradient) && colorData.selectDetail.color.gradient) + ((JSON.stringify(colorData.selectDetail.color.gradient) == JSON.stringify(color?.gradient) && colorData.selectDetail.color.gradient)) ){ isNoSelect = true - console.log('=======',123) colorData.selectColor = item colorData.colorList.index = index }else if(color?.rgba?.r){ @@ -175,7 +172,6 @@ export default defineComponent({ } colorData.colorList.list[newVal].push(item) } - console.log('=======',isNoSelect) if(!isNoSelect){ let color = colorData.selectDetail.newDetail?.color?.rgba?.r != null?colorData.selectDetail.newDetail?.color:colorData.selectDetail.color let item:any = {} diff --git a/src/component/Detail/detailRight/editPrintElement.vue b/src/component/Detail/detailRight/editPrintElement.vue index 6b6dd48a..1b8c393d 100644 --- a/src/component/Detail/detailRight/editPrintElement.vue +++ b/src/component/Detail/detailRight/editPrintElement.vue @@ -89,8 +89,8 @@ - - + +
- +
- - - +
+
@@ -157,19 +171,15 @@ :validate-trigger="[]" label="How will you use AiDA in your design process?" > -
+

Click to upload or drag and drop

PDF file, max 20MB

+
-
-
+ class="progress-bar-container" + v-if="pdfUploadStatus === 'uploading'" + > +
+
@@ -209,23 +242,19 @@ :validate-trigger="[]" label="How will you use AiDA in your design process?" > -
+
@@ -233,25 +262,48 @@

Video file (MP4, MOV), 1080p, max 100MB

+
-
-
+ class="progress-bar-container" + v-if="videoUploadStatus === 'uploading'" + > +
+
@@ -342,7 +394,7 @@ import { ref, onUnmounted, onMounted, computed } from 'vue' import { debounce } from 'lodash-es' import type { Rule } from 'ant-design-vue/es/form' - import { message } from 'ant-design-vue' + import { message, Upload } from 'ant-design-vue' import { Https } from '@/tool/https' import { useRoute } from 'vue-router' import type { UploadChangeParam } from 'ant-design-vue' @@ -359,7 +411,7 @@ const route = useRoute() const isCompleted = ref(false) - const hasValidEmail = ref(false) + const readOnly = computed(() => { if (route.query.id && !hasValidEmail.value) { return true @@ -383,7 +435,10 @@ designDescription: '', pdfPath: '', videoPath: '', - secureToken: null + secureToken: '' + }) + const hasValidEmail = computed(() => { + return !!form.value.secureToken }) // 验证码输入组件引用 @@ -620,7 +675,6 @@ console.log('coderes', res) form.value.secureToken = res.data.secureToken - hasValidEmail.value = true message.success('Verification successful!') showModal.value = false @@ -675,7 +729,7 @@ const beforeUploadFile = (type: FileType, file: File) => { if (!hasValidEmail.value) { message.error('Please verify your email first') - return false + return Upload.LIST_IGNORE } let maxSize: number let allowedExtensions: string[] @@ -693,7 +747,7 @@ allowedMimeTypes = ['video/mp4', 'video/quicktime'] errorMessage = 'Please upload a MP4 or MOV file only.' } else { - return false + return Upload.LIST_IGNORE } // 验证文件类型 @@ -705,18 +759,18 @@ if (!isValidType) { message.error(errorMessage) // 从文件列表中移除 - if (type === 'pdf') { - const index = pdfList.value.findIndex(item => item.uid === file.uid) - if (index > -1) { - pdfList.value.splice(index, 1) - } - } else { - const index = videoList.value.findIndex(item => item.uid === file.uid) - if (index > -1) { - videoList.value.splice(index, 1) - } - } - return false // 阻止上传 + // if (type === 'pdf') { + // const index = pdfList.value.findIndex(item => item.uid === file.uid) + // if (index > -1) { + // pdfList.value.splice(index, 1) + // } + // } else { + // const index = videoList.value.findIndex(item => item.uid === file.uid) + // if (index > -1) { + // videoList.value.splice(index, 1) + // } + // } + return Upload.LIST_IGNORE } // 验证文件大小 @@ -726,21 +780,21 @@ `File size exceeds ${sizeLimit} limit. Please upload a smaller file.` ) // 从文件列表中移除 - if (type === 'pdf') { - const index = pdfList.value.findIndex(item => item.uid === file.uid) - if (index > -1) { - pdfList.value.splice(index, 1) - } - } else { - const index = videoList.value.findIndex(item => item.uid === file.uid) - if (index > -1) { - videoList.value.splice(index, 1) - } - } - return false // 阻止上传 + // if (type === 'pdf') { + // const index = pdfList.value.findIndex(item => item.uid === file.uid) + // if (index > -1) { + // pdfList.value.splice(index, 1) + // } + // } else { + // const index = videoList.value.findIndex(item => item.uid === file.uid) + // if (index > -1) { + // videoList.value.splice(index, 1) + // } + // } + return Upload.LIST_IGNORE } - return true // 允许上传 + return true } // PDF文件上传前验证 @@ -822,18 +876,22 @@ } const completeChunkUpload = async (type: FileType, file: File) => { - const endpoint = - type === 'pdf' - ? Https.httpUrls.uploadPDFComplete - : Https.httpUrls.uploadVideoComplete + try { + const endpoint = + type === 'pdf' + ? Https.httpUrls.uploadPDFComplete + : Https.httpUrls.uploadVideoComplete - return Https.axiosPost(endpoint, { - uploadId: chunkUploadState[type].uploadId, - email: form.value.email, - fileName: file.name, - totalSize: file.size, - secureToken: form.value.secureToken - }) + return Https.axiosPost(endpoint, { + uploadId: chunkUploadState[type].uploadId, + email: form.value.email, + fileName: file.name, + totalSize: file.size, + secureToken: form.value.secureToken + }) + } catch (error) { + console.log('complete错误', error) + } } type FileType = 'pdf' | 'video' @@ -922,10 +980,12 @@ isUploadingPdf.value = false uploadProgressPdf.value = 0 pdfUploadStatus.value = 'error' + pdfList.value = [] } else { isUploadingVideo.value = false uploadProgressVideo.value = 0 videoUploadStatus.value = 'error' + videoList.value = [] } } } @@ -940,6 +1000,18 @@ return handleUploadFile(option, 'video') } + const handleRemoveFile = (type: 'video' | 'pdf') => { + if (type === 'pdf') { + form.value.pdfPath = '' + pdfUploadStatus.value = 'idle' + uploadProgressPdf.value = 0 + } else if (type === 'video') { + form.value.videoPath = '' + videoUploadStatus.value = 'idle' + uploadProgressVideo.value = 0 + } + } + const conditionsList = ref([ { check: false, @@ -1047,8 +1119,7 @@ .desc { color: #b10000; - // font-family: 'Instrument'; - font-family: revert-layer; + font-family: 'Instrument'; font-weight: 500; font-size: 2.4rem; } @@ -1067,8 +1138,7 @@ .desc { color: #b10000; - // font-family: 'Instrument'; - font-family: revert-layer; + font-family: 'Instrument'; font-weight: 500; font-size: 2.4rem; } @@ -1139,14 +1209,26 @@ line-height: 6rem; } } - :deep(.ant-select-arrow) { - height: 4rem; - width: 6.2rem; - justify-content: center; - display: flex; - align-items: center; - border-left: 0.1rem solid #d5d5d5; - } + // :deep(.ant-select-arrow) { + + // justify-content: center; + // display: flex; + // align-items: center; + // } + } + } + .select-container { + position: relative; + .arrow-wrapper { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + width: 6rem; + height: 4rem; + box-sizing: border-box; + border-left: 0.1rem solid #d5d5d5; + pointer-events: none; } } @@ -1240,9 +1322,10 @@ .upload-container { margin-top: 6rem; - + position: relative; :deep(.ant-upload-drag) { height: 32rem; + border-radius: 0.8rem; .ant-upload-btn { padding: 0; @@ -1278,9 +1361,13 @@ display: flex; align-items: center; justify-content: center; - border: 0.2rem solid #d5d5d5; + border: 0.2rem dashed #d5d5d5; border-radius: 0.8rem; background-color: #fafafa; + position: absolute; + top: 0; + left: 0; + width: 100%; .uploading-text { color: #585858; @@ -1308,7 +1395,20 @@ } } } - + .custom-upload-list { + padding: 0.4rem 1rem; + margin-top: 1rem; + &:hover { + background-color: #f5f5f5; + border-radius: 0.4rem; + } + .c-svg { + width: fit-content; + } + .delete-file { + cursor: pointer; + } + } .conditions { margin-top: 12rem; @@ -1376,8 +1476,7 @@ } .desc { - // font-family: 'instrument'; - font-family: revert-layer; + font-family: 'Instrument'; font-weight: 400; font-size: 1.6rem; color: #6d6d6d; @@ -1465,9 +1564,12 @@ diff --git a/src/views/AwardPage/components/ApplySection.vue b/src/views/AwardPage/components/ApplySection.vue index ba40b94c..ca120cfb 100644 --- a/src/views/AwardPage/components/ApplySection.vue +++ b/src/views/AwardPage/components/ApplySection.vue @@ -1,28 +1,170 @@ diff --git a/src/views/AwardPage/components/Bloom.vue b/src/views/AwardPage/components/Bloom.vue index c0161f8a..de4134c1 100644 --- a/src/views/AwardPage/components/Bloom.vue +++ b/src/views/AwardPage/components/Bloom.vue @@ -1,5 +1,5 @@ @@ -37,7 +61,9 @@ let bloomObserver: IntersectionObserver | null = null const setupBloomInitialState = () => { - const titleEls = [titleRef.value, subtitleRef.value].filter(Boolean) as HTMLElement[] + const titleEls = [titleRef.value, subtitleRef.value].filter( + Boolean + ) as HTMLElement[] if (titleEls.length) { gsap.set(titleEls, { opacity: 0, @@ -58,7 +84,9 @@ const playBloomAnimation = () => { if (hasPlayedBloomAnim.value) return - const titleEls = [titleRef.value, subtitleRef.value].filter(Boolean) as HTMLElement[] + const titleEls = [titleRef.value, subtitleRef.value].filter( + Boolean + ) as HTMLElement[] const textEl = textRef.value if (!titleEls.length || !textEl) return @@ -81,7 +109,7 @@ duration: 0.3, ease: 'power2.out' }, - '+=0.12' + '-=0.3' ) tl.to( textEl, @@ -103,8 +131,8 @@ setupBloomInitialState() if ('IntersectionObserver' in window) { bloomObserver = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { + entries => { + entries.forEach(entry => { if (entry.isIntersecting) { playBloomAnimation() } @@ -125,11 +153,19 @@ onBeforeUnmount(() => { bloomObserver?.disconnect() }) - - diff --git a/src/views/AwardPage/components/PrizesSection.vue b/src/views/AwardPage/components/PrizesSection.vue index 6c4848af..12bf8433 100644 --- a/src/views/AwardPage/components/PrizesSection.vue +++ b/src/views/AwardPage/components/PrizesSection.vue @@ -24,10 +24,13 @@ >
-
{{ item.money }}
+
+ {{ item.money }} +
{{ item.name }}
{ font-size: 2.4rem; color: #e0e0e0; text-align: center; + white-space: pre-line; } } } diff --git a/src/views/AwardPage/components/Slogan.vue b/src/views/AwardPage/components/Slogan.vue index 8829ab7c..25c4f994 100644 --- a/src/views/AwardPage/components/Slogan.vue +++ b/src/views/AwardPage/components/Slogan.vue @@ -1,5 +1,9 @@