Files
mce/templates/MetaLab-2026/static/js/register.js
Victor_Jay 1933ea86b4 feat: 注册准则阅读控制 + Footer 合规信息
准则阅读(三层控制):
- registration.show_guidelines — 弹窗/复选框模式
- registration.force_guidelines — 是否强制倒计时+滚动
- registration.guidelines_timer — 倒计时秒数

Footer 合规(8 字段,空值不显示,链接纯手写):
- site.operator_name / site.icp_number / site.icp_license
- site.police_number / site.wenwangwen / site.algorithm_filing
- site.privacy_url / site.terms_url
- 新增 safeHTML 模板函数输出不转义 HTML
2026-06-21 23:50:52 +08:00

335 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
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 guidelinesAgreedCheckbox = document.getElementById('guidelinesAgreed');
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;
// 准则配置(从模板 data 属性读取)
var forceGuidelines = modalOverlay ? modalOverlay.getAttribute('data-force-guidelines') === 'true' : true;
var guidelinesTimer = modalOverlay ? parseInt(modalOverlay.getAttribute('data-guidelines-timer')) || 60 : 60;
var showModal = !!modalOverlay;
// 读取 redirect 参数(注册成功后跳转目标)
function getRedirect() {
var m = window.location.search.match(/[?&]redirect=([^&]+)/);
if (m) return decodeURIComponent(m[1]);
return '/';
}
var countdown = guidelinesTimer;
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 = guidelinesTimer;
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';
if (forceGuidelines) {
timerCount.textContent = guidelinesTimer;
timerLabel.style.display = '';
timerCount.style.display = '';
timerUnit.style.display = '';
timerDone.style.display = 'none';
if (timer) 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.disabled = false;
}
} 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-z]/.test(password) && /[A-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-z]/.test(v) || !/[A-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-z]/.test(password) || !/[A-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;
}
// 复选框模式:检查是否勾选准则
if (guidelinesAgreedCheckbox && !guidelinesAgreedCheckbox.checked) {
window.MetaLab.toast(toast, '请先阅读并同意社区准则', true);
return;
}
// Disable submit to prevent double-click
submitBtn.disabled = true;
submitBtn.textContent = '检查中...';
// Step 1: Check if email already registered
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;
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) {
window.MetaLab.toast(toast, '该邮箱已注册,请直接登录', true);
submitBtn.textContent = '注 册';
submitBtn.disabled = false;
} else {
submitBtn.textContent = '注 册';
if (showModal) {
// 弹窗模式:弹出准则阅读
openModal(function () {
doRegister(email, password, confirm);
});
} else {
// 复选框模式:直接注册
doRegister(email, password, confirm);
}
}
} else {
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, resp.message || '🎉 注册成功!', 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
}));
}
})();