fix(web): design1/2 缩略图与 design2 设为背景交互修复
- design1:预览/最近文件缩略图按 background 边界截取,去掉多余白盘 - design2:设为背景贴齐 1012x648 卡框,保存/打开保留用户背景 src - design2:列表可选中锁定对象并加载完整属性,缩略图叠加 front_bg 模板 - design2:固定卡框锚点,避免选中背景时标尺/蒙版随属性面板漂移 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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,14 +145,21 @@
|
||||
}
|
||||
});
|
||||
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 });
|
||||
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;
|
||||
@@ -104,9 +168,6 @@
|
||||
quality -= 0.18;
|
||||
mult -= 0.04;
|
||||
}
|
||||
} finally {
|
||||
window._soonSkipDesign1CdMask = false;
|
||||
}
|
||||
hidden.forEach(function (obj) { obj.visible = true; });
|
||||
canvas.renderAll();
|
||||
if (typeof window.soonClearFabricOverlay === 'function') window.soonClearFabricOverlay(canvas);
|
||||
|
||||
@@ -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"))//已锁定
|
||||
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"))//未锁定
|
||||
$("#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();
|
||||
// 更新对象列表以同步锁状态
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
+368
-58
@@ -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"))//已锁定
|
||||
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"))//未锁定
|
||||
$("#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();
|
||||
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();
|
||||
window._soonSyncingFontControls = true;
|
||||
try {
|
||||
updateControls();
|
||||
} finally {
|
||||
window._soonSyncingFontControls = false;
|
||||
}
|
||||
recordState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 +1142,12 @@ 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) {
|
||||
if (!(objsArray[0] && objsArray[0].type === 1)) {
|
||||
delete obj.src;
|
||||
}
|
||||
}
|
||||
// 其他对象,通过 objs 数组判断类型
|
||||
else if (i - 1 < objsArray.length) {
|
||||
let objMeta = objsArray[i - 1];
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
// 设为背景时,锁定所有编辑操作,并设置为不可选中
|
||||
obj.set({
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
lockSkewingX: true,
|
||||
lockSkewingY: true,
|
||||
selectable: false,
|
||||
evented: false
|
||||
});
|
||||
} 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())
|
||||
|
||||
Reference in New Issue
Block a user