소스 검색

Merge branch 'feature_qwAddParam' of Web/contentCooper into master

jihuaqiang 5 일 전
부모
커밋
833e95df43

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

@@ -0,0 +1,192 @@
+// 添加计划弹窗
+
+import React, { useEffect, useState } from 'react';
+import { Modal, Form, Input, Select, Button, Card, Typography, message } from 'antd';
+import { WeComPlanType, WeVideoItem, AddWeComPlanParam } from '../../type';
+import { CloseOutlined, PlusOutlined, CaretRightFilled } from '@ant-design/icons';
+import VideoSelectModal from '../videoSelectModal';
+import VideoPlayModal from '../videoPlayModal';
+
+const { Paragraph } = Typography;
+
+const AddPlanModal: React.FC<{
+	initType: string;
+	visible: boolean;
+	onClose: () => void;
+	onOk: (param: AddWeComPlanParam) => void;
+	isLoading: boolean;
+}> = ({ initType, visible, onClose, onOk, isLoading }) => {
+	const [form] = Form.useForm();
+	const type = Form.useWatch(['type'], form);
+	const [selectedVideos, setSelectedVideos] = useState<WeVideoItem[]>([]);
+	const [isVideoSelectVisible, setIsVideoSelectVisible] = useState(false);
+	const [playingVideo, setPlayingVideo] = useState<WeVideoItem | null>(null);
+	
+	useEffect(() => {
+		form.setFieldsValue({
+			type: initType,
+			subChannel: '',
+		});
+		setSelectedVideos([]);
+	}, [visible]);
+	const handleSelectVideo = (videos: WeVideoItem[]) => {
+		setSelectedVideos(videos);
+		setIsVideoSelectVisible(false);
+	}
+	const removeVideo = (videoId: number) => {
+		setSelectedVideos(selectedVideos.filter((video) => video.videoId !== videoId));
+	}
+	const openVideoSelector = () => {
+		setIsVideoSelectVisible(true);
+	}
+	const handleChangeSelectVideo = (videoId: number, scene: 0 | 1) => {
+		setSelectedVideos(selectedVideos.map((video) => video.videoId === videoId ? { ...video, scene } : video));
+	}
+	const playVideo = (video: WeVideoItem) => {
+		setPlayingVideo(video);
+	}
+	const handleOk = () => {
+		form.validateFields().then((values) => {
+			if (selectedVideos.length === 0) {
+				message.error('请选择发布内容');
+				return;
+			}
+			onOk({
+				type: values.type,
+				subChannel: values.subChannel,
+				videoList: selectedVideos,
+			});
+
+		});
+	}
+	const handleChangeType = (value: string) => {
+		form.setFieldsValue({
+			type: value,
+		});
+		setSelectedVideos(selectedVideos.map((video) => ({ ...video, scene: 0 })));
+	}
+
+	return <>
+	<Modal
+		width={1000}
+		open={visible}
+		onCancel={onClose}
+		title="创建企微发布内容"
+		destroyOnClose
+			onOk={handleOk}
+			okText="创建"
+			cancelText="取消"
+			confirmLoading={isLoading}
+		>
+		<Form form={form} layout="vertical">
+			<Form.Item label="发布类型" name="type">
+				<Select onChange={ handleChangeType }>
+					<Select.Option value="0">社群</Select.Option>
+					<Select.Option value="1">自动回复</Select.Option>
+				</Select>
+			</Form.Item>
+			<Form.Item 
+				label="子渠道" 
+				name="subChannel"
+				rules={[
+					{
+						pattern: /^[a-zA-Z0-9]*$/,
+						message: '只能输入英文和数字',
+					},
+					{
+						max: 20,
+						message: '最长不超过20个字符',
+					}
+				]}
+			>
+				<Input 
+					placeholder="请输入子渠道" 
+					allowClear 
+					maxLength={20}
+					onKeyPress={(e) => {
+						if (!/[a-zA-Z0-9]/.test(e.key)) {
+							e.preventDefault();
+						}
+					}}
+				/>
+			</Form.Item>
+			<Form.Item label="发布内容" required>
+				<div className="flex flex-wrap gap-4">
+						{selectedVideos.map((video) => (
+							<Card
+								key={video.videoId}
+								className="w-[240px] relative group"
+							>
+								<Button
+									shape="circle"
+									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"
+									size="small"
+									onClick={() => removeVideo(video.videoId)}
+								/>
+								<div className="p-0">
+									<Paragraph className="mt-1 !mb-1" ellipsis={{ rows: 2, tooltip: true }} title={video.title}>{video.title}</Paragraph>
+								</div>
+								<div
+									className="relative"
+									style={{ paddingBottom: '79.8%' }}
+									onClick={(e) => {
+										e.stopPropagation(); // Prevent card selection if clicking thumbnail/play
+										playVideo(video);
+									}}
+								>
+									<img src={video.cover} referrerPolicy="no-referrer" className="absolute inset-0 w-full h-full object-cover" />
+									<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" />
+									</div>
+								</div>
+								<div className="p-3 flex justify-between items-center relative z-10">
+									<Select
+										placeholder="选择场景"
+										style={{ width: 180 }}
+										defaultValue={video.scene ?? 0}
+										value={video.scene}
+										onChange={(value: 0 | 1) => handleChangeSelectVideo(video.videoId, value)}
+										options={ type === WeComPlanType.社群 ?
+											[{ label: '群发', value: 0 }, { label: '单发', value: 1 }]
+											: [{ label: '关注回复', value: 0 }]
+										}
+										onClick={(e) => e.stopPropagation()}
+									/>
+								</div>
+							</Card>
+						))}
+
+						{/* Add Video Button - Conditionally Rendered */}
+						{selectedVideos.length < 3 && (
+							<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
+							>
+								<PlusOutlined className="text-2xl mb-2" />
+								<Typography.Text>添加视频</Typography.Text>
+							</div>
+						)}
+					</div>	
+			</Form.Item>
+		</Form>
+	</Modal>
+	<VideoSelectModal
+		visible={isVideoSelectVisible}
+		onClose={() => {
+			setIsVideoSelectVisible(false);
+		}}
+		onOk={handleSelectVideo}
+		initialSelectedIds={selectedVideos.map((video) => video.videoId)}
+		planType={type}
+		/>
+		<VideoPlayModal
+			visible={!!playingVideo}
+			onClose={() => setPlayingVideo(null)}
+			videoUrl={playingVideo?.video || ''}
+			title={playingVideo?.title || ''}
+		/>
+	</>
+	};
+
+export default AddPlanModal;

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

