Web 端数据交互与 design2 布局修复

统一门户 JSON 契约:新增 GET /templates/{id},模板/云文件/保存/打开走虚拟 key;
layer 保存弹层替代 prompt,本地 .soon 已登录后台 POST 登记;修复保存静默失败与 design2 全宽布局。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-09 01:20:34 +08:00
parent ebe191b06d
commit 877fd278c2
27 changed files with 1232 additions and 326 deletions
+200 -19
View File
@@ -2,9 +2,23 @@
'use strict';
var FILE_PREFIX = 'soondesign_file:';
var TEMPLATE_PREFIX = 'soondesign_template:';
function soonParseTemplateKey(key) {
if (!key || typeof key !== 'string') return null;
var m = key.match(/^soondesign_template:(\d+)$/);
if (!m) return null;
return { id: parseInt(m[1], 10) };
}
function soonMakeTemplateKey(id) {
return TEMPLATE_PREFIX + id;
}
function soonIsWebPortal() {
return typeof window !== 'undefined' && window.platformBridge && !window.fs;
if (typeof window === 'undefined') return false;
if (window.SOON_DEPLOY_CONFIG) return true;
return !!(window.platformBridge && window.fs == null);
}
function soonGetAccessToken() {
@@ -31,6 +45,10 @@
var meta = window._soonFileMeta;
return (meta && meta.name) ? meta.name : 'design.soon';
}
if (n.indexOf(TEMPLATE_PREFIX) === 0) {
var tpl = soonParseTemplateKey(n);
return tpl ? ('template-' + tpl.id + '.soon') : 'template.soon';
}
if (n.indexOf('soondesign_session:') === 0) {
n = n.replace(/^soondesign_session:/, '');
}
@@ -45,8 +63,68 @@
return soonMakeFileKey(data.id, data.version);
}
function soonSyncOpenNavigation(fileKey, type) {
if (typeof sessionStorage === 'undefined') {
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) window._soonFileMeta = null;
return;
}
try {
if (fileKey) {
sessionStorage.setItem('soondesign_open_file', fileKey);
sessionStorage.setItem('soondesign_open_type', String(type || 1));
if (fileKey.indexOf(FILE_PREFIX) === 0 && window._soonFileMeta) {
sessionStorage.setItem('soondesign_open_meta', JSON.stringify(window._soonFileMeta));
} else {
sessionStorage.removeItem('soondesign_open_meta');
if (fileKey.indexOf(FILE_PREFIX) !== 0) window._soonFileMeta = null;
}
} else {
sessionStorage.removeItem('soondesign_open_file');
sessionStorage.removeItem('soondesign_open_type');
sessionStorage.removeItem('soondesign_open_meta');
window._soonFileMeta = null;
}
} catch (e) { /* ignore quota */ }
}
function soonResolveOpenFileKey(getParams) {
var params = typeof getParams === 'function' ? getParams() : null;
if (!params || typeof params.has !== 'function') return '';
var file = params.has('file') ? (params.get('file') || '') : null;
if (file === null && typeof sessionStorage !== 'undefined') {
try {
file = sessionStorage.getItem('soondesign_open_file') || '';
if (file) {
sessionStorage.removeItem('soondesign_open_file');
sessionStorage.removeItem('soondesign_open_type');
}
} catch (e) {
file = '';
}
}
return file || '';
}
function soonConsumeOpenMeta(fileKey) {
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) {
window._soonFileMeta = null;
return;
}
if (typeof sessionStorage === 'undefined') return;
try {
var metaRaw = sessionStorage.getItem('soondesign_open_meta');
if (metaRaw) {
window._soonFileMeta = JSON.parse(metaRaw);
sessionStorage.removeItem('soondesign_open_meta');
}
} catch (e) { /* ignore */ }
}
function soonBindFileMeta(fileKey, opts) {
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) return;
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) {
window._soonFileMeta = null;
return;
}
var parsed = soonParseFileKey(fileKey);
if (!parsed) return;
var prev = window._soonFileMeta || {};
@@ -137,16 +215,22 @@
soonApplyMembership(null);
return Promise.resolve(_membership);
}
var url = soonApiBase() + '/plans/me';
var url = soonApiBase() + '/auth/me';
var fetchFn = typeof window.soonAuthedFetch === 'function'
? window.soonAuthedFetch(url, { headers: { Accept: 'application/json' } })
: fetch(url, { headers: { Authorization: 'Bearer ' + soonGetAccessToken(), Accept: 'application/json' } });
_membership.loading = fetchFn
.then(function (r) { return r.json(); })
.then(function (r) {
if (r.status === 401) {
soonApplyMembership(null);
return null;
}
return r.json();
})
.then(function (j) {
if (j && j.ok && j.data) {
soonApplyMembership(j.data.membership || j.data);
} else {
soonApplyMembership(j.data);
} else if (j !== null) {
soonApplyMembership(null);
}
return _membership;
@@ -184,20 +268,27 @@
}
return;
}
if (soonIsMember()) {
onAllowed();
function allowOrActivate() {
if (soonIsMember()) {
onAllowed();
return;
}
if (typeof window.soonShowActivateGate === 'function') {
window.soonShowActivateGate({
reason: actionLabel || '导出或打印',
onSuccess: function () {
onAllowed();
},
});
} else {
soonToast('请先激活会员后再' + (actionLabel || '操作'), 'warn');
}
}
if (!_membership.loaded) {
soonLoadMembership(false).then(allowOrActivate);
return;
}
if (typeof window.soonShowActivateGate === 'function') {
window.soonShowActivateGate({
reason: actionLabel || '导出或打印',
onSuccess: function () {
onAllowed();
},
});
} else {
soonToast('请先激活会员后再' + (actionLabel || '操作'), 'warn');
}
allowOrActivate();
}
function soonGuardCloudSave() {
@@ -223,7 +314,11 @@
function soonOpenSoonJsonLocally(j, fileName) {
var key = soonPutSoonSession(j, fileName);
if (!key) return '';
if (!key) {
soonToast('无法打开文件(存储空间不足)', 'error');
return '';
}
soonScheduleCloudImport(key, fileName);
var type = soonSoonTypeFromJson(j);
if (window.platformBridge && window.platformBridge.openDesignPage) {
window.platformBridge.openDesignPage(key, type);
@@ -231,6 +326,75 @@
return key;
}
var PENDING_IMPORT_KEY = 'soondesign_pending_import';
function soonScheduleCloudImport(sessionKey, fileName) {
if (!soonGetAccessToken() || !sessionKey) return;
if (sessionKey.indexOf('soondesign_session:') !== 0) return;
try {
sessionStorage.setItem(PENDING_IMPORT_KEY, JSON.stringify({
sessionKey: sessionKey,
name: soonNormalizeSoonName(fileName || 'design.soon'),
at: Date.now()
}));
} catch (e) { /* ignore */ }
}
function soonClearPendingImport() {
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e) { /* ignore */ }
}
function soonTryConsumeCloudImport(currentFileKey) {
if (!currentFileKey || currentFileKey.indexOf('soondesign_session:') !== 0) return;
if (!soonGetAccessToken()) return;
if (typeof window.openAs !== 'undefined' && window.openAs && window.openAs.name &&
window.openAs.name.indexOf(FILE_PREFIX) === 0) {
soonClearPendingImport();
return;
}
var pendingRaw;
try {
pendingRaw = sessionStorage.getItem(PENDING_IMPORT_KEY);
} catch (e) {
return;
}
if (!pendingRaw) return;
var pending;
try {
pending = JSON.parse(pendingRaw);
} catch (e) {
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e2) { /* ignore */ }
return;
}
if (!pending || pending.sessionKey !== currentFileKey) return;
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e) { /* ignore */ }
var jsonRaw;
try {
jsonRaw = sessionStorage.getItem(currentFileKey);
if (!jsonRaw && typeof localStorage !== 'undefined') jsonRaw = localStorage.getItem(currentFileKey);
} catch (e) {
return;
}
if (!jsonRaw) return;
var bridge = window.platformBridge;
if (!bridge || typeof bridge.importSoonFile !== 'function') return;
bridge.importSoonFile(pending.name || 'design.soon', jsonRaw).then(function (res) {
if (res && res.fileKey && typeof window.openAs !== 'undefined' && window.openAs) {
window.openAs.name = res.fileKey;
}
}).catch(function (err) {
if (err && err.status) return;
soonToast((err && err.message) ? err.message : '云端登记失败,可稍后保存重试', 'warn');
});
}
function soonReportOpenLoadError(message) {
soonToast(message || '文件加载失败', 'error');
}
function soonToast(message, type) {
if (typeof window.soonToast === 'function') window.soonToast(message, type);
}
@@ -289,6 +453,14 @@
function soonDisplayFileName(pathOrKey) {
if (!pathOrKey) return '';
if (pathOrKey.indexOf(TEMPLATE_PREFIX) === 0) {
var tplMeta = window._soonTemplateMeta;
var tplParsed = soonParseTemplateKey(pathOrKey);
if (tplMeta && tplParsed && tplMeta.id === tplParsed.id && tplMeta.name) {
return tplMeta.name;
}
return tplParsed ? ('模板 #' + tplParsed.id) : pathOrKey;
}
if (pathOrKey.indexOf(FILE_PREFIX) === 0) {
var meta = window._soonFileMeta;
if (meta && meta.name) return meta.name;
@@ -333,12 +505,21 @@
window.soonNormalizeSoonName = soonNormalizeSoonName;
window.soonApplyCloudMeta = soonApplyCloudMeta;
window.soonBindFileMeta = soonBindFileMeta;
window.soonSyncOpenNavigation = soonSyncOpenNavigation;
window.soonResolveOpenFileKey = soonResolveOpenFileKey;
window.soonConsumeOpenMeta = soonConsumeOpenMeta;
window.soonFormatOpenTitle = soonFormatOpenTitle;
window.soonGuardCloudSave = soonGuardCloudSave;
window.soonGuardPreviewDeliver = soonGuardPreviewDeliver;
window.soonHandlePayReturn = soonHandlePayReturn;
window.soonOpenSoonJsonLocally = soonOpenSoonJsonLocally;
window.soonPutSoonSession = soonPutSoonSession;
window.soonScheduleCloudImport = soonScheduleCloudImport;
window.soonTryConsumeCloudImport = soonTryConsumeCloudImport;
window.soonClearPendingImport = soonClearPendingImport;
window.soonReportOpenLoadError = soonReportOpenLoadError;
window.soonParseTemplateKey = soonParseTemplateKey;
window.soonMakeTemplateKey = soonMakeTemplateKey;
window.soonSoonTypeFromJson = soonSoonTypeFromJson;
window.soonLoadMembership = soonLoadMembership;
window.soonApplyMembership = soonApplyMembership;