Compare commits

..

6 Commits

Author SHA1 Message Date
24kycj 381a48b5c5 优化图片和显示问题 2026-09-22 17:39:48 +08:00
24kycj 0f26163ad7 优化调整bug 2026-06-30 09:18:08 +08:00
24kycj ce76bb8018 fix(web): 修复 .soon/模板加载、旧文件兼容与另存为离开提示
- 删除 design1 ui 遗留代码,修复加载竞态与新建不弹保存提示
- cloud-files 归一化旧版 .soon,IndexedDB 写入完成后再跳转设计页
- 模板/本地 .soon 首次保存须另存为,离开前统一确认
- 发版缓存 bust(version.js、soon-cache.js)及宝塔 Nginx 配置文档

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 00:30:20 +08:00
24kycj efeb3bcc41 feat(design1): 光盘导出环形裁剪与画布/缩略图样式统一
导出与预览 PNG 按光盘外圆与中心孔透明裁剪;画布蒙版改为与背景同色 #31373D;缩略图改 PNG 环形铺底;Web/Electron 同步。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 22:16:15 +08:00
24kycj c24004d6c7 优化调整 2026-06-28 18:54:37 +08:00
24kycj 784cacf259 fix(web): design1/2 缩略图与 design2 设为背景交互修复
- design1:预览/最近文件缩略图按 background 边界截取,去掉多余白盘
- design2:设为背景贴齐 1012x648 卡框,保存/打开保留用户背景 src
- design2:列表可选中锁定对象并加载完整属性,缩略图叠加 front_bg 模板
- design2:固定卡框锚点,避免选中背景时标尺/蒙版随属性面板漂移

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 19:26:17 +08:00
40 changed files with 1569 additions and 408 deletions
+35
View File
@@ -0,0 +1,35 @@
---
description: .soon 文件 JSON 契约(与 soonDesign-back / soonWorkerDisk 对齐)
globs:
- frontend-web/js/design1/**
- frontend-web/js/design2/**
- frontend-electron/js/design1/**
- frontend-electron/js/design2/**
- frontend-web/js/common/cloud-files.js
alwaysApply: false
---
# .soon 文件格式(不可擅自变更)
## 权威参考
- 旧版程序:`soonDesign-back/lib/design1/output.js`、`design2/output.js`gitignore,本机对照)
- 下游消费:`soonWorkerDisk/.../workAdd.vue`(依赖 `frontData`、`backData`、`frontDisplayPic` 等)
## 保存契约
`output()` 生成的生产字段与画布字段**必须完整写入**,禁止在 save/upload/session/IDB 路径删除或改名:
- 生产:`frontData`、`backData`、`frontColorPic`、`backColorPic`、`frontBlackPic`、`backBlackPic`、`frontDisplayPic`、`backDisplayPic`、`flag`、`soonType`
- 画布:`f`、`b`、`fo`、`bo`(及旧版可能有的 `fp`、`bp`
保存模式:`Object.assign({ f, b, fo, bo, ... }, op1)` → `JSON.stringify`,与 `soonDesign-back` 一致。
## 禁止
- 保存/上传/缓存前 `delete` 上述生产字段(历史上 `soonStripDesign1HeavyFields` 类逻辑)
- 仅保留 `f/b/fo/bo` 的「精简 .soon」写法
## 加载
`soonNormalizeSoonJson` 只允许补全缺失的 `f/b/fo/bo`(兼容极旧文件),**不得 strip** 生产字段,以便打开后另存仍满足下游。
+1
View File
@@ -42,6 +42,7 @@ scripts\start.bat
- 数据库:在 `backend-web` 目录执行 `mysql ... < database/schema.sql`;已有库 `php scripts/migrate-schema.php`
- 后端:`backend-web/config/local.php.example``local.php`
- 前端 API`frontend-web/config/local.js`(生产配置,勿覆盖)
- Web 静态缓存:`scripts/write-web-version.ps1` 会随 `start.bat` 自动写入 `config/version.js`;线上 Nginx 请对齐 [docker/nginx/frontend.conf](docker/nginx/frontend.conf)HTML/JS 禁强缓存)
支付与 notify 说明见 [docs/PAYMENT.md](docs/PAYMENT.md)。列表分页契约见 [docs/API-PAGINATION.md](docs/API-PAGINATION.md)。
+11 -1
View File
@@ -9,8 +9,18 @@ server {
location = / { return 302 /pages/index.web.html; }
location = /pages { return 302 /pages/index.web.html; }
location = /pages/ { return 302 /pages/index.web.html; }
location ~* \.(html|web\.html)$ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files $uri =404;
}
location = /config/version.js {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
try_files $uri =404;
}
location ~* \.(js|css)$ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
try_files $uri =404;
}
location / { try_files $uri $uri/ =404; }
+2
View File
@@ -70,3 +70,5 @@ $enc = [Text.UTF8Encoding]::new($false)
$webLocal = Join-Path $Root 'frontend-web\config\local.js'
New-Item -ItemType Directory -Force -Path (Split-Path $webLocal -Parent) | Out-Null
[IO.File]::WriteAllText($webLocal, $localJs.Trim() + "`n", $enc)
& (Join-Path $Root 'scripts\write-web-version.ps1') | Out-Null
+91
View File
@@ -0,0 +1,91 @@
server
{
listen 80;
listen 443 ssl;
listen 443 quic;
http2 on;
server_name design.cardsoon.com;
index pages/index.web.html index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/design.cardsoon.com;
#CERT-APPLY-CHECK--START
# 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
include /www/server/panel/vhost/nginx/well-known/design.cardsoon.com.conf;
#CERT-APPLY-CHECK--END
include /www/server/panel/vhost/nginx/extension/design.cardsoon.com/*.conf;
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
ssl_certificate /www/server/panel/vhost/cert/design.cardsoon.com/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/design.cardsoon.com/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-00.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/design.cardsoon.com.conf;
#REWRITE-END
rewrite ^/$ /pages/index.web.html redirect;
rewrite ^/pages/?$ /pages/index.web.html redirect;
location = /config/version.js {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
location ~* \.html$ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
location ~* \.(js|css)$ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|webp|ico|svg)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/design.cardsoon.com.log;
error_log /www/wwwlogs/design.cardsoon.com.error.log;
}
+12 -5
View File
@@ -325,12 +325,19 @@ textarea:hover{
}
.transparent-button{
display: inline-block;
height: 19px;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-width: 40px;
height: 24px;
padding: 0 6px;
border: #646E76 solid 1px;
position: relative;
bottom: 2px;
padding: 3px;
font-size: 12px;
line-height: 1;
white-space: nowrap;
cursor: pointer;
vertical-align: middle;
}
.transparent-button:hover{
background-color: #646E76;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+2 -2
View File
@@ -1751,7 +1751,7 @@ function initCanvasEvents() {
const ctx = currentCanvas.getContext();
let bgObj = (currentCanvas === canvas1) ? background_image1 : background_image2;
if (bgObj) {
if (bgObj && !window._soonSkipDesign1CdMask) {
const width = currentCanvas.width;
const height = currentCanvas.height;
@@ -1795,7 +1795,7 @@ function initCanvasEvents() {
ctx.arc(cx, cy, r_inner, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; // 90% 遮罩
ctx.fillStyle = '#31373D';
ctx.fill();
ctx.restore();
}
+161 -21
View File
@@ -51,6 +51,118 @@ async function saveImageAsPNG(buffer) {
}
}
function soonDesign1GetHoleDiameterMm(canvas) {
var hole = 24;
if (canvas === window.canvas1 && typeof window.bg_version !== 'undefined' && window.bg_version === 2) {
hole = 42;
}
return hole;
}
function soonDesign1ApplyDiscAlpha(tctx, w, h, holeDiameterMm) {
var cx = w / 2;
var cy = h / 2;
var rOuter = w / 2;
var rInner = rOuter * (holeDiameterMm / 120);
tctx.save();
tctx.globalCompositeOperation = 'destination-in';
tctx.fillStyle = '#fff';
tctx.beginPath();
tctx.arc(cx, cy, rOuter, 0, Math.PI * 2);
tctx.moveTo(cx + rInner, cy);
tctx.arc(cx, cy, rInner, 0, Math.PI * 2, true);
tctx.fill('evenodd');
tctx.restore();
}
function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality, clipDisc) {
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 (clipDisc && format !== 'jpeg') {
soonDesign1ApplyDiscAlpha(tctx, outW, outH, soonDesign1GetHoleDiameterMm(canvas));
}
if (format === 'jpeg') return tmp.toDataURL('image/jpeg', quality == null ? 0.75 : quality);
return tmp.toDataURL('image/png');
}
function soonDesign1CaptureDiscPng(canvas, outW, outH) {
window._soonSkipDesign1CdMask = true;
try {
if (canvas.discardActiveObject) canvas.discardActiveObject();
canvas.renderAll();
return soonDesign1CaptureCanvasRect(canvas, outW, outH, 'png', null, true);
} finally {
window._soonSkipDesign1CdMask = false;
canvas.renderAll();
}
}
function soonNormalizeSoonJson(j) {
if (!j || typeof j !== 'object') return null;
if (!j.f || !j.f.objects || !Array.isArray(j.f.objects)) {
if (j.objects && Array.isArray(j.objects) && j.objects.length > 0) {
j.f = { version: j.version || '4.6.0', objects: j.objects, hoverCursor: j.hoverCursor || 'move' };
} else {
return null;
}
}
if (j.f.objects.length === 0) return null;
if (!j.b || !j.b.objects || !Array.isArray(j.b.objects)) {
j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
}
if (!j.fo) j.fo = [];
if (!j.bo) j.bo = [];
var o0 = j.f.objects[0];
if (o0) {
if (o0.left == null || isNaN(o0.left)) o0.left = 0;
if (o0.top == null || isNaN(o0.top)) o0.top = 0;
}
return j;
}
window.soonNormalizeSoonJson = soonNormalizeSoonJson;
function soonRunWhenDesignBgReady(fn, onTimeout) {
if (typeof fn !== 'function') return;
function ready() {
return !!(window.background_image1 || window.background_image);
}
if (ready()) {
fn();
return;
}
var n = 0;
(function tick() {
if (ready()) {
fn();
return;
}
if (++n > 120) {
if (typeof onTimeout === 'function') onTimeout();
return;
}
setTimeout(tick, 50);
})();
}
window.soonRunWhenDesignBgReady = soonRunWhenDesignBgReady;
// 将 display_func 附加到 window 对象,确保全局可访问
window.display_func = function display_func(img1, img2, img3) {
$("#base_control").hide();
@@ -217,13 +329,16 @@ 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;
}
});
let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787);
hiddenForPreview.forEach(function (o) { o.visible = true; });
canvas1.renderAll();
let url5 = new fabric.Group(g5).toDataURL({
height: 2787,
width: 2787,
@@ -596,12 +711,16 @@ window.output = function output(callback = null, _save = save) {
left: all_left
});
g3.push(img1);
let url3 = new fabric.Group(g3).toDataURL({
height: 2787,
width: 2787,
top: all_top,
left: all_left
var hiddenForSave = [];
canvas1.getObjects().forEach(function (o) {
if (o.isGuideLine) {
hiddenForSave.push(o);
o.visible = false;
}
});
let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787);
hiddenForSave.forEach(function (o) { o.visible = true; });
canvas1.renderAll();
let url5 = new fabric.Group(g5).toDataURL({
height: 2787,
width: 2787,
@@ -1079,14 +1198,20 @@ window.regenerateQrCodesAndBarcodes = function regenerateQrCodesAndBarcodes(canv
window.openFile = function open(file, jAlready) {
if (!file) return;
function doOpenWithJson(j) {
if (!j || !background_image) return;
if (!j.f || !j.f.objects || !Array.isArray(j.f.objects) || j.f.objects.length === 0) return;
if (!j.b || !j.b.objects || !Array.isArray(j.b.objects) || j.b.objects.length === 0) {
j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
j = soonNormalizeSoonJson(j);
if (!j) {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
return;
}
function runOpen() {
var bgRef = window.background_image1 || window.background_image;
if (!bgRef) {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
return;
}
openAs.name = file;
var left = background_image.left;
var top = background_image.top;
var left = bgRef.left;
var top = bgRef.top;
var new_left = j.f.objects[0].left;
var new_top = j.f.objects[0].top;
for (var i = 0; i < j.f.objects.length; i++) {
@@ -1158,6 +1283,10 @@ window.openFile = function open(file, jAlready) {
}
});
}
soonRunWhenDesignBgReady(runOpen, function () {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
});
}
if (arguments.length >= 2 && jAlready) {
doOpenWithJson(jAlready);
openAs.name = file;
@@ -1165,12 +1294,22 @@ window.openFile = function open(file, jAlready) {
}
file = resolveDiskPath(file);
if (window.platformBridge && window.platformBridge.readJsonFile) {
if (typeof window.soonShowOpenFileLoading === 'function') window.soonShowOpenFileLoading();
return window.platformBridge.readJsonFile(file).then(function(j) {
if (typeof window.soonHideOpenFileLoading === 'function') window.soonHideOpenFileLoading();
if (j) {
doOpenWithJson(j);
openAs.name = file;
} else if (typeof window.soonReportOpenLoadError === 'function') {
window.soonReportOpenLoadError('模板/文件加载失败');
}
}).catch(function() {});
}).catch(function () {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
else if (typeof window.soonHideOpenFileLoading === 'function') window.soonHideOpenFileLoading();
}).finally(function () {
if (typeof window.soonHideOpenFileLoading === 'function') window.soonHideOpenFileLoading();
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
});
}
try {
var fs = typeof require !== 'undefined' ? require('fs') : null;
@@ -1260,6 +1399,8 @@ function saveAs(op1, callback) {
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
let con_o = Object.assign(o, op1);
con_o.soonType = 1;
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
dialogApi.showSaveDialog({
@@ -1272,7 +1413,6 @@ function saveAs(op1, callback) {
var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
var content = JSON.stringify(con_o);
con_o.soonType = 1;
if (window.platformBridge && window.platformBridge.writeFile) {
await window.platformBridge.writeFile(result.fileHandle || fp, content);
openAs.name = isWebPortal() ? ('soondesign_session:' + fp) : (result.filePath || fp);
@@ -1326,8 +1466,9 @@ function save(op1, callback) {
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
let con_o = Object.assign(o, op1);
if (openAs.name != "") {
con_o.soonType = 1;
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
if (openAs.name != "") {
var content = JSON.stringify(con_o);
if (isWebPortal() && openAs.name.indexOf('soondesign_session:') === 0) {
webSessionStore(content, openAs.name);
@@ -1371,7 +1512,6 @@ function save(op1, callback) {
var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
var content = JSON.stringify(con_o);
con_o.soonType = 1;
if (window.platformBridge && window.platformBridge.writeFile) {
await window.platformBridge.writeFile(result.fileHandle || fp, content);
openAs.name = isWebPortal() ? ('soondesign_session:' + fp) : (result.filePath || fp);
File diff suppressed because one or more lines are too long
+29 -5
View File
@@ -781,17 +781,22 @@ $('#down_obj').click(function () {
$('#set_bg').click(function () {
//设置为背景
let obj = canvas.getActiveObject()
let idx = getIndex(canvas.getActiveObject())
if (!obj) return;
let idx = getIndex(obj)
if (idx <= 0) return;
const scaleX = 1012 / obj.width
const scaleY = 648 / obj.height
// 使用缩放后的实际尺寸计算位置,确保与初始背景图片位置一致
// 修正:使用当前活动画布的容器宽度,背面应取 canvas2 的容器宽度
const containerW = canvas.width || 1012;
const containerH = canvas.height || 648;
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
left: (containerW - scaledWidth) / 2,
top: (containerH - scaledHeight) / 2
})
$('#text_w').val(1012)
$('#text_h').val(648)
@@ -810,12 +815,31 @@ $('#set_bg').click(function () {
evented: false
});
$("#lock_img").attr("src", soonAsset("lock20.png"))
canvas.sendToBack(canvas.getActiveObject())
canvas.bringForward(canvas.getActiveObject())
// 修正:先 remove 当前画布上的旧背景占位图(正面 background_image1 / 背面 background_image2),
// 否则 sendToBack 后用户图被压到最底层,仍会被占位图覆盖,肉眼看不到。
var oldBg = (canvas === canvas2) ? background_image2 : background_image1;
if (oldBg && oldBg !== obj) {
canvas.remove(oldBg);
}
canvas.sendToBack(obj);
canvas.bringForward(obj);
canvas.renderAll()
let objItem = objs.splice(idx - 1, 1)[0]
objs.unshift(objItem)
// 同步 background_image 引用:背面时更新 background_image2,正面更新 background_image1
if (canvas === canvas2) {
background_image2 = obj;
window.background_image2 = obj;
} else {
background_image1 = obj;
window.background_image1 = obj;
}
background_image = obj;
window.background_image = obj;
// 确保背景图(索引0)的锁定属性被正确设置
setTimeout(() => {
let bgObj = canvas.getObjects()[0];
+12 -5
View File
@@ -355,12 +355,19 @@ textarea:hover{
}
.transparent-button{
display: inline-block;
height: 19px;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-width: 40px;
height: 24px;
padding: 0 6px;
border: #646E76 solid 1px;
position: relative;
bottom: 2px;
padding: 3px;
font-size: 12px;
line-height: 1;
white-space: nowrap;
cursor: pointer;
vertical-align: middle;
}
.transparent-button:hover{
background-color: #646E76;
Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+4
View File
@@ -0,0 +1,4 @@
(function () {
'use strict';
window.SOON_APP_VERSION = '0f26163';
})();
+2 -1
View File
@@ -36,7 +36,8 @@
window.soonJs = function (rel) {
var n = String(rel || '').replace(/^\/+/, '');
return window.SOON_JS_BASE + n;
var url = window.SOON_JS_BASE + n;
return (typeof window.soonWithVersion === 'function') ? window.soonWithVersion(url) : url;
};
/** 等 HTMLImage 解码完成后再 toDataURL / setSrc,避免间歇性空白背景 */
+214 -27
View File
@@ -3,6 +3,7 @@
var FILE_PREFIX = 'soondesign_file:';
var TEMPLATE_PREFIX = 'soondesign_template:';
var SESSION_PREFIX = 'soondesign_session:';
function soonParseTemplateKey(key) {
if (!key || typeof key !== 'string') return null;
@@ -39,12 +40,60 @@
return FILE_PREFIX + id + ':v' + version;
}
function soonIsCloudFileKey(key) {
return !!(key && typeof key === 'string' && key.indexOf(FILE_PREFIX) === 0);
}
function soonIsTemplateKey(key) {
return !!(key && typeof key === 'string' && key.indexOf(TEMPLATE_PREFIX) === 0);
}
/** 打开模板 / 本地 .soon / session 导入:首次保存须另存为;云端文件或已选定保存目标后可原地保存 */
function soonNeedsSaveDialog(key) {
return !key || soonIsTemplateKey(key);
if (!key) return true;
if (soonIsCloudFileKey(key)) return false;
if (window._soonSaveInPlaceKey && key === window._soonSaveInPlaceKey) return false;
return true;
}
function soonOnOpenDesignFile(fileKey) {
window._soonSaveInPlaceKey = '';
}
function soonMarkSaveInPlace(fileKey) {
if (fileKey) window._soonSaveInPlaceKey = fileKey;
}
/** 离开页/新建前是否需提示保存:有未撤销编辑,或已打开过文件/模板(含未改动的打开态) */
function soonShouldConfirmBeforeLeave() {
try {
if (typeof window.step !== 'undefined' && window.step && window.step.val > 0) return true;
} catch (e) { /* ignore */ }
var oa = typeof window.openAs !== 'undefined' ? window.openAs : null;
return !!(oa && oa.name);
}
function soonConfirmLeaveThen(run) {
if (typeof run !== 'function') return;
if (typeof window.soonShouldConfirmBeforeLeave === 'function' && !window.soonShouldConfirmBeforeLeave()) {
run();
return;
}
if (typeof window.layer === 'undefined' || !window.layer || typeof window.language_str !== 'function') {
run();
return;
}
window.layer.confirm(window.language_str('whetherSave'), {
btn: [window.language_str('save'), window.language_str('noSave'), window.language_str('cancel')]
}, function () {
if (typeof window.output === 'function') {
window.output(function () { run(); });
} else {
run();
}
}, function () {
run();
});
}
function soonDefaultNewSoonName() {
@@ -77,9 +126,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 +194,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 +217,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);
@@ -562,38 +672,108 @@
return j && j.soonType ? j.soonType : (j && j.backBlackPic ? 2 : 1);
}
function soonPutSoonSession(j, fileName) {
if (!j) return '';
/** 兼容旧版 .soon:补全 f/b/fo/bo,保留生产字段(frontData 等) */
function soonNormalizeSoonJson(j) {
if (!j || typeof j !== 'object') return null;
if (!j.f || !j.f.objects || !Array.isArray(j.f.objects)) {
if (j.objects && Array.isArray(j.objects) && j.objects.length > 0) {
j.f = {
version: j.version || '4.6.0',
objects: j.objects,
hoverCursor: j.hoverCursor || 'move'
};
} else {
return null;
}
}
if (j.f.objects.length === 0) return null;
if (!j.b || !j.b.objects || !Array.isArray(j.b.objects)) {
j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
}
if (!j.fo) j.fo = [];
if (!j.bo) j.bo = [];
var o0 = j.f.objects[0];
if (o0) {
if (o0.left == null || isNaN(o0.left)) o0.left = 0;
if (o0.top == null || isNaN(o0.top)) o0.top = 0;
}
return j;
}
function soonRunWhenDesignBgReady(fn, onTimeout) {
if (typeof fn !== 'function') return;
function ready() {
return !!(window.background_image1 || window.background_image);
}
if (ready()) {
fn();
return;
}
var n = 0;
(function tick() {
if (ready()) {
fn();
return;
}
if (++n > 120) {
if (typeof onTimeout === 'function') onTimeout();
return;
}
setTimeout(tick, 50);
})();
}
function soonPrepareSoonSessionWrite(j, fileName) {
var normalized = soonNormalizeSoonJson(j);
if (!normalized) return null;
var displayName = soonEnsureSoonExt(fileName || 'design.soon');
var key = soonMakeSessionKey(displayName);
if (typeof window.soonLocalCacheAndThumb === 'function') {
window.soonLocalCacheAndThumb(key, j, { source: 'session', name: displayName });
try { sessionStorage.setItem(key, 'idb'); } catch (e) { /* ignore */ }
return key;
}
var payload = '';
try { payload = JSON.stringify(normalized); } catch (e) { return null; }
try {
sessionStorage.setItem(key, JSON.stringify(j));
return key;
sessionStorage.setItem(key, payload);
} catch (e) {
return '';
try {
sessionStorage.setItem(key, 'idb');
} catch (e2) {
return null;
}
}
return { key: key, slim: normalized, displayName: displayName, normalized: normalized };
}
function soonPutSoonSession(j, fileName) {
var prep = soonPrepareSoonSessionWrite(j, fileName);
if (!prep) return '';
if (typeof window.soonLocalCacheAndThumb === 'function') {
window.soonLocalCacheAndThumb(prep.key, prep.slim, { source: 'session', name: prep.displayName });
}
return prep.key;
}
function soonOpenSoonJsonLocally(j, fileName) {
var key = soonPutSoonSession(j, fileName);
if (!key) {
soonToast('无法打开文件(存储空间不足)', 'error');
return '';
var prep = soonPrepareSoonSessionWrite(j, fileName);
if (!prep) {
soonToast('无法打开文件(格式无效或存储空间不足)', 'error');
return Promise.resolve('');
}
var cacheP = typeof window.soonLocalCacheAndThumb === 'function'
? window.soonLocalCacheAndThumb(prep.key, prep.slim, { source: 'session', name: prep.displayName })
: Promise.resolve(true);
return cacheP.then(function () {
if (typeof window.soonRecentUpsertFromOpen === 'function') {
window.soonRecentUpsertFromOpen(key, j);
window.soonRecentUpsertFromOpen(prep.key, prep.normalized);
}
soonScheduleCloudImport(key, fileName);
var type = soonSoonTypeFromJson(j);
soonScheduleCloudImport(prep.key, fileName);
var type = soonSoonTypeFromJson(prep.normalized);
if (window.platformBridge && window.platformBridge.openDesignPage) {
window.platformBridge.openDesignPage(key, type);
window.platformBridge.openDesignPage(prep.key, type);
}
return key;
return prep.key;
}).catch(function () {
soonToast('无法打开文件(本地缓存写入失败)', 'error');
return '';
});
}
var PENDING_IMPORT_KEY = 'soondesign_pending_import';
@@ -883,6 +1063,8 @@
window.soonParseTemplateKey = soonParseTemplateKey;
window.soonMakeTemplateKey = soonMakeTemplateKey;
window.soonSoonTypeFromJson = soonSoonTypeFromJson;
window.soonNormalizeSoonJson = soonNormalizeSoonJson;
window.soonRunWhenDesignBgReady = soonRunWhenDesignBgReady;
window.soonLoadMembership = soonLoadMembership;
window.soonApplyMembership = soonApplyMembership;
window.soonRefreshPortalIdentity = soonRefreshPortalIdentity;
@@ -892,7 +1074,12 @@
window.soonShowApiError = soonShowApiError;
window.soonDisplayFileName = soonDisplayFileName;
window.soonIsTemplateKey = soonIsTemplateKey;
window.soonIsCloudFileKey = soonIsCloudFileKey;
window.soonNeedsSaveDialog = soonNeedsSaveDialog;
window.soonOnOpenDesignFile = soonOnOpenDesignFile;
window.soonMarkSaveInPlace = soonMarkSaveInPlace;
window.soonShouldConfirmBeforeLeave = soonShouldConfirmBeforeLeave;
window.soonConfirmLeaveThen = soonConfirmLeaveThen;
window.soonDefaultNewSoonName = soonDefaultNewSoonName;
window.soonEnsureSoonExt = soonEnsureSoonExt;
window.soonSessionSlug = soonSessionSlug;
+53
View File
@@ -0,0 +1,53 @@
(function () {
'use strict';
var v = window.SOON_APP_VERSION || '';
window.soonWithVersion = function (url) {
if (!url || /^https?:\/\//i.test(url) || /^data:/i.test(url)) return url;
if (!v) return url;
var s = String(url);
var hashIdx = s.indexOf('#');
var hash = hashIdx >= 0 ? s.slice(hashIdx) : '';
var noHash = hashIdx >= 0 ? s.slice(0, hashIdx) : s;
return noHash.split('?')[0] + '?v=' + encodeURIComponent(v) + hash;
};
window.soonPatchForward = function (fromEl) {
var fn = window.soonWithVersion;
if (typeof fn !== 'function') return;
var el = fromEl && fromEl.nextElementSibling;
while (el) {
if (el.tagName === 'SCRIPT') {
var src = el.getAttribute('src');
if (src && src.indexOf('version.js') < 0 && src.indexOf('soon-cache.js') < 0) {
el.src = fn(src);
}
} else if (el.tagName === 'LINK' && el.rel === 'stylesheet') {
var href = el.getAttribute('href');
if (href) el.href = fn(href);
}
el = el.nextElementSibling;
}
};
if (v) {
var KEY = 'soondesign_app_version';
try {
var prev = localStorage.getItem(KEY);
if (prev && prev !== v) {
localStorage.setItem(KEY, v);
var url = new URL(window.location.href);
if (url.searchParams.get('_sv') !== v) {
url.searchParams.set('_sv', v);
window.location.replace(url.toString());
return;
}
}
localStorage.setItem(KEY, v);
} catch (e) { /* ignore */ }
}
if (document.currentScript) {
window.soonPatchForward(document.currentScript);
}
})();
+1 -1
View File
@@ -502,7 +502,7 @@ if (ipcRenderer) {
// 网页端:按顺序动态插入 script,确保 core/ui 执行时 window.$ 已存在
function loadScript(src, next) {
var s = document.createElement('script');
s.src = src;
s.src = (typeof window.soonWithVersion === 'function') ? window.soonWithVersion(src) : src;
s.onload = s.onerror = function () { if (typeof next === 'function') next(); };
document.body.appendChild(s);
}
+57 -21
View File
@@ -262,6 +262,13 @@ if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
applySysLan(window.platformBridge.getLocale());
}
function onCloseConfirm() {
if (typeof window.soonConfirmLeaveThen === 'function') {
window.soonConfirmLeaveThen(function () {
if (ipcRenderer) ipcRenderer.send('run-close');
else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose();
});
return;
}
layer.confirm(language_str("whetherSave"), {
btn: [language_str("save"), language_str("noSave"), language_str("cancel")]
, btn3: function (index, layero) {}
@@ -448,21 +455,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 +1819,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();
// 更新对象列表以同步锁状态
@@ -1860,7 +1896,7 @@ function initCanvasEvents() {
ctx.arc(cx, cy, r_inner, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; // 90% 遮罩
ctx.fillStyle = '#31373D';
ctx.fill();
ctx.restore();
}
+137 -18
View File
@@ -52,6 +52,104 @@ async function saveImageAsPNG(buffer) {
}
}
/** 孔径规则与 core.js after:render 一致 */
function soonDesign1GetHoleDiameterMm(canvas) {
var hole = 24;
if (canvas === window.canvas1 && typeof window.bg_version !== 'undefined' && window.bg_version === 2) {
hole = 42;
}
return hole;
}
/** 圆外四角与中心孔 alpha 置 0,保留环形可打印区 */
function soonDesign1ApplyDiscAlpha(tctx, w, h, holeDiameterMm) {
var cx = w / 2;
var cy = h / 2;
var rOuter = w / 2;
var rInner = rOuter * (holeDiameterMm / 120);
tctx.save();
tctx.globalCompositeOperation = 'destination-in';
tctx.fillStyle = '#fff';
tctx.beginPath();
tctx.arc(cx, cy, rOuter, 0, Math.PI * 2);
tctx.moveTo(cx + rInner, cy);
tctx.arc(cx, cy, rInner, 0, Math.PI * 2, true);
tctx.fill('evenodd');
tctx.restore();
}
function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality, clipDisc) {
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 (clipDisc && format !== 'jpeg') {
soonDesign1ApplyDiscAlpha(tctx, outW, outH, soonDesign1GetHoleDiameterMm(canvas));
}
if (format === 'jpeg') return tmp.toDataURL('image/jpeg', quality == null ? 0.75 : quality);
return tmp.toDataURL('image/png');
}
/** 导出/打印:跳过编辑器暗蒙版,按光盘环形裁剪 */
function soonDesign1CaptureDiscPng(canvas, outW, outH) {
window._soonSkipDesign1CdMask = true;
try {
if (canvas.discardActiveObject) canvas.discardActiveObject();
canvas.renderAll();
return soonDesign1CaptureCanvasRect(canvas, outW, outH, 'png', null, true);
} finally {
window._soonSkipDesign1CdMask = false;
canvas.renderAll();
}
}
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();
window._soonSkipDesign1CdMask = true;
canvas.renderAll();
var url = '';
var sizes = [280, 240, 200, 180, 160];
for (var i = 0; i < sizes.length; i++) {
url = soonDesign1CaptureCanvasRect(canvas, sizes[i], sizes[i], 'png', null, true);
if (url && url.indexOf('data:image/') === 0 && url.length <= THUMB_MAX) break;
url = '';
}
window._soonSkipDesign1CdMask = false;
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 +315,16 @@ 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;
}
});
let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787);
hiddenForPreview.forEach(function (o) { o.visible = true; });
canvas1.renderAll();
let url5 = new fabric.Group(g5).toDataURL({
height: 2787,
width: 2787,
@@ -614,12 +715,16 @@ window.output = function output(callback = null, _save = save) {
left: all_left
});
g3.push(img1);
let url3 = new fabric.Group(g3).toDataURL({
height: 2787,
width: 2787,
top: all_top,
left: all_left
var hiddenForSave = [];
canvas1.getObjects().forEach(function (o) {
if (o.isGuideLine) {
hiddenForSave.push(o);
o.visible = false;
}
});
let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787);
hiddenForSave.forEach(function (o) { o.visible = true; });
canvas1.renderAll();
let url5 = new fabric.Group(g5).toDataURL({
height: 2787,
width: 2787,
@@ -1096,22 +1201,25 @@ window.regenerateQrCodesAndBarcodes = function regenerateQrCodesAndBarcodes(canv
// 使用 window.openFile 避免与浏览器原生的 window.open 冲突
window.openFile = function open(file, jAlready) {
if (!file) return;
if (typeof window.soonOnOpenDesignFile === 'function') window.soonOnOpenDesignFile(file);
if (typeof window.soonBindFileMeta === 'function') window.soonBindFileMeta(file);
function doOpenWithJson(j) {
if (!j || !background_image) {
if (typeof window.soonNormalizeSoonJson === 'function') {
j = window.soonNormalizeSoonJson(j);
}
if (!j) {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
return;
}
if (!j.f || !j.f.objects || !Array.isArray(j.f.objects) || j.f.objects.length === 0) {
function runOpen() {
var bgRef = window.background_image1 || window.background_image;
if (!bgRef) {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
return;
}
if (!j.b || !j.b.objects || !Array.isArray(j.b.objects) || j.b.objects.length === 0) {
j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
}
openAs.name = file;
var left = background_image.left;
var top = background_image.top;
var left = bgRef.left;
var top = bgRef.top;
var new_left = j.f.objects[0].left;
var new_top = j.f.objects[0].top;
for (var i = 0; i < j.f.objects.length; i++) {
@@ -1185,6 +1293,14 @@ window.openFile = function open(file, jAlready) {
}
});
}
if (typeof window.soonRunWhenDesignBgReady === 'function') {
window.soonRunWhenDesignBgReady(runOpen, function () {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
});
} else {
runOpen();
}
}
if (arguments.length >= 2 && jAlready) {
doOpenWithJson(jAlready);
openAs.name = file;
@@ -1257,6 +1373,7 @@ function cleanupSrcFields(jsonObjects, objsArray) {
function applyCloudWriteResult(result, fallbackName) {
if (result && result.fileKey) {
openAs.name = result.fileKey;
if (typeof window.soonMarkSaveInPlace === 'function') window.soonMarkSaveInPlace(result.fileKey);
return result.fileKey;
}
return fallbackName;
@@ -1361,6 +1478,7 @@ function saveAs(op1, callback) {
if (fs) {
fs.writeFileSync(result.filePath || fp, content, 'utf8');
openAs.name = result.filePath || fp;
if (typeof window.soonMarkSaveInPlace === 'function') window.soonMarkSaveInPlace(openAs.name);
layer.msg(language_str("saveSucc") + openAs.name);
saveHistory();
if (callback && typeof callback === 'function') callback();
@@ -1446,6 +1564,7 @@ function save(op1, callback) {
if (fs) {
fs.writeFileSync(result.filePath || fp, content, 'utf8');
openAs.name = result.filePath || fp;
if (typeof window.soonMarkSaveInPlace === 'function') window.soonMarkSaveInPlace(openAs.name);
layer.msg(language_str("saveSucc") + openAs.name);
saveHistory();
if (callback && typeof callback === 'function') callback();
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -495,7 +495,7 @@ if (ipcRenderer) {
if (typeof require === 'undefined') {
function loadScript(src, next) {
var s = document.createElement('script');
s.src = src;
s.src = (typeof window.soonWithVersion === 'function') ? window.soonWithVersion(src) : src;
s.onload = s.onerror = function () { if (typeof next === 'function') next(); };
document.body.appendChild(s);
}
+368 -58
View File
@@ -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]
* 设计前提保存 .soon canvas[0] == 用户背景图set_bg 已把它放到 0
* 之前的实现会 fabricCanvas.remove(all[0]) 并把 all[1]前景当成新 userBg导致重打开后
* 用户背景图丢失前景元素被错误地缩放并锁定到卡框大小本版本不再删除 all[0]
* 改为直接把已有的 all[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 userBg = all[0];
if (!userBg) return null;
var frame = soonDesign2FrameOrigin(fabricCanvas);
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();
}
}
+147 -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,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];
@@ -1073,6 +1171,7 @@ function cleanupSrcFields(jsonObjects, objsArray) {
function applyCloudWriteResult(result, fallbackName) {
if (result && result.fileKey) {
openAs.name = result.fileKey;
if (typeof window.soonMarkSaveInPlace === 'function') window.soonMarkSaveInPlace(result.fileKey);
return result.fileKey;
}
return fallbackName;
@@ -1174,6 +1273,7 @@ function saveAs(op1, callback) {
if (fs) {
fs.writeFileSync(fp, content, 'utf8');
openAs.name = fp;
if (typeof window.soonMarkSaveInPlace === 'function') window.soonMarkSaveInPlace(openAs.name);
if (typeof window.soonToast === 'function') window.soonToast(language_str('saveSucc') + openAs.name, 'success');
else layer.msg(language_str('saveSucc') + openAs.name, { shade: 0 });
saveHistory();
@@ -1259,6 +1359,7 @@ function save(op1, callback) {
if (fs) {
fs.writeFileSync(fp, content, 'utf8');
openAs.name = fp;
if (typeof window.soonMarkSaveInPlace === 'function') window.soonMarkSaveInPlace(openAs.name);
if (typeof window.soonToast === 'function') window.soonToast(language_str('saveSucc') + openAs.name, 'success');
else layer.msg(language_str('saveSucc') + openAs.name, { shade: 0 });
saveHistory();
@@ -1331,13 +1432,31 @@ function saveHistory() {
// 使用 window.openFile 避免与浏览器原生的 window.open 冲突
window.openFile = function open(file, jAlready) {
if (!file) return;
if (typeof window.soonOnOpenDesignFile === 'function') window.soonOnOpenDesignFile(file);
if (typeof window.soonBindFileMeta === 'function') window.soonBindFileMeta(file);
function doOpenWithJson(j) {
if (!j || !background_image) {
if (typeof window.soonNormalizeSoonJson === 'function') {
j = window.soonNormalizeSoonJson(j);
}
if (!j) {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
return;
}
// 防御性:j.b 缺失时给一个空画布占位(避免 canvas2.loadFromJSON 报 undefined
if (!j.b || typeof j.b !== 'object' || !Array.isArray(j.b.objects)) {
if (typeof console !== 'undefined' && console.warn) {
console.warn('[design2.openFile] j.b 缺失,自动以空背面回退。文件:', file);
}
j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
}
function runOpen() {
if (!background_image) {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
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 +1469,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 +1618,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 +1637,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);
@@ -1523,6 +1655,14 @@ window.openFile = function open(file, jAlready) {
canvas2Loaded = true
saveInitialState()
})
}
if (typeof window.soonRunWhenDesignBgReady === 'function') {
window.soonRunWhenDesignBgReady(runOpen, function () {
if (typeof window.soonReportOpenLoadError === 'function') window.soonReportOpenLoadError('模板/文件加载失败');
});
} else {
runOpen();
}
}
if (arguments.length >= 2 && jAlready) {
doOpenWithJson(jAlready);
+63 -86
View File
@@ -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,41 @@ $('#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)
// 旧背景:取当前画布对应的占位背景图(正面->background_image1,背面->background_image2),
// 背面场景下 background_image 已被 #back_side 切换为 background_image2。
var oldBg = (canvas === canvas2) ? background_image2 : background_image1
if (!oldBg) oldBg = 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();
@@ -1300,22 +1281,6 @@ function getAbsoluteXY(_obj) {
return [_obj.get('left'), _obj.get('top')]
}
$('#open').click(function () {
if (step.val == 0) {
OpenDialog();
return;
}
layer.confirm(
language_str('whetherSave'),
function (index) {
if (typeof window.output === 'function') {
window.output(OpenDialog);
}
layer.close(index);
},
function (index) {
OpenDialog();
}
)
function OpenDialog() {
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function(opts) { return window.platformBridge.showOpenDialog(opts); } } : null);
if (!dialogApi) return;
@@ -1355,12 +1320,21 @@ $('#open').click(function () {
})
.catch(function(err) {});
}
if (typeof window.soonConfirmLeaveThen === 'function') {
window.soonConfirmLeaveThen(OpenDialog);
} else {
OpenDialog();
}
})
function goFirstPage() {
if (window.platformBridge && window.platformBridge.openFirstPage) window.platformBridge.openFirstPage();
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-first-page');
}
$('#new').click(function () {
if (typeof window.soonConfirmLeaveThen === 'function') {
window.soonConfirmLeaveThen(goFirstPage);
return;
}
if (step.val == 0) {
goFirstPage();
return;
@@ -1385,23 +1359,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 +1401,8 @@ function selectPic(target, index) {
let activeObj = canvas.getActiveObject();
// 检查对象是否被锁定
if (isObjectLocked(activeObj)) {
// 列表选中时允许锁定对象保持选中;画布点击仍不可选
if (!window._selectingFromList && isObjectLocked(activeObj)) {
canvas.discardActiveObject();
canvas.renderAll();
return;
@@ -2092,6 +2049,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 +2063,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 +2077,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 +2098,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 +2116,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 +2128,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 +2231,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 +2287,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 +2393,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())
+4 -1
View File
@@ -810,7 +810,10 @@ layui.use(['layer', 'form', 'jquery'], function () {
}
window.soonOpenSoonJsonLocally(soonData, fileName);
var openP = window.soonOpenSoonJsonLocally(soonData, fileName);
if (openP && typeof openP.then === 'function') {
openP.catch(function () {});
}
}).catch(function (err) { console.log(err); });
+28 -8
View File
@@ -423,6 +423,13 @@
typeof window.soonBindFileMeta === 'function') {
window.soonBindFileMeta(cacheKey, { name: hit.meta.name });
}
// 兜底:design2(背面文件)若缓存里 j.b 缺失,主动补一个空背面结构,
// 避免 design2.canvas2.loadFromJSON(undefined) 抛错或吃掉整段加载。
if ((hit.json.soonType === 2 || hit.json.backBlackPic || (hit.json.bo && hit.json.bo.length) || (hit.json.f && hit.json.b === undefined && typeof hit.json.b === 'undefined'))) {
if (!hit.json.b || typeof hit.json.b !== 'object' || !Array.isArray(hit.json.b.objects)) {
hit.json.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
}
}
return Promise.resolve(hit.json);
}
@@ -444,18 +451,23 @@
}
return fetchFromNetwork(key, cacheKey);
function fetchFromNetwork(key, cacheKey) {
if (key.indexOf('soondesign_session:') === 0) {
function readSessionJson(key, attempt) {
attempt = attempt || 0;
if (typeof window.soonLocalGet === 'function') {
return window.soonLocalGet(cacheKey).then(function (hit) {
if (hit && hit.json) return hit.json;
var raw = null;
try {
var j = sessionStorage.getItem(key);
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
if (!j || j === 'idb') return null;
var parsed = JSON.parse(j);
return afterNetworkJson(parsed, { source: 'session', name: key });
} catch (e) { return null; }
raw = sessionStorage.getItem(key);
if (!raw && typeof localStorage !== 'undefined') raw = localStorage.getItem(key);
if (raw && raw !== 'idb') return JSON.parse(raw);
} catch (e) { /* ignore */ }
if (raw === 'idb' && attempt < 8) {
return new Promise(function (resolve) {
setTimeout(resolve, 60);
}).then(function () { return readSessionJson(key, attempt + 1); });
}
return null;
});
}
try {
@@ -465,6 +477,14 @@
return Promise.resolve(JSON.parse(j));
} catch (e) { return Promise.resolve(null); }
}
function fetchFromNetwork(key, cacheKey) {
if (key.indexOf('soondesign_session:') === 0) {
return readSessionJson(key, 0).then(function (parsed) {
if (!parsed) return null;
return afterNetworkJson(parsed, { source: 'session', name: key });
});
}
if (key && key.indexOf('soondesign_file:') === 0 && typeof fetch !== 'undefined') {
var fileMatch = key.match(/^soondesign_file:(\d+)/);
if (fileMatch && getAccessToken()) {
+3
View File
@@ -2,6 +2,8 @@
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<script src="../../config/version.js"></script>
<script src="../../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="SoonDesign 管理后台">
<link rel="icon" href="../../assets/images/favicon.ico">
@@ -37,6 +39,7 @@
</div>
</div>
<script src="../../js/common/soon-cache.js"></script>
<script src="../../config/local.js"></script>
<script src="../../js/common/auth-redirect.js"></script>
<script src="../../vendor/layui/layui.js"></script>
+3
View File
@@ -2,6 +2,8 @@
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<script src="../../config/version.js"></script>
<script src="../../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="SoonDesign 管理后台登录">
<link rel="icon" href="../../assets/images/favicon.ico">
@@ -41,6 +43,7 @@
</div>
</div>
<script src="../../js/common/soon-cache.js"></script>
<script src="../../config/local.js"></script>
<script src="../../js/common/auth-redirect.js"></script>
<script src="../../vendor/layui/layui.js"></script>
+3
View File
@@ -3,6 +3,8 @@
<head>
<meta charset="utf-8">
<script src="../config/version.js"></script>
<script src="../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="description" content="SoonDesign 在线卡证设计">
<link rel="icon" href="../assets/images/favicon.ico">
@@ -869,6 +871,7 @@
<img id="source_back" src="../assets/images/back_bg_1.png" style="display: none" />
</div>
</div>
<script src="../js/common/soon-cache.js"></script>
<script src="../vendor/js/fabric1.min.js"></script>
<script src="../vendor/js/guideLines1.js"></script>
<script src="../vendor/js/print.min.js"></script>
+3
View File
@@ -3,6 +3,8 @@
<head>
<meta charset="utf-8">
<script src="../config/version.js"></script>
<script src="../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="description" content="SoonDesign 在线卡证设计">
<link rel="icon" href="../assets/images/favicon.ico">
@@ -661,6 +663,7 @@
</div>
</div>
<script src="../js/common/soon-cache.js"></script>
<script src="../vendor/js/fabric2.min.js"></script>
<script src="../vendor/js/guideLines2.js"></script>
<script src="../vendor/js/print.min.js"></script>
+5
View File
@@ -6,6 +6,9 @@
<meta charset="utf-8">
<script src="../config/version.js"></script>
<script src="../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="description" content="SoonDesign 在线卡证设计">
@@ -134,6 +137,8 @@
<script src="../js/common/soon-cache.js"></script>
<script src="../config/local.js"></script>
<script src="../js/common/auth-redirect.js"></script>
+3
View File
@@ -2,6 +2,8 @@
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<script src="../config/version.js"></script>
<script src="../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="SoonDesign 在线卡证设计">
<link rel="icon" href="../assets/images/favicon.ico">
@@ -39,6 +41,7 @@
</div>
</div>
<script src="../js/common/soon-cache.js"></script>
<script src="../config/local.js"></script>
<script src="../js/common/portal-topbar.js"></script>
<script>SoonPortalTopbar.mount('#portal-topbar', { variant: 'auth' });</script>
+3
View File
@@ -2,6 +2,8 @@
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<script src="../config/version.js"></script>
<script src="../js/common/soon-cache.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="SoonDesign 在线卡证设计">
<link rel="icon" href="../assets/images/favicon.ico">
@@ -39,6 +41,7 @@
</div>
</div>
<script src="../js/common/soon-cache.js"></script>
<script src="../config/local.js"></script>
<script src="../js/common/portal-topbar.js"></script>
<script>SoonPortalTopbar.mount('#portal-topbar', { variant: 'auth' });</script>
+21
View File
@@ -0,0 +1,21 @@
# 写入 frontend-web/config/version.jsstart.bat / sync-config 自动调用,发版上传前也会更新)
param([string]$Version = '')
$ErrorActionPreference = 'Stop'
$Root = Split-Path $PSScriptRoot -Parent
$WebRoot = Join-Path $Root 'frontend-web'
if (-not $Version) {
Push-Location $Root
try { $Version = (git rev-parse --short HEAD 2>$null) } finally { Pop-Location }
if (-not $Version) { $Version = Get-Date -Format 'yyyyMMddHHmmss' }
}
$out = @"
(function () {
'use strict';
window.SOON_APP_VERSION = '$Version';
})();
"@
$path = Join-Path $WebRoot 'config\version.js'
[System.IO.File]::WriteAllText($path, $out, (New-Object System.Text.UTF8Encoding $false))