From c4c4753403ff86f0b195d7689b5778d871d4fc74 Mon Sep 17 00:00:00 2001 From: X1627315083 <1627315083@qq.com> Date: Fri, 16 Jan 2026 14:37:53 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8Ddetail=E6=97=A0=E6=B3=95s?= =?UTF-8?q?ubmit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component/Detail/DesignDetail.vue | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/component/Detail/DesignDetail.vue b/src/component/Detail/DesignDetail.vue index 42ffc847..f12d3154 100644 --- a/src/component/Detail/DesignDetail.vue +++ b/src/component/Detail/DesignDetail.vue @@ -414,6 +414,9 @@ export default defineComponent({ trims:(newData && detailData.currentDetailType == 'element' && isCurrent && !detailData.isEditPattern.value)?{prints:newData}:list[i].trims?.prints?list[i].trims:{prints:[]}, accessory:(newData && detailData.currentDetailType == 'accessory' && isCurrent && !detailData.isEditPattern.value)?{prints:newData}:list[i].trims?.prints?list[i].trims:{prints:[]}, } + if(!data.partialDesign.partialDesignMinioPath){ + data.partialDesign.partialDesignMinioPath = data.path + } data.printObject.prints = printObjectToJSON(data.printObject.prints) data.trims.prints = printObjectToJSON(data.trims.prints) if((detailData.isEditPattern.value && list[i].color?.gradient) || (!detailData.isEditPattern.value && (list[i].newDetail?.color?.gradient || list[i].color?.gradient))){ @@ -444,7 +447,7 @@ export default defineComponent({ sketchString:'', modelId:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.id:detailData.designDetail.oldModel?detailData.designDetail.oldModel.id:'', modelType:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.type:detailData.designDetail.oldModel?detailData.designDetail.oldModel.type:'', - designType:detailData.selectDetail.id?'merage':'default', + designType:detailData.selectDetail.id?'merge':'default', timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone, processId:userDetail.value.userId, probjectId:store.state.Workspace.probjects.id, @@ -479,7 +482,7 @@ export default defineComponent({ sketchString:'', modelId:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.id:detailData.designDetail.oldModel?detailData.designDetail.oldModel.id:'', modelType:(detailData.currentDetailType == 'models' && detailData.designDetail.newModel)?detailData.designDetail.newModel.type:detailData.designDetail.oldModel?detailData.designDetail.oldModel.type:'', - designType:detailData.selectDetail.id?'merage':'default', + designType:detailData.selectDetail.id?'merge':'default', timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone, processId:userDetail.value.userId, probjectId:store.state.Workspace.probjects.id, @@ -612,7 +615,7 @@ export default defineComponent({ if(detailDom.detailRight?.privewDetail)await (detailDom.detailRight as any).privewDetail() if(str == 'all'){ otherData = { - color: detailData.selectDetail.newDetail?.color.r?detailData.selectDetail.newDetail?.color:detailData.selectDetail.color, + color: detailData.selectDetail.newDetail?.color?.r?detailData.selectDetail.newDetail?.color:detailData.selectDetail.color, printObject: detailData.selectDetail.newDetail?.print?.length>0?{prints:detailData.selectDetail.newDetail?.print}:detailData.selectDetail.printObject || null, trims: detailData.selectDetail.newDetail?.element?.length>0?detailData.selectDetail.newDetail?.element:detailData.selectDetail.trims || null, } @@ -635,7 +638,7 @@ export default defineComponent({ } } if(detailData.currentDetailType == 'print'){ - otherData.printObject = {prints:detailData.selectDetail.newDetail?.print} + otherData.printObject = detailData.selectDetail.newDetail?.print?.length>0?{prints:detailData.selectDetail.newDetail?.print}:detailData.selectDetail.printObject || null } if(detailData.currentDetailType == 'element'){ otherData.trims = detailData.selectDetail.newDetail?.element From 226e183f5221360a2afd1ada7aeeb9fe470c9a72 Mon Sep 17 00:00:00 2001 From: X1627315083 <1627315083@qq.com> Date: Fri, 16 Jan 2026 14:57:48 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8D=B0=E8=8A=B1?= =?UTF-8?q?=E7=94=BB=E5=B8=83=E4=B8=8D=E5=90=8C=E6=AD=A5=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E5=92=8Coverall=E6=A8=A1=E5=BC=8F=E5=A4=A7=E5=B0=8F=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component/Detail/DesignDetail.vue | 23 ++----------------- .../Detail/detailRight/editPrintElement.vue | 3 ++- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/component/Detail/DesignDetail.vue b/src/component/Detail/DesignDetail.vue index f12d3154..c2f32f13 100644 --- a/src/component/Detail/DesignDetail.vue +++ b/src/component/Detail/DesignDetail.vue @@ -358,25 +358,6 @@ export default defineComponent({ detailData.selectDetail.maskUrl = '' detailData.selectDetail.maskMinioUrl = '' } - function isNetworkPath(str) { - if (typeof str !== 'string' || str.trim() === '') { - return false; - } - const urlPatterns = [ - /^https?:\/\//i, // http:// 或 https:// - /^ftp:\/\//i, // ftp:// - /^ws:\/\//i, // ws:// - /^wss:\/\//i, // wss:// - /^\/\//, // 协议相对路径 //example.com - /^data:/i, // data:image/png;base64,...(这是Base64,不是网络路径) - ]; - // 排除data:URL(这实际上是Base64) - if (str.startsWith('data:')) { - return false; - } - - return urlPatterns.some(pattern => pattern.test(str)); - } const printObjectToJSON = (list:any)=>{ if(list?.length > 0){ list.forEach((item:any)=>{ @@ -417,8 +398,8 @@ export default defineComponent({ if(!data.partialDesign.partialDesignMinioPath){ data.partialDesign.partialDesignMinioPath = data.path } - data.printObject.prints = printObjectToJSON(data.printObject.prints) - data.trims.prints = printObjectToJSON(data.trims.prints) + printObjectToJSON(data.printObject.prints) + printObjectToJSON(data.trims.prints) if((detailData.isEditPattern.value && list[i].color?.gradient) || (!detailData.isEditPattern.value && (list[i].newDetail?.color?.gradient || list[i].color?.gradient))){ gradient = list[i].newDetail?.color?.gradient || list[i].color.gradient gradient.colorImg = await setGradual(gradient,320,700) diff --git a/src/component/Detail/detailRight/editPrintElement.vue b/src/component/Detail/detailRight/editPrintElement.vue index 0ceee854..4b926521 100644 --- a/src/component/Detail/detailRight/editPrintElement.vue +++ b/src/component/Detail/detailRight/editPrintElement.vue @@ -277,7 +277,7 @@ export default defineComponent({ let x = Number(style.left.replace(/px/g,'')) let y = Number(style.top.replace(/px/g,'')) location = [(x*sketchWH[0]) ,(y*sketchWH[1])] - scale =[ editPrintElementData.systemDesignerPercentage/100, editPrintElementData.systemDesignerPercentage/100] + scale = item.scale // scale = [item.pattern.style.width/item.pattern.style.height,item.pattern.style.height/item.pattern.style.width] // location = [item.pattern.style.left,item.pattern.style.top] } @@ -911,6 +911,7 @@ export default defineComponent({ } const inputFillScale = (scale:any)=>{ let arr = editPrintElementData.printStyleList[props.type].overall + console.log(arr,scale,editPrintElementData.imgDomIndex) arr[editPrintElementData.imgDomIndex].scale = [scale,scale] editPrintElementDom.pingpuRef.updataList([ { From 618b9bab1ff5f8014fca9795f6c1051a5a359933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com> Date: Fri, 16 Jan 2026 15:16:33 +0800 Subject: [PATCH 3/6] fix --- .../CanvasEditor/managers/PartManager.js | 165 +++++++++++++----- .../Canvas/CanvasEditor/utils/objectHelper.js | 11 +- src/component/Canvas/canvasExample.vue | 4 +- 3 files changed, 128 insertions(+), 52 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/managers/PartManager.js b/src/component/Canvas/CanvasEditor/managers/PartManager.js index c13e25b0..aa193b8c 100644 --- a/src/component/Canvas/CanvasEditor/managers/PartManager.js +++ b/src/component/Canvas/CanvasEditor/managers/PartManager.js @@ -7,6 +7,9 @@ import addIcon from "@/assets/images/canvas/add.png"; import removeIcon from "@/assets/images/canvas/remove.png"; import { Https } from "@/tool/https"; import store from "@/store"; +import { createStaticCanvas } from "../utils/canvasFactory"; +import { getObjectAlphaToCanvas } from "../utils/objectHelper"; + /** * 部件选择管理器 @@ -55,11 +58,10 @@ export class PartManager { OperationType.PART_ERASER, ]; + this.pointList = []; // 存储点选坐标 + // 当前工具 this.activeTool = this.toolManager.activeTool; - - // 选区状态变化回调 - this.onSelectionChanged = null; } /** @@ -74,11 +76,12 @@ export class PartManager { // 如果从非选区工具切换到选区工具,初始化事件 if (!wasActive && this.isActive) { this.initEvents(); + this.createPartObject(); } // 如果从选区工具切换到非选区工具,清理事件和选区 else if (wasActive && !this.isActive) { this.cleanupEvents(); - this.clearSelection(); + this.clearPartObject(); } } @@ -211,31 +214,70 @@ export class PartManager { // 点选工具模式下点击事件处理 _pointDownkHandler(options) { - const button = options.button; - const isLeft = button === 1;// 左键1(添加) 右键3(删除) - const icon = `url("${isLeft ? addIcon : removeIcon}") 16 16, default` - this.canvas.upperCanvasEl.style.cursor = icon; + // const button = options.button; + // const isLeft = button === 1;// 左键1(添加) 右键3(删除) + // const icon = `url("${isLeft ? addIcon : removeIcon}") 16 16, default` + // this.canvas.upperCanvasEl.style.cursor = icon; } // 点选工具模式下移动事件处理 _pointMoveHandler(options) { } // 点选工具模式下抬起事件处理 - _pointUpHandler(options) { + async _pointUpHandler(options) { const button = options.button; const isLeft = button === 1;// 左键1(添加) 右键3(删除) this.canvas.upperCanvasEl.style.cursor = this.defaultCursor; const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); - const { x, y } = options.pointer; + const { x, y } = options.absolutePointer; const width = fixedObject.width * fixedObject.scaleX; const height = fixedObject.height * fixedObject.scaleY; - const X = x - (fixedObject.left - width / 2); - const Y = y - (fixedObject.top - height / 2); - this.getSegAnythingImage({ - image_path: "aida-users/24299/sketch/70bb39cc-63e0-44a9-a627-3542d0f9cd70.png", - type: "point", - points: [[X, Y], [X - 10, Y - 10]], - labels: [1, 0], + const X = (x - (fixedObject.left - width / 2)) / fixedObject.scaleX; + const Y = (y - (fixedObject.top - height / 2)) / fixedObject.scaleY; + const label = isLeft ? 1 : 0; + const points = []; + const labels = []; + this.pointList.forEach((item) => { + points.push([item.x, item.y]); + labels.push(item.label); }); + points.push([X, Y]); + labels.push(label); + const url = await this.getSegAnythingImage({ + image_path: this.props.clothingMinIOPath, + type: "point", + points, + labels, + // type: "box", + // box: [0,0,0,0], + }); + this.pointList.push({ + x: X, + y: Y, + label: label, + }) + const image1 = await this.loadImageToObject(url); + const group = this.partObject; + this.removeAllChildren(); + const rgba = { r: 0, g: 255, b: 0, a: 200 } + const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); + const image2 = new fabric.Image(canvas); + image2.set({ + originX: fixedObject.originX, + originY: fixedObject.originY, + }); + group.add(image2); + for (let i = 0; i < this.pointList.length; i++) { + const item = this.pointList[i]; + const icon = await this.loadImageToObject(item.label === 1 ? addIcon : removeIcon); + icon.set({ + left: item.x - group.width / 2, + top: item.y - group.height / 2, + originX: fixedObject.originX, + originY: fixedObject.originY, + }) + group.add(icon); + } + this.canvas.renderAll(); } @@ -278,38 +320,69 @@ export class PartManager { // 获取分隔后图片 async getSegAnythingImage(obj) { - const user_id = store.state.UserHabit.userDetail.userId; - const data = { - user_id, - ...obj, - } - Https.axiosPost(Https.httpUrls.segAnything, data) - .then(response => { - console.log(response); - }) - .catch(error => { - console.error(error); - }); - + return new Promise((resolve, reject) => { + // const user_id = store.state.UserHabit.userDetail.userId; + const user_id = 24299; + const data = { + user_id, + ...obj, + } + Https.axiosPost(Https.httpUrls.segAnything, data) + .then(response => { + if (response) { + resolve(response); + } else { + console.error("获取分隔后图片失败"); + } + }) + .catch(error => { + console.error(error); + }); + }); } - /** - * 清除选区 - */ - clearSelection() { - // 移除选区对象 - // this.removeSelectionFromCanvas(); + // 删除指定ID的对象 + removeObjectsById(id) { + const objects = this.canvas.getObjects().filter(obj => obj.id === id); + this.canvas.remove(...objects); + } + loadImageToObject(url) { + return new Promise((resolve, reject) => { + fabric.Image.fromURL(url, (img) => { + resolve(img); + }, { crossOrigin: "anonymous" });// 防止污染 + }); + } - // 重置选区状态 + removeAllChildren() { + this.partObject?._objects?.forEach(child => { + group.remove(child); + }); + } + createPartObject() { + const fixedObject = this.canvasManager.getFixedLayerObject(); + if (!fixedObject) return console.warn("未找到固定图层"); + const group = new fabric.Group(); + group.set({ + id: this.partId, + opacity: 1, + left: fixedObject.left, + top: fixedObject.top, + width: fixedObject.width, + height: fixedObject.height, + scaleX: fixedObject.scaleX, + scaleY: fixedObject.scaleY, + originX: fixedObject.originX, + originY: fixedObject.originY, + selectable: false, + evented: false, + }) + this.canvas.add(group); + this.partObject = group; + } + clearPartObject() { + this.removeObjectsById(this.partId); this.partObject = null; - this.partPath = null; - - // 触发选区变化回调 - if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") { - this.onSelectionChanged(); - } - - return true; } /** @@ -317,7 +390,7 @@ export class PartManager { */ dispose() { this.cleanupEvents(); - this.clearSelection(); + this.clearPartObject(); this.canvas = null; this.commandManager = null; this.layerManager = null; diff --git a/src/component/Canvas/CanvasEditor/utils/objectHelper.js b/src/component/Canvas/CanvasEditor/utils/objectHelper.js index 7042de27..8cf3b7ed 100644 --- a/src/component/Canvas/CanvasEditor/utils/objectHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/objectHelper.js @@ -64,9 +64,10 @@ export async function restoreFabricObject(serializedObject, canvas) { * @param {fabric.Object} object - 要处理的 fabric 对象 * @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明 * @param {number} diff - 差值,默认 25 + * @param {Object} rgba - 自定义 rgba 值,默认 { r: 255, g: 255, b: 255, a: 255 } * @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败 */ -export function getObjectAlphaToCanvas(object, revData, diff = 30) { +export function getObjectAlphaToCanvas(object, revData, diff = 30, rgba = { r: 255, g: 255, b: 255, a: 255 }) { const image = object.getElement(); const { width, height } = image; if (!width || !height) { @@ -95,10 +96,10 @@ export function getObjectAlphaToCanvas(object, revData, diff = 30) { data.data[i + 2] = 0; data.data[i + 3] = 0; } else { - data.data[i + 0] = 255; - data.data[i + 1] = 255; - data.data[i + 2] = 255; - data.data[i + 3] = 255; + data.data[i + 0] = rgba.r; + data.data[i + 1] = rgba.g; + data.data[i + 2] = rgba.b; + data.data[i + 3] = rgba.a; } } else { data.data[i + 0] = 0; diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index 945ae303..cef06618 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -8,7 +8,8 @@ // 当前显示的组件 const canvasEditor = ref(); const currentView = ref("canvasEditor"); // 默认显示红绿图示例 canvasEditor redGreenExample - + const clothingMinIOPath = + "aida-users/24299/sketchboard/female/Outwear/32d2485c-90fa-43d0-bc31-e59aaea466ba.png"; const clothingImageUrl = "/src/assets/images/canvas/xiangao.png"; const clothingImageUrlInit = "/src/assets/images/canvas/xiangaofenge.png"; @@ -404,6 +405,7 @@ ref="canvasEditor" key="canvasEditor" v-if="currentView === 'canvasEditor'" + :clothingMinIOPath="clothingMinIOPath" :clothingImageUrl="clothingImageUrl" :clothingImageUrl2="clothingImageUrlInit" :otherData="otherData" From 9b29939bfedbeb2f6b3f4d6fbab45a691693345f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com> Date: Fri, 16 Jan 2026 16:31:54 +0800 Subject: [PATCH 4/6] fix --- src/component/Canvas/CanvasEditor/index.vue | 1 + .../CanvasEditor/managers/CanvasManager.js | 37 ++++++++++++++----- .../Canvas/CanvasEditor/utils/helper.js | 17 +++++++++ src/component/Canvas/OverallCanvas/index.vue | 1 + 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 510fbf25..1b38c3c5 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -475,6 +475,7 @@ onMounted(async () => { await canvasManager?.createOtherLayers(props.otherData); await layerManager?.layerSort?.rearrangeObjects(); } + emit("canvas-load-json-success"); } diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index 9a6ccfdb..49042262 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -31,6 +31,7 @@ import { fillToCssStyle, calculateRotatedTopLeftDeg, calculateCenterPoint, + calculateTopLeftPoint, createPatternTransform, getTransformScaleAngle, base64ToCanvas, @@ -1140,9 +1141,9 @@ export class CanvasManager { scaleY: 0,//对象的缩放比例 opacity: v.opacity, angle: v.angle, - flipX: v.flipX,//是否水平翻转 - flipY: v.flipY,//是否垂直翻转 - blendMode: v.globalCompositeOperation,// 混合模式 + flipX: v.flipX, + flipY: v.flipY, + blendMode: v.globalCompositeOperation, gapX: 0,// 平铺模式下的间距 gapY: 0,// 平铺模式下的间距 } @@ -1151,18 +1152,24 @@ export class CanvasManager { let top = (v.top - (flTop - flHeight * flScaleY / 2)); let width = (v.width * v.scaleX); let height = (v.height * v.scaleY); - let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle); - let oX = (cx-width/2) / flScaleX; - let oY = (cy-height/2) / flScaleY; + if(v.originX === "center" && v.originY === "center") { + let {x:cx, y:cy} = calculateTopLeftPoint(width, height, left, top, v.angle); + left = cx; + top = cy; + } + let oX = left / flScaleX; + let oY = top / flScaleY; let oScaleX = (v.width * v.scaleX) / (flWidth * flScaleX); let oScaleY = (v.height * v.scaleY) / (flHeight * flScaleY); - // obj.object.width = width; - // obj.object.height = height; obj.object.top = oY; obj.object.left = oX; obj.object.scaleX = oScaleX; obj.object.scaleY = oScaleY; if(obj.ifSingle){ + // 单个的是从中心计算的 + let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle); + let oX = (cx-width/2) / flScaleX; + let oY = (cy-height/2) / flScaleY; obj.location = [oX, oY]; obj.scale = [oScaleX, oScaleY]; }else{ @@ -1647,6 +1654,9 @@ export class CanvasManager { let gapX = 0 let gapY = 0 let fillSource = image + let flipX = false; + let flipY = false; + let blendMode = BlendMode.MULTIPLY; if(item.object){ top += item.object.top * fixedLayerObj.scaleY left += item.object.left * fixedLayerObj.scaleX @@ -1654,6 +1664,9 @@ export class CanvasManager { scaleY *= item.object.scaleY opacity = item.object.opacity angle = item.object.angle + flipX = item.object.flipX + flipY = item.object.flipY + blendMode = item.object.blendMode || BlendMode.MULTIPLY; gapX = item.object.gapX gapY = item.object.gapY fillSource = imageAddGapToCanvas(image, gapX, gapY); @@ -1668,7 +1681,11 @@ export class CanvasManager { left: left, scaleX: scaleX, scaleY: scaleY, - globalCompositeOperation: BlendMode.MULTIPLY, + opacity: opacity, + angle: angle, + flipX: flipX, + flipY: flipY, + globalCompositeOperation: blendMode, fill: new fabric.Pattern({ source: fillSource, repeat: "repeat", @@ -1692,7 +1709,7 @@ export class CanvasManager { type: LayerType.BITMAP, visible: true, locked: true, - opacity: 1, + opacity: opacity, isPrintTrims: true, blendMode: BlendMode.MULTIPLY, fabricObjects: [rect.toObject(["id", "layerId", "layerName"])], diff --git a/src/component/Canvas/CanvasEditor/utils/helper.js b/src/component/Canvas/CanvasEditor/utils/helper.js index 3e47cd6c..f87bcd48 100644 --- a/src/component/Canvas/CanvasEditor/utils/helper.js +++ b/src/component/Canvas/CanvasEditor/utils/helper.js @@ -933,6 +933,23 @@ export function calculateCenterPoint(W, H, currentX, currentY, currentAngleDeg) const Cy = currentY + (W / 2) * sinCurrent + (H / 2) * cosCurrent; return { x: Cx, y: Cy }; } +/** + * 根据中心点坐标计算左上角坐标 + * @param {number} W - 宽度 + * @param {number} H - 高度 + * @param {number} centerX - 中心点x坐标 + * @param {number} centerY - 中心点y坐标 + * @param {number} currentAngleDeg - 当前角度(度) + * @returns {Object} 左上角坐标 {x, y} +*/ +export function calculateTopLeftPoint(W, H, centerX, centerY, currentAngleDeg) { + const currentAngle = (currentAngleDeg * Math.PI) / 180; + const cosCurrent = Math.cos(currentAngle); + const sinCurrent = Math.sin(currentAngle); + const Cx = centerX + (W / 2) * cosCurrent - (H / 2) * sinCurrent; + const Cy = centerY + (W / 2) * sinCurrent + (H / 2) * cosCurrent; + return { x: Cx, y: Cy }; +} diff --git a/src/component/Canvas/OverallCanvas/index.vue b/src/component/Canvas/OverallCanvas/index.vue index b8cc48a7..b6461491 100644 --- a/src/component/Canvas/OverallCanvas/index.vue +++ b/src/component/Canvas/OverallCanvas/index.vue @@ -194,6 +194,7 @@ left: item.object.left / (props.width / canvas.width), onDelete: (v) => onDeleteItem(v), }); + console.log("==========", props) canvas.add(rect); }; const setFill = async (item) => { From 6b0d26ed6e96e0a3b6e39d73f1184702786dfb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com> Date: Fri, 16 Jan 2026 16:42:53 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E9=9A=90=E8=97=8F=E7=94=BB=E5=B8=83?= =?UTF-8?q?=E6=97=B6=E5=80=99=E5=85=B3=E9=97=AD=E9=94=AE=E7=9B=98=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component/Canvas/CanvasEditor/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 1b38c3c5..86b5dda3 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -349,7 +349,7 @@ onMounted(async () => { }); // 绑定快捷键事件 - keyboardManager.init(); + if(!props.hideCanvas) keyboardManager.init(); // 绑定画布操作事件 canvasManager.setupCanvasEvents(activeElementId, layerManager); canvasManager.setupCanvasInitEvent(handleCanvasInit); // 绑定画布初始化事件 From 43e3a79124a3f056a268fb6b1595f131e3d1a021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com> Date: Mon, 19 Jan 2026 10:47:30 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E7=82=B9=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PartSelectorPanel.vue | 12 +- src/component/Canvas/CanvasEditor/index.vue | 6 + .../CanvasEditor/managers/PartManager.js | 137 ++++++++++++------ .../CanvasEditor/managers/ToolManager.js | 3 + .../Canvas/CanvasEditor/utils/helper.js | 29 +++- src/component/Canvas/test.vue | 6 +- 6 files changed, 138 insertions(+), 55 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue b/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue index c5ec0856..658d647f 100644 --- a/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue @@ -63,6 +63,10 @@ $t("Canvas.CreateAndCopy") }} +
+ + 清空当前点位 +
@@ -226,12 +230,16 @@ // 创建 function onCreate() { - + props.partManager.createPart(); } // 复制并创建 function onCopyCreate() { } + // 清空当前点位 + function onReset() { + props.partManager.clearPart(); + } @@ -410,7 +418,7 @@ .tool-actions { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 5px; padding: 0 30px; } diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 86b5dda3..237a8512 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -144,6 +144,7 @@ const props = defineProps({ }, }); +const loading = ref(false); // 引用和状态 const canvasRef = ref(null); const canvasContainerRef = shallowRef(null); @@ -277,6 +278,7 @@ onMounted(async () => { canvasManager.canvas.activeLayerId = activeLayerId; canvasManager.activeLayerId = activeLayerId; canvasManager.canvas.activeElementId = activeElementId; + canvasManager.canvas.loading = loading; // 创建命令管理器 commandManager = new CommandManager({ @@ -1392,6 +1394,7 @@ defineExpose({ />
+
@@ -1466,6 +1469,9 @@ defineExpose({ top: 0; bottom: 0; } +.app-container >.loading{ + position: absolute; +} .background-grid { --offsetX: 50%; diff --git a/src/component/Canvas/CanvasEditor/managers/PartManager.js b/src/component/Canvas/CanvasEditor/managers/PartManager.js index aa193b8c..6d593eac 100644 --- a/src/component/Canvas/CanvasEditor/managers/PartManager.js +++ b/src/component/Canvas/CanvasEditor/managers/PartManager.js @@ -1,5 +1,5 @@ import { fabric } from "fabric-with-all"; -import { generateId } from "../utils/helper"; +import { traceImageContour, imageToCanvas } from "../utils/helper"; import { OperationType } from "../utils/layerHelper"; import { CreateSelectionCommand } from "../commands/SelectionCommands"; import { ClearSelectionCommand } from "../commands/LassoCutoutCommand"; @@ -34,15 +34,6 @@ export class PartManager { // 状态 this.isActive = false; - this.partObject = null; // 当前选区对象 - this.partId = "part_selector"; - this.defaultCursor = "default"; - - // 绘制状态 - this.drawingObject = null; - this.startPoint = null; - this.partPath = null; // 存储选区路径数据 - // 不再直接绑定事件处理函数 this._mouseDownHandler = null; @@ -58,10 +49,15 @@ export class PartManager { OperationType.PART_ERASER, ]; - this.pointList = []; // 存储点选坐标 // 当前工具 this.activeTool = this.toolManager.activeTool; + + this.partGroup = null; // 当前选区对象 + this.partId = "part_selector"; + this.partCanvas = null;// 选区画布 + // 点选工具相关 + this.pointList = []; // 存储点选坐标 } /** @@ -82,15 +78,13 @@ export class PartManager { else if (wasActive && !this.isActive) { this.cleanupEvents(); this.clearPartObject(); + this.clearPointData(); } } - /** - * 初始化选区相关事件 - */ + /** 初始化选区相关事件 */ initEvents() { if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化 - this.defaultCursor = this.canvas.defaultCursor; // 保存实例引用,用于事件处理函数中 const self = this; @@ -187,9 +181,7 @@ export class PartManager { document.addEventListener("keydown", this._keyDownHandler); } - /** - * 清理事件监听 - */ + /** 清理事件监听 */ cleanupEvents() { if (!this.canvas) return; @@ -212,20 +204,19 @@ export class PartManager { } } - // 点选工具模式下点击事件处理 + /** 点选工具模式下点击事件处理 */ _pointDownkHandler(options) { // const button = options.button; // const isLeft = button === 1;// 左键1(添加) 右键3(删除) // const icon = `url("${isLeft ? addIcon : removeIcon}") 16 16, default` // this.canvas.upperCanvasEl.style.cursor = icon; } - // 点选工具模式下移动事件处理 + /** 点选工具模式下移动事件处理 */ _pointMoveHandler(options) { } - // 点选工具模式下抬起事件处理 + /** 点选工具模式下抬起事件处理 */ async _pointUpHandler(options) { const button = options.button; const isLeft = button === 1;// 左键1(添加) 右键3(删除) - this.canvas.upperCanvasEl.style.cursor = this.defaultCursor; const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const { x, y } = options.absolutePointer; @@ -256,10 +247,11 @@ export class PartManager { label: label, }) const image1 = await this.loadImageToObject(url); - const group = this.partObject; - this.removeAllChildren(); + this.resetPartObject(); + const group = this.partGroup; const rgba = { r: 0, g: 255, b: 0, a: 200 } const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); + this.partCanvas = canvas; const image2 = new fabric.Image(canvas); image2.set({ originX: fixedObject.originX, @@ -279,47 +271,57 @@ export class PartManager { } this.canvas.renderAll(); } - - - // 框选工具模式下点击事件处理 - _rectangleDownHandler(options) { + /** 清空点选数据 */ + clearPointData() { + this.pointList = []; + this.partCanvas = null; } - // 框选工具模式下移动事件处理 + + + /** 框选工具模式下点击事件处理 */ + _rectangleDownHandler(options) { + console.log(options.absolutePointer); + } + /** 框选工具模式下移动事件处理 */ _rectangleMoveHandler(options) { } - // 框选工具模式下抬起事件处理 + /** 框选工具模式下抬起事件处理 */ _rectangleUpHandler(options) { + console.log(options.absolutePointer); } - // 绘制工具模式下点击事件处理 + /** 绘制工具模式下点击事件处理 */ _brushDownHandler(options) { } - // 绘制工具模式下移动事件处理 + /** 绘制工具模式下移动事件处理 */ _brushMoveHandler(options) { } - // 绘制工具模式下抬起事件处理 + /** 绘制工具模式下抬起事件处理 */ _brushUpHandler(options) { } - // 擦除工具模式下抬起事件处理 + /** 擦除工具模式下抬起事件处理 */ _eraseUpHandler(options) { } - // 擦除工具模式下点击事件处理 + /** 擦除工具模式下点击事件处理 */ _eraseDownHandler(options) { } - // 擦除工具模式下移动事件处理 + /** 擦除工具模式下移动事件处理 */ _eraseMoveHandler(options) { } - // 获取分隔后图片 + /** 获取分隔后图片 */ async getSegAnythingImage(obj) { + setTimeout(() => { + this.canvas.loading.value = true; + }); return new Promise((resolve, reject) => { // const user_id = store.state.UserHabit.userDetail.userId; const user_id = 24299; @@ -329,6 +331,7 @@ export class PartManager { } Https.axiosPost(Https.httpUrls.segAnything, data) .then(response => { + this.canvas.loading.value = false; if (response) { resolve(response); } else { @@ -336,12 +339,13 @@ export class PartManager { } }) .catch(error => { + this.canvas.loading.value = false; console.error(error); }); }); } - // 删除指定ID的对象 + /** 删除指定ID的对象 */ removeObjectsById(id) { const objects = this.canvas.getObjects().filter(obj => obj.id === id); this.canvas.remove(...objects); @@ -354,10 +358,11 @@ export class PartManager { }); } - removeAllChildren() { - this.partObject?._objects?.forEach(child => { - group.remove(child); - }); + /** 重置点位对象组 */ + resetPartObject(render = false) { + this.clearPartObject(); + this.createPartObject(); + if (render) this.canvas.renderAll(); } createPartObject() { const fixedObject = this.canvasManager.getFixedLayerObject(); @@ -378,11 +383,53 @@ export class PartManager { evented: false, }) this.canvas.add(group); - this.partObject = group; + this.partGroup = group; } + /** 清空点位对象组 */ clearPartObject() { this.removeObjectsById(this.partId); - this.partObject = null; + this.partGroup = null; + } + + /** 创建当前选区 */ + createPart() { + if (!this.partCanvas) return console.warn("没有点位画布"); + const fixedObject = this.canvasManager.getFixedLayerObject(); + if (!fixedObject) return console.warn("未找到固定图层"); + const scaleY = fixedObject.scaleY + const scaleX = fixedObject.scaleX + const top = fixedObject.top - fixedObject.height * scaleY / 2; + const left = fixedObject.left - fixedObject.width * scaleX / 2; + const arr = traceImageContour(this.partCanvas); + let minX = fixedObject.width; + let minY = fixedObject.height; + const str = arr.map((v) => { + if (v.x < minX) minX = v.x; + if (v.y < minY) minY = v.y; + return `${v.x} ${v.y}` + }).join(" L "); + const path = new fabric.Path(`M ${str} z`); + path.set({ + left: left + minX * scaleX, + top: top + minY * scaleY, + scaleX: scaleX, + scaleY: scaleY, + fill: "rgba(127, 255, 127, 0.3)", + stroke: "#2AA81B", + strokeWidth: 2, + strokeDashArray: [8, 4], + strokeLineCap: "round",// 折线端点样式 + strokeLineJoin: "bevel", // 折线连接样式 + strokeUniform: true, // 保持描边宽度不随缩放改变 + }); + // this.partGroup.add(path); + this.canvas.add(path); + this.canvas.renderAll(); + } + /** 清空点位 */ + clearPart() { + this.pointList = []; + this.resetPartObject(true); } /** @@ -391,6 +438,8 @@ export class PartManager { dispose() { this.cleanupEvents(); this.clearPartObject(); + this.clearPointData(); + this.canvas = null; this.commandManager = null; this.layerManager = null; diff --git a/src/component/Canvas/CanvasEditor/managers/ToolManager.js b/src/component/Canvas/CanvasEditor/managers/ToolManager.js index 4046d25d..3b34ec84 100644 --- a/src/component/Canvas/CanvasEditor/managers/ToolManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ToolManager.js @@ -429,6 +429,9 @@ export class ToolManager { if (this.canvasManager && this.canvasManager.selectionManager) { this.canvasManager.selectionManager.setCurrentTool(toolId); } + if (this.canvasManager && this.canvasManager.partManager) { + this.canvasManager.partManager.setCurrentTool(toolId); + } // 通知观察者 this.notifyObservers(toolId); diff --git a/src/component/Canvas/CanvasEditor/utils/helper.js b/src/component/Canvas/CanvasEditor/utils/helper.js index f87bcd48..63e2af8d 100644 --- a/src/component/Canvas/CanvasEditor/utils/helper.js +++ b/src/component/Canvas/CanvasEditor/utils/helper.js @@ -995,13 +995,13 @@ export function getTransformScaleAngle(Transform) { } /** - * 图片转换为canvas + * base64转换为canvas * @param {String} base64 - 图片base64编码 * @param {Number} scale - 缩放比例 * @param {Boolean} sr - 缩放反转,默认false * @returns {Promise} canvas元素 */ -export async function base64ToCanvas(base64, scale = 1, sr = false) { +export function base64ToCanvas(base64, scale = 1, sr = false) { return new Promise((resolve, reject) => { const image = new Image(); image.src = base64; @@ -1014,7 +1014,7 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) { const height = sr ? image.height / scale : image.height * scale; canvas.width = width; canvas.height = height; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.clearRect(0, 0, width, height); ctx.drawImage(image, 0, 0, width, height); resolve(canvas); @@ -1022,6 +1022,25 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) { image.onerror = reject; }); } +/** + * image转换为canvas + * @param {HTMLImageElement} image - 图片元素 + * @param {Number} scale - 缩放比例 + * @param {Boolean} sr - 缩放反转,默认false + * @returns {Promise} canvas元素 +*/ +export async function imageToCanvas(image, scale = 1, sr = false) { + await image.decode(); + const canvas = document.createElement('canvas'); + const width = (sr ? image.width / scale : image.width * scale); + const height = sr ? image.height / scale : image.height * scale; + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + ctx.clearRect(0, 0, width, height); + ctx.drawImage(image, 0, 0, width, height); + return resolve(canvas); +} /** * 图片边界跟踪算法(透明底) @@ -1029,7 +1048,7 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) { * @returns {Array} 边界点数组 [{x, y}, ...] */ export function traceImageContour(canvas) { - const ctx = canvas.getContext("2d"); + const ctx = canvas.getContext("2d", { willReadFrequently: true }); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; const width = canvas.width; @@ -1124,7 +1143,7 @@ export function imageAddGapToCanvas(image, gapX, gapY) { const tcanvas = document.createElement('canvas'); tcanvas.width = image.width + gapX; tcanvas.height = image.height + gapY; - const ctx = tcanvas.getContext('2d'); + const ctx = tcanvas.getContext('2d', { willReadFrequently: true }); ctx.clearRect(0, 0, tcanvas.width, tcanvas.height); ctx.drawImage(image, 0, 0); return tcanvas; diff --git a/src/component/Canvas/test.vue b/src/component/Canvas/test.vue index 1433898e..bf297908 100644 --- a/src/component/Canvas/test.vue +++ b/src/component/Canvas/test.vue @@ -1,11 +1,9 @@