import { useEffect, useState } from 'react' import { Card, Form, Button, Switch, Input, InputNumber, message, Typography, Space, Alert, Select, Table, Tag, Popconfirm, Modal } from 'antd' import { SaveOutlined, CheckCircleOutlined, ReloadOutlined, GlobalOutlined, NotificationOutlined, KeyOutlined, LinkOutlined, PlusOutlined, EditOutlined, DeleteOutlined, SendOutlined } from '@ant-design/icons' import { apiService } from '../services/api' import { useMediaQuery } from 'react-responsive' import { useTranslation } from 'react-i18next' import type { SystemConfig, BuilderApiKeyUpdateRequest, NotificationConfig, NotificationConfigRequest, NotificationConfigUpdateRequest } from '../types' import { TelegramConfigForm } from '../components/notifications' import SystemUpdate from './SystemUpdate' const { Title, Text, Paragraph } = Typography interface ProxyConfig { id?: number type: string enabled: boolean host?: string port?: number username?: string subscriptionUrl?: string lastSubscriptionUpdate?: number createdAt: number updatedAt: number } interface ProxyCheckResponse { success: boolean message: string responseTime?: number latency?: number } const SystemSettings: React.FC = () => { const { t, i18n: i18nInstance } = useTranslation() const isMobile = useMediaQuery({ maxWidth: 768 }) // 第一部分:多语言 const [languageForm] = Form.useForm() const [currentLang, setCurrentLang] = useState('auto') // 第二部分:消息推送设置 const [notificationConfigs, setNotificationConfigs] = useState([]) const [notificationLoading, setNotificationLoading] = useState(false) const [notificationModalVisible, setNotificationModalVisible] = useState(false) const [editingNotificationConfig, setEditingNotificationConfig] = useState(null) const [notificationForm] = Form.useForm() const [testLoading, setTestLoading] = useState(false) // 第三部分:Relayer配置 const [relayerForm] = Form.useForm() const [autoRedeemForm] = Form.useForm() const [systemConfig, setSystemConfig] = useState(null) const [relayerLoading, setRelayerLoading] = useState(false) const [autoRedeemLoading, setAutoRedeemLoading] = useState(false) // 第四部分:代理设置 const [proxyForm] = Form.useForm() const [proxyLoading, setProxyLoading] = useState(false) const [proxyChecking, setProxyChecking] = useState(false) const [proxyCheckResult, setProxyCheckResult] = useState(null) const [currentProxyConfig, setCurrentProxyConfig] = useState(null) useEffect(() => { // 初始化多语言设置 const savedLanguage = localStorage.getItem('i18n_language') || 'auto' setCurrentLang(savedLanguage) languageForm.setFieldsValue({ language: savedLanguage }) // 加载其他配置 fetchNotificationConfigs() fetchSystemConfig() fetchProxyConfig() }, []) // ==================== 第一部分:多语言 ==================== const detectSystemLanguage = (): string => { const systemLanguage = navigator.language || navigator.languages?.[0] || 'en' const lang = systemLanguage.toLowerCase() if (lang.startsWith('zh')) { if (lang.includes('tw') || lang.includes('hk') || lang.includes('mo')) { return 'zh-TW' } return 'zh-CN' } return 'en' } const handleLanguageSubmit = async (values: { language: string }) => { try { let actualLang = values.language if (values.language === 'auto') { actualLang = detectSystemLanguage() localStorage.setItem('i18n_language', 'auto') } else { localStorage.setItem('i18n_language', values.language) } setCurrentLang(values.language) await i18nInstance.changeLanguage(actualLang) message.success(t('languageSettings.changeSuccess') || '语言设置已保存') } catch (error) { message.error(t('languageSettings.changeFailed') || '语言设置保存失败') } } // ==================== 第二部分:消息推送设置 ==================== const fetchNotificationConfigs = async () => { setNotificationLoading(true) try { const response = await apiService.notifications.list({ type: 'telegram' }) if (response.data.code === 0 && response.data.data) { setNotificationConfigs(response.data.data) } else { message.error(response.data.msg || t('notificationSettings.fetchFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.fetchFailed')) } finally { setNotificationLoading(false) } } const handleNotificationCreate = () => { setEditingNotificationConfig(null) notificationForm.resetFields() notificationForm.setFieldsValue({ type: 'telegram', enabled: true, config: { botToken: '', chatIds: [] } }) setNotificationModalVisible(true) } const handleNotificationEdit = (config: NotificationConfig) => { setEditingNotificationConfig(config) let botToken = '' let chatIds = '' if (config.config) { if ('data' in config.config && config.config.data) { const data = config.config.data as any botToken = data.botToken || '' if (data.chatIds) { if (Array.isArray(data.chatIds)) { chatIds = data.chatIds.join(',') } else if (typeof data.chatIds === 'string') { chatIds = data.chatIds } } } else { if ('botToken' in config.config) { botToken = (config.config as any).botToken || '' } if ('chatIds' in config.config) { const ids = (config.config as any).chatIds if (Array.isArray(ids)) { chatIds = ids.join(',') } else if (typeof ids === 'string') { chatIds = ids } } } } notificationForm.setFieldsValue({ type: config.type, name: config.name, enabled: config.enabled, config: { botToken: botToken, chatIds: chatIds } }) setNotificationModalVisible(true) } const handleNotificationDelete = async (id: number) => { try { const response = await apiService.notifications.delete({ id }) if (response.data.code === 0) { message.success(t('notificationSettings.deleteSuccess')) fetchNotificationConfigs() } else { message.error(response.data.msg || t('notificationSettings.deleteFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.deleteFailed')) } } const handleNotificationUpdateEnabled = async (id: number, enabled: boolean) => { try { const response = await apiService.notifications.updateEnabled({ id, enabled }) if (response.data.code === 0) { message.success(enabled ? t('notificationSettings.enableSuccess') : t('notificationSettings.disableSuccess')) fetchNotificationConfigs() } else { message.error(response.data.msg || t('notificationSettings.updateStatusFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.updateStatusFailed')) } } const handleNotificationTest = async () => { setTestLoading(true) try { const response = await apiService.notifications.test({ message: '这是一条测试消息' }) if (response.data.code === 0 && response.data.data) { message.success(t('notificationSettings.testSuccess')) } else { message.error(response.data.msg || t('notificationSettings.testFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.testFailed')) } finally { setTestLoading(false) } } const handleNotificationSubmit = async () => { try { const values = await notificationForm.validateFields() const chatIds = typeof values.config.chatIds === 'string' ? values.config.chatIds.split(',').map((id: string) => id.trim()).filter((id: string) => id) : values.config.chatIds || [] const configData: NotificationConfigRequest | NotificationConfigUpdateRequest = { type: values.type, name: values.name, enabled: values.enabled, config: { botToken: values.config.botToken, chatIds: chatIds } } if (editingNotificationConfig?.id) { const updateData = { ...configData, id: editingNotificationConfig.id } as NotificationConfigUpdateRequest const response = await apiService.notifications.update(updateData) if (response.data.code === 0) { message.success(t('notificationSettings.updateSuccess')) setNotificationModalVisible(false) fetchNotificationConfigs() } else { message.error(response.data.msg || t('notificationSettings.updateFailed')) } } else { const response = await apiService.notifications.create(configData) if (response.data.code === 0) { message.success(t('notificationSettings.createSuccess')) setNotificationModalVisible(false) fetchNotificationConfigs() } else { message.error(response.data.msg || t('notificationSettings.createFailed')) } } } catch (error: any) { if (error.errorFields) { return } message.error(error.message || t('message.error')) } } const notificationColumns = [ { title: t('notificationSettings.configName'), dataIndex: 'name', key: 'name', }, { title: t('notificationSettings.type'), dataIndex: 'type', key: 'type', render: (type: string) => {type.toUpperCase()} }, { title: t('notificationSettings.status'), dataIndex: 'enabled', key: 'enabled', render: (enabled: boolean) => ( {enabled ? t('notificationSettings.enabledStatus') : t('notificationSettings.disabledStatus')} ) }, { title: t('common.actions'), key: 'action', width: isMobile ? 120 : 200, render: (_: any, record: NotificationConfig) => ( handleNotificationUpdateEnabled(record.id!, checked)} /> handleNotificationDelete(record.id!)} okText={t('common.confirm')} cancelText={t('common.cancel')} > ) } ] // ==================== 第三部分:Relayer配置 ==================== const fetchSystemConfig = async () => { try { const response = await apiService.systemConfig.get() if (response.data.code === 0 && response.data.data) { const config = response.data.data setSystemConfig(config) // 将已配置的值填充到输入框中 relayerForm.setFieldsValue({ builderApiKey: config.builderApiKeyDisplay || '', builderSecret: config.builderSecretDisplay || '', builderPassphrase: config.builderPassphraseDisplay || '', }) autoRedeemForm.setFieldsValue({ autoRedeemEnabled: config.autoRedeemEnabled }) } } catch (error: any) { console.error('获取系统配置失败:', error) } } const handleRelayerSubmit = async (values: BuilderApiKeyUpdateRequest) => { setRelayerLoading(true) try { const updateData: BuilderApiKeyUpdateRequest = {} if (values.builderApiKey && values.builderApiKey.trim()) { updateData.builderApiKey = values.builderApiKey.trim() } if (values.builderSecret && values.builderSecret.trim()) { updateData.builderSecret = values.builderSecret.trim() } if (values.builderPassphrase && values.builderPassphrase.trim()) { updateData.builderPassphrase = values.builderPassphrase.trim() } if (!updateData.builderApiKey && !updateData.builderSecret && !updateData.builderPassphrase) { message.warning(t('builderApiKey.noChanges') || '没有需要更新的字段') setRelayerLoading(false) return } const response = await apiService.systemConfig.updateBuilderApiKey(updateData) if (response.data.code === 0) { message.success(t('builderApiKey.saveSuccess')) fetchSystemConfig() relayerForm.resetFields() } else { message.error(response.data.msg || t('builderApiKey.saveFailed')) } } catch (error: any) { message.error(error.message || t('builderApiKey.saveFailed')) } finally { setRelayerLoading(false) } } const handleAutoRedeemSubmit = async (values: { autoRedeemEnabled: boolean }) => { setAutoRedeemLoading(true) try { const response = await apiService.systemConfig.updateAutoRedeem({ enabled: values.autoRedeemEnabled }) if (response.data.code === 0) { message.success(t('systemSettings.autoRedeem.saveSuccess') || '自动赎回配置已保存') fetchSystemConfig() } else { message.error(response.data.msg || t('systemSettings.autoRedeem.saveFailed') || '保存自动赎回配置失败') } } catch (error: any) { message.error(error.message || t('systemSettings.autoRedeem.saveFailed') || '保存自动赎回配置失败') } finally { setAutoRedeemLoading(false) } } // ==================== 第四部分:代理设置 ==================== const fetchProxyConfig = async () => { try { const response = await apiService.proxyConfig.get() if (response.data.code === 0) { const data = response.data.data setCurrentProxyConfig(data) if (data) { proxyForm.setFieldsValue({ enabled: data.enabled, host: data.host || '', port: data.port || undefined, username: data.username || '', password: '', }) } else { proxyForm.resetFields() } } else { message.error(response.data.msg || '获取代理配置失败') } } catch (error: any) { message.error(error.message || '获取代理配置失败') } } const handleProxySubmit = async (values: any) => { setProxyLoading(true) try { const response = await apiService.proxyConfig.saveHttp({ enabled: values.enabled || false, host: values.host, port: values.port, username: values.username || undefined, password: values.password || undefined, }) if (response.data.code === 0) { message.success('保存代理配置成功。新配置将立即生效,已建立的 WebSocket 连接需要重新连接才能使用新代理。') fetchProxyConfig() setProxyCheckResult(null) } else { message.error(response.data.msg || '保存代理配置失败') } } catch (error: any) { message.error(error.message || '保存代理配置失败') } finally { setProxyLoading(false) } } const handleProxyCheck = async () => { setProxyChecking(true) setProxyCheckResult(null) try { const response = await apiService.proxyConfig.check() if (response.data.code === 0 && response.data.data) { const result = response.data.data setProxyCheckResult(result) if (result.success) { message.success(`代理检查成功:${result.message}${result.responseTime ? ` (响应时间: ${result.responseTime}ms)` : ''}`) } else { message.warning(`代理检查失败:${result.message}`) } } else { message.error(response.data.msg || '代理检查失败') } } catch (error: any) { message.error(error.message || '代理检查失败') } finally { setProxyChecking(false) } } return (
{t('systemSettings.title') || '通用设置'}
{/* 系统更新 */} {/* 第一部分:多语言 */} {t('systemSettings.language.title') || '多语言设置'} } style={{ marginBottom: '16px' }} >
{ return prevValues.type !== currentValues.type || prevValues.config !== currentValues.config }}> {() => { const currentType = notificationForm.getFieldValue('type') || 'telegram' if (currentType === 'telegram') { return } return null }}
{/* 第三部分:Relayer配置 */} {t('systemSettings.relayer.title') || 'Relayer 配置'} } style={{ marginBottom: '16px' }} > {/* Builder API Key 配置 */}
{t('builderApiKey.title') || 'Builder API Key'} {t('builderApiKey.description')} {t('builderApiKey.purposeTitle')}
  • {t('builderApiKey.purpose1')}
  • {t('builderApiKey.purpose2')}
  • {t('builderApiKey.purpose3')}
{t('builderApiKey.getApiKey')} {t('builderApiKey.openSettings')}
} type="info" showIcon style={{ marginBottom: '16px' }} />
(visible ? 👁️ : 👁️‍🗨️)} /> (visible ? 👁️ : 👁️‍🗨️)} />
{/* 自动赎回配置 */}
{t('systemSettings.autoRedeem.title') || '自动赎回'}
{!systemConfig?.builderApiKeyConfigured && ( )}
{/* 第四部分:代理设置 */} {t('systemSettings.proxy.title') || '代理设置'} } style={{ marginBottom: '16px' }} >
{proxyCheckResult && ( )}
{proxyCheckResult && ( {proxyCheckResult.message} {(proxyCheckResult.responseTime !== undefined || proxyCheckResult.latency !== undefined) && (
{t('proxySettings.latency') || '延迟'}: {(proxyCheckResult.latency ?? proxyCheckResult.responseTime) ?? 0}ms
)} } style={{ marginTop: '16px' }} showIcon /> )}
) } export default SystemSettings