ebe191b06d
- 会员改为永久激活方案;云端文件不再拦截非会员;预览弹窗内导出/打印才校验 - 首页最近文件支持本地记录,登录后与云端合并;移除独立会员页与订阅页 - 模板库入库管理:soon_templates 表、Admin 上传 CRUD、/templates 轻量列表与按需下载 Co-authored-by: Cursor <cursoragent@cursor.com>
358 lines
12 KiB
JavaScript
358 lines
12 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var FILE_PREFIX = 'soondesign_file:';
|
|
|
|
function soonIsWebPortal() {
|
|
return typeof window !== 'undefined' && window.platformBridge && !window.fs;
|
|
}
|
|
|
|
function soonGetAccessToken() {
|
|
if (typeof window.soonGetAccessToken === 'function') {
|
|
return window.soonGetAccessToken();
|
|
}
|
|
try { return localStorage.getItem('soon_access') || ''; } catch (e) { return ''; }
|
|
}
|
|
|
|
function soonParseFileKey(key) {
|
|
if (!key || typeof key !== 'string') return null;
|
|
var m = key.match(/^soondesign_file:(\d+)(?::v(\d+))?$/);
|
|
if (!m) return null;
|
|
return { id: parseInt(m[1], 10), version: m[2] ? parseInt(m[2], 10) : null };
|
|
}
|
|
|
|
function soonMakeFileKey(id, version) {
|
|
return FILE_PREFIX + id + ':v' + version;
|
|
}
|
|
|
|
function soonNormalizeSoonName(pathOrName) {
|
|
var n = String(pathOrName || 'design.soon');
|
|
if (n.indexOf(FILE_PREFIX) === 0) {
|
|
var meta = window._soonFileMeta;
|
|
return (meta && meta.name) ? meta.name : 'design.soon';
|
|
}
|
|
if (n.indexOf('soondesign_session:') === 0) {
|
|
n = n.replace(/^soondesign_session:/, '');
|
|
}
|
|
n = n.split(/[/\\]/).pop();
|
|
if (!/\.soon$/i.test(n)) n = (n.replace(/\.soon$/i, '') || 'design') + '.soon';
|
|
return n;
|
|
}
|
|
|
|
function soonApplyCloudMeta(data) {
|
|
if (!data || !data.id) return null;
|
|
window._soonFileMeta = { id: data.id, version: data.version, name: data.name };
|
|
return soonMakeFileKey(data.id, data.version);
|
|
}
|
|
|
|
function soonBindFileMeta(fileKey, opts) {
|
|
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) return;
|
|
var parsed = soonParseFileKey(fileKey);
|
|
if (!parsed) return;
|
|
var prev = window._soonFileMeta || {};
|
|
window._soonFileMeta = {
|
|
id: parsed.id,
|
|
version: parsed.version != null ? parsed.version : prev.version,
|
|
name: (opts && opts.name) || (prev.id === parsed.id ? prev.name : '') || ''
|
|
};
|
|
}
|
|
|
|
function soonFormatOpenTitle(name) {
|
|
if (!name) return '';
|
|
if (typeof window.soonDisplayFileName === 'function') return window.soonDisplayFileName(name);
|
|
return String(name).split(/[/\\]/).pop();
|
|
}
|
|
|
|
var _membership = {
|
|
loaded: false,
|
|
isMember: false,
|
|
name: '普通用户',
|
|
tier: 'free',
|
|
loading: null,
|
|
};
|
|
|
|
function soonApiBase() {
|
|
var cfg = (typeof window !== 'undefined' && window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base);
|
|
if (cfg) return String(cfg).replace(/\/+$/, '');
|
|
if (typeof window !== 'undefined' && window.location) {
|
|
return window.location.origin + '/api/v1';
|
|
}
|
|
return '/api/v1';
|
|
}
|
|
|
|
function soonApplyMembership(data) {
|
|
var m = data || {};
|
|
var sub = m.subscription || {};
|
|
var isMember = m.is_member === true
|
|
|| m.tier === 'member'
|
|
|| m.tier === 'pro'
|
|
|| (sub.status && sub.status !== 'free' && sub.status !== 'expired');
|
|
_membership = {
|
|
loaded: true,
|
|
isMember: !!isMember,
|
|
name: isMember ? (m.name || '会员') : '普通用户',
|
|
tier: m.tier || (isMember ? 'member' : 'free'),
|
|
loading: null,
|
|
};
|
|
if (typeof window.soonRefreshPortalIdentity === 'function') {
|
|
window.soonRefreshPortalIdentity();
|
|
}
|
|
return _membership;
|
|
}
|
|
|
|
function soonRefreshPortalIdentity() {
|
|
var el = document.getElementById('auth_identity');
|
|
if (!el) return;
|
|
if (!soonGetAccessToken()) {
|
|
el.style.display = 'none';
|
|
el.textContent = '';
|
|
return;
|
|
}
|
|
if (!_membership.loaded) {
|
|
soonLoadMembership(false).then(function () {
|
|
soonRefreshPortalIdentity();
|
|
});
|
|
return;
|
|
}
|
|
var text;
|
|
var cls = 'soon-portal-identity';
|
|
if (_membership.isMember) {
|
|
cls += ' soon-portal-identity--member';
|
|
text = _membership.name || '会员';
|
|
} else {
|
|
cls += ' soon-portal-identity--free';
|
|
text = _membership.name || '普通用户';
|
|
}
|
|
el.className = cls;
|
|
el.textContent = text;
|
|
el.style.display = 'inline-flex';
|
|
var account = document.getElementById('auth_account');
|
|
if (account) account.style.display = 'inline-flex';
|
|
}
|
|
|
|
function soonLoadMembership(force) {
|
|
if (_membership.loaded && !force) return Promise.resolve(_membership);
|
|
if (_membership.loading && !force) return _membership.loading;
|
|
if (!soonGetAccessToken()) {
|
|
soonApplyMembership(null);
|
|
return Promise.resolve(_membership);
|
|
}
|
|
var url = soonApiBase() + '/plans/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 (j) {
|
|
if (j && j.ok && j.data) {
|
|
soonApplyMembership(j.data.membership || j.data);
|
|
} else {
|
|
soonApplyMembership(null);
|
|
}
|
|
return _membership;
|
|
})
|
|
.catch(function () {
|
|
soonApplyMembership(null);
|
|
return _membership;
|
|
})
|
|
.finally(function () {
|
|
_membership.loading = null;
|
|
});
|
|
return _membership.loading;
|
|
}
|
|
|
|
function soonIsMember() {
|
|
return !!_membership.isMember;
|
|
}
|
|
|
|
function soonGuardPreviewDeliver(actionLabel, onAllowed) {
|
|
if (typeof onAllowed !== 'function') return;
|
|
if (!soonIsWebPortal()) {
|
|
onAllowed();
|
|
return;
|
|
}
|
|
if (!soonGetAccessToken()) {
|
|
if (typeof window.soonShowLoginGate === 'function') {
|
|
window.soonShowLoginGate({
|
|
reason: actionLabel || '导出或打印',
|
|
onSuccess: function () {
|
|
soonGuardPreviewDeliver(actionLabel, onAllowed);
|
|
},
|
|
});
|
|
} else {
|
|
soonRequireLogin(actionLabel);
|
|
}
|
|
return;
|
|
}
|
|
if (soonIsMember()) {
|
|
onAllowed();
|
|
return;
|
|
}
|
|
if (typeof window.soonShowActivateGate === 'function') {
|
|
window.soonShowActivateGate({
|
|
reason: actionLabel || '导出或打印',
|
|
onSuccess: function () {
|
|
onAllowed();
|
|
},
|
|
});
|
|
} else {
|
|
soonToast('请先激活会员后再' + (actionLabel || '操作'), 'warn');
|
|
}
|
|
}
|
|
|
|
function soonGuardCloudSave() {
|
|
return true;
|
|
}
|
|
|
|
function soonSoonTypeFromJson(j) {
|
|
return j && j.soonType ? j.soonType : (j && j.backBlackPic ? 2 : 1);
|
|
}
|
|
|
|
function soonPutSoonSession(j, fileName) {
|
|
if (!j) return '';
|
|
var hint = (fileName || 'design').replace(/\.soon$/i, '');
|
|
var key = 'soondesign_session:' + hint + '-' + Date.now();
|
|
try {
|
|
sessionStorage.setItem(key, JSON.stringify(j));
|
|
try { localStorage.setItem(key, JSON.stringify(j)); } catch (e2) { /* ignore quota */ }
|
|
return key;
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function soonOpenSoonJsonLocally(j, fileName) {
|
|
var key = soonPutSoonSession(j, fileName);
|
|
if (!key) return '';
|
|
var type = soonSoonTypeFromJson(j);
|
|
if (window.platformBridge && window.platformBridge.openDesignPage) {
|
|
window.platformBridge.openDesignPage(key, type);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
function soonToast(message, type) {
|
|
if (typeof window.soonToast === 'function') window.soonToast(message, type);
|
|
}
|
|
|
|
function soonRequireLogin(actionLabel) {
|
|
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';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function soonParseApiError(response, bodyText) {
|
|
var msg = '操作失败,请稍后重试';
|
|
var code = '';
|
|
try {
|
|
var j = bodyText ? JSON.parse(bodyText) : null;
|
|
if (j && j.message) msg = j.message;
|
|
if (j && j.error) code = j.error;
|
|
} catch (e) { /* ignore */ }
|
|
if (response && response.status === 401) {
|
|
return { status: 401, message: '请先登录', code: code || 'unauthorized' };
|
|
}
|
|
if (response && response.status === 409) {
|
|
return { status: 409, message: '文件已被其他端修改,请刷新后重试', code: code || 'conflict' };
|
|
}
|
|
if (response && response.status === 413) {
|
|
if (code === 'file_limit_exceeded') {
|
|
return { status: 413, message: msg || '文件数量已达上限,请清理文件', code: code };
|
|
}
|
|
return { status: 413, message: msg || '存储空间不足,请清理文件', code: code || 'quota_exceeded' };
|
|
}
|
|
if (response && response.status === 404) {
|
|
return { status: 404, message: '文件不存在', code: code || 'not_found' };
|
|
}
|
|
return { status: response ? response.status : 0, message: msg, code: code };
|
|
}
|
|
|
|
function soonShowApiError(info) {
|
|
if (!info) return;
|
|
var kind = 'error';
|
|
if (info.status === 401) kind = 'warn';
|
|
else if (info.status === 409) kind = 'warn';
|
|
else if (info.status === 413) kind = 'warn';
|
|
soonToast(info.message, kind);
|
|
if (info.status === 401) {
|
|
soonRequireLogin('操作');
|
|
}
|
|
}
|
|
|
|
function soonDisplayFileName(pathOrKey) {
|
|
if (!pathOrKey) return '';
|
|
if (pathOrKey.indexOf(FILE_PREFIX) === 0) {
|
|
var meta = window._soonFileMeta;
|
|
if (meta && meta.name) return meta.name;
|
|
var parsed = soonParseFileKey(pathOrKey);
|
|
return parsed ? ('文件 #' + parsed.id) : pathOrKey;
|
|
}
|
|
if (pathOrKey.indexOf('soondesign_session:') === 0) {
|
|
return pathOrKey.replace(/^soondesign_session:/, '');
|
|
}
|
|
return String(pathOrKey).split(/[/\\]/).pop();
|
|
}
|
|
|
|
function soonHandlePayReturn() {
|
|
var paid = null;
|
|
var flag = false;
|
|
try {
|
|
paid = new URLSearchParams(location.search).get('paid');
|
|
} catch (e) { /* ignore */ }
|
|
try {
|
|
flag = !!sessionStorage.getItem('soon_pay_return');
|
|
} catch (e) { /* ignore */ }
|
|
if (!paid && !flag) return;
|
|
try {
|
|
sessionStorage.removeItem('soon_pay_return');
|
|
} catch (e) { /* ignore */ }
|
|
if (paid) {
|
|
try {
|
|
var u = new URL(location.href);
|
|
u.searchParams.delete('paid');
|
|
history.replaceState(null, '', u.pathname + (u.search || '') + (u.hash || ''));
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
soonLoadMembership(true).then(function () {
|
|
soonRefreshPortalIdentity();
|
|
soonToast('支付成功,会员已激活', 'success');
|
|
});
|
|
}
|
|
|
|
window.soonIsWebPortal = soonIsWebPortal;
|
|
window.soonParseFileKey = soonParseFileKey;
|
|
window.soonMakeFileKey = soonMakeFileKey;
|
|
window.soonNormalizeSoonName = soonNormalizeSoonName;
|
|
window.soonApplyCloudMeta = soonApplyCloudMeta;
|
|
window.soonBindFileMeta = soonBindFileMeta;
|
|
window.soonFormatOpenTitle = soonFormatOpenTitle;
|
|
window.soonGuardCloudSave = soonGuardCloudSave;
|
|
window.soonGuardPreviewDeliver = soonGuardPreviewDeliver;
|
|
window.soonHandlePayReturn = soonHandlePayReturn;
|
|
window.soonOpenSoonJsonLocally = soonOpenSoonJsonLocally;
|
|
window.soonPutSoonSession = soonPutSoonSession;
|
|
window.soonSoonTypeFromJson = soonSoonTypeFromJson;
|
|
window.soonLoadMembership = soonLoadMembership;
|
|
window.soonApplyMembership = soonApplyMembership;
|
|
window.soonRefreshPortalIdentity = soonRefreshPortalIdentity;
|
|
window.soonIsMember = soonIsMember;
|
|
window.soonRequireLogin = soonRequireLogin;
|
|
window.soonParseApiError = soonParseApiError;
|
|
window.soonShowApiError = soonShowApiError;
|
|
window.soonDisplayFileName = soonDisplayFileName;
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', soonHandlePayReturn);
|
|
} else {
|
|
soonHandlePayReturn();
|
|
}
|
|
})();
|