✉️ 添加完整的邮件系统功能
后端新增功能: - 集成 nodemailer 支持 SMTP 邮件发送 - 新增邮箱验证系统(VerificationDB) - 新增密码重置令牌系统(PasswordResetTokenDB) - 实现邮件发送限流(30分钟3次,全天10次) - 添加 SMTP 配置管理接口 - 支持邮箱激活和密码重置邮件发送 前端新增功能: - 注册时邮箱必填并需验证 - 邮箱验证激活流程 - 重发激活邮件功能 - 基于邮箱的密码重置流程(替代管理员审核) - 管理后台 SMTP 配置界面 - SMTP 测试邮件发送功能 安全改进: - 邮件发送防刷限流保护 - 验证令牌随机生成(48字节) - 重置链接有效期限制 - 支持 SSL/TLS 加密传输 支持的邮箱服务:QQ邮箱、163邮箱、企业邮箱等主流SMTP服务 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -682,6 +682,9 @@
|
||||
</div>
|
||||
<small style="color: #666; font-size: 12px;">点击图片刷新验证码</small>
|
||||
</div>
|
||||
<div v-if="showResendVerify" class="alert alert-info" style="margin-bottom: 10px;">
|
||||
邮箱未验证?<a style="color:#667eea; cursor: pointer;" @click="resendVerification">点击重发激活邮件</a>
|
||||
</div>
|
||||
<div style="text-align: right; margin-bottom: 15px;">
|
||||
<a @click="showForgotPasswordModal = true" style="color: #667eea; cursor: pointer; font-size: 14px; text-decoration: none;">
|
||||
忘记密码?
|
||||
@@ -697,8 +700,8 @@
|
||||
<input type="text" class="form-input" v-model="registerForm.username" required minlength="3" maxlength="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">邮箱 (可选)</label>
|
||||
<input type="email" class="form-input" v-model="registerForm.email">
|
||||
<label class="form-label">邮箱 (必填,用于激活)</label>
|
||||
<input type="email" class="form-input" v-model="registerForm.email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">密码 (至少6字符)</label>
|
||||
@@ -1504,6 +1507,61 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统设置 -->
|
||||
<div class="card" style="margin-bottom: 30px;">
|
||||
<h3 style="margin-bottom: 20px;">
|
||||
<i class="fas fa-sliders-h"></i> 系统设置
|
||||
</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">最大上传大小 (MB)</label>
|
||||
<input type="number" class="form-input" v-model.number="systemSettings.maxUploadSizeMB" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<hr style="margin: 20px 0;">
|
||||
<h4 style="margin-bottom: 12px;">SMTP 邮件配置(用于注册激活和找回密码)</h4>
|
||||
<div class="alert alert-info" style="margin-bottom: 15px;">
|
||||
支持 QQ/163/企业邮箱等。QQ 邮箱示例:主机 <code>smtp.qq.com</code>,端口 <code>465</code>,勾选 SSL,用户名=邮箱地址,密码=授权码。
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">SMTP 主机</label>
|
||||
<input type="text" class="form-input" v-model="systemSettings.smtp.host" placeholder="如 smtp.qq.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">端口</label>
|
||||
<input type="number" class="form-input" v-model.number="systemSettings.smtp.port" placeholder="465/587">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">SSL/TLS</label>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<input type="checkbox" id="smtp-secure" v-model="systemSettings.smtp.secure">
|
||||
<label for="smtp-secure" style="margin: 0;">使用 SSL(465 通常需要)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名(邮箱)</label>
|
||||
<input type="text" class="form-input" v-model="systemSettings.smtp.user" placeholder="your@qq.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">发件人 From(可选)</label>
|
||||
<input type="text" class="form-input" v-model="systemSettings.smtp.from" placeholder="显示名称 <your@qq.com>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">密码/授权码</label>
|
||||
<input type="password" class="form-input" v-model="systemSettings.smtp.password" :placeholder="systemSettings.smtp.has_password ? '已配置,留空则不修改' : '请输入授权码'">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 15px;">
|
||||
<button class="btn btn-primary" @click="updateSystemSettings">
|
||||
<i class="fas fa-save"></i> 保存设置
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="testSmtp">
|
||||
<i class="fas fa-envelope"></i> 发送测试邮件
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-bottom: 20px;">用户管理</h3>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; table-layout: fixed; min-width: 900px;">
|
||||
@@ -1686,23 +1744,41 @@
|
||||
<!-- 忘记密码模态框 -->
|
||||
<div v-if="showForgotPasswordModal" class="modal-overlay" @mousedown.self="handleModalMouseDown" @mouseup.self="handleModalMouseUp('showForgotPasswordModal')">
|
||||
<div class="modal-content" @click.stop>
|
||||
<h3 style="margin-bottom: 20px;">忘记密码 - 提交重置请求</h3>
|
||||
<h3 style="margin-bottom: 20px;">忘记密码 - 邮箱重置</h3>
|
||||
<p style="color: #666; margin-bottom: 15px; font-size: 14px;">
|
||||
请输入您的用户名和新密码,提交后需要等待管理员审核批准
|
||||
请输入注册邮箱,我们会发送重置链接到您的邮箱
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input type="text" class="form-input" v-model="forgotPasswordForm.username" placeholder="请输入用户名" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">新密码 (至少6字符)</label>
|
||||
<input type="password" class="form-input" v-model="forgotPasswordForm.new_password" placeholder="输入新密码" minlength="6" required>
|
||||
<label class="form-label">邮箱</label>
|
||||
<input type="email" class="form-input" v-model="forgotPasswordForm.email" placeholder="请输入注册邮箱" required>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||||
<button class="btn btn-primary" @click="requestPasswordReset" style="flex: 1;">
|
||||
<i class="fas fa-paper-plane"></i> 提交请求
|
||||
<i class="fas fa-paper-plane"></i> 发送重置邮件
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="showForgotPasswordModal = false; forgotPasswordForm = {username: '', new_password: ''}" style="flex: 1;">
|
||||
<button class="btn btn-secondary" @click="showForgotPasswordModal = false; forgotPasswordForm = {email: ''}" style="flex: 1;">
|
||||
<i class="fas fa-times"></i> 取消
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 邮件重置密码模态框 -->
|
||||
<div v-if="showResetPasswordModal" class="modal-overlay" @mousedown.self="handleModalMouseDown" @mouseup.self="handleModalMouseUp('showResetPasswordModal')">
|
||||
<div class="modal-content" @click.stop>
|
||||
<h3 style="margin-bottom: 20px;">设置新密码</h3>
|
||||
<p style="color: #666; margin-bottom: 15px; font-size: 14px;">
|
||||
重置链接已验证,请输入新密码
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">新密码 (至少6字符)</label>
|
||||
<input type="password" class="form-input" v-model="resetPasswordForm.new_password" placeholder="输入新密码" minlength="6" required>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||||
<button class="btn btn-primary" @click="submitResetPassword" style="flex: 1;">
|
||||
<i class="fas fa-unlock"></i> 重置密码
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="showResetPasswordModal = false; resetPasswordForm = {token: '', new_password: ''}" style="flex: 1;">
|
||||
<i class="fas fa-times"></i> 取消
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user