소스 검색

feat: 完善公众号发布模块

jihuaqiang 1 주 전
부모
커밋
e112790251

+ 1 - 0
index.html

@@ -4,6 +4,7 @@
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<meta name="referrer" content="no-referrer" />
     <title>票圈对外内容平台</title>
     <title>票圈对外内容平台</title>
   </head>
   </head>
   <body>
   <body>

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {
     "dev": "vite",
     "dev": "vite",
-    "build:test": "vite --mode development",
+    "build:test": "tsc && vite build --config vite.test.config.ts",
     "build": "tsc && vite build",
     "build": "tsc && vite build",
     "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
     "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
     "preview": "vite preview"
     "preview": "vite preview"

+ 1 - 1
src/components/layout/headerbar.tsx

@@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom';
 
 
 const { Header } = Layout;
 const { Header } = Layout;
 
 
-const Headerbar = (props: { colorBgContainer: string }) => {
+const Headerbar = () => {
 	const navigate = useNavigate()
 	const navigate = useNavigate()
 	const setAlgorithm = useConfigStore(state => state.setAlgorithm)
 	const setAlgorithm = useConfigStore(state => state.setAlgorithm)
 	const userInfo = sso.getUserInfo()
 	const userInfo = sso.getUserInfo()

+ 7 - 6
src/components/layout/index.tsx

@@ -1,19 +1,20 @@
 import React from 'react';
 import React from 'react';
-import { Layout, theme } from 'antd';
+import { Layout } from 'antd';
+// import { theme } from 'antd';
 import PageSidebar from './sidebar';
 import PageSidebar from './sidebar';
 import PageContent from './contentbar';
 import PageContent from './contentbar';
 import Headerbar from './headerbar';
 import Headerbar from './headerbar';
 
 
 const PageLayout: React.FC = () => {
 const PageLayout: React.FC = () => {
-  const {
-    token: { colorBgContainer },
-  } = theme.useToken();
+  // const {
+  //   token: { colorBgContainer },
+	// } = theme.useToken();
 
 
   return (
   return (
 		<Layout className='w-full min-h-screen flex flex-col'>
 		<Layout className='w-full min-h-screen flex flex-col'>
-			<Headerbar colorBgContainer={colorBgContainer} />
+			<Headerbar  />
       <Layout className='w-full'>
       <Layout className='w-full'>
-      	<PageSidebar />
+				<PageSidebar />
         <PageContent></PageContent>
         <PageContent></PageContent>
       </Layout>
       </Layout>
     </Layout>
     </Layout>

+ 3 - 0
src/http/api.ts

@@ -39,3 +39,6 @@ export const gzhDataList = `${import.meta.env.VITE_API_URL}/contentPlatform/data
 export const gzhDataExport = `${import.meta.env.VITE_API_URL}/contentPlatform/datastat/gzh/export`
 export const gzhDataExport = `${import.meta.env.VITE_API_URL}/contentPlatform/datastat/gzh/export`
 export const qwDataList = `${import.meta.env.VITE_API_URL}/contentPlatform/datastat/qw`
 export const qwDataList = `${import.meta.env.VITE_API_URL}/contentPlatform/datastat/qw`
 export const qwDataExport = `${import.meta.env.VITE_API_URL}/contentPlatform/datastat/qw/export`
 export const qwDataExport = `${import.meta.env.VITE_API_URL}/contentPlatform/datastat/qw/export`
+
+/* 文件上传 */
+export const adFileUpload = `https://testapi.piaoquantv.com/ad/file/upload` // 文件上传

+ 0 - 1
src/http/index.ts

@@ -2,7 +2,6 @@ import axios from 'axios'
 import sso from './sso'
 import sso from './sso'
 import router from '@src/router/index'
 import router from '@src/router/index'
 import type { AxiosInstance, CreateAxiosDefaults, AxiosRequestConfig } from 'axios'
 import type { AxiosInstance, CreateAxiosDefaults, AxiosRequestConfig } from 'axios'
-import { message } from 'antd'
 
 
 const config = {
 const config = {
   timeout: 60000,
   timeout: 60000,

+ 5 - 0
src/http/sso.ts

@@ -21,6 +21,11 @@ export function getUserInfo(): UserInfo {
   return activeUser ? JSON.parse(activeUser) : {}
   return activeUser ? JSON.parse(activeUser) : {}
 }
 }
 
 
+export function getAccessToken(): string {
+  // const userInfo = getUserInfo()
+  return 'PayRwNWQ66AoVtBfZHnr3iDmNQ8o9vekKFI15ZKVcC7rVvmGORr-hWIpW6Uv4FKLHwgOCm2RIX2UyEybZWn9pA'
+}
+
 export function setUserInfo(userInfo: UserInfo): void {
 export function setUserInfo(userInfo: UserInfo): void {
   localStorage.removeItem('userInfo')
   localStorage.removeItem('userInfo')
   localStorage.setItem('userInfo', JSON.stringify(userInfo))
   localStorage.setItem('userInfo', JSON.stringify(userInfo))

+ 5 - 10
src/views/cooperationAccount/gzh/index.tsx

@@ -34,14 +34,6 @@ interface AuthResultData {
   ghId: string;
   ghId: string;
 }
 }
 
 
-// 定义接口响应类型
-interface ApiResponse<T> {
-  success: boolean;
-  msg?: string;
-  data?: T;
-  code?: number;
-}
-
 const AccountDialog = ({ title, isOpen, handleOk: parentHandleOk, handleCancel, authFailed = false, accountData }: AccountDialogProps) => {
 const AccountDialog = ({ title, isOpen, handleOk: parentHandleOk, handleCancel, authFailed = false, accountData }: AccountDialogProps) => {
   const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
   const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
   const [accountName, setAccountName] = useState<string>("授权成功后展示");
   const [accountName, setAccountName] = useState<string>("授权成功后展示");
@@ -61,7 +53,7 @@ const AccountDialog = ({ title, isOpen, handleOk: parentHandleOk, handleCancel,
   const getQrCode = async () => {
   const getQrCode = async () => {
     try {
     try {
       const code = generateRandomCode();
       const code = generateRandomCode();
-      const res = await http.post<ApiResponse<{ qrcodeStr: string }>>(accountGetAuthQrCode, { params: { code } });
+      const res = await http.post<{ qrcodeStr: string }>(accountGetAuthQrCode, { params: { code } });
       if (res.success && res.data && res.data.qrcodeStr) {
       if (res.success && res.data && res.data.qrcodeStr) {
         setQrCodeUrl(res.data.qrcodeStr);
         setQrCodeUrl(res.data.qrcodeStr);
         // 开始轮询检查授权结果
         // 开始轮询检查授权结果
@@ -238,7 +230,10 @@ const AccountDialog = ({ title, isOpen, handleOk: parentHandleOk, handleCancel,
           <div className={"mt-[10px]"}>
           <div className={"mt-[10px]"}>
             <p className={"text-[20px] font-medium"}>授权失败</p>
             <p className={"text-[20px] font-medium"}>授权失败</p>
           </div>
           </div>
-          <Button type="primary" onClick={handleOk} className={"mb-[20px]"}>重新扫码</Button>
+          <Button type="primary" onClick={() => {
+            setAuthSuccess(false);
+            getQrCode();
+          }} className={"mb-[20px]"}>重新扫码</Button>
         </div>
         </div>
       ) : authSuccess ? (
       ) : authSuccess ? (
         <div className={"p-[20px] text-[#333] text-center"}>
         <div className={"p-[20px] text-[#333] text-center"}>

+ 11 - 17
src/views/publishContent/weCom/index.tsx

@@ -3,16 +3,7 @@ import { Space, Table, Button, Input, Select, Tabs } from 'antd';
 import type { TableProps } from 'antd';
 import type { TableProps } from 'antd';
 import styles from './index.module.css';
 import styles from './index.module.css';
 import PunlishPlanModal from '../weGZH/components/publishPlanModal';
 import PunlishPlanModal from '../weGZH/components/publishPlanModal';
-
-export interface PlanData {
-	// key: string;
-	officialAccount: string;
-	scene: string;
-	videoCount: number;
-	videoTitle: string;
-	planPublishTime: string;
-	publisher: string;
-}
+import { wxPlanType } from './type';
 
 
 const WeGZHContent: React.FC = () => {
 const WeGZHContent: React.FC = () => {
 	// 状态管理
 	// 状态管理
@@ -20,11 +11,11 @@ const WeGZHContent: React.FC = () => {
 	const [selectedPublisher, setSelectedPublisher] = useState<string>();
 	const [selectedPublisher, setSelectedPublisher] = useState<string>();
 	const [isShowAddPunlishPlan, setIsShowAddPunlishPlan] = useState<boolean>(false);
 	const [isShowAddPunlishPlan, setIsShowAddPunlishPlan] = useState<boolean>(false);
 	const [actionType, setActionType] = useState<'add' | 'edit'>('add');
 	const [actionType, setActionType] = useState<'add' | 'edit'>('add');
-	const [editPlanData, setEditPlanData] = useState<PlanData>();
+	const [editPlanData, setEditPlanData] = useState<wxPlanType>();
 	const [activeKey, setActiveKey] = useState<string>('1');
 	const [activeKey, setActiveKey] = useState<string>('1');
 
 
 	// 表格列配置
 	// 表格列配置
-	const columns: TableProps<PlanData>['columns'] = [
+	const columns: TableProps<wxPlanType>['columns'] = [
 		{
 		{
 			title: '公众号名称',
 			title: '公众号名称',
 			dataIndex: 'officialAccount',
 			dataIndex: 'officialAccount',
@@ -68,29 +59,32 @@ const WeGZHContent: React.FC = () => {
 		},
 		},
 	];
 	];
 
 
-	const editPlan = (record: PlanData) => {
+	const editPlan = (record: wxPlanType) => {
+		console.log(editPlanData)
 		setEditPlanData(record);
 		setEditPlanData(record);
 		setActionType('edit');
 		setActionType('edit');
 		setIsShowAddPunlishPlan(true);
 		setIsShowAddPunlishPlan(true);
 	};
 	};
 
 
 	// 模拟数据
 	// 模拟数据
-	const data: PlanData[] = [
+	const data: wxPlanType[] = [
 		{
 		{
 			officialAccount: '小慧爱厨房',
 			officialAccount: '小慧爱厨房',
-			scene: '关注回复',
+			scene: 1,
 			videoCount: 3,
 			videoCount: 3,
 			videoTitle: '养老金最新规定,快来看看...',
 			videoTitle: '养老金最新规定,快来看看...',
 			planPublishTime: '2024-08-13 13:32:07',
 			planPublishTime: '2024-08-13 13:32:07',
 			publisher: '平台发布',
 			publisher: '平台发布',
+			videoList: [],
 		},
 		},
 		{
 		{
 			officialAccount: '小阳看天下',
 			officialAccount: '小阳看天下',
-			scene: '关注回复',
+			scene: 1,
 			videoCount: 1,
 			videoCount: 1,
 			videoTitle: '养老金最新规定,快来看看...',
 			videoTitle: '养老金最新规定,快来看看...',
 			planPublishTime: '2024-08-13 13:32:07',
 			planPublishTime: '2024-08-13 13:32:07',
 			publisher: '用户发布',
 			publisher: '用户发布',
+			videoList: [],
 		},
 		},
 	];
 	];
 
 
@@ -184,7 +178,7 @@ const WeGZHContent: React.FC = () => {
 					onCancel={() => setIsShowAddPunlishPlan(false)}
 					onCancel={() => setIsShowAddPunlishPlan(false)}
 					onOk={() => setIsShowAddPunlishPlan(false)}
 					onOk={() => setIsShowAddPunlishPlan(false)}
 					actionType={actionType}
 					actionType={actionType}
-					editPlanData={editPlanData}
+					editPlanData={undefined}
 				/>
 				/>
 			</div>
 			</div>
 		</div>
 		</div>

+ 11 - 0
src/views/publishContent/weCom/type.ts

@@ -0,0 +1,11 @@
+import { VideoItem } from "../weGZH/components/types";
+
+export interface wxPlanType {
+	officialAccount: string;
+	videoCount: number;
+	videoTitle: string;
+	planPublishTime: string;
+	publisher: string;
+	videoList: VideoItem[];
+	scene: number;
+}

+ 57 - 35
src/views/publishContent/weGZH/components/editTitleCoverModal/index.tsx

@@ -4,16 +4,18 @@ import {
 	Form,
 	Form,
 	Radio,
 	Radio,
 	Input,
 	Input,
-	Button,
 	Upload,
 	Upload,
 	Image,
 	Image,
 	Space,
 	Space,
 	message,
 	message,
 } from 'antd';
 } from 'antd';
-import { UploadOutlined, PlusOutlined, CheckCircleFilled } from '@ant-design/icons';
+import { PlusOutlined, CheckCircleFilled } from '@ant-design/icons';
 import type { RadioChangeEvent } from 'antd';
 import type { RadioChangeEvent } from 'antd';
 import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
 import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
 import { VideoItem } from '../types';
 import { VideoItem } from '../types';
+import { getAccessToken } from '@src/http/sso';
+import { adFileUpload, getVideoContentCoverFrameListApi } from '@src/http/api';
+import http from '@src/http';
 
 
 const { TextArea } = Input;
 const { TextArea } = Input;
 
 
@@ -24,39 +26,37 @@ interface EditTitleCoverModalProps {
 	video: VideoItem | null;
 	video: VideoItem | null;
 }
 }
 
 
-// Mock screenshots for demonstration
-const mockScreenshots = [
-	'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png',
-	'https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.jpeg',
-	'https://gw.alipayobjects.com/zos/antfincdn/cV16ZqzMjW/photo-1473091540282-9b846e7965e3.jpeg',
-];
-
 const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCancel, onOk, video }) => {
 const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCancel, onOk, video }) => {
 	const [form] = Form.useForm();
 	const [form] = Form.useForm();
 	const [titleType, setTitleType] = useState<'original' | 'custom'>('original');
 	const [titleType, setTitleType] = useState<'original' | 'custom'>('original');
 	const [coverType, setCoverType] = useState<'original' | 'screenshot' | 'upload'>('original');
 	const [coverType, setCoverType] = useState<'original' | 'screenshot' | 'upload'>('original');
 	const [selectedScreenshot, setSelectedScreenshot] = useState<string | null>(null);
 	const [selectedScreenshot, setSelectedScreenshot] = useState<string | null>(null);
 	const [fileList, setFileList] = useState<UploadFile[]>([]);
 	const [fileList, setFileList] = useState<UploadFile[]>([]);
-
+	const [screenshotImagesList, setScreenshotImagesList] = useState<string[]>([]);
+	useEffect(() => {
+		if (visible) { 
+			getScreenshotImagesList()
+		}
+	}, [visible])
 	useEffect(() => {
 	useEffect(() => {
 		if (video && visible) {
 		if (video && visible) {
 			// Reset form based on incoming video data
 			// Reset form based on incoming video data
-			const hasCustomTitle = video.customTitle && video.customTitle !== video.title;
-			const isCustomCover = video.customThumbnail && video.customThumbnail !== video.thumbnail;
-			const isScreenshotCover = mockScreenshots.includes(video.customThumbnail || '');
+			const hasCustomTitle = video.titleIsEdit === 1;
+			const isCustomCover = video.coverIsEdit === 1;
+			const isScreenshotCover = screenshotImagesList?.includes(video.cover || '');
 
 
 			const initialTitleType = hasCustomTitle ? 'custom' : 'original';
 			const initialTitleType = hasCustomTitle ? 'custom' : 'original';
 			let initialCoverType: 'original' | 'screenshot' | 'upload' = 'original';
 			let initialCoverType: 'original' | 'screenshot' | 'upload' = 'original';
 			if (isCustomCover) {
 			if (isCustomCover) {
 				if (isScreenshotCover) {
 				if (isScreenshotCover) {
 					initialCoverType = 'screenshot';
 					initialCoverType = 'screenshot';
-					setSelectedScreenshot(video.customThumbnail || null);
+					setSelectedScreenshot(video.cover || null);
 					setFileList([]);
 					setFileList([]);
 				} else {
 				} else {
 					initialCoverType = 'upload';
 					initialCoverType = 'upload';
 					setSelectedScreenshot(null);
 					setSelectedScreenshot(null);
 					// Assume customThumbnail for upload is a URL, create UploadFile structure
 					// Assume customThumbnail for upload is a URL, create UploadFile structure
-					setFileList([{ uid: '-1', name: 'custom_cover.png', status: 'done', url: video.customThumbnail }]);
+					setFileList([{ uid: '-1', name: 'custom_cover.png', status: 'done', url: video.cover }]);
 				}
 				}
 			} else {
 			} else {
 				setSelectedScreenshot(null);
 				setSelectedScreenshot(null);
@@ -67,7 +67,7 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 			setCoverType(initialCoverType);
 			setCoverType(initialCoverType);
 			form.setFieldsValue({
 			form.setFieldsValue({
 				titleType: initialTitleType,
 				titleType: initialTitleType,
-				customTitle: hasCustomTitle ? video.customTitle : '',
+				title: hasCustomTitle ? video.title : '',
 				coverType: initialCoverType,
 				coverType: initialCoverType,
 			});
 			});
 		} else {
 		} else {
@@ -78,17 +78,27 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 			setSelectedScreenshot(null);
 			setSelectedScreenshot(null);
 			setFileList([]);
 			setFileList([]);
 		}
 		}
-	}, [video, visible, form]);
+	}, [video, visible, form, screenshotImagesList]);
 
 
+	const getScreenshotImagesList = async () => {
+		const res = await http.post<string[]>(getVideoContentCoverFrameListApi, {
+			videoId: video?.videoId
+		})
+		if (res.code === 0) { 
+			setScreenshotImagesList(res.data || [])
+		}
+	}
 	const handleOk = () => {
 	const handleOk = () => {
 		form.validateFields().then(values => {
 		form.validateFields().then(values => {
 			const updatedData: Partial<VideoItem> = {};
 			const updatedData: Partial<VideoItem> = {};
 
 
 			// Handle Title
 			// Handle Title
 			if (values.titleType === 'custom') {
 			if (values.titleType === 'custom') {
-				updatedData.customTitle = values.customTitle || '';
+				updatedData.titleIsEdit = 1;
+				updatedData.title = values.title || '';
 			} else {
 			} else {
-				updatedData.customTitle = video?.title; // Revert to original if selected
+				updatedData.titleIsEdit = 0;
+				updatedData.title = video?.title; // Revert to original if selected
 			}
 			}
 
 
 			// Handle Cover
 			// Handle Cover
@@ -97,7 +107,8 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 					message.error('请选择一个视频截图作为封面');
 					message.error('请选择一个视频截图作为封面');
 					return;
 					return;
 				}
 				}
-				updatedData.customThumbnail = selectedScreenshot;
+				updatedData.coverIsEdit = 1;
+				updatedData.cover = selectedScreenshot;
 			} else if (values.coverType === 'upload') {
 			} else if (values.coverType === 'upload') {
 				if (fileList.length === 0 || !fileList[0].url) {
 				if (fileList.length === 0 || !fileList[0].url) {
 					// If using status, check for 'done' status and response URL
 					// If using status, check for 'done' status and response URL
@@ -106,9 +117,10 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 				}
 				}
 				// Assuming the upload process directly provides the final URL in fileList[0].url
 				// Assuming the upload process directly provides the final URL in fileList[0].url
 				// In a real app, you might get this from upload response
 				// In a real app, you might get this from upload response
-				updatedData.customThumbnail = fileList[0].url;
+				updatedData.cover = fileList[0].url;
+				updatedData.coverIsEdit = 1;
 			} else { // Original
 			} else { // Original
-				updatedData.customThumbnail = video?.thumbnail; // Revert to original
+				updatedData.cover = video?.cover; // Revert to original
 			}
 			}
 
 
 			onOk(updatedData);
 			onOk(updatedData);
@@ -118,6 +130,7 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 	};
 	};
 
 
 	const handleUploadChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
 	const handleUploadChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
+		console.log('newFileList', newFileList);
 		// Only keep the latest file
 		// Only keep the latest file
 		setFileList(newFileList.slice(-1));
 		setFileList(newFileList.slice(-1));
 		// If upload is successful, manually set coverType to 'upload'
 		// If upload is successful, manually set coverType to 'upload'
@@ -127,7 +140,7 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 			// In a real app, you would get the URL from the response
 			// In a real app, you would get the URL from the response
 			// For demo: assuming the file object gets a url property after upload
 			// For demo: assuming the file object gets a url property after upload
 			if (!newFileList[0].url) {
 			if (!newFileList[0].url) {
-				newFileList[0].url = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'; // Placeholder URL
+				newFileList[0].url = newFileList[0].response.data.fileUrl; // Placeholder URL
 			}
 			}
 		}
 		}
 	};
 	};
@@ -156,6 +169,18 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 			<div style={{ marginTop: 8 }}>上传封面</div>
 			<div style={{ marginTop: 8 }}>上传封面</div>
 		</button>
 		</button>
 	);
 	);
+	const checkFile = (file:UploadFile) => {
+		if ((file.size || 0) > 5 * 1024 * 1024) {
+			message.error('图片大小不能超过5MB')
+			return Upload.LIST_IGNORE // 阻止上传,如果返回false,该文件还是会出现在fileList中
+		}
+		
+		return true // 允许上传
+	}
+	
+	const headers = {
+		token: getAccessToken()
+	}
 
 
 	return (
 	return (
 		<Modal
 		<Modal
@@ -180,7 +205,7 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 				{titleType === 'custom' && (
 				{titleType === 'custom' && (
 					<Form.Item
 					<Form.Item
 						label="自定义标题"
 						label="自定义标题"
-						name="customTitle"
+						name="title"
 						rules={[{ required: true, message: '请输入自定义标题' }]}
 						rules={[{ required: true, message: '请输入自定义标题' }]}
 					>
 					>
 						<TextArea rows={2} maxLength={50} showCount placeholder="请输入标题" />
 						<TextArea rows={2} maxLength={50} showCount placeholder="请输入标题" />
@@ -200,7 +225,7 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 				{coverType === 'screenshot' && (
 				{coverType === 'screenshot' && (
 					<Form.Item label="视频截图" wrapperCol={{ offset: 4, span: 20 }}>
 					<Form.Item label="视频截图" wrapperCol={{ offset: 4, span: 20 }}>
 						<Space wrap>
 						<Space wrap>
-							{mockScreenshots.map((imgUrl, index) => (
+							{screenshotImagesList.map((imgUrl, index) => (
 								<div
 								<div
 									key={index}
 									key={index}
 									className={`relative w-24 h-24 border-2 cursor-pointer ${selectedScreenshot === imgUrl ? 'border-blue-500' : 'border-transparent'}`}
 									className={`relative w-24 h-24 border-2 cursor-pointer ${selectedScreenshot === imgUrl ? 'border-blue-500' : 'border-transparent'}`}
@@ -221,19 +246,16 @@ const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCa
 					<Form.Item label="自定义上传" wrapperCol={{ offset: 4, span: 20 }}>
 					<Form.Item label="自定义上传" wrapperCol={{ offset: 4, span: 20 }}>
 						<Upload
 						<Upload
 							// Replace with your actual upload endpoint
 							// Replace with your actual upload endpoint
-							action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
+							action={adFileUpload}
+							headers={headers}
+							accept="image/*"
 							listType="picture-card"
 							listType="picture-card"
-							fileList={fileList}
+							beforeUpload={checkFile}
 							onChange={handleUploadChange}
 							onChange={handleUploadChange}
+							fileList={fileList}
+							showUploadList={{ showPreviewIcon: false }}
 							maxCount={1}
 							maxCount={1}
-							accept=".png,.jpg,.jpeg,.gif"
-							beforeUpload={(file) => {
-								const isLt2M = file.size / 1024 / 1024 < 2;
-								if (!isLt2M) {
-									message.error('图片必须小于 2MB!');
-								}
-								return isLt2M;
-							}}
+							data={{ fileType: 'PICTURE' }}
 						>
 						>
 							{fileList.length >= 1 ? null : uploadButton}
 							{fileList.length >= 1 ? null : uploadButton}
 						</Upload>
 						</Upload>

+ 57 - 38
src/views/publishContent/weGZH/components/publishPlanModal/index.tsx

@@ -1,10 +1,11 @@
 import React, { useEffect, useState } from 'react';
 import React, { useEffect, useState } from 'react';
-import { Modal, Form, Select, Button, Row, Col, Card, Typography, message } from 'antd';
-import { CloseOutlined, PlusOutlined, EditOutlined, PlayCircleOutlined, CaretRightFilled } from '@ant-design/icons';
+import { Modal, Form, Select, Button, Card, Typography, message } from 'antd';
+import { CloseOutlined, PlusOutlined, EditOutlined, CaretRightFilled } from '@ant-design/icons';
 import VideoSelectModal from '../videoSelectModal';
 import VideoSelectModal from '../videoSelectModal';
 import EditTitleCoverModal from '../editTitleCoverModal';
 import EditTitleCoverModal from '../editTitleCoverModal';
 import { VideoItem } from '../types'; // Import from common types
 import { VideoItem } from '../types'; // Import from common types
-import { PlanData } from '../..';
+import { GzhPlanType } from '../../hooks/useGzhPlanList';
+import { useAccountOptions } from '../../hooks/useAccountOptions';
 
 
 const { Option } = Select;
 const { Option } = Select;
 const { Text } = Typography;
 const { Text } = Typography;
@@ -12,21 +13,24 @@ const { Text } = Typography;
 interface AddPunlishPlanModalProps {
 interface AddPunlishPlanModalProps {
 	visible: boolean;
 	visible: boolean;
 	onCancel: () => void;
 	onCancel: () => void;
-	onOk: (values: any) => void; // Pass form values on OK
+	onOk: (values: GzhPlanType) => void; // Pass form values on OK
 	actionType: 'add' | 'edit';
 	actionType: 'add' | 'edit';
-	editPlanData?: PlanData;
+	editPlanData?: GzhPlanType;
+	isSubmiting?: boolean;
 }
 }
 
 
-const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCancel, onOk, actionType, editPlanData }) => {
+const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, isSubmiting, onCancel, onOk, actionType, editPlanData }) => {
 	const [form] = Form.useForm();
 	const [form] = Form.useForm();
 	const [selectedVideos, setSelectedVideos] = useState<VideoItem[]>([]);
 	const [selectedVideos, setSelectedVideos] = useState<VideoItem[]>([]);
 	const [isVideoSelectVisible, setIsVideoSelectVisible] = useState(false);
 	const [isVideoSelectVisible, setIsVideoSelectVisible] = useState(false);
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null); // State for video player modal
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null); // State for video player modal
 	const [editingVideo, setEditingVideo] = useState<VideoItem | null>(null); // State for editing modal
 	const [editingVideo, setEditingVideo] = useState<VideoItem | null>(null); // State for editing modal
+	const { accountOptions } = useAccountOptions();
 
 
 	useEffect(() => {
 	useEffect(() => {
 		if (actionType === 'edit') {
 		if (actionType === 'edit') {
 			form.setFieldsValue(editPlanData);
 			form.setFieldsValue(editPlanData);
+			setSelectedVideos(editPlanData?.videoList || []);
 		}
 		}
 	}, [actionType, editPlanData]);
 	}, [actionType, editPlanData]);
 
 
@@ -43,9 +47,9 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 				const videosToSubmit = selectedVideos.map(v => ({
 				const videosToSubmit = selectedVideos.map(v => ({
 					...v,
 					...v,
 					title: v.customTitle || v.title,
 					title: v.customTitle || v.title,
-					thumbnail: v.customThumbnail || v.thumbnail,
+					cover: v.customCover || v.cover,
 				}));
 				}));
-				onOk({ ...values, videos: videosToSubmit });
+				onOk({ ...values, scene: 0, videoList: videosToSubmit });
 			})
 			})
 			.catch((info) => {
 			.catch((info) => {
 				console.log('Validate Failed:', info);
 				console.log('Validate Failed:', info);
@@ -53,7 +57,7 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 	};
 	};
 
 
 	const removeVideo = (idToRemove: number) => {
 	const removeVideo = (idToRemove: number) => {
-		setSelectedVideos(currentVideos => currentVideos.filter(video => video.id !== idToRemove));
+		setSelectedVideos(currentVideos => currentVideos.filter(video => video.videoId !== idToRemove));
 	};
 	};
 
 
 	const openVideoSelector = () => {
 	const openVideoSelector = () => {
@@ -64,13 +68,13 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 		// Merge existing custom data with newly selected videos
 		// Merge existing custom data with newly selected videos
 		const currentCustomData = new Map<number, Partial<VideoItem>>();
 		const currentCustomData = new Map<number, Partial<VideoItem>>();
 		selectedVideos.forEach(v => {
 		selectedVideos.forEach(v => {
-			if (v.customTitle || v.customThumbnail) {
-				currentCustomData.set(v.id, { customTitle: v.customTitle, customThumbnail: v.customThumbnail });
+			if (v.cover) {
+				currentCustomData.set(v.videoId, { title: v.title, cover: v.cover });
 			}
 			}
 		});
 		});
 
 
 		const mergedVideos = newlySelectedVideos.map(newVideo => {
 		const mergedVideos = newlySelectedVideos.map(newVideo => {
-			const customData = currentCustomData.get(newVideo.id);
+			const customData = currentCustomData.get(newVideo.videoId);
 			return { ...newVideo, ...customData };
 			return { ...newVideo, ...customData };
 		});
 		});
 
 
@@ -95,9 +99,10 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 	};
 	};
 
 
 	const handleEditOk = (updatedData: Partial<VideoItem>) => {
 	const handleEditOk = (updatedData: Partial<VideoItem>) => {
+		console.log('updatedData', updatedData);
 		setSelectedVideos(currentVideos =>
 		setSelectedVideos(currentVideos =>
 			currentVideos.map(v =>
 			currentVideos.map(v =>
-				v.id === editingVideo?.id ? { ...v, ...updatedData } : v
+				v.videoId === editingVideo?.videoId ? { ...v, ...updatedData } : v
 			)
 			)
 		);
 		);
 		setEditingVideo(null); // Close modal
 		setEditingVideo(null); // Close modal
@@ -112,22 +117,28 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 			<Modal
 			<Modal
 				title="创建发布计划"
 				title="创建发布计划"
 				open={visible}
 				open={visible}
+				destroyOnClose
 				onCancel={onCancel}
 				onCancel={onCancel}
 				width={900} // Adjust width as needed
 				width={900} // Adjust width as needed
 				footer={[
 				footer={[
 					<Button key="back" onClick={onCancel}>
 					<Button key="back" onClick={onCancel}>
 						取消
 						取消
 					</Button>,
 					</Button>,
-					<Button key="submit" type="primary" onClick={handleOk}>
+					<Button key="submit" type="primary" loading={isSubmiting} onClick={handleOk}>
 						确定
 						确定
 					</Button>,
 					</Button>,
 				]}
 				]}
 				zIndex={10}
 				zIndex={10}
-				destroyOnClose
 			>
 			>
 				<Form form={form} layout="vertical">
 				<Form form={form} layout="vertical">
 					<Form.Item
 					<Form.Item
-						name="publisher"
+						name="id"
+						label="计划ID"
+						hidden
+					>
+					</Form.Item>
+					<Form.Item
+						name="publishStage"
 						label="发布方"
 						label="发布方"
 						labelCol={{ span: 4 }}
 						labelCol={{ span: 4 }}
 						labelAlign='left'
 						labelAlign='left'
@@ -135,38 +146,45 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 						rules={[{ required: true, message: '请选择发布方' }]}
 						rules={[{ required: true, message: '请选择发布方' }]}
 					>
 					>
 						<Select placeholder="选择发布方" allowClear className='!w-50'>
 						<Select placeholder="选择发布方" allowClear className='!w-50'>
-							<Option value="platform">平台发布</Option>
-							<Option value="user">用户发布</Option>
+							<Option value={0}>平台发布</Option>
+							<Option value={1}>用户发布</Option>
 						</Select>
 						</Select>
 					</Form.Item>
 					</Form.Item>
 					<Form.Item
 					<Form.Item
-						name="officialAccount"
+						name="accountId"
 						label="公众号名称"
 						label="公众号名称"
 						labelCol={{ span: 4 }}
 						labelCol={{ span: 4 }}
 						labelAlign='left'
 						labelAlign='left'
 						layout="horizontal"
 						layout="horizontal"
 						rules={[{ required: true, message: '请选择公众号' }]}
 						rules={[{ required: true, message: '请选择公众号' }]}
 					>
 					>
-						<Select placeholder="选择公众号" allowClear className='!w-50'>
-							<Option value="account1">小慧爱厨房</Option>
-							<Option value="account2">小阳看天下</Option>
-							{/* Add more options as needed */}
+						<Select
+							placeholder="选择公众号"
+							allowClear
+							className='!w-50'
+							options={accountOptions.map(option => ({ label: option.name, value: option.id }))}>
 						</Select>
 						</Select>
 					</Form.Item>
 					</Form.Item>
 					<Form.Item
 					<Form.Item
+						name="scene"
 						label="发布场景"
 						label="发布场景"
 						layout="horizontal"
 						layout="horizontal"
 						labelCol={{ span: 4 }}
 						labelCol={{ span: 4 }}
 						labelAlign='left'
 						labelAlign='left'
 					>
 					>
-						<Text>关注回复</Text>
+						<Select
+							placeholder="发布场景"
+							className='!w-50'
+							defaultValue={0}
+							options={[{ label: '关注回复', value: 0 }]}>
+						</Select>
 					</Form.Item>
 					</Form.Item>
 
 
 					<Form.Item label="发布内容" required>
 					<Form.Item label="发布内容" required>
 						<div className="flex flex-wrap gap-4">
 						<div className="flex flex-wrap gap-4">
 							{selectedVideos.map((video) => (
 							{selectedVideos.map((video) => (
 								<Card
 								<Card
-									key={video.id}
+									key={video.videoId}
 									className="w-[240px] relative group"
 									className="w-[240px] relative group"
 								>
 								>
 									<Button
 									<Button
@@ -174,25 +192,26 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 										icon={<CloseOutlined />}
 										icon={<CloseOutlined />}
 										className="!absolute top-1 right-1 z-10 bg-gray-400 bg-opacity-50 border-none text-white hidden group-hover:inline-flex justify-center items-center"
 										className="!absolute top-1 right-1 z-10 bg-gray-400 bg-opacity-50 border-none text-white hidden group-hover:inline-flex justify-center items-center"
 										size="small"
 										size="small"
-										onClick={() => removeVideo(video.id)}
+										onClick={() => removeVideo(video.videoId)}
 									/>
 									/>
 									<div className="p-0">
 									<div className="p-0">
-										<Text type="secondary" className="text-xs">{video.source}</Text>
-										<Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.customTitle || video.title}>{video.customTitle || video.title}</Text>
+										<Text type="secondary" className="text-xs">票圈 | 3亿人喜欢的视频平台</Text>
+										<Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.title}>{video.title}</Text>
 									</div>
 									</div>
-									<div className="relative h-[120px] bg-gray-200 cursor-pointer group/thumb"
-										 onClick={(e) => {
-											 e.stopPropagation(); // Prevent card selection if clicking thumbnail/play
-											 playVideo(video);
-										 }}
+									<div
+										className="relative h-[120px] bg-gray-200 cursor-pointer group/thumb"
+										onClick={(e) => {
+											e.stopPropagation(); // Prevent card selection if clicking thumbnail/play
+											playVideo(video);
+										}}
 									>
 									>
-										<img src={video.customThumbnail || video.thumbnail} alt={video.customTitle || video.title} className="w-full h-full object-cover" />
+										<img src={video.cover} referrerPolicy="no-referrer" alt={video.title} className="w-full h-full object-cover" />
 										<div className="absolute inset-0 flex justify-center items-center cursor-pointer">
 										<div className="absolute inset-0 flex justify-center items-center cursor-pointer">
 											<CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
 											<CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
 										</div>
 										</div>
 									</div>
 									</div>
 									<div className="p-3">
 									<div className="p-3">
-										<Text type="secondary" className="text-xs">传播效率: {video.spreadEfficiency.toFixed(2)}</Text>
+										<Text type="secondary" className="text-xs">传播效率: {video.score?.toFixed(2)}</Text>
 										<Button
 										<Button
 											icon={<EditOutlined />}
 											icon={<EditOutlined />}
 											className="w-full mt-2"
 											className="w-full mt-2"
@@ -224,14 +243,14 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 				visible={isVideoSelectVisible}
 				visible={isVideoSelectVisible}
 				onClose={handleVideoSelectionCancel}
 				onClose={handleVideoSelectionCancel}
 				onOk={handleVideoSelectionOk}
 				onOk={handleVideoSelectionOk}
-				initialSelectedIds={selectedVideos.map(v => v.id)} // Pass current selection IDs
+				initialSelectedIds={selectedVideos.map(v => v.videoId)} // Pass current selection IDs
 			/>
 			/>
 
 
 			{/* Video Player Modal */}
 			{/* Video Player Modal */}
 			<Modal
 			<Modal
 				open={!!playingVideo}
 				open={!!playingVideo}
 				onCancel={closeVideoPlayer}
 				onCancel={closeVideoPlayer}
-				title={playingVideo?.customTitle || playingVideo?.title}
+				title={playingVideo?.title}
 				footer={null}
 				footer={null}
 				destroyOnClose // Unmount video element when closed
 				destroyOnClose // Unmount video element when closed
 				width={720} // Adjust as needed
 				width={720} // Adjust as needed
@@ -243,7 +262,7 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
 						controls
 						controls
 						autoPlay
 						autoPlay
 						className="w-full h-auto max-h-[80vh] block"
 						className="w-full h-auto max-h-[80vh] block"
-						src={playingVideo.videoUrl}
+						src={playingVideo.video}
 					>
 					>
 						Your browser does not support the video tag.
 						Your browser does not support the video tag.
 					</video>
 					</video>

+ 14 - 8
src/views/publishContent/weGZH/components/types.ts

@@ -1,10 +1,16 @@
 export interface VideoItem {
 export interface VideoItem {
-	id: number;
-	source: string;
-	title: string;
-	thumbnail: string; // URL to original thumbnail image
-	spreadEfficiency: number;
-	videoUrl: string;
-	customTitle?: string; // Optional custom title
-	customThumbnail?: string; // Optional URL to custom thumbnail image
+	cover: string,
+	coverIsEdit: number,
+	score: number,
+	title: string,
+	titleIsEdit: number,
+	video: string,
+	videoId: number,
+	customCover: string,
+	customTitle: string,
 } 
 } 
+
+export interface VideoListResponse {
+	objs: VideoItem[],
+	totalSize: number
+}

+ 46 - 29
src/views/publishContent/weGZH/components/videoSelectModal/index.tsx

@@ -4,20 +4,19 @@ import {
 	Button,
 	Button,
 	Select,
 	Select,
 	Input,
 	Input,
-	Row,
-	Col,
 	Card,
 	Card,
 	Typography,
 	Typography,
 	Pagination,
 	Pagination,
-	Checkbox,
 	Space,
 	Space,
 	message,
 	message,
 	Modal,
 	Modal,
 } from 'antd';
 } from 'antd';
-import { EditOutlined, PlayCircleOutlined, CheckCircleFilled, CaretRightFilled } from '@ant-design/icons';
-import { VideoItem } from '../types';
+import { CheckCircleFilled, CaretRightFilled } from '@ant-design/icons';
+import { VideoItem, VideoListResponse } from '../types';
+import http from '@src/http';
+import { getVideoContentListApi } from '@src/http/api';
+import { useVideoCategoryOptions } from '../../hooks/useVideoCategoryOptions';
 
 
-const { Option } = Select;
 const { Text } = Typography;
 const { Text } = Typography;
 
 
 interface VideoSelectModalProps {
 interface VideoSelectModalProps {
@@ -27,25 +26,42 @@ interface VideoSelectModalProps {
 	initialSelectedIds?: number[];
 	initialSelectedIds?: number[];
 }
 }
 
 
-// Mock data for demonstration - Ensure videoUrl is added
-const mockVideos: VideoItem[] = Array.from({ length: 15 }, (_, i) => ({
-	id: i + 100,
-	source: '票圈 | 3亿人喜欢的视频平台',
-	title: `最美中国——上海之海 ${i + 1},带你领略航拍风景`,
-	thumbnail: 'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png',
-	spreadEfficiency: 8.56 + i * 0.1,
-	videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4', // Example video URL
-}));
-
 const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, onOk, initialSelectedIds = [] }) => {
 const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, onOk, initialSelectedIds = [] }) => {
+	const { videoCategoryOptions } = useVideoCategoryOptions();
 	const [category, setCategory] = useState<string>();
 	const [category, setCategory] = useState<string>();
 	const [searchTerm, setSearchTerm] = useState<string>('');
 	const [searchTerm, setSearchTerm] = useState<string>('');
 	const [currentPage, setCurrentPage] = useState(1);
 	const [currentPage, setCurrentPage] = useState(1);
 	const [pageSize, setPageSize] = useState(10);
 	const [pageSize, setPageSize] = useState(10);
+	const [total, setTotal] = useState(0);
+	const [loading, setLoading] = useState(false);
+	const [videoList, setVideoList] = useState<VideoItem[]>([]);
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
 	const MAX_SELECTION = 3;
 	const MAX_SELECTION = 3;
 
 
+	const getVideoList = async (pageNum?: number) => {
+		setLoading(true);
+		setCurrentPage(pageNum || currentPage);
+		const res = await http.post<VideoListResponse>(getVideoContentListApi, {
+			category,
+			title: searchTerm,
+			pageNum: pageNum || currentPage,
+			pageSize: pageSize,
+		}).catch(() => {
+			message.error('获取视频列表失败');
+		}).finally(() => {
+			setLoading(false);
+		});
+		if (res && res.code === 0) {
+			setVideoList(res.data.objs);
+			setTotal(res.data.totalSize);
+		}
+	}
+
+	useEffect(() => {
+		getVideoList();
+	}, []);
+
 	useEffect(() => {
 	useEffect(() => {
 		if (visible) {
 		if (visible) {
 			setSelectedVideoIds(new Set(initialSelectedIds));
 			setSelectedVideoIds(new Set(initialSelectedIds));
@@ -55,6 +71,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 	const handleSearch = () => {
 	const handleSearch = () => {
 		console.log('Searching for:', { category, searchTerm });
 		console.log('Searching for:', { category, searchTerm });
 		setCurrentPage(1);
 		setCurrentPage(1);
+		getVideoList();
 	};
 	};
 
 
 	const handleSelectVideo = (videoId: number) => {
 	const handleSelectVideo = (videoId: number) => {
@@ -74,7 +91,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 	};
 	};
 
 
 	const handleOk = () => {
 	const handleOk = () => {
-		const selectedVideos = mockVideos.filter(video => selectedVideoIds.has(video.id));
+		const selectedVideos = videoList.filter(video => selectedVideoIds.has(video.videoId));
 		onOk(selectedVideos);
 		onOk(selectedVideos);
 		onClose();
 		onClose();
 	};
 	};
@@ -87,8 +104,6 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		setPlayingVideo(null);
 		setPlayingVideo(null);
 	};
 	};
 
 
-	const paginatedData = mockVideos.slice((currentPage - 1) * pageSize, currentPage * pageSize);
-
 	return (
 	return (
 		<>
 		<>
 			<Drawer
 			<Drawer
@@ -97,16 +112,18 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 				onClose={onClose}
 				onClose={onClose}
 				width={800}
 				width={800}
 				placement="right"
 				placement="right"
+				loading={loading}
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				footer={
 				footer={
 					<div className="flex justify-between items-center">
 					<div className="flex justify-between items-center">
 						<Pagination
 						<Pagination
 							current={currentPage}
 							current={currentPage}
 							pageSize={pageSize}
 							pageSize={pageSize}
-							total={mockVideos.length}
+							total={total}
 							onChange={(page, size) => {
 							onChange={(page, size) => {
 								setCurrentPage(page);
 								setCurrentPage(page);
 								setPageSize(size);
 								setPageSize(size);
+								getVideoList(page);
 							}}
 							}}
 							pageSizeOptions={['10', '20', '50']}
 							pageSizeOptions={['10', '20', '50']}
 							size="small"
 							size="small"
@@ -129,7 +146,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 							style={{ width: 180 }}
 							style={{ width: 180 }}
 							value={category}
 							value={category}
 							onChange={setCategory}
 							onChange={setCategory}
-							options={[{ label: '品类A', value: 'A' }, { label: '品类B', value: 'B' }]}
+							options={videoCategoryOptions.map(option => ({ label: option, value: option }))}
 						/>
 						/>
 					</div>
 					</div>
 					<div className="flex items-center gap-2">
 					<div className="flex items-center gap-2">
@@ -145,31 +162,31 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 				</div>
 				</div>
 
 
 				<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
 				<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
-					{paginatedData.map((video) => {
-						const isSelected = selectedVideoIds.has(video.id);
+					{videoList.map((video) => {
+						const isSelected = selectedVideoIds.has(video.videoId);
 						const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
 						const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
 						return (
 						return (
 							<Card
 							<Card
-								key={video.id}
+								key={video.videoId}
 								className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2' : ''}`}
 								className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2' : ''}`}
 								styles={{ body: { padding: 0 } }}
 								styles={{ body: { padding: 0 } }}
-								onClick={() => !isDisabled && handleSelectVideo(video.id)}
+								onClick={() => !isDisabled && handleSelectVideo(video.videoId)}
 							>
 							>
 								<div className="p-3">
 								<div className="p-3">
-									<Text type="secondary" className="text-xs">{video.source}</Text>
+									<Text type="secondary" className="text-xs">票圈 | 3亿人喜欢的视频平台</Text>
 									<Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.title}>{video.title}</Text>
 									<Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.title}>{video.title}</Text>
 								</div>
 								</div>
 								<div
 								<div
 									className="relative h-[120px] bg-gray-200 group/thumb"
 									className="relative h-[120px] bg-gray-200 group/thumb"
 									onClick={(e) => { e.stopPropagation(); playVideo(video); }}
 									onClick={(e) => { e.stopPropagation(); playVideo(video); }}
 								>
 								>
-									<img src={video.thumbnail} alt={video.title} className="w-full h-full object-cover" />
+									<img src={video.cover} alt={video.title} referrerPolicy="no-referrer" className="w-full h-full object-cover" />
 									<div className="absolute inset-0 flex justify-center items-center cursor-pointer">
 									<div className="absolute inset-0 flex justify-center items-center cursor-pointer">
 										<CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
 										<CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
 									</div>
 									</div>
 								</div>
 								</div>
 								<div className="p-3 flex justify-between items-center">
 								<div className="p-3 flex justify-between items-center">
-									<Text type="secondary" className="text-xs">传播效率: {video.spreadEfficiency.toFixed(2)}</Text>
+									<Text type="secondary" className="text-xs">传播效率: {video.score?.toFixed(2)}</Text>
 									{isSelected ? (
 									{isSelected ? (
 										<CheckCircleFilled className="text-green-500 text-xl" />
 										<CheckCircleFilled className="text-green-500 text-xl" />
 									) : (
 									) : (
@@ -192,7 +209,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 				styles={{ body: { padding: 0, background: '#000' } }}
 				styles={{ body: { padding: 0, background: '#000' } }}
 			>
 			>
 				{playingVideo && (
 				{playingVideo && (
-					<video controls autoPlay className="w-full h-auto max-h-[80vh] block" src={playingVideo.videoUrl}>
+					<video controls autoPlay className="w-full h-auto max-h-[80vh] block" src={playingVideo.video}>
 						Your browser does not support the video tag.
 						Your browser does not support the video tag.
 					</video>
 					</video>
 				)}
 				)}

+ 2 - 11
src/views/publishContent/weGZH/hooks/useGzhPlanList.ts

@@ -1,16 +1,7 @@
 import { useState } from 'react';
 import { useState } from 'react';
 import { getGzhPlanListApi } from '@src/http/api';
 import { getGzhPlanListApi } from '@src/http/api';
 import request from '@src/http/index';
 import request from '@src/http/index';
-
-interface VideoType {
-  cover: string;
-	coverIsEdit: number;
-	score: number;
-	title: string;
-	titleIsEdit: number;
-	video: string;
-	videoId: number;
-}
+import { VideoItem } from '../components/types';
 
 
 export interface GzhPlanType {
 export interface GzhPlanType {
   accountId: number;
   accountId: number;
@@ -21,7 +12,7 @@ export interface GzhPlanType {
   scene: number;
   scene: number;
   title: string[];
   title: string[];
 	videoCount: number;
 	videoCount: number;
-	videoList: VideoType[];
+	videoList: VideoItem[];
 }
 }
 
 
 interface GzhPlanListParams {
 interface GzhPlanListParams {

+ 49 - 6
src/views/publishContent/weGZH/index.tsx

@@ -1,13 +1,14 @@
 import React, { useEffect, useState } from 'react';
 import React, { useEffect, useState } from 'react';
-import { Space, Table, Button, Input, Select, DatePicker, Tabs } from 'antd';
+import { Space, Table, Button, Input, Select, DatePicker, Tabs, message } from 'antd';
 import type { TableProps } from 'antd';
 import type { TableProps } from 'antd';
-import { Dayjs } from 'dayjs';
+import dayjs, { Dayjs } from 'dayjs';
 import styles from './index.module.css';
 import styles from './index.module.css';
 import PunlishPlanModal from './components/publishPlanModal';
 import PunlishPlanModal from './components/publishPlanModal';
 const { RangePicker } = DatePicker;
 const { RangePicker } = DatePicker;
 import { useAccountOptions } from '@src/views/publishContent/weGZH/hooks/useAccountOptions';
 import { useAccountOptions } from '@src/views/publishContent/weGZH/hooks/useAccountOptions';
 import { useGzhPlanList, GzhPlanType } from '@src/views/publishContent/weGZH/hooks/useGzhPlanList';
 import { useGzhPlanList, GzhPlanType } from '@src/views/publishContent/weGZH/hooks/useGzhPlanList';
-
+import http from '@src/http';
+import { saveGzhPlanApi } from '@src/http/api';
 const WeGZHContent: React.FC = () => {
 const WeGZHContent: React.FC = () => {
 	// 状态管理
 	// 状态管理
 	const [selectedAccount, setSelectedAccount] = useState<string>();
 	const [selectedAccount, setSelectedAccount] = useState<string>();
@@ -18,6 +19,7 @@ const WeGZHContent: React.FC = () => {
 	const [actionType, setActionType] = useState<'add' | 'edit'>('add');
 	const [actionType, setActionType] = useState<'add' | 'edit'>('add');
 	const [editPlanData, setEditPlanData] = useState<GzhPlanType>();
 	const [editPlanData, setEditPlanData] = useState<GzhPlanType>();
 	const [activeKey, setActiveKey] = useState<string>('1');
 	const [activeKey, setActiveKey] = useState<string>('1');
+	const [isSubmiting, setIsSubmiting] = useState<boolean>(false);
 
 
 	const { accountOptions } = useAccountOptions();
 	const { accountOptions } = useAccountOptions();
 	const { gzhPlanList, getGzhPlanList, totalSize } = useGzhPlanList();
 	const { gzhPlanList, getGzhPlanList, totalSize } = useGzhPlanList();
@@ -35,6 +37,9 @@ const WeGZHContent: React.FC = () => {
 			dataIndex: 'scene',
 			dataIndex: 'scene',
 			key: 'scene',
 			key: 'scene',
 			width: 120,
 			width: 120,
+			render: (_, record) => {
+				return record.scene === 0 ? '关注回复' : '自动回复';
+			}
 		},
 		},
 		{
 		{
 			title: '视频数量',
 			title: '视频数量',
@@ -53,6 +58,9 @@ const WeGZHContent: React.FC = () => {
 			dataIndex: 'createTimestamp',
 			dataIndex: 'createTimestamp',
 			key: 'createTimestamp',
 			key: 'createTimestamp',
 			width: 200,
 			width: 200,
+			render: (_, record) => {
+				return record.createTimestamp ? dayjs(record.createTimestamp).format('YYYY-MM-DD HH:mm:ss') : '';
+			}
 		},
 		},
 		{
 		{
 			title: '发布方',
 			title: '发布方',
@@ -60,7 +68,7 @@ const WeGZHContent: React.FC = () => {
 			key: 'publishStage',
 			key: 'publishStage',
 			width: 120,
 			width: 120,
 			render: (_, record) => {
 			render: (_, record) => {
-				return record.publishStage === 1 ? '平台发布' : '用户发布';
+				return record.publishStage === 0 ? '平台发布' : '用户发布';
 			}
 			}
 		},
 		},
 		{
 		{
@@ -84,9 +92,31 @@ const WeGZHContent: React.FC = () => {
 
 
 	const addPunlishPlan = () => {
 	const addPunlishPlan = () => {
 		setActionType('add');
 		setActionType('add');
+		setEditPlanData(undefined);
 		setIsShowAddPunlishPlan(true);
 		setIsShowAddPunlishPlan(true);
 	}
 	}
 
 
+	const handleAddPunlishPlan = async (params: GzhPlanType) => {
+		setIsSubmiting(true);
+		const res = await http.post<GzhPlanType>(saveGzhPlanApi, params)
+			.catch(err => {
+				message.error(err.msg);
+			})
+			.finally(() => {
+				setIsSubmiting(false);
+			});
+		if (res?.code === 0) {
+			message.success('发布计划创建成功');
+			getGzhPlanList({
+				pageNum: 1,
+				pageSize: 10,
+			});
+			setIsShowAddPunlishPlan(false);
+		} else {
+			message.error(res?.msg);
+		}
+	}
+
 	useEffect(() => {
 	useEffect(() => {
 		getGzhPlanList({
 		getGzhPlanList({
 			pageNum: 1,
 			pageNum: 1,
@@ -94,6 +124,18 @@ const WeGZHContent: React.FC = () => {
 		});
 		});
 	}, []);
 	}, []);
 
 
+	const handleSearch = () => {
+		getGzhPlanList({
+			pageNum: 1,
+			pageSize: 10,
+			title: videoTitle,
+			accountId: selectedAccount ? parseInt(selectedAccount) : undefined,
+			publishStage: selectedPublisher ? parseInt(selectedPublisher) : undefined,
+			createTimestampStart: dateRange?.[0]?.unix(),
+			createTimestampEnd: dateRange?.[1]?.unix(),
+		});
+	}
+
 	return (
 	return (
 		<div>
 		<div>
 			<div className="rounded-lg">
 			<div className="rounded-lg">
@@ -151,7 +193,7 @@ const WeGZHContent: React.FC = () => {
 						/>
 						/>
 					</div>
 					</div>
 
 
-					<Button type="primary" className="ml-2">搜索</Button>
+					<Button type="primary" className="ml-2" onClick={ handleSearch}>搜索</Button>
 				</div>
 				</div>
 				<Tabs
 				<Tabs
 					defaultActiveKey="1"
 					defaultActiveKey="1"
@@ -181,9 +223,10 @@ const WeGZHContent: React.FC = () => {
 				<PunlishPlanModal
 				<PunlishPlanModal
 					visible={isShowAddPunlishPlan}
 					visible={isShowAddPunlishPlan}
 					onCancel={() => setIsShowAddPunlishPlan(false)}
 					onCancel={() => setIsShowAddPunlishPlan(false)}
-					onOk={() => setIsShowAddPunlishPlan(false)}
+					onOk={handleAddPunlishPlan}
 					actionType={actionType}
 					actionType={actionType}
 					editPlanData={editPlanData}
 					editPlanData={editPlanData}
+					isSubmiting={isSubmiting}
 				/>
 				/>
 			</div>
 			</div>
 		</div>
 		</div>

+ 38 - 0
vite.test.config.ts

@@ -0,0 +1,38 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react-swc'
+import path, { resolve } from 'path'
+import { fileURLToPath } from 'node:url'
+import svgr from 'vite-plugin-svgr'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+
+// https://vitejs.dev/config/
+export default defineConfig({
+	mode: 'development',
+  plugins: [
+    react(),
+    svgr({ svgrOptions: { icon: true } })
+  ],
+  build: {
+    target: "esnext",
+  },
+  resolve: {
+    alias: {
+      '@src': resolve(__dirname, 'src'),
+      '@assets': resolve(__dirname, 'assets'),
+      '@': resolve(__dirname, '.'),
+    }
+  },
+  server: {
+    host: '0.0.0.0',
+    port: 3305,
+    proxy: {
+      '/api': {
+        target: 'https://testadmin.piaoquantv.com/',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, ''),
+      }
+    }
+  }
+})