Files
mce/templates/MetaLab-2026/static/js/register.js
Victor_Jay 0014bdb9ff feat: 品牌CMS配置化 + cookie_secure配置
- config.yaml 新增 server.cookie_secure 配置项
- site_settings 新增 site_name/site_tagline/contact_email 站点信息
- SiteInfo 结构体扩展,支持运行时从 DB 读取站点品牌信息
- cookie.go 使用 cfg.Server.CookieSecure 替代 Mode!=debug 判断
- SetSessionCookie/ClearSessionCookie 支持 siteSettings 运行时覆盖
- 模板替换硬编码 MetaLab → {{.SiteName}}(nav/header/footer/home/login/register/guidelines/admin,12+处)
- 管理后台站点设置页新增站点名称/标语/联系邮箱输入项
- 页脚版权年份改为动态 CurrentYear
- 注册成功消息 CMS 化(后端+前端)
2026-06-21 23:34:14 +08:00

313 lines
12 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 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-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;
}
// 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, 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
}));
}
})();