11
This commit is contained in:
762
admin/public/register.html
Normal file
762
admin/public/register.html
Normal file
@@ -0,0 +1,762 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>邀请注册</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-container {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 40px 50px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 95vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-header h1 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-header p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-code-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-code-badge.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: inline-block;
|
||||||
|
color: #333;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 15px;
|
||||||
|
min-width: 100px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input.error {
|
||||||
|
border-color: #ff4757;
|
||||||
|
background: #fff5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms-code-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group .sms-code-wrapper .form-input {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-code-btn {
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-code-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #ff4757;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-left: 0;
|
||||||
|
width: 100%;
|
||||||
|
flex-basis: 100%;
|
||||||
|
display: none;
|
||||||
|
padding-left: 115px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn.loading {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn.loading::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-alert {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-alert.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-tips {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-tips h3 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-tips ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-tips li {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-left: 18px;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-tips li::before {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.register-container {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="register-container">
|
||||||
|
<div class="register-header">
|
||||||
|
<h1>🎉 邀请注册</h1>
|
||||||
|
<p>填写信息完成注册,邀请人将获得奖励</p>
|
||||||
|
<div id="inviteCodeBadge" class="invite-code-badge hidden"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="successMessage" class="success-message"></div>
|
||||||
|
<div id="errorAlert" class="error-alert"></div>
|
||||||
|
|
||||||
|
<form id="registerForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="email">邮箱地址</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入您的邮箱地址"
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
/>
|
||||||
|
<div class="error-message" id="emailError"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="emailCode">验证码</label>
|
||||||
|
<div class="sms-code-wrapper">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="emailCode"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入邮箱验证码"
|
||||||
|
required
|
||||||
|
maxlength="6"
|
||||||
|
style="flex: 1; margin-right: 10px;"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="sendCodeBtn"
|
||||||
|
class="send-code-btn"
|
||||||
|
>
|
||||||
|
发送验证码
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="error-message" id="emailCodeError"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="password">密码</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入密码(至少6位)"
|
||||||
|
required
|
||||||
|
autocomplete="new-password"
|
||||||
|
minlength="6"
|
||||||
|
/>
|
||||||
|
<div class="error-message" id="passwordError"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="confirmPassword">确认密码</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
required
|
||||||
|
autocomplete="new-password"
|
||||||
|
minlength="6"
|
||||||
|
/>
|
||||||
|
<div class="error-message" id="confirmPasswordError"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="inviteCode">邀请码(选填)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="inviteCode"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入邀请码(可选)"
|
||||||
|
/>
|
||||||
|
<div class="error-message" id="inviteCodeError"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="submit-btn" id="submitBtn">
|
||||||
|
立即注册
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="register-tips">
|
||||||
|
<h3>注册说明</h3>
|
||||||
|
<ul>
|
||||||
|
<li>邮箱将作为您的登录账号</li>
|
||||||
|
<li>密码长度至少6位,建议使用字母+数字组合</li>
|
||||||
|
<li>邮箱验证码有效期为5分钟</li>
|
||||||
|
<li>邀请码为选填,填写邀请码注册成功后,邀请人将获得3天试用期奖励</li>
|
||||||
|
<li>请妥善保管您的账号信息</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 配置 API 地址(根据环境自动判断)
|
||||||
|
const API_BASE_URL = window.location.hostname === 'localhost'
|
||||||
|
? 'http://localhost:9097/admin_api'
|
||||||
|
: 'https://work.light120.com/admin_api';
|
||||||
|
|
||||||
|
// 获取 URL 参数中的邀请码
|
||||||
|
function getInviteCodeFromUrl() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get('code') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 倒计时状态
|
||||||
|
let countdownTimer = null;
|
||||||
|
let countdownSeconds = 0;
|
||||||
|
|
||||||
|
// 显示/隐藏消息
|
||||||
|
function showMessage(elementId, message, isError = false) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
element.textContent = message;
|
||||||
|
element.className = isError ? 'error-alert show' : 'success-message show';
|
||||||
|
|
||||||
|
// 3秒后自动隐藏
|
||||||
|
setTimeout(() => {
|
||||||
|
element.className = isError ? 'error-alert' : 'success-message';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示邀请码徽章
|
||||||
|
function displayInviteCodeBadge(code) {
|
||||||
|
if (code) {
|
||||||
|
const badge = document.getElementById('inviteCodeBadge');
|
||||||
|
badge.textContent = `邀请码: ${code}`;
|
||||||
|
badge.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动填充邀请码
|
||||||
|
function autoFillInviteCode(code) {
|
||||||
|
if (code) {
|
||||||
|
const inviteCodeInput = document.getElementById('inviteCode');
|
||||||
|
inviteCodeInput.value = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
async function sendEmailCode(email) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/invite/send-email-code`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: email
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送验证码失败:', error);
|
||||||
|
throw new Error('网络请求失败,请检查网络连接');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
function startCountdown(seconds = 60) {
|
||||||
|
const sendBtn = document.getElementById('sendCodeBtn');
|
||||||
|
countdownSeconds = seconds;
|
||||||
|
sendBtn.disabled = true;
|
||||||
|
|
||||||
|
const updateCountdown = () => {
|
||||||
|
if (countdownSeconds > 0) {
|
||||||
|
sendBtn.textContent = `${countdownSeconds}秒后重新发送`;
|
||||||
|
countdownSeconds--;
|
||||||
|
countdownTimer = setTimeout(updateCountdown, 1000);
|
||||||
|
} else {
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
sendBtn.textContent = '发送验证码';
|
||||||
|
if (countdownTimer) {
|
||||||
|
clearTimeout(countdownTimer);
|
||||||
|
countdownTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCountdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证邮箱格式
|
||||||
|
function validateEmail(email) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示字段错误
|
||||||
|
function showFieldError(fieldId, message) {
|
||||||
|
const input = document.getElementById(fieldId);
|
||||||
|
const errorDiv = document.getElementById(fieldId + 'Error');
|
||||||
|
|
||||||
|
input.classList.add('error');
|
||||||
|
errorDiv.textContent = message;
|
||||||
|
errorDiv.classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除字段错误
|
||||||
|
function clearFieldError(fieldId) {
|
||||||
|
const input = document.getElementById(fieldId);
|
||||||
|
const errorDiv = document.getElementById(fieldId + 'Error');
|
||||||
|
|
||||||
|
input.classList.remove('error');
|
||||||
|
errorDiv.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证表单
|
||||||
|
function validateForm() {
|
||||||
|
const inviteCode = document.getElementById('inviteCode').value.trim();
|
||||||
|
const email = document.getElementById('email').value.trim();
|
||||||
|
const emailCode = document.getElementById('emailCode').value.trim();
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const confirmPassword = document.getElementById('confirmPassword').value;
|
||||||
|
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
// 邀请码为选填,无需验证
|
||||||
|
|
||||||
|
// 验证邮箱
|
||||||
|
if (!email) {
|
||||||
|
showFieldError('email', '请输入邮箱地址');
|
||||||
|
isValid = false;
|
||||||
|
} else if (!validateEmail(email)) {
|
||||||
|
showFieldError('email', '邮箱格式不正确');
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
clearFieldError('email');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证邮箱验证码
|
||||||
|
if (!emailCode) {
|
||||||
|
showFieldError('emailCode', '请输入邮箱验证码');
|
||||||
|
isValid = false;
|
||||||
|
} else if (!/^\d{6}$/.test(emailCode)) {
|
||||||
|
showFieldError('emailCode', '验证码为6位数字');
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
clearFieldError('emailCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
if (!password) {
|
||||||
|
showFieldError('password', '请输入密码');
|
||||||
|
isValid = false;
|
||||||
|
} else if (password.length < 6) {
|
||||||
|
showFieldError('password', '密码长度不能少于6位');
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
clearFieldError('password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证确认密码
|
||||||
|
if (!confirmPassword) {
|
||||||
|
showFieldError('confirmPassword', '请再次输入密码');
|
||||||
|
isValid = false;
|
||||||
|
} else if (password !== confirmPassword) {
|
||||||
|
showFieldError('confirmPassword', '两次输入的密码不一致');
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
clearFieldError('confirmPassword');
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册请求
|
||||||
|
async function register(email, emailCode, password, inviteCode) {
|
||||||
|
try {
|
||||||
|
const requestBody = {
|
||||||
|
email: email,
|
||||||
|
email_code: emailCode,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果邀请码不为空,则添加到请求中
|
||||||
|
if (inviteCode) {
|
||||||
|
requestBody.invite_code = inviteCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/invite/register`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('注册请求失败:', error);
|
||||||
|
throw new Error('网络请求失败,请检查网络连接');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表单提交
|
||||||
|
async function handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 清除之前的错误信息
|
||||||
|
document.getElementById('errorAlert').classList.remove('show');
|
||||||
|
document.getElementById('successMessage').classList.remove('show');
|
||||||
|
|
||||||
|
// 验证表单
|
||||||
|
if (!validateForm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inviteCode = document.getElementById('inviteCode').value.trim();
|
||||||
|
const email = document.getElementById('email').value.trim();
|
||||||
|
const emailCode = document.getElementById('emailCode').value.trim();
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
|
||||||
|
// 邀请码为选填,可以为空
|
||||||
|
|
||||||
|
// 禁用提交按钮
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.classList.add('loading');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await register(email, emailCode, password, inviteCode);
|
||||||
|
|
||||||
|
if (result.code === 0 || result.success) {
|
||||||
|
showMessage('successMessage', '注册成功!请使用邮箱和密码登录系统。');
|
||||||
|
|
||||||
|
// 清空表单
|
||||||
|
document.getElementById('registerForm').reset();
|
||||||
|
|
||||||
|
// 3秒后可以提示跳转(如果需要)
|
||||||
|
setTimeout(() => {
|
||||||
|
// 可以在这里添加跳转到登录页的逻辑
|
||||||
|
// window.location.href = '/login.html';
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
showMessage('errorAlert', result.message || '注册失败,请重试', true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('errorAlert', error.message || '注册失败,请稍后重试', true);
|
||||||
|
} finally {
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const inviteCodeFromUrl = getInviteCodeFromUrl();
|
||||||
|
|
||||||
|
// 自动填充邀请码
|
||||||
|
autoFillInviteCode(inviteCodeFromUrl);
|
||||||
|
|
||||||
|
// 显示邀请码徽章
|
||||||
|
if (inviteCodeFromUrl) {
|
||||||
|
displayInviteCodeBadge(inviteCodeFromUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定表单提交事件
|
||||||
|
document.getElementById('registerForm').addEventListener('submit', handleSubmit);
|
||||||
|
|
||||||
|
// 发送验证码按钮事件
|
||||||
|
document.getElementById('sendCodeBtn').addEventListener('click', async function() {
|
||||||
|
const email = document.getElementById('email').value.trim();
|
||||||
|
|
||||||
|
// 验证邮箱
|
||||||
|
if (!email) {
|
||||||
|
showFieldError('email', '请先输入邮箱地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateEmail(email)) {
|
||||||
|
showFieldError('email', '邮箱格式不正确');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFieldError('email');
|
||||||
|
|
||||||
|
// 禁用按钮
|
||||||
|
const sendBtn = document.getElementById('sendCodeBtn');
|
||||||
|
sendBtn.disabled = true;
|
||||||
|
sendBtn.textContent = '发送中...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await sendEmailCode(email);
|
||||||
|
|
||||||
|
if (result.code === 0 || result.success) {
|
||||||
|
showMessage('successMessage', '验证码已发送到您的邮箱,请查收');
|
||||||
|
startCountdown(60);
|
||||||
|
} else {
|
||||||
|
showMessage('errorAlert', result.message || '发送验证码失败,请重试', true);
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
sendBtn.textContent = '发送验证码';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('errorAlert', error.message || '发送验证码失败,请稍后重试', true);
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
sendBtn.textContent = '发送验证码';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 实时验证
|
||||||
|
document.getElementById('email').addEventListener('blur', function() {
|
||||||
|
const email = this.value.trim();
|
||||||
|
if (email && !validateEmail(email)) {
|
||||||
|
showFieldError('email', '邮箱格式不正确');
|
||||||
|
} else {
|
||||||
|
clearFieldError('email');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('password').addEventListener('blur', function() {
|
||||||
|
const password = this.value;
|
||||||
|
if (password && password.length < 6) {
|
||||||
|
showFieldError('password', '密码长度不能少于6位');
|
||||||
|
} else {
|
||||||
|
clearFieldError('password');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirmPassword').addEventListener('blur', function() {
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const confirmPassword = this.value;
|
||||||
|
if (confirmPassword && password !== confirmPassword) {
|
||||||
|
showFieldError('confirmPassword', '两次输入的密码不一致');
|
||||||
|
} else {
|
||||||
|
clearFieldError('confirmPassword');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输入时清除错误
|
||||||
|
document.getElementById('email').addEventListener('input', function() {
|
||||||
|
if (!this.classList.contains('error')) return;
|
||||||
|
const email = this.value.trim();
|
||||||
|
if (email && validateEmail(email)) {
|
||||||
|
clearFieldError('email');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('password').addEventListener('input', function() {
|
||||||
|
if (!this.classList.contains('error')) return;
|
||||||
|
const password = this.value;
|
||||||
|
if (password && password.length >= 6) {
|
||||||
|
clearFieldError('password');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果确认密码已输入,重新验证
|
||||||
|
const confirmPassword = document.getElementById('confirmPassword').value;
|
||||||
|
if (confirmPassword) {
|
||||||
|
if (password === confirmPassword) {
|
||||||
|
clearFieldError('confirmPassword');
|
||||||
|
} else {
|
||||||
|
showFieldError('confirmPassword', '两次输入的密码不一致');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirmPassword').addEventListener('input', function() {
|
||||||
|
if (!this.classList.contains('error')) return;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const confirmPassword = this.value;
|
||||||
|
if (confirmPassword && password === confirmPassword) {
|
||||||
|
clearFieldError('confirmPassword');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('emailCode').addEventListener('input', function() {
|
||||||
|
if (!this.classList.contains('error')) return;
|
||||||
|
const emailCode = this.value.trim();
|
||||||
|
if (emailCode && /^\d{6}$/.test(emailCode)) {
|
||||||
|
clearFieldError('emailCode');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -25,8 +25,6 @@ import JobTypes from '@/views/work/job_types.vue'
|
|||||||
// 首页模块
|
// 首页模块
|
||||||
import HomeIndex from '@/views/home/index.vue'
|
import HomeIndex from '@/views/home/index.vue'
|
||||||
|
|
||||||
// 邀请注册模块
|
|
||||||
import InviteRegister from '@/views/invite/invite_register.vue'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -56,7 +54,8 @@ const componentMap = {
|
|||||||
'system/version': Version,
|
'system/version': Version,
|
||||||
'work/job_types': JobTypes,
|
'work/job_types': JobTypes,
|
||||||
'home/index': HomeIndex,
|
'home/index': HomeIndex,
|
||||||
'invite/invite_register': InviteRegister,
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default componentMap
|
export default componentMap
|
||||||
|
|||||||
@@ -1,359 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="invite-register-page">
|
|
||||||
<div class="register-container">
|
|
||||||
<Card class="register-card">
|
|
||||||
<div slot="title" class="card-title">
|
|
||||||
<Icon type="ios-person-add" size="24" />
|
|
||||||
<span>邀请注册</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="register-form">
|
|
||||||
<!-- 邀请码提示 -->
|
|
||||||
<Alert v-if="inviteCode" type="success" show-icon style="margin-bottom: 20px;">
|
|
||||||
<span slot="desc">您正在通过邀请码 <strong>{{ inviteCode }}</strong> 进行注册</span>
|
|
||||||
</Alert>
|
|
||||||
<Alert v-else type="warning" show-icon style="margin-bottom: 20px;">
|
|
||||||
<span slot="desc">未检测到邀请码,请通过邀请链接访问</span>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Form ref="registerForm" :model="formData" :rules="formRules" :label-width="100">
|
|
||||||
<!-- 手机号 -->
|
|
||||||
<FormItem label="手机号" prop="phone">
|
|
||||||
<Input
|
|
||||||
v-model="formData.phone"
|
|
||||||
placeholder="请输入手机号"
|
|
||||||
:maxlength="11"
|
|
||||||
@on-blur="validatePhone"
|
|
||||||
>
|
|
||||||
<Icon type="ios-phone-portrait" slot="prefix" />
|
|
||||||
</Input>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<!-- 密码 -->
|
|
||||||
<FormItem label="密码" prop="password">
|
|
||||||
<Input
|
|
||||||
v-model="formData.password"
|
|
||||||
type="password"
|
|
||||||
placeholder="请输入密码(至少6位)"
|
|
||||||
:maxlength="50"
|
|
||||||
password
|
|
||||||
>
|
|
||||||
<Icon type="ios-lock" slot="prefix" />
|
|
||||||
</Input>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<!-- 确认密码 -->
|
|
||||||
<FormItem label="确认密码" prop="confirmPassword">
|
|
||||||
<Input
|
|
||||||
v-model="formData.confirmPassword"
|
|
||||||
type="password"
|
|
||||||
placeholder="请再次输入密码"
|
|
||||||
:maxlength="50"
|
|
||||||
password
|
|
||||||
>
|
|
||||||
<Icon type="ios-lock" slot="prefix" />
|
|
||||||
</Input>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<!-- 短信验证码 -->
|
|
||||||
<FormItem label="短信验证码" prop="sms_code">
|
|
||||||
<div class="sms-code-wrapper">
|
|
||||||
<Input
|
|
||||||
v-model="formData.sms_code"
|
|
||||||
placeholder="请输入短信验证码"
|
|
||||||
:maxlength="6"
|
|
||||||
style="width: 200px;"
|
|
||||||
>
|
|
||||||
<Icon type="ios-keypad" slot="prefix" />
|
|
||||||
</Input>
|
|
||||||
<Button
|
|
||||||
:disabled="smsCodeDisabled"
|
|
||||||
:loading="smsCodeLoading"
|
|
||||||
@click="handleSendSmsCode"
|
|
||||||
style="margin-left: 10px;"
|
|
||||||
>
|
|
||||||
{{ smsCodeButtonText }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
|
||||||
<FormItem>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
:loading="registerLoading"
|
|
||||||
@click="handleRegister"
|
|
||||||
style="width: 100%;"
|
|
||||||
>
|
|
||||||
注册
|
|
||||||
</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
<!-- 注册说明 -->
|
|
||||||
<div class="register-tips">
|
|
||||||
<p><strong>注册说明:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>手机号将作为您的登录账号</li>
|
|
||||||
<li>密码长度至少6位,建议使用字母+数字组合</li>
|
|
||||||
<li>短信验证码有效期为5分钟</li>
|
|
||||||
<li>注册成功后,邀请人将获得3天试用期奖励</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import inviteRegisterServer from '@/api/invite/invite_register_server.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'InviteRegister',
|
|
||||||
data() {
|
|
||||||
// 验证确认密码
|
|
||||||
const validateConfirmPassword = (rule, value, callback) => {
|
|
||||||
if (!value) {
|
|
||||||
callback(new Error('请再次输入密码'));
|
|
||||||
} else if (value !== this.formData.password) {
|
|
||||||
callback(new Error('两次输入的密码不一致'));
|
|
||||||
} else {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
inviteCode: '', // 邀请码(从URL参数获取)
|
|
||||||
formData: {
|
|
||||||
phone: '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
sms_code: ''
|
|
||||||
},
|
|
||||||
formRules: {
|
|
||||||
phone: [
|
|
||||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
password: [
|
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
|
||||||
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
confirmPassword: [
|
|
||||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
|
||||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
|
||||||
],
|
|
||||||
sms_code: [
|
|
||||||
{ required: true, message: '请输入短信验证码', trigger: 'blur' },
|
|
||||||
{ pattern: /^\d{6}$/, message: '验证码为6位数字', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
smsCodeLoading: false,
|
|
||||||
smsCodeDisabled: false,
|
|
||||||
smsCodeCountdown: 0,
|
|
||||||
registerLoading: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
smsCodeButtonText() {
|
|
||||||
if (this.smsCodeCountdown > 0) {
|
|
||||||
return `${this.smsCodeCountdown}秒后重新获取`;
|
|
||||||
}
|
|
||||||
return '获取验证码';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// 从URL参数获取邀请码
|
|
||||||
this.getInviteCodeFromUrl();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 从URL参数获取邀请码
|
|
||||||
*/
|
|
||||||
getInviteCodeFromUrl() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const code = urlParams.get('code');
|
|
||||||
if (code) {
|
|
||||||
this.inviteCode = code;
|
|
||||||
} else {
|
|
||||||
this.$Message.warning('未检测到邀请码,请通过邀请链接访问');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证手机号
|
|
||||||
*/
|
|
||||||
validatePhone() {
|
|
||||||
this.$refs.registerForm.validateField('phone');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送短信验证码
|
|
||||||
*/
|
|
||||||
async handleSendSmsCode() {
|
|
||||||
// 先验证手机号
|
|
||||||
this.$refs.registerForm.validateField('phone', async (valid) => {
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.formData.phone) {
|
|
||||||
this.$Message.error('请先输入手机号');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证手机号格式
|
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
|
||||||
if (!phoneRegex.test(this.formData.phone)) {
|
|
||||||
this.$Message.error('手机号格式不正确');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.smsCodeLoading = true;
|
|
||||||
try {
|
|
||||||
const result = await inviteRegisterServer.sendSmsCode(this.formData.phone);
|
|
||||||
if (result.code === 0) {
|
|
||||||
this.$Message.success('短信验证码已发送,请注意查收');
|
|
||||||
// 开始倒计时
|
|
||||||
this.startCountdown();
|
|
||||||
} else {
|
|
||||||
this.$Message.error(result.message || '发送短信验证码失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error.response?.data?.message || error.message || '发送短信验证码失败';
|
|
||||||
this.$Message.error(errorMessage);
|
|
||||||
} finally {
|
|
||||||
this.smsCodeLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始倒计时
|
|
||||||
*/
|
|
||||||
startCountdown() {
|
|
||||||
this.smsCodeCountdown = 60;
|
|
||||||
this.smsCodeDisabled = true;
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
this.smsCodeCountdown--;
|
|
||||||
if (this.smsCodeCountdown <= 0) {
|
|
||||||
clearInterval(timer);
|
|
||||||
this.smsCodeDisabled = false;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册
|
|
||||||
*/
|
|
||||||
async handleRegister() {
|
|
||||||
// 验证表单
|
|
||||||
this.$refs.registerForm.validate(async (valid) => {
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.inviteCode) {
|
|
||||||
this.$Message.error('邀请码不能为空,请通过邀请链接访问');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registerLoading = true;
|
|
||||||
try {
|
|
||||||
const result = await inviteRegisterServer.register({
|
|
||||||
phone: this.formData.phone,
|
|
||||||
password: this.formData.password,
|
|
||||||
sms_code: this.formData.sms_code,
|
|
||||||
invite_code: this.inviteCode
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.code === 0) {
|
|
||||||
this.$Message.success('注册成功!');
|
|
||||||
// 延迟跳转到登录页面或提示用户登录
|
|
||||||
setTimeout(() => {
|
|
||||||
this.$Message.info('请使用注册的手机号和密码登录');
|
|
||||||
// 可以跳转到登录页面
|
|
||||||
// this.$router.push('/login');
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
this.$Message.error(result.message || '注册失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error.response?.data?.message || error.message || '注册失败';
|
|
||||||
this.$Message.error(errorMessage);
|
|
||||||
} finally {
|
|
||||||
this.registerLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.invite-register-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-card {
|
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #2d8cf0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-form {
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sms-code-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-tips {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f8f8f9;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-left: 4px solid #2d8cf0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-tips p {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #515a6e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-tips ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
color: #808695;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-tips ul li {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -7,7 +7,9 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|||||||
const is_production = process.env.NODE_ENV === 'production' || process.argv.includes('--mode=production')
|
const is_production = process.env.NODE_ENV === 'production' || process.argv.includes('--mode=production')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './src/main.js',
|
entry: {
|
||||||
|
main: './src/main.js'
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'js/[name].[contenthash:8].js',
|
filename: 'js/[name].[contenthash:8].js',
|
||||||
@@ -50,9 +52,30 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
|
// 主应用 HTML
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './public/index.html',
|
template: './public/index.html',
|
||||||
title: 'Admin Framework Demo'
|
title: 'Admin Framework Demo',
|
||||||
|
filename: 'index.html',
|
||||||
|
chunks: ['main']
|
||||||
|
}),
|
||||||
|
// 注册页面 HTML(独立页面,不依赖 webpack)
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './public/register.html',
|
||||||
|
title: '邀请注册',
|
||||||
|
filename: 'register.html',
|
||||||
|
inject: false, // 不注入 webpack 生成的脚本
|
||||||
|
minify: is_production ? {
|
||||||
|
removeComments: true,
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
removeEmptyAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
keepClosingSlash: true,
|
||||||
|
minifyJS: true,
|
||||||
|
minifyCSS: true
|
||||||
|
} : false
|
||||||
}),
|
}),
|
||||||
...(is_production ? [
|
...(is_production ? [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
const Framework = require("../../framework/node-core-framework.js");
|
const Framework = require("../../framework/node-core-framework.js");
|
||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
|
const email_service = require('../services/email_service.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
@@ -21,26 +22,25 @@ module.exports = {
|
|||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* required:
|
* required:
|
||||||
* - phone
|
* - email
|
||||||
* - password
|
* - password
|
||||||
* - sms_code
|
* - email_code
|
||||||
* - invite_code
|
|
||||||
* properties:
|
* properties:
|
||||||
* phone:
|
* email:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 手机号
|
* description: 邮箱地址
|
||||||
* example: '13800138000'
|
* example: 'user@example.com'
|
||||||
* password:
|
* password:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 密码
|
* description: 密码
|
||||||
* example: 'password123'
|
* example: 'password123'
|
||||||
* sms_code:
|
* email_code:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 短信验证码
|
* description: 验证码
|
||||||
* example: '123456'
|
* example: '123456'
|
||||||
* invite_code:
|
* invite_code:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 邀请码
|
* description: 邀请码(选填)
|
||||||
* example: 'INV123_ABC123'
|
* example: 'INV123_ABC123'
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
@@ -49,17 +49,17 @@ module.exports = {
|
|||||||
'POST /invite/register': async (ctx) => {
|
'POST /invite/register': async (ctx) => {
|
||||||
try {
|
try {
|
||||||
const body = ctx.getBody();
|
const body = ctx.getBody();
|
||||||
const { phone, password, sms_code, invite_code } = body;
|
const { email, password, email_code, invite_code } = body;
|
||||||
|
|
||||||
// 验证参数
|
// 验证必填参数
|
||||||
if (!phone || !password || !sms_code || !invite_code) {
|
if (!email || !password || !email_code) {
|
||||||
return ctx.fail('手机号、密码、短信验证码和邀请码不能为空');
|
return ctx.fail('邮箱、密码和验证码不能为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证手机号格式
|
// 验证邮箱格式
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!phoneRegex.test(phone)) {
|
if (!emailRegex.test(email)) {
|
||||||
return ctx.fail('手机号格式不正确');
|
return ctx.fail('邮箱格式不正确');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证密码长度
|
// 验证密码长度
|
||||||
@@ -67,23 +67,28 @@ module.exports = {
|
|||||||
return ctx.fail('密码长度不能少于6位');
|
return ctx.fail('密码长度不能少于6位');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证短信验证码(这里需要调用短信验证服务)
|
// 验证验证码
|
||||||
const smsVerifyResult = await verifySmsCode(phone, sms_code);
|
const emailVerifyResult = await verifyEmailCode(email, email_code);
|
||||||
if (!smsVerifyResult.success) {
|
if (!emailVerifyResult.success) {
|
||||||
return ctx.fail(smsVerifyResult.message || '短信验证码错误或已过期');
|
return ctx.fail(emailVerifyResult.message || '验证码错误或已过期');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { pla_account } = await Framework.getModels();
|
const { pla_account } = await Framework.getModels();
|
||||||
|
|
||||||
// 检查手机号是否已注册
|
// 检查邮箱是否已注册
|
||||||
const existingUser = await pla_account.findOne({
|
const existingUser = await pla_account.findOne({
|
||||||
where: { login_name: phone }
|
where: { login_name: email }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return ctx.fail('该手机号已被注册');
|
return ctx.fail('该邮箱已被注册');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证邀请码(如果提供了邀请码)
|
||||||
|
let inviter = null;
|
||||||
|
let inviter_id = null;
|
||||||
|
|
||||||
|
if (invite_code) {
|
||||||
// 解析邀请码,获取邀请人ID
|
// 解析邀请码,获取邀请人ID
|
||||||
// 邀请码格式:INV{user_id}_{timestamp}
|
// 邀请码格式:INV{user_id}_{timestamp}
|
||||||
const inviteMatch = invite_code.match(/^INV(\d+)_/);
|
const inviteMatch = invite_code.match(/^INV(\d+)_/);
|
||||||
@@ -91,27 +96,28 @@ module.exports = {
|
|||||||
return ctx.fail('邀请码格式不正确');
|
return ctx.fail('邀请码格式不正确');
|
||||||
}
|
}
|
||||||
|
|
||||||
const inviter_id = parseInt(inviteMatch[1]);
|
inviter_id = parseInt(inviteMatch[1]);
|
||||||
|
|
||||||
// 验证邀请人是否存在
|
// 验证邀请人是否存在
|
||||||
const inviter = await pla_account.findOne({
|
inviter = await pla_account.findOne({
|
||||||
where: { id: inviter_id }
|
where: { id: inviter_id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!inviter) {
|
if (!inviter) {
|
||||||
return ctx.fail('邀请码无效,邀请人不存在');
|
return ctx.fail('邀请码无效,邀请人不存在');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 生成设备SN码(基于手机号和时间戳)
|
// 生成设备SN码(基于邮箱和时间戳)
|
||||||
const sn_code = `SN${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
|
const sn_code = `SN${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
|
||||||
|
|
||||||
// 创建新用户
|
// 创建新用户
|
||||||
const newUser = await pla_account.create({
|
const newUser = await pla_account.create({
|
||||||
name: phone, // 默认使用手机号作为名称
|
name: email.split('@')[0], // 默认使用邮箱用户名作为名称
|
||||||
sn_code: sn_code,
|
sn_code: sn_code,
|
||||||
device_id: '', // 设备ID由客户端登录时提供
|
device_id: '', // 设备ID由客户端登录时提供
|
||||||
platform_type: 'boss', // 默认平台类型
|
platform_type: 'boss', // 默认平台类型
|
||||||
login_name: phone,
|
login_name: email,
|
||||||
pwd: password,
|
pwd: password,
|
||||||
keyword: '',
|
keyword: '',
|
||||||
is_enabled: 1,
|
is_enabled: 1,
|
||||||
@@ -158,7 +164,7 @@ module.exports = {
|
|||||||
inviter_sn_code: inviter.sn_code,
|
inviter_sn_code: inviter.sn_code,
|
||||||
invitee_id: newUser.id,
|
invitee_id: newUser.id,
|
||||||
invitee_sn_code: newUser.sn_code,
|
invitee_sn_code: newUser.sn_code,
|
||||||
invitee_phone: phone,
|
invitee_phone: email, // 使用邮箱代替手机号
|
||||||
invite_code: invite_code,
|
invite_code: invite_code,
|
||||||
register_time: new Date(),
|
register_time: new Date(),
|
||||||
reward_status: 1, // 已发放
|
reward_status: 1, // 已发放
|
||||||
@@ -167,7 +173,9 @@ module.exports = {
|
|||||||
is_delete: 0
|
is_delete: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[邀请注册] 用户 ${phone} 通过邀请码 ${invite_code} 注册成功,邀请人 ${inviter.sn_code} 获得3天试用期`);
|
console.log(`[邀请注册] 用户 ${email} 通过邀请码 ${invite_code} 注册成功,邀请人 ${inviter.sn_code} 获得3天试用期`);
|
||||||
|
} else {
|
||||||
|
console.log(`[邀请注册] 用户 ${email} 注册成功(无邀请码)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.success({
|
return ctx.success({
|
||||||
@@ -186,10 +194,10 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /admin_api/invite/send-sms:
|
* /admin_api/invite/send-email-code:
|
||||||
* post:
|
* post:
|
||||||
* summary: 发送短信验证码
|
* summary: 发送验证码
|
||||||
* description: 发送短信验证码到指定手机号
|
* description: 发送验证码到指定邮箱地址
|
||||||
* tags: [后台-邀请注册]
|
* tags: [后台-邀请注册]
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
@@ -198,107 +206,114 @@ module.exports = {
|
|||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* required:
|
* required:
|
||||||
* - phone
|
* - email
|
||||||
* properties:
|
* properties:
|
||||||
* phone:
|
* email:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 手机号
|
* description: 邮箱地址
|
||||||
* example: '13800138000'
|
* example: 'user@example.com'
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: 发送成功
|
* description: 发送成功
|
||||||
*/
|
*/
|
||||||
'POST /invite/send-sms': async (ctx) => {
|
'POST /invite/send-email-code': async (ctx) => {
|
||||||
try {
|
try {
|
||||||
const body = ctx.getBody();
|
const body = ctx.getBody();
|
||||||
const { phone } = body;
|
const { email } = body;
|
||||||
|
|
||||||
if (!phone) {
|
if (!email) {
|
||||||
return ctx.fail('手机号不能为空');
|
return ctx.fail('邮箱地址不能为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证手机号格式
|
// 验证邮箱格式
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!phoneRegex.test(phone)) {
|
if (!emailRegex.test(email)) {
|
||||||
return ctx.fail('手机号格式不正确');
|
return ctx.fail('邮箱格式不正确');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送短信验证码
|
// 发送验证码
|
||||||
const smsResult = await sendSmsCode(phone);
|
const emailResult = await sendEmailCode(email);
|
||||||
if (!smsResult.success) {
|
if (!emailResult.success) {
|
||||||
return ctx.fail(smsResult.message || '发送短信验证码失败');
|
return ctx.fail(emailResult.message || '发送验证码失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.success({
|
return ctx.success({
|
||||||
message: '短信验证码已发送',
|
message: '验证码已发送',
|
||||||
expire_time: smsResult.expire_time || 300 // 默认5分钟过期
|
expire_time: emailResult.expire_time || 300 // 默认5分钟过期
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('发送短信验证码失败:', error);
|
console.error('发送验证码失败:', error);
|
||||||
return ctx.fail('发送短信验证码失败: ' + error.message);
|
return ctx.fail('发送验证码失败: ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送短信验证码
|
* 发送验证码
|
||||||
* @param {string} phone 手机号
|
* @param {string} email 邮箱地址
|
||||||
* @returns {Promise<{success: boolean, message?: string, expire_time?: number}>}
|
* @returns {Promise<{success: boolean, message?: string, expire_time?: number}>}
|
||||||
*/
|
*/
|
||||||
async function sendSmsCode(phone) {
|
async function sendEmailCode(email) {
|
||||||
try {
|
try {
|
||||||
// TODO: 实现真实的短信发送逻辑
|
|
||||||
// 这里可以使用第三方短信服务(如阿里云、腾讯云等)
|
|
||||||
|
|
||||||
// 生成6位随机验证码
|
// 生成6位随机验证码
|
||||||
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
|
|
||||||
// 将验证码存储到缓存中(可以使用Redis或内存缓存)
|
// 将验证码存储到缓存中(可以使用Redis或内存缓存)
|
||||||
// 格式:sms_code:{phone} = {code, expire_time}
|
// 格式:email_code:{email} = {code, expire_time}
|
||||||
const expire_time = Date.now() + 5 * 60 * 1000; // 5分钟后过期
|
const expire_time = Date.now() + 5 * 60 * 1000; // 5分钟后过期
|
||||||
|
|
||||||
// 这里应该存储到缓存中,暂时使用全局变量(生产环境应使用Redis)
|
// 这里应该存储到缓存中,暂时使用全局变量(生产环境应使用Redis)
|
||||||
if (!global.smsCodeCache) {
|
if (!global.emailCodeCache) {
|
||||||
global.smsCodeCache = {};
|
global.emailCodeCache = {};
|
||||||
}
|
}
|
||||||
global.smsCodeCache[phone] = {
|
global.emailCodeCache[email] = {
|
||||||
code: code,
|
code: code,
|
||||||
expire_time: expire_time
|
expire_time: expire_time
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: 调用真实的短信发送接口
|
// 调用邮件服务发送验证码
|
||||||
console.log(`[短信验证] 发送验证码到 ${phone}: ${code} (5分钟内有效)`);
|
const email_result = await email_service.send_verification_code(email, code);
|
||||||
|
|
||||||
|
if (!email_result.success) {
|
||||||
|
// 如果邮件发送失败,删除已生成的验证码
|
||||||
|
delete global.emailCodeCache[email];
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: email_result.message || '发送验证码失败'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[邮箱验证] 验证码已发送到 ${email}: ${code} (5分钟内有效)`);
|
||||||
|
|
||||||
// 模拟发送成功
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
expire_time: 300
|
expire_time: 300
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('发送短信验证码失败:', error);
|
console.error('发送验证码失败:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: error.message || '发送短信验证码失败'
|
message: error.message || '发送验证码失败'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证短信验证码
|
* 验证验证码
|
||||||
* @param {string} phone 手机号
|
* @param {string} email 邮箱地址
|
||||||
* @param {string} code 验证码
|
* @param {string} code 验证码
|
||||||
* @returns {Promise<{success: boolean, message?: string}>}
|
* @returns {Promise<{success: boolean, message?: string}>}
|
||||||
*/
|
*/
|
||||||
async function verifySmsCode(phone, code) {
|
async function verifyEmailCode(email, code) {
|
||||||
try {
|
try {
|
||||||
if (!global.smsCodeCache) {
|
if (!global.emailCodeCache) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '验证码不存在或已过期'
|
message: '验证码不存在或已过期'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached = global.smsCodeCache[phone];
|
const cached = global.emailCodeCache[email];
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -308,7 +323,7 @@ async function verifySmsCode(phone, code) {
|
|||||||
|
|
||||||
// 检查是否过期
|
// 检查是否过期
|
||||||
if (Date.now() > cached.expire_time) {
|
if (Date.now() > cached.expire_time) {
|
||||||
delete global.smsCodeCache[phone];
|
delete global.emailCodeCache[email];
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '验证码已过期,请重新获取'
|
message: '验证码已过期,请重新获取'
|
||||||
@@ -324,13 +339,13 @@ async function verifySmsCode(phone, code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证成功后删除缓存
|
// 验证成功后删除缓存
|
||||||
delete global.smsCodeCache[phone];
|
delete global.emailCodeCache[email];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true
|
success: true
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('验证短信验证码失败:', error);
|
console.error('验证验证码失败:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: error.message || '验证失败'
|
message: error.message || '验证失败'
|
||||||
|
|||||||
189
api/services/email_service.js
Normal file
189
api/services/email_service.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
/**
|
||||||
|
* 邮件服务
|
||||||
|
* 使用QQ邮箱SMTP服务发送邮件
|
||||||
|
*/
|
||||||
|
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
const config = require('../../config/config.js');
|
||||||
|
|
||||||
|
// 创建邮件传输器
|
||||||
|
let transporter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化邮件传输器
|
||||||
|
*/
|
||||||
|
function init_transporter() {
|
||||||
|
if (transporter) {
|
||||||
|
return transporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QQ邮箱SMTP配置
|
||||||
|
const email_config = config.email || {
|
||||||
|
host: 'smtp.qq.com',
|
||||||
|
port: 465,
|
||||||
|
secure: true, // 使用SSL
|
||||||
|
auth: {
|
||||||
|
user: process.env.QQ_EMAIL_USER || '', // QQ邮箱账号
|
||||||
|
pass: process.env.QQ_EMAIL_PASS || '' // QQ邮箱授权码(不是密码)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
transporter = nodemailer.createTransport({
|
||||||
|
host: email_config.host,
|
||||||
|
port: email_config.port,
|
||||||
|
secure: email_config.secure,
|
||||||
|
auth: email_config.auth
|
||||||
|
});
|
||||||
|
|
||||||
|
return transporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件
|
||||||
|
* @param {Object} options 邮件选项
|
||||||
|
* @param {string} options.to 收件人邮箱
|
||||||
|
* @param {string} options.subject 邮件主题
|
||||||
|
* @param {string} options.html 邮件HTML内容
|
||||||
|
* @param {string} options.text 邮件纯文本内容(可选)
|
||||||
|
* @returns {Promise<{success: boolean, message?: string, messageId?: string}>}
|
||||||
|
*/
|
||||||
|
async function send_email(options) {
|
||||||
|
try {
|
||||||
|
const transporter_instance = init_transporter();
|
||||||
|
|
||||||
|
if (!transporter_instance) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '邮件服务未配置'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有配置邮箱账号,返回错误
|
||||||
|
const email_config = config.email || {};
|
||||||
|
if (!email_config.auth || !email_config.auth.user || !email_config.auth.pass) {
|
||||||
|
console.warn('[邮件服务] QQ邮箱未配置,使用模拟发送');
|
||||||
|
// 开发环境可以模拟发送
|
||||||
|
if (config.env === 'development') {
|
||||||
|
console.log(`[模拟邮件] 发送到 ${options.to}`);
|
||||||
|
console.log(`[模拟邮件] 主题: ${options.subject}`);
|
||||||
|
console.log(`[模拟邮件] 内容: ${options.text || options.html}`);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '邮件已发送(模拟)',
|
||||||
|
messageId: 'mock-' + Date.now()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '邮件服务未配置,请联系管理员'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮件
|
||||||
|
const mail_options = {
|
||||||
|
from: `"${email_config.fromName || '自动找工作系统'}" <${email_config.auth.user}>`,
|
||||||
|
to: options.to,
|
||||||
|
subject: options.subject,
|
||||||
|
html: options.html,
|
||||||
|
text: options.text || options.html.replace(/<[^>]*>/g, '') // 如果没有text,从html提取
|
||||||
|
};
|
||||||
|
|
||||||
|
const info = await transporter_instance.sendMail(mail_options);
|
||||||
|
|
||||||
|
console.log(`[邮件服务] 邮件发送成功: ${options.to}, MessageId: ${info.messageId}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '邮件发送成功',
|
||||||
|
messageId: info.messageId
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[邮件服务] 发送邮件失败:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.message || '发送邮件失败'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码邮件
|
||||||
|
* @param {string} email 收件人邮箱
|
||||||
|
* @param {string} code 验证码
|
||||||
|
* @returns {Promise<{success: boolean, message?: string, messageId?: string}>}
|
||||||
|
*/
|
||||||
|
async function send_verification_code(email, code) {
|
||||||
|
const subject = '【自动找工作系统】注册验证码';
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.code-box {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<h2>验证码</h2>
|
||||||
|
<p>您好,</p>
|
||||||
|
<p>您正在注册自动找工作系统,验证码为:</p>
|
||||||
|
<div class="code-box">${code}</div>
|
||||||
|
<p>验证码有效期为 <strong>5分钟</strong>,请勿泄露给他人。</p>
|
||||||
|
<div class="tip">
|
||||||
|
<p>如果这不是您的操作,请忽略此邮件。</p>
|
||||||
|
<p>此邮件由系统自动发送,请勿回复。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return await send_email({
|
||||||
|
to: email,
|
||||||
|
subject: subject,
|
||||||
|
html: html
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
send_email,
|
||||||
|
send_verification_code,
|
||||||
|
init_transporter
|
||||||
|
};
|
||||||
@@ -64,7 +64,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 白名单URL - 不需要token验证的接口
|
// 白名单URL - 不需要token验证的接口
|
||||||
"allowUrls": ["/admin_api/sys_user/login", "/admin_api/sys_user/authorityMenus", "/admin_api/sys_user/register", "/api/user/loginByWeixin", "/file/", "/sys_file/", "/admin_api/win_data/viewLogInfo", "/api/user/wx_auth", '/api/docs', 'api/swagger.json', 'payment/notify', 'payment/refund-notify', 'wallet/transfer_notify', 'user/sms/send', 'user/sms/verify', '/api/version/check','/api/file/upload_file_to_oss_by_auto_work','/api/version/create', '/admin_api/invite/register', '/admin_api/invite/send-sms'],
|
"allowUrls": ["/admin_api/sys_user/login", "/admin_api/sys_user/authorityMenus", "/admin_api/sys_user/register", "/api/user/loginByWeixin", "/file/", "/sys_file/", "/admin_api/win_data/viewLogInfo", "/api/user/wx_auth", '/api/docs', 'api/swagger.json', 'payment/notify', 'payment/refund-notify', 'wallet/transfer_notify', 'user/sms/send', 'user/sms/verify', '/api/version/check','/api/file/upload_file_to_oss_by_auto_work','/api/version/create', '/admin_api/invite/register', '/admin_api/invite/send-email-code'],
|
||||||
|
|
||||||
|
|
||||||
// AI服务配置
|
// AI服务配置
|
||||||
@@ -89,5 +89,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
qq_map_key: "VIABZ-3N6HT-4BLXK-VF3FD-TM6YF-YRFQM",
|
qq_map_key: "VIABZ-3N6HT-4BLXK-VF3FD-TM6YF-YRFQM",
|
||||||
|
|
||||||
|
// 邮件服务配置(QQ邮箱)
|
||||||
|
email: {
|
||||||
|
host: 'smtp.qq.com',
|
||||||
|
port: 465,
|
||||||
|
secure: true, // 使用SSL
|
||||||
|
fromName: '自动找工作系统',
|
||||||
|
auth: {
|
||||||
|
user: 'light603@qq.com' || '', // QQ邮箱账号,例如: 123456789@qq.com
|
||||||
|
pass: 'fxqnednoacqybbba' || '' // QQ邮箱授权码(不是密码,需要在QQ邮箱设置中获取)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"mqtt": "^5.14.0",
|
"mqtt": "^5.14.0",
|
||||||
"mysql2": "^1.7.0",
|
"mysql2": "^1.7.0",
|
||||||
"node-schedule": "latest",
|
"node-schedule": "latest",
|
||||||
|
"nodemailer": "^6.9.7",
|
||||||
"node-uuid": "^1.4.8",
|
"node-uuid": "^1.4.8",
|
||||||
"redis": "^5.8.3",
|
"redis": "^5.8.3",
|
||||||
"sequelize": "^5.22.5",
|
"sequelize": "^5.22.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user