nieyuge 2 nedēļas atpakaļ
vecāks
revīzija
efa5957afc

+ 3 - 1
src/http/api.ts

@@ -54,8 +54,10 @@ export const readNotice = `${import.meta.env.VITE_API_URL}/contentPlatform/notic
 export const readAllNotice = `${import.meta.env.VITE_API_URL}/contentPlatform/notice/readAll`
 
 // 文件上传
-export const fileUpload = `${import.meta.env.VITE_API_URL}/file/upload`
 export const getTempStsToken = `${import.meta.env.VITE_API_URL}/file/getTempStsToken`
+export const uploadContentList = `${import.meta.env.VITE_API_URL}/contentPlatform/uploadContent/list`
+export const uploadPublishVideo = `${import.meta.env.VITE_API_URL}/contentPlatform/uploadContent/publishVideo`
+export const uploadDeleteVideo = `${import.meta.env.VITE_API_URL}/contentPlatform/uploadContent/deleteVideo`
 
 // 设置绑定微信用户
 export const getBindPQUserInfo = `${import.meta.env.VITE_API_URL}/contentPlatform/setting/getBindPQUserInfo`

+ 1 - 1
src/views/publishContent/publishContent.router.tsx

@@ -41,7 +41,7 @@ const demoRoutes: AdminRouterItem[] = [
 		},
 		children: [
 			{
-				path: 'wegzh',
+				path: 'wegzh/:code?',
 				element: <LazyComponent Component={WeGZH} />,
 				meta: {
 					label: "公众号",

+ 92 - 56
src/views/publishContent/videos/components/uploadVideoModal/index.tsx

@@ -6,13 +6,14 @@ 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 } from "../../../../../http/api";
+import { adFileUpload, getTempStsToken, uploadPublishVideo } from "../../../../../http/api";
 
 interface UploadVideoModalProps {
   visible: boolean;
   onClose: () => void;
   onOk?: (videoInfo: any) => void;
   isLoading?: boolean;
+  videoInfo?: any;
 }
 
 interface UploadStatus {
@@ -22,7 +23,6 @@ interface UploadStatus {
   errType?: string;
 }
 
-
 interface UploadCreds {
   host: string;
   hosts: string[];
@@ -38,12 +38,12 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
   visible, 
   onClose, 
   onOk, 
-  isLoading = false 
+  isLoading = false,
+  videoInfo
 }) => {
   // 视频文件状态
   const [videoFile, setVideoFile] = useState<File & { localUrl?: string } | null>(null);
   const [videoUploadProgress, setVideoUploadProgress] = useState(0);
-  const [videoUploadSpeed, setVideoUploadSpeed] = useState('0Mb/s');
   const [videoUploadStatus, setVideoUploadStatus] = useState<UploadStatus>({
     isUploading: false,
     isUploaded: false,
@@ -68,6 +68,7 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
 
   // 表单引用
   const [form] = Form.useForm();
+  const [isEditMode, setIsEditMode] = useState(false);
 
   // 视频预览
   const [videoPreviewOpen, setVideoPreviewOpen] = useState(false);
@@ -78,13 +79,12 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
     // 重置视频文件状态
     setVideoFile(null);
     setVideoUploadProgress(0);
-    setVideoUploadSpeed('0Mb/s');
     setVideoUploadStatus({
       isUploading: false,
       isUploaded: false,
       isError: false
     });
-    
+
     // 重置封面文件状态
     setCoverFileList([]);
     setCoverUploadStatus({
@@ -92,12 +92,13 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       isUploaded: false,
       isError: false
     });
-    
+
     // 重置OSS状态
     setVideoCreds(null);
     setVideoUploader(null);
     setVideoUrl('');
-    
+    setIsEditMode(false);
+
     if (speedTimer) {
       clearInterval(speedTimer);
       setSpeedTimer(null);
@@ -114,13 +115,62 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       }
     };
 	}, [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 {
@@ -130,7 +180,7 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       }
       // 这里需要根据实际API接口调整
       const response = await http.post<any>(getTempStsToken, params);
-      
+
       if (response.code === 0) {
         const credsData = response.data;
         if (fileType === 2) { // 视频文件
@@ -172,7 +222,7 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
 
     try {
       setVideoUploadStatus(prev => ({ ...prev, isUploading: true, isError: false }));
-      
+
       // 获取上传凭证
       const uploadCreds = await getSignature(2); // 2表示视频文件
       setVideoCreds(uploadCreds);
@@ -181,17 +231,14 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       const uploaderInstance = await initVideoUploader(uploadCreds);
       setVideoUploader(uploaderInstance);
 
-      // 开始速度监控
-      startSpeedMonitoring(uploaderInstance);
-
       // 开始上传
       await uploaderInstance.multipartUpload();
-      
+
       // 上传完成
       setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isUploaded: true }));
-      setVideoUploadSpeed('');
-      message.success('视频上传成功');
-
+			if (!isEditMode) {
+				message.success('视频上传成功');
+			}
     } catch (error: any) {
       console.error('视频上传失败:', error);
       setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isError: true, errType: 'uploadError' }));
@@ -203,7 +250,7 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
   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 }));
@@ -225,23 +272,6 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
     return true; // 允许上传
   };
 
-  // 开始速度监控
-  const startSpeedMonitoring = (uploaderInstance: OSSSDK) => {
-    const timer = uploaderInstance.getSpeed((speed: string) => {
-      setVideoUploadSpeed(speed + 'Mb/s');
-    });
-    setSpeedTimer(timer);
-  };
-
-  // 停止速度监控
-  const stopSpeedMonitoring = () => {
-    if (speedTimer) {
-      clearInterval(speedTimer);
-      setSpeedTimer(null);
-    }
-    setVideoUploadSpeed('');
-  };
-
   // 重试视频上传
   const handleVideoRetry = async () => {
     setVideoUploadStatus(prev => ({ ...prev, isError: false }));
@@ -249,20 +279,16 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
     if (videoUploader && videoCreds) {
       try {
         setVideoUploadStatus(prev => ({ ...prev, isUploading: true }));
-        
+
         // 重新获取凭证
         const newCreds = await getSignature(2, videoCreds.upload);
         setVideoCreds(newCreds);
         videoUploader.updateConfig(newCreds);
         
-        // 开始速度监控
-        startSpeedMonitoring(videoUploader);
-        
         // 断点续传
         await videoUploader.resumeMultipartUpload();
         
         setVideoUploadStatus(prev => ({ ...prev, isUploading: false, isUploaded: true }));
-        setVideoUploadSpeed('');
         message.success('视频上传成功');
         
       } catch (error) {
@@ -278,7 +304,6 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
     if (videoUploader) {
       videoUploader.cancelUpload();
     }
-    stopSpeedMonitoring();
     setVideoUploadStatus(prev => ({ ...prev, isUploading: false }));
     message.info('已取消视频上传');
   };
@@ -290,26 +315,40 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       return;
     }
 
+    // 表单校验
+    try {
+      await form.validateFields();
+    } catch (error) {
+      message.warning('请填写完整的视频信息');
+      return;
+    }
+
+    // 校验封面是否上传
+    if (coverFileList.length === 0 || !coverUploadStatus.isUploaded) {
+      message.warning('请上传视频封面');
+      return;
+    }
+
     try {
 			const formData = form.getFieldsValue();
 			
       const publishData = {
         ...formData,
-        videoPath: videoCreds?.fileName || videoUrl,
-        coverPath: coverFileList.length > 0 ? coverFileList[0].url : '',
+        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>('/contentPlatform/video/publish', publishData);
-      
-      if (response.data.code === 0) {
+      const response = await http.post<any>(uploadPublishVideo, publishData);
+      if (response.code === 0) {
         message.success('发布成功');
-        onOk?.(response.data.data);
+        onOk?.(response.data);
         onClose();
         resetStates();
       } else {
-        message.error(response.data.msg || '发布失败');
+        message.error(response.msg || '发布失败');
       }
     } catch (error) {
       console.error('发布失败:', error);
@@ -346,7 +385,6 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
     return false; // 阻止自动上传
   };
 
-
   // 删除视频文件
   const handleVideoRemove = () => {
     setVideoFile(null);
@@ -356,7 +394,6 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       isUploaded: false,
       isError: false
     });
-    stopSpeedMonitoring();
     if (videoUploader) {
       videoUploader.cancelUpload();
     }
@@ -409,7 +446,7 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
       }} 
       footer={null}
       width={800}
-      title="上传视频"
+      title={isEditMode ? "修改视频" : "上传视频"}
       destroyOnHidden
     >
       <div className={styles['upload-video-modal']}>
@@ -459,14 +496,13 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
               <div style={{ flex: 1 }}>
                 <div className={styles['progress-info']}>
                   <span className={styles['file-name']}>{videoFile.name}</span>
-                  {videoUploadSpeed && <span className={styles['upload-speed']}>{videoUploadSpeed}</span>}
                 </div>
                 <Progress 
                   percent={videoUploadProgress} 
                   status={videoUploadStatus.isError ? 'exception' : 'active'}
                   strokeColor={videoUploadStatus.isError ? '#F2584F' : '#FF4383'}
                 />
-                <div className={styles['progress-text']}>{getVideoProgressText()}</div>
+                {!isEditMode ? <div className={styles['progress-text']}>{getVideoProgressText()}</div> : null}
               </div>
             </div>
           </div>
@@ -500,7 +536,7 @@ const UploadVideoModal: React.FC<UploadVideoModalProps> = ({
             )}
           </Upload>
           
-          {getCoverProgressText() && (
+          {!isEditMode && getCoverProgressText() && (
             <div className={styles['progress-text']}>{getCoverProgressText()}</div>
           )}
         </div>

+ 126 - 13
src/views/publishContent/videos/index.tsx

@@ -1,44 +1,150 @@
-import { Button, Input, Select, Table, Spin } from 'antd';
-import React, { useState } from 'react';
+import { Button, Input, Select, Table, Spin, Popconfirm, message, Popover } from 'antd';
+import React, { useState, useEffect } from 'react';
 import styles from './index.module.css';
+import http from '@src/http/index';
+import { uploadContentList, uploadDeleteVideo } from "@src/http/api";
 import UploadVideoModal from './components/uploadVideoModal';
 import { enumToOptions } from '@src/utils/helper';
+import { UploadContentResponse } from './type';
+import dayjs from 'dayjs';
+import VideoPlayModal from '../weCom/components/videoPlayModal';
 // Define a type for the expected API response (adjust if needed based on actual API)
 const TableHeight = window.innerHeight - 380;
 
 enum AuditStatus {
-	审核中 = '审核中',
-	审核通过 = '审核通过',
-	审核拒绝 = '审核拒绝',
+	待审核 = 0,
+	审核中 = 1,
+	审核通过 = 2,
+	审核不通过 = 3,
 }
 
 const MyVideos: React.FC = () => {
 	const [isLoading, setIsLoading] = useState(false);
 	const [auditStatus, setAuditStatus] = useState<string>();
 	const [videoTitle, setVideoTitle] = useState<string>();
-    const [tableData] = useState<any[]>([]);
-    const [totalSize] = useState(0);
-    const [pageNum] = useState(1);
-    const [pageSize] = useState(10);
+	const [tableData, setTableData] = useState<any[]>([]);
+	const [totalSize, setTotalSize] = useState(0);
+	const [pageNum] = useState(1);
+	const [pageSize] = useState(10);
 
+	const [isVideoPlayModalVisible, setIsVideoPlayModalVisible] = useState<boolean>(false);
 	const [isShowUploadVideoModal, setIsShowUploadVideoModal] = useState(false);
-    const [isAddPlanLoading] = useState(false);
+	const [playVideoUrl, setPlayVideoUrl] = useState('');
+	const [playVideoTitle, setPlayVideoTitle] = useState('');
+	const [isAddPlanLoading] = useState(false);
+	const [editingVideo, setEditingVideo] = useState<any>(null);
+	
+	// 页面加载时调用getTableData
+	useEffect(() => {
+		getTableData();
+	}, []);
 
-    const getTableData = async (_pageNum?: number, _pageSize?: number) => {
+	const getTableData = async (_pageNum?: number, _pageSize?: number) => {
 		setIsLoading(true);
-		
+		http.post<UploadContentResponse>(uploadContentList, {
+			auditStatus: auditStatus,
+			title: videoTitle,
+			pageNum: _pageNum ?? pageNum,
+			pageSize: _pageSize || pageSize,
+		}).then(res => {
+			const { code, data } = res;
+			if (code === 0) {
+				setTableData(data.objs);
+				setTotalSize(data.totalSize);
+			}
+		}).finally(() => {
+			setIsLoading(false);
+		});
 	}
 
 	const columns = [
+		{
+			title: '视频ID',
+			dataIndex: 'id',
+		},
+		{
+			title: '视频封面',
+			dataIndex: 'cover',
+			render: (_, record) => {
+				return <img src={record.coverUrl} alt="cover" className="w-20 h-10 object-cover" />
+			}
+		},
 		{
 			title: '视频标题',
 			dataIndex: 'title',
-			key: 'title',
 		},
+		{
+			title: '视频播放地址',
+			render: (_, record) => {
+				return <span className="text-blue-500 underline cursor-pointer" onClick={playVideo(record)}>播放视频</span>
+			}
+		},
+		{
+			title: '上传时间',
+			render: (_, record) => {
+				return <span>{record.createTimeStamp ? dayjs(record.createTimeStamp).format('YYYY-MM-DD HH:mm:ss') : ''}</span>
+			}
+		},
+		{
+			title: '审核状态',
+			render: (_, record) => {
+				return <>
+					{record.auditStatus === 3 ? <><span className="text-red-500">审核不通过</span> <span className="text-blue-500 underline cursor-pointer"><Popover placement="top" content={record.auditReason}>(查看原因)</Popover></span></> : record.auditStatus === 2 ? <span className="text-green-500">审核通过</span> : <span className="text-black-500">审核中</span>}
+				</>
+			}
+		},
+		{
+			title: '操作',
+			render: (_, record) => {
+				return <>
+					{record.auditStatus === 3 ? <><span className="text-blue-500 underline cursor-pointer" onClick={() => { handleEditVideo(record) }}>修改视频</span>&emsp;</> : record.auditStatus === 2 ? <><span className="text-blue-500 underline cursor-pointer" onClick={() => { uploadPublishVideo(record) }}>创建发布</span>&emsp;</> : ''}
+					<Popconfirm
+						title="确定删除该视频吗?"
+						okText="确定"
+						cancelText="取消"
+						onConfirm={() => deleteVideo(record)}>
+						<span className="text-blue-500 underline cursor-pointer">删除</span>
+					</Popconfirm>
+				</>
+			}
+		}
 	]
 
 	const handleUploadVideo = () => {
+		setEditingVideo(null);
 		setIsShowUploadVideoModal(true);
+		getTableData(pageNum, pageSize)
+	}
+
+	const handleEditVideo = (record: any) => {
+		setEditingVideo(record);
+		setIsShowUploadVideoModal(true);
+	}
+
+	const playVideo = (record: any) => () => {
+		setPlayVideoUrl(record.videoUrl);
+		setIsVideoPlayModalVisible(true);
+		setPlayVideoTitle(record.title);
+	}
+
+	const deleteVideo = (record: any) => {
+		http.post(uploadDeleteVideo, {
+			videoId: record.videoId,
+		}).then(res => {
+			const { code } = res;
+			if (code === 0) {
+				message.success('删除成功');
+				getTableData(pageNum, pageSize);
+			} else {
+				message.error(res.msg || '删除失败');
+			}
+		}).catch(err => {
+			message.error(err.msg || '删除失败');
+		})
+	}
+
+	const uploadPublishVideo = (record: any) => {
+		return location.href = '/publishContent/wegzh/' + record.videoId;
 	}
 
 	return (
@@ -100,6 +206,13 @@ const MyVideos: React.FC = () => {
 					}}
 					onOk={handleUploadVideo}
 					isLoading={isAddPlanLoading}
+					videoInfo={editingVideo}
+				/>
+				<VideoPlayModal
+					visible={isVideoPlayModalVisible}
+					onClose={() => setIsVideoPlayModalVisible(false)}
+					videoUrl={playVideoUrl}
+					title={playVideoTitle}
 				/>
 			</div>
 		</Spin>

+ 4 - 0
src/views/publishContent/videos/type.ts

@@ -0,0 +1,4 @@
+export interface UploadContentResponse {
+  objs: any[];
+  totalSize: number;
+}

+ 2 - 0
src/views/publishContent/weGZH/index.tsx

@@ -1,4 +1,5 @@
 import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
 import { Space, Table, Button, Input, Select, DatePicker, Tabs, message, Typography, Spin, Popconfirm } from 'antd';
 import type { TableProps } from 'antd';
 import dayjs, { Dayjs } from 'dayjs';
@@ -15,6 +16,7 @@ const TableHeight = window.innerHeight - 380;
 
 const WeGZHContent: React.FC = () => {
 	const [planType, setPlanType] = useState<GzhPlanType>(GzhPlanType.自动回复);
+	const { code } = useParams<{ code?: string }>();
 	// 状态管理
 	const [selectedAccount, setSelectedAccount] = useState<string>();
 	const [videoTitle, setVideoTitle] = useState<string>('');