fix(web): 本地优先保存与 design 页体验修复

- 保存先写本地缓存,登录后可选云端同步;退出登录不再因文件操作跳转登录

- 修复 Layui 遮罩残留、design2 保存后线条、toast 被 finally 提前关闭

- 保存过程恢复 loading spinner;成功提示 2.5 秒

- 云端 payload 瘦身与体积超限提示;后端 schema 迁移与 FileService 容错

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-24 19:24:34 +08:00
parent 4d19d7e814
commit 5fb269cc0a
36 changed files with 894 additions and 358 deletions
+7 -2
View File
@@ -85,16 +85,21 @@
fullUrl = getApiBase() + '/' + String(url).replace(/^\/+/, '');
}
var hadSession = !!(token || getRefreshToken());
return fetch(fullUrl, opts).then(function (response) {
if (response.status !== 401) return response;
if (!getRefreshToken()) return response;
return refreshAccessToken().then(function (newToken) {
var retryHeaders = Object.assign({}, opts.headers || {});
retryHeaders['Authorization'] = 'Bearer ' + newToken;
var retryOpts = Object.assign({}, opts, { headers: retryHeaders });
return fetch(fullUrl, retryOpts);
}).catch(function () {
clearSession();
redirectToLogin();
if (hadSession) {
clearSession();
redirectToLogin();
}
return response;
});
});
+75 -25
View File
@@ -88,22 +88,29 @@
}
});
if (hidden.length) canvas.renderAll();
window._soonSkipDesign1CdMask = true;
var mult = 0.18;
var quality = 0.72;
var url = '';
for (var i = 0; i < 3; i++) {
try {
var candidate = canvas.toDataURL({ format: 'jpeg', quality: quality, multiplier: mult });
if (candidate && candidate.indexOf('data:image/') === 0 && candidate.length <= THUMB_MAX) {
url = candidate;
break;
}
} catch (e) { /* ignore */ }
quality -= 0.18;
mult -= 0.04;
try {
for (var i = 0; i < 3; i++) {
try {
var candidate = canvas.toDataURL({ format: 'jpeg', quality: quality, multiplier: mult });
if (candidate && candidate.indexOf('data:image/') === 0 && candidate.length <= THUMB_MAX) {
url = candidate;
break;
}
} catch (e) { /* ignore */ }
quality -= 0.18;
mult -= 0.04;
}
} finally {
window._soonSkipDesign1CdMask = false;
}
hidden.forEach(function (obj) { obj.visible = true; });
if (hidden.length) canvas.renderAll();
canvas.renderAll();
if (typeof window.soonClearFabricOverlay === 'function') window.soonClearFabricOverlay(canvas);
if (canvas.requestRenderAll) canvas.requestRenderAll();
return url;
}
@@ -134,8 +141,20 @@
return soonEnsureSoonExt(name);
}
function dismissLayerMask() {
if (typeof window.soonCleanupLayerMask === 'function') {
window.soonCleanupLayerMask();
return;
}
soonHideOpenFileLoading();
invokeLayerCleanup();
}
function soonReportSaveError(err) {
if (typeof window.soonFinishSaveUi === 'function') window.soonFinishSaveUi();
else dismissLayerMask();
if (err && err.conflictSynced) return;
if (err && err.apiNotified) return;
if (typeof window.soonShowApiError === 'function') {
window.soonShowApiError({
status: err && err.status,
@@ -149,11 +168,27 @@
function soonWriteSoonContentWeb(fp, content, prevOpenKey, onDone, onError) {
if (!window.platformBridge || !window.platformBridge.writeFile) return false;
window.platformBridge.writeFile(fp, content).then(function (res) {
if (typeof window.soonShowOpenFileLoading === 'function') window.soonShowOpenFileLoading();
var writePromise;
try {
writePromise = window.platformBridge.writeFile(fp, content);
} catch (syncErr) {
dismissLayerMask();
if (typeof onError === 'function') onError(syncErr);
else soonReportSaveError(syncErr);
return true;
}
if (!writePromise || typeof writePromise.then !== 'function') {
dismissLayerMask();
return true;
}
writePromise.then(function (res) {
if (typeof onDone === 'function') onDone(res, prevOpenKey);
}).catch(function (err) {
if (typeof onError === 'function') onError(err);
else soonReportSaveError(err);
}).finally(function () {
dismissLayerMask();
});
return true;
}
@@ -656,37 +691,52 @@
});
}
function invokeLayerCleanup() {
if (typeof window.soonCleanupLayerMask === 'function') {
window.soonCleanupLayerMask();
}
}
function soonReportOpenLoadError(message) {
soonHideOpenFileLoading();
invokeLayerCleanup();
soonToast(message || '文件加载失败', 'error');
}
function soonShowOpenFileLoading() {
if (typeof layer === 'undefined' || !layer.load) return;
soonHideOpenFileLoading();
window._soonOpenLoadIdx = layer.load(1, { shade: [0.12, '#000'] });
window._soonOpenLoadIdx = layer.load(1, { shade: 0 });
}
function soonHideOpenFileLoading() {
if (window._soonOpenLoadIdx == null) return;
if (typeof layer !== 'undefined' && layer.close) layer.close(window._soonOpenLoadIdx);
if (typeof layer !== 'undefined' && layer.closeAll) {
layer.closeAll('loading');
}
if (window._soonOpenLoadIdx != null && typeof layer !== 'undefined' && layer.close) {
try { layer.close(window._soonOpenLoadIdx); } catch (e) { /* ignore */ }
}
window._soonOpenLoadIdx = null;
invokeLayerCleanup();
}
function soonToast(message, type) {
if (typeof window.soonToast === 'function') window.soonToast(message, type);
}
function soonRequireLogin(actionLabel) {
function soonRequireLogin(actionLabel, options) {
options = options || {};
if (soonGetAccessToken()) return true;
var label = actionLabel || '继续';
soonToast('请先登录后再' + label, 'warn');
if (typeof window.soonRedirectToLogin === 'function') {
setTimeout(function () { window.soonRedirectToLogin(); }, 600);
} else if (typeof window.redirectToLogin === 'function') {
setTimeout(function () { window.redirectToLogin(); }, 600);
} else {
window.location.href = 'login.web.html';
soonToast('请先登录后再' + (label || '操作'), 'warn');
if (options.redirect) {
if (typeof window.soonRedirectToLogin === 'function') {
setTimeout(function () { window.soonRedirectToLogin(); }, 600);
} else if (typeof window.redirectToLogin === 'function') {
setTimeout(function () { window.redirectToLogin(); }, 600);
} else {
window.location.href = 'login.web.html';
}
}
return false;
}
@@ -724,8 +774,8 @@
else if (info.status === 409) kind = 'warn';
else if (info.status === 413) kind = 'warn';
soonToast(info.message, kind);
if (info.status === 401) {
soonRequireLogin('操作');
if (info.status === 401 && info.requireLogin) {
soonRequireLogin('操作', { redirect: true });
}
}
@@ -79,6 +79,7 @@
if (idx != null) layer.close(idx);
if (payIdx != null) layer.close(payIdx);
}
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
if (fireStay && opts && typeof opts.onStay === 'function') opts.onStay();
}
@@ -93,6 +94,7 @@
_gate.payIndex = null;
if (typeof layer !== 'undefined' && gateIdx != null) layer.close(gateIdx);
_gate = { index: null, payIndex: null, plan: null, gateOpts: null };
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
toast('恭喜您,会员已激活!', 'success');
if (typeof window.soonLoadMembership === 'function') {
window.soonLoadMembership(true).then(function () {
@@ -69,6 +69,7 @@
if (idx != null && typeof layer !== 'undefined') {
try { layer.close(idx); } catch (e) { /* ignore */ }
}
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
}
function bindModal(modal) {
@@ -607,6 +607,7 @@
displayChannels: channels,
onPaid: function () {
layer.close(idx);
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
if (typeof opts.onPaid === 'function') opts.onPaid();
},
onBack: opts.onBack,
@@ -617,6 +618,7 @@
},
end: function () {
if (payCtrl) payCtrl.destroy();
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
if (typeof opts.onClose === 'function') opts.onClose();
},
});
+3
View File
@@ -17,6 +17,9 @@
localStorage.removeItem('soon_access');
localStorage.removeItem('soon_refresh');
}
if (typeof window.soonApplyMembership === 'function') {
window.soonApplyMembership(null);
}
}
function initClearCache() {
+73 -1
View File
@@ -1,14 +1,81 @@
(function () {
'use strict';
function soonCleanupLayerMask() {
if (typeof layer !== 'undefined' && layer.closeAll) {
layer.closeAll('loading');
}
if (window._soonOpenLoadIdx != null && typeof layer !== 'undefined' && layer.close) {
try { layer.close(window._soonOpenLoadIdx); } catch (e) { /* ignore */ }
window._soonOpenLoadIdx = null;
}
if (typeof document === 'undefined') return;
var layers = document.querySelectorAll('.layui-layer');
for (var j = 0; j < layers.length; j++) {
if (layers[j].getAttribute('type') !== 'loading') continue;
var loadTimes = layers[j].getAttribute('times');
if (loadTimes && typeof layer !== 'undefined' && layer.close) {
try { layer.close(loadTimes); } catch (e2) { /* ignore */ }
}
}
var shades = document.querySelectorAll('.layui-layer-shade');
for (var i = shades.length - 1; i >= 0; i--) {
var shade = shades[i];
var times = shade.getAttribute('times');
var layerEl = times ? document.getElementById('layui-layer' + times) : null;
if (!layerEl || layerEl.getAttribute('type') === 'loading') {
if (shade.parentNode) shade.parentNode.removeChild(shade);
}
}
}
function soonLayerDismiss(type) {
if (typeof layer !== 'undefined' && layer.closeAll) {
layer.closeAll(type);
}
soonCleanupLayerMask();
}
function soonClearFabricOverlay(canvas) {
if (!canvas || !canvas.contextTop) return;
if (typeof canvas.clearContext === 'function') {
canvas.clearContext(canvas.contextTop);
}
}
function soonRefreshDesignCanvases() {
var names = ['canvas1', 'canvas2', 'canvas'];
for (var n = 0; n < names.length; n++) {
var c = window[names[n]];
if (!c || typeof c.renderAll !== 'function') continue;
if (c.discardActiveObject) c.discardActiveObject();
soonClearFabricOverlay(c);
if (c.requestRenderAll) c.requestRenderAll();
else c.renderAll();
}
}
function soonFinishSaveUi() {
if (typeof window.soonHideOpenFileLoading === 'function') window.soonHideOpenFileLoading();
else soonCleanupLayerMask();
soonRefreshDesignCanvases();
if (typeof requestAnimationFrame === 'function') {
requestAnimationFrame(function () {
soonCleanupLayerMask();
soonRefreshDesignCanvases();
});
}
}
function soonToast(message, type) {
if (typeof layer === 'undefined' || !layer.msg) return;
var skin = 'soon-toast';
if (type === 'success') skin += ' soon-toast--success';
else if (type === 'error') skin += ' soon-toast--error';
else if (type === 'warn') skin += ' soon-toast--warn';
var time = type === 'success' ? 2500 : (type === 'error' ? 4200 : 3600);
layer.msg(message, {
time: 2400,
time: time,
shade: 0,
offset: '72px',
icon: -1,
@@ -16,5 +83,10 @@
});
}
window.soonCleanupLayerMask = soonCleanupLayerMask;
window.soonLayerDismiss = soonLayerDismiss;
window.soonClearFabricOverlay = soonClearFabricOverlay;
window.soonRefreshDesignCanvases = soonRefreshDesignCanvases;
window.soonFinishSaveUi = soonFinishSaveUi;
window.soonToast = soonToast;
})();
+4 -4
View File
@@ -102,10 +102,10 @@ function applyFontList(data) {
$("#count_font_family").append("<option value='" + item + "'>" + item + "</option>");
}
}
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('font-list', function(event, data) { applyFontList(data); });
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) {
if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) {
window.platformBridge.getSystemFonts().then(applyFontList);
} else if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('font-list', function(event, data) { applyFontList(data); });
}
function applySysLan(data) {
let lang = localStorage.getItem("lang");
@@ -1816,7 +1816,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;
+13 -1
View File
@@ -1204,6 +1204,9 @@ window.openFile = function open(file, jAlready) {
}).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 {
@@ -1277,6 +1280,8 @@ function soonPageDesignType() {
}
function onCloudWriteDone(res, fallback, callback) {
if (typeof window.soonFinishSaveUi === 'function') window.soonFinishSaveUi();
else if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
applyCloudWriteResult(res, fallback);
if (res && res.fileKey && typeof window.soonClearPendingImport === 'function') {
window.soonClearPendingImport();
@@ -1284,7 +1289,12 @@ function onCloudWriteDone(res, fallback, callback) {
if (typeof window.soonRecentOnCloudSave === 'function') {
window.soonRecentOnCloudSave(res, fallback, soonPageDesignType());
}
layer.msg(language_str("saveSucc") + cloudSaveSuccessLabel(res, fallback));
var msg = language_str("saveSucc") + cloudSaveSuccessLabel(res, fallback);
if (res && res.syncFailed) {
msg = '已保存到本地,云端同步失败';
}
if (typeof window.soonToast === 'function') window.soonToast(msg, res && res.syncFailed ? 'warn' : 'success');
else layer.msg(msg, { shade: 0, time: 2500 });
if (callback && typeof callback === 'function') callback();
}
@@ -1328,6 +1338,7 @@ function saveAs(op1, callback) {
if (!guardWebSave()) return;
var prevOpenKey = openAs.name || '';
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
if (typeof window.soonRefreshDesignCanvases === 'function') window.soonRefreshDesignCanvases();
con_o.soonType = 1;
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
@@ -1395,6 +1406,7 @@ function save(op1, callback) {
if (!guardWebSave()) return;
var prevOpenKey = openAs.name || '';
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
if (typeof window.soonRefreshDesignCanvases === 'function') window.soonRefreshDesignCanvases();
con_o.soonType = 1;
var content = JSON.stringify(con_o);
var needsDialog = typeof window.soonNeedsSaveDialog === 'function'
+3 -18
View File
@@ -552,10 +552,6 @@ function relayoutDesign2Canvas() {
if (typeof window.soonDesign2ScaleBgSources === 'function') {
window.soonDesign2ScaleBgSources();
}
if (window.is_bgi_add) {
canvas1.requestRenderAll();
canvas2.requestRenderAll();
}
}
function soonDesign2ScaleBgSources() {
@@ -572,16 +568,6 @@ 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 };
}
function soonDesign2PrepareBgSources(done) {
var sf = document.getElementById('source_front');
var sb = document.getElementById('source_back');
@@ -634,7 +620,6 @@ function soonDesign2Kickoff() {
window.soonDesign2ScaleBgSources = soonDesign2ScaleBgSources;
window.soonDesign2PrepareBgSources = soonDesign2PrepareBgSources;
window.soonDesign2BgOverlayMetrics = soonDesign2BgOverlayMetrics;
window.soonDesign2Kickoff = soonDesign2Kickoff;
initRulers();
@@ -2037,10 +2022,10 @@ function applyFontList(data) {
$('#count_font_family').append("<option value='" + item + "'>" + item + '</option>')
}
}
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('font-list', function(event, data) { applyFontList(data); })
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) {
if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) {
window.platformBridge.getSystemFonts().then(applyFontList)
} else if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('font-list', function(event, data) { applyFontList(data); })
}
function applySysLan(data) {
let lang = localStorage.getItem('lang')
+19 -4
View File
@@ -1096,6 +1096,8 @@ function soonPageDesignType() {
}
function onCloudWriteDone(res, fallback, callback) {
if (typeof window.soonFinishSaveUi === 'function') window.soonFinishSaveUi();
else if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
applyCloudWriteResult(res, fallback);
if (res && res.fileKey && typeof window.soonClearPendingImport === 'function') {
window.soonClearPendingImport();
@@ -1103,7 +1105,12 @@ function onCloudWriteDone(res, fallback, callback) {
if (typeof window.soonRecentOnCloudSave === 'function') {
window.soonRecentOnCloudSave(res, fallback, soonPageDesignType());
}
layer.msg(language_str('saveSucc') + cloudSaveSuccessLabel(res, fallback));
var msg = language_str('saveSucc') + cloudSaveSuccessLabel(res, fallback);
if (res && res.syncFailed) {
msg = '已保存到本地,云端同步失败';
}
if (typeof window.soonToast === 'function') window.soonToast(msg, res && res.syncFailed ? 'warn' : 'success');
else layer.msg(msg, { shade: 0, time: 2500 });
if (callback && typeof callback === 'function') callback();
}
@@ -1147,6 +1154,7 @@ function saveAs(op1, callback) {
if (!guardWebSave()) return;
var prevOpenKey = openAs.name || '';
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
if (typeof window.soonRefreshDesignCanvases === 'function') window.soonRefreshDesignCanvases();
con_o.soonType = 2;
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
@@ -1166,7 +1174,8 @@ function saveAs(op1, callback) {
if (fs) {
fs.writeFileSync(fp, content, 'utf8');
openAs.name = fp;
layer.msg(language_str('saveSucc') + 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();
if (callback && typeof callback === 'function') callback();
}
@@ -1212,6 +1221,7 @@ function save(op1, callback) {
if (!guardWebSave()) return;
var prevOpenKey = openAs.name || '';
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
if (typeof window.soonRefreshDesignCanvases === 'function') window.soonRefreshDesignCanvases();
con_o.soonType = 2;
var content = JSON.stringify(con_o);
var needsDialog = typeof window.soonNeedsSaveDialog === 'function'
@@ -1225,7 +1235,8 @@ function save(op1, callback) {
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
fs.writeFileSync(openAs.name, content, 'utf8');
layer.msg(language_str('saveSucc') + 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();
if (callback && typeof callback === 'function') callback();
}
@@ -1248,7 +1259,8 @@ function save(op1, callback) {
if (fs) {
fs.writeFileSync(fp, content, 'utf8');
openAs.name = fp;
layer.msg(language_str('saveSucc') + 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();
if (callback && typeof callback === 'function') callback();
}
@@ -1529,6 +1541,9 @@ window.openFile = function open(file, jAlready) {
}).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 {
-34
View File
@@ -365,8 +365,6 @@ $('#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')
@@ -2703,42 +2701,10 @@ if (typeof window._design2CanvasEventsInitialized === 'undefined') {
// - mouse:up (已在 core.js 中处理)
// 如果需要 canvas1 特定的处理逻辑,请修改 core.js 中的相应函数
// 保留 canvas1 特定的 after:render 处理
canvas1.on('after:render', function () {
if (!window.is_bgi_add) return
var image = document.getElementById('source_front')
var metrics = typeof window.soonDesign2BgOverlayMetrics === 'function'
? window.soonDesign2BgOverlayMetrics(image)
: null
if (!metrics) return
ctx1.drawImage(
image,
(canvas1.width - metrics.w) / 2,
(canvas1.height - metrics.h) / 2,
metrics.w,
metrics.h
)
})
// 保留 canvas1 特定的 text:changed 处理
canvas1.on('text:changed', (e) => {
$('#text_text').val(e.target.get('text'))
})
// 保留 canvas2 特定的 after:render 处理
canvas2.on('after:render', function () {
if (!window.is_bgi_add) return
var image = document.getElementById('source_back')
var metrics = typeof window.soonDesign2BgOverlayMetrics === 'function'
? window.soonDesign2BgOverlayMetrics(image)
: null
if (!metrics) return
ctx2.drawImage(
image,
(canvas2.width - metrics.w) / 2,
(canvas2.height - metrics.h) / 2,
metrics.w,
metrics.h
)
})
requestAnimationFrame(function () {
requestAnimationFrame(function () {
if (typeof window.soonDesign2Kickoff === 'function') {
+175 -57
View File
@@ -47,6 +47,57 @@
'Accept': 'application/json'
};
var CLOUD_JSON_LIMIT = 16 * 1024 * 1024 - 65536;
var CLOUD_STRIP_KEYS = [
'frontColorPic', 'backColorPic', 'frontBlackPic', 'backBlackPic', 'backDisplayPic'
];
var CLOUD_REGEN_IMAGE_TYPES = { 2: 1, 8: 1, 9: 1 };
function slimCloudFabricSide(data, sideKey, metaKey) {
var canvas = data[sideKey];
var metaList = data[metaKey];
if (!canvas || !Array.isArray(canvas.objects)) return;
canvas.objects.forEach(function (obj, i) {
if (!obj || typeof obj.src !== 'string') return;
var meta = Array.isArray(metaList) && i > 0 ? metaList[i - 1] : null;
var t = meta && meta.type;
if (CLOUD_REGEN_IMAGE_TYPES[t]) delete obj.src;
});
}
function slimCloudPayload(data) {
CLOUD_STRIP_KEYS.forEach(function (key) { delete data[key]; });
var preview = data.frontDisplayPic;
if (typeof preview === 'string' && preview.length > 120000) delete data.frontDisplayPic;
slimCloudFabricSide(data, 'f', 'fo');
slimCloudFabricSide(data, 'b', 'bo');
return data;
}
function formatPayloadTooLargeMessage(sizeBytes) {
var mb = Math.max(0.1, Math.round(sizeBytes / 1024 / 1024 * 10) / 10);
return '设计文件约 ' + mb + 'MB,超过云端上传限制。请减少或压缩自定义图片;'
+ '若仍失败,请让管理员将 MySQL max_allowed_packet 调至 32M 以上。';
}
function prepareCloudJsonStr(jsonStr) {
if (typeof jsonStr !== 'string') jsonStr = String(jsonStr || '{}');
if (jsonStr.length <= CLOUD_JSON_LIMIT) return jsonStr;
var slim = jsonStr;
try {
var data = slimCloudPayload(JSON.parse(jsonStr));
slim = JSON.stringify(data);
if (slim.length <= CLOUD_JSON_LIMIT) return slim;
delete data.frontDisplayPic;
slim = JSON.stringify(data);
if (slim.length <= CLOUD_JSON_LIMIT) return slim;
} catch (e) { /* ignore */ }
var err = new Error(formatPayloadTooLargeMessage(slim.length));
err.code = 'payload_too_large';
err.status = 413;
throw err;
}
function parseCloudRef(p) {
return typeof window.soonParseFileKey === 'function' ? window.soonParseFileKey(p) : null;
}
@@ -83,13 +134,12 @@
var err = new Error(info.message || 'cloud_error');
err.status = info.status;
err.code = info.code;
err.apiNotified = true;
return Promise.reject(err);
}
function requireCloudAuth(actionLabel) {
if (getAccessToken()) return true;
if (typeof window.soonRequireLogin === 'function') return window.soonRequireLogin(actionLabel);
return false;
return !!getAccessToken();
}
function navigateToPage(pathWithQuery) {
@@ -106,21 +156,41 @@
return 'en';
}
function getSystemFonts() {
var list = [
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia',
'Impact', 'Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi', 'FangSong',
'Times New Roman', 'Trebuchet MS', 'Verdana', 'PingFang SC', 'Hiragino Sans GB'
];
var WEB_FONT_FALLBACK = [
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia',
'Impact', 'Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi', 'FangSong',
'Times New Roman', 'Trebuchet MS', 'Verdana', 'PingFang SC', 'Hiragino Sans GB',
'Helvetica Neue', 'Segoe UI', 'Tahoma', 'STHeiti', 'STSong', 'STKaiti', 'STFangsong'
];
function mergeFontNames() {
var set = {};
WEB_FONT_FALLBACK.forEach(function (name) { set[name] = 1; });
if (typeof document !== 'undefined' && document.fonts && document.fonts.forEach) {
var set = {};
document.fonts.forEach(function (f) {
var name = (f.family || '').replace(/^["']|["']$/g, '');
if (name) set[name] = 1;
});
list = Object.keys(set).length ? Object.keys(set).sort() : list;
}
return Promise.resolve(list);
return Object.keys(set).sort();
}
function getSystemFonts() {
if (typeof window !== 'undefined' && window.queryLocalFonts) {
return window.queryLocalFonts().then(function (fonts) {
var set = {};
WEB_FONT_FALLBACK.forEach(function (name) { set[name] = 1; });
(fonts || []).forEach(function (f) {
var name = (f.fullName || f.family || '').replace(/^["']|["']$/g, '');
if (name) set[name] = 1;
});
var merged = Object.keys(set).sort();
return merged.length ? merged : mergeFontNames();
}).catch(function () {
return mergeFontNames();
});
}
return Promise.resolve(mergeFontNames());
}
function listCloudFiles(pageOrLimit, sizeOrOffset) {
@@ -152,6 +222,11 @@
function createCloudFile(name, jsonStr) {
if (!requireCloudAuth('保存')) return Promise.reject(new Error('unauthorized'));
try {
jsonStr = prepareCloudJsonStr(jsonStr);
} catch (e) {
return Promise.reject(e);
}
return authedFetch('files', {
method: 'POST',
headers: JSON_HEADERS,
@@ -197,6 +272,11 @@
function updateCloudFile(id, name, jsonStr, version) {
if (!requireCloudAuth('保存')) return Promise.reject(new Error('unauthorized'));
try {
jsonStr = prepareCloudJsonStr(jsonStr);
} catch (e) {
return Promise.reject(e);
}
var body = { name: normalizeSoonName(name), json: jsonStr };
if (version != null) body.version = version;
return authedFetch('files/' + id, {
@@ -511,14 +591,23 @@
var name = input && input.value ? String(input.value).trim() : defaultName;
name = resolveSaveDialogName(name, options, isSoon);
layer.close(index);
if (typeof window.soonCleanupLayerMask === 'function') {
setTimeout(function () { window.soonCleanupLayerMask(); }, 0);
}
resolve({ canceled: false, filePath: name, useCloud: !!isSoon });
},
btn2: function (index) {
layer.close(index);
if (typeof window.soonCleanupLayerMask === 'function') {
setTimeout(function () { window.soonCleanupLayerMask(); }, 0);
}
resolve({ canceled: true });
},
cancel: function (index) {
layer.close(index);
if (typeof window.soonCleanupLayerMask === 'function') {
setTimeout(function () { window.soonCleanupLayerMask(); }, 0);
}
resolve({ canceled: true });
}
});
@@ -563,59 +652,75 @@
return cacheWriteForKey(res.fileKey, meta).then(function () { return res; });
}
if (!getAccessToken()) {
if (cloudRef && cloudRef.id) {
requireCloudAuth('保存');
return Promise.reject(new Error('unauthorized'));
function resolveLocalSaveKey() {
if (name.indexOf('soondesign_session:') === 0) return name;
if (name.indexOf('soondesign_file:') === 0) return name;
if (typeof window.soonMakeSessionKey === 'function') {
return window.soonMakeSessionKey(name);
}
var sessionKey = typeof window.soonMakeSessionKey === 'function'
? window.soonMakeSessionKey(name)
: (name.indexOf('soondesign_session:') === 0
? name
: 'soondesign_session:' + fileName.replace(/\.soon$/i, ''));
var prevSessionKey = name.indexOf('soondesign_session:') === 0 ? name : '';
function finishSessionSave() {
try { sessionStorage.setItem(sessionKey, 'idb'); } catch (e) { /* ignore */ }
return { fileKey: sessionKey, name: fileName, version: 0 };
return 'soondesign_session:' + fileName.replace(/\.soon$/i, '');
}
function localSaveMeta(localKey) {
if (localKey.indexOf('soondesign_file:') === 0) {
return { source: 'cloud', name: fileName, type: parsedJson ? parsedJson.soonType : 1 };
}
if (prevSessionKey && prevSessionKey !== sessionKey &&
typeof window.soonLocalRenameKey === 'function') {
return cacheWriteForKey(sessionKey, { source: 'session', name: fileName }).then(function () {
return window.soonLocalRenameKey(prevSessionKey, sessionKey, { source: 'session', name: fileName })
.then(finishSessionSave);
}).catch(function () {
return Promise.reject(new Error('save_failed'));
return { source: 'session', name: fileName, type: parsedJson ? parsedJson.soonType : 1 };
}
function finishLocalSave(localKey) {
if (localKey.indexOf('soondesign_session:') === 0) {
try { sessionStorage.setItem(localKey, 'idb'); } catch (e) { /* ignore */ }
}
return { fileKey: localKey, name: fileName, version: 0, localOnly: true };
}
function saveLocalFirst() {
var localKey = resolveLocalSaveKey();
var prevKey = (name.indexOf('soondesign_session:') === 0 || name.indexOf('soondesign_file:') === 0) ? name : '';
var meta = localSaveMeta(localKey);
if (prevKey && prevKey !== localKey && typeof window.soonLocalRenameKey === 'function') {
return cacheWriteForKey(localKey, meta).then(function () {
return window.soonLocalRenameKey(prevKey, localKey, meta).then(function () {
return finishLocalSave(localKey);
});
});
}
return cacheWriteForKey(sessionKey, { source: 'session', name: fileName }).then(function () {
return finishSessionSave();
return cacheWriteForKey(localKey, meta).then(function () {
return finishLocalSave(localKey);
});
}
function syncCloudIfAuthed(localResult) {
if (!getAccessToken()) return Promise.resolve(localResult);
if (cloudRef && cloudRef.id) {
var ver = cloudRef.version;
if (ver == null && window._soonFileMeta && window._soonFileMeta.id === cloudRef.id) {
ver = window._soonFileMeta.version;
}
var cloudSaveName = fileName;
if (typeof window.soonResolveCloudFileName === 'function') {
var resolved = window.soonResolveCloudFileName(name);
if (resolved) cloudSaveName = resolved;
}
return updateCloudFile(cloudRef.id, cloudSaveName, str, ver).then(function (res) {
return afterCloudSave(res, localResult.fileKey || name);
}).catch(function () {
localResult.syncFailed = true;
return localResult;
});
}
var prevKey = localResult.fileKey || name;
return createCloudFile(fileName, str).then(function (res) {
return afterCloudSave(res, prevKey);
}).catch(function () {
return Promise.reject(new Error('save_failed'));
localResult.syncFailed = true;
return localResult;
});
}
if (!requireCloudAuth('保存')) {
return Promise.reject(new Error('unauthorized'));
}
if (cloudRef && cloudRef.id) {
var ver = cloudRef.version;
if (ver == null && window._soonFileMeta && window._soonFileMeta.id === cloudRef.id) {
ver = window._soonFileMeta.version;
}
if (typeof window.soonResolveCloudFileName === 'function') {
var cloudSaveName = window.soonResolveCloudFileName(name);
if (cloudSaveName) fileName = cloudSaveName;
}
var prevCloudKey = name;
return updateCloudFile(cloudRef.id, fileName, str, ver).then(function (res) {
return afterCloudSave(res, prevCloudKey);
});
}
var prevKey = name.indexOf('soondesign_session:') === 0 ? name : '';
return createCloudFile(fileName, str).then(function (res) {
return afterCloudSave(res, prevKey);
return saveLocalFirst().then(syncCloudIfAuthed).catch(function () {
return Promise.reject(new Error('save_failed'));
});
},
readFile: function (pathOrHandle) {
@@ -713,6 +818,11 @@
window.ipcRenderer = {
send: function (ch, a1, a2) {
if (ch === 'get-sys-language') { window._sysLanPending = true; setTimeout(function () { if (window._sysLanCb && window._sysLanPending) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
if (ch === 'get-sys-fonts' && bridge.getSystemFonts) {
bridge.getSystemFonts().then(function (fonts) {
if (typeof window._fontListCb === 'function') window._fontListCb(null, fonts || []);
});
}
if (ch === 'open-first-page' && bridge.openFirstPage) { bridge.openFirstPage(); }
if (ch === 'open-design-page' && bridge.openDesignPage && (a1 !== undefined || a2 !== undefined)) { bridge.openDesignPage(a1 || '', a2 || 1); }
if (ch === 'open-help-file' && bridge.openHelp) { bridge.openHelp(); }
@@ -720,6 +830,14 @@
},
on: function (ch, cb) {
if (ch === 'sys-lan') { window._sysLanCb = cb; if (window._sysLanPending) setTimeout(function () { if (window._sysLanCb) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
if (ch === 'font-list') {
window._fontListCb = cb;
if (bridge.getSystemFonts) {
bridge.getSystemFonts().then(function (fonts) {
if (typeof window._fontListCb === 'function') window._fontListCb(null, fonts || []);
});
}
}
if (ch === 'close') window._closeCb = cb;
}
};