|
@@ -1,632 +0,0 @@
|
|
|
-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<UploadVideoModalProps> = ({
|
|
|
|
|
- visible,
|
|
|
|
|
- onClose,
|
|
|
|
|
- onOk,
|
|
|
|
|
- videoInfo
|
|
|
|
|
-}) => {
|
|
|
|
|
- // 视频文件状态
|
|
|
|
|
- const [videoFile, setVideoFile] = useState<File & { localUrl?: string } | null>(null);
|
|
|
|
|
- const [videoUploadProgress, setVideoUploadProgress] = useState(0);
|
|
|
|
|
- const [videoUploadStatus, setVideoUploadStatus] = useState<UploadStatus>({
|
|
|
|
|
- isUploading: false,
|
|
|
|
|
- isUploaded: false,
|
|
|
|
|
- isError: false
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 封面文件状态
|
|
|
|
|
- const [coverFileList, setCoverFileList] = useState<UploadFile[]>([]);
|
|
|
|
|
- const [coverUploadStatus, setCoverUploadStatus] = useState<UploadStatus>({
|
|
|
|
|
- isUploading: false,
|
|
|
|
|
- isUploaded: false,
|
|
|
|
|
- isError: false
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // OSS相关状态
|
|
|
|
|
- const [videoCreds, setVideoCreds] = useState<UploadCreds | null>(null);
|
|
|
|
|
- const [videoUploader, setVideoUploader] = useState<OSSSDK | null>(null);
|
|
|
|
|
- const [videoUrl, setVideoUrl] = useState('');
|
|
|
|
|
-
|
|
|
|
|
- // 重试相关状态
|
|
|
|
|
- const [speedTimer, setSpeedTimer] = useState<NodeJS.Timeout | null>(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<UploadCreds> => {
|
|
|
|
|
- try {
|
|
|
|
|
- const params: any = { fileType };
|
|
|
|
|
- if (uploadId) {
|
|
|
|
|
- params.uploadId = uploadId;
|
|
|
|
|
- }
|
|
|
|
|
- // 这里需要根据实际API接口调整
|
|
|
|
|
- const response = await http.post<any>(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<OSSSDK> => {
|
|
|
|
|
- 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<any>(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 (
|
|
|
|
|
- <>
|
|
|
|
|
- <Modal
|
|
|
|
|
- open={visible}
|
|
|
|
|
- onCancel={() => {
|
|
|
|
|
- onClose();
|
|
|
|
|
- resetStates();
|
|
|
|
|
- }}
|
|
|
|
|
- footer={null}
|
|
|
|
|
- width={800}
|
|
|
|
|
- title={isEditMode ? "修改视频" : "上传视频"}
|
|
|
|
|
- destroyOnHidden
|
|
|
|
|
- >
|
|
|
|
|
- <div className={styles['upload-video-modal']}>
|
|
|
|
|
- {/* 视频上传区域:上传成功后隐藏选择模块 */}
|
|
|
|
|
- {!videoFile && (
|
|
|
|
|
- <div className={styles['upload-section']}>
|
|
|
|
|
- <h4>视频文件</h4>
|
|
|
|
|
- <Upload
|
|
|
|
|
- fileList={[]}
|
|
|
|
|
- beforeUpload={beforeVideoUpload}
|
|
|
|
|
- onRemove={handleVideoRemove}
|
|
|
|
|
- accept="video/*"
|
|
|
|
|
- maxCount={1}
|
|
|
|
|
- showUploadList={false}
|
|
|
|
|
- >
|
|
|
|
|
- <Button icon={<UploadOutlined />} disabled={videoUploadStatus.isUploading}>
|
|
|
|
|
- 选择视频文件
|
|
|
|
|
- </Button>
|
|
|
|
|
- </Upload>
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- {/* 视频上传进度区域 */}
|
|
|
|
|
- {videoFile && (
|
|
|
|
|
- <div className={styles['upload-progress-section']}>
|
|
|
|
|
- <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
|
|
|
|
- <div
|
|
|
|
|
- style={{ width: 124, height: 104, background: '#fafafa', border: '1px dashed #d9d9d9', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', position: 'relative' }}
|
|
|
|
|
- onMouseEnter={() => setIsVideoHovering(true)}
|
|
|
|
|
- onMouseLeave={() => setIsVideoHovering(false)}
|
|
|
|
|
- >
|
|
|
|
|
- <video src={videoFile.localUrl} style={{ width: '100%', height: '100%', objectFit: 'cover' }} muted />
|
|
|
|
|
- {!videoUploadStatus.isUploading && isVideoHovering && (
|
|
|
|
|
- <div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.35)' }}>
|
|
|
|
|
- <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, display: 'flex', justifyContent: 'center', padding: 8, gap: 3 }}>
|
|
|
|
|
- <Button size="small" type="primary" onClick={() => setVideoPreviewOpen(true)}>
|
|
|
|
|
- 预览
|
|
|
|
|
- </Button>
|
|
|
|
|
- {!isEditMode &&
|
|
|
|
|
- <Button size="small" danger onClick={handleVideoRemove}>
|
|
|
|
|
- 删除
|
|
|
|
|
- </Button>
|
|
|
|
|
- }
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div style={{ flex: 1 }}>
|
|
|
|
|
- <div className={styles['progress-info']}>
|
|
|
|
|
- <span className={styles['file-name']}>{videoFile.name}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <Progress
|
|
|
|
|
- percent={videoUploadProgress}
|
|
|
|
|
- status={videoUploadStatus.isError ? 'exception' : 'active'}
|
|
|
|
|
- strokeColor={videoUploadStatus.isError ? '#F2584F' : '#FF4383'}
|
|
|
|
|
- />
|
|
|
|
|
- {!isEditMode ? <div className={styles['progress-text']}>{getVideoProgressText()}</div> : null}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- {/* 封面上传区域 */}
|
|
|
|
|
- <div className={styles['upload-section']}>
|
|
|
|
|
- <h4>封面图片</h4>
|
|
|
|
|
- <Upload
|
|
|
|
|
- action={adFileUpload}
|
|
|
|
|
- headers={{
|
|
|
|
|
- token: getAccessToken()
|
|
|
|
|
- }}
|
|
|
|
|
- accept="image/*"
|
|
|
|
|
- listType="picture-card"
|
|
|
|
|
- beforeUpload={checkCoverFile}
|
|
|
|
|
- onChange={handleCoverUploadChange}
|
|
|
|
|
- fileList={coverFileList}
|
|
|
|
|
- showUploadList={{ showPreviewIcon: false }}
|
|
|
|
|
- maxCount={1}
|
|
|
|
|
- data={{ fileType: 'PICTURE' }}
|
|
|
|
|
- onRemove={handleCoverRemove}
|
|
|
|
|
- >
|
|
|
|
|
- {coverFileList.length >= 1 ? null : (
|
|
|
|
|
- <button style={{ border: 0, background: 'none' }} type="button">
|
|
|
|
|
- <PlusOutlined />
|
|
|
|
|
- <div style={{ marginTop: 8 }}>
|
|
|
|
|
- {coverUploadStatus.isUploaded ? '已选择封面' : '上传封面'}
|
|
|
|
|
- </div>
|
|
|
|
|
- </button>
|
|
|
|
|
- )}
|
|
|
|
|
- </Upload>
|
|
|
|
|
-
|
|
|
|
|
- {!isEditMode && getCoverProgressText() && (
|
|
|
|
|
- <div className={styles['progress-text']}>{getCoverProgressText()}</div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- {/* 视频信息编辑区域 */}
|
|
|
|
|
- <div className={styles['video-info-section']}>
|
|
|
|
|
- <Form form={form} layout="vertical">
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- label="视频标题"
|
|
|
|
|
- name="title"
|
|
|
|
|
- rules={[{ required: true, message: '请输入视频标题' }]}
|
|
|
|
|
- >
|
|
|
|
|
- <Input placeholder="请输入视频标题" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </Form>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- {/* 操作按钮区域 */}
|
|
|
|
|
- <div className={styles['action-section']}>
|
|
|
|
|
- <div className={styles['right-actions']}>
|
|
|
|
|
- <Space>
|
|
|
|
|
- {/* 视频上传操作 */}
|
|
|
|
|
- {videoFile && !isEditMode && (
|
|
|
|
|
- <>
|
|
|
|
|
- {videoUploadStatus.isUploading && (
|
|
|
|
|
- <Button onClick={cancelVideoUpload}>
|
|
|
|
|
- 取消视频上传
|
|
|
|
|
- </Button>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- {videoUploadStatus.isError && (
|
|
|
|
|
- <Button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- danger
|
|
|
|
|
- icon={<ReloadOutlined />}
|
|
|
|
|
- onClick={handleVideoRetry}
|
|
|
|
|
- className={styles['retry-btn']}
|
|
|
|
|
- >
|
|
|
|
|
- 重试视频
|
|
|
|
|
- </Button>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- {!videoUploadStatus.isUploading && videoUploadStatus.isUploaded && (
|
|
|
|
|
- <Button
|
|
|
|
|
- danger
|
|
|
|
|
- onClick={handleVideoRemove}
|
|
|
|
|
- >
|
|
|
|
|
- 删除已上传视频
|
|
|
|
|
- </Button>
|
|
|
|
|
- )}
|
|
|
|
|
- </>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- {/* 发布按钮 */}
|
|
|
|
|
- {videoUploadStatus.isUploaded && (
|
|
|
|
|
- <Button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- onClick={publishVideo}
|
|
|
|
|
- loading={publishLoading}
|
|
|
|
|
- >
|
|
|
|
|
- 发布视频
|
|
|
|
|
- </Button>
|
|
|
|
|
- )}
|
|
|
|
|
- </Space>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </Modal>
|
|
|
|
|
- {/* 视频预览弹窗 */}
|
|
|
|
|
- <Modal
|
|
|
|
|
- open={videoPreviewOpen}
|
|
|
|
|
- onCancel={() => setVideoPreviewOpen(false)}
|
|
|
|
|
- footer={null}
|
|
|
|
|
- width={720}
|
|
|
|
|
- title="预览视频"
|
|
|
|
|
- destroyOnHidden
|
|
|
|
|
- >
|
|
|
|
|
- <video src={videoFile?.localUrl} style={{ height: '100%', margin: '0 auto' }} controls />
|
|
|
|
|
- </Modal>
|
|
|
|
|
- </>
|
|
|
|
|
- );
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-export default UploadVideoModal;
|
|
|