480 lines
22 KiB
HTML
480 lines
22 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ _('register') }} - 高可用学习平台{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/register.css') }}">
|
|
{% endblock %}
|
|
|
|
{% block full_content %}
|
|
<div class="overlay"></div>
|
|
|
|
<div class="theme-toggle" id="theme-toggle">
|
|
{% if session.get('theme', 'light') == 'light' %}☀️{% else %}🌙{% endif %}
|
|
</div>
|
|
|
|
<div class="language-selector">
|
|
<select id="language-select">
|
|
<option value="zh" {% if session.get('language', 'zh') == 'zh' %}selected{% endif %}>简体中文</option>
|
|
<option value="en" {% if session.get('language', 'zh') == 'en' %}selected{% endif %}>English</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="main-container">
|
|
<div class="login-container">
|
|
<div class="logo">
|
|
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" onerror="this.src='/api/placeholder/90/90'">
|
|
</div>
|
|
<h1>高可用学习平台</h1>
|
|
<p class="subtitle">{{ _('create_account') }}</p>
|
|
|
|
<div class="step-container">
|
|
<div class="step {% if not email_verified %}active{% else %}completed{% endif %}" id="step-1">1
|
|
<div class="step-label">{{ _('email_verification') }}</div>
|
|
</div>
|
|
<div class="step {% if email_verified %}active{% endif %}" id="step-2">2
|
|
<div class="step-label">{{ _('account_info') }}</div>
|
|
</div>
|
|
<div class="step" id="step-3">3
|
|
<div class="step-label">{{ _('registration_success') }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="registration-step {% if not email_verified %}active{% endif %}" id="step-1-content">
|
|
<form id="email-verification-form" method="post" action="{{ url_for('auth.register') }}">
|
|
{{ email_form.hidden_tag() }}
|
|
<input type="hidden" name="send_code" value="1">
|
|
|
|
<div class="form-group">
|
|
<label for="email">{{ _('email') }}</label>
|
|
<div class="verification-container">
|
|
<div class="input-with-icon verification-input">
|
|
<span class="input-icon">📧</span>
|
|
{{ email_form.email(class="form-control", placeholder=_('email_placeholder'), id="verification-email") }}
|
|
</div>
|
|
<button type="submit" class="send-code-btn" id="send-code-btn">
|
|
{{ _('send_verification_code') }}
|
|
</button>
|
|
</div>
|
|
{% for error in email_form.email.errors %}
|
|
<div class="validation-message show">{{ error }}</div>
|
|
{% endfor %}
|
|
<div class="validation-message" id="email-error"></div>
|
|
</div>
|
|
|
|
<div class="form-buttons">
|
|
<a href="{{ url_for('auth.login') }}" class="btn btn-login btn-back">{{ _('back_to_login') }}</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="registration-step {% if email_verified %}active{% endif %}" id="step-2-content">
|
|
<form id="registration-form" method="post" action="{{ url_for('auth.register') }}">
|
|
{{ registration_form.hidden_tag() }}
|
|
<input type="hidden" name="register" value="1">
|
|
|
|
<div class="form-group">
|
|
<label for="email">{{ _('email') }}</label>
|
|
<div class="input-with-icon">
|
|
<span class="input-icon">📧</span>
|
|
{{ registration_form.email(class="form-control", readonly="readonly", value=verified_email) }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="verification_code">{{ _('verification_code') }}</label>
|
|
<div class="input-with-icon">
|
|
<span class="input-icon">🔑</span>
|
|
{{ registration_form.verification_code(class="form-control", placeholder=_('enter_verification_code')) }}
|
|
</div>
|
|
{% for error in registration_form.verification_code.errors %}
|
|
<div class="validation-message show">{{ error }}</div>
|
|
{% endfor %}
|
|
<div class="validation-message" id="code-error"></div>
|
|
<div class="countdown" id="resend-countdown"></div>
|
|
<button type="button" class="btn-link" id="resend-btn" style="display: none; background: none; border: none; color: var(--primary-color); padding: 0; margin-top: 5px; cursor: pointer; text-decoration: underline;">{{ _('resend_code') }}</button>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="username">{{ _('username') }}</label>
|
|
<div class="input-with-icon">
|
|
<span class="input-icon">👤</span>
|
|
{{ registration_form.username(class="form-control", placeholder=_('enter_username')) }}
|
|
</div>
|
|
{% for error in registration_form.username.errors %}
|
|
<div class="validation-message show">{{ error }}</div>
|
|
{% endfor %}
|
|
<div class="validation-message" id="username-error"></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">{{ _('password') }}</label>
|
|
<div class="input-with-icon">
|
|
<span class="input-icon">🔒</span>
|
|
{{ registration_form.password(class="form-control", placeholder=_('enter_password'), id="register-password") }}
|
|
<span class="password-toggle" id="password-toggle">👁️</span>
|
|
</div>
|
|
{% for error in registration_form.password.errors %}
|
|
<div class="validation-message show">{{ error }}</div>
|
|
{% endfor %}
|
|
<div class="validation-message" id="password-error"></div>
|
|
<div class="strength-meter">
|
|
<div class="strength-meter-fill" id="strength-meter-fill"></div>
|
|
</div>
|
|
<div class="password-strength-text" id="password-strength-text"></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password2">{{ _('repeat_password') }}</label>
|
|
<div class="input-with-icon">
|
|
<span class="input-icon">🔒</span>
|
|
{{ registration_form.password2(class="form-control", placeholder=_('repeat_password_placeholder')) }}
|
|
</div>
|
|
{% for error in registration_form.password2.errors %}
|
|
<div class="validation-message show">{{ error }}</div>
|
|
{% endfor %}
|
|
<div class="validation-message" id="password2-error"></div>
|
|
</div>
|
|
|
|
<div class="form-buttons">
|
|
<button type="button" class="btn btn-login btn-back" id="back-to-step1">{{ _('back') }}</button>
|
|
<button type="submit" class="btn btn-login btn-next">{{ _('register') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer>
|
|
<p>© 2023 高可用学习平台 | <a href="#">{{ _('privacy_policy') }}</a> | <a href="#">{{ _('terms_of_service') }}</a></p>
|
|
</footer>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// 主题切换
|
|
const themeToggle = document.getElementById('theme-toggle');
|
|
|
|
themeToggle.addEventListener('click', function() {
|
|
const currentTheme = "{{ session.get('theme', 'light') }}";
|
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
window.location.href = "{{ url_for('main.set_theme', theme='') }}" + newTheme + "?next={{ request.path|urlencode }}";
|
|
});
|
|
|
|
// 语言选择器
|
|
const languageSelect = document.getElementById('language-select');
|
|
languageSelect.addEventListener('change', function() {
|
|
window.location.href = "{{ url_for('main.set_language', lang='') }}" + this.value + "?next={{ request.path|urlencode }}";
|
|
});
|
|
|
|
// 密码可见性切换
|
|
const passwordInput = document.getElementById('register-password');
|
|
const passwordToggle = document.getElementById('password-toggle');
|
|
|
|
if (passwordToggle && passwordInput) {
|
|
passwordToggle.addEventListener('click', function() {
|
|
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
|
passwordInput.setAttribute('type', type);
|
|
passwordToggle.innerHTML = type === 'password' ? '👁️' : '👁️🗨️';
|
|
});
|
|
}
|
|
|
|
// 密码强度检测
|
|
if (passwordInput) {
|
|
const strengthMeter = document.getElementById('strength-meter-fill');
|
|
const strengthText = document.getElementById('password-strength-text');
|
|
|
|
passwordInput.addEventListener('input', function() {
|
|
const password = this.value;
|
|
const strength = checkPasswordStrength(password);
|
|
|
|
// 更新强度指示器
|
|
strengthMeter.className = 'strength-meter-fill';
|
|
|
|
if (password.length === 0) {
|
|
strengthMeter.style.width = '0';
|
|
strengthText.textContent = '';
|
|
} else if (strength < 2) {
|
|
strengthMeter.classList.add('weak');
|
|
strengthText.textContent = '{{ _("password_weak") }}';
|
|
} else if (strength < 3) {
|
|
strengthMeter.classList.add('medium');
|
|
strengthText.textContent = '{{ _("password_medium") }}';
|
|
} else if (strength < 4) {
|
|
strengthMeter.classList.add('strong');
|
|
strengthText.textContent = '{{ _("password_strong") }}';
|
|
} else {
|
|
strengthMeter.classList.add('very-strong');
|
|
strengthText.textContent = '{{ _("password_very_strong") }}';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 检查密码强度的函数
|
|
function checkPasswordStrength(password) {
|
|
let strength = 0;
|
|
|
|
if (password.length >= 8) strength++;
|
|
if (/[a-z]/.test(password)) strength++;
|
|
if (/[A-Z]/.test(password)) strength++;
|
|
if (/[0-9]/.test(password)) strength++;
|
|
if (/[^a-zA-Z0-9]/.test(password)) strength++;
|
|
|
|
return strength;
|
|
}
|
|
|
|
// 注册表单验证
|
|
const registrationForm = document.getElementById('registration-form');
|
|
const verificationCodeInput = document.getElementById('verification_code');
|
|
const usernameInput = document.getElementById('username');
|
|
const password2Input = document.getElementById('password2');
|
|
|
|
const codeError = document.getElementById('code-error');
|
|
const usernameError = document.getElementById('username-error');
|
|
const password2Error = document.getElementById('password2-error');
|
|
|
|
if (registrationForm) {
|
|
registrationForm.addEventListener('submit', function(e) {
|
|
let isValid = true;
|
|
|
|
// 验证码验证
|
|
if (verificationCodeInput.value.trim() === '') {
|
|
codeError.textContent = '{{ _("verification_code_required") }}';
|
|
codeError.classList.add('show');
|
|
isValid = false;
|
|
} else if (verificationCodeInput.value.length !== 6) {
|
|
codeError.textContent = '{{ _("verification_code_invalid") }}';
|
|
codeError.classList.add('show');
|
|
isValid = false;
|
|
} else {
|
|
codeError.classList.remove('show');
|
|
}
|
|
|
|
// 用户名验证
|
|
if (usernameInput.value.trim() === '') {
|
|
usernameError.textContent = '{{ _("username_required") }}';
|
|
usernameError.classList.add('show');
|
|
isValid = false;
|
|
} else {
|
|
usernameError.classList.remove('show');
|
|
}
|
|
|
|
// 密码匹配验证
|
|
if (password2Input.value !== passwordInput.value) {
|
|
password2Error.textContent = '{{ _("passwords_not_match") }}';
|
|
password2Error.classList.add('show');
|
|
isValid = false;
|
|
} else {
|
|
password2Error.classList.remove('show');
|
|
}
|
|
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 邮箱验证表单
|
|
const emailVerificationForm = document.getElementById('email-verification-form');
|
|
const emailInput = document.getElementById('verification-email');
|
|
const emailError = document.getElementById('email-error');
|
|
const sendCodeBtn = document.getElementById('send-code-btn');
|
|
|
|
if (emailVerificationForm) {
|
|
emailVerificationForm.addEventListener('submit', function(e) {
|
|
if (emailInput.value.trim() === '') {
|
|
e.preventDefault();
|
|
emailError.textContent = '{{ _("email_required") }}';
|
|
emailError.classList.add('show');
|
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailInput.value.trim())) {
|
|
e.preventDefault();
|
|
emailError.textContent = '{{ _("invalid_email") }}';
|
|
emailError.classList.add('show');
|
|
} else {
|
|
emailError.classList.remove('show');
|
|
}
|
|
});
|
|
|
|
// 动态发送验证码
|
|
sendCodeBtn.addEventListener('click', function(e) {
|
|
if (emailInput.value.trim() === '' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailInput.value.trim())) {
|
|
return; // 让表单自己处理验证
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
// 禁用按钮
|
|
sendCodeBtn.disabled = true;
|
|
sendCodeBtn.textContent = '{{ _("sending") }}...';
|
|
|
|
// 发送Ajax请求
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '{{ url_for("auth.send_verification_code") }}', true);
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4) {
|
|
if (xhr.status === 200) {
|
|
try {
|
|
const response = JSON.parse(xhr.responseText);
|
|
if (response.success) {
|
|
// 显示成功消息
|
|
showNotification(response.message, 'success');
|
|
|
|
// 存储邮箱地址
|
|
const verifiedEmail = emailInput.value.trim();
|
|
|
|
// 显示下一步
|
|
document.getElementById('step-1').classList.remove('active');
|
|
document.getElementById('step-1').classList.add('completed');
|
|
document.getElementById('step-2').classList.add('active');
|
|
|
|
document.getElementById('step-1-content').classList.remove('active');
|
|
document.getElementById('step-2-content').classList.add('active');
|
|
|
|
// 设置邮箱字段
|
|
document.getElementById('email').value = verifiedEmail;
|
|
|
|
// 启动倒计时
|
|
startResendCountdown();
|
|
} else {
|
|
// 显示错误消息
|
|
showNotification(response.message, 'error');
|
|
sendCodeBtn.disabled = false;
|
|
sendCodeBtn.textContent = '{{ _("send_verification_code") }}';
|
|
}
|
|
} catch (e) {
|
|
showNotification('{{ _("server_error") }}', 'error');
|
|
sendCodeBtn.disabled = false;
|
|
sendCodeBtn.textContent = '{{ _("send_verification_code") }}';
|
|
}
|
|
} else {
|
|
showNotification('{{ _("server_error") }}', 'error');
|
|
sendCodeBtn.disabled = false;
|
|
sendCodeBtn.textContent = '{{ _("send_verification_code") }}';
|
|
}
|
|
}
|
|
};
|
|
xhr.send('email=' + encodeURIComponent(emailInput.value.trim()));
|
|
});
|
|
}
|
|
|
|
// 返回按钮
|
|
const backToStep1 = document.getElementById('back-to-step1');
|
|
if (backToStep1) {
|
|
backToStep1.addEventListener('click', function() {
|
|
document.getElementById('step-1').classList.add('active');
|
|
document.getElementById('step-1').classList.remove('completed');
|
|
document.getElementById('step-2').classList.remove('active');
|
|
|
|
document.getElementById('step-1-content').classList.add('active');
|
|
document.getElementById('step-2-content').classList.remove('active');
|
|
});
|
|
}
|
|
|
|
// 重发验证码
|
|
const resendBtn = document.getElementById('resend-btn');
|
|
const resendCountdown = document.getElementById('resend-countdown');
|
|
|
|
if (resendBtn) {
|
|
resendBtn.addEventListener('click', function() {
|
|
// 禁用按钮
|
|
resendBtn.style.display = 'none';
|
|
|
|
// 发送Ajax请求
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '{{ url_for("auth.send_verification_code") }}', true);
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
try {
|
|
const response = JSON.parse(xhr.responseText);
|
|
if (response.success) {
|
|
showNotification(response.message, 'success');
|
|
startResendCountdown();
|
|
} else {
|
|
showNotification(response.message, 'error');
|
|
resendBtn.style.display = 'block';
|
|
}
|
|
} catch (e) {
|
|
showNotification('{{ _("server_error") }}', 'error');
|
|
resendBtn.style.display = 'block';
|
|
}
|
|
} else if (xhr.readyState === 4) {
|
|
showNotification('{{ _("server_error") }}', 'error');
|
|
resendBtn.style.display = 'block';
|
|
}
|
|
};
|
|
xhr.send('email=' + encodeURIComponent(document.getElementById('email').value));
|
|
});
|
|
}
|
|
|
|
// 验证码倒计时
|
|
function startResendCountdown() {
|
|
let seconds = 60;
|
|
resendCountdown.textContent = `{{ _("resend_code_in") }} ${seconds} {{ _("seconds") }}`;
|
|
resendCountdown.style.display = 'block';
|
|
resendBtn.style.display = 'none';
|
|
|
|
const interval = setInterval(function() {
|
|
seconds--;
|
|
resendCountdown.textContent = `{{ _("resend_code_in") }} ${seconds} {{ _("seconds") }}`;
|
|
|
|
if (seconds <= 0) {
|
|
clearInterval(interval);
|
|
resendCountdown.style.display = 'none';
|
|
resendBtn.style.display = 'block';
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
// 显示通知
|
|
function showNotification(message, type) {
|
|
// 检查是否已有通知
|
|
let notification = document.querySelector('.notification');
|
|
if (notification) {
|
|
document.body.removeChild(notification);
|
|
}
|
|
|
|
// 创建新通知
|
|
notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
|
|
const icon = type === 'success' ? '✓' : '✗';
|
|
|
|
notification.innerHTML = `
|
|
<div class="notification-icon">${icon}</div>
|
|
<div class="notification-content">
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// 显示通知
|
|
setTimeout(() => {
|
|
notification.classList.add('show');
|
|
}, 10);
|
|
|
|
// 自动隐藏
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => {
|
|
try {
|
|
document.body.removeChild(notification);
|
|
} catch (e) {
|
|
// 忽略可能的错误
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// 如果是第二步,启动倒计时
|
|
{% if email_verified %}
|
|
startResendCountdown();
|
|
{% endif %}
|
|
});
|
|
</script>
|
|
{% endblock %}
|