Initial commit: 网络验证平台

This commit is contained in:
Admin
2026-04-27 17:22:56 +08:00
commit afe67d704e
780 changed files with 88960 additions and 0 deletions
@@ -0,0 +1,484 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { toast } from 'vue-sonner'
import { BasicPage } from '@/components/global-layout'
const route = useRoute()
const API_BASE = 'http://localhost:8080/api/v1'
const loading = ref(false)
const saving = ref(false)
const testing = ref(false)
const sendingCode = ref(false)
interface Permission {
allow_email_verify: boolean
allow_password_reset: boolean
allow_custom_smtp: boolean
allow_custom_template: boolean
}
interface SMTPConfig {
id: number
host: string
port: number
user: string
from_name: string
from_email: string
use_ssl: boolean
status: string
}
interface EmailConfig {
enable_email_verify: boolean
require_email_verify: boolean
enable_password_reset: boolean
permission: Permission
smtp_config: SMTPConfig | null
}
interface EmailTemplate {
id: number
type: string
name: string
subject: string
content: string
is_default: boolean
status: string
}
const config = ref<EmailConfig | null>(null)
const templates = ref<EmailTemplate[]>([])
const form = ref({
enable_email_verify: false,
require_email_verify: false,
enable_password_reset: false,
smtp_host: '',
smtp_port: 465,
smtp_user: '',
smtp_password: '',
smtp_from_name: '',
smtp_from_email: '',
smtp_use_ssl: true,
})
const testEmail = ref('')
const sendCodeEmail = ref('')
const sendCodePurpose = ref('register')
const canUseEmailVerify = computed(() => config.value?.permission?.allow_email_verify)
const canUsePasswordReset = computed(() => config.value?.permission?.allow_password_reset)
const canUseCustomSMTP = computed(() => config.value?.permission?.allow_custom_smtp)
const canUseCustomTemplate = computed(() => config.value?.permission?.allow_custom_template)
async function fetchConfig() {
loading.value = true
try {
const token = localStorage.getItem('token')
const response = await fetch(`${API_BASE}/dev/applications/${route.params.id}/email-config`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
const data = await response.json()
if (data.code === 200) {
config.value = data.data
form.value.enable_email_verify = data.data.enable_email_verify || false
form.value.require_email_verify = data.data.require_email_verify || false
form.value.enable_password_reset = data.data.enable_password_reset || false
if (data.data.smtp_config) {
form.value.smtp_host = data.data.smtp_config.host || ''
form.value.smtp_port = data.data.smtp_config.port || 465
form.value.smtp_user = data.data.smtp_config.user || ''
form.value.smtp_from_name = data.data.smtp_config.from_name || ''
form.value.smtp_from_email = data.data.smtp_config.from_email || ''
form.value.smtp_use_ssl = data.data.smtp_config.use_ssl ?? true
}
}
else {
toast.error(data.message || '获取配置失败')
}
}
catch (error) {
console.error('获取配置失败:', error)
toast.error('获取配置失败')
}
finally {
loading.value = false
}
}
async function fetchTemplates() {
try {
const token = localStorage.getItem('token')
const response = await fetch(`${API_BASE}/dev/applications/${route.params.id}/email-templates`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
const data = await response.json()
if (data.code === 200) {
templates.value = data.data || []
}
}
catch (error) {
console.error('获取模板失败:', error)
}
}
async function handleSave() {
saving.value = true
try {
const token = localStorage.getItem('token')
const response = await fetch(`${API_BASE}/dev/applications/${route.params.id}/email-config`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
enable_email_verify: form.value.enable_email_verify,
require_email_verify: form.value.require_email_verify,
enable_password_reset: form.value.enable_password_reset,
smtp_config: {
host: form.value.smtp_host,
port: form.value.smtp_port,
user: form.value.smtp_user,
password: form.value.smtp_password,
from_name: form.value.smtp_from_name,
from_email: form.value.smtp_from_email,
use_ssl: form.value.smtp_use_ssl,
},
}),
})
const data = await response.json()
if (data.code === 200) {
toast.success('保存成功')
fetchConfig()
}
else {
toast.error(data.message || '保存失败')
}
}
catch (error) {
console.error('保存失败:', error)
toast.error('保存失败')
}
finally {
saving.value = false
}
}
async function handleTestEmail() {
if (!testEmail.value) {
toast.error('请输入测试邮箱地址')
return
}
testing.value = true
try {
const token = localStorage.getItem('token')
const response = await fetch(`${API_BASE}/dev/applications/${route.params.id}/email-config/test`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: testEmail.value,
}),
})
const data = await response.json()
if (data.code === 200) {
toast.success('测试邮件已发送,请检查收件箱')
}
else {
toast.error(data.message || '发送失败')
}
}
catch (error) {
console.error('发送失败:', error)
toast.error('发送失败')
}
finally {
testing.value = false
}
}
async function handleSendVerifyCode() {
if (!sendCodeEmail.value) {
toast.error('请输入邮箱地址')
return
}
sendingCode.value = true
try {
const token = localStorage.getItem('token')
const response = await fetch(`${API_BASE}/dev/applications/${route.params.id}/send-verify-code`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: sendCodeEmail.value,
purpose: sendCodePurpose.value,
}),
})
const data = await response.json()
if (data.code === 200) {
toast.success('验证码已发送')
}
else {
toast.error(data.message || '发送失败')
}
}
catch (error) {
console.error('发送失败:', error)
toast.error('发送失败')
}
finally {
sendingCode.value = false
}
}
onMounted(() => {
fetchConfig()
fetchTemplates()
})
</script>
<template>
<BasicPage
title="邮箱设置"
description="配置应用的邮箱验证相关设置"
:breadcrumbs="[
{ title: '应用管理', href: '/developer/applications' },
{ title: '邮箱设置' },
]"
sticky
>
<div v-if="loading" class="flex items-center justify-center py-12">
<Icon icon="lucide:loader-2" class="size-8 animate-spin text-muted-foreground" />
</div>
<div v-else class="space-y-6">
<UiCard>
<UiCardHeader>
<UiCardTitle>邮箱验证设置</UiCardTitle>
<UiCardDescription>配置应用的邮箱验证功能</UiCardDescription>
</UiCardHeader>
<UiCardContent class="space-y-4">
<div class="flex items-center justify-between">
<div class="space-y-0.5">
<UiLabel>启用邮箱验证</UiLabel>
<p class="text-sm text-muted-foreground">
允许用户使用邮箱注册和验证
</p>
</div>
<UiSwitch v-model:checked="form.enable_email_verify" />
</div>
<div class="flex items-center justify-between">
<div class="space-y-0.5">
<UiLabel>强制邮箱验证</UiLabel>
<p class="text-sm text-muted-foreground">
注册时必须验证邮箱才能完成注册
</p>
</div>
<UiSwitch v-model:checked="form.require_email_verify" :disabled="!form.enable_email_verify" />
</div>
<div v-if="canUsePasswordReset" class="flex items-center justify-between">
<div class="space-y-0.5">
<UiLabel>启用密码重置</UiLabel>
<p class="text-sm text-muted-foreground">
允许用户通过邮箱验证码重置密码
</p>
</div>
<UiSwitch v-model:checked="form.enable_password_reset" />
</div>
</UiCardContent>
</UiCard>
<UiCard>
<UiCardHeader>
<UiCardTitle>SMTP 配置</UiCardTitle>
<UiCardDescription>配置邮件发送服务器</UiCardDescription>
</UiCardHeader>
<UiCardContent class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-2">
<UiLabel for="smtp_host">
SMTP 服务器
</UiLabel>
<UiInput id="smtp_host" v-model="form.smtp_host" placeholder="smtp.example.com" />
</div>
<div class="space-y-2">
<UiLabel for="smtp_port">
端口
</UiLabel>
<UiInput id="smtp_port" v-model.number="form.smtp_port" type="number" placeholder="465" />
</div>
<div class="space-y-2">
<UiLabel for="smtp_user">
用户名
</UiLabel>
<UiInput id="smtp_user" v-model="form.smtp_user" placeholder="your@email.com" />
</div>
<div class="space-y-2">
<UiLabel for="smtp_password">
密码/授权码
</UiLabel>
<UiInput id="smtp_password" v-model="form.smtp_password" type="password" placeholder="••••••••" />
</div>
<div class="space-y-2">
<UiLabel for="smtp_from_name">
发件人名称
</UiLabel>
<UiInput id="smtp_from_name" v-model="form.smtp_from_name" placeholder="应用名称" />
</div>
<div class="space-y-2">
<UiLabel for="smtp_from_email">
发件人邮箱
</UiLabel>
<UiInput id="smtp_from_email" v-model="form.smtp_from_email" placeholder="noreply@example.com" />
</div>
</div>
<div class="flex items-center justify-between">
<div class="space-y-0.5">
<UiLabel>使用 SSL</UiLabel>
<p class="text-sm text-muted-foreground">
推荐开启端口 465 通常需要 SSL
</p>
</div>
<UiSwitch v-model:checked="form.smtp_use_ssl" />
</div>
<div class="flex items-end gap-4 pt-4 border-t">
<div class="flex-1 space-y-2">
<UiLabel for="test_email">
测试邮箱
</UiLabel>
<UiInput id="test_email" v-model="testEmail" type="email" placeholder="test@example.com" />
</div>
<UiButton :disabled="testing" @click="handleTestEmail">
<Icon v-if="testing" icon="lucide:loader-2" class="mr-2 size-4 animate-spin" />
发送测试邮件
</UiButton>
</div>
</UiCardContent>
</UiCard>
<UiCard>
<UiCardHeader>
<UiCardTitle>发送验证码测试</UiCardTitle>
<UiCardDescription>测试验证码发送功能</UiCardDescription>
</UiCardHeader>
<UiCardContent class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="space-y-2">
<UiLabel for="send_code_email">
邮箱地址
</UiLabel>
<UiInput id="send_code_email" v-model="sendCodeEmail" type="email" placeholder="user@example.com" />
</div>
<div class="space-y-2">
<UiLabel for="send_code_purpose">
用途
</UiLabel>
<UiSelect v-model="sendCodePurpose">
<UiSelectTrigger>
<UiSelectValue placeholder="选择用途" />
</UiSelectTrigger>
<UiSelectContent>
<UiSelectItem value="register">
注册验证
</UiSelectItem>
<UiSelectItem value="reset_password">
重置密码
</UiSelectItem>
<UiSelectItem value="change_email">
更换邮箱
</UiSelectItem>
</UiSelectContent>
</UiSelect>
</div>
<div class="flex items-end">
<UiButton :disabled="sendingCode" @click="handleSendVerifyCode">
<Icon v-if="sendingCode" icon="lucide:loader-2" class="mr-2 size-4 animate-spin" />
发送验证码
</UiButton>
</div>
</div>
</UiCardContent>
</UiCard>
<UiCard v-if="canUseCustomTemplate">
<UiCardHeader>
<div class="flex items-center justify-between">
<div>
<UiCardTitle>邮件模板</UiCardTitle>
<UiCardDescription>自定义邮件模板</UiCardDescription>
</div>
<UiButton size="sm">
<Icon icon="lucide:plus" class="mr-2 size-4" />
新建模板
</UiButton>
</div>
</UiCardHeader>
<UiCardContent>
<div v-if="templates.length === 0" class="text-center py-8 text-muted-foreground">
<Icon icon="lucide:mail" class="size-12 mx-auto mb-4 opacity-50" />
<p>暂无自定义模板将使用系统默认模板</p>
</div>
<div v-else class="space-y-4">
<div
v-for="template in templates"
:key="template.id"
class="flex items-center justify-between p-4 rounded-lg border"
>
<div>
<p class="font-medium">
{{ template.name }}
</p>
<p class="text-sm text-muted-foreground">
{{ template.subject }}
</p>
</div>
<div class="flex items-center gap-2">
<UiBadge v-if="template.is_default" variant="secondary">
默认
</UiBadge>
<UiButton variant="ghost" size="icon">
<Icon icon="lucide:pencil" class="size-4" />
</UiButton>
</div>
</div>
</div>
</UiCardContent>
</UiCard>
<div class="flex justify-end">
<UiButton :disabled="saving" @click="handleSave">
<Icon v-if="saving" icon="lucide:loader-2" class="mr-2 size-4 animate-spin" />
保存设置
</UiButton>
</div>
</div>
</BasicPage>
</template>