@@ -28,7 +28,7 @@ interface VideoSelectModalProps {
 	initialSelectedIds?: number[];
 }
 
-const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible, onClose, onOk, initialSelectedIds = [] }) => {
+const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, onOk, initialSelectedIds = [] }) => {
 	const { videoCategoryOptions } = useVideoCategoryOptions();
 	const [category, setCategory] = useState<string>();
 	const [searchTerm, setSearchTerm] = useState<string>('');
@@ -114,26 +114,26 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		setPlayingVideo(null);
 	};
 
-	const handleChangeSelectVideo = (videoId: number, scene: number) => {
-		setVideoList((prev: WeVideoItem[]) => {
-			const newList = prev.map(video => {
-				if (video.videoId === videoId) {
-					return { ...video, scene: scene as 0 | 1 };
-				}
-				return video;
-			});
-			return newList;
-		});
-		setVideoListAll((prev: WeVideoItem[]) => {
-			const newList = prev.map(video => {
-				if (video.videoId === videoId) {
-					return { ...video, scene: scene as 0 | 1 };
-				}
-				return video;
-			});
-			return newList;
-		});
-	}
+	// const handleChangeSelectVideo = (videoId: number, scene: number) => {
+	// 	setVideoList((prev: WeVideoItem[]) => {
+	// 		const newList = prev.map(video => {
+	// 			if (video.videoId === videoId) {
+	// 				return { ...video, scene: scene as 0 | 1 };
+	// 			}
+	// 			return video;
+	// 		});
+	// 		return newList;
+	// 	});
+	// 	setVideoListAll((prev: WeVideoItem[]) => {
+	// 		const newList = prev.map(video => {
+	// 			if (video.videoId === videoId) {
+	// 				return { ...video, scene: scene as 0 | 1 };
+	// 			}
+	// 			return video;
+	// 		});
+	// 		return newList;
+	// 	});
+	// }
 
 	return (
 		<>
@@ -229,7 +229,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 										<div className={`w-5 h-5 border-2 ${isDisabled ? 'border-gray-200 bg-gray-100' : 'border-gray-300' } rounded-full`}></div>
 									)}
 								</div>
