永久会员与模板库后台化:预览门控、轻量首页与 Admin CRUD
- 会员改为永久激活方案;云端文件不再拦截非会员;预览弹窗内导出/打印才校验 - 首页最近文件支持本地记录,登录后与云端合并;移除独立会员页与订阅页 - 模板库入库管理:soon_templates 表、Admin 上传 CRUD、/templates 轻量列表与按需下载 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -66,9 +66,8 @@
|
||||
var _membership = {
|
||||
loaded: false,
|
||||
isMember: false,
|
||||
name: '免费版',
|
||||
name: '普通用户',
|
||||
tier: 'free',
|
||||
expiresAt: null,
|
||||
loading: null,
|
||||
};
|
||||
|
||||
@@ -81,18 +80,6 @@
|
||||
return '/api/v1';
|
||||
}
|
||||
|
||||
function formatExpireDate(iso) {
|
||||
if (!iso) return '';
|
||||
var d = new Date(String(iso).replace(' ', 'T'));
|
||||
if (isNaN(d.getTime())) return '';
|
||||
var y = d.getFullYear();
|
||||
var mo = String(d.getMonth() + 1);
|
||||
var day = String(d.getDate());
|
||||
if (mo.length < 2) mo = '0' + mo;
|
||||
if (day.length < 2) day = '0' + day;
|
||||
return y + '-' + mo + '-' + day;
|
||||
}
|
||||
|
||||
function soonApplyMembership(data) {
|
||||
var m = data || {};
|
||||
var sub = m.subscription || {};
|
||||
@@ -103,9 +90,8 @@
|
||||
_membership = {
|
||||
loaded: true,
|
||||
isMember: !!isMember,
|
||||
name: m.name || (isMember ? '订阅版' : '免费版'),
|
||||
name: isMember ? (m.name || '会员') : '普通用户',
|
||||
tier: m.tier || (isMember ? 'member' : 'free'),
|
||||
expiresAt: sub.expires_at || null,
|
||||
loading: null,
|
||||
};
|
||||
if (typeof window.soonRefreshPortalIdentity === 'function') {
|
||||
@@ -132,13 +118,10 @@
|
||||
var cls = 'soon-portal-identity';
|
||||
if (_membership.isMember) {
|
||||
cls += ' soon-portal-identity--member';
|
||||
text = _membership.name || '订阅版';
|
||||
if (_membership.expiresAt) {
|
||||
text += ' · 至 ' + formatExpireDate(_membership.expiresAt);
|
||||
}
|
||||
text = _membership.name || '会员';
|
||||
} else {
|
||||
cls += ' soon-portal-identity--free';
|
||||
text = _membership.name || '免费版';
|
||||
text = _membership.name || '普通用户';
|
||||
}
|
||||
el.className = cls;
|
||||
el.textContent = text;
|
||||
@@ -182,34 +165,70 @@
|
||||
return !!_membership.isMember;
|
||||
}
|
||||
|
||||
function soonMemberPageUrl() {
|
||||
return 'member.web.html';
|
||||
}
|
||||
|
||||
function soonRequireMember(actionLabel) {
|
||||
if (!soonGetAccessToken()) return soonRequireLogin(actionLabel);
|
||||
if (soonIsMember()) return true;
|
||||
if (typeof window.soonShowSubscribeGate === 'function') {
|
||||
window.soonShowSubscribeGate({ action: actionLabel || '使用' });
|
||||
} else {
|
||||
soonToast('订阅后可' + (actionLabel || '使用'), 'warn');
|
||||
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');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function soonGuardCloudSave() {
|
||||
if (!soonIsWebPortal()) return true;
|
||||
return soonRequireMember('保存');
|
||||
return true;
|
||||
}
|
||||
|
||||
function soonGuardMemberPreview() {
|
||||
if (!soonIsWebPortal()) return true;
|
||||
return soonRequireMember('预览效果');
|
||||
function soonSoonTypeFromJson(j) {
|
||||
return j && j.soonType ? j.soonType : (j && j.backBlackPic ? 2 : 1);
|
||||
}
|
||||
|
||||
function soonGuardMemberExport() {
|
||||
if (!soonIsWebPortal()) return true;
|
||||
return soonRequireMember('导出');
|
||||
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) {
|
||||
@@ -244,14 +263,11 @@
|
||||
if (response && response.status === 409) {
|
||||
return { status: 409, message: '文件已被其他端修改,请刷新后重试', code: code || 'conflict' };
|
||||
}
|
||||
if (response && response.status === 403 && (code === 'membership_required' || msg.indexOf('订阅') >= 0 || msg.indexOf('会员') >= 0)) {
|
||||
return { status: 403, message: msg || '此功能需要订阅后使用', code: code || 'membership_required' };
|
||||
}
|
||||
if (response && response.status === 413) {
|
||||
if (code === 'file_limit_exceeded') {
|
||||
return { status: 413, message: msg || '文件数量已达上限,请清理文件或续订', code: code };
|
||||
return { status: 413, message: msg || '文件数量已达上限,请清理文件', code: code };
|
||||
}
|
||||
return { status: 413, message: msg || '存储空间不足,请清理文件或续订', code: code || 'quota_exceeded' };
|
||||
return { status: 413, message: msg || '存储空间不足,请清理文件', code: code || 'quota_exceeded' };
|
||||
}
|
||||
if (response && response.status === 404) {
|
||||
return { status: 404, message: '文件不存在', code: code || 'not_found' };
|
||||
@@ -265,14 +281,9 @@
|
||||
if (info.status === 401) kind = 'warn';
|
||||
else if (info.status === 409) kind = 'warn';
|
||||
else if (info.status === 413) kind = 'warn';
|
||||
else if (info.status === 403 && info.code === 'membership_required') kind = 'warn';
|
||||
soonToast(info.message, kind);
|
||||
if (info.status === 401) {
|
||||
soonRequireLogin('操作');
|
||||
} else if (info.status === 403 && info.code === 'membership_required') {
|
||||
if (typeof window.soonShowSubscribeGate === 'function') {
|
||||
window.soonShowSubscribeGate({ action: '使用' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +301,32 @@
|
||||
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;
|
||||
@@ -298,15 +335,23 @@
|
||||
window.soonBindFileMeta = soonBindFileMeta;
|
||||
window.soonFormatOpenTitle = soonFormatOpenTitle;
|
||||
window.soonGuardCloudSave = soonGuardCloudSave;
|
||||
window.soonGuardMemberPreview = soonGuardMemberPreview;
|
||||
window.soonGuardMemberExport = soonGuardMemberExport;
|
||||
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.soonRequireMember = soonRequireMember;
|
||||
window.soonRequireLogin = soonRequireLogin;
|
||||
window.soonParseApiError = soonParseApiError;
|
||||
window.soonShowApiError = soonShowApiError;
|
||||
window.soonDisplayFileName = soonDisplayFileName;
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', soonHandlePayReturn);
|
||||
} else {
|
||||
soonHandlePayReturn();
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user