diff --git a/frontend-web/js/common/cloud-files.js b/frontend-web/js/common/cloud-files.js index 408fb11..866e699 100644 --- a/frontend-web/js/common/cloud-files.js +++ b/frontend-web/js/common/cloud-files.js @@ -77,9 +77,66 @@ return soonEnsureSoonExt(soonSessionSlug(keyOrName)); } + /** 缩略图截取框:对齐 background_image 设计边界(design1 光盘外接方 / design2 卡面) */ + function soonResolveSavePreviewCrop(canvas) { + if (!canvas) return null; + var bg = null; + if (typeof window !== 'undefined' && window.canvas1 === canvas && window.background_image1) { + bg = window.background_image1; + } + if (!bg && typeof canvas.getObjects === 'function') { + var objs = canvas.getObjects(); + if (objs.length && objs[0] && objs[0].type === 'image') bg = objs[0]; + } + if (!bg || !bg.width || !bg.height) return null; + var sx = bg.scaleX == null ? 1 : bg.scaleX; + var sy = bg.scaleY == null ? 1 : bg.scaleY; + var cw = canvas.width || 0; + var ch = canvas.height || 0; + var w; + var h; + var left = bg.left; + var top = bg.top; + w = bg.width * sx; + h = bg.height * sy; + if (!(w > 0 && h > 0)) return null; + if (cw > 0 && ch > 0) { + var x1 = Math.max(0, left); + var y1 = Math.max(0, top); + var x2 = Math.min(cw, left + w); + var y2 = Math.min(ch, top + h); + w = x2 - x1; + h = y2 - y1; + if (w <= 0 || h <= 0) return null; + left = x1; + top = y1; + } + return { left: left, top: top, width: w, height: h }; + } + + function soonIsDesign2SaveCanvas(canvas) { + return !!(typeof window.soonBuildDesign2SavePreviewPic === 'function' + && window.canvas1 && canvas === window.canvas1); + } + + function soonIsDesign1SaveCanvas(canvas) { + return !!(typeof window.soonBuildDesign1SavePreviewPic === 'function' + && window.canvas1 && canvas === window.canvas1); + } + function soonBuildSavePreviewPic(canvas) { var THUMB_MAX = 102400; if (!canvas || typeof canvas.getObjects !== 'function') return ''; + if (soonIsDesign2SaveCanvas(canvas)) { + var d2Pic = window.soonBuildDesign2SavePreviewPic(canvas); + if (d2Pic) return d2Pic; + return ''; + } + if (soonIsDesign1SaveCanvas(canvas)) { + var d1Pic = window.soonBuildDesign1SavePreviewPic(canvas); + if (d1Pic) return d1Pic; + return ''; + } var hidden = []; canvas.getObjects().forEach(function (obj) { if (obj.isGuideLine) { @@ -88,24 +145,28 @@ } }); if (hidden.length) canvas.renderAll(); - window._soonSkipDesign1CdMask = true; - var mult = 0.18; + var crop = soonResolveSavePreviewCrop(canvas); + var mult = crop ? 0.28 : 0.18; var quality = 0.72; var url = ''; - try { - for (var i = 0; i < 3; i++) { - try { - var candidate = canvas.toDataURL({ format: 'jpeg', quality: quality, multiplier: mult }); - if (candidate && candidate.indexOf('data:image/') === 0 && candidate.length <= THUMB_MAX) { - url = candidate; - break; - } - } catch (e) { /* ignore */ } - quality -= 0.18; - mult -= 0.04; - } - } finally { - window._soonSkipDesign1CdMask = false; + for (var i = 0; i < 3; i++) { + try { + var opts = { format: 'jpeg', quality: quality, multiplier: mult }; + if (crop) { + opts.left = crop.left; + opts.top = crop.top; + opts.width = crop.width; + opts.height = crop.height; + } + canvas.renderAll(); + var candidate = canvas.toDataURL(opts); + if (candidate && candidate.indexOf('data:image/') === 0 && candidate.length <= THUMB_MAX) { + url = candidate; + break; + } + } catch (e) { /* ignore */ } + quality -= 0.18; + mult -= 0.04; } hidden.forEach(function (obj) { obj.visible = true; }); canvas.renderAll(); diff --git a/frontend-web/js/design1/core.js b/frontend-web/js/design1/core.js index 13370cf..4d6da44 100644 --- a/frontend-web/js/design1/core.js +++ b/frontend-web/js/design1/core.js @@ -448,21 +448,47 @@ function addBackground() { } // 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 -window.updateControls = function updateControls() { - // 使用 window.canvas 和 window.background_image 作为后备,确保在 eval() 执行时也能访问 +window._soonSyncingFontControls = false; + +function soonFontSizeUiVal(fontSizePx) { + var d = window.dpi || 600; + var uiVal = parseFloat(fontSizePx) * 72 / d; + if (uiVal % 0.5 !== 0) uiVal = Math.round(uiVal * 2) / 2; + return uiVal; +} + +function soonSyncActiveFontSizeInput(activeObj, currentCanvas) { + if (!activeObj) return; + var idx = getIndex(activeObj, currentCanvas); + if (idx <= 0) return; + var objData = (window.objs || objs)[idx - 1]; + if (!objData) return; + var uiVal = soonFontSizeUiVal(activeObj.get('fontSize')); + if (objData.type === 3) $("#text_size").val(uiVal); + else if (objData.type === 4) $("#out_text_size").val(uiVal); + else if (objData.type === 10) $("#count_size").val(uiVal); +} + +window.updateControls = function () { const currentCanvas = window.canvas || canvas; const currentBackgroundImage = window.background_image || background_image; if (!currentCanvas || !currentCanvas.getActiveObject()) return; - $("#text_r").val(currentCanvas.getActiveObject().get("angle").toFixed(0)); - $("#text_x").val((currentCanvas.getActiveObject().get("left") - currentBackgroundImage.left).toFixed(0)); - $("#text_y").val((currentCanvas.getActiveObject().get("top") - currentBackgroundImage.top).toFixed(0)); - $("#text_h").val((currentCanvas.getActiveObject().get("scaleY") * currentCanvas.getActiveObject().get("height")).toFixed(0)); - $("#text_w").val((currentCanvas.getActiveObject().get("scaleX") * currentCanvas.getActiveObject().get("width")).toFixed(0)); - // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性) - if (currentCanvas.getActiveObject().get("lockMovementX")) { - $("#lock_img").attr("src", soonAsset("lock20.png"))//已锁定 - } else { - $("#lock_img").attr("src", soonAsset("unlock20.png"))//未锁定 + const activeObj = currentCanvas.getActiveObject(); + window._soonSyncingFontControls = true; + try { + $("#text_r").val(activeObj.get("angle").toFixed(0)); + $("#text_x").val((activeObj.get("left") - currentBackgroundImage.left).toFixed(0)); + $("#text_y").val((activeObj.get("top") - currentBackgroundImage.top).toFixed(0)); + $("#text_h").val((activeObj.get("scaleY") * activeObj.get("height")).toFixed(0)); + $("#text_w").val((activeObj.get("scaleX") * activeObj.get("width")).toFixed(0)); + soonSyncActiveFontSizeInput(activeObj, currentCanvas); + if (activeObj.get("lockMovementX")) { + $("#lock_img").attr("src", soonAsset("lock20.png")); + } else { + $("#lock_img").attr("src", soonAsset("unlock20.png")); + } + } finally { + window._soonSyncingFontControls = false; } }; // 向后兼容 @@ -1786,18 +1812,21 @@ function initCanvasEvents() { return; } let target = event.target; + var modAction = event.action || (event.transform && event.transform.action); - if ([3, 4, 10].includes(objData.type)) { - target.fontSize = (target.fontSize * target.scaleX).toFixed(0); - target.scaleX = 1; target.scaleY = 1; + if ([3, 4, 10].includes(objData.type) && modAction === 'scale') { + target.set({ + fontSize: Math.round(target.fontSize * (target.scaleX || 1)), + scaleX: 1, + scaleY: 1, + }); target._clearCache(); - if (objData.type == 3) $("#text_size").val(target.fontSize); - if (objData.type == 4) $("#out_text_size").val(target.fontSize); - if (objData.type == 10) $("#count_size").val(target.fontSize); currentCanvas.renderAll(); - } else if (objData.type == 5) { - target.width *= target.scaleX; target.height *= target.scaleY; - target.scaleX = 1; target.scaleY = 1; + } else if (objData.type == 5 && modAction === 'scale') { + target.width *= target.scaleX || 1; + target.height *= target.scaleY || 1; + target.scaleX = 1; + target.scaleY = 1; } window.recordState(); // 更新对象列表以同步锁状态 diff --git a/frontend-web/js/design1/output.js b/frontend-web/js/design1/output.js index 17765ee..21df98d 100644 --- a/frontend-web/js/design1/output.js +++ b/frontend-web/js/design1/output.js @@ -52,6 +52,62 @@ async function saveImageAsPNG(buffer) { } } +/** 从画布像素截取光盘外接方(含 after:render 圆外暗蒙版),与编辑器所见一致 */ +function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality) { + var bg = window.background_image1 || window.background_image; + if (!canvas || !bg) return ''; + var srcEl = canvas.lowerCanvasEl; + if (!srcEl || !srcEl.getContext) return ''; + var sx = bg.scaleX == null ? 1 : bg.scaleX; + var sy = bg.scaleY == null ? 1 : bg.scaleY; + var sw = bg.width * sx; + var sh = bg.height * sy; + var retina = (typeof canvas.getRetinaScaling === 'function') ? canvas.getRetinaScaling() : 1; + outW = outW || Math.round(sw); + outH = outH || Math.round(sh); + var tmp = document.createElement('canvas'); + tmp.width = outW; + tmp.height = outH; + var tctx = tmp.getContext('2d'); + tctx.drawImage( + srcEl, + bg.left * retina, bg.top * retina, sw * retina, sh * retina, + 0, 0, outW, outH + ); + if (format === 'jpeg') return tmp.toDataURL('image/jpeg', quality == null ? 0.75 : quality); + return tmp.toDataURL('image/png'); +} + +function soonBuildDesign1SavePreviewPic(canvas) { + var THUMB_MAX = 102400; + var c1 = window.canvas1; + if (!canvas || (c1 && canvas !== c1)) return ''; + var hidden = []; + canvas.getObjects().forEach(function (obj) { + if (obj.isGuideLine) { + hidden.push(obj); + obj.visible = false; + } + }); + if (canvas.discardActiveObject) canvas.discardActiveObject(); + canvas.renderAll(); + + var url = ''; + var sizes = [320, 280, 240, 200]; + var quals = [0.82, 0.68, 0.52, 0.38]; + for (var i = 0; i < sizes.length; i++) { + url = soonDesign1CaptureCanvasRect(canvas, sizes[i], sizes[i], 'jpeg', quals[i]); + if (url && url.indexOf('data:image/') === 0 && url.length <= THUMB_MAX) break; + url = ''; + } + + hidden.forEach(function (obj) { obj.visible = true; }); + canvas.renderAll(); + if (canvas.requestRenderAll) canvas.requestRenderAll(); + return url; +} +window.soonBuildDesign1SavePreviewPic = soonBuildDesign1SavePreviewPic; + window.display_func = function display_func(img1, img2, img3) { $("#base_control").hide(); $("#line_control").hide(); @@ -217,13 +273,18 @@ window.display_func = function display_func(img1, img2, img3) { left: all_left }); - g3.push(img1); - let url3 = new fabric.Group(g3).toDataURL({ - height: 2787, - width: 2787, - top: all_top, - left: all_left + var hiddenForPreview = []; + canvas1.getObjects().forEach(function (o) { + if (o.isGuideLine) { + hiddenForPreview.push(o); + o.visible = false; + } }); + canvas1.discardActiveObject(); + canvas1.renderAll(); + let url3 = soonDesign1CaptureCanvasRect(canvas1, 2787, 2787, 'png'); + hiddenForPreview.forEach(function (o) { o.visible = true; }); + canvas1.renderAll(); let url5 = new fabric.Group(g5).toDataURL({ height: 2787, width: 2787, diff --git a/frontend-web/js/design1/ui.js b/frontend-web/js/design1/ui.js index 4dabd89..0fc9e1f 100644 --- a/frontend-web/js/design1/ui.js +++ b/frontend-web/js/design1/ui.js @@ -1453,6 +1453,7 @@ $("#new").click(function () { // =========================================================== $("#text_y").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; if (!canvas.getActiveObject()) return; if ($("#text_y").val() == "") { $("#text_y").val(0) @@ -1466,6 +1467,7 @@ $("#text_y").bind('input propertychange', function () { window.recordState(); }) $("#text_x").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; if (!canvas.getActiveObject()) return; if ($("#text_x").val() == "") { $("#text_x").val(0) @@ -1479,6 +1481,7 @@ $("#text_x").bind('input propertychange', function () { window.recordState(); }) $("#text_w").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; if (!canvas.getActiveObject()) return; let val = "0"; if ($("#text_w").val() != "") { @@ -1497,6 +1500,7 @@ $("#text_w").bind('input propertychange', function () { window.recordState(); }) $("#text_h").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; if (!canvas.getActiveObject()) return; let idx = getIndex(canvas.getActiveObject()); if (idx <= 0 || !objs[idx - 1]) return; @@ -1523,6 +1527,7 @@ $("#text_h").bind('input propertychange', function () { window.recordState(); }) $("#text_r").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; if (!canvas.getActiveObject()) return; if ($("#text_r").val() == "") { $("#text_r").val("0") @@ -1619,6 +1624,7 @@ $("#text_back_color").bind('input propertychange', function () { window.recordState(); }) $("#text_size").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; let val = 1 if (parseFloat($("#text_size").val())) { val = parseFloat($("#text_size").val()) @@ -1671,6 +1677,7 @@ $("#out_text_back_color").bind('input propertychange', function () { window.recordState(); }) $("#out_text_size").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; let val = 1 if (parseFloat($("#out_text_size").val())) { val = parseFloat($("#out_text_size").val()) @@ -1786,6 +1793,7 @@ $("#counter_color").bind('input propertychange', function () { window.recordState(); }) $("#count_size").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; let val = 1 if (parseFloat($("#count_size").val())) { val = parseFloat($("#count_size").val()) diff --git a/frontend-web/js/design2/core.js b/frontend-web/js/design2/core.js index 7b67185..fae5777 100644 --- a/frontend-web/js/design2/core.js +++ b/frontend-web/js/design2/core.js @@ -165,7 +165,13 @@ function initRulers() { // background_image在画布坐标系中的left值,需要转换到标尺坐标系 // 画布从标尺的marginOffset位置开始,所以:标尺0点 = marginOffset + background_image.left (如果存在) let originX = marginOffset; - if (typeof background_image !== 'undefined' && background_image) { + var rulerSide = ($('#back_side').hasClass('ui-button-active')) ? canvas2 : canvas1; + var anchorX = typeof soonDesign2GetFrameAnchor === 'function' + ? soonDesign2GetFrameAnchor(rulerSide) + : null; + if (anchorX) { + originX = marginOffset + anchorX.left; + } else if (typeof background_image !== 'undefined' && background_image) { originX = marginOffset + background_image.left; } const startX = 0; @@ -257,7 +263,13 @@ function initRulers() { // background_image在画布坐标系中的top值,需要转换到标尺坐标系 // 画布从标尺的marginOffset + rulerHeightOffset位置开始,所以:标尺0点 = marginOffset + rulerHeightOffset + background_image.top (如果存在) let originY = marginOffset + rulerHeightOffset; - if (typeof background_image !== 'undefined' && background_image) { + var rulerSideY = ($('#back_side').hasClass('ui-button-active')) ? canvas2 : canvas1; + var anchorY = typeof soonDesign2GetFrameAnchor === 'function' + ? soonDesign2GetFrameAnchor(rulerSideY) + : null; + if (anchorY) { + originY = marginOffset + rulerHeightOffset + anchorY.top; + } else if (typeof background_image !== 'undefined' && background_image) { originY = marginOffset + rulerHeightOffset + background_image.top; } const startY = 0; @@ -355,15 +367,19 @@ function initRulers() { }, 100); }); - // 监听画布缩放/渲染变化,仅用于重绘标尺 - // 辅助线交给 Fabric 自己渲染,否则会出现“重影 / 变两三根”的现象 + canvas1.off('after:render'); + canvas2.off('after:render'); canvas1.on('after:render', function () { drawHorizontalRuler(); drawVerticalRuler(); + soonDesign2DrawCardMask(canvas1, ctx1, document.getElementById('source_front')); + soonDesign2DrawTemplateOverlay(canvas1, ctx1, document.getElementById('source_front')); }); canvas2.on('after:render', function () { drawHorizontalRuler(); drawVerticalRuler(); + soonDesign2DrawCardMask(canvas2, ctx2, document.getElementById('source_back')); + soonDesign2DrawTemplateOverlay(canvas2, ctx2, document.getElementById('source_back')); }); // --- 辅助线功能 (参考 design1) --- @@ -568,6 +584,221 @@ function soonDesign2ScaleBgSources() { } } +function soonDesign2BgOverlayMetrics(imgEl) { + if (!imgEl || !imgEl.complete || imgEl.naturalWidth <= 0) return null; + var z = zoom || window.zoom || 0.5; + var w = parseFloat(imgEl.style.width); + if (!w || w <= 0) w = imgEl.naturalWidth * z; + var h = parseFloat(imgEl.style.height); + if (!h || h <= 0) h = imgEl.naturalHeight * z; + return { w: w, h: h }; +} + +/** 卡面区域:与 background_image 对齐(1012×648),fallback 模板线框 */ +function soonDesign2ResolveCardFrame(fabricCanvas, imgEl) { + if (!fabricCanvas) return null; + var metaList = (fabricCanvas === canvas2) ? (window.objs2 || objs2) : (window.objs1 || objs1); + if (soonDesign2IsUserBgMeta(metaList)) { + var anchor = soonDesign2GetFrameAnchor(fabricCanvas); + var card = soonDesign2CardSize(); + if (anchor) { + return { left: anchor.left, top: anchor.top, width: card.w, height: card.h }; + } + } + var bg = (fabricCanvas === canvas1) ? background_image1 : background_image2; + if (bg && bg.width) { + var sx = bg.scaleX == null ? 1 : bg.scaleX; + var sy = bg.scaleY == null ? 1 : bg.scaleY; + return { + left: bg.left, + top: bg.top, + width: bg.width * sx, + height: bg.height * sy + }; + } + var cw = fabricCanvas.width || 1012; + var ch = fabricCanvas.height || 648; + if (imgEl) { + var metrics = soonDesign2BgOverlayMetrics(imgEl); + if (metrics) { + return { + left: (cw - metrics.w) / 2, + top: (ch - metrics.h) / 2, + width: metrics.w, + height: metrics.h + }; + } + } + return { left: 0, top: 0, width: cw, height: ch }; +} + +function soonDesign2CardSize() { + return { w: 1012, h: 648 }; +} + +function soonDesign2IsUserBgMeta(metaList) { + return !!(metaList && metaList.length > 0 && metaList[0] && metaList[0].type === 1); +} + +function soonDesign2SyncFrameAnchor(fabricCanvas, frame) { + if (!frame) return; + var pt = { left: frame.left, top: frame.top }; + if (fabricCanvas === canvas2) { + window.soonDesign2FrameAnchor2 = pt; + } else { + window.soonDesign2FrameAnchor1 = pt; + } + var active = window.canvas || canvas; + if (!active || fabricCanvas === active) { + window.soonDesign2FrameAnchor = pt; + } +} + +function soonDesign2GetFrameAnchor(fabricCanvas) { + if (fabricCanvas === canvas2 && window.soonDesign2FrameAnchor2) { + return window.soonDesign2FrameAnchor2; + } + if (window.soonDesign2FrameAnchor1) { + return window.soonDesign2FrameAnchor1; + } + return window.soonDesign2FrameAnchor || null; +} + +/** 用户设为背景的对象:坐标系锚点,属性面板不应再移动它 */ +function soonDesign2ActiveIsAnchorBg() { + var c = window.canvas || canvas; + if (!c) return false; + var ao = c.getActiveObject(); + if (!ao) return false; + var meta = (c === canvas2) ? (window.objs2 || objs2) : (window.objs1 || objs1); + if (c === (window.canvas || canvas)) { + meta = window.objs || objs || meta; + } + if (!soonDesign2IsUserBgMeta(meta)) return false; + var bg = (c === canvas2) ? (window.background_image2 || background_image2) : (window.background_image1 || background_image1); + return ao === bg; +} + +function soonDesign2FrameOrigin(fabricCanvas) { + var frame = soonDesign2ResolveCardFrame(fabricCanvas, null); + if (frame) return frame; + var card = soonDesign2CardSize(); + var cw = (fabricCanvas && fabricCanvas.width) || card.w; + var ch = (fabricCanvas && fabricCanvas.height) || card.h; + return { left: (cw - card.w) / 2, top: (ch - card.h) / 2, width: card.w, height: card.h }; +} + +/** 用户图铺满卡面框(1012×648),与蒙版/线框同源坐标 */ +function soonDesign2SnapImageToCardFrame(img, fabricCanvas, frame) { + if (!img || !img.width) return frame || soonDesign2FrameOrigin(fabricCanvas); + frame = frame || soonDesign2FrameOrigin(fabricCanvas); + var card = soonDesign2CardSize(); + img.set({ + originX: 'left', + originY: 'top', + scaleX: card.w / img.width, + scaleY: card.h / img.height, + left: frame.left, + top: frame.top + }); + img.setCoords(); + soonDesign2SyncFrameAnchor(fabricCanvas, frame); + return frame; +} + +function soonDesign2ApplyUserBgAsCanvas0(userImg, fabricCanvas, oldBg) { + if (!userImg || !fabricCanvas) return; + if (oldBg && oldBg !== userImg) { + fabricCanvas.remove(oldBg); + } + fabricCanvas.sendToBack(userImg); + if (fabricCanvas === canvas2) { + background_image2 = userImg; + window.background_image2 = userImg; + } else { + background_image1 = userImg; + window.background_image1 = userImg; + } + var activeCanvas = window.canvas || canvas; + if (fabricCanvas === activeCanvas) { + background_image = userImg; + window.background_image = userImg; + } +} + +function soonDesign2LockBgObject(bgObj) { + if (!bgObj) return; + bgObj.set({ + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + lockSkewingX: true, + lockSkewingY: true, + selectable: false, + evented: false, + hoverCursor: 'default' + }); +} + +/** 打开旧文件:去掉残留模板层,用户背景独占 canvas[0] 并贴齐卡框 */ +function soonDesign2NormalizeUserBg(fabricCanvas, metaList) { + if (!soonDesign2IsUserBgMeta(metaList) || !fabricCanvas) return null; + var all = fabricCanvas.getObjects().filter(function (o) { return !o.isGuideLine; }); + if (!all.length) return null; + var frame = soonDesign2FrameOrigin(fabricCanvas); + var userBg = all[0]; + if (all.length > 1) { + fabricCanvas.remove(all[0]); + userBg = fabricCanvas.getObjects().filter(function (o) { return !o.isGuideLine; })[0]; + } + if (!userBg) return null; + soonDesign2SnapImageToCardFrame(userBg, fabricCanvas, frame); + soonDesign2ApplyUserBgAsCanvas0(userBg, fabricCanvas, null); + soonDesign2LockBgObject(userBg); + return userBg; +} + +window.soonDesign2CardSize = soonDesign2CardSize; +window.soonDesign2IsUserBgMeta = soonDesign2IsUserBgMeta; +window.soonDesign2SyncFrameAnchor = soonDesign2SyncFrameAnchor; +window.soonDesign2GetFrameAnchor = soonDesign2GetFrameAnchor; +window.soonDesign2ActiveIsAnchorBg = soonDesign2ActiveIsAnchorBg; +window.soonDesign2FrameOrigin = soonDesign2FrameOrigin; +window.soonDesign2SnapImageToCardFrame = soonDesign2SnapImageToCardFrame; +window.soonDesign2ApplyUserBgAsCanvas0 = soonDesign2ApplyUserBgAsCanvas0; +window.soonDesign2LockBgObject = soonDesign2LockBgObject; +window.soonDesign2NormalizeUserBg = soonDesign2NormalizeUserBg; + +function soonDesign2DrawCardMask(fabricCanvas, ctx, imgEl) { + if (window._soonSkipDesign2CardMask || !fabricCanvas || !ctx) return; + var frame = soonDesign2ResolveCardFrame(fabricCanvas, imgEl); + if (!frame) return; + var left = frame.left; + var top = frame.top; + var cardW = frame.width; + var cardH = frame.height; + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, fabricCanvas.width, fabricCanvas.height); + ctx.moveTo(left + cardW, top); + ctx.lineTo(left, top); + ctx.lineTo(left, top + cardH); + ctx.lineTo(left + cardW, top + cardH); + ctx.closePath(); + ctx.fillStyle = 'rgba(0, 0, 0, 0.9)'; + ctx.fill('evenodd'); + ctx.restore(); +} + +function soonDesign2DrawTemplateOverlay(fabricCanvas, ctx, imgEl) { + if (!window.is_bgi_add || !fabricCanvas || !ctx || !imgEl) return; + var frame = soonDesign2ResolveCardFrame(fabricCanvas, imgEl); + if (!frame) return; + ctx.drawImage(imgEl, frame.left, frame.top, frame.width, frame.height); +} + function soonDesign2PrepareBgSources(done) { var sf = document.getElementById('source_front'); var sb = document.getElementById('source_back'); @@ -647,21 +878,47 @@ canvas2.preserveObjectStacking = true // 对象选择和属性更新函数 // ========================================== // 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 -window.updateControls = function updateControls() { - // 使用 window.canvas 和 window.background_image 作为后备,确保在 eval() 执行时也能访问 +window._soonSyncingFontControls = false; + +function soonFontSizeUiVal(fontSizePx) { + var d = window.dpi || 300; + var uiVal = parseFloat(fontSizePx) * 72 / d; + if (uiVal % 0.5 !== 0) uiVal = Math.round(uiVal * 2) / 2; + return uiVal; +} + +function soonSyncActiveFontSizeInput(activeObj, currentCanvas) { + if (!activeObj || !currentCanvas) return; + var idx = getIndex(activeObj, currentCanvas); + if (idx <= 0) return; + var objData = (currentCanvas === canvas1 ? objs1 : objs2)[idx - 1]; + if (!objData) return; + var uiVal = soonFontSizeUiVal(activeObj.get('fontSize')); + if (objData.type === 3) $("#text_size").val(uiVal); + else if (objData.type === 4) $("#out_text_size").val(uiVal); + else if (objData.type === 10) $("#count_size").val(uiVal); +} + +window.updateControls = function () { const currentCanvas = window.canvas || canvas; const currentBackgroundImage = window.background_image || background_image; if (!currentCanvas || !currentCanvas.getActiveObject()) return; - $("#text_r").val(currentCanvas.getActiveObject().get("angle").toFixed(0)); - $("#text_x").val((currentCanvas.getActiveObject().get("left") - currentBackgroundImage.left).toFixed(0)); - $("#text_y").val((currentCanvas.getActiveObject().get("top") - currentBackgroundImage.top).toFixed(0)); - $("#text_h").val((currentCanvas.getActiveObject().get("scaleY") * currentCanvas.getActiveObject().get("height")).toFixed(0)); - $("#text_w").val((currentCanvas.getActiveObject().get("scaleX") * currentCanvas.getActiveObject().get("width")).toFixed(0)); - // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性) - if (currentCanvas.getActiveObject().get("lockMovementX")) { - $("#lock_img").attr("src", soonAsset("lock20.png"))//已锁定 - } else { - $("#lock_img").attr("src", soonAsset("unlock20.png"))//未锁定 + const activeObj = currentCanvas.getActiveObject(); + window._soonSyncingFontControls = true; + try { + $("#text_r").val(activeObj.get("angle").toFixed(0)); + $("#text_x").val((activeObj.get("left") - currentBackgroundImage.left).toFixed(0)); + $("#text_y").val((activeObj.get("top") - currentBackgroundImage.top).toFixed(0)); + $("#text_h").val((activeObj.get("scaleY") * activeObj.get("height")).toFixed(0)); + $("#text_w").val((activeObj.get("scaleX") * activeObj.get("width")).toFixed(0)); + soonSyncActiveFontSizeInput(activeObj, currentCanvas); + if (activeObj.get("lockMovementX")) { + $("#lock_img").attr("src", soonAsset("lock20.png")); + } else { + $("#lock_img").attr("src", soonAsset("unlock20.png")); + } + } finally { + window._soonSyncingFontControls = false; } }; // 向后兼容 @@ -1052,8 +1309,10 @@ function handleObjectSelected(e) { return; } - // 3. 获取索引 - let i = getIndex(select_obj, canvas); + // 3. 解析 objs 下标(用户设为背景时在 canvas[0],不能仅用 getIndex) + let metaIdx = typeof soonDesign2ResolveObjMetaIndex === 'function' + ? soonDesign2ResolveObjMetaIndex(select_obj, canvas) + : (getIndex(select_obj, canvas) > 0 ? getIndex(select_obj, canvas) - 1 : -1); // 如果不是通过对象列表选中的,且对象被锁定,则取消选中 if (!window._selectingFromList && isObjectLocked(select_obj)) { @@ -1065,58 +1324,52 @@ function handleObjectSelected(e) { return; } - if (i != 0) { - // --- 选中了普通对象 --- - --i; // 转换为 objs 数组下标 - - // 安全检查:确保 objs[i] 存在且有 type 属性 - if (objs[i] && typeof objs[i].type !== 'undefined') { - switch (objs[i].type) { + if (metaIdx >= 0) { + if (objs[metaIdx] && typeof objs[metaIdx].type !== 'undefined') { + switch (objs[metaIdx].type) { case 1: // 图片 $("#component_type").text(language_str("img")); - selectObject(select_obj, i); selectPic(select_obj, i); break; + selectObject(select_obj, metaIdx); selectPic(select_obj, metaIdx); break; case 2: // 合成图片 $("#component_type").text(language_str("simg")); - selectObject(select_obj, i); selectOutPic(select_obj, i); break; + selectObject(select_obj, metaIdx); selectOutPic(select_obj, metaIdx); break; case 3: // 文本 $("#component_type").text(language_str("text")); - selectObject(select_obj, i); selectText(select_obj, i); break; + selectObject(select_obj, metaIdx); selectText(select_obj, metaIdx); break; case 4: // 合成文本 $("#component_type").text(language_str("stext")); - selectObject(select_obj, i); selectOutText(select_obj, i); break; + selectObject(select_obj, metaIdx); selectOutText(select_obj, metaIdx); break; case 5: // 矩形 $("#component_type").text(language_str("rect")); - selectObject(select_obj, i); selectRect(select_obj, i); break; + selectObject(select_obj, metaIdx); selectRect(select_obj, metaIdx); break; case 6: // 圆形 $("#component_type").text(language_str("circ")); - selectObject(select_obj, i); selectCircle(select_obj, i); break; + selectObject(select_obj, metaIdx); selectCircle(select_obj, metaIdx); break; case 7: // 直线 $("#component_type").text(language_str("line")); - selectObject(select_obj, i); selectLine(select_obj, i); break; + selectObject(select_obj, metaIdx); selectLine(select_obj, metaIdx); break; case 8: // 二维码 $("#component_type").text(language_str("qrc")); - selectObject(select_obj, i); selectQrCode(select_obj, i); break; + selectObject(select_obj, metaIdx); selectQrCode(select_obj, metaIdx); break; case 9: // 条形码 $("#component_type").text(language_str("barc")); - selectObject(select_obj, i); selectBarCode(select_obj, i); break; + selectObject(select_obj, metaIdx); selectBarCode(select_obj, metaIdx); break; case 10: // 计数器 $("#component_type").text(language_str("counter")); - selectObject(select_obj, i); selectCounter(select_obj, i); break; + selectObject(select_obj, metaIdx); selectCounter(select_obj, metaIdx); break; case 11: // 圆形文本 $("#component_type").text(language_str("ctext")); - selectObject(select_obj, i); selectCircleText(select_obj, i); break; + selectObject(select_obj, metaIdx); selectCircleText(select_obj, metaIdx); break; } } - // 【关键】这里传入 true! - // 意思是:因为我已经是在画布上选中它了,selectObj 函数只负责把左边的条目变灰,不要再回头去 setActiveObject 了 - selectObj(i, true); + selectObj(metaIdx, true); } else { - // --- 选中了背景 --- + // --- 模板背景(canvas[0] 且不在 objs 中)--- $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); - selectObj(-1, true); $("#component_type").text(language_str("bg")); + selectObj(-1, true); } } @@ -1363,22 +1616,24 @@ function initCanvasEvents() { return; } let target = event.target; + var modAction = event.action || (event.transform && event.transform.action); - if ([3, 4, 10].includes(objData.type)) { - target.fontSize = (target.fontSize * target.scaleX).toFixed(0); - target.scaleX = 1; target.scaleY = 1; + if ([3, 4, 10].includes(objData.type) && modAction === 'scale') { + target.set({ + fontSize: Math.round(target.fontSize * (target.scaleX || 1)), + scaleX: 1, + scaleY: 1, + }); target._clearCache(); - if (objData.type == 3) $("#text_size").val(target.fontSize); - if (objData.type == 4) $("#out_text_size").val(target.fontSize); - if (objData.type == 10) $("#count_size").val(target.fontSize); currentCanvas.renderAll(); - updateControls(); - } else if (objData.type == 5) { - target.width *= target.scaleX; target.height *= target.scaleY; - target.scaleX = 1; target.scaleY = 1; - updateControls(); + } else if (objData.type == 5 && modAction === 'scale') { + target.width *= target.scaleX || 1; + target.height *= target.scaleY || 1; + target.scaleX = 1; + target.scaleY = 1; } recordState(); + updateControls(); // 更新对象列表以同步锁状态 if (typeof window.updateList === 'function') { window.updateList(); @@ -1430,8 +1685,10 @@ window.updateList = function updateList() { // 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性) // 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1 let isLocked = false; - if (currentCanvas.getObjects()[i + 1]) { - let canvasObj = currentCanvas.getObjects()[i + 1]; + var canvasObj = typeof window.soonCanvasObjForListIndex === 'function' + ? window.soonCanvasObjForListIndex(i) + : currentCanvas.getObjects()[i + 1]; + if (canvasObj) { isLocked = canvasObj.get('lockMovementX') === true; } @@ -1454,9 +1711,9 @@ window.updateList = function updateList() { // 重新绑定点击事件(防止 updateList 后点击失效) $(".obj_list > div").on("click", function () { - let imgId = $(this).find("img").attr("id"); + let imgId = $(this).find("img[id^='obj']").attr("id"); if (imgId) { - let idx = parseInt(imgId.replace("obj", "")); + let idx = parseInt(imgId.replace("obj", ""), 10); selectObj(idx); } }); @@ -1465,6 +1722,40 @@ window.updateList = function updateList() { // 标记是否通过对象列表选中(用于区分鼠标点击和列表点击) window._selectingFromList = false; +// 对象列表下标 → 画布对象(设为背景后 objs 与 canvas 一一对应) +function soonCanvasObjForListIndex(listIdx) { + var c = window.canvas || canvas; + var metaList = window.objs || objs; + if (!c || listIdx < 0 || listIdx >= metaList.length) return null; + var all = c.getObjects().filter(function (o) { return !o.isGuideLine; }); + var bgListedInObjs = metaList.length >= all.length; + if (bgListedInObjs) { + return all[listIdx] || null; + } + for (var j = 0; j < all.length; j++) { + var o = all[j]; + var ci = getIndex(o, c); + if (ci > 0 && ci - 1 === listIdx) return o; + } + return null; +} +window.soonCanvasObjForListIndex = soonCanvasObjForListIndex; + +/** 画布选中对象 → objs 下标;-1 表示模板背景(不在 objs 中) */ +function soonDesign2ResolveObjMetaIndex(select_obj, fabricCanvas) { + if (!select_obj || !fabricCanvas) return -1; + var metaList = fabricCanvas === canvas2 ? (window.objs2 || objs2) : (window.objs1 || objs1); + if (fabricCanvas === (window.canvas || canvas)) { + metaList = window.objs || objs || metaList; + } + if (!metaList || !metaList.length) return -1; + for (var li = 0; li < metaList.length; li++) { + if (soonCanvasObjForListIndex(li) === select_obj) return li; + } + return -1; +} +window.soonDesign2ResolveObjMetaIndex = soonDesign2ResolveObjMetaIndex; + // 将 selectObj 附加到 window 对象,确保全局可访问 window.selectObj = function selectObj(objIndices, isFromCanvas = false) { let indicesArray = (Array.isArray(objIndices)) ? objIndices : ((typeof objIndices === 'number' && objIndices >= 0) ? [objIndices] : []); @@ -1474,10 +1765,10 @@ window.selectObj = function selectObj(objIndices, isFromCanvas = false) { // 遍历列表查找并高亮 $('.obj_list > div').each(function () { - let $img = $(this).find("img"); + let $img = $(this).find("img[id^='obj']"); if ($img.length === 0) return; - let idIndex = parseInt($img.attr("id").replace("obj", "")); + let idIndex = parseInt($img.attr("id").replace("obj", ""), 10); if (indicesArray.includes(idIndex)) { $(this).css("background-color", "#6F7A84"); @@ -1491,7 +1782,7 @@ window.selectObj = function selectObj(objIndices, isFromCanvas = false) { // 如果是点击列表,触发画布选中 if (!isFromCanvas && indicesArray.length === 1) { - let target = canvas.getObjects()[indicesArray[0] + 1]; + let target = soonCanvasObjForListIndex(indicesArray[0]); if (target) { // 检查对象是否被锁定 let isLocked = isObjectLocked(target); @@ -1733,6 +2024,15 @@ function soonPlaceBgOnCanvas(image, canvasSelector) { image.selectable = false image.hoverable = false image.hoverCursor = 'default' + var fc = (canvasSelector === '#canvas2') ? canvas2 : canvas1; + if (fc && typeof soonDesign2SyncFrameAnchor === 'function') { + soonDesign2SyncFrameAnchor(fc, { + left: image.left, + top: image.top, + width: image.width * (image.get('scaleX') || 1), + height: image.height * (image.get('scaleY') || 1) + }); + } } function soonOpenPendingDesignFile() { @@ -2264,7 +2564,12 @@ var out_pic_op = slider.render({ if (canvas.getActiveObject()) { canvas.getActiveObject().set('opacity', 1 - value / 100); canvas.renderAll(); - updateControls(); + window._soonSyncingFontControls = true; + try { + updateControls(); + } finally { + window._soonSyncingFontControls = false; + } recordState(); } } @@ -2279,7 +2584,12 @@ var pic_op = slider.render({ if (canvas.getActiveObject()) { canvas.getActiveObject().set('opacity', 1 - value / 100); canvas.renderAll(); - updateControls(); + window._soonSyncingFontControls = true; + try { + updateControls(); + } finally { + window._soonSyncingFontControls = false; + } recordState(); } } diff --git a/frontend-web/js/design2/output.js b/frontend-web/js/design2/output.js index cc6164e..5c89c7e 100644 --- a/frontend-web/js/design2/output.js +++ b/frontend-web/js/design2/output.js @@ -1,3 +1,99 @@ +/** design2 正面预览/缩略图:1012×648 视口,与 display_func 中 url3 一致 */ +function soonCollectDesign2FrontPreview(canvas, bgImage, objMetaList) { + if (!canvas || !bgImage || typeof canvas.getObjects !== 'function') return null; + var list = canvas.getObjects().filter(function (obj) { return !obj.isGuideLine; }); + var g3 = []; + var all_left = 0; + var all_top = 0; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + var idx = getIndex(item, canvas); + var metaIdx = typeof window.soonDesign2ResolveObjMetaIndex === 'function' + ? window.soonDesign2ResolveObjMetaIndex(item, canvas) + : (idx > 0 ? idx - 1 : -1); + if (metaIdx < 0 && idx !== 0) continue; + if (metaIdx >= 0 && (!objMetaList[metaIdx] || typeof objMetaList[metaIdx].type === 'undefined')) continue; + if (getCoordsMinX(bgImage.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(bgImage.getCoords())); + } + if (getCoordsMinY(bgImage.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(bgImage.getCoords())); + } + g3.push(fabric.util.object.clone(item)); + } + return { g3: g3, all_left: all_left, all_top: all_top }; +} + +function soonExportDesign2PreviewDataUrl(g3, all_left, all_top, quality, multiplier) { + var z = (typeof zoom !== 'undefined' ? zoom : null) || window.zoom || 1; + var opts = { + height: 648, + width: 1012, + top: all_top / z, + left: all_left / z, + format: 'jpeg', + quality: quality == null ? 0.72 : quality + }; + if (multiplier != null && multiplier > 0) opts.multiplier = multiplier; + return new fabric.Group(g3).toDataURL(opts); +} + +/** 缩略图/预览:叠加系统卡面模板 front_bg(与编辑区 source_front 一致) */ +function soonDesign2AppendTemplateOverlayToGroup(g3, canvas, bgImage) { + if (!g3 || !canvas) return false; + var sf = document.getElementById('source_front'); + if (!sf || !(sf.complete && sf.naturalWidth > 0)) return false; + var frame = typeof window.soonDesign2FrameOrigin === 'function' + ? window.soonDesign2FrameOrigin(canvas) + : null; + if (!frame && bgImage && bgImage.width) { + var sx = bgImage.scaleX == null ? 1 : bgImage.scaleX; + var sy = bgImage.scaleY == null ? 1 : bgImage.scaleY; + frame = { left: bgImage.left, top: bgImage.top, width: bgImage.width * sx, height: bgImage.height * sy }; + } + if (!frame || !(frame.width > 0 && frame.height > 0)) return false; + g3.push(new fabric.Image(sf, { + left: frame.left, + top: frame.top, + originX: 'left', + originY: 'top', + scaleX: frame.width / sf.naturalWidth, + scaleY: frame.height / sf.naturalHeight + })); + return true; +} + +function soonBuildDesign2SavePreviewPic(canvas) { + var THUMB_MAX = 102400; + var c1 = window.canvas1; + if (!canvas || (c1 && canvas !== c1)) return ''; + var bg = window.background_image1 || window.background_image; + var meta = window.objs1; + if (!bg || !Array.isArray(meta)) return ''; + if (typeof canvas.renderAll === 'function') canvas.renderAll(); + var pack = soonCollectDesign2FrontPreview(canvas, bg, meta); + if (!pack || !pack.g3.length) return ''; + soonDesign2AppendTemplateOverlayToGroup(pack.g3, canvas, bg); + var quality = 0.72; + var mult = 0.55; + var url = ''; + for (var i = 0; i < 4; i++) { + try { + url = soonExportDesign2PreviewDataUrl(pack.g3, pack.all_left, pack.all_top, quality, mult); + if (url && url.indexOf('data:image/') === 0 && url.length <= THUMB_MAX) return url; + } catch (e) { /* ignore */ } + quality -= 0.16; + mult -= 0.08; + if (mult < 0.28) mult = 0.28; + } + try { + url = soonExportDesign2PreviewDataUrl(pack.g3, pack.all_left, pack.all_top, 0.32, 0.28); + if (url && url.indexOf('data:image/') === 0) return url; + } catch (e2) { /* ignore */ } + return ''; +} +window.soonBuildDesign2SavePreviewPic = soonBuildDesign2SavePreviewPic; + window.display_func = function display_func(img1, img2, img3) { $('#base_control').hide() $('#line_control').hide() @@ -1046,9 +1142,11 @@ window.cleanupSrcFields = function cleanupSrcFields(jsonObjects, objsArray) { let obj = jsonObjects[i]; // 只处理 fabric.Image 对象且有 src 字段的 if (obj && obj.type === 'image' && obj.src) { - // 背景图片(索引 0)- 删除 src + // 模板背景(索引 0)删 src;用户设为背景时 fo[0] 为 type 1,须保留 if (i === 0) { - delete obj.src; + if (!(objsArray[0] && objsArray[0].type === 1)) { + delete obj.src; + } } // 其他对象,通过 objs 数组判断类型 else if (i - 1 < objsArray.length) { @@ -1338,6 +1436,8 @@ window.openFile = function open(file, jAlready) { return; } openAs.name = file; + const fo = j.fo || [] + const bo = j.bo || [] var left = background_image.left let top = background_image.top let new_left = j.f.objects[0].left @@ -1350,18 +1450,25 @@ window.openFile = function open(file, jAlready) { o.left = o.left - new_left + left o.top = o.top - new_top + top } - // 如果背景图片没有src,从路径恢复(因为保存时删除了src以减小文件大小) - if (j.f.objects[0] && !j.f.objects[0].src) { + var isUserBgFront = typeof window.soonDesign2IsUserBgMeta === 'function' + ? window.soonDesign2IsUserBgMeta(fo) + : (fo[0] && fo[0].type === 1) + var isUserBgBack = typeof window.soonDesign2IsUserBgMeta === 'function' + ? window.soonDesign2IsUserBgMeta(bo) + : (bo[0] && bo[0].type === 1) + if (j.f.objects[0] && !j.f.objects[0].src && !isUserBgFront) { + j.f.objects[0].src = soonAsset('bg_front_2.png') + } else if (j.f.objects[0] && !j.f.objects[0].src && isUserBgFront && j.f.objects.length > 1) { j.f.objects[0].src = soonAsset('bg_front_2.png') } - if (j.b.objects[0] && !j.b.objects[0].src) { + if (j.b.objects[0] && !j.b.objects[0].src && !isUserBgBack) { + j.b.objects[0].src = soonAsset('bg_back_2.png') + } else if (j.b.objects[0] && !j.b.objects[0].src && isUserBgBack && j.b.objects.length > 1) { j.b.objects[0].src = soonAsset('bg_back_2.png') } // 为二维码和条形码对象创建临时的 src(Fabric.js 需要 src 来创建 Image 对象) // 加载完成后会重新生成,所以这里只需要一个占位符 - const fo = j.fo || [] - const bo = j.bo || [] const placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' // 为正面二维码、条形码和合成图片创建临时 src @@ -1492,6 +1599,9 @@ window.openFile = function open(file, jAlready) { } canvas1.loadFromJSON(j.f, function () { + if (typeof window.soonDesign2NormalizeUserBg === 'function') { + window.soonDesign2NormalizeUserBg(canvas1, fo); + } // 加载后解锁所有对象(背景除外) if (typeof window.unlockAllObjects === 'function') { window.unlockAllObjects(canvas1); @@ -1508,6 +1618,9 @@ window.openFile = function open(file, jAlready) { saveInitialState() }) canvas2.loadFromJSON(j.b, function () { + if (typeof window.soonDesign2NormalizeUserBg === 'function') { + window.soonDesign2NormalizeUserBg(canvas2, bo); + } // 加载后解锁所有对象(背景除外) if (typeof window.unlockAllObjects === 'function') { window.unlockAllObjects(canvas2); diff --git a/frontend-web/js/design2/ui.js b/frontend-web/js/design2/ui.js index 558c99b..f5047e6 100644 --- a/frontend-web/js/design2/ui.js +++ b/frontend-web/js/design2/ui.js @@ -365,6 +365,8 @@ $('#version_change').click(function () { if (typeof window.soonDesign2ScaleBgSources === 'function') { window.soonDesign2ScaleBgSources() } + canvas1.requestRenderAll() + canvas2.requestRenderAll() } sf.onerror = sf.onload sf.src = soonAsset('front_bg' + bg_version + '_2.png') @@ -785,62 +787,38 @@ $('#down_obj').click(function () { } }) $('#set_bg').click(function () { - //设置为背景 let obj = canvas.getActiveObject() - let idx = getIndex(canvas.getActiveObject()) - const scaleX = 1012 / obj.width - const scaleY = 648 / obj.height - // 使用缩放后的实际尺寸计算位置,确保与初始背景图片位置一致 - const scaledWidth = obj.width * scaleX; - const scaledHeight = obj.height * scaleY; - obj.set({ - scaleX: scaleX, - scaleY: scaleY, - left: ($('#canvas1').width() - scaledWidth) / 2, - top: ($('#canvas1').height() - scaledHeight) / 2 - }) - $('#text_w').val(1012) - $('#text_h').val(648) - $('#text_x').val(0) - $('#text_y').val(0) - // 设为背景时,锁定所有编辑操作,并设置为不可选中 - obj.set({ - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockSkewingX: true, - lockSkewingY: true, - selectable: false, - evented: false - }); + let idx = getIndex(obj) + var oldBg = background_image1 || background_image + var frame = typeof window.soonDesign2FrameOrigin === 'function' + ? window.soonDesign2FrameOrigin(canvas) + : { left: oldBg.left, top: oldBg.top } + if (typeof window.soonDesign2SnapImageToCardFrame === 'function') { + window.soonDesign2SnapImageToCardFrame(obj, canvas, frame) + } + var card = typeof window.soonDesign2CardSize === 'function' + ? window.soonDesign2CardSize() + : { w: 1012, h: 648 } + window._soonSyncingFontControls = true; + try { + $('#text_w').val(card.w) + $('#text_h').val(card.h) + $('#text_x').val(0) + $('#text_y').val(0) + } finally { + window._soonSyncingFontControls = false; + } + if (typeof window.soonDesign2LockBgObject === 'function') { + window.soonDesign2LockBgObject(obj) + } $("#lock_img").attr("src", soonAsset("lock20.png")) - canvas.sendToBack(canvas.getActiveObject()) - canvas.bringForward(canvas.getActiveObject()) + if (typeof window.soonDesign2ApplyUserBgAsCanvas0 === 'function') { + window.soonDesign2ApplyUserBgAsCanvas0(obj, canvas, oldBg) + } canvas.renderAll() let objItem = objs.splice(idx - 1, 1)[0] objs.unshift(objItem) - // 确保背景图(索引0)的锁定属性被正确设置 - setTimeout(() => { - let bgObj = canvas.getObjects()[0]; - if (bgObj) { - bgObj.set({ - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockSkewingX: true, - lockSkewingY: true, - selectable: false, - evented: false - }); - canvas.renderAll(); - } - }, 100); - recordState() if (typeof window.updateList === 'function') { window.updateList(); @@ -1385,23 +1363,6 @@ function selectObject(target, index) { $('#base_control').show() //$("#text_x").val((target.left - background_image.left).toFixed(1)); } -function updateControls() { - // 检查是否有选中对象,如果没有则直接返回 - if (!canvas || !canvas.getActiveObject()) return; - - const activeObj = canvas.getActiveObject(); - $('#text_r').val(activeObj.get('angle').toFixed(0)) - $('#text_x').val((activeObj.get('left') - background_image.left).toFixed(0)) - $('#text_y').val((activeObj.get('top') - background_image.top).toFixed(0)) - $('#text_h').val((activeObj.get('scaleY') * activeObj.get('height')).toFixed(0)) - $('#text_w').val((activeObj.get('scaleX') * activeObj.get('width')).toFixed(0)) - // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性) - if (activeObj.get('lockMovementX')) { - $('#lock_img').attr('src', soonAsset('lock20.png')) //已锁定 - } else { - $('#lock_img').attr('src', soonAsset('unlock20.png')) //未锁定 - } -} function checkName(name) { //检查字段名是否重复了 for (let item of objs1) { @@ -1444,8 +1405,8 @@ function selectPic(target, index) { let activeObj = canvas.getActiveObject(); - // 检查对象是否被锁定 - if (isObjectLocked(activeObj)) { + // 列表选中时允许锁定对象保持选中;画布点击仍不可选 + if (!window._selectingFromList && isObjectLocked(activeObj)) { canvas.discardActiveObject(); canvas.renderAll(); return; @@ -2092,6 +2053,8 @@ function getCoordsMaxY(acoords) { } $('#text_y').bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; + if (typeof window.soonDesign2ActiveIsAnchorBg === 'function' && window.soonDesign2ActiveIsAnchorBg()) return; if ($('#text_y').val() == '') { $('#text_y').val(0) } @@ -2104,6 +2067,8 @@ $('#text_y').bind('input propertychange', function () { recordState() }) $('#text_x').bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; + if (typeof window.soonDesign2ActiveIsAnchorBg === 'function' && window.soonDesign2ActiveIsAnchorBg()) return; if ($('#text_x').val() == '') { $('#text_x').val(0) } @@ -2116,11 +2081,17 @@ $('#text_x').bind('input propertychange', function () { recordState() }) $('#text_w').bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; + if (typeof window.soonDesign2ActiveIsAnchorBg === 'function' && window.soonDesign2ActiveIsAnchorBg()) return; let val = '0' if ($('#text_w').val() != '') { val = $('#text_w').val() } - let type = objs[getIndex(canvas.getActiveObject()) - 1].type + var metaIdx = typeof window.soonDesign2ResolveObjMetaIndex === 'function' + ? window.soonDesign2ResolveObjMetaIndex(canvas.getActiveObject(), canvas) + : getIndex(canvas.getActiveObject()) - 1; + if (metaIdx < 0 || !objs[metaIdx]) return; + let type = objs[metaIdx].type if (type == 3 || type == 4) { canvas.getActiveObject().set('width', parseInt(val)) } else { @@ -2131,8 +2102,13 @@ $('#text_w').bind('input propertychange', function () { recordState() }) $('#text_h').bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; + if (typeof window.soonDesign2ActiveIsAnchorBg === 'function' && window.soonDesign2ActiveIsAnchorBg()) return; let idx = getIndex(canvas.getActiveObject()) - if (objs[idx - 1].type == 7) { + var metaIdx = typeof window.soonDesign2ResolveObjMetaIndex === 'function' + ? window.soonDesign2ResolveObjMetaIndex(canvas.getActiveObject(), canvas) + : idx - 1; + if (metaIdx >= 0 && objs[metaIdx] && objs[metaIdx].type == 7) { if ($('#text_h').val() == '') { $('#text_h').val(canvas.getActiveObject().get('strokeWidth')) } @@ -2144,7 +2120,8 @@ $('#text_h').bind('input propertychange', function () { if ($('#text_h').val() != '') { val = $('#text_h').val() } - let type = objs[getIndex(canvas.getActiveObject()) - 1].type + if (metaIdx < 0 || !objs[metaIdx]) return; + let type = objs[metaIdx].type if (type == 3 || type == 4) { canvas.getActiveObject().set('height', parseInt(val)) } else { @@ -2155,6 +2132,7 @@ $('#text_h').bind('input propertychange', function () { recordState() }) $('#text_r').bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; if ($('#text_r').val() == '') { $('#text_r').val('0') } @@ -2257,6 +2235,7 @@ $('#out_text_back_color').bind('input propertychange', function () { recordState() }) $("#text_size").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; let val = 1 if (parseFloat($("#text_size").val())) { val = parseFloat($("#text_size").val()) @@ -2312,6 +2291,7 @@ $('#out_text_color').bind('input propertychange', function () { recordState() }) $("#out_text_size").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; let val = 1 if (parseFloat($("#out_text_size").val())) { val = parseFloat($("#out_text_size").val()) @@ -2417,6 +2397,7 @@ $('#counter_color').bind('input propertychange', function () { recordState() }) $("#count_size").bind('input propertychange', function () { + if (window._soonSyncingFontControls) return; let val = 1 if (parseFloat($("#count_size").val())) { val = parseFloat($("#count_size").val())