diff --git a/README.md b/README.md index 38f6d25..85ecc7f 100644 --- a/README.md +++ b/README.md @@ -1031,6 +1031,46 @@ KatelyaTV 支持标准的苹果 CMS V10 API 格式。 站长或管理员访问 `/admin` 即可进行管理员配置 +### 🔧 视频源配置管理 + +管理员界面提供了完整的视频源配置管理功能: + +#### 📤 导出配置 + +- **一键导出**:点击"📤 导出配置"按钮,系统会自动生成符合标准格式的 `config.json` 文件 +- **自动格式化**:导出的配置文件包含所有已启用的视频源,格式完全符合项目要求 +- **本地保存**:配置文件会自动下载到浏览器的下载文件夹,文件名包含日期标记 + +#### 📂 导入配置 + +- **文件选择**:点击"📂 导入配置"按钮,选择本地的 `.json` 配置文件 +- **格式验证**:系统会自动验证配置文件格式,确保数据正确性 +- **批量导入**:支持一次性导入多个视频源,显示详细的导入结果 +- **错误提示**:如果导入过程中出现错误,会显示具体的错误信息 + +#### 📋 支持的配置格式 + +```json +{ + "cache_time": 7200, + "api_site": { + "source_key": { + "api": "https://example.com/api.php/provide/vod", + "name": "视频源名称", + "detail": "https://example.com" // 可选 + } + } +} +``` + +#### ✨ 其他管理功能 + +- **拖拽排序**:支持通过拖拽调整视频源的优先级顺序 +- **启用/禁用**:可以临时禁用某个视频源而不删除配置 +- **实时生效**:所有配置修改都会立即生效,无需重启服务 + +> **💡 提示**:导入的配置会永久保存在数据库中,不会因为浏览器刷新而丢失。这比直接修改 `config.json` 文件更加可靠和方便。 + ## 📱 AndroidTV 使用 目前该项目可以配合 [OrionTV](https://github.com/zimplexing/OrionTV) 在 Android TV 上使用,可以直接作为 OrionTV 后端 diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 8e7ecf3..10bdca7 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -721,6 +721,154 @@ const VideoSourceConfig = ({ }); }; + // 导出配置 + const handleExportConfig = () => { + try { + // 构建符合要求的配置格式 + const exportConfig = { + cache_time: config?.SiteConfig?.SiteInterfaceCacheTime || 7200, + api_site: {} as Record + }; + + // 将视频源转换为config.json格式 + sources.forEach(source => { + if (!source.disabled) { + exportConfig.api_site[source.key] = { + api: source.api, + name: source.name, + ...(source.detail && { detail: source.detail }) + }; + } + }); + + // 生成JSON文件并下载 + const dataStr = JSON.stringify(exportConfig, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + + const link = document.createElement('a'); + link.href = url; + link.download = `config_${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + showSuccess('配置文件已导出到下载文件夹'); + } catch (error) { + showError('导出失败: ' + (error instanceof Error ? error.message : '未知错误')); + } + }; + + // 导入配置 + const handleImportConfig = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + // 检查文件类型 + if (!file.name.toLowerCase().endsWith('.json')) { + showError('请选择JSON文件'); + return; + } + + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const content = e.target?.result as string; + const importConfig = JSON.parse(content); + + // 验证配置格式 + if (!importConfig.api_site || typeof importConfig.api_site !== 'object') { + showError('配置文件格式错误:缺少 api_site 字段'); + return; + } + + // 确认导入 + const result = await Swal.fire({ + title: '确认导入', + text: `检测到 ${Object.keys(importConfig.api_site).length} 个视频源,是否继续导入?`, + icon: 'question', + showCancelButton: true, + confirmButtonText: '确认导入', + cancelButtonText: '取消', + confirmButtonColor: '#059669', + cancelButtonColor: '#6b7280' + }); + + if (!result.isConfirmed) return; + + // 批量导入视频源 + let successCount = 0; + let errorCount = 0; + const errors: string[] = []; + + for (const [key, source] of Object.entries(importConfig.api_site)) { + try { + // 类型检查和验证 + if (!source || typeof source !== 'object' || Array.isArray(source)) { + throw new Error(`${key}: 无效的配置对象`); + } + + const sourceObj = source as { api?: string; name?: string; detail?: string }; + + if (!sourceObj.api || !sourceObj.name) { + throw new Error(`${key}: 缺少必要字段 api 或 name`); + } + + await callSourceApi({ + action: 'add', + key: key, + name: sourceObj.name, + api: sourceObj.api, + detail: sourceObj.detail || '' + }); + successCount++; + } catch (error) { + errorCount++; + errors.push(`${key}: ${error instanceof Error ? error.message : '未知错误'}`); + } + } + + // 显示导入结果 + if (errorCount === 0) { + showSuccess(`成功导入 ${successCount} 个视频源`); + } else { + await Swal.fire({ + title: '导入完成', + html: ` +
+

✅ 成功导入: ${successCount} 个

+

❌ 导入失败: ${errorCount} 个

+ ${errors.length > 0 ? ` +
+ 查看错误详情 +
+ ${errors.map(err => `
${err}
`).join('')} +
+
+ ` : ''} +
+ `, + icon: successCount > 0 ? 'warning' : 'error', + confirmButtonText: '确定' + }); + } + + } catch (error) { + showError('配置文件解析失败: ' + (error instanceof Error ? error.message : '文件格式错误')); + } + }; + + reader.onerror = () => { + showError('文件读取失败'); + }; + + reader.readAsText(file); + + // 清空input,允许重复选择同一文件 + event.target.value = ''; + }; + const handleDragEnd = (event: any) => { const { active, over } = event; if (!over || active.id === over.id) return; @@ -829,16 +977,40 @@ const VideoSourceConfig = ({ return (
{/* 添加视频源表单 */} -
+

视频源列表

- +
+ {/* 导入按钮 */} + + + {/* 导出按钮 */} + + + {/* 添加视频源按钮 */} + +
{showAddForm && ( diff --git a/test-config.json b/test-config.json new file mode 100644 index 0000000..786ffca --- /dev/null +++ b/test-config.json @@ -0,0 +1,14 @@ +{ + "cache_time": 7200, + "api_site": { + "test_source": { + "api": "https://test.example.com/api.php/provide/vod", + "name": "测试视频源", + "detail": "https://test.example.com" + }, + "another_test": { + "api": "https://another.example.com/api.php/provide/vod", + "name": "另一个测试源" + } + } +}