소스 검색

Merge branch 'feature_260509_replace_upload_new_datasource' of Web/contentCooper into master

liulidong 1 주 전
부모
커밋
c6c8c4e9cf

+ 1 - 0
src/http/api.ts

@@ -30,6 +30,7 @@ export const getGzhPlanListApi = `${import.meta.env.VITE_API_URL}/contentPlatfor
 export const saveGzhPlanApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/gzh/save`
 export const deleteGzhPlanApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/gzh/delete`
 export const getQwPlanListApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/qw/list`
+export const qwPlanExportApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/qw/export`
 export const saveQwPlanApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/qw/save`
 export const deleteQwPlanApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/qw/delete`
 export const getVideoContentCategoryListApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/videoContentCategoryList`

+ 2 - 2
src/views/publishContent/weCom/components/addPlanModal/index.tsx

@@ -120,7 +120,7 @@ const AddPlanModal: React.FC<{
 				/>
 			</Form.Item>
 			<Form.Item label="发布内容" required>
-				<div className="flex flex-wrap gap-4">
+				<div className="flex flex-wrap gap-4 max-h-[660px] overflow-y-auto pr-2">
 						{selectedVideos.map((video) => (
 							<Card
 								key={video.videoId}
@@ -167,7 +167,7 @@ const AddPlanModal: React.FC<{
 						))}
 
 						{/* Add Video Button - Conditionally Rendered */}
-						{selectedVideos.length < 3 && (
+						{selectedVideos.length < 20 && (
 							<div
 								className="w-[240px] h-[316px] flex flex-col justify-center items-center  border border-dashed border-gray-300 rounded cursor-pointer dark:border-gray-600  hover:border-blue-500 hover:text-blue-500"
 								onClick={openVideoSelector} // Open the drawer on click

+ 2 - 1
src/views/publishContent/weCom/components/videoSelectModal/index.tsx

@@ -64,6 +64,7 @@ const renderDemandDetail = (video: WeVideoItem) => (
 			<Descriptions.Item label="三级渠道">{video.channelLevel3 || '-'}</Descriptions.Item>
 			<Descriptions.Item label="人群分组">{video.crowdSegment || '-'}</Descriptions.Item>
 			<Descriptions.Item label="需求策略">{video.demandStrategy || '-'}</Descriptions.Item>
+			<Descriptions.Item label="匹配手段">{video.matchMethod || '-'}</Descriptions.Item>
 			<Descriptions.Item label="需求类型">{video.demandType || '-'}</Descriptions.Item>
 			<Descriptions.Item label="需求过滤排序">{video.demandFilterSortStrategy || '-'}</Descriptions.Item>
 			<Descriptions.Item label="驱动维度时间">{video.driveDimensionTime || '-'}</Descriptions.Item>
@@ -148,7 +149,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 	const currentPageRef = useRef(1);
 	const getVideoListRef = useRef<(pageNum: number, mode: LoadMode) => Promise<void>>();
 	const reqIdRef = useRef(0);
-	const MAX_SELECTION = 3;
+	const MAX_SELECTION = 20;
 	const { uploadLogVideoPlay, uploadLogVideoPlayEnd, uploadLogVideoCollect, uploadLogVideoListQuery } = useLogger();
 	const debouncedUploadLogVideoListQuery = useMemo(() => debounce(uploadLogVideoListQuery, 500), [uploadLogVideoListQuery]);
 

+ 72 - 3
src/views/publishContent/weCom/index.tsx

@@ -1,10 +1,11 @@
 import React, { useEffect, useState } from 'react';
-import { Space, Table, Button, Input, Select, Tabs, message, Spin, Popconfirm } from 'antd';
+import { Space, Table, Button, Input, Select, Tabs, message, Spin, Popconfirm, DatePicker, Modal } from 'antd';
 import type { TableProps } from 'antd';
+import type { Dayjs } from 'dayjs';
 import styles from './index.module.css';
 import { AddWeComPlanParam, WeComPlan, WeComPlanListResponse, WeComPlanType } from './type';
 import request from '@src/http/index';
-import { deleteQwPlanApi, getQwPlanListApi, getShareQrLink, saveQwPlanApi } from "@src/http/api"
+import { deleteQwPlanApi, getQwPlanListApi, getShareQrLink, qwPlanExportApi, saveQwPlanApi } from "@src/http/api"
 import LinkDetailModal from './components/linkDetailModal';
 import PlanDetailModal from './components/planDetailModal';
 import http from '@src/http/index';
@@ -13,6 +14,9 @@ import VideoPlayModal from './components/videoPlayModal';
 import modal from 'antd/es/modal';
 import AddPlanModal from './components/addPlanModal';
 import { QRCodeSVG } from 'qrcode.react';
+import { DownloadOutlined } from '@ant-design/icons';
+
+const { RangePicker } = DatePicker;
 // Define a type for the expected API response (adjust if needed based on actual API)
 const TableHeight = window.innerHeight - 380;
 const WeGZHContent: React.FC = () => {
@@ -39,6 +43,11 @@ const WeGZHContent: React.FC = () => {
 	const [isVideoPlayModalVisible, setIsVideoPlayModalVisible] = useState<boolean>(false);
 	const [isAddPlanLoading, setIsAddPlanLoading] = useState<boolean>(false);
 
+	// 已选视频导出 modal
+	const [isExportModalVisible, setIsExportModalVisible] = useState<boolean>(false);
+	const [exportDateRange, setExportDateRange] = useState<[Dayjs, Dayjs] | undefined>(undefined);
+	const [isExporting, setIsExporting] = useState<boolean>(false);
+
 	const getTableData = (_pageNum?: number, _pageSize?: number) => {
 		if (_pageNum) { 
 			setPageNum(_pageNum);
@@ -191,6 +200,37 @@ const WeGZHContent: React.FC = () => {
 		setIsShowAddPunlishPlan(true);
 	}
 
+	const handleExport = () => {
+		setIsExporting(true);
+		request.post<string>(qwPlanExportApi, {
+			type: +activeKey,
+			scene: selectedPublisher,
+			subChannel,
+			title: videoTitle,
+			startDate: exportDateRange?.[0]?.format('YYYY-MM-DD'),
+			endDate: exportDateRange?.[1]?.format('YYYY-MM-DD'),
+		}).then(res => {
+			if (res.code === 0 && res.data) {
+				const a = document.createElement('a');
+				a.href = res.data;
+				a.target = '_blank';
+				a.rel = 'noopener noreferrer';
+				document.body.appendChild(a);
+				a.click();
+				document.body.removeChild(a);
+				message.success('已开始下载');
+				setIsExportModalVisible(false);
+				setExportDateRange(undefined);
+			} else {
+				message.error(res.msg || '导出失败');
+			}
+		}).catch(err => {
+			message.error(err.msg || '导出失败');
+		}).finally(() => {
+			setIsExporting(false);
+		});
+	}
+
 	const handleAddPlan = (param: AddWeComPlanParam) => {
 		setIsAddPlanLoading(true);
 		const { type, subChannel, videoList } = param;
@@ -279,7 +319,14 @@ const WeGZHContent: React.FC = () => {
 					activeKey={activeKey}
 					onChange={(key: string) => setActiveKey(key as WeComPlanType)}
 					tabBarExtraContent={
-						{ right: <Button type="primary" onClick={addPunlishPlan}>+ 创建发布</Button> }}
+						{
+							right: (
+								<Space>
+									<Button type="primary" icon={<DownloadOutlined />} onClick={() => setIsExportModalVisible(true)}>已选视频导出</Button>
+									<Button type="primary" onClick={addPunlishPlan}>+ 创建发布</Button>
+								</Space>
+							)
+						}}
 				/>		
 				{/* 表格区域 */}
 				<Table
@@ -328,6 +375,28 @@ const WeGZHContent: React.FC = () => {
 					videoUrl={editPlanData?.video || ''}
 					title={editPlanData?.title || ''}
 				/>
+				<Modal
+					title="已选视频导出"
+					open={isExportModalVisible}
+					onCancel={() => setIsExportModalVisible(false)}
+					onOk={handleExport}
+					confirmLoading={isExporting}
+					okText="确认导出"
+					cancelText="取消"
+				>
+					<div className="flex items-center gap-2 my-4">
+						<span className="text-gray-600">日期范围:</span>
+						<RangePicker
+							placeholder={['开始日期', '结束日期']}
+							value={exportDateRange}
+							onChange={(dates) => setExportDateRange(dates as [Dayjs, Dayjs] | undefined)}
+							allowClear
+						/>
+					</div>
+					<div className="text-gray-400 text-sm">
+						不选日期默认导出当天数据;最多导出 2000 条;会沿用当前页的「场景 / 子渠道 / 视频标题」筛选条件。
+					</div>
+				</Modal>
 			</div>
 		</Spin>
 	);

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

@@ -65,6 +65,7 @@ export interface WeVideoItem {
 	standardElement?: string;
 	categoryName?: string;
 	experimentId?: string;
+	matchMethod?: string;
 }
 
 export interface AddWeComPlanParam {

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

@@ -284,7 +284,7 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, isSu
 					<Form.Item label="发布内容" required>
 						{ 
 							selectVideoType === 0 ?
-								(<div className="flex flex-wrap gap-4">
+								(<div className="flex flex-wrap gap-4 max-h-[660px] overflow-y-auto pr-2">
 									{selectedVideos.map((video) => (
 										<Card
 											key={video.videoId}
@@ -326,7 +326,7 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, isSu
 									))}
 		
 									{/* Add Video Button - Conditionally Rendered */}
-									{selectedVideos.length < 3 && (
+									{selectedVideos.length < 20 && (
 										<div
 											className={`w-[240px] h-[316px] flex flex-col justify-center items-center border border-dashed rounded ${accountId ? 'border-gray-300 cursor-pointer dark:border-gray-600 hover:border-blue-500 hover:text-blue-500' : 'border-gray-200 text-gray-300 cursor-not-allowed bg-gray-50'}`}
 											onClick={openVideoSelector} // Open the drawer on click

+ 1 - 0
src/views/publishContent/weGZH/components/types.ts

@@ -39,6 +39,7 @@ export interface VideoItem {
 	standardElement?: string;
 	categoryName?: string;
 	experimentId?: string;
+	matchMethod?: string;
 }
 
 export interface VideoListResponse {

+ 2 - 1
src/views/publishContent/weGZH/components/videoSelectModal/index.tsx

@@ -65,6 +65,7 @@ const renderDemandDetail = (video: VideoItem) => (
 			<Descriptions.Item label="三级渠道">{video.channelLevel3 || '-'}</Descriptions.Item>
 			<Descriptions.Item label="人群分组">{video.crowdSegment || '-'}</Descriptions.Item>
 			<Descriptions.Item label="需求策略">{video.demandStrategy || '-'}</Descriptions.Item>
+			<Descriptions.Item label="匹配手段">{video.matchMethod || '-'}</Descriptions.Item>
 			<Descriptions.Item label="需求类型">{video.demandType || '-'}</Descriptions.Item>
 			<Descriptions.Item label="需求过滤排序">{video.demandFilterSortStrategy || '-'}</Descriptions.Item>
 			<Descriptions.Item label="驱动维度时间">{video.driveDimensionTime || '-'}</Descriptions.Item>
@@ -146,7 +147,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 	const currentPageRef = useRef(1);
 	const getVideoListRef = useRef<(pageNum: number, mode: LoadMode) => Promise<void>>();
 	const reqIdRef = useRef(0);
-	const MAX_SELECTION = 3;
+	const MAX_SELECTION = 20;
 
 	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
 	useEffect(() => { loadingRef.current = loading; }, [loading]);