- login.js/register.js 登录成功后优先跳转 redirect 参数指定页面 - 新增 common.RedirectToLogin 辅助函数,自动携带当前路径 - 替换所有 9 处硬编码 /auth/login 重定向
313 lines
12 KiB
JavaScript
313 lines
12 KiB
JavaScript
/* ============================================================
|
||
MetaLab Register JS — 注册页面专属脚本
|
||
============================================================ */
|
||
(function () {
|
||
'use strict';
|
||
|
||
// ---- DOM refs ----
|
||
var openGuidelines = document.getElementById('openGuidelines');
|
||
var modalOverlay = document.getElementById('modalOverlay');
|
||
var modalBody = document.getElementById('modalBody');
|
||
var modalClose = document.getElementById('modalClose');
|
||
var modalEnforce = document.getElementById('modalEnforce');
|
||
var confirmBtn = document.getElementById('confirmBtn');
|
||
var previewCloseBtn = document.getElementById('previewCloseBtn');
|
||
var timerCount = document.getElementById('timerCount');
|
||
var timerLabel = document.getElementById('timerLabel');
|
||
var timerUnit = document.getElementById('timerUnit');
|
||
var timerDone = document.getElementById('timerDone');
|
||
var timer = document.getElementById('timer');
|
||
var scrollHint = document.getElementById('scrollHint');
|
||
var submitBtn = document.getElementById('submitBtn');
|
||
var registerForm = document.getElementById('registerForm');
|
||
var toast = document.getElementById('toast');
|
||
var emailInput = document.getElementById('email');
|
||
var passwordInput = document.getElementById('password');
|
||
var confirmPasswordInput = document.getElementById('confirmPassword');
|
||
var rememberMeCheckbox = document.getElementById('rememberMe');
|
||
var emailError = document.getElementById('emailError');
|
||
var passwordError = document.getElementById('passwordError');
|
||
var confirmPasswordError = document.getElementById('confirmPasswordError');
|
||
|
||
// Guard: exit if not on auth page
|
||
if (!registerForm) return;
|
||
|
||
// 读取 redirect 参数(注册成功后跳转目标)
|
||
function getRedirect() {
|
||
var m = window.location.search.match(/[?&]redirect=([^&]+)/);
|
||
if (m) return decodeURIComponent(m[1]);
|
||
return '/';
|
||
}
|
||
|
||
var countdown = 60;
|
||
var timerInterval = null;
|
||
var reachedBottom = false;
|
||
var agreed = false;
|
||
var isRegistrationMode = false;
|
||
var countdownPaused = false;
|
||
|
||
// ---- Modal Open ----
|
||
function openModal(onAgreed) {
|
||
isRegistrationMode = typeof onAgreed === 'function';
|
||
clearInterval(timerInterval);
|
||
countdown = 60;
|
||
reachedBottom = false;
|
||
agreed = false;
|
||
modalBody.scrollTop = 0;
|
||
modalOverlay.classList.add('active');
|
||
modalOverlay._onAgreed = isRegistrationMode ? onAgreed : null;
|
||
|
||
if (isRegistrationMode) {
|
||
modalEnforce.style.display = '';
|
||
confirmBtn.style.display = '';
|
||
previewCloseBtn.style.display = 'none';
|
||
timerCount.textContent = '60';
|
||
timerLabel.style.display = '';
|
||
timerCount.style.display = '';
|
||
timerUnit.style.display = '';
|
||
timerDone.style.display = 'none';
|
||
timer.classList.remove('done');
|
||
countdownPaused = false;
|
||
confirmBtn.disabled = true;
|
||
scrollHint.style.display = 'flex';
|
||
scrollHint.textContent = '▼ 请滚动至底部完成阅读';
|
||
scrollHint.style.color = 'var(--color-danger)';
|
||
startCountdown();
|
||
} else {
|
||
modalEnforce.style.display = 'none';
|
||
confirmBtn.style.display = 'none';
|
||
previewCloseBtn.style.display = '';
|
||
}
|
||
}
|
||
|
||
// ---- Countdown ----
|
||
function startCountdown() {
|
||
timerInterval = setInterval(function () {
|
||
if (countdownPaused) return;
|
||
countdown--;
|
||
timerCount.textContent = countdown;
|
||
if (countdown <= 0) {
|
||
clearInterval(timerInterval);
|
||
timerLabel.style.display = 'none';
|
||
timerCount.style.display = 'none';
|
||
timerUnit.style.display = 'none';
|
||
timerDone.style.display = '';
|
||
timer.classList.add('done');
|
||
scrollHint.textContent = '▼ 请滚动至底部完成阅读';
|
||
updateConfirmState();
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
// ---- Scroll Detection ----
|
||
modalBody.addEventListener('scroll', function () {
|
||
if (reachedBottom) return;
|
||
if (modalBody.scrollTop + modalBody.clientHeight >= modalBody.scrollHeight - 5) {
|
||
reachedBottom = true;
|
||
scrollHint.textContent = '✓ 已阅读至底部';
|
||
scrollHint.style.color = '#27ae60';
|
||
updateConfirmState();
|
||
}
|
||
});
|
||
|
||
// ---- Pause countdown on tab/window inactive ----
|
||
document.addEventListener('visibilitychange', function () {
|
||
if (!isRegistrationMode) return;
|
||
countdownPaused = document.hidden;
|
||
});
|
||
window.addEventListener('blur', function () {
|
||
if (isRegistrationMode) countdownPaused = true;
|
||
});
|
||
window.addEventListener('focus', function () {
|
||
if (isRegistrationMode) countdownPaused = false;
|
||
});
|
||
|
||
// ---- Update Confirm Button ----
|
||
function updateConfirmState() {
|
||
if (countdown <= 0 && reachedBottom) {
|
||
confirmBtn.disabled = false;
|
||
scrollHint.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// ---- Confirm (agree to guidelines) ----
|
||
confirmBtn.addEventListener('click', function () {
|
||
agreed = true;
|
||
modalOverlay.classList.remove('active');
|
||
if (modalOverlay._onAgreed) {
|
||
modalOverlay._onAgreed();
|
||
modalOverlay._onAgreed = null;
|
||
}
|
||
});
|
||
|
||
// ---- Close Modal ----
|
||
modalClose.addEventListener('click', function () {
|
||
modalOverlay.classList.remove('active');
|
||
});
|
||
previewCloseBtn.addEventListener('click', function () {
|
||
modalOverlay.classList.remove('active');
|
||
});
|
||
modalOverlay.addEventListener('click', function (e) {
|
||
if (e.target === modalOverlay) {
|
||
modalOverlay.classList.remove('active');
|
||
}
|
||
});
|
||
|
||
// ---- Open Guidelines Link (preview only) ----
|
||
openGuidelines.addEventListener('click', function (e) {
|
||
e.preventDefault();
|
||
openModal(null);
|
||
});
|
||
|
||
// ---- Form Validation ----
|
||
function checkFormValidity() {
|
||
var email = emailInput.value.trim();
|
||
var password = passwordInput.value;
|
||
var confirm = confirmPasswordInput.value;
|
||
var emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||
var passwordValid = password.length >= 8 && /[a-zA-Z]/.test(password) && /\d/.test(password);
|
||
var confirmValid = passwordValid && confirm === password;
|
||
submitBtn.disabled = !(emailValid && passwordValid && confirmValid);
|
||
}
|
||
|
||
emailInput.addEventListener('input', function () {
|
||
var v = emailInput.value.trim();
|
||
if (v && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) {
|
||
emailInput.classList.add('error');
|
||
emailError.style.display = 'block';
|
||
} else {
|
||
emailInput.classList.remove('error');
|
||
emailError.style.display = 'none';
|
||
}
|
||
checkFormValidity();
|
||
});
|
||
|
||
passwordInput.addEventListener('input', function () {
|
||
var v = passwordInput.value;
|
||
if (v && (v.length < 8 || !/[a-zA-Z]/.test(v) || !/\d/.test(v))) {
|
||
passwordInput.classList.add('error');
|
||
passwordError.style.display = 'block';
|
||
} else {
|
||
passwordInput.classList.remove('error');
|
||
passwordError.style.display = 'none';
|
||
}
|
||
if (confirmPasswordInput.value) {
|
||
validateConfirmPassword();
|
||
}
|
||
checkFormValidity();
|
||
});
|
||
|
||
confirmPasswordInput.addEventListener('input', function () {
|
||
validateConfirmPassword();
|
||
checkFormValidity();
|
||
});
|
||
|
||
function validateConfirmPassword() {
|
||
var confirm = confirmPasswordInput.value;
|
||
var password = passwordInput.value;
|
||
if (confirm && confirm !== password) {
|
||
confirmPasswordInput.classList.add('error');
|
||
confirmPasswordError.style.display = 'block';
|
||
} else {
|
||
confirmPasswordInput.classList.remove('error');
|
||
confirmPasswordError.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// ---- Form Submit: validate → check email → open guidelines → on agree → submit ----
|
||
registerForm.addEventListener('submit', function (e) {
|
||
e.preventDefault();
|
||
|
||
var email = emailInput.value.trim();
|
||
var password = passwordInput.value;
|
||
var confirm = confirmPasswordInput.value;
|
||
|
||
// Client-side validation
|
||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||
emailInput.classList.add('error');
|
||
emailError.style.display = 'block';
|
||
return;
|
||
}
|
||
if (password.length < 8 || !/[a-zA-Z]/.test(password) || !/\d/.test(password)) {
|
||
passwordInput.classList.add('error');
|
||
passwordError.style.display = 'block';
|
||
return;
|
||
}
|
||
if (confirm !== password) {
|
||
confirmPasswordInput.classList.add('error');
|
||
confirmPasswordError.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
// Disable submit to prevent double-click
|
||
submitBtn.disabled = true;
|
||
submitBtn.textContent = '检查中...';
|
||
|
||
// Step 1: Check if email already registered (before showing 60s guidelines)
|
||
var checkXhr = new XMLHttpRequest();
|
||
checkXhr.open('POST', '/api/auth/check-email', true);
|
||
checkXhr.setRequestHeader('Content-Type', 'application/json');
|
||
checkXhr.onreadystatechange = function () {
|
||
if (checkXhr.readyState !== 4) return;
|
||
|
||
submitBtn.disabled = true; // will be re-enabled by checkFormValidity if needed
|
||
|
||
var resp;
|
||
try { resp = JSON.parse(checkXhr.responseText); } catch (err) { resp = null; }
|
||
|
||
if (checkXhr.status === 200 && resp && resp.success) {
|
||
if (resp.data && resp.data.exists) {
|
||
// Already registered → notify user immediately, skip guidelines
|
||
window.MetaLab.toast(toast, '该邮箱已注册,请直接登录', true);
|
||
submitBtn.textContent = '注 册';
|
||
submitBtn.disabled = false;
|
||
} else {
|
||
// Not registered → proceed to guidelines modal
|
||
submitBtn.textContent = '注 册';
|
||
openModal(function () {
|
||
doRegister(email, password, confirm);
|
||
});
|
||
}
|
||
} else {
|
||
// API error → show message, don't block
|
||
var msg = (resp && resp.message) ? resp.message : '网络异常,请稍后重试';
|
||
window.MetaLab.toast(toast, msg, true);
|
||
submitBtn.textContent = '注 册';
|
||
submitBtn.disabled = false;
|
||
}
|
||
};
|
||
checkXhr.send(JSON.stringify({ email: email }));
|
||
});
|
||
|
||
// ---- AJAX Register ----
|
||
function doRegister(email, password, confirmPassword) {
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('POST', '/api/auth/register', true);
|
||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||
xhr.onreadystatechange = function () {
|
||
if (xhr.readyState === 4) {
|
||
var resp;
|
||
try { resp = JSON.parse(xhr.responseText); } catch (err) { resp = null; }
|
||
|
||
if (xhr.status === 200 && resp && resp.success) {
|
||
window.MetaLab.toast(toast, '🎉 注册成功!欢迎加入 MetaLab', false);
|
||
// Token 已通过 HttpOnly Cookie 自动设置,JS 无需处理
|
||
// 注册即登录,跳转首页
|
||
setTimeout(function () {
|
||
window.location.href = getRedirect();
|
||
}, 1500);
|
||
} else {
|
||
var msg = (resp && resp.message) ? resp.message : '注册失败,请稍后重试';
|
||
window.MetaLab.toast(toast, msg, true);
|
||
}
|
||
}
|
||
};
|
||
xhr.send(JSON.stringify({
|
||
email: email,
|
||
password: password,
|
||
confirm_password: confirmPassword,
|
||
remember_me: rememberMeCheckbox.checked
|
||
}));
|
||
}
|
||
})();
|