-								{isSelected && (
+								{/* {isSelected && (
 									<div className="p-3 flex justify-between items-center relative z-10">
 										<Select
 											placeholder="选择场景"
@@ -244,7 +244,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 											onClick={(e) => e.stopPropagation()}
 										/>
 									</div>
-								)}
+								)} */}
 							</Card>
 						);
 					})}

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

@@ -2,21 +2,22 @@ import React, { useEffect, useState } from 'react';
 import { Space, Table, Button, Input, Select, Tabs, message, Spin, Popconfirm } from 'antd';
 import type { TableProps } from 'antd';
 import styles from './index.module.css';
-import { WeComPlan, WeComPlanListResponse, WeComPlanType, WeVideoItem } from './type';
+import { AddWeComPlanParam, WeComPlan, WeComPlanListResponse, WeComPlanType } from './type';
 import request from '@src/http/index';
 import { deleteQwPlanApi, getQwPlanListApi, getShareQrPic, saveQwPlanApi } from "@src/http/api"
-import VideoSelectModal from './components/videoSelectModal';
 import LinkDetailModal from './components/linkDetailModal';
 import PlanDetailModal from './components/planDetailModal';
 import http from '@src/http/index';
 import copy from 'copy-to-clipboard';
 import VideoPlayModal from './components/videoPlayModal';
 import modal from 'antd/es/modal';
