Files
verify/frontend/src/pages/developer/applications/[id]/email.vue
T

485 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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: '/admin/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>