feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.
This commit is contained in:
251
frontend/src/pages/EmailConfig.tsx
Normal file
251
frontend/src/pages/EmailConfig.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
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'
|
||||
|
||||
const API_BASE = 'http://localhost:8088'
|
||||
|
||||
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_BASE}/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_BASE}/api/mail/services/test`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user