Files
codexautopool/frontend/src/pages/EmailConfig.tsx

251 lines
12 KiB
TypeScript
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.
import { useState, useEffect } from 'react'
import { CheckCircle, Save, Mail, Plus, Trash2, TestTube, Loader2, Settings, Server } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
import { useConfig } from '../hooks/useConfig'
import type { MailServiceConfig } from '../types'
export default function EmailConfig() {
const { config, updateEmailConfig } = useConfig()
const [saved, setSaved] = useState(false)
const [services, setServices] = useState<MailServiceConfig[]>(config.email?.services || [])
const [testingIndex, setTestingIndex] = useState<number | null>(null)
const [testResults, setTestResults] = useState<Record<number, { success: boolean; message: string }>>({})
// 同步配置变化
useEffect(() => {
if (config.email?.services) {
setServices(config.email.services)
}
}, [config.email?.services])
const handleSave = async () => {
// 保存到前端 context
updateEmailConfig({ services })
// 保存到后端
try {
const res = await fetch('/api/mail/services', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ services }),
})
if (res.ok) {
setSaved(true)
setTimeout(() => setSaved(false), 2000)
}
} catch (error) {
console.error('保存失败:', error)
}
}
const handleAddService = () => {
setServices([
...services,
{
name: `邮箱服务 ${services.length + 1}`,
apiBase: '',
apiToken: '',
domain: '',
},
])
}
const handleRemoveService = (index: number) => {
if (services.length <= 1) {
return // 至少保留一个服务
}
const newServices = services.filter((_, i) => i !== index)
setServices(newServices)
}
const handleUpdateService = (index: number, updates: Partial<MailServiceConfig>) => {
const newServices = [...services]
newServices[index] = { ...newServices[index], ...updates }
setServices(newServices)
}
const handleTestService = async (index: number) => {
const service = services[index]
setTestingIndex(index)
setTestResults(prev => ({ ...prev, [index]: { success: false, message: '测试中...' } }))
try {
const res = await fetch('/api/mail/services/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: service.name,
api_base: service.apiBase,
api_token: service.apiToken,
domain: service.domain,
email_path: service.emailPath,
}),
})
const data = await res.json()
if (res.ok && data.code === 0) {
setTestResults(prev => ({ ...prev, [index]: { success: true, message: '连接成功' } }))
} else {
setTestResults(prev => ({ ...prev, [index]: { success: false, message: data.message || '连接失败' } }))
}
} catch (error) {
setTestResults(prev => ({ ...prev, [index]: { success: false, message: '网络错误' } }))
} finally {
setTestingIndex(null)
}
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 dark:text-slate-100 flex items-center gap-2">
<Mail className="h-7 w-7 text-purple-500" />
</h1>
<p className="text-sm text-slate-500 dark:text-slate-400">
</p>
</div>
<div className="flex gap-2">
<Button
variant="outline"
onClick={handleAddService}
icon={<Plus className="h-4 w-4" />}
>
</Button>
<Button
onClick={handleSave}
icon={saved ? <CheckCircle className="h-4 w-4" /> : <Save className="h-4 w-4" />}
>
{saved ? '已保存' : '保存配置'}
</Button>
</div>
</div>
{/* Service Cards */}
<div className="space-y-4">
{services.map((service, index) => (
<Card key={index}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Server className="h-5 w-5 text-purple-500" />
<span>{service.name || `服务 ${index + 1}`}</span>
<span className="text-sm font-normal text-slate-500">
(@{service.domain || '未设置域名'})
</span>
</CardTitle>
<div className="flex items-center gap-2">
{testResults[index] && (
<span className={`text-sm ${testResults[index].success ? 'text-green-500' : 'text-red-500'}`}>
{testResults[index].message}
</span>
)}
<Button
variant="ghost"
size="sm"
onClick={() => handleTestService(index)}
disabled={testingIndex === index || !service.apiBase}
icon={testingIndex === index ? <Loader2 className="h-4 w-4 animate-spin" /> : <TestTube className="h-4 w-4" />}
>
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveService(index)}
disabled={services.length <= 1}
icon={<Trash2 className="h-4 w-4" />}
className="text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
>
</Button>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input
label="服务名称"
placeholder="如:主邮箱服务"
value={service.name}
onChange={(e) => handleUpdateService(index, { name: e.target.value })}
hint="用于识别不同的邮箱服务"
/>
<Input
label="邮箱域名"
placeholder="如example.com"
value={service.domain}
onChange={(e) => handleUpdateService(index, { domain: e.target.value })}
hint="生成邮箱地址的域名后缀"
/>
</div>
<Input
label="API 地址"
placeholder="https://mail.example.com"
value={service.apiBase}
onChange={(e) => handleUpdateService(index, { apiBase: e.target.value })}
hint="邮箱服务 API 地址"
/>
<Input
label="API Token"
type="password"
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
value={service.apiToken}
onChange={(e) => handleUpdateService(index, { apiToken: e.target.value })}
hint="邮箱服务的 API 认证令牌"
/>
{/* Advanced Settings (Collapsed by default) */}
<details className="group">
<summary className="cursor-pointer text-sm text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 flex items-center gap-1">
<Settings className="h-4 w-4" />
</summary>
<div className="mt-4 space-y-4 pl-5 border-l-2 border-slate-200 dark:border-slate-700">
<Input
label="邮件列表 API 路径"
placeholder="/api/public/emailList (默认)"
value={service.emailPath || ''}
onChange={(e) => handleUpdateService(index, { emailPath: e.target.value })}
hint="获取邮件列表的 API 路径"
/>
<Input
label="创建用户 API 路径"
placeholder="/api/public/addUser (默认)"
value={service.addUserApi || ''}
onChange={(e) => handleUpdateService(index, { addUserApi: e.target.value })}
hint="创建邮箱用户的 API 路径"
/>
</div>
</details>
</CardContent>
</Card>
))}
</div>
{/* Help Info */}
<Card>
<CardContent>
<div className="text-sm text-slate-500 dark:text-slate-400">
<p className="font-medium mb-2"></p>
<ul className="list-disc list-inside space-y-1">
<li>使</li>
<li> API Token </li>
<li> xxx@esyteam.edu.kg</li>
<li></li>
<li>使</li>
</ul>
</div>
</CardContent>
</Card>
</div>
)
}