import { useState, useRef, useCallback } from 'react'; import { Button, Modal, Input, Table, Upload, Popconfirm, message } from 'antd'; import { FileTextOutlined, CloudUploadOutlined } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { uploadVideo, createShotReplication, getShotReplicationList } from '../api'; export default function VideoFrameExtractor() { const navigate = useNavigate(); const [videoUrl, setVideoUrl] = useState(''); const [error, setError] = useState(''); const [isModalOpen, setIsModalOpen] = useState(false); const [productName, setProductName] = useState(''); const [videoDuration, setVideoDuration] = useState(0); const [loading, setLoading] = useState(false); const [tableData, setTableData] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [total, setTotal] = useState(0); const videoRef = useRef(null); const canvasRef = useRef(null); const cleanupResources = useCallback(() => { if (videoUrl) { URL.revokeObjectURL(videoUrl); } setVideoUrl(''); setError(''); setVideoDuration(0); setProductName(''); }, [videoUrl]); const handleFileChange = async (file: File) => { if (!file.type.startsWith('video/')) { setError('请选择视频文件'); return false; } if (file.size > 100 * 1024 * 1024) { setError('视频文件大小不能超过 100MB'); return false; } cleanupResources(); setLoading(true); try { const uploadResult = await uploadVideo(file); setVideoUrl(uploadResult.url); setError(''); message.success('视频上传成功'); } catch (err) { setError('视频上传失败,请重试'); message.error('视频上传失败'); } finally { setLoading(false); } return false; }; const handleVideoLoaded = useCallback(() => { if (videoRef.current) { setVideoDuration(videoRef.current.duration); } }, []); const handleCreate = async () => { if (!videoUrl || !productName.trim()) { message.warning('请先上传视频并输入产品名称'); return; } setLoading(true); try { const params = { video_url: videoUrl, video_duration_seconds: videoDuration, title: productName.trim(), idempotency_key: `shot_${Date.now()}`, }; await createShotReplication(params); message.success('任务创建成功'); // 清空上传内容和输入框 cleanupResources(); navigate('/'); } catch (err) { message.error('任务创建失败,请重试'); } finally { setLoading(false); } }; const fetchList = async (page: number, size: number) => { try { const res = await getShotReplicationList(page, size); setTableData(res.items || []); setTotal(res.total || 0); setCurrentPage(page); setPageSize(size); } catch (err) { message.error('获取列表失败'); } }; const handlePageChange = (page: number, size: number) => { fetchList(page, size); }; // 打开弹窗时获取列表数据 const handleOpenModal = () => { setIsModalOpen(true); fetchList(1, 10); }; return (

拆镜复刻

一键拆解画面分镜,助力仿拍或创作

🎬

上传视频开始创作

支持 MP4、MOV 格式视频,最大支持 100MB

{error && (
{error}
)} {!videoUrl && (

点击或拖拽到此上传视频文件

支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频

)} {videoUrl && (
{/* 产品名称输入框 */}
setProductName(e.target.value)} style={{ width: '100%', height: 40, borderRadius: 8, borderColor: '#e5e7eb' }} />
)} setIsModalOpen(false)} width={800} footer={null} >
( // 产品图片 // ), // }, { title: '产品名称', dataIndex: 'title', key: 'title', }, { title: '状态', dataIndex: 'status', key: 'status', }, { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', }, { title: '操作', key: 'action', render: (record) => ( ), }, ]} dataSource={tableData} rowKey="id" pagination={{ current: currentPage, pageSize: pageSize, total: total, onChange: handlePageChange, showSizeChanger: false, showQuickJumper: false, showTotal: (total) => `共 ${total} 条`, placement: ['bottomCenter'], itemRender: (_, type, originalElement) => { if (type === 'page') { return {originalElement}; } if (type === 'prev') { return 上一页; } if (type === 'next') { return 下一页; } return originalElement; }, }} /> ); }