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:
24kycj
2026-06-25 19:26:17 +08:00
parent 0f196bdec8
commit 784cacf259
7 changed files with 746 additions and 183 deletions
+120 -7
View File
@@ -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);