fix(web): 弹窗登录注册后刷新顶栏,导出流程支持内嵌注册

弹窗登录/注册成功后隐藏 guest 顶栏并更新会员身份;导出打印 gate 内立即注册改为同层弹窗,注册完自动继续会员验证。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-25 09:18:38 +08:00
parent 5fb269cc0a
commit 7dadc41bb1
2 changed files with 193 additions and 88 deletions
+84 -43
View File
@@ -2,6 +2,8 @@
'use strict'; 'use strict';
var _gate = { index: null, opts: null }; var _gate = { index: null, opts: null };
var MAIL_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16v12H4z"/><path d="M4 8l8 5 8-5"/></svg>';
var LOCK_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="10" rx="1"/><path d="M8 11V8a4 4 0 118 0v3"/></svg>';
function esc(s) { function esc(s) {
if (s == null) return ''; if (s == null) return '';
@@ -36,28 +38,36 @@
toast('登录功能暂不可用,请刷新页面后重试', 'warn'); toast('登录功能暂不可用,请刷新页面后重试', 'warn');
} }
function shellHtml(reason) { function shellHtml(panel, reason) {
var mailIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16v12H4z"/><path d="M4 8l8 5 8-5"/></svg>'; var isReg = panel === 'register';
var lockIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="10" rx="1"/><path d="M8 11V8a4 4 0 118 0v3"/></svg>'; var action = esc(reason || '导出或打印');
return '<div class="soon-login-gate">' + return '<div class="soon-login-gate">' +
'<header class="soon-login-gate__head">' + '<header class="soon-login-gate__head">' +
'<span class="soon-login-gate__eyebrow">登录</span>' + '<span class="soon-login-gate__eyebrow">' + (isReg ? '注册' : '登录') + '</span>' +
'<h3 class="soon-login-gate__title">登录后继续</h3>' + '<h3 class="soon-login-gate__title">' + (isReg ? '注册后继续' : '登录后继续') + '</h3>' +
'<p class="soon-login-gate__desc">预览内' + esc(reason || '导出或打印') + '成品需先登录账号。</p>' + '<p class="soon-login-gate__desc">' +
'</header>' + (isReg ? '注册并登录后即可' + action + '成品。' : '预览内' + action + '成品需先登录账号。') +
'<form class="soon-login-gate__form" data-role="login-form">' + '</p></header>' +
'<form class="soon-login-gate__form" data-role="' + (isReg ? 'register' : 'login') + '-form">' +
'<div class="soon-input-wrap soon-login-gate__field">' + '<div class="soon-input-wrap soon-login-gate__field">' +
'<span class="soon-input-icon" aria-hidden="true">' + mailIcon + '</span>' + '<span class="soon-input-icon" aria-hidden="true">' + MAIL_ICON + '</span>' +
'<input type="email" name="email" required class="soon-input soon-login-gate__input" placeholder="邮箱" autocomplete="email">' + '<input type="email" name="email" required class="soon-input soon-login-gate__input" placeholder="邮箱" autocomplete="email">' +
'</div>' + '</div>' +
'<div class="soon-input-wrap soon-login-gate__field">' + '<div class="soon-input-wrap soon-login-gate__field">' +
'<span class="soon-input-icon" aria-hidden="true">' + lockIcon + '</span>' + '<span class="soon-input-icon" aria-hidden="true">' + LOCK_ICON + '</span>' +
'<input type="password" name="password" required class="soon-input soon-login-gate__input" placeholder="密码" autocomplete="current-password">' + '<input type="password" name="password" required' +
(isReg ? ' minlength="8"' : '') +
' class="soon-input soon-login-gate__input" placeholder="' +
(isReg ? '密码(至少 8 位)' : '密码') +
'" autocomplete="' + (isReg ? 'new-password' : 'current-password') + '">' +
'</div>' + '</div>' +
'<p class="soon-login-gate__error" data-role="login-error" hidden></p>' + '<p class="soon-login-gate__error" data-role="' + (isReg ? 'register' : 'login') + '-error" hidden></p>' +
'<button type="submit" class="soon-btn soon-btn--block soon-login-gate__submit">登录</button>' + '<button type="submit" class="soon-btn soon-btn--block soon-login-gate__submit">' +
'<p class="soon-login-gate__links">还没有账号?<a href="register.web.html" target="_blank" rel="noopener">立即注册</a></p>' + (isReg ? '注册并登录' : '登录') +
'</form>' + '</button>' +
'<p class="soon-login-gate__links">' +
(isReg ? '已有账号?<a href="#" data-action="show-login">去登录</a>' : '还没有账号?<a href="#" data-action="show-register">立即注册</a>') +
'</p></form>' +
'<footer class="soon-login-gate__foot">' + '<footer class="soon-login-gate__foot">' +
'<button type="button" class="soon-login-gate__stay" data-action="stay">继续设计</button>' + '<button type="button" class="soon-login-gate__stay" data-action="stay">继续设计</button>' +
'</footer></div>'; '</footer></div>';
@@ -72,49 +82,59 @@
if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask(); if (typeof window.soonCleanupLayerMask === 'function') window.soonCleanupLayerMask();
} }
function bindModal(modal) { function finishAuthSuccess() {
var form = modal.querySelector('[data-role="login-form"]'); var opts = _gate.opts;
var errEl = modal.querySelector('[data-role="login-error"]'); closeGate();
modal.addEventListener('click', function (e) { var chain = typeof window.soonRefreshPortalAuthUi === 'function'
var stay = e.target.closest('[data-action="stay"]'); ? Promise.resolve(window.soonRefreshPortalAuthUi())
if (stay) { : Promise.resolve();
e.preventDefault(); return chain.then(function () {
closeGate(); if (typeof window.soonReloadRecentFiles === 'function') window.soonReloadRecentFiles();
} if (opts && typeof opts.onSuccess === 'function') opts.onSuccess();
}); });
}
function storeTokens(data) {
if (!data || !data.access_token) return false;
localStorage.setItem('soon_access', data.access_token);
if (data.refresh_token) localStorage.setItem('soon_refresh', data.refresh_token);
return true;
}
function swapPanel(modal, panel) {
if (!modal || !modal.parentNode) return;
var parent = modal.parentNode;
var reason = _gate.opts && _gate.opts.reason;
modal.outerHTML = shellHtml(panel, reason);
var root = parent.querySelector('.soon-login-gate');
if (root) bindModal(root);
}
function bindAuthForm(modal, role, path, failMsg) {
var form = modal.querySelector('[data-role="' + role + '-form"]');
if (!form) return; if (!form) return;
form.addEventListener('submit', function (e) { form.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
var errEl = modal.querySelector('[data-role="' + role + '-error"]');
if (errEl) { if (errEl) {
errEl.hidden = true; errEl.hidden = true;
errEl.textContent = ''; errEl.textContent = '';
} }
var email = (form.email && form.email.value || '').trim(); var email = (form.email && form.email.value || '').trim();
var password = form.password ? form.password.value : ''; var password = form.password ? form.password.value : '';
fetch(apiBase() + '/auth/login', { fetch(apiBase() + path, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, password: password }), body: JSON.stringify({ email: email, password: password }),
}).then(function (r) { return r.json(); }).then(function (j) { }).then(function (r) { return r.json(); }).then(function (j) {
if (!j.ok || !j.data || !j.data.access_token) { if (!j.ok || !storeTokens(j.data)) {
if (errEl) { if (errEl) {
errEl.textContent = (j && j.message) || '登录失败'; errEl.textContent = (j && j.message) || failMsg;
errEl.hidden = false; errEl.hidden = false;
} }
return; return;
} }
localStorage.setItem('soon_access', j.data.access_token); finishAuthSuccess();
if (j.data.refresh_token) localStorage.setItem('soon_refresh', j.data.refresh_token);
var opts = _gate.opts;
closeGate();
var load = typeof window.soonLoadMembership === 'function'
? window.soonLoadMembership(true)
: Promise.resolve();
load.then(function () {
if (typeof window.soonRefreshPortalIdentity === 'function') window.soonRefreshPortalIdentity();
if (typeof window.soonReloadRecentFiles === 'function') window.soonReloadRecentFiles();
if (opts && typeof opts.onSuccess === 'function') opts.onSuccess();
});
}).catch(function () { }).catch(function () {
if (errEl) { if (errEl) {
errEl.textContent = '网络错误,请稍后重试'; errEl.textContent = '网络错误,请稍后重试';
@@ -124,7 +144,28 @@
}); });
} }
function soonShowLoginGate(opts) { function bindModal(modal) {
modal.addEventListener('click', function (e) {
if (e.target.closest('[data-action="stay"]')) {
e.preventDefault();
closeGate();
return;
}
if (e.target.closest('[data-action="show-register"]')) {
e.preventDefault();
swapPanel(modal, 'register');
return;
}
if (e.target.closest('[data-action="show-login"]')) {
e.preventDefault();
swapPanel(modal, 'login');
}
});
bindAuthForm(modal, 'login', '/auth/login', '登录失败');
bindAuthForm(modal, 'register', '/auth/register', '注册失败');
}
function openGate(opts, panel) {
opts = opts || {}; opts = opts || {};
ensureLayer(function () { ensureLayer(function () {
if (_gate.index != null) { if (_gate.index != null) {
@@ -141,7 +182,7 @@
shadeClose: true, shadeClose: true,
area: [width + 'px', 'auto'], area: [width + 'px', 'auto'],
offset: 'auto', offset: 'auto',
content: shellHtml(opts.reason), content: shellHtml(panel, opts.reason),
success: function (layero, index) { success: function (layero, index) {
var layerEl = layero && layero[0] ? layero[0] : layero; var layerEl = layero && layero[0] ? layero[0] : layero;
if (layerEl && layerEl.classList) layerEl.classList.add('soon-layer--login-gate'); if (layerEl && layerEl.classList) layerEl.classList.add('soon-layer--login-gate');
@@ -153,7 +194,6 @@
} }
var modal = layerEl.querySelector('.soon-login-gate'); var modal = layerEl.querySelector('.soon-login-gate');
_gate.index = index; _gate.index = index;
_gate.opts = opts;
if (modal) bindModal(modal); if (modal) bindModal(modal);
}, },
end: function () { end: function () {
@@ -163,5 +203,6 @@
}); });
} }
window.soonShowLoginGate = soonShowLoginGate; window.soonShowLoginGate = function (opts) { openGate(opts, 'login'); };
window.soonShowRegisterGate = function (opts) { openGate(opts, 'register'); };
})(); })();
+109 -45
View File
@@ -1,30 +1,70 @@
(function () { (function () {
'use strict'; 'use strict';
var _opts = {};
var _logoutBound = false;
function showAuthedNav() { function showAuthedNav() {
var guest = document.getElementById('auth_guest'); var guest = document.getElementById('auth_guest');
var account = document.getElementById('auth_account'); var account = document.getElementById('auth_account');
var session = document.getElementById('auth_session'); var session = document.getElementById('auth_session');
var logout = document.getElementById('auth_logout');
if (guest) guest.style.display = 'none'; if (guest) guest.style.display = 'none';
if (account) account.style.display = 'inline-flex'; if (account) account.style.display = 'inline-flex';
if (session) session.style.display = 'inline-flex'; if (session) session.style.display = 'inline-flex';
if (logout) logout.style.display = 'inline-flex';
}
function showGuestNav() {
var guest = document.getElementById('auth_guest');
var account = document.getElementById('auth_account');
var session = document.getElementById('auth_session');
var admin = document.getElementById('auth_admin');
var avatar = document.getElementById('auth_avatar');
var identity = document.getElementById('auth_identity');
var logout = document.getElementById('auth_logout');
if (guest) guest.style.display = '';
if (account) account.style.display = 'none';
if (session) session.style.display = 'none';
if (admin) admin.style.display = 'none';
if (logout) logout.style.display = 'none';
if (avatar) {
avatar.style.display = 'none';
avatar.textContent = '';
avatar.title = '';
}
if (identity) {
identity.style.display = 'none';
identity.textContent = '';
}
} }
function clearSession() { function clearSession() {
if (typeof window.soonClearAuthSession === 'function') { if (typeof window.soonClearAuthSession === 'function') window.soonClearAuthSession();
window.soonClearAuthSession(); else {
} else {
localStorage.removeItem('soon_access'); localStorage.removeItem('soon_access');
localStorage.removeItem('soon_refresh'); localStorage.removeItem('soon_refresh');
} }
if (typeof window.soonApplyMembership === 'function') { if (typeof window.soonApplyMembership === 'function') window.soonApplyMembership(null);
window.soonApplyMembership(null); }
function applyUserUi(data) {
if (typeof window.soonApplyMembership === 'function') window.soonApplyMembership(data);
var admin = document.getElementById('auth_admin');
var avatar = document.getElementById('auth_avatar');
if (data.role === 'admin' && admin) admin.style.display = 'inline-flex';
if (avatar && data.email) {
avatar.style.display = 'inline-flex';
avatar.textContent = data.email.charAt(0).toUpperCase();
avatar.title = data.email;
} }
if (typeof window.soonRefreshPortalIdentity === 'function') window.soonRefreshPortalIdentity();
} }
function initClearCache() { function initClearCache() {
var btn = document.getElementById('auth_clear_cache'); var btn = document.getElementById('auth_clear_cache');
if (!btn) return; if (!btn || btn.getAttribute('data-soon-bound') === '1') return;
btn.setAttribute('data-soon-bound', '1');
btn.addEventListener('click', function (e) { btn.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
if (typeof layui === 'undefined' || typeof window.soonClearLocalDesignCache !== 'function') return; if (typeof layui === 'undefined' || typeof window.soonClearLocalDesignCache !== 'function') return;
@@ -49,45 +89,82 @@
else location.reload(); else location.reload();
}, 400); }, 400);
}); });
} },
}); });
}); });
}); });
} }
function initTopbar(options) { function bindLogoutOnce(options) {
options = options || {}; if (_logoutBound) return;
var logout = document.getElementById('auth_logout');
if (!logout) return;
_logoutBound = true;
logout.addEventListener('click', function (e) {
e.preventDefault();
clearSession();
showGuestNav();
if (options.logoutReload !== false) location.reload();
else location.href = 'index.web.html';
});
}
function bindPopupLink(id, fn) {
var el = document.getElementById(id);
if (!el || el.getAttribute('data-soon-popup-bound') === '1') return;
el.setAttribute('data-soon-popup-bound', '1');
el.addEventListener('click', function (e) {
e.preventDefault();
fn();
});
}
function bindPopupAuthLinks() {
if (typeof window.soonShowLoginGate !== 'function') return;
bindPopupLink('auth_login', function () {
window.soonShowLoginGate({ reason: '登录账号' });
});
if (typeof window.soonShowRegisterGate === 'function') {
bindPopupLink('auth_register', function () {
window.soonShowRegisterGate({ reason: '注册账号' });
});
}
}
function refreshAuthUi(options) {
options = options || _opts || {};
initClearCache(); initClearCache();
bindLogoutOnce(options);
bindPopupAuthLinks();
var tok = localStorage.getItem('soon_access') || ''; var tok = localStorage.getItem('soon_access') || '';
var login = document.getElementById('auth_login'); if (!tok) {
var reg = document.getElementById('auth_register'); showGuestNav();
var admin = document.getElementById('auth_admin'); return Promise.resolve();
var logout = document.getElementById('auth_logout'); }
var avatar = document.getElementById('auth_avatar');
if (!tok) return;
showAuthedNav(); showAuthedNav();
if (login) login.style.display = 'none';
if (reg) reg.style.display = 'none';
if (logout) logout.style.display = 'inline-flex';
var base = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || ''; var base = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
if (!base) return; if (!base) {
if (typeof window.soonRefreshPortalIdentity === 'function') window.soonRefreshPortalIdentity();
return Promise.resolve();
}
var meFetch = typeof window.soonAuthedFetch === 'function' var meFetch = typeof window.soonAuthedFetch === 'function'
? window.soonAuthedFetch(base + '/auth/me', { headers: { Authorization: 'Bearer ' + tok } }) ? window.soonAuthedFetch(base + '/auth/me', { headers: { Authorization: 'Bearer ' + tok } })
: fetch(base + '/auth/me', { headers: { Authorization: 'Bearer ' + tok } }); : fetch(base + '/auth/me', { headers: { Authorization: 'Bearer ' + tok } });
meFetch
return meFetch
.then(function (r) { .then(function (r) {
if (r.status === 401) { if (r.status === 401) {
clearSession(); clearSession();
location.reload(); showGuestNav();
return null; return null;
} }
if (!r.ok) { if (!r.ok) {
if (typeof window.soonToast === 'function') window.soonToast('网络异常,部分功能可能不可用', 'warn'); if (typeof window.soonToast === 'function') window.soonToast('网络异常,部分功能可能不可用', 'warn');
if (typeof window.soonRefreshPortalIdentity === 'function') window.soonRefreshPortalIdentity();
return null; return null;
} }
return r.json(); return r.json();
@@ -96,36 +173,23 @@
if (!j) return; if (!j) return;
if (!j.ok || !j.data) { if (!j.ok || !j.data) {
clearSession(); clearSession();
location.reload(); showGuestNav();
return; return;
} }
if (typeof window.soonApplyMembership === 'function') { applyUserUi(j.data);
window.soonApplyMembership(j.data);
}
if (j.data.role === 'admin' && admin) admin.style.display = 'inline-flex';
if (avatar && j.data.email) {
avatar.style.display = 'inline-flex';
avatar.textContent = j.data.email.charAt(0).toUpperCase();
avatar.title = j.data.email;
}
if (typeof window.soonRefreshPortalIdentity === 'function') {
window.soonRefreshPortalIdentity();
}
if (typeof options.onAuthed === 'function') options.onAuthed(j.data); if (typeof options.onAuthed === 'function') options.onAuthed(j.data);
}) })
.catch(function () { .catch(function () {
if (typeof window.soonToast === 'function') window.soonToast('网络异常,部分功能可能不可用', 'warn'); if (typeof window.soonToast === 'function') window.soonToast('网络异常,部分功能可能不可用', 'warn');
if (typeof window.soonRefreshPortalIdentity === 'function') window.soonRefreshPortalIdentity();
}); });
if (logout) {
logout.addEventListener('click', function (e) {
e.preventDefault();
clearSession();
if (options.logoutReload !== false) location.reload();
else location.href = 'index.web.html';
});
}
} }
window.soonPortalAuth = { init: initTopbar }; function initTopbar(options) {
_opts = options || {};
return refreshAuthUi(_opts);
}
window.soonRefreshPortalAuthUi = refreshAuthUi;
window.soonPortalAuth = { init: initTopbar, refresh: refreshAuthUi };
})(); })();