diff --git a/package.json b/package.json
index d19a4cc..c93ad26 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,6 @@
"react-dom": "^18.2.0",
"react-icons": "^5.4.0",
"redis": "^4.6.7",
- "sweetalert2": "^11.11.0",
"swiper": "^11.2.8",
"tailwind-merge": "^2.6.0",
"vidstack": "^0.6.15",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cd4c437..846452f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -83,9 +83,6 @@ importers:
redis:
specifier: ^4.6.7
version: 4.7.1
- sweetalert2:
- specifier: ^11.11.0
- version: 11.22.2
swiper:
specifier: ^11.2.8
version: 11.2.8
@@ -4639,9 +4636,6 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
- sweetalert2@11.22.2:
- resolution: {integrity: sha512-GFQGzw8ZXF23PO79WMAYXLl4zYmLiaKqYJwcp5eBF07wiI5BYPbZtKi2pcvVmfUQK+FqL1risJAMxugcPbGIyg==}
-
swiper@11.2.8:
resolution: {integrity: sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg==}
engines: {node: '>= 4.7.0'}
@@ -10404,8 +10398,6 @@ snapshots:
csso: 5.0.5
picocolors: 1.1.1
- sweetalert2@11.22.2: {}
-
swiper@11.2.8: {}
symbol-tree@3.2.4: {}
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 2d13d95..e7be48a 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -1,4 +1,4 @@
-/* eslint-disable @typescript-eslint/no-explicit-any, no-console, @typescript-eslint/no-non-null-assertion */
+/* eslint-disable @typescript-eslint/no-explicit-any, no-console, @typescript-eslint/no-non-null-assertion,react-hooks/exhaustive-deps */
'use client';
@@ -22,7 +22,10 @@ import {
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import {
+ AlertCircle,
+ AlertTriangle,
Check,
+ CheckCircle,
ChevronDown,
ChevronUp,
Database,
@@ -36,7 +39,6 @@ import {
import { GripVertical } from 'lucide-react';
import { Suspense, useCallback, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
-import Swal from 'sweetalert2';
import { AdminConfig, AdminConfigResult } from '@/lib/admin.types';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
@@ -44,19 +46,145 @@ import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
import DataMigration from '@/components/DataMigration';
import PageLayout from '@/components/PageLayout';
-// 统一弹窗方法(必须在首次使用前定义)
-const showError = (message: string) =>
- Swal.fire({ icon: 'error', title: '错误', text: message });
+// 通用弹窗组件
+interface AlertModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ type: 'success' | 'error' | 'warning';
+ title: string;
+ message?: string;
+ timer?: number;
+ showConfirm?: boolean;
+}
-const showSuccess = (message: string) =>
- Swal.fire({
- icon: 'success',
- title: '成功',
- text: message,
- timer: 2000,
- showConfirmButton: false,
+const AlertModal = ({
+ isOpen,
+ onClose,
+ type,
+ title,
+ message,
+ timer,
+ showConfirm = false
+}: 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(
+
+
+
+
+ {getIcon()}
+
+
+
+ {title}
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+ {showConfirm && (
+
+ )}
+
+
+
,
+ document.body
+ );
+};
+
+// 弹窗状态管理
+const useAlertModal = () => {
+ const [alertModal, setAlertModal] = useState<{
+ isOpen: boolean;
+ type: 'success' | 'error' | 'warning';
+ title: string;
+ message?: string;
+ timer?: number;
+ showConfirm?: boolean;
+ }>({
+ isOpen: false,
+ type: 'success',
+ title: '',
});
+ const showAlert = (config: Omit) => {
+ setAlertModal({ ...config, isOpen: true });
+ };
+
+ const hideAlert = () => {
+ setAlertModal(prev => ({ ...prev, isOpen: false }));
+ };
+
+ return { alertModal, showAlert, hideAlert };
+};
+
+// 统一弹窗方法(必须在首次使用前定义)
+const showError = (message: string, showAlert?: (config: any) => void) => {
+ if (showAlert) {
+ showAlert({ type: 'error', title: '错误', message, showConfirm: true });
+ } else {
+ console.error(message);
+ }
+};
+
+const showSuccess = (message: string, showAlert?: (config: any) => void) => {
+ if (showAlert) {
+ showAlert({ type: 'success', title: '成功', message, timer: 2000 });
+ } else {
+ console.log(message);
+ }
+};
+
// 新增站点配置类型
interface SiteConfig {
SiteName: string;
@@ -136,6 +264,7 @@ interface UserConfigProps {
}
const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
+ const { alertModal, showAlert, hideAlert } = useAlertModal();
const [showAddUserForm, setShowAddUserForm] = useState(false);
const [showChangePasswordForm, setShowChangePasswordForm] = useState(false);
const [showAddUserGroupForm, setShowAddUserGroupForm] = useState(false);
@@ -165,6 +294,20 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
tags?: string[];
} | null>(null);
const [selectedApis, setSelectedApis] = useState([]);
+ const [showConfigureUserGroupModal, setShowConfigureUserGroupModal] = useState(false);
+ const [selectedUserForGroup, setSelectedUserForGroup] = useState<{
+ username: string;
+ role: 'user' | 'admin' | 'owner';
+ tags?: string[];
+ } | null>(null);
+ const [selectedUserGroups, setSelectedUserGroups] = useState([]);
+ const [showDeleteUserGroupModal, setShowDeleteUserGroupModal] = useState(false);
+ const [deletingUserGroup, setDeletingUserGroup] = useState<{
+ name: string;
+ affectedUsers: Array<{ username: string; role: 'user' | 'admin' | 'owner' }>;
+ } | null>(null);
+ const [showDeleteUserModal, setShowDeleteUserModal] = useState(false);
+ const [deletingUser, setDeletingUser] = useState(null);
// 当前登录用户名
const currentUsername = getAuthInfoFromBrowserCookie()?.username || null;
@@ -205,9 +348,9 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
setShowEditUserGroupForm(false);
}
- showSuccess(action === 'add' ? '用户组添加成功' : action === 'edit' ? '用户组更新成功' : '用户组删除成功');
+ showSuccess(action === 'add' ? '用户组添加成功' : action === 'edit' ? '用户组更新成功' : '用户组删除成功', showAlert);
} catch (err) {
- showError(err instanceof Error ? err.message : '操作失败');
+ showError(err instanceof Error ? err.message : '操作失败', showAlert);
}
};
@@ -221,51 +364,29 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
handleUserGroupAction('edit', editingUserGroup.name, editingUserGroup.enabledApis);
};
- const handleDeleteUserGroup = async (groupName: string) => {
+ const handleDeleteUserGroup = (groupName: string) => {
// 计算会受影响的用户数量
const affectedUsers = config?.UserConfig?.Users?.filter(user =>
user.tags && user.tags.includes(groupName)
) || [];
- const affectedCount = affectedUsers.length;
- const affectedUserNames = affectedUsers.map(u => u.username).join(', ');
-
- const { isConfirmed } = await Swal.fire({
- title: '确认删除用户组',
- html: `
-
-
删除用户组 ${groupName} 将影响所有使用该组的用户,此操作不可恢复!
- ${affectedCount > 0 ? `
-
-
- ⚠️ 将影响 ${affectedCount} 个用户:
-
-
- ${affectedUserNames}
-
-
- 这些用户的用户组将被自动移除
-
-
- ` : `
-
- `}
-
- `,
- icon: 'warning',
- showCancelButton: true,
- confirmButtonText: '确认删除',
- cancelButtonText: '取消',
- confirmButtonColor: '#dc2626',
+ setDeletingUserGroup({
+ name: groupName,
+ affectedUsers: affectedUsers.map(u => ({ username: u.username, role: u.role }))
});
+ setShowDeleteUserGroupModal(true);
+ };
- if (!isConfirmed) return;
+ const handleConfirmDeleteUserGroup = async () => {
+ if (!deletingUserGroup) return;
- await handleUserGroupAction('delete', groupName);
+ try {
+ await handleUserGroupAction('delete', deletingUserGroup.name);
+ setShowDeleteUserGroupModal(false);
+ setDeletingUserGroup(null);
+ } catch (err) {
+ // 错误处理已在 handleUserGroupAction 中处理
+ }
};
const handleStartEditUserGroup = (group: { name: string; enabledApis: string[] }) => {
@@ -293,9 +414,9 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
}
await refreshConfig();
- showSuccess('用户组分配成功');
+ showSuccess('用户组分配成功', showAlert);
} catch (err) {
- showError(err instanceof Error ? err.message : '操作失败');
+ showError(err instanceof Error ? err.message : '操作失败', showAlert);
}
};
@@ -339,20 +460,9 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
setShowAddUserForm(false); // 关闭添加用户表单
};
- const handleDeleteUser = async (username: string) => {
- const { isConfirmed } = await Swal.fire({
- title: '确认删除用户',
- text: `删除用户 ${username} 将同时删除其搜索历史、播放记录和收藏夹,此操作不可恢复!`,
- icon: 'warning',
- showCancelButton: true,
- confirmButtonText: '确认删除',
- cancelButtonText: '取消',
- confirmButtonColor: '#dc2626',
- });
-
- if (!isConfirmed) return;
-
- await handleUserAction('deleteUser', username);
+ const handleDeleteUser = (username: string) => {
+ setDeletingUser(username);
+ setShowDeleteUserModal(true);
};
const handleConfigureUserApis = (user: {
@@ -365,6 +475,29 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
setShowConfigureApisModal(true);
};
+ const handleConfigureUserGroup = (user: {
+ username: string;
+ role: 'user' | 'admin' | 'owner';
+ tags?: string[];
+ }) => {
+ setSelectedUserForGroup(user);
+ setSelectedUserGroups(user.tags || []);
+ setShowConfigureUserGroupModal(true);
+ };
+
+ const handleSaveUserGroups = async () => {
+ if (!selectedUserForGroup) return;
+
+ try {
+ await handleAssignUserGroup(selectedUserForGroup.username, selectedUserGroups);
+ setShowConfigureUserGroupModal(false);
+ setSelectedUserForGroup(null);
+ setSelectedUserGroups([]);
+ } catch (err) {
+ // 错误处理已在 handleAssignUserGroup 中处理
+ }
+ };
+
// 提取URL域名的辅助函数
const extractDomain = (url: string): string => {
try {
@@ -401,7 +534,7 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
setSelectedUser(null);
setSelectedApis([]);
} catch (err) {
- showError(err instanceof Error ? err.message : '操作失败');
+ showError(err instanceof Error ? err.message : '操作失败', showAlert);
}
};
@@ -439,7 +572,19 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
// 成功后刷新配置(无需整页刷新)
await refreshConfig();
} catch (err) {
- showError(err instanceof Error ? err.message : '操作失败');
+ showError(err instanceof Error ? err.message : '操作失败', showAlert);
+ }
+ };
+
+ const handleConfirmDeleteUser = async () => {
+ if (!deletingUser) return;
+
+ try {
+ await handleUserAction('deleteUser', deletingUser);
+ setShowDeleteUserModal(false);
+ setDeletingUser(null);
+ } catch (err) {
+ // 错误处理已在 handleUserAction 中处理
}
};
@@ -795,31 +940,7 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
(user.role === 'user' ||
user.username === currentUsername))) && (
)}
+
+ {/* 通用弹窗组件 */}
+
);
};
@@ -1628,6 +2023,7 @@ const CategoryConfig = ({
config: AdminConfig | null;
refreshConfig: () => Promise;
}) => {
+ const { alertModal, showAlert, hideAlert } = useAlertModal();
const [categories, setCategories] = useState([]);
const [showAddForm, setShowAddForm] = useState(false);
const [orderChanged, setOrderChanged] = useState(false);
@@ -1680,7 +2076,7 @@ const CategoryConfig = ({
// 成功后刷新配置
await refreshConfig();
} catch (err) {
- showError(err instanceof Error ? err.message : '操作失败');
+ showError(err instanceof Error ? err.message : '操作失败', showAlert);
throw err; // 向上抛出方便调用处判断
}
};
@@ -1952,12 +2348,24 @@ const CategoryConfig = ({
)}
+
+ {/* 通用弹窗组件 */}
+
);
};
// 新增配置文件组件
const ConfigFileComponent = ({ config, refreshConfig }: { config: AdminConfig | null; refreshConfig: () => Promise }) => {
+ const { alertModal, showAlert, hideAlert } = useAlertModal();
const [configContent, setConfigContent] = useState('');
const [saving, setSaving] = useState(false);
const [subscriptionUrl, setSubscriptionUrl] = useState('');
@@ -1983,7 +2391,7 @@ const ConfigFileComponent = ({ config, refreshConfig }: { config: AdminConfig |
// 拉取订阅配置
const handleFetchConfig = async () => {
if (!subscriptionUrl.trim()) {
- showError('请输入订阅URL');
+ showError('请输入订阅URL', showAlert);
return;
}
@@ -2006,12 +2414,12 @@ const ConfigFileComponent = ({ config, refreshConfig }: { config: AdminConfig |
// 更新本地配置的最后检查时间
const currentTime = new Date().toISOString();
setLastCheckTime(currentTime);
- showSuccess('配置拉取成功');
+ showSuccess('配置拉取成功', showAlert);
} else {
- showError('拉取失败:未获取到配置内容');
+ showError('拉取失败:未获取到配置内容', showAlert);
}
} catch (err) {
- showError(err instanceof Error ? err.message : '拉取失败');
+ showError(err instanceof Error ? err.message : '拉取失败', showAlert);
} finally {
setFetching(false);
}
@@ -2037,10 +2445,10 @@ const ConfigFileComponent = ({ config, refreshConfig }: { config: AdminConfig |
throw new Error(data.error || `保存失败: ${resp.status}`);
}
- showSuccess('配置文件保存成功');
+ showSuccess('配置文件保存成功', showAlert);
await refreshConfig();
} catch (err) {
- showError(err instanceof Error ? err.message : '保存失败');
+ showError(err instanceof Error ? err.message : '保存失败', showAlert);
} finally {
setSaving(false);
}
@@ -2173,12 +2581,24 @@ const ConfigFileComponent = ({ config, refreshConfig }: { config: AdminConfig |
+
+ {/* 通用弹窗组件 */}
+
);
};
// 新增站点配置组件
const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig | null; refreshConfig: () => Promise }) => {
+ const { alertModal, showAlert, hideAlert } = useAlertModal();
const [siteSettings, setSiteSettings] = useState({
SiteName: '',
Announcement: '',
@@ -2331,10 +2751,10 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig |
throw new Error(data.error || `保存失败: ${resp.status}`);
}
- showSuccess('保存成功, 请刷新页面');
+ showSuccess('保存成功, 请刷新页面', showAlert);
await refreshConfig();
} catch (err) {
- showError(err instanceof Error ? err.message : '保存失败');
+ showError(err instanceof Error ? err.message : '保存失败', showAlert);
} finally {
setSaving(false);
}
@@ -2727,15 +3147,28 @@ const SiteConfigComponent = ({ config, refreshConfig }: { config: AdminConfig |
{saving ? '保存中…' : '保存'}
+
+ {/* 通用弹窗组件 */}
+
);
};
function AdminPageClient() {
+ const { alertModal, showAlert, hideAlert } = useAlertModal();
const [config, setConfig] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [role, setRole] = useState<'owner' | 'admin' | null>(null);
+ const [showResetConfigModal, setShowResetConfigModal] = useState(false);
const [expandedTabs, setExpandedTabs] = useState<{ [key: string]: boolean }>({
userConfig: false,
videoSource: false,
@@ -2765,7 +3198,7 @@ function AdminPageClient() {
setRole(data.Role);
} catch (err) {
const msg = err instanceof Error ? err.message : '获取配置失败';
- showError(msg);
+ showError(msg, showAlert);
setError(msg);
} finally {
if (showLoading) {
@@ -2788,26 +3221,21 @@ function AdminPageClient() {
};
// 新增: 重置配置处理函数
- const handleResetConfig = async () => {
- const { isConfirmed } = await Swal.fire({
- title: '确认重置配置',
- text: '此操作将重置用户封禁和管理员设置、自定义视频源,站点配置将重置为默认值,是否继续?',
- icon: 'warning',
- showCancelButton: true,
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- });
- if (!isConfirmed) return;
+ const handleResetConfig = () => {
+ setShowResetConfigModal(true);
+ };
+ const handleConfirmResetConfig = async () => {
try {
const response = await fetch(`/api/admin/reset`);
if (!response.ok) {
throw new Error(`重置失败: ${response.status}`);
}
- showSuccess('重置成功,请刷新页面!');
+ showSuccess('重置成功,请刷新页面!', showAlert);
await fetchConfig();
+ setShowResetConfigModal(false);
} catch (err) {
- showError(err instanceof Error ? err.message : '重置失败');
+ showError(err instanceof Error ? err.message : '重置失败', showAlert);
}
};
@@ -2834,7 +3262,7 @@ function AdminPageClient() {
}
if (error) {
- // 错误已通过 SweetAlert2 展示,此处直接返回空
+ // 错误已通过弹窗展示,此处直接返回空
return null;
}
@@ -2952,6 +3380,73 @@ function AdminPageClient() {
+
+ {/* 通用弹窗组件 */}
+
+
+ {/* 重置配置确认弹窗 */}
+ {showResetConfigModal && createPortal(
+
+
+
+
+
+ 确认重置配置
+
+
setShowResetConfigModal(false)}
+ className='text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors'
+ >
+
+
+
+
+
+
+
+
+ 此操作将重置用户封禁和管理员设置、自定义视频源,站点配置将重置为默认值,是否继续?
+
+
+
+
+ {/* 操作按钮 */}
+
+ setShowResetConfigModal(false)}
+ className='px-6 py-2.5 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors'
+ >
+ 取消
+
+
+ 确认重置
+
+
+
+
+
,
+ document.body
+ )}
);
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index ace6098..2a6582d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -4,7 +4,6 @@ import type { Metadata, Viewport } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
-import 'sweetalert2/dist/sweetalert2.min.css';
import { getConfig } from '@/lib/config';
diff --git a/src/components/DataMigration.tsx b/src/components/DataMigration.tsx
index 2c40256..99e9b4f 100644
--- a/src/components/DataMigration.tsx
+++ b/src/components/DataMigration.tsx
@@ -1,30 +1,181 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
-import { AlertTriangle, Download, FileCheck, Lock, Upload } from 'lucide-react';
-import { useRef, useState } from 'react';
-import Swal from 'sweetalert2';
+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(
+
+
+
+
+ {getIcon()}
+
+
+
+ {title}
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+ {html && (
+
+ )}
+
+
+ {showConfirm && onConfirm ? (
+ <>
+
+ 取消
+
+ {
+ onConfirm();
+ onClose();
+ }}
+ className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
+ >
+ {confirmText}
+
+ >
+ ) : (
+
+ 确定
+
+ )}
+
+
+
+
,
+ 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()) {
- Swal.fire({
- icon: 'error',
+ showAlert({
+ type: 'error',
title: '错误',
- text: '请输入加密密码',
- returnFocus: false,
+ message: '请输入加密密码',
});
return;
}
@@ -67,22 +218,19 @@ const DataMigration = ({ onRefreshConfig }: DataMigrationProps) => {
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
- Swal.fire({
- icon: 'success',
+ showAlert({
+ type: 'success',
title: '导出成功',
- text: '数据已成功导出,请妥善保管备份文件和密码',
+ message: '数据已成功导出,请妥善保管备份文件和密码',
timer: 3000,
- showConfirmButton: false,
- returnFocus: false,
});
setExportPassword('');
} catch (error) {
- Swal.fire({
- icon: 'error',
+ showAlert({
+ type: 'error',
title: '导出失败',
- text: error instanceof Error ? error.message : '导出过程中发生错误',
- returnFocus: false,
+ message: error instanceof Error ? error.message : '导出过程中发生错误',
});
} finally {
setIsExporting(false);
@@ -100,21 +248,19 @@ const DataMigration = ({ onRefreshConfig }: DataMigrationProps) => {
// 导入数据
const handleImport = async () => {
if (!selectedFile) {
- Swal.fire({
- icon: 'error',
+ showAlert({
+ type: 'error',
title: '错误',
- text: '请选择备份文件',
- returnFocus: false,
+ message: '请选择备份文件',
});
return;
}
if (!importPassword.trim()) {
- Swal.fire({
- icon: 'error',
+ showAlert({
+ type: 'error',
title: '错误',
- text: '请输入解密密码',
- returnFocus: false,
+ message: '请输入解密密码',
});
return;
}
@@ -137,8 +283,8 @@ const DataMigration = ({ onRefreshConfig }: DataMigrationProps) => {
throw new Error(result.error || `导入失败: ${response.status}`);
}
- await Swal.fire({
- icon: 'success',
+ showAlert({
+ type: 'success',
title: '导入成功',
html: `
@@ -149,30 +295,30 @@ const DataMigration = ({ onRefreshConfig }: DataMigrationProps) => {
请刷新页面以查看最新数据。
`,
- confirmButtonText: '刷新页面',
- returnFocus: false,
+ confirmText: '刷新页面',
+ showConfirm: true,
+ onConfirm: async () => {
+ // 清理状态
+ setSelectedFile(null);
+ setImportPassword('');
+ if (fileInputRef.current) {
+ fileInputRef.current.value = '';
+ }
+
+ // 刷新配置
+ if (onRefreshConfig) {
+ await onRefreshConfig();
+ }
+
+ // 刷新页面
+ window.location.reload();
+ },
});
-
- // 清理状态
- setSelectedFile(null);
- setImportPassword('');
- if (fileInputRef.current) {
- fileInputRef.current.value = '';
- }
-
- // 刷新配置
- if (onRefreshConfig) {
- await onRefreshConfig();
- }
-
- // 刷新页面
- window.location.reload();
} catch (error) {
- Swal.fire({
- icon: 'error',
+ showAlert({
+ type: 'error',
title: '导入失败',
- text: error instanceof Error ? error.message : '导入过程中发生错误',
- returnFocus: false,
+ message: error instanceof Error ? error.message : '导入过程中发生错误',
});
} finally {
setIsImporting(false);
@@ -180,163 +326,179 @@ const DataMigration = ({ onRefreshConfig }: DataMigrationProps) => {
};
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}
- />
-
- 导入时需要使用相同密码
-
-
-
- {/* 备份内容列表 */}
-
-
备份内容:
-
-
• 管理配置
-
• 用户数据
-
• 播放记录
-
• 收藏夹
-
-
-
-
- {/* 导出按钮 */}
-
- {isExporting ? (
-
- ) : (
-
-
- 导出数据
-
- )}
-
-
+ <>
+
+ {/* 简洁警告提示 */}
+
+
+
+ 数据迁移操作请谨慎,确保已备份重要数据
+
- {/* 数据导入 */}
-
-
-
-
+ {/* 主要操作区域 - 响应式布局 */}
+
+ {/* 数据导出 */}
+
+
-
-
数据导入
-
⚠️ 将清空现有数据
+
+
+
+ {/* 密码输入 */}
+
+
+
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}
+ />
+
+ 导入时需要使用相同密码
+
+
+
+ {/* 备份内容列表 */}
+
+
备份内容:
+
+
• 管理配置
+
• 用户数据
+
• 播放记录
+
• 收藏夹
+
+
+
+
+ {/* 导出按钮 */}
+
+ {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}
- />
+
数据导入
+
⚠️ 将清空现有数据
- {/* 导入按钮 */}
-
- {isImporting ? (
-
-
- 导入中...
+
+
+ {/* 文件选择 */}
+
+
+
- ) : (
-
-
- 导入数据
+
+ {/* 密码输入 */}
+
+
+ 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}
+ />
- )}
-
+
+
+ {/* 导入按钮 */}
+
+ {isImporting ? (
+
+ ) : (
+
+
+ 导入数据
+
+ )}
+
+
-
+
+ {/* 弹窗组件 */}
+
+ >
);
};