import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Card, Row, Col, Button, Switch, InputNumber, Input, Space, Tag, Alert, Modal, message, Descriptions, Divider, Typography, Spin, Radio, } from 'antd'; import { ClockCircleOutlined, DatabaseOutlined, CheckCircleOutlined, PlayCircleOutlined, SaveOutlined, SettingOutlined, InfoCircleOutlined, ArrowLeftOutlined, ThunderboltOutlined, SafetyOutlined, CloudDownloadOutlined, FileProtectOutlined, } from '@ant-design/icons'; import api from '../api'; import { useNavigate } from 'react-router-dom'; const { Title, Text, Paragraph } = Typography; const AutoBackupSettings = () => { const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [status, setStatus] = useState(null); const [settings, setSettings] = useState({ enabled: false, hour: 2, minute: 0, includeFiles: true, compress: true, maxCount: 30, maxAgeDays: 90, }); const [modified, setModified] = useState(false); const isInitialMount = useRef(true); const isFetching = useRef(false); useEffect(() => { if (isInitialMount.current) { fetchStatus(); isInitialMount.current = false; } }, []); const fetchStatus = useCallback(async () => { if (isFetching.current) return; try { setLoading(true); isFetching.current = true; const response = await api.get('/backup/auto/status'); if (response?.success) { const data = response.data; setStatus(data); // 解析 Cron 表达式获取小时和分钟 if (data.cronExpression) { const parts = data.cronExpression.split(' '); const minute = parseInt(parts[0]); const hour = parseInt(parts[1]); setSettings(prev => ({ ...prev, enabled: data.enabled || false, hour: isNaN(hour) ? 2 : hour, minute: isNaN(minute) ? 0 : minute, includeFiles: data.includeFiles !== undefined ? data.includeFiles : true, compress: data.compress !== undefined ? data.compress : true, maxCount: data.maxCount || 30, maxAgeDays: data.maxAgeDays || 90, })); } } } catch (error) { console.error('获取自动备份状态失败:', error); } finally { setLoading(false); isFetching.current = false; } }, []); const handleSave = async () => { try { setLoading(true); const response = await api.post('/backup/auto/settings', { enabled: settings.enabled, hour: settings.hour, minute: settings.minute, includeFiles: settings.includeFiles, compress: settings.compress, maxCount: settings.maxCount, maxAgeDays: settings.maxAgeDays, }); if (response?.success) { message.success('自动备份设置已保存'); setModified(false); // 不需要立即刷新,因为保存成功后状态已经是最新的 } } catch (error) { message.error('保存失败:' + (error.response?.data?.message || error.message)); } finally { setLoading(false); } }; const handleExecuteNow = async () => { Modal.confirm({ title: (
立即执行备份
), content: '确定要立即执行一次备份吗?这将在后台创建一个新的备份文件。', okText: '立即备份', cancelText: '取消', onOk: async () => { try { setLoading(true); const response = await api.post('/backup/auto/execute', { description: `手动触发 - ${new Date().toLocaleString('zh-CN')}`, includeFiles: settings.includeFiles, compress: settings.compress, }); if (response?.success) { message.success('备份执行成功!'); } } catch (error) { message.error('执行失败:' + (error.response?.data?.message || error.message)); } finally { setLoading(false); } }, }); }; const formatTime = (hour, minute) => { return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; }; const formatNextRun = (nextRun) => { if (!nextRun) return '未知'; return new Date(nextRun).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); }; const getStatusColor = () => { if (!status) return 'default'; if (status.enabled && status.isActive) return 'success'; if (status.enabled && !status.isActive) return 'warning'; return 'default'; }; const getStatusText = () => { if (!status) return '加载中...'; if (status.enabled && status.isActive) return '自动备份运行中'; if (status.enabled && !status.isActive) return '自动备份已启用但未运行'; return '自动备份已禁用'; }; // 状态卡片组件 const StatusCard = ({ icon, title, value, subtitle, gradient }) => (
{React.cloneElement(icon, { style: { fontSize: 120 } })}
{title}
{value}
{subtitle && {subtitle}}
); // 设置项组件 const SettingItem = ({ title, description, children, bordered = true }) => (
{title}
{description && ( {description} )}
{children}
); if (loading && !status) { return (
); } return (
{/* 页面头部 */}
<SettingOutlined style={{ marginRight: 12, verticalAlign: 'middle' }} /> 自动备份设置 配置定时自动备份,确保数据安全无忧
{/* 状态概览 */}
<ThunderboltOutlined style={{ marginRight: 8 }} /> 状态概览 } title="当前状态" value={getStatusText()} gradient={status?.enabled && status.isActive ? 'linear-gradient(135deg, #10b981 0%, #34d399 100%)' : 'linear-gradient(135deg, #6b7280 0%, #9ca3af 100%)'} /> } title="下次执行时间" value={formatNextRun(status?.nextRun)} gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)" /> } title="备份时间" value={formatTime(settings.hour, settings.minute)} gradient="linear-gradient(135deg, #10b981 0%, #34d399 100%)" /> } title="保留策略" value={`${settings.maxCount}个 / ${settings.maxAgeDays}天`} gradient="linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)" />
{/* 设置卡片 */} {/* 基本设置 */}
基本设置 } style={{ borderRadius: '20px', boxShadow: '0 4px 20px rgba(0, 0, 0, 0.05)', height: '100%', }} > { setSettings({ ...settings, enabled: checked }); setModified(true); }} size="default" checkedChildren="开启" unCheckedChildren="关闭" /> { setSettings({ ...settings, hour: value }); setModified(true); }} addonAfter="时" disabled={!settings.enabled} style={{ width: 100 }} /> { setSettings({ ...settings, minute: value }); setModified(true); }} addonAfter="分" disabled={!settings.enabled} style={{ width: 100 }} />
{/* 高级设置 */}
高级设置 } style={{ borderRadius: '20px', boxShadow: '0 4px 20px rgba(0, 0, 0, 0.05)', height: '100%', }} > { setSettings({ ...settings, includeFiles: checked }); setModified(true); }} disabled={!settings.enabled} checkedChildren="包含" unCheckedChildren="不包含" /> { setSettings({ ...settings, compress: checked }); setModified(true); }} disabled={!settings.enabled} checkedChildren="压缩" unCheckedChildren="不压缩" /> { setSettings({ ...settings, maxCount: value }); setModified(true); }} addonAfter="个" disabled={!settings.enabled} style={{ width: 120 }} /> { setSettings({ ...settings, maxAgeDays: value }); setModified(true); }} addonAfter="天" disabled={!settings.enabled} style={{ width: 120 }} />
{/* 提示信息 */}
最佳实践建议 } description={
备份时间:建议设置在凌晨 2-4 点业务低峰期,避免影响正常使用
保留策略:开发环境建议 7 个/7 天,生产环境建议 30 个/90 天
压缩备份:强烈建议开启,可显著减少磁盘空间占用
定期测试:每月至少手动执行一次备份,验证功能正常
} type="info" showIcon={false} style={{ borderRadius: '16px', border: '1px solid #dbeafe', background: 'linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%)', }} />
{/* 安全提示 */}
安全提示 } description="自动备份功能需要服务器持续运行。如果服务器重启,自动备份任务会自动恢复。请确保服务器时间设置正确。" type="warning" showIcon={false} style={{ borderRadius: '16px', border: '1px solid #fef3c7', background: 'linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%)', }} />
); }; export default AutoBackupSettings;