feat: Add TVBox configuration support and management interface

- Introduced a new configuration page for TVBox with JSON and Base64 format options.
- Updated API endpoints for TVBox configuration retrieval.
- Enhanced the sidebar navigation to link to the new configuration page.
- Improved error handling and user feedback for configuration copying.
- Added detailed usage instructions and feature descriptions in the documentation.
- Fixed issues related to deployment and static generation.
This commit is contained in:
katelya
2025-09-03 19:54:58 +08:00
parent 2294f1b066
commit 1ca36f7454
7 changed files with 192 additions and 245 deletions
+124
View File
@@ -0,0 +1,124 @@
'use client';
import { useCallback, useState } from 'react';
export const dynamic = 'force-dynamic';
export default function ConfigPage() {
const [copied, setCopied] = useState(false);
const [format, setFormat] = useState<'json' | 'base64'>('json');
const getConfigUrl = useCallback(() => {
if (typeof window === 'undefined') return '';
const baseUrl = window.location.origin;
return `${baseUrl}/api/tvbox?format=${format}`;
}, [format]);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(getConfigUrl());
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Copy failed silently
}
};
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-8">
TVBox
</h1>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
</h2>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
</label>
<select
value={format}
onChange={(e) => setFormat(e.target.value as 'json' | 'base64')}
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="json">JSON </option>
<option value="base64">Base64 </option>
</select>
</div>
<div className="flex items-center space-x-2">
<input
type="text"
readOnly
value={getConfigUrl()}
className="flex-1 p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
/>
<button
onClick={handleCopy}
className={`px-4 py-3 rounded-md font-medium transition-colors ${
copied
? 'bg-green-500 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
>
{copied ? '已复制' : '复制'}
</button>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
使
</h2>
<div className="space-y-4 text-gray-700 dark:text-gray-300">
<div>
<h3 className="font-semibold text-lg mb-2">1. </h3>
<p> JSON Base64 </p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">2. TVBox</h3>
<p> TVBox </p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">3. 使</h3>
<p> TVBox </p>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="space-y-2">
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li> TVBox</li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
+12 -230
View File
@@ -1,242 +1,24 @@
'use client';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import PageLayout from '@/components/PageLayout';
export const dynamic = 'force-dynamic';
export default function TVBoxPage() {
const [baseUrl, setBaseUrl] = useState('');
const [copySuccess, setCopySuccess] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
// 获取当前域名
setBaseUrl(window.location.origin);
}, []);
const handleCopy = async (text: string, type: string) => {
try {
await navigator.clipboard.writeText(text);
setCopySuccess(type);
setTimeout(() => setCopySuccess(null), 2000);
} catch (err) {
// 降级方案:使用document.execCommand
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
setCopySuccess(type);
setTimeout(() => setCopySuccess(null), 2000);
}
};
const configs = [
{
name: 'TVBox JSON配置',
description: '直接返回JSON格式的配置文件,适用于支持在线配置的TVBox应用',
url: `${baseUrl}/api/tvbox`,
type: 'json'
},
{
name: 'TVBox Base64配置',
description: '返回Base64编码的配置文件,适用于大部分TVBox应用',
url: `${baseUrl}/api/tvbox?format=txt`,
type: 'base64'
}
];
// 重定向到新的配置页面
router.replace('/config');
}, [router]);
return (
<PageLayout>
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
{/* 页面标题 */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
📺 TVBox配置接口
</h1>
<p className="text-lg text-gray-600 dark:text-gray-300">
KatelyaTV的视频源导入到TVBox应用中使用
</p>
</div>
{/* 功能介绍 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
🎯
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-3">
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
KatelyaTV的所有视频源
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
TVBox标准JSON格式
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
Base64编码格式
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
CORS跨域支持
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
</span>
</div>
</div>
</div>
</div>
{/* 配置链接 */}
<div className="space-y-6">
{configs.map((config) => (
<div
key={config.type}
className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6"
>
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
{config.name}
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
{config.description}
</p>
</div>
<button
onClick={() => handleCopy(config.url, config.type)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
copySuccess === config.type
? 'bg-green-500 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
>
{copySuccess === config.type ? '已复制!' : '复制链接'}
</button>
</div>
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<code className="text-sm text-gray-800 dark:text-gray-200 break-all">
{config.url}
</code>
</div>
</div>
))}
</div>
{/* 使用说明 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
📖 使
</h2>
<div className="space-y-4 text-gray-700 dark:text-gray-300">
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
1.
</h3>
<p className="text-sm ml-4">
"复制链接"
</p>
</div>
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
2. TVBox
</h3>
<p className="text-sm ml-4">
TVBox应用
</p>
</div>
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
3.
</h3>
<p className="text-sm ml-4">
KatelyaTV添加新的视频源时TVBox中刷新配置即可同步最新源站
</p>
</div>
</div>
</div>
{/* API参数说明 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
🔧 API参数说明
</h2>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<tr>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
format
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
json() txt(base64编码)
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
?format=txt
</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* 解析接口说明 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
🎬
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
KatelyaTV提供内置的视频解析接口
</p>
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<code className="text-sm text-gray-800 dark:text-gray-200">
{baseUrl}/api/parse?url=
</code>
</div>
<div className="mt-4 text-sm text-gray-600 dark:text-gray-400">
<p>TV</p>
</div>
</div>
</div>
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-gray-600 dark:text-gray-300">...</p>
</div>
</PageLayout>
</div>
);
}
+1 -1
View File
@@ -141,7 +141,7 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
{
icon: Settings,
label: 'TVBox配置',
href: '/tvbox',
href: '/config',
},
];