import React, { useEffect, useState, useCallback } from 'react' import { Card, Table, Button, Space, Tag, Popconfirm, message, Typography, Modal, Form, Input, Switch, Tooltip, Row, Col, Tabs } from 'antd' import { PlusOutlined, EditOutlined, DeleteOutlined, SendOutlined, ReloadOutlined, CheckOutlined, RobotOutlined, FormOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import { apiService } from '../services/api' import type { NotificationConfig, NotificationConfigRequest, NotificationConfigUpdateRequest, NotificationTemplate, TemplateTypeInfo, TemplateVariablesResponse, TemplateVariable } from '../types' import { useMediaQuery } from 'react-responsive' import { TelegramConfigForm } from '../components/notifications' import TextArea from 'antd/es/input/TextArea' const { Title, Text, Paragraph } = Typography const variableTagStyle: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', cursor: 'pointer', marginBottom: 6, marginRight: 6, borderRadius: 6, padding: '4px 10px', fontSize: 12, transition: 'all 0.2s ease', border: '1px solid #e8e8e8', background: '#ffffff', color: 'rgba(0, 0, 0, 0.65)', } const variableTagHoverStyle: React.CSSProperties = { borderColor: '#1890ff', background: '#e6f7ff', color: '#1890ff', transform: 'translateY(-1px)', boxShadow: '0 2px 4px rgba(24, 144, 255, 0.2)', } /** * 变量分类标签映射 */ const CATEGORY_LABELS: Record = { common: 'notificationSettings.templates.commonVariables', order: 'notificationSettings.templates.orderVariables', copy_trading: 'notificationSettings.templates.copyTradingVariables', redeem: 'notificationSettings.templates.redeemVariables', error: 'notificationSettings.templates.errorVariables', filter: 'notificationSettings.templates.filterVariables', strategy: 'notificationSettings.templates.strategyVariables' } const NotificationSettingsPage: React.FC = () => { const { t } = useTranslation() const isMobile = useMediaQuery({ maxWidth: 768 }) // 机器人配置相关状态 const [configs, setConfigs] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) const [editingConfig, setEditingConfig] = useState(null) const [form] = Form.useForm() const [testLoading, setTestLoading] = useState(false) // 模板配置相关状态 const [templateTypes, setTemplateTypes] = useState([]) const [selectedTemplateType, setSelectedTemplateType] = useState('ORDER_SUCCESS') const [currentTemplate, setCurrentTemplate] = useState(null) const [templateVariables, setTemplateVariables] = useState(null) const [templateContent, setTemplateContent] = useState('') const [testTemplateLoading, setTestTemplateLoading] = useState(false) // 加载机器人配置 useEffect(() => { fetchConfigs() }, []) // 加载模板类型 useEffect(() => { fetchTemplateTypes() }, []) // 当选中的模板类型改变时,加载模板详情和变量 useEffect(() => { if (selectedTemplateType) { fetchTemplateDetail(selectedTemplateType) fetchTemplateVariables(selectedTemplateType) } }, [selectedTemplateType]) const fetchConfigs = async () => { setLoading(true) try { const response = await apiService.notifications.list({ type: 'telegram' }) if (response.data.code === 0 && response.data.data) { setConfigs(response.data.data) } else { message.error(response.data.msg || t('notificationSettings.fetchFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.fetchFailed')) } finally { setLoading(false) } } const fetchTemplateTypes = async () => { try { const response = await apiService.notifications.getTemplateTypes() if (response.data.code === 0 && response.data.data) { setTemplateTypes(response.data.data) } } catch (error) { console.error('获取模板类型失败:', error) } } const fetchTemplateDetail = async (templateType: string) => { try { const response = await apiService.notifications.getTemplateDetail({ templateType }) if (response.data.code === 0 && response.data.data) { setCurrentTemplate(response.data.data) setTemplateContent(response.data.data.templateContent) } } catch (error) { console.error('获取模板详情失败:', error) } } const fetchTemplateVariables = async (templateType: string) => { try { const response = await apiService.notifications.getTemplateVariables({ templateType }) if (response.data.code === 0 && response.data.data) { setTemplateVariables(response.data.data) } } catch (error) { console.error('获取模板变量失败:', error) } } // 机器人配置相关方法 const handleCreate = () => { setEditingConfig(null) form.resetFields() form.setFieldsValue({ type: 'telegram', enabled: true, config: { botToken: '', chatIds: [] } }) setModalVisible(true) } const handleEdit = (config: NotificationConfig) => { setEditingConfig(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 } } } } form.setFieldsValue({ type: config.type, name: config.name, enabled: config.enabled, config: { botToken: botToken, chatIds: chatIds } }) setModalVisible(true) } const handleDelete = async (id: number) => { try { const response = await apiService.notifications.delete({ id }) if (response.data.code === 0) { message.success(t('notificationSettings.deleteSuccess')) fetchConfigs() } else { message.error(response.data.msg || t('notificationSettings.deleteFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.deleteFailed')) } } const handleUpdateEnabled = 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')) fetchConfigs() } else { message.error(response.data.msg || t('notificationSettings.updateStatusFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.updateStatusFailed')) } } const handleTest = 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 handleSubmit = async () => { try { const values = await form.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 (editingConfig?.id) { const updateData = { ...configData, id: editingConfig.id } as NotificationConfigUpdateRequest const response = await apiService.notifications.update(updateData) if (response.data.code === 0) { message.success(t('notificationSettings.updateSuccess')) setModalVisible(false) fetchConfigs() } 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')) setModalVisible(false) fetchConfigs() } else { message.error(response.data.msg || t('notificationSettings.createFailed')) } } } catch (error: any) { if (error.errorFields) { return } message.error(error.message || t('message.error')) } } const getConfigFormComponent = (type: string) => { switch (type?.toLowerCase()) { case 'telegram': return default: return null } } // 模板配置相关方法 const handleTemplateTypeChange = (type: string) => { setSelectedTemplateType(type) } const handleTemplateContentChange = (e: React.ChangeEvent) => { setTemplateContent(e.target.value) } const handleSaveTemplate = async () => { try { const response = await apiService.notifications.updateTemplate({ templateType: selectedTemplateType, templateContent: templateContent }) if (response.data.code === 0) { message.success(t('notificationSettings.templates.saveSuccess')) fetchTemplateDetail(selectedTemplateType) } else { message.error(response.data.msg || t('notificationSettings.templates.saveFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.templates.saveFailed')) } } const handleResetTemplate = async () => { try { const response = await apiService.notifications.resetTemplate({ templateType: selectedTemplateType }) if (response.data.code === 0) { message.success(t('notificationSettings.templates.resetSuccess')) fetchTemplateDetail(selectedTemplateType) } else { message.error(response.data.msg || t('notificationSettings.templates.resetFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.templates.resetFailed')) } } const handleTestTemplate = async () => { setTestTemplateLoading(true) try { const response = await apiService.notifications.testTemplate({ templateType: selectedTemplateType, templateContent: templateContent }) if (response.data.code === 0 && response.data.data) { message.success(t('notificationSettings.templates.testSuccess')) } else { message.error(response.data.msg || t('notificationSettings.templates.testFailed')) } } catch (error: any) { message.error(error.message || t('notificationSettings.templates.testFailed')) } finally { setTestTemplateLoading(false) } } const handleCopyVariable = useCallback((variable: string) => { const text = `{{${variable}}}` if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(() => { message.success(t('notificationSettings.templates.copied')) }).catch(() => { fallbackCopy(text) }) } else { fallbackCopy(text) } function fallbackCopy(text: string) { const textArea = document.createElement('textarea') textArea.value = text textArea.style.position = 'fixed' textArea.style.left = '-9999px' textArea.style.top = '-9999px' document.body.appendChild(textArea) textArea.focus() textArea.select() try { document.execCommand('copy') message.success(t('notificationSettings.templates.copied')) } catch { message.error(t('common.copyFailed')) } document.body.removeChild(textArea) } }, [t]) const [variableHoverKey, setVariableHoverKey] = useState(null) const renderVariableItem = (variable: TemplateVariable) => { const isHover = variableHoverKey === variable.key const label = t(`notificationSettings.templates.variableLabels.${variable.key}`) const description = t(`notificationSettings.templates.variableDescriptions.${variable.key}`) const variableElement = ( handleCopyVariable(variable.key)} onMouseEnter={() => !isMobile && setVariableHoverKey(variable.key)} onMouseLeave={() => !isMobile && setVariableHoverKey(null)} onKeyDown={(e) => e.key === 'Enter' && handleCopyVariable(variable.key)} > {label} ) if (isMobile) { return ( {variableElement} ) } return ( {variableElement} ) } const renderVariablesPanel = () => { if (!templateVariables) return null return ( {t('notificationSettings.templates.variables')} } style={{ height: '100%', borderRadius: 8 }} bodyStyle={{ padding: '12px 16px', maxHeight: 420, overflowY: 'auto' }} > {templateVariables.categories.map(category => { const categoryVariables = templateVariables.variables.filter(v => v.category === category.key) if (categoryVariables.length === 0) return null return (
{t(CATEGORY_LABELS[category.key])}
{categoryVariables.sort((a, b) => a.sortOrder - b.sortOrder).map(renderVariableItem)}
) })} {t('notificationSettings.templates.clickToCopy')}
) } // 机器人配置表格列 const configColumns = [ { 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('notificationSettings.chatIds'), key: 'chatIds', render: (_: any, record: NotificationConfig) => { let chatIds: string[] = [] if (record.config) { if ('data' in record.config && record.config.data) { const data = (record.config as any).data if (data.chatIds) { if (Array.isArray(data.chatIds)) { chatIds = data.chatIds.filter((id: any) => id && String(id).trim()) } else if (typeof data.chatIds === 'string') { chatIds = data.chatIds.split(',').map((id: string) => id.trim()).filter((id: string) => id) } } } else if ('chatIds' in record.config) { const ids = (record.config as any).chatIds if (Array.isArray(ids)) { chatIds = ids.filter((id: any) => id && String(id).trim()) } else if (typeof ids === 'string') { chatIds = (ids as string).split(',').map((id: string) => id.trim()).filter((id: string) => id) } } } return chatIds.length > 0 ? ( {chatIds.join(', ')} ) : ( {t('notificationSettings.chatIdsNotConfigured')} ) } }, { title: t('common.actions'), key: 'action', width: isMobile ? 120 : 200, render: (_: any, record: NotificationConfig) => ( handleUpdateEnabled(record.id!, checked)} /> handleDelete(record.id!)} okText={t('common.confirm')} cancelText={t('common.cancel')} > ) } ] const templateTypeTabItems = templateTypes.map(type => ({ key: type.type, label: ( {t(`notificationSettings.templateTypes.${type.type}`)} ), })) return (
{t('notificationSettings.title')}
{/* 机器人配置 */} {t('notificationSettings.botConfig')} } style={{ marginBottom: '16px' }} extra={ } > {/* 模板配置 */} {t('notificationSettings.templateConfig')} } style={{ marginBottom: '16px' }} >
{t('notificationSettings.templates.templateContent')} {currentTemplate && ( {currentTemplate.isDefault ? t('notificationSettings.templates.isDefault') : t('notificationSettings.templates.isCustom')} )}