/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client'; import { AlertCircle, AlertTriangle, CheckCircle, Download, FileCheck, Lock, Upload } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; interface DataMigrationProps { onRefreshConfig?: () => Promise; } interface AlertModalProps { isOpen: boolean; onClose: () => void; type: 'success' | 'error' | 'warning'; title: string; message?: string; html?: string; confirmText?: string; onConfirm?: () => void; showConfirm?: boolean; timer?: number; } const AlertModal = ({ isOpen, onClose, type, title, message, html, confirmText = '确定', onConfirm, showConfirm = false, timer }: AlertModalProps) => { const [isVisible, setIsVisible] = useState(false); // 控制动画状态 useEffect(() => { if (isOpen) { setIsVisible(true); if (timer) { setTimeout(() => { onClose(); }, timer); } } else { setIsVisible(false); } }, [isOpen, timer, onClose]); if (!isOpen) return null; const getIcon = () => { switch (type) { case 'success': return ; case 'error': return ; case 'warning': return ; default: return null; } }; const getBgColor = () => { switch (type) { case 'success': return 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800'; case 'error': return 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800'; case 'warning': return 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800'; default: return 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800'; } }; return createPortal(
e.stopPropagation()}>
{getIcon()}

{title}

{message && (

{message}

)} {html && (
)}
{showConfirm && onConfirm ? ( <> ) : ( )}
, document.body ); }; const DataMigration = ({ onRefreshConfig }: DataMigrationProps) => { const [exportPassword, setExportPassword] = useState(''); const [importPassword, setImportPassword] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const [isExporting, setIsExporting] = useState(false); const [isImporting, setIsImporting] = useState(false); const [alertModal, setAlertModal] = useState<{ isOpen: boolean; type: 'success' | 'error' | 'warning'; title: string; message?: string; html?: string; confirmText?: string; onConfirm?: () => void; showConfirm?: boolean; timer?: number; }>({ isOpen: false, type: 'success', title: '', }); const fileInputRef = useRef(null); const showAlert = (config: Omit) => { setAlertModal({ ...config, isOpen: true }); }; const hideAlert = () => { setAlertModal(prev => ({ ...prev, isOpen: false })); }; // 导出数据 const handleExport = async () => { if (!exportPassword.trim()) { showAlert({ type: 'error', title: '错误', message: '请输入加密密码', showConfirm: true }); return; } try { setIsExporting(true); const response = await fetch('/api/admin/data_migration/export', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ password: exportPassword, }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || `导出失败: ${response.status}`); } // 获取文件名 const contentDisposition = response.headers.get('content-disposition'); const filenameMatch = contentDisposition?.match(/filename="(.+)"/); const filename = filenameMatch?.[1] || 'OrangeTV-backup.dat'; // 下载文件 const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; a.style.position = 'fixed'; a.style.top = '0'; a.style.left = '0'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); showAlert({ type: 'success', title: '导出成功', message: '数据已成功导出,请妥善保管备份文件和密码', timer: 3000, }); setExportPassword(''); } catch (error) { showAlert({ type: 'error', title: '导出失败', message: error instanceof Error ? error.message : '导出过程中发生错误', showConfirm: true, }); } finally { setIsExporting(false); } }; // 文件选择处理 const handleFileSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { setSelectedFile(file); } }; // 导入数据 const handleImport = async () => { if (!selectedFile) { showAlert({ type: 'error', title: '错误', message: '请选择备份文件', showConfirm: true }); return; } if (!importPassword.trim()) { showAlert({ type: 'error', title: '错误', message: '请输入解密密码', showConfirm: true }); return; } try { setIsImporting(true); const formData = new FormData(); formData.append('file', selectedFile); formData.append('password', importPassword); const response = await fetch('/api/admin/data_migration/import', { method: 'POST', body: formData, }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || `导入失败: ${response.status}`); } showAlert({ type: 'success', title: '导入成功', html: `

导入完成!

导入的用户数量: ${result.importedUsers}

备份时间: ${new Date(result.timestamp).toLocaleString('zh-CN')}

服务器版本: ${result.serverVersion || '未知版本'}

请刷新页面以查看最新数据。

`, confirmText: '刷新页面', showConfirm: true, onConfirm: async () => { // 清理状态 setSelectedFile(null); setImportPassword(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } // 刷新配置 if (onRefreshConfig) { await onRefreshConfig(); } // 刷新页面 window.location.reload(); }, }); } catch (error) { showAlert({ type: 'error', title: '导入失败', message: error instanceof Error ? error.message : '导入过程中发生错误', showConfirm: true }); } finally { setIsImporting(false); } }; return ( <>
{/* 简洁警告提示 */}

数据迁移操作请谨慎,确保已备份重要数据

{/* 主要操作区域 - 响应式布局 */}
{/* 数据导出 */}

数据导出

创建加密备份文件

{/* 密码输入 */}
setExportPassword(e.target.value)} placeholder="设置强密码保护备份文件" className="w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors" disabled={isExporting} />

导入时需要使用相同密码

{/* 备份内容列表 */}

备份内容:

• 管理配置
• 用户数据
• 播放记录
• 收藏夹
{/* 导出按钮 */}
{/* 数据导入 */}

数据导入

⚠️ 将清空现有数据

{/* 文件选择 */}
{/* 密码输入 */}
setImportPassword(e.target.value)} placeholder="输入导出时的加密密码" className="w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-colors" disabled={isImporting} />
{/* 导入按钮 */}
{/* 弹窗组件 */} ); }; export default DataMigration;