From cddc22d2b3c88b01a9e4d4736edc9356d772c938 Mon Sep 17 00:00:00 2001 From: fawney19 Date: Thu, 1 Jan 2026 02:10:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E9=AA=8C=E8=AF=81=E6=A8=A1=E5=9D=97=E5=B9=B6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=BB=A3=E7=A0=81=E5=AE=A1=E6=9F=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构: 将 verification 模块重命名为 email,目录结构更清晰 - 新增: 独立的邮件配置管理页面 (EmailSettings.vue) - 新增: 邮件模板管理功能(支持自定义 HTML 模板和预览) - 新增: 查询验证状态 API,支持页面刷新后恢复验证流程 - 新增: 注册邮箱后缀白名单/黑名单限制功能 - 修复: 统一密码最小长度为 6 位(前后端一致) - 修复: SMTP 连接添加 30 秒超时配置,防止 worker 挂起 - 修复: 邮件模板变量添加 HTML 转义,防止 XSS - 修复: 验证状态清除改为 db.commit 后执行,避免竞态条件 - 优化: RegisterDialog 重写验证码输入组件,提升用户体验 - 优化: Input 组件支持 disableAutofill 属性 --- frontend/src/api/admin.ts | 78 ++ frontend/src/api/auth.ts | 21 +- frontend/src/components/ui/dialog/Dialog.vue | 5 +- frontend/src/components/ui/input.vue | 11 +- .../auth/components/RegisterDialog.vue | 425 ++++++--- frontend/src/layouts/MainLayout.vue | 2 + frontend/src/router/index.ts | 5 + frontend/src/style.css | 7 + frontend/src/views/admin/EmailSettings.vue | 856 ++++++++++++++++++ frontend/src/views/admin/SystemSettings.vue | 327 +------ frontend/src/views/user/Settings.vue | 4 +- src/api/admin/system.py | 296 +++++- src/api/auth/routes.py | 184 +++- src/config/settings.py | 10 + src/models/api.py | 31 +- .../{verification => email}/__init__.py | 0 .../{verification => email}/email_sender.py | 160 +++- src/services/email/email_template.py | 442 +++++++++ .../email_verification.py | 56 +- src/services/system/config.py | 23 +- src/services/verification/email_template.py | 238 ----- 21 files changed, 2373 insertions(+), 808 deletions(-) create mode 100644 frontend/src/views/admin/EmailSettings.vue rename src/services/{verification => email}/__init__.py (100%) rename src/services/{verification => email}/email_sender.py (60%) create mode 100644 src/services/email/email_template.py rename src/services/{verification => email}/email_verification.py (77%) delete mode 100644 src/services/verification/email_template.py diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts index 41d6141..d19a5fb 100644 --- a/frontend/src/api/admin.ts +++ b/frontend/src/api/admin.ts @@ -124,6 +124,37 @@ export interface ModelExport { config?: any } +// 邮件模板接口 +export interface EmailTemplateInfo { + type: string + name: string + variables: string[] + subject: string + html: string + is_custom: boolean + default_subject?: string + default_html?: string +} + +export interface EmailTemplatesResponse { + templates: EmailTemplateInfo[] +} + +export interface EmailTemplatePreviewResponse { + html: string + variables: Record +} + +export interface EmailTemplateResetResponse { + message: string + template: { + type: string + name: string + subject: string + html: string + } +} + // Provider 模型查询响应 export interface ProviderModelsQueryResponse { success: boolean @@ -395,5 +426,52 @@ export const adminApi = { config ) return response.data + }, + + // 邮件模板相关 + // 获取所有邮件模板 + async getEmailTemplates(): Promise { + const response = await apiClient.get('/api/admin/system/email/templates') + return response.data + }, + + // 获取指定类型的邮件模板 + async getEmailTemplate(templateType: string): Promise { + const response = await apiClient.get( + `/api/admin/system/email/templates/${templateType}` + ) + return response.data + }, + + // 更新邮件模板 + async updateEmailTemplate( + templateType: string, + data: { subject?: string; html?: string } + ): Promise<{ message: string }> { + const response = await apiClient.put<{ message: string }>( + `/api/admin/system/email/templates/${templateType}`, + data + ) + return response.data + }, + + // 预览邮件模板 + async previewEmailTemplate( + templateType: string, + data?: { html?: string } & Record + ): Promise { + const response = await apiClient.post( + `/api/admin/system/email/templates/${templateType}/preview`, + data || {} + ) + return response.data + }, + + // 重置邮件模板为默认值 + async resetEmailTemplate(templateType: string): Promise { + const response = await apiClient.post( + `/api/admin/system/email/templates/${templateType}/reset` + ) + return response.data } } diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index e0079d5..17355f0 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -51,6 +51,18 @@ export interface VerifyEmailResponse { success: boolean } +export interface VerificationStatusRequest { + email: string +} + +export interface VerificationStatusResponse { + email: string + has_pending_code: boolean + is_verified: boolean + cooldown_remaining: number | null + code_expires_in: number | null +} + export interface RegisterRequest { email: string username: string @@ -67,7 +79,6 @@ export interface RegisterResponse { export interface RegistrationSettingsResponse { enable_registration: boolean require_email_verification: boolean - verification_code_expire_minutes?: number } export interface User { @@ -154,5 +165,13 @@ export const authApi = { '/api/auth/registration-settings' ) return response.data + }, + + async getVerificationStatus(email: string): Promise { + const response = await apiClient.post( + '/api/auth/verification-status', + { email } + ) + return response.data } } diff --git a/frontend/src/components/ui/dialog/Dialog.vue b/frontend/src/components/ui/dialog/Dialog.vue index c0cba50..a77759a 100644 --- a/frontend/src/components/ui/dialog/Dialog.vue +++ b/frontend/src/components/ui/dialog/Dialog.vue @@ -71,8 +71,8 @@ - -
+ +
@@ -105,6 +105,7 @@ const props = defineProps<{ icon?: Component // Lucide icon component iconClass?: string // Custom icon color class zIndex?: number // Custom z-index for nested dialogs (default: 60) + noPadding?: boolean // Disable default content padding }>() // Emits 定义 diff --git a/frontend/src/components/ui/input.vue b/frontend/src/components/ui/input.vue index 5f25280..4dea378 100644 --- a/frontend/src/components/ui/input.vue +++ b/frontend/src/components/ui/input.vue @@ -3,6 +3,9 @@ :class="inputClass" :value="modelValue" :autocomplete="autocompleteAttr" + :data-lpignore="disableAutofill ? 'true' : undefined" + :data-1p-ignore="disableAutofill ? 'true' : undefined" + :data-form-type="disableAutofill ? 'other' : undefined" v-bind="$attrs" @input="handleInput" > @@ -16,6 +19,7 @@ interface Props { modelValue?: string | number class?: string autocomplete?: string + disableAutofill?: boolean } const props = defineProps() @@ -23,7 +27,12 @@ const emit = defineEmits<{ 'update:modelValue': [value: string] }>() -const autocompleteAttr = computed(() => props.autocomplete ?? 'off') +const autocompleteAttr = computed(() => { + if (props.disableAutofill) { + return 'one-time-code' + } + return props.autocomplete ?? 'off' +}) const inputClass = computed(() => cn( diff --git a/frontend/src/features/auth/components/RegisterDialog.vue b/frontend/src/features/auth/components/RegisterDialog.vue index 9cc3632..0db304e 100644 --- a/frontend/src/features/auth/components/RegisterDialog.vue +++ b/frontend/src/features/auth/components/RegisterDialog.vue @@ -3,42 +3,41 @@ v-model:open="isOpen" size="lg" > - - -
-
+
+ +
+
Logo
+

+ 注册新账户 +

+

+ 请填写您的邮箱和个人信息完成注册 +

- - - 注册新账户 - - - 请填写您的邮箱和个人信息完成注册 - - - +
- +
@@ -46,110 +45,127 @@
- +
- -

- 验证码错误,请重新输入 -

-

- ✓ 邮箱验证成功 -

+
+ +
+ + + + + 正在发送验证码... +
+ + +
- +
- + -

- 密码长度至少 8 位 -

- +
- - - - -
-
+ +
已有账户?
- +
+ + diff --git a/frontend/src/views/admin/SystemSettings.vue b/frontend/src/views/admin/SystemSettings.vue index a95fad3..ae5a01b 100644 --- a/frontend/src/views/admin/SystemSettings.vue +++ b/frontend/src/views/admin/SystemSettings.vue @@ -185,218 +185,6 @@
- - -
-
- - -

- 邮件服务器地址 -

-
- -
- - -

- 常用端口: 587 (TLS), 465 (SSL), 25 (非加密) -

-
- -
- - -

- 通常是您的邮箱地址 -

-
- -
- - -

- 邮箱密码或应用专用密码 -

-
- -
- - -

- 显示为发件人的邮箱地址 -

-
- -
- - -

- 显示为发件人的名称 -

-
- -
- - -

- 验证码的有效时间 -

-
- -
-
- -
- -

- 推荐开启以提高安全性 -

-
-
-
- -
-
- -
- -

- 部分服务需要隐式 SSL,一般使用端口 465 -

-
-
-
-
- -
- -
- -
-

- {{ smtpTestResult.success ? '✓ SMTP 连接测试成功' : '✗ SMTP 连接测试失败' }} -

-

- {{ smtpTestResult.message }} -

-
-
-