feat(auth): clarify login identity and speed up mobile entry
This commit is contained in:
@@ -5,11 +5,19 @@ window.AccountClient = (function () {
|
||||
function read(name) { try { return localStorage.getItem(name || key); } catch (_) { return null; } }
|
||||
function rememberKind() { try { if (user) localStorage.setItem(kindKey, user.kind); } catch (_) {} }
|
||||
function request(url, data) {
|
||||
var controller = new AbortController();
|
||||
// Bound only authentication waits. Full report snapshots contain photos and
|
||||
// must retain their existing download budget on a slow mobile connection.
|
||||
var timer = url.indexOf('api/auth/') === 0 ? setTimeout(function () { controller.abort(); }, 12000) : null;
|
||||
return fetch(url, { credentials: 'same-origin', method: data ? 'POST' : 'GET',
|
||||
signal: controller.signal,
|
||||
headers: data ? { 'Content-Type': 'application/json' } : {},
|
||||
body: data ? JSON.stringify(data) : undefined }).then(function (r) {
|
||||
return r.json().then(function (v) { if (!r.ok || !v.ok) throw new Error(v.message || '请求失败'); return v; });
|
||||
});
|
||||
}).catch(function (error) {
|
||||
if (error.name === 'AbortError') throw new Error('网络响应超时,请检查网络后重试');
|
||||
throw error;
|
||||
}).finally(function () { clearTimeout(timer); });
|
||||
}
|
||||
function browser(consent) {
|
||||
return request('api/auth/browser', { consent: consent, token: read() }).then(function (v) {
|
||||
|
||||
+75
-24
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user