feat(auth): clarify login identity and speed up mobile entry

This commit is contained in:
xsl
2026-10-12 11:01:49 +08:00
parent 85790ef985
commit d2931a41e4
28 changed files with 1903 additions and 1046 deletions
+75 -24
View File
@@ -32,8 +32,38 @@
/* ─── 屏幕管理:页面内不放返回按钮,前进时 pushState,微信/浏览器返回键经 popstate 回到上一屏 ─── */
var currentScreen = 's-login';
var fontStylesLoaded = false;
function loadAppFont() {
if (fontStylesLoaded) return;
fontStylesLoaded = true;
var link = document.createElement('link');
link.rel = 'stylesheet'; link.href = 'css/fonts.css';
document.head.appendChild(link);
}
function renderAccountStatus() {
var user = AccountClient.user();
document.querySelectorAll('[data-auth-status]').forEach(function (label) {
label.textContent = user ? (user.kind === 'wechat' ? '微信已登录' : '本机浏览器已登录') : '尚未登录';
label.parentElement.dataset.kind = user ? user.kind : '';
});
}
function loginSucceeded() {
showScreen('s-splash', 'replace');
$('#login-success-message').textContent = AccountClient.user().kind === 'wechat'
? '当前使用:微信登录\n同一微信账号可跨手机查看报告。'
: '当前使用:本机浏览器登录\n报告仅能通过当前浏览器身份查看。';
openOverlay('p-login-success');
document.querySelectorAll('.screen').forEach(function (screen) { screen.inert = true; });
$('[data-action="login-confirm"]').focus();
}
function closeLoginSuccess() {
closeOverlay('p-login-success');
document.querySelectorAll('.screen').forEach(function (screen) { screen.inert = false; });
$('#s-splash [data-action="open-privacy"]').focus({ preventScroll: true });
}
function showScreen(id, nav) { // nav: 'push' 新增历史 | 'replace' 替换当前历史 | 缺省不动历史
if (id !== 's-login' && !AccountClient.user() && !(/[?&]mock=1/.test(location.search))) id = 's-login';
if (['s-profile', 's-cam', 's-report'].includes(id)) loadAppFont();
document.querySelectorAll('.screen').forEach(function (s) {
s.classList.toggle('active', s.id === id);
});
@@ -42,6 +72,7 @@
document.body.scrollTop = 0;
sc.scrollTop = 0;
currentScreen = id;
renderAccountStatus();
if (id === 's-report') resetReport(); // 每次进入报告页都还原初始状态
if (nav === 'push') history.pushState({ screen: id }, '');
else if (nav === 'replace') history.replaceState({ screen: id }, '');
@@ -52,6 +83,7 @@
window.addEventListener('popstate', function (e) {
if (Date.now() < popSilentUntil) { popSilentUntil = 0; return; }
var to = (e.state && e.state.screen) || 's-splash';
if ($('#p-login-success').classList.contains('show')) closeLoginSuccess();
['p-privacy', 'p-photoguide', 'p-policy', 'p-zoom'].forEach(closeOverlay);
if (currentScreen === 's-cam') leaveCamera();
if (to === 's-cam') { to = 's-profile'; history.replaceState({ screen: to }, ''); } // 「前进」回拍照页不自动开相机
@@ -86,6 +118,11 @@
bindBackdropClose('p-policy');
// ESC 关闭(分析中过渡不响应;放大图/协议层在最上,优先关)
document.addEventListener('keydown', function (e) {
if ($('#p-login-success').classList.contains('show')) {
if (e.key === 'Escape' || e.key === 'Esc') closeLoginSuccess();
if (e.key === 'Tab') { e.preventDefault(); $('[data-action="login-confirm"]').focus(); }
return;
}
if (e.key !== 'Escape' && e.key !== 'Esc') return;
var order = ['p-zoom', 'p-policy', 'p-privacy', 'p-photoguide'];
for (var i = 0; i < order.length; i++) {
@@ -323,7 +360,7 @@
resetDetectState(); // 降级无检测、无补光:蒙版回到静态线框,快门出现
toast('已切换为系统相机拍摄');
// 系统相机拍回的照片也要校验(有脸、角度对),模型照常预加载
if (!IS_MOCK && typeof faceapi !== 'undefined') loadModels().catch(function () {});
if (!IS_MOCK) loadModels().catch(function () {});
}
// 从视频帧截取照片(完整帧、摄像头原始视角,压到最长边 1280 的 JPEG)
// 抓完整帧而非取景框可见区:新 UI 轮廓外全黑,用户只关注轮廓内人脸;按可见区裁切在侧脸/偏站位时会切掉人脸(Face++ INVALID_IMAGE_FACE)
@@ -545,7 +582,28 @@
var CENTER_TOL = 0.18; // 人脸中心偏离取景中线超过屏宽 18% → 提示移入取景框
var LUMA_DARK = 65, LUMA_BRIGHT = 235; // 脸部平均亮度(0–255):过暗/过曝提示调整光线
var modelsPromise = null, modelsOk = false, detectDisabled = false; // detectDisabled:face-api 缺失→全程手动;模型加载失败仅当次禁用,下次进拍照页重试
var modelsPromise = null, modelsOk = false, detectDisabled = false;
var faceApiPromise = null;
function loadFaceApi() {
if (window.faceapi) return Promise.resolve();
if (!faceApiPromise) {
faceApiPromise = new Promise(function (resolve, reject) {
var script = document.createElement('script');
var timer = setTimeout(function () { finish(new Error('检测引擎加载超时')); }, 20000);
function finish(error) {
clearTimeout(timer);
script.onload = script.onerror = null;
if (error) { script.remove(); faceApiPromise = null; reject(error); }
else resolve();
}
script.src = 'vendor/faceapi/face-api.js';
script.onload = function () { finish(window.faceapi ? null : new Error('检测引擎不可用')); };
script.onerror = function () { finish(new Error('检测引擎加载失败')); };
document.head.appendChild(script);
});
}
return faceApiPromise;
}
var detRunning = false, detTimer = null; // 检测循环
var okSince = 0; // 当前连续 ok 起点(0=未在累积)
var nudgeTimer = null; // 12s 无进展提示
@@ -555,10 +613,10 @@
// 模型只加载一次(缓存 promise);失败清空缓存,下次进拍照页重试
function loadModels() {
if (!modelsPromise) {
modelsPromise = Promise.all([
modelsPromise = loadFaceApi().then(function () { return Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
]).then(function () { modelsOk = true; },
]); }).then(function () { modelsOk = true; },
function (err) { modelsPromise = null; throw err; });
}
return modelsPromise;
@@ -1206,12 +1264,6 @@
/* ─── 相机可用后加载检测模型(加载完成前快门即手动模式) ─── */
function beginDetect() {
if (IS_MOCK || camFallback) { resetDetectState(); return; }
if (typeof faceapi === 'undefined') {
detectDisabled = true; // face-api 本身缺失:全程手动,不再重试
resetDetectState();
toast('自动识别不可用,已切换手动拍摄');
return;
}
detectDisabled = false; // 上次模型加载失败允许重试(modelsPromise 失败时已清空,loadModels 会重新拉取)
resetDetectState();
if (!modelsOk) showCamStatus('检测引擎加载中…');
@@ -1265,6 +1317,7 @@
'open-policy': function () { openOverlay('p-policy'); },
'login-browser': function () { loginBrowser(); },
'login-wechat': function () { loginWechat(); },
'login-confirm': closeLoginSuccess,
'open-privacy': function () { if (!AccountClient.user()) { showScreen('s-login', 'replace'); return; } openOverlay('p-privacy'); },
'close-privacy': function () { closeOverlay('p-privacy'); },
'close-policy': function () { closeOverlay('p-policy'); },
@@ -1370,25 +1423,20 @@
loginBusy = true;
$('#login-message').textContent = '正在登录…';
AccountClient.browser(true).then(function () {
showScreen('s-splash', 'replace');
loginSucceeded();
}).catch(function (e) { $('#login-message').textContent = e.message; })
.finally(function () { loginBusy = false; });
}
function loginWechat() {
if (!loginAllowed()) return;
if (!/MicroMessenger/i.test(navigator.userAgent)) {
$('#login-message').textContent = '请在微信内打开本页面,或使用本机浏览器登录。';
return;
}
loginBusy = true;
AccountClient.request('api/auth/config').then(function (data) {
if (!data.wechat) {
$('#login-message').textContent = data.reason + '。报告仍会保存至服务端。';
return;
}
if (!/MicroMessenger/i.test(navigator.userAgent)) {
$('#login-message').textContent = '请在微信内打开本页面,或使用本机浏览器登录。';
return;
}
return AccountClient.request('api/auth/wechat', { consent: true }).then(function (data) {
location.assign(data.url);
});
$('#login-message').textContent = '正在前往微信授权…';
AccountClient.request('api/auth/wechat', { consent: true }).then(function (data) {
location.assign(data.url);
}).catch(function (e) { $('#login-message').textContent = e.message; })
.finally(function () { loginBusy = false; });
}
@@ -1446,7 +1494,10 @@
: '微信授权未完成,请重试;如仍失败,请将错误页面截图发给管理员。也可选择本机浏览器登录。';
} else if (loginSuccess && (!AccountClient.user() || AccountClient.user().kind !== 'wechat')) {
$('#login-message').textContent = '微信返回后登录会话未能保存,请在同一微信窗口重新登录。';
} else if (AccountClient.user()) showScreen('s-splash', 'replace');
} else if (AccountClient.user()) {
if (loginSuccess) loginSucceeded();
else showScreen('s-splash', 'replace');
}
else $('#login-message').textContent = '微信登录可跨手机查看报告;本机登录仅限当前浏览器。';
}).catch(function (e) { $('#login-message').textContent = e.message; })
.finally(function () {