import { useEffect, useState } from 'react'; import { Table, Input, Button, Space, Form, Select, DatePicker, message, Modal, Drawer, Tag, Image, Popconfirm, Typography, } from 'antd'; import { DownloadOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import { searchVideos, batchDelete, batchDownload, VideoItem, DownloadResult } from '../api/videos'; import { listCategories } from '../api/categories'; import { useAuthStore } from '../store/auth'; const { RangePicker } = DatePicker; const { Text } = Typography; const formatDuration = (seconds?: number) => { if (!seconds) return '-'; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m}:${s.toString().padStart(2, '0')}`; }; const formatSize = (bytes?: number) => { if (!bytes) return '-'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; let size = bytes; while (size >= 1024 && i < units.length - 1) { size /= 1024; i++; } return `${size.toFixed(2)} ${units[i]}`; }; export default function VideosPage() { const user = useAuthStore((state) => state.user); const [videos, setVideos] = useState([]); const [loading, setLoading] = useState(false); const [selectedRows, setSelectedRows] = useState([]); const [total, setTotal] = useState(0); const [pageNo, setPageNo] = useState(1); const [pageSize, setPageSize] = useState(20); const [categories, setCategories] = useState[]>([]); const [detail, setDetail] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); const [form] = Form.useForm(); const fetchVideos = async (params?: Record) => { setLoading(true); try { const values = form.getFieldsValue(); const query: Record = { pageNo, pageSize, ...params, }; if (values.keyword) query.keyword = values.keyword; if (values.cateId) query.cateId = values.cateId; if (values.status) query.status = values.status; if (values.dateRange?.[0] && values.dateRange?.[1]) { query.startTime = values.dateRange[0].toISOString(); query.endTime = values.dateRange[1].toISOString(); } const result = await searchVideos(query); setVideos(result.videos); setTotal(result.total); setPageNo(result.pageNo); setPageSize(result.pageSize); } finally { setLoading(false); } }; const fetchCategories = async () => { try { const data = await listCategories(); setCategories(data); } catch { // ignore } }; useEffect(() => { fetchVideos(); fetchCategories(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleSearch = () => { setPageNo(1); fetchVideos({ pageNo: 1 }); }; const handleReset = () => { form.resetFields(); setPageNo(1); fetchVideos({ pageNo: 1 }); }; const handleBatchDelete = async () => { const ids = selectedRows.map((v) => v.videoId); try { await batchDelete(ids); message.success(`已删除 ${ids.length} 个视频`); setSelectedRows([]); fetchVideos(); } catch (error) { message.error('删除失败'); } }; const handleBatchDownload = async () => { const ids = selectedRows.map((v) => v.videoId); try { const results = await batchDownload(ids); const valid = results.filter((r) => r.urls.length > 0); showDownloadResults(valid); setSelectedRows([]); } catch (error) { message.error('获取下载地址失败'); } }; const showDownloadResults = (results: DownloadResult[]) => { Modal.info({ title: '批量下载地址', width: 800, content: (
{results.map((item) => (
{item.title || item.videoId}
    {item.urls.map((url, idx) => (
  • {url.url} {url.definition}
  • ))}
))}
), }); }; const handleRowClick = (record: VideoItem) => { setDetail(record); setDrawerOpen(true); }; const columns = [ { title: '封面', dataIndex: 'coverURL', width: 80, render: (url?: string) => url ? : '-', }, { title: '视频 ID', dataIndex: 'videoId', width: 180, ellipsis: true }, { title: '标题', dataIndex: 'title', ellipsis: true }, { title: '分类', dataIndex: 'cateName', width: 120, render: (name?: string) => name || '-', }, { title: '时长', dataIndex: 'duration', width: 100, render: (duration?: number) => formatDuration(duration), }, { title: '大小', dataIndex: 'size', width: 120, render: (size?: number) => formatSize(size), }, { title: '状态', dataIndex: 'status', width: 100, render: (status?: string) => {status || '-'}, }, { title: '创建时间', dataIndex: 'creationTime', width: 180, render: (time?: string) => (time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '-'), }, { title: '操作', key: 'action', width: 100, render: (_: unknown, record: VideoItem) => ( ), }, ]; return (
{user?.role === 'admin' && ( )} `共 ${t} 条`, onChange: (page, size) => { setPageNo(page); setPageSize(size || 20); fetchVideos({ pageNo: page, pageSize: size }); }, }} rowSelection={{ selectedRowKeys: selectedRows.map((v) => v.videoId), onChange: (_keys, rows) => setSelectedRows(rows), }} /> setDrawerOpen(false)} > {detail && ( {detail.coverURL && }

视频 ID: {detail.videoId}

标题: {detail.title}

描述: {detail.description || '-'}

分类: {detail.cateName || '-'}

标签: {detail.tags || '-'}

时长: {formatDuration(detail.duration)}

大小: {formatSize(detail.size)}

状态: {detail.status || '-'}

创建时间: {detail.creationTime ? dayjs(detail.creationTime).format('YYYY-MM-DD HH:mm:ss') : '-'}

)}
); }