feat: establish core backend API server with S2A proxy, configuration, and mail services, alongside frontend email configuration page and toast component.

This commit is contained in:
2026-01-30 15:47:18 +08:00
parent 1fd984e1ab
commit 2e10b900fa
3 changed files with 76 additions and 5 deletions

View File

@@ -355,7 +355,61 @@ func handleMailServices(w http.ResponseWriter, r *http.Request) {
}
api.Success(w, safeServices)
case "POST":
api.Error(w, http.StatusNotImplemented, "更新邮箱服务配置暂未实现")
var req struct {
Services []struct {
Name string `json:"name"`
APIBase string `json:"apiBase"`
APIToken string `json:"apiToken"`
Domain string `json:"domain"`
EmailPath string `json:"emailPath"`
AddUserAPI string `json:"addUserApi"`
} `json:"services"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logger.Error(fmt.Sprintf("解析邮箱服务配置失败: %v", err), "", "mail")
api.Error(w, http.StatusBadRequest, "解析请求失败")
return
}
// 转换为 config.MailServiceConfig
var services []config.MailServiceConfig
for _, s := range req.Services {
emailPath := s.EmailPath
if emailPath == "" {
emailPath = "/api/public/emailList"
}
addUserAPI := s.AddUserAPI
if addUserAPI == "" {
addUserAPI = "/api/public/addUser"
}
services = append(services, config.MailServiceConfig{
Name: s.Name,
APIBase: s.APIBase,
APIToken: s.APIToken,
Domain: s.Domain,
EmailPath: emailPath,
AddUserAPI: addUserAPI,
})
}
// 更新邮箱服务配置
mail.Init(services)
// 保存到全局配置
if config.Global != nil {
config.Global.MailServices = services
}
logger.Success(fmt.Sprintf("邮箱服务配置已保存: %d 个服务", len(services)), "", "mail")
for _, s := range services {
logger.Info(fmt.Sprintf(" - %s (%s) @ %s", s.Name, s.Domain, s.APIBase), "", "mail")
}
api.Success(w, map[string]interface{}{
"message": fmt.Sprintf("已保存 %d 个邮箱服务配置", len(services)),
"count": len(services),
})
default:
api.Error(w, http.StatusMethodNotAllowed, "不支持的方法")
}

View File

@@ -78,7 +78,7 @@ function ToastContainer({ toasts, onRemove }: ToastContainerProps) {
if (toasts.length === 0) return null
return (
<div className="fixed bottom-6 right-6 z-[9999] space-y-3">
<div className="fixed top-6 right-6 z-[9999] space-y-3">
{toasts.map((toast) => (
<ToastItem key={toast.id} toast={toast} onRemove={onRemove} />
))}

View File

@@ -1,13 +1,15 @@
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 { Card, CardHeader, CardTitle, CardContent, Button, Input, useToast } from '../components/common'
import { useConfig } from '../hooks/useConfig'
import type { MailServiceConfig } from '../types'
export default function EmailConfig() {
const { config, updateEmailConfig } = useConfig()
const toast = useToast()
const [saved, setSaved] = useState(false)
const [saving, setSaving] = 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 }>>({})
@@ -20,6 +22,12 @@ export default function EmailConfig() {
}, [config.email?.services])
const handleSave = async () => {
if (services.length === 0) {
toast.warning('请至少添加一个邮箱服务')
return
}
setSaving(true)
// 保存到前端 context
updateEmailConfig({ services })
@@ -31,13 +39,20 @@ export default function EmailConfig() {
body: JSON.stringify({ services }),
})
if (res.ok) {
const data = await res.json()
if (res.ok && data.code === 0) {
setSaved(true)
setTimeout(() => setSaved(false), 2000)
toast.success(`保存成功: ${data.data.message}`)
} else {
toast.error('保存失败: ' + (data.message || '未知错误'))
}
} catch (error) {
console.error('保存失败:', error)
toast.error('保存失败: ' + (error instanceof Error ? error.message : '网络错误'))
}
setSaving(false)
}
const handleAddService = () => {
@@ -121,9 +136,11 @@ export default function EmailConfig() {
</Button>
<Button
onClick={handleSave}
disabled={saving}
loading={saving}
icon={saved ? <CheckCircle className="h-4 w-4" /> : <Save className="h-4 w-4" />}
>
{saved ? '已保存' : '保存配置'}
{saved ? '已保存' : saving ? '保存中...' : '保存配置'}
</Button>
</div>
</div>