Files
BYW-VoiceHub/VoiceHub-main/app/components/Admin/SmtpManager.vue
T
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

378 lines
13 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.
<template>
<div class="max-w-[1400px] mx-auto space-y-10 pb-20 px-2">
<!-- 头部区域 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">邮件服务配置</h2>
<p class="text-xs text-zinc-500 mt-1">
配置 SMTP 服务以发送系统验证码、投稿通知及其他重要提醒
</p>
</div>
<div class="flex items-center gap-3">
<button
class="flex items-center gap-2 px-6 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-300 text-xs font-bold rounded-xl transition-all active:scale-95 disabled:opacity-50"
:disabled="reloading"
@click="reloadSmtpConfig"
>
<RotateCw :size="14" :class="reloading ? 'animate-spin' : ''" />
{{ reloading ? '重载中...' : '重载SMTP' }}
</button>
<button
class="flex items-center gap-2 px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-blue-900/20 transition-all active:scale-95"
:disabled="saving"
@click="saveConfig"
>
<Save :size="14" /> {{ saving ? '保存中...' : '保存配置' }}
</button>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-12 gap-8">
<!-- 左侧栏:SMTP 设置与测试 -->
<div class="xl:col-span-4 space-y-8">
<!-- SMTP 核心设置 -->
<section class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-6 space-y-6">
<div class="flex items-center justify-between">
<h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2"
>
<Server :size="16" class="text-blue-500" /> 服务配置
</h3>
<button
class="relative w-10 h-5 rounded-full transition-colors"
:class="config.smtpEnabled ? 'bg-blue-600' : 'bg-zinc-800'"
@click="config.smtpEnabled = !config.smtpEnabled"
>
<div
class="absolute top-1 w-3 h-3 bg-white rounded-full transition-all"
:class="config.smtpEnabled ? 'left-6' : 'left-1'"
/>
</button>
</div>
<div class="space-y-4">
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>SMTP 服务器</label
>
<input
v-model="config.smtpHost"
type="text"
placeholder="smtp.example.com"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>端口</label
>
<input
v-model.number="config.smtpPort"
type="number"
placeholder="587"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
</div>
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>加密方式</label
>
<CustomSelect
:model-value="config.smtpSecure ? 'SSL/TLS' : '无'"
:options="['SSL/TLS', '无']"
class="w-full"
@update:model-value="(val) => (config.smtpSecure = val === 'SSL/TLS')"
/>
</div>
</div>
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>发件人账号</label
>
<input
v-model="config.smtpUsername"
type="text"
placeholder="your-email@example.com"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
</div>
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>发件人邮箱 (From Email)</label
>
<input
v-model="config.smtpFromEmail"
type="text"
:placeholder="config.smtpUsername || 'your-email@example.com'"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
<p class="text-[9px] text-zinc-500 px-1 italic">
通常与发件人账号一致,部分服务商强制要求一致。
</p>
</div>
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>授权码 / 密码</label
>
<input
v-model="config.smtpPassword"
type="password"
placeholder="••••••••••••"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
</div>
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>发件人姓名</label
>
<input
v-model="config.smtpFromName"
type="text"
placeholder="校园广播站"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
</div>
</div>
</section>
<!-- SMTP 测试模块 -->
<section class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-6 space-y-6">
<h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2"
>
<Send :size="16" class="text-emerald-500" /> 服务测试
</h3>
<div class="space-y-4">
<div class="space-y-1.5">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>测试接收邮箱</label
>
<div class="flex gap-2">
<input
v-model="testEmail"
type="email"
placeholder="输入测试邮箱地址"
class="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30"
>
<button
class="flex items-center gap-2 px-4 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50"
:disabled="testing || !testEmail || !config.smtpEnabled"
@click="sendTestEmail"
>
<Check v-if="testResult?.success" :size="14" class="text-emerald-500" />
<Send v-else :size="14" />
{{ testing ? '发送中' : testResult?.success ? '已发送' : '发送' }}
</button>
</div>
</div>
<div v-if="testResult">
<div
class="flex items-center gap-2 p-3 rounded-xl text-xs"
:class="
testResult.success
? 'bg-emerald-500/10 text-emerald-500 border border-emerald-500/20'
: 'bg-red-500/10 text-red-500 border border-red-500/20'
"
>
<CheckCircle v-if="testResult.success" :size="14" />
<XCircle v-else :size="14" />
<div class="flex flex-col gap-0.5">
<p class="font-bold">{{ testResult.message }}</p>
<p v-if="testResult.detail" class="text-[10px] opacity-80 break-all font-mono">
{{ testResult.detail }}
</p>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- 右侧栏:模板编辑器 -->
<div class="xl:col-span-8">
<EmailTemplateManager />
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useToast } from '~/composables/useToast'
import EmailTemplateManager from '~/components/Admin/EmailTemplateManager.vue'
import CustomSelect from '~/components/UI/Common/CustomSelect.vue'
import { Server, Save, Check, Send, CheckCircle, XCircle, RotateCw } from 'lucide-vue-next'
const { showToast: showNotification } = useToast()
// 响应式数据
const config = ref({
smtpEnabled: false,
smtpHost: '',
smtpPort: 587,
smtpSecure: false,
smtpUsername: '',
smtpPassword: '',
smtpFromEmail: '',
smtpFromName: '校园广播站'
})
const testEmail = ref('')
const testing = ref(false)
const saving = ref(false)
const reloading = ref(false)
const testResult = ref(null)
const originalConfig = ref({})
// 加载配置
const loadConfig = async () => {
try {
const response = await $fetch('/api/admin/system-settings')
config.value = {
smtpEnabled: response.smtpEnabled || false,
smtpHost: response.smtpHost || '',
smtpPort: response.smtpPort || 587,
smtpSecure: response.smtpSecure || false,
smtpUsername: response.smtpUsername || '',
smtpPassword: response.smtpPassword || '',
smtpFromEmail: response.smtpFromEmail || '',
smtpFromName: response.smtpFromName || '校园广播站'
}
// 保存原始配置用于重置
originalConfig.value = { ...config.value }
} catch (error) {
console.error('加载SMTP配置失败:', error)
showNotification('加载配置失败', 'error')
}
}
// 保存配置
const saveConfig = async () => {
if (config.value.smtpEnabled) {
// 验证必填字段
if (!config.value.smtpHost || !config.value.smtpUsername || !config.value.smtpPassword) {
showNotification('请填写完整的SMTP配置信息', 'error')
return
}
}
saving.value = true
try {
await $fetch('/api/admin/system-settings', {
method: 'POST',
body: config.value
})
originalConfig.value = { ...config.value }
showNotification('SMTP配置保存成功', 'success')
} catch (error) {
console.error('保存SMTP配置失败:', error)
showNotification(error.data?.message || '保存配置失败', 'error')
} finally {
saving.value = false
}
}
// 重置配置
const resetConfig = () => {
config.value = { ...originalConfig.value }
testResult.value = null
showNotification('配置已重置', 'info')
}
const reloadSmtpConfig = async () => {
reloading.value = true
try {
const response = await $fetch('/api/admin/smtp/reload', {
method: 'POST'
})
if (!response.success) {
throw new Error(response.message || 'SMTP重载失败')
}
showNotification(response.message || 'SMTP配置已重载', 'success')
} catch (error) {
console.error('重载SMTP配置失败:', error)
showNotification(error.data?.message || error.message || '重载SMTP配置失败', 'error')
} finally {
reloading.value = false
}
}
// 测试连接
const testConnection = async () => {
if (!config.value.smtpEnabled || !config.value.smtpHost) {
showNotification('请先配置SMTP服务器信息', 'error')
return
}
testing.value = true
testResult.value = null
try {
const response = await $fetch('/api/admin/smtp/test-connection', {
method: 'POST',
body: config.value
})
testResult.value = response
} catch (error) {
console.error('测试连接失败:', error)
testResult.value = {
success: false,
message: error.data?.message || '测试连接失败'
}
} finally {
testing.value = false
}
}
// 发送测试邮件
const sendTestEmail = async () => {
if (!testEmail.value) {
showNotification('请输入测试邮箱地址', 'error')
return
}
testing.value = true
testResult.value = null
try {
const response = await $fetch('/api/admin/smtp/test-email', {
method: 'POST',
body: {
...config.value,
testEmail: testEmail.value
}
})
testResult.value = response
if (response.success) {
showNotification('测试邮件发送成功', 'success')
setTimeout(() => {
testResult.value = null
}, 5000)
}
} catch (error) {
console.error('发送测试邮件失败:', error)
testResult.value = {
success: false,
message: error.data?.message || '发送测试邮件失败'
}
} finally {
testing.value = false
}
}
// 生命周期
onMounted(() => {
loadConfig()
})
</script>
<style scoped></style>