diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 10bdca7..da8ebbe 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -626,6 +626,8 @@ const VideoSourceConfig = ({ const [sources, setSources] = useState([]); const [showAddForm, setShowAddForm] = useState(false); const [orderChanged, setOrderChanged] = useState(false); + const [batchMode, setBatchMode] = useState(false); + const [selectedSources, setSelectedSources] = useState>(new Set()); const [newSource, setNewSource] = useState({ name: '', key: '', @@ -721,6 +723,105 @@ const VideoSourceConfig = ({ }); }; + // 批量操作相关函数 + const handleToggleBatchMode = () => { + setBatchMode(!batchMode); + setSelectedSources(new Set()); // 切换模式时清空选择 + }; + + const handleSelectSource = (key: string, checked: boolean) => { + const newSelected = new Set(selectedSources); + if (checked) { + newSelected.add(key); + } else { + newSelected.delete(key); + } + setSelectedSources(newSelected); + }; + + const handleSelectAll = (checked: boolean) => { + if (checked) { + // 只选择可删除的视频源(from !== 'config') + const deletableSources = sources.filter(source => source.from !== 'config'); + setSelectedSources(new Set(deletableSources.map(source => source.key))); + } else { + setSelectedSources(new Set()); + } + }; + + const handleBatchDelete = async () => { + if (selectedSources.size === 0) { + showError('请先选择要删除的视频源'); + return; + } + + const selectedArray = Array.from(selectedSources); + const result = await Swal.fire({ + title: '确认批量删除', + text: `即将删除 ${selectedArray.length} 个视频源,此操作不可撤销!`, + icon: 'warning', + showCancelButton: true, + confirmButtonText: '确认删除', + cancelButtonText: '取消', + confirmButtonColor: '#ef4444', + cancelButtonColor: '#6b7280' + }); + + if (!result.isConfirmed) return; + + // 批量删除 + let successCount = 0; + let errorCount = 0; + const errors: string[] = []; + + for (const key of selectedArray) { + try { + await callSourceApi({ action: 'delete', key }); + successCount++; + } catch (error) { + errorCount++; + const sourceName = sources.find(s => s.key === key)?.name || key; + errors.push(`${sourceName}: ${error instanceof Error ? error.message : '删除失败'}`); + } + } + + // 显示删除结果 + if (errorCount === 0) { + showSuccess(`成功删除 ${successCount} 个视频源`); + setSelectedSources(new Set()); // 清空选择 + setBatchMode(false); // 退出批量模式 + } else { + await Swal.fire({ + title: '删除完成', + html: ` +
+

✅ 成功删除: ${successCount} 个

+

❌ 删除失败: ${errorCount} 个

+ ${errors.length > 0 ? ` +
+ 查看错误详情 +
+ ${errors.map(err => `
${err}
`).join('')} +
+
+ ` : ''} +
+ `, + icon: successCount > 0 ? 'warning' : 'error', + confirmButtonText: '确定' + }); + + // 清空已成功删除的选择项 + const failedKeys = new Set( + errors.map(err => { + const keyMatch = err.split(':')[0]; + return sources.find(s => s.name === keyMatch)?.key; + }).filter((key): key is string => Boolean(key)) + ); + setSelectedSources(failedKeys); + } + }; + // 导出配置 const handleExportConfig = () => { try { @@ -905,6 +1006,7 @@ const VideoSourceConfig = ({ style={style} className='hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors select-none' > + {/* 拖拽手柄 */} + + {/* 批量选择复选框 */} + {batchMode && ( + + handleSelectSource(source.key, e.target.checked)} + disabled={source.from === 'config'} + className='w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 disabled:opacity-50' + /> + + )} {source.name} @@ -976,40 +1091,79 @@ const VideoSourceConfig = ({ return (
- {/* 添加视频源表单 */} -
+ {/* 视频源管理工具栏 */} +

视频源列表

+
- {/* 导入按钮 */} - - - {/* 导出按钮 */} - - - {/* 添加视频源按钮 */} - + {/* 批量操作区域 */} + {!batchMode ? ( + <> + {/* 普通模式按钮 */} + + + {/* 导入导出按钮 */} +
+ + + +
+ + {/* 添加视频源按钮 */} + + + ) : ( + <> + {/* 批量模式按钮 */} + + +
+ + 已选 {selectedSources.size} 个 + + + +
+ + )}
@@ -1070,7 +1224,21 @@ const VideoSourceConfig = ({ + {/* 拖拽手柄列 */} + )} +
+ + {/* 批量选择列 */} + {batchMode && ( + + 0 && selectedSources.size === sources.filter(s => s.from !== 'config').length} + onChange={(e) => handleSelectAll(e.target.checked)} + className='w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600' + /> + 名称