From 026d564c9235e3fc8b7175624e4696e9ef5f456b Mon Sep 17 00:00:00 2001 From: 24kycj <1637269896@qq.com> Date: Wed, 10 Dec 2025 01:40:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B0=83=E6=95=B4=E8=BE=85?= =?UTF-8?q?=E5=8A=A9=E7=BA=BF=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design1.html | 1767 +++++++++++++++++++++-------------------- design2.html | 33 +- lib/design1/core.js | 98 ++- lib/design1/output.js | 108 ++- lib/design2/core.js | 277 ++++++- lib/design2/output.js | 98 ++- lib/design2/ui.js | 60 ++ package.json | 115 ++- 8 files changed, 1591 insertions(+), 965 deletions(-) diff --git a/design1.html b/design1.html index ae3f866..ba28595 100644 --- a/design1.html +++ b/design1.html @@ -1,884 +1,885 @@ - - - - - - - Soon Design - - - - - - - - - - - - - - - - - + + + + + + + Soon Design + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/design2.html b/design2.html index 9d8c1e9..72cf2f3 100644 --- a/design2.html +++ b/design2.html @@ -21,9 +21,12 @@ top: 0; left: 0; height: 20px; - background-color: #2C2C2C; - border-bottom: 1px solid #1E1E1E; + /* 改为更明显的颜色,不透明度提高到 0.9 */ + background-color: rgba(44, 44, 44, 0.9) !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); z-index: 1000; + /* 鼠标移上去显示上下调整箭头,提示可拖拽 */ + cursor: row-resize; } .ruler-vertical { @@ -31,9 +34,12 @@ top: 0; left: 0; width: 20px; - background-color: #2C2C2C; - border-right: 1px solid #1E1E1E; + /* 改为更明显的颜色,不透明度提高到 0.9 */ + background-color: rgba(44, 44, 44, 0.9) !important; + border-right: 1px solid rgba(255, 255, 255, 0.2); z-index: 1000; + /* 鼠标移上去显示左右调整箭头 */ + cursor: col-resize; } .ruler-corner { @@ -42,15 +48,28 @@ left: 0; width: 20px; height: 20px; - background-color: #2C2C2C; - border-right: 1px solid #1E1E1E; - border-bottom: 1px solid #1E1E1E; + background-color: rgba(44, 44, 44, 0.9) !important; + border-right: 1px solid rgba(255, 255, 255, 0.2); + border-bottom: 1px solid rgba(255, 255, 255, 0.2); z-index: 1001; } .canvas-wrapper { position: relative; } + + /* 拖拽过程中的辅助线样式 */ + .guide-line-drag { + position: absolute; + background-color: #0066FF; + /* 深蓝色高亮 */ + z-index: 9999; + pointer-events: none; + /* 让鼠标穿透,不影响拖拽事件 */ + box-shadow: 0 0 6px #0066FF; + /* 增强发光效果 */ + opacity: 0.9; + } diff --git a/lib/design1/core.js b/lib/design1/core.js index 90bccde..7b5e0d6 100644 --- a/lib/design1/core.js +++ b/lib/design1/core.js @@ -2305,6 +2305,11 @@ function initCanvasEvents() { let idx = getIndex(event.target); if (idx <= 0) return; let objData = objs[idx - 1]; + // 安全检查:确保 objData 存在且有 type 属性 + if (!objData || typeof objData.type === 'undefined') { + console.warn('objData is undefined or missing type at index', idx - 1); + return; + } let target = event.target; if ([3, 4, 10].includes(objData.type)) { @@ -2327,10 +2332,10 @@ function initCanvasEvents() { // 8. 【完美修复版】渲染后绘制:外部蒙版 + 中心圆孔 (修复三角形Bug) // ================================================== currentCanvas.on('after:render', function () { + const ctx = currentCanvas.getContext(); let bgObj = (currentCanvas === canvas1) ? background_image1 : background_image2; if (bgObj) { - const ctx = currentCanvas.getContext(); const width = currentCanvas.width; const height = currentCanvas.height; @@ -2378,6 +2383,32 @@ function initCanvasEvents() { ctx.fill(); ctx.restore(); } + + // 在遮罩之后重新绘制辅助线,确保辅助线在最上层 + const objects = currentCanvas.getObjects(); + objects.forEach(obj => { + if (obj.isGuideLine) { + ctx.save(); + // 应用对象的变换矩阵 + const m = obj.calcTransformMatrix(); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + + // 设置透明度 + ctx.globalAlpha = obj.opacity || 1; + + // 应用阴影 + if (obj.shadow) { + ctx.shadowColor = obj.shadow.color; + ctx.shadowBlur = obj.shadow.blur; + ctx.shadowOffsetX = obj.shadow.offsetX; + ctx.shadowOffsetY = obj.shadow.offsetY; + } + + // 使用 Fabric 对象的内置渲染方法 + obj._render(ctx); + ctx.restore(); + } + }); }); }); } @@ -2469,30 +2500,54 @@ function initRulers() { // --- 2. 在画布中创建辅助线 --- function createFabricGuide(type, domX, domY) { - // 计算画布内的逻辑坐标 (减去 margin 偏移,除以缩放) - let boxOffset = canvasBox.offset(); - let wrapperOffset = canvasWrapper.offset(); - - // 计算相对于 canvasBox 的像素位置 - let relX = domX - (boxOffset.left - wrapperOffset.left); - let relY = domY - (boxOffset.top - wrapperOffset.top); - + // domX 和 domY 是相对于 canvas-wrapper 的坐标 + // 需要转换为相对于 canvas_box 的坐标,然后再转换为 Fabric 画布坐标 + + // 获取 canvas_box 相对于 canvas-wrapper 的位置 + let boxPosition = canvasBox.position(); + let boxMarginLeft = parseFloat(canvasBox.css('margin-left')) || 0; + let boxMarginTop = parseFloat(canvasBox.css('margin-top')) || 0; + + // 计算相对于 canvas_box 的坐标(减去 box 在 wrapper 中的位置和 margin) + let relX = domX - boxPosition.left - boxMarginLeft; + let relY = domY - boxPosition.top - boxMarginTop; + // 转为 Fabric 坐标 (除以 zoom) let canvasX = relX / zoom; let canvasY = relY / zoom; - // 构造线对象 + // 获取画布的实际尺寸和可见区域尺寸 + let canvasWidth = canvas.width || 10000; + let canvasHeight = canvas.height || 10000; + // 获取可见区域的尺寸(考虑缩放) + let visibleWidth = canvasWrapper.width() || canvasWidth; + let visibleHeight = canvasWrapper.height() || canvasHeight; + // 使用足够大的固定值,确保辅助线能够覆盖整个画布和可见区域 + // 使用 100000 确保无论画布多大、如何缩放,都能覆盖整个可见区域 + let lineExtent = 100000; + + // 构造线对象,使用足够大的范围确保覆盖整个画布 + // 线的起点和终点要足够远,确保覆盖整个可见区域 let linePoints = []; if (type === 'horizontal') { - linePoints = [-5000, canvasY, 5000, canvasY]; + // 水平线:从很远的左边到很远的右边 + linePoints = [-lineExtent, canvasY, lineExtent, canvasY]; } else { - linePoints = [canvasX, -5000, canvasX, 5000]; + // 垂直线:从很远的上面到很远的下面 + linePoints = [canvasX, -lineExtent, canvasX, lineExtent]; } let guideLine = new fabric.Line(linePoints, { - stroke: '#00FFFF', + stroke: '#0066FF', strokeWidth: 1 / zoom, - strokeDashArray: [5, 5], + opacity: 0.9, + shadow: { + color: '#0066FF', + blur: 6, + offsetX: 0, + offsetY: 0, + affectStroke: true + }, selectable: true, evented: true, hasControls: false, @@ -2501,6 +2556,9 @@ function initRulers() { lockScalingX: true, lockScalingY: true, hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize', + perPixelTargetFind: false, // 使用边界框检测而不是像素检测 + targetFindTolerance: 10, // 增加点击容差,扩大可点击区域(像素) + padding: 10, // 增加选择边界的内边距 isGuideLine: true, ignoreSave: true }); @@ -2508,9 +2566,9 @@ function initRulers() { // 限制移动方向 guideLine.on('moving', function (e) { if (type === 'horizontal') { - this.left = -5000; + this.left = -lineExtent; } else { - this.top = -5000; + this.top = -lineExtent; } }); @@ -2558,14 +2616,14 @@ function initRulers() { function drawHorizontal() { ctxH.clearRect(0, 0, currentCanvasWidth, rulerHeight); - ctxH.strokeStyle = '#999'; + ctxH.strokeStyle = '#666'; ctxH.lineWidth = 1; ctxH.beginPath(); ctxH.moveTo(0, 19); ctxH.lineTo(currentCanvasWidth, 19); ctxH.stroke(); - ctxH.fillStyle = '#CCC'; + ctxH.fillStyle = '#888'; ctxH.font = '9px Arial'; ctxH.textAlign = 'left'; ctxH.textBaseline = 'top'; @@ -2626,14 +2684,14 @@ function initRulers() { function drawVertical() { ctxV.clearRect(0, 0, rulerWidth, currentCanvasHeight); - ctxV.strokeStyle = '#999'; + ctxV.strokeStyle = '#666'; ctxV.lineWidth = 1; ctxV.beginPath(); ctxV.moveTo(19, 0); ctxV.lineTo(19, currentCanvasHeight); ctxV.stroke(); - ctxV.fillStyle = '#CCC'; + ctxV.fillStyle = '#888'; ctxV.font = '9px Arial'; ctxV.textAlign = 'center'; ctxV.textBaseline = 'middle'; diff --git a/lib/design1/output.js b/lib/design1/output.js index a7f1594..2e3e51d 100644 --- a/lib/design1/output.js +++ b/lib/design1/output.js @@ -14,7 +14,8 @@ function display_func(img1, img2, img3) { $("#component_type").text(language_str("bg"));//"背景" canvas1.discardActiveObject().renderAll(); canvas2.discardActiveObject().renderAll(); - let _objs1 = canvas1.getObjects(); + // 过滤掉辅助线 + let _objs1 = canvas1.getObjects().filter(obj => !obj.isGuideLine); let g1 = []; let g3 = []; let g5 = []; @@ -24,9 +25,16 @@ function display_func(img1, img2, img3) { let black_top = 0;//黑色顶裁剪 let all_left = 0;//预览图左裁剪 let all_top = 0;//预览图顶裁剪 - let list1 = [..._objs1] + // 过滤掉辅助线 + let list1 = _objs1.filter(obj => !obj.isGuideLine); for (let item of list1) { let idx = getIndex(item, canvas1); + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue; + } + //console.log("------"); //console.log(item.getCoords()); //console.log(getCoordsMinX(background_image.getCoords())); @@ -149,7 +157,8 @@ function display_func(img1, img2, img3) { top: black_top, left: black_left }); - let _objs2 = canvas2.getObjects(); + // 过滤掉辅助线 + let _objs2 = canvas2.getObjects().filter(obj => !obj.isGuideLine); let g2 = []; let g4 = []; let g6 = []; @@ -160,7 +169,14 @@ function display_func(img1, img2, img3) { all_top = 0; all_left = 0; for (let item of _objs2) { + let idx = getIndex(item, canvas2); + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue; + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); } @@ -362,7 +378,9 @@ function output(callback = null, _save = save) { let black_top = 0;//黑色顶裁剪 let all_left = 0;//预览图左裁剪 let all_top = 0;//预览图顶裁剪 - for (let item of _objs1) { + // 过滤掉辅助线 + let filteredObjs1 = _objs1.filter(obj => !obj.isGuideLine); + for (let item of filteredObjs1) { let idx = getIndex(item, canvas1); if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { @@ -372,6 +390,12 @@ function output(callback = null, _save = save) { all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); } g3.push(fabric.util.object.clone(item)); + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue; + } + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { continue;//计数器 或 合成文字 } @@ -485,8 +509,16 @@ function output(callback = null, _save = save) { black_top = 0;//黑色顶裁剪 all_left = 0;//黑色左裁剪 all_top = 0;//黑色顶裁剪 - for (let item of _objs2) { + // 过滤掉辅助线 + let filteredObjs2 = _objs2.filter(obj => !obj.isGuideLine); + for (let item of filteredObjs2) { let idx = getIndex(item, canvas2); + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue; + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); } @@ -635,8 +667,19 @@ function output(callback = null, _save = save) { op.frontBlackPic = ''; op.backBlackPic = ''; for (let item of _objs1) { + // 跳过辅助线 + if (item.isGuideLine) { + continue; + } + let idx = getIndex(item, canvas1); if (idx == 0) continue;//background-image + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined') { + continue; + } + let type = objs1[idx - 1].type; if (type == 2) { //out_pic @@ -750,8 +793,19 @@ function output(callback = null, _save = save) { } //obj2 for (let item of _objs2) { + // 跳过辅助线 + if (item.isGuideLine) { + continue; + } + let idx = getIndex(item, canvas2); if (idx == 0) continue;//background-image + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined') { + continue; + } + let type = objs2[idx - 1].type; if (type == 2) { //out_pic @@ -941,9 +995,31 @@ function cleanupSrcFields(jsonObjects, objsArray) { } function saveAs(op1, callback) { + // 临时移除辅助线,保存后再恢复 + let guideLines1 = []; + let guideLines2 = []; + canvas1.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines1.push(obj); + canvas1.remove(obj); + } + }); + canvas2.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines2.push(obj); + canvas2.remove(obj); + } + }); + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + // 恢复辅助线 + guideLines1.forEach(obj => canvas1.add(obj)); + guideLines2.forEach(obj => canvas2.add(obj)); + canvas1.renderAll(); + canvas2.renderAll(); + // 清理不需要的 src 字段 cleanupSrcFields(j.objects, objs1); cleanupSrcFields(b.objects, objs2); @@ -975,9 +1051,31 @@ function saveAs(op1, callback) { } function save(op1, callback) { + // 临时移除辅助线,保存后再恢复 + let guideLines1 = []; + let guideLines2 = []; + canvas1.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines1.push(obj); + canvas1.remove(obj); + } + }); + canvas2.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines2.push(obj); + canvas2.remove(obj); + } + }); + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + // 恢复辅助线 + guideLines1.forEach(obj => canvas1.add(obj)); + guideLines2.forEach(obj => canvas2.add(obj)); + canvas1.renderAll(); + canvas2.renderAll(); + // 清理不需要的 src 字段 cleanupSrcFields(j.objects, objs1); cleanupSrcFields(b.objects, objs2); diff --git a/lib/design2/core.js b/lib/design2/core.js index 7f63325..501f90a 100644 --- a/lib/design2/core.js +++ b/lib/design2/core.js @@ -301,11 +301,196 @@ function initRulers() { canvas1.on('after:render', function () { drawHorizontalRuler(); drawVerticalRuler(); + // 重新绘制辅助线(确保在遮罩之上) + const ctx = canvas1.getContext(); + const objects = canvas1.getObjects(); + objects.forEach(obj => { + if (obj.isGuideLine) { + ctx.save(); + const m = obj.calcTransformMatrix(); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + ctx.globalAlpha = obj.opacity || 1; + if (obj.shadow) { + ctx.shadowColor = obj.shadow.color; + ctx.shadowBlur = obj.shadow.blur; + ctx.shadowOffsetX = obj.shadow.offsetX; + ctx.shadowOffsetY = obj.shadow.offsetY; + } + obj._render(ctx); + ctx.restore(); + } + }); }); canvas2.on('after:render', function () { drawHorizontalRuler(); drawVerticalRuler(); + // 重新绘制辅助线(确保在遮罩之上) + const ctx = canvas2.getContext(); + const objects = canvas2.getObjects(); + objects.forEach(obj => { + if (obj.isGuideLine) { + ctx.save(); + const m = obj.calcTransformMatrix(); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + ctx.globalAlpha = obj.opacity || 1; + if (obj.shadow) { + ctx.shadowColor = obj.shadow.color; + ctx.shadowBlur = obj.shadow.blur; + ctx.shadowOffsetX = obj.shadow.offsetX; + ctx.shadowOffsetY = obj.shadow.offsetY; + } + obj._render(ctx); + ctx.restore(); + } + }); }); + + // --- 辅助线功能 (参考 design1) --- + // canvasWrapper 已在上面声明,直接使用 + const canvasBox = $('#canvas-div'); // design2 使用 canvas-div 作为容器 + + // 确保容器定位 + if (canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative'); + + // 定义临时拖拽线 DOM + let $dragLine = null; + + // 通用拖拽开始函数 + const startDragGuide = (type, e) => { + e.preventDefault(); + e.stopPropagation(); + + // 创建临时显示的线 + $dragLine = $('
'); + canvasWrapper.append($dragLine); + + if (type === 'horizontal') { + // 从上往下拖 (水平线) + $dragLine.css({ + left: 0, width: '100%', height: '1px', top: e.offsetY + }); + } else { + // 从左往右拖 (垂直线) + $dragLine.css({ + top: 0, height: '100%', width: '1px', left: e.offsetX + }); + } + + // 绑定 document 移动事件 + $(document).on('mousemove.ruler', function (moveEvent) { + let wrapperOffset = canvasWrapper.offset(); + if (type === 'horizontal') { + let relY = moveEvent.pageY - wrapperOffset.top; + $dragLine.css('top', relY + 'px'); + } else { + let relX = moveEvent.pageX - wrapperOffset.left; + $dragLine.css('left', relX + 'px'); + } + }); + + // 绑定松开事件 (创建真实的 Fabric 线) + $(document).on('mouseup.ruler', function (upEvent) { + $(document).off('mousemove.ruler mouseup.ruler'); + + if (!$dragLine) return; + + // 获取最终位置 + let wrapperOffset = canvasWrapper.offset(); + let finalX = upEvent.pageX - wrapperOffset.left; + let finalY = upEvent.pageY - wrapperOffset.top; + + // 移除临时线 + $dragLine.remove(); + $dragLine = null; + + // 只有拖入画布区域才创建 + if (finalX > rulerWidth && finalY > rulerHeight) { + createFabricGuide(type, finalX, finalY); + } + }); + }; + + // 绑定到标尺 DOM + $('#ruler-h').off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e)); + $('#ruler-v').off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e)); + + // --- 在画布中创建辅助线 --- + function createFabricGuide(type, domX, domY) { + // domX 和 domY 是相对于 canvas-wrapper 的坐标 + // 需要转换为相对于 canvas 的坐标,然后再转换为 Fabric 画布坐标 + + // 获取 canvas 相对于 canvas-wrapper 的位置 + let canvas1El = $('#canvas1'); + let canvasPosition = canvas1El.position(); + let canvasMarginLeft = parseFloat(canvas1El.css('margin-left')) || 0; + let canvasMarginTop = parseFloat(canvas1El.css('margin-top')) || 0; + + // 计算相对于 canvas 的坐标(减去 canvas 在 wrapper 中的位置和 margin) + let relX = domX - canvasPosition.left - canvasMarginLeft; + let relY = domY - canvasPosition.top - canvasMarginTop; + + // 转为 Fabric 坐标 (除以 zoom) + let canvasX = relX / zoom; + let canvasY = relY / zoom; + + // 使用足够大的固定值,确保辅助线能够覆盖整个画布和可见区域 + let lineExtent = 100000; + + // 构造线对象 + let linePoints = []; + if (type === 'horizontal') { + linePoints = [-lineExtent, canvasY, lineExtent, canvasY]; + } else { + linePoints = [canvasX, -lineExtent, canvasX, lineExtent]; + } + + let guideLine = new fabric.Line(linePoints, { + stroke: '#0066FF', + strokeWidth: 1 / zoom, + opacity: 0.9, + shadow: { + color: '#0066FF', + blur: 6, + offsetX: 0, + offsetY: 0, + affectStroke: true + }, + selectable: true, + evented: true, + hasControls: false, + hasBorders: false, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize', + perPixelTargetFind: false, + targetFindTolerance: 10, + padding: 10, + isGuideLine: true, + ignoreSave: true + }); + + // 限制移动方向 + guideLine.on('moving', function (e) { + if (type === 'horizontal') { + this.left = -lineExtent; + } else { + this.top = -lineExtent; + } + }); + + // 确定应该添加到哪个 canvas(使用当前活动的 canvas) + let targetCanvas = canvas; + if (!targetCanvas || (targetCanvas !== canvas1 && targetCanvas !== canvas2)) { + // 如果没有活动的 canvas,默认使用 canvas1 + targetCanvas = canvas1; + } + + targetCanvas.add(guideLine); + targetCanvas.setActiveObject(guideLine); + targetCanvas.renderAll(); + } + } initRulers(); $('#source_front').width(zoom * $('#source_front').width()) @@ -705,14 +890,20 @@ function handleObjectSelected(e) { return; } - // 2. 获取索引 + // 2. 如果是辅助线,直接返回,不处理(在获取索引之前检查) + if (select_obj.isGuideLine) { + return; + } + + // 3. 获取索引 let i = getIndex(select_obj, canvas); if (i != 0) { // --- 选中了普通对象 --- --i; // 转换为 objs 数组下标 - if (objs[i]) { + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (objs[i] && typeof objs[i].type !== 'undefined') { switch (objs[i].type) { case 1: // 图片 $("#component_type").text(language_str("img")); @@ -764,9 +955,15 @@ function handleObjectSelected(e) { function handleObjectMoving(e) { let obj = e.target; + // 跳过辅助线 + if (obj.isGuideLine) { + updateControls(); + return; + } let idx = getIndex(obj, canvas); // 圆形文本特殊处理 - if (idx > 0 && idx <= objs.length && objs[idx - 1] && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { + // 安全检查:确保 objs[idx - 1] 存在且有 type 属性 + if (idx > 0 && idx <= objs.length && objs[idx - 1] && typeof objs[idx - 1].type !== 'undefined' && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { let pointer = { x: obj.left, y: obj.top }; let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2); let angleDeg = getDeg(pointer); @@ -865,9 +1062,18 @@ function initCanvasEvents() { // 7. 对象修改后 currentCanvas.on('object:modified', function (event) { if (currentCanvas.getActiveObjects().length > 1) return; + // 跳过辅助线 + if (event.target.isGuideLine) { + return; + } let idx = getIndex(event.target, currentCanvas); if (idx <= 0) return; let objData = objs[idx - 1]; + // 安全检查:确保 objData 存在且有 type 属性 + if (!objData || typeof objData.type === 'undefined') { + console.warn('objData is undefined or missing type at index', idx - 1); + return; + } let target = event.target; if ([3, 4, 10].includes(objData.type)) { @@ -1011,14 +1217,23 @@ function getIndex(target, _canvas = null) { } function getType(target) { + // 如果是辅助线,返回 null + if (target.isGuideLine) { + return null; + } let temp_objs = canvas.getObjects(); let i = 0; for (let res of temp_objs) { if (target == res) { + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (!objs[i] || typeof objs[i].type === 'undefined') { + return null; + } return objs[i].type; } i++; } + return null; } function checkName(name) { @@ -1658,7 +1873,8 @@ function display_func(img1, img2, img3) { $('#component_type').text(language_str('bg')) //"背景" canvas1.discardActiveObject().renderAll() canvas2.discardActiveObject().renderAll() - let _objs1 = canvas1.getObjects() + // 过滤掉辅助线 + let _objs1 = canvas1.getObjects().filter(obj => !obj.isGuideLine) let g1 = [] let g3 = [] let g5 = [] @@ -1670,6 +1886,12 @@ function display_func(img1, img2, img3) { let all_top = 0 //预览图顶裁剪 for (let item of _objs1) { let idx = getIndex(item, canvas1) + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue + } + //console.log("------"); //console.log(item.getCoords()); //console.log(getCoordsMinX(background_image.getCoords())); @@ -1683,6 +1905,7 @@ function display_func(img1, img2, img3) { all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) } g3.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { continue //计数器 或 合成文字 } @@ -1761,7 +1984,8 @@ function display_func(img1, img2, img3) { top: black_top / zoom, left: black_left / zoom }) - let _objs2 = canvas2.getObjects() + // 过滤掉辅助线 + let _objs2 = canvas2.getObjects().filter(obj => !obj.isGuideLine) let g2 = [] let g4 = [] let g6 = [] @@ -1773,6 +1997,12 @@ function display_func(img1, img2, img3) { all_left = 0 for (let item of _objs2) { let idx = getIndex(item, canvas2) + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) } @@ -1780,6 +2010,7 @@ function display_func(img1, img2, img3) { all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) } g4.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { continue //黑色字或计数器 或 合成文字 } @@ -1990,7 +2221,8 @@ function output(callback = null, _save = save) { $('#component_type').text(language_str('bg')) //"背景" canvas1.discardActiveObject().renderAll() canvas2.discardActiveObject().renderAll() - let _objs1 = canvas1.getObjects() + // 过滤掉辅助线 + let _objs1 = canvas1.getObjects().filter(obj => !obj.isGuideLine) let g1 = [] let g3 = [] let g5 = [] //黑色图层 @@ -2002,6 +2234,11 @@ function output(callback = null, _save = save) { let all_top = 0 //预览图顶裁剪 for (let item of _objs1) { let idx = getIndex(item, canvas1) + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue + } //console.log("------"); //console.log(item.getCoords()); //console.log(getCoordsMinX(background_image.getCoords())); @@ -2093,7 +2330,8 @@ function output(callback = null, _save = save) { top: black_top / zoom, left: black_left / zoom }) - let _objs2 = canvas2.getObjects() + // 过滤掉辅助线 + let _objs2 = canvas2.getObjects().filter(obj => !obj.isGuideLine) let g2 = [] let g4 = [] let g6 = [] //黑色图层 @@ -2105,6 +2343,11 @@ function output(callback = null, _save = save) { all_top = 0 //黑色顶裁剪 for (let item of _objs2) { let idx = getIndex(item, canvas2) + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue + } if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) } @@ -2220,9 +2463,17 @@ function output(callback = null, _save = save) { //op.backBlPic = url6; op.frontBlackPic = desEncrypt(url5) op.backBlackPic = desEncrypt(url6) - for (let item of _objs1) { + // 过滤掉辅助线 + let filteredObjs1 = _objs1.filter(obj => !obj.isGuideLine) + for (let item of filteredObjs1) { let idx = getIndex(item, canvas1) if (idx == 0) continue //background-image + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined') { + continue + } + let type = objs1[idx - 1].type if (type == 2) { //out_pic @@ -2357,9 +2608,17 @@ function output(callback = null, _save = save) { } } //obj2 - for (let item of _objs2) { + // 过滤掉辅助线 + let filteredObjs2 = _objs2.filter(obj => !obj.isGuideLine) + for (let item of filteredObjs2) { let idx = getIndex(item, canvas2) if (idx == 0) continue //background-image + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined') { + continue + } + let type = objs2[idx - 1].type if (type == 2) { //out_pic diff --git a/lib/design2/output.js b/lib/design2/output.js index 80ff3a2..40116fd 100644 --- a/lib/design2/output.js +++ b/lib/design2/output.js @@ -15,7 +15,8 @@ function display_func(img1, img2, img3) { $('#component_type').text(language_str('bg')) //"背景" canvas1.discardActiveObject().renderAll() canvas2.discardActiveObject().renderAll() - let _objs1 = canvas1.getObjects() + // 过滤掉辅助线 + let _objs1 = canvas1.getObjects().filter(obj => !obj.isGuideLine) let g1 = [] let g3 = [] let g5 = [] @@ -25,8 +26,15 @@ function display_func(img1, img2, img3) { let black_top = 0 //黑色顶裁剪 let all_left = 0 //预览图左裁剪 let all_top = 0 //预览图顶裁剪 - for (let item of _objs1) { + // 过滤掉辅助线 + let list1 = _objs1.filter(obj => !obj.isGuideLine) + for (let item of list1) { let idx = getIndex(item, canvas1) + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue + } //console.log("------"); //console.log(item.getCoords()); //console.log(getCoordsMinX(background_image.getCoords())); @@ -119,7 +127,8 @@ function display_func(img1, img2, img3) { top: black_top / zoom, left: black_left / zoom }) - let _objs2 = canvas2.getObjects() + // 过滤掉辅助线 + let _objs2 = canvas2.getObjects().filter(obj => !obj.isGuideLine) let g2 = [] let g4 = [] let g6 = [] @@ -129,8 +138,15 @@ function display_func(img1, img2, img3) { black_left = 0 all_top = 0 all_left = 0 - for (let item of _objs2) { + // 过滤掉辅助线 + let filteredObjs2 = _objs2.filter(obj => !obj.isGuideLine) + for (let item of filteredObjs2) { let idx = getIndex(item, canvas2) + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue + } if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) } @@ -350,7 +366,8 @@ function output(callback = null, _save = save) { $('#component_type').text(language_str('bg')) //"背景" canvas1.discardActiveObject().renderAll() canvas2.discardActiveObject().renderAll() - let _objs1 = canvas1.getObjects() + // 过滤掉辅助线 + let _objs1 = canvas1.getObjects().filter(obj => !obj.isGuideLine) let g1 = [] let g3 = [] let g5 = [] //黑色图层 @@ -360,8 +377,14 @@ function output(callback = null, _save = save) { let black_top = 0 //黑色顶裁剪 let all_left = 0 //预览图左裁剪 let all_top = 0 //预览图顶裁剪 + // _objs1 已经在上面过滤过辅助线了,直接使用 for (let item of _objs1) { let idx = getIndex(item, canvas1) + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue + } //console.log("------"); //console.log(item.getCoords()); //console.log(getCoordsMinX(background_image.getCoords())); @@ -454,7 +477,8 @@ function output(callback = null, _save = save) { top: black_top / zoom, left: black_left / zoom }) - let _objs2 = canvas2.getObjects() + // 过滤掉辅助线 + let _objs2 = canvas2.getObjects().filter(obj => !obj.isGuideLine) let g2 = [] let g4 = [] let g6 = [] //黑色图层 @@ -464,8 +488,14 @@ function output(callback = null, _save = save) { black_top = 0 //黑色顶裁剪 all_left = 0 //黑色左裁剪 all_top = 0 //黑色顶裁剪 + // _objs2 已经在上面过滤过辅助线了,直接使用 for (let item of _objs2) { let idx = getIndex(item, canvas2) + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue + } if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) } @@ -582,8 +612,14 @@ function output(callback = null, _save = save) { //op.backBlPic = url6; op.frontBlackPic = desEncrypt(url5) op.backBlackPic = desEncrypt(url6) + // _objs1 已经在上面过滤过辅助线了,直接使用 for (let item of _objs1) { let idx = getIndex(item, canvas1) + + // 安全检查:确保 objs1[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) { + continue + } if (idx == 0) continue //background-image let type = objs1[idx - 1].type if (type == 2) { @@ -719,8 +755,14 @@ function output(callback = null, _save = save) { } } //obj2 + // _objs2 已经在上面过滤过辅助线了,直接使用 for (let item of _objs2) { let idx = getIndex(item, canvas2) + + // 安全检查:确保 objs2[idx - 1] 存在且有 type 属性 + if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) { + continue + } if (idx == 0) continue //background-image let type = objs2[idx - 1].type if (type == 2) { @@ -933,9 +975,31 @@ function cleanupSrcFields(jsonObjects, objsArray) { } function saveAs(op1, callback) { + // 临时移除辅助线,保存后再恢复 + let guideLines1 = [] + let guideLines2 = [] + canvas1.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines1.push(obj) + canvas1.remove(obj) + } + }) + canvas2.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines2.push(obj) + canvas2.remove(obj) + } + }) + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + // 恢复辅助线 + guideLines1.forEach(obj => canvas1.add(obj)) + guideLines2.forEach(obj => canvas2.add(obj)) + canvas1.renderAll() + canvas2.renderAll() + // 清理不需要的 src 字段 cleanupSrcFields(j.objects, objs1); cleanupSrcFields(b.objects, objs2); @@ -969,9 +1033,31 @@ function saveAs(op1, callback) { } function save(op1, callback) { + // 临时移除辅助线,保存后再恢复 + let guideLines1 = [] + let guideLines2 = [] + canvas1.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines1.push(obj) + canvas1.remove(obj) + } + }) + canvas2.getObjects().forEach((obj, index) => { + if (obj.isGuideLine) { + guideLines2.push(obj) + canvas2.remove(obj) + } + }) + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + // 恢复辅助线 + guideLines1.forEach(obj => canvas1.add(obj)) + guideLines2.forEach(obj => canvas2.add(obj)) + canvas1.renderAll() + canvas2.renderAll() + // 清理不需要的 src 字段 cleanupSrcFields(j.objects, objs1); cleanupSrcFields(b.objects, objs2); diff --git a/lib/design2/ui.js b/lib/design2/ui.js index 33500e7..b4aa2e4 100644 --- a/lib/design2/ui.js +++ b/lib/design2/ui.js @@ -2576,10 +2576,22 @@ function handleObjectSelected() { $('#component_type').text(language_str('bg')) //comfirm return } + + // 如果是辅助线,直接返回,不处理 + if (select_obj.isGuideLine) { + return + } + let i = getIndex(select_obj) if (i != 0) { //不是背景 --i + + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (!objs[i] || typeof objs[i].type === 'undefined') { + return + } + switch (objs[i].type) { case 1: //console.log("图片"); @@ -2721,10 +2733,22 @@ canvas1.on('mouse:down', (e) => { canvas = canvas1; objs = objs1; let select_obj = canvas.getActiveObject() + + // 如果是辅助线,直接返回,不处理 + if (select_obj && select_obj.isGuideLine) { + return + } + let i = getIndex(select_obj, canvas) if (i != 0) { //不是背景 --i + + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (!objs[i] || typeof objs[i].type === 'undefined') { + return + } + switch (objs[i].type) { case 1: $('#component_type').text(language_str('img')) @@ -2811,8 +2835,20 @@ canvas1.on('object:modified', function (event) { if (canvas1.getActiveObjects().length > 1) { return } + + // 跳过辅助线 + if (event.target.isGuideLine) { + return + } + let idx = getIndex(event.target, canvas1); if (idx <= 0) return; + + // 安全检查:确保 objs[idx - 1] 存在且有 type 属性 + if (!objs[idx - 1] || typeof objs[idx - 1].type === 'undefined') { + return + } + if (objs[idx - 1].type == 3) { event.target.fontSize *= event.target.scaleX event.target.fontSize = event.target.fontSize.toFixed(0) @@ -3011,10 +3047,22 @@ canvas2.on('mouse:down', (e) => { canvas = canvas2; objs = objs2; let select_obj = canvas.getActiveObject() + + // 如果是辅助线,直接返回,不处理 + if (select_obj && select_obj.isGuideLine) { + return + } + let i = getIndex(select_obj, canvas) if (i != 0) { //不是背景 --i + + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (!objs[i] || typeof objs[i].type === 'undefined') { + return + } + switch (objs[i].type) { case 1: $('#component_type').text(language_str('img')) @@ -3097,8 +3145,20 @@ canvas2.on('object:modified', function (event) { if (canvas.getActiveObjects().length > 1) { return } + + // 跳过辅助线 + if (event.target.isGuideLine) { + return + } + let idx = getIndex(event.target, canvas2); if (idx <= 0) return; + + // 安全检查:确保 objs[idx - 1] 存在且有 type 属性 + if (!objs[idx - 1] || typeof objs[idx - 1].type === 'undefined') { + return + } + if (objs[idx - 1].type == 3) { event.target.fontSize *= event.target.scaleX event.target.fontSize = event.target.fontSize.toFixed(0) diff --git a/package.json b/package.json index b82d6f3..4687750 100644 --- a/package.json +++ b/package.json @@ -3,49 +3,97 @@ "version": "2.3.017", "description": "tools of design soon files", "main": "main.js", - "author": "Jerry ", - "repository": { - "type": "git", - "url": "https://github.com/cello847/soondesign" - }, - "homepage": "https://soondesign.cardsoon.com", - "bugs": { - "url": "https://github.com/cello847/soondesign/issues" - }, "scripts": { - "start": "electron-forge start", - "package:x64": "electron-forge package --arch=x64", - "package:arm64": "electron-forge package --arch=arm64", - "package:universal": "electron-forge package --arch=universal" - }, + "start": "electron .", + "build": "electron-builder", + "build:win": "electron-builder --win --x64", + "build:mac": "electron-builder --mac --x64", + "build:linux": "electron-builder --linux --x64", + "build:all": "electron-builder -mwl" + }, + "build": { + "productName": "SoonDesign", + "appId": "com.SoonDesign", + "copyright": "", + "directories": { + "output": "build" + }, + "extraFiles": [ + "lib" + ], + "nsis": { + "oneClick": false, + "allowElevation": true, + "allowToChangeInstallationDirectory": true, + "installerIcon": "public/images/favicon.ico", + "uninstallerIcon": "public/images/favicon.ico", + "installerHeaderIcon": "public/images/favicon.ico", + "createDesktopShortcut": true, + "createStartMenuShortcut": true + }, + "fileAssociations": [ + { + "name": "SoonDesign file", + "ext": "soon", + "icon": "public/images/favicon.ico", + "description": "SoonDesign file associations" + } + ], + "asar": true, + "asarUnpack": [ + "lib/**" + ], + "win": { + "icon": "public/images/favicon.ico", + "requestedExecutionLevel": "asInvoker", + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ] + }, + "mac": { + "target": [ + { "target": "dmg", "arch": ["x64"] }, + { "target": "zip", "arch": ["x64"] } + ], + "category": "public.app-category.utilities", + "icon": "public/images/favicon.icns" + }, + "linux": { + "target": [ + { "target": "AppImage", "arch": ["x64"] }, + { "target": "deb", "arch": ["x64"] } + ], + "category": "Utility", + "icon": "public/images/IconA256.png", + "desktop": { + "Name": "SoonDesign", + "Comment": "tools of design soon files" + } + } + }, "electronPackagerConfig": { "packageManager": "npm", - "icon": "./public/images/favicon.icns" + "icon": "./public/images/favicon.ico" }, - "config": { - "forge": { - "packagerConfig": { - "icon": "./public/images/favicon.icns", - "asar": true, - "extraResource": [ - "./help" - ], - "osxUniversal": { - } - } - } - }, + + "repository": "", "keywords": [ - "SoonDesign", - "soon", + "Electron", + "quick", "start", "tutorial", "demo" ], + "author": "", "license": "MIT", "devDependencies": { - "@electron-forge/cli": "^6.2.0", - "electron": "^25.0.0" + "electron": "^25.0.0", + "electron-builder": "^23.3.3" }, "dependencies": { "@electron/remote": "^2.0.8", @@ -58,8 +106,5 @@ "less": "^4.1.2", "less-loader": "^10.2.0", "save": "^2.9.0" - }, - "optionalDependencies": { - "create-dmg": "^6.1.0" } }