import { Button, Modal, Upload, Progress, message, Form, Input, Space } from "antd"; import { UploadOutlined, ReloadOutlined, PlusOutlined } from "@ant-design/icons"; import React, { useState, useEffect, useCallback } from "react"; import OSSSDK from "../../../../../utils/OSSSDK"; import http from "../../../../../http"; import styles from "./index.module.css"; import type { UploadFile, UploadProps } from "antd/es/upload/interface"; import { getAccessToken } from "../../../../../http/sso"; import { adFileUpload, getTempStsToken, uploadPublishVideo } from "../../../../../http/api"; interface UploadVideoModalProps { visible: boolean; onClose: () => void; onOk?: (videoInfo: any) => void; isLoading?: boolean; videoInfo?: any; } interface UploadStatus { isUploading: boolean; isUploaded: boolean; isError: boolean; errType?: string; } interface UploadCreds { host: string; hosts: string[]; fileName: string; upload: string; accessKeyId: string; accessKeySecret: string; securityToken: string; expiration: string; } const UploadVideoModal: React.FC = ({ visible, onClose, onOk, videoInfo }) => { // 视频文件状态 const [videoFile, setVideoFile] = useState(null); const [videoUploadProgress, setVideoUploadProgress] = useState(0); const [videoUploadStatus, setVideoUploadStatus] = useState({ isUploading: false, isUploaded: false, isError: false }); // 封面文件状态 const [coverFileList, setCoverFileList] = useState([]); const [coverUploadStatus, setCoverUploadStatus] = useState({ isUploading: false, isUploaded: false, isError: false }); // OSS相关状态 const [videoCreds, setVideoCreds] = useState(null); const [videoUploader, setVideoUploader] = useState(null); const [videoUrl, setVideoUrl] = useState(''); // 重试相关状态 const [speedTimer, setSpeedTimer] = useState(null); // 表单引用 const [form] = Form.useForm(); const [isEditMode, setIsEditMode] = useState(false); // 视频预览 const [videoPreviewOpen, setVideoPreviewOpen] = useState(false); const [isVideoHovering, setIsVideoHovering] = useState(false); // 发布视频loading状态 const [publishLoading, setPublishLoading] = useState(false); // 重置状态 const resetStates = useCallback(() => { // 重置视频文件状态 setVideoFile(null); setVideoUploadProgress(0); setVideoUploadStatus({ isUploading: false, isUploaded: false, isError: false }); // 重置封面文件状态 setCoverFileList([]); setCoverUploadStatus({ isUploading: false, isUploaded: false, isError: false }); // 重置OSS状态 setVideoCreds(null); setVideoUploader(null); setVideoUrl(''); setIsEditMode(false); if (speedTimer) { clearInterval(speedTimer); setSpeedTimer(null); } form.resetFields(); }, [speedTimer, form]); // 组件卸载时清理 useEffect(() => { return () => { if (speedTimer) clearInterval(speedTimer); if (videoUploader) { videoUploader.cancelUpload(); } }; }, [speedTimer, videoUploader]); useEffect(() => { if (videoFile) { startVideoUpload(videoFile); } }, [videoFile]); // 当videoInfo发生变化时,初始化表单和状态 useEffect(() => { if (visible && videoInfo) { setIsEditMode(true); setVideoUploadProgress(100); // 填充表单数据 form.setFieldsValue({ title: videoInfo.title, }); // 设置视频URL和状态 setVideoUrl(videoInfo.videoUrl); setVideoUploadStatus({ isUploading: false, isUploaded: true, isError: false }); // 设置封面文件 if (videoInfo.coverUrl) { setCoverFileList([{ uid: '-1', name: videoInfo.coverName || 'cover.jpg', status: 'done', url: videoInfo.coverUrl, response: { data: { fileUrl: videoInfo.coverUrl } } }]); setCoverUploadStatus({ isUploading: false, isUploaded: true, isError: false }); } // 对于编辑模式,我们需要创建一个模拟的videoFile对象来显示视频预览 if (videoInfo.videoUrl) { // 提取文件名作为显示名称 const fileName = videoInfo.videoName || videoInfo.videoUrl.split('/').pop() || 'video.mp4'; setVideoFile({ localUrl: videoInfo.videoUrl, name: fileName, type: 'video/mp4', size: 0 // 实际项目中可能需要从服务器获取文件大小 } as File & { localUrl?: string }); } } }, [visible, videoInfo, form]); // 获取上传凭证 const getSignature = async (fileType: number, uploadId?: string): Promise => { try { const params: any = { fileType }; if (uploadId) { params.uploadId = uploadId; } // 这里需要根据实际API接口调整 const response = await http.post(getTempStsToken, params); if (response.code === 0) { const credsData = response.data; if (fileType === 2) { // 视频文件 setVideoUrl(credsData.fileName); } return credsData; } else { throw new Error(response.data.msg || '获取签名失败'); } } catch (error) { console.error('获取签名失败:', error); throw error; } }; // 初始化视频上传器 const initVideoUploader = async (creds: UploadCreds): Promise => { if (!videoFile) { throw new Error('视频文件不存在'); } const uploader = new OSSSDK(videoFile, creds, (checkpoint: any[]) => { // 更新上传进度 const progress = Number((checkpoint[checkpoint.length - 1].percent * 100).toFixed(2)); setVideoUploadProgress(progress); }); return uploader; }; // 开始视频上传 const startVideoUpload = async (file?: File) => { const targetFile = file || videoFile; if (!targetFile) { message.error('请先选择视频文件'); return; } try { setVideoUploadStatus(prev => ({ ...prev, isUploading: true, isError: false })); // 获取上传凭证 const uploadCreds = await getSignature(2); // 2表示视频文件 setVideoCreds(uploadCreds); // 初始化上传器 const uploaderInstance = await initVideoUploader(uploadCreds); setVideoUploader(uploaderInstance); // 开始上传 await uploaderInstance.multipartUpload(); // 上传完成 setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isUploaded: true })); if (!isEditMode) { message.success('视频上传成功'); } } catch (error: any) { console.error('视频上传失败:', error); setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isError: true, errType: 'uploadError' })); message.error('视频上传失败,请重试'); } }; // 封面上传处理函数 const handleCoverUploadChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { // 只保留最新的文件 setCoverFileList(newFileList.slice(-1)); // 如果上传成功,更新状态 if (newFileList.length > 0 && newFileList[0].status === 'done') { setCoverUploadStatus(prev => ({ ...prev, isUploaded: true, isError: false })); message.success('封面上传成功'); } else if (newFileList.length > 0 && newFileList[0].status === 'error') { setCoverUploadStatus(prev => ({ ...prev, isError: true })); message.error('封面上传失败'); } else if (newFileList.length > 0 && newFileList[0].status === 'uploading') { setCoverUploadStatus(prev => ({ ...prev, isUploading: true, isError: false })); } }; // 封面文件验证 const checkCoverFile = (file: UploadFile) => { if ((file.size || 0) > 5 * 1024 * 1024) { message.error('图片大小不能超过5MB'); return Upload.LIST_IGNORE; // 阻止上传 } return true; // 允许上传 }; // 重试视频上传 const handleVideoRetry = async () => { setVideoUploadStatus(prev => ({ ...prev, isError: false })); if (videoUploader && videoCreds) { try { setVideoUploadStatus(prev => ({ ...prev, isUploading: true })); // 重新获取凭证 const newCreds = await getSignature(2, videoCreds.upload); setVideoCreds(newCreds); videoUploader.updateConfig(newCreds); // 断点续传 await videoUploader.resumeMultipartUpload(); setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isUploaded: true })); message.success('视频上传成功'); } catch (error) { console.error('重试视频上传失败:', error); setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isError: true })); message.error('重试失败'); } } }; // 取消视频上传 const cancelVideoUpload = () => { if (videoUploader) { videoUploader.cancelUpload(); } setVideoUploadStatus(prev => ({ ...prev, isUploading: false })); message.info('已取消视频上传'); }; // 发布视频 const publishVideo = async () => { if (!videoUploadStatus.isUploaded) { message.warning('请等待视频上传完成'); return; } // 表单校验 try { await form.validateFields(); } catch (error) { message.warning('请填写完整的视频信息'); return; } // 校验封面是否上传 if (coverFileList.length === 0 || !coverUploadStatus.isUploaded) { message.warning('请上传视频封面'); return; } try { // 设置loading状态为true setPublishLoading(true); const formData = form.getFieldsValue(); const publishData = { ...formData, videoUrl: isEditMode ? videoUrl : (videoCreds?.fileName || videoUrl), coverUrl: coverFileList.length > 0 ? coverFileList[0].response.data.fileUrl : '', fileExtensions: 'mp4', // 可以根据文件类型动态设置 ...(isEditMode && videoInfo?.videoId && { videoId: videoInfo.videoId }) }; // 这里需要根据实际API接口调整 const response = await http.post(uploadPublishVideo, publishData); if (response.code === 0) { message.success('发布成功'); onOk?.(response.data); onClose(); resetStates(); } else { message.error(response.msg || '发布失败'); } } catch (error) { console.error('发布失败:', error); message.error('发布失败,请重试'); } finally { // 请求结束后(无论成功或失败),设置loading状态为false setPublishLoading(false); } }; // 视频文件上传前处理 const beforeVideoUpload = (file: File & { localUrl?: string }) => { // 验证文件类型 const isVideo = file.type.startsWith('video/'); if (!isVideo) { message.error('只能上传视频文件!'); return false; } // 验证文件大小 (例如:限制500MB) const isLt500M = file.size / 1024 / 1024 < 500; if (!isLt500M) { message.error('视频大小不能超过500MB!'); return false; } file.localUrl = URL.createObjectURL(file); setVideoFile(file); setVideoUploadProgress(0); setVideoUploadStatus({ isUploading: false, isUploaded: false, isError: false }); return false; // 阻止自动上传 }; // 删除视频文件 const handleVideoRemove = () => { setVideoFile(null); setVideoUploadProgress(0); setVideoUploadStatus({ isUploading: false, isUploaded: false, isError: false }); if (videoUploader) { videoUploader.cancelUpload(); } }; // 删除封面文件 const handleCoverRemove = () => { setCoverFileList([]); setCoverUploadStatus({ isUploading: false, isUploaded: false, isError: false }); }; // 获取视频进度文本 const getVideoProgressText = () => { if (videoUploadStatus.isError) { return '上传失败,点击重试重新上传'; } else if (!videoUploadStatus.isUploading && videoUploadStatus.isUploaded) { return '上传完成'; } else if (!videoUploadStatus.isUploading && !videoUploadStatus.isUploaded) { return '等待中...'; } else { return ''; } }; // 获取封面进度文本 const getCoverProgressText = () => { if (coverUploadStatus.isError) { return '上传失败,请重新选择文件'; } else if (!coverUploadStatus.isUploading && coverUploadStatus.isUploaded) { return '上传完成'; } else if (coverFileList.length > 0 && coverFileList[0].status === 'uploading') { return '上传中...'; } else { return ''; } }; return ( <> { onClose(); resetStates(); }} footer={null} width={800} title={isEditMode ? "修改视频" : "上传视频"} destroyOnHidden >
{/* 视频上传区域:上传成功后隐藏选择模块 */} {!videoFile && (

视频文件

)} {/* 视频上传进度区域 */} {videoFile && (
setIsVideoHovering(true)} onMouseLeave={() => setIsVideoHovering(false)} >
{videoFile.name}
{!isEditMode ?
{getVideoProgressText()}
: null}
)} {/* 封面上传区域 */}

封面图片

{coverFileList.length >= 1 ? null : ( )} {!isEditMode && getCoverProgressText() && (
{getCoverProgressText()}
)}
{/* 视频信息编辑区域 */}
{/* 操作按钮区域 */}
{/* 视频上传操作 */} {videoFile && ( <> {videoUploadStatus.isUploading && ( )} {videoUploadStatus.isError && ( )} {!videoUploadStatus.isUploading && videoUploadStatus.isUploaded && ( )} )} {/* 发布按钮 */} {videoUploadStatus.isUploaded && ( )}
{/* 视频预览弹窗 */} setVideoPreviewOpen(false)} footer={null} width={720} title="预览视频" destroyOnHidden > ); }; export default UploadVideoModal;