+import AddPlanModal from './components/addPlanModal';
 // Define a type for the expected API response (adjust if needed based on actual API)
 const TableHeight = window.innerHeight - 380;
 const WeGZHContent: React.FC = () => {
 	// 状态管理
 	const [videoTitle, setVideoTitle] = useState<string>('');
+	const [subChannel, setSubChannel] = useState<string>('');
 	const [selectedPublisher, setSelectedPublisher] = useState<number>();
 	const [isShowAddPunlishPlan, setIsShowAddPunlishPlan] = useState<boolean>(false);
 	const [editPlanData, setEditPlanData] = useState<WeComPlan>();
@@ -34,6 +35,7 @@ const WeGZHContent: React.FC = () => {
 	const [isPlanDetailModalVisible, setIsPlanDetailModalVisible] = useState<boolean>(false);
 
 	const [isVideoPlayModalVisible, setIsVideoPlayModalVisible] = useState<boolean>(false);
+	const [isAddPlanLoading, setIsAddPlanLoading] = useState<boolean>(false);
 
 	const getTableData = (_pageNum?: number) => {
 		setPageNum(_pageNum || 1);
@@ -42,6 +44,7 @@ const WeGZHContent: React.FC = () => {
 				pageSize: 10,
 				scene: selectedPublisher,
 				title: videoTitle,
+				subChannel,
 				type: +activeKey,
 			}
 		).then(res => {
@@ -86,6 +89,12 @@ const WeGZHContent: React.FC = () => {
 				}
 			}
 		},
+		{
+			title: '发布子渠道',
+			dataIndex: 'subChannel',
+			key: 'subChannel',
+			width: 120,
+		},
 		{
 			title: '操作',
 			key: 'action',
@@ -175,21 +184,30 @@ const WeGZHContent: React.FC = () => {
 		setIsShowAddPunlishPlan(true);
 	}
 
-	const handleOk = (selectedVideos: WeVideoItem[]) => {
+	const handleAddPlan = (param: AddWeComPlanParam) => {
+		setIsAddPlanLoading(true);
+		const { type, subChannel, videoList } = param;
 		http.post<WeComPlan[]>(saveQwPlanApi, {
-			type: +activeKey,
-			videoList: selectedVideos,
-		}).then(res => {			
+			type,
+			subChannel,
+			videoList: videoList,
+		}).then(res => {	
+			setIsAddPlanLoading(false);
 			if (res.code === 0) {
 				message.success('创建成功');
 				setCreatedVideoLinks(res.data);
 				setIsLinkDetailModalVisible(true);
 				setIsShowAddPunlishPlan(false);
-				getTableData();
+				if (type !== activeKey) { 
+					setActiveKey(type as WeComPlanType);
+				} else {
+					getTableData();
+				}
 			} else {
 				message.error(res.msg || '创建失败');
 			}
 		}).catch(err => {
+			setIsAddPlanLoading(false);
 			message.error(err.msg || '创建失败');
 		});
 	}
@@ -231,6 +249,16 @@ const WeGZHContent: React.FC = () => {
 						/>
 					</div>
 
+					<div className="flex items-center gap-2">
+						<Input
+							placeholder="请输入子渠道"
+							style={{ width: 200 }}
+							value={subChannel}
+							onChange={e => setSubChannel(e.target.value)}
+							allowClear
+						/>
+					</div>
+
 					<Button type="primary" className="ml-2" onClick={() => getTableData(1)}>搜索</Button>
 				</div>
 				<Tabs
@@ -261,16 +289,17 @@ const WeGZHContent: React.FC = () => {
 						onChange: (page) => getTableData(page),
 					}}
 				/>
-				<VideoSelectModal
+				<AddPlanModal
 					visible={isShowAddPunlishPlan}
+					initType={activeKey}
 					onClose={() => {
 						setIsShowAddPunlishPlan(false);
 						setEditPlanData(undefined);
 					}}
-					onOk={handleOk}
-					initialSelectedIds={editPlanData ? [editPlanData.id] : []}
-					planType={activeKey}
+					onOk={handleAddPlan}
+					isLoading={isAddPlanLoading}
 				/>
+				
 				<LinkDetailModal
 					visible={isLinkDetailModalVisible}
 					onClose={() => setIsLinkDetailModalVisible(false)}

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

@@ -27,3 +27,9 @@ export interface WeVideoItem {
 	score: number,
 	scene?: 0 | 1;
 }
+
+export interface AddWeComPlanParam {
+	type: string;
+	subChannel: string;
+	videoList: WeVideoItem[];
+}

+ 44 - 1
src/views/weData/qw/index.tsx

@@ -78,7 +78,30 @@ const Qw: React.FC = () => {
       dataIndex: 'score',
       key: 'score',
     },
-  ];
+	];
+	
+	const subChannelColumns:TableProps['columns'] = [
+		{
+			title: '日期',
+			dataIndex: 'dateStr',
+			key: 'dateStr',
+		},
+		{
+			title: '子渠道',
+			dataIndex: 'subChannel',
+			key: 'subChannel',
+		},
+		{
+			title: '小程序访问人数',
+			dataIndex: 'firstLevel',
+			key: 'firstLevel',
+		},
+		{
+			title: '传播得分',
+			dataIndex: 'score',
+			key: 'score',
+		},
+	];
 
   const fetchQwData = async (page = 1, pageSize = 10, type = 0) => {
     try {
@@ -268,6 +291,26 @@ const Qw: React.FC = () => {
                 }}
                 onChange={handleTableChange}
               />
+            ),
+					},
+					{
+            key: "5",
+            label: "分子渠道",
+            children: (
+              <Table
+                dataSource={dataSource}
+                columns={subChannelColumns}
+                rowKey="dateStr"
+                loading={loading}
+                pagination={{
+                  current: pagination.current,
+                  pageSize: pagination.pageSize,
+                  total: pagination.total,
+                  showSizeChanger: true,
+                  showTotal: (total) => `共 ${total} 条`,
+                }}
+                onChange={handleTableChange}
+              />
             ),
           },
         ]}