Browse Source

内容池筛选和交互修改

jihuaqiang 1 week ago
parent
commit
288caa0ad6

+ 54 - 0
src/views/contentManage/contentManage.router.tsx

@@ -0,0 +1,54 @@
+import Icon, { UploadOutlined, PlayCircleOutlined } from '@ant-design/icons'
+import { AdminRouterItem } from "../../router";
+import React, { Suspense } from 'react';
+
+import LogoIcon from "@src/assets/images/login/logo.svg?react";
+import { Outlet } from "react-router-dom";
+
+// Lazy load components
+const Videos = React.lazy(() => import('./videos/index'));
+
+// Loading fallback component
+// eslint-disable-next-line react-refresh/only-export-components
+const LazyLoadingFallback = () => (
+  <div className="flex items-center justify-center flex-col h-[50vh]">
+		<Icon component={LogoIcon} className="text-[50px] opacity-50" />
+		<div className="text-gray-500 text-xl">加载中</div>
+  </div>
+);
+
+// Wrapper component with Suspense
+// eslint-disable-next-line react-refresh/only-export-components
+const LazyComponent = ({ Component }: { Component: React.ComponentType<any> }) => (
+  <Suspense fallback={<LazyLoadingFallback />}>
+    <Component />
+  </Suspense>
+);
+
+const demoRoutes: AdminRouterItem[] = [
+  {
+		path: 'contentManage',
+		element: <Outlet />,
+		sortKey: 3,
+		meta: {
+			label: "内容管理",
+			title: "内容管理",
+			key: "/contentManage",
+			icon: <PlayCircleOutlined />,
+		},
+		children: [
+			{
+				path: 'videos',
+				element: <LazyComponent Component={Videos} />,
+				meta: {
+					label: "上传",
+					title: "上传",
+					key: "/contentManage/videos",
+					icon: <UploadOutlined className="!text-[20px]"/>,
+				}
+			},
+		]
+	}
+]
+
+export default demoRoutes

+ 0 - 0
src/views/publishContent/videos/components/uploadVideoModal/README.md → src/views/contentManage/videos/components/uploadVideoModal/README.md


+ 0 - 0
src/views/publishContent/videos/components/uploadVideoModal/index.module.css → src/views/contentManage/videos/components/uploadVideoModal/index.module.css


+ 0 - 0
src/views/publishContent/videos/components/uploadVideoModal/index.tsx → src/views/contentManage/videos/components/uploadVideoModal/index.tsx


+ 0 - 0
src/views/publishContent/videos/index.module.css → src/views/contentManage/videos/index.module.css


+ 1 - 1
src/views/publishContent/videos/index.tsx → src/views/contentManage/videos/index.tsx

@@ -7,7 +7,7 @@ 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';
+import VideoPlayModal from '@src/views/publishContent/weCom/components/videoPlayModal';
 import { UserInfo } from '../../setting/type';
 // Define a type for the expected API response (adjust if needed based on actual API)
 const TableHeight = window.innerHeight - 380;

+ 0 - 0
src/views/publishContent/videos/type.ts → src/views/contentManage/videos/type.ts


+ 4 - 4
src/views/cooperationAccount/cooperationAccount.router.tsx

@@ -9,8 +9,8 @@ const demoRoutes: AdminRouterItem[] = [
 		element: <Outlet />,
 		sortKey: 1,
 		meta: {
-			label: "合作账号管理",
-			title: "合作账号管理",
+			label: "合作账号",
+			title: "合作账号",
 			key: "/cooperationAccount",
 			icon: <TeamOutlined />,
 		},
@@ -19,8 +19,8 @@ const demoRoutes: AdminRouterItem[] = [
 				path: 'gzh',
 				element: <GZH />,
 				meta: {
-					label: "公众号管理",
-					title: "公众号管理",
+					label: "公众号",
+					title: "公众号",
 					key: "/cooperationAccount/gzh",
 				},
 			},

+ 6 - 17
src/views/publishContent/publishContent.router.tsx

@@ -1,4 +1,4 @@
-import Icon, { DesktopOutlined, UploadOutlined } from '@ant-design/icons'
+import Icon, { DesktopOutlined } from '@ant-design/icons'
 import { AdminRouterItem } from "../../router";
 import React, { Suspense } from 'react';
 import WeComIcon from "@src/assets/images/publishContent/wxCom.svg?react";
@@ -9,7 +9,6 @@ import { Outlet } from "react-router-dom";
 // Lazy load components
 const WeCom = React.lazy(() => import('./weCom/index'));
 const WeGZH = React.lazy(() => import('./weGZH/index'));
-const Videos = React.lazy(() => import('./videos/index'));
 
 // Loading fallback component
 // eslint-disable-next-line react-refresh/only-export-components
@@ -34,8 +33,8 @@ const demoRoutes: AdminRouterItem[] = [
 		element: <Outlet />,
 		sortKey: 2,
 		meta: {
-			label: "发布内容管理",
-			title: "发布内容管理",
+			label: "发布计划",
+			title: "发布计划",
 			key: "/publishContent",
 			icon: <DesktopOutlined />,
 		},
@@ -54,22 +53,12 @@ const demoRoutes: AdminRouterItem[] = [
 				path: 'wecom',
 				element: <LazyComponent Component={WeCom} />,
 				meta: {
-					label: "企微",
-					title: "企微",
+					label: "企",
+					title: "企",
 					key: "/publishContent/wecom",
 					icon: <Icon component={WeComIcon} className="!text-[20px]"/>,
 				}
-			},
-			{
-				path: 'videos',
-				element: <LazyComponent Component={Videos} />,
-				meta: {
-					label: "上传内容管理",
-					title: "上传内容管理",
-					key: "/publishContent/videos",
-					icon: <UploadOutlined className="!text-[20px]"/>,
-				}
-			},
+			}
 		]
 	}
 ]

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

@@ -178,7 +178,8 @@ const AddPlanModal: React.FC<{
 		}}
 		onOk={handleSelectVideo}
 		initialSelectedIds={selectedVideos.map((video) => video.videoId)}
-		planType={type}
+			planType={type}
+			selectedVideos={selectedVideos}
 		/>
 		<VideoPlayModal
 			visible={!!playingVideo}

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

@@ -100,7 +100,7 @@ const LinkDetailModal: React.FC<LinkDetailModalProps> = ({ visible, onClose, vid
       title: '场景',
       dataIndex: 'scene',
       key: 'scene',
-      render: (scene: number | undefined) => (scene === 0 ? '群发' : scene === 1 ? '单发' : '未知'), // Adjust based on your scene values
+      render: (scene: number | undefined) => (scene === 1 ? '单发' : '群发'),
     },
     {
       title: '操作',

+ 0 - 16
src/views/publishContent/weCom/components/planDetailModal/index.tsx

@@ -90,18 +90,6 @@ const PlanDetailModal: React.FC<PlanDetailModalProps> = ({ visible, onClose, pla
      // Returning a placeholder for now.
      return plan.type === +WeComPlanType.社群 ? '社群' : '自动回复'; // Example
   }
-  
-  // Determine account type text (adjust based on your data model)
-  const getAccountTypeText = () => {
-    // This field isn't in WeComPlan, returning placeholder.
-    return '小慧爱厨房'; // Placeholder based on image
-  }
-
-  // Determine account name text (adjust based on your data model)
-  const getAccountNameText = () => {
-      // This field isn't in WeComPlan, returning placeholder.
-      return '企微号'; // Placeholder based on image
-  }
 
   return (
     <Modal
@@ -117,10 +105,6 @@ const PlanDetailModal: React.FC<PlanDetailModalProps> = ({ visible, onClose, pla
       destroyOnClose
     >
       <Descriptions bordered column={1} size="small" labelStyle={{ width: '120px' }}>
-        <Descriptions.Item label="链接ID">{planData.id}</Descriptions.Item>
-        {/* Placeholder fields based on image - replace with actual data source if available */}
-        <Descriptions.Item label="发布账号类型">{getAccountTypeText()}</Descriptions.Item>
-        <Descriptions.Item label="公众号名称">{getAccountNameText()}</Descriptions.Item>
         <Descriptions.Item label="发布场景">{getPublishTypeText(planData)}</Descriptions.Item>
 
         <Descriptions.Item label="场景">{getSceneText(planData)}</Descriptions.Item>

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

@@ -10,14 +10,20 @@ import {
 	Space,
 	message,
 	Modal,
+	Radio,
+	RadioChangeEvent,
+	Tooltip,
+	Tag,
 } from 'antd';
-import { CheckCircleFilled, CaretRightFilled } from '@ant-design/icons';
-import { VideoListResponse } from '@src/views/publishContent/weGZH/components/types';
+import { CheckCircleFilled, CaretRightFilled, QuestionCircleOutlined } from '@ant-design/icons';
+import { VideoItem, VideoListResponse } from '@src/views/publishContent/weGZH/components/types';
 import http from '@src/http';
 import { getVideoContentListApi, getUploadVideoContentListApi } from '@src/http/api';
 import { useVideoCategoryOptions } from '@src/views/publishContent/weGZH/hooks/useVideoCategoryOptions';
 import { WeComPlanType, WeVideoItem, VideoSearchPlanType } from '@src/views/publishContent/weCom/type'
 import { enumToOptions } from '@src/utils/helper';
+import { isNil } from 'lodash';
+import { GzhPlanType } from '@src/views/publishContent/weGZH/hooks/useGzhPlanList';
 
 export enum VideoLibraryType {
 	平台视频库 = 0,
@@ -27,11 +33,13 @@ export enum VideoLibraryType {
 const { Text, Paragraph } = Typography;
 
 interface VideoSelectModalProps {
-	planType: WeComPlanType;
+	planType: WeComPlanType | GzhPlanType;
 	visible: boolean;
 	onClose: () => void;
-	onOk: (selectedVideos: WeVideoItem[]) => void;
+	onOk: (selectedVideos: (VideoItem | WeVideoItem)[]) => void;
 	initialSelectedIds?: number[];
+	selectedVideos?: (VideoItem | WeVideoItem)[];
+	defaultVideoLibraryType?: VideoLibraryType;
 }
 
 export enum VideoSortType {
@@ -41,35 +49,91 @@ export enum VideoSortType {
 	推荐指数 = 3,
 }
 
-const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, onOk, planType, initialSelectedIds = [] }) => {
+export enum RecentNotUsedType {
+	历史 = 0,
+	近30天 = 1,
+	近14天 = 2,
+	近7天 = 3,
+	近3天 = 4,
+}
+
+export enum SortTypeEnum {
+	推荐指数 = 0,
+	更新时间 = 1,
+}
+
+export const Tags = [
+	'票圈受欢迎',
+	'同类用户喜欢',
+	'你的用户爱看',
+	'猜TA想看',
+];
+
+interface GetVideoListParams {
+	pageNum?: number;
+	_pageSize?: number;
+	_sortType?: SortTypeEnum;
+	_recentNotUsed?: RecentNotUsedType;
+	_category?: string;
+	_tags?: string[];
+	_videoLibraryType?: VideoLibraryType;
+}
+
+const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, onOk, planType, initialSelectedIds = [], selectedVideos = [], defaultVideoLibraryType }) => {
 	const { videoCategoryOptions } = useVideoCategoryOptions();
 	const [category, setCategory] = useState<string>();
-	const [sort, setSort] = useState<VideoSortType>(VideoSortType.推荐指数);
+	// const [sort, setSort] = useState<VideoSortType>(VideoSortType.推荐指数);
+	const [sortType, setSortType] = useState<SortTypeEnum>(SortTypeEnum.推荐指数);
+	const [recentNotUsed, setRecentNotUsed] = useState<RecentNotUsedType>(RecentNotUsedType.历史);
+	const [tags, setTags] = useState<string[]>([]);
 	const [searchTerm, setSearchTerm] = useState<string>('');
 	const [currentPage, setCurrentPage] = useState(1);
 	const [pageSize, setPageSize] = useState(10);
 	const [total, setTotal] = useState(0);
 	const [loading, setLoading] = useState(false);
-	const [videoList, setVideoList] = useState<WeVideoItem[]>([]);
-	const [videoListAll, setVideoListAll] = useState<WeVideoItem[]>([]);
+	const [videoList, setVideoList] = useState<(WeVideoItem | VideoItem)[]>([]);
+	const [videoListAll, setVideoListAll] = useState<(WeVideoItem | VideoItem)[]>([]);
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
-	const [playingVideo, setPlayingVideo] = useState<WeVideoItem | null>(null);
-	const [videoLibraryType, setVideoLibraryType] = useState<VideoLibraryType>(VideoLibraryType.平台视频库);
+	const [playingVideo, setPlayingVideo] = useState<WeVideoItem | VideoItem | null>(null);
+	const [videoLibraryType, setVideoLibraryType] = useState<VideoLibraryType>(defaultVideoLibraryType || VideoLibraryType.平台视频库);
 	const MAX_SELECTION = 3;
 
-	const getVideoList = async (pageNum?: number, _pageSize?: number) => {
+	const getVideoListType = (planType: GzhPlanType | WeComPlanType) => {
+		if (planType === GzhPlanType.自动回复) {
+			return VideoSearchPlanType.自动回复;
+		} else if (planType === GzhPlanType.公众号推送) {
+			return VideoSearchPlanType.公众号推送;
+		} else if (planType === GzhPlanType.服务号推送) {
+			return VideoSearchPlanType.服务号推送;
+		} else if (planType === WeComPlanType.社群) {
+			return VideoSearchPlanType.企微社群;
+		} else {
+			return VideoSearchPlanType.企微自动回复;
+		}
+	}
+
+	useEffect(() => {
+		if (defaultVideoLibraryType) {
+			setVideoLibraryType(defaultVideoLibraryType);
+		}
+	}, [defaultVideoLibraryType]);
+
+	const getVideoList = async ({pageNum, _pageSize, _sortType, _recentNotUsed, _category, _tags, _videoLibraryType}: GetVideoListParams) => {
 		setLoading(true);
 		setCurrentPage(pageNum || currentPage);
 		setPageSize(_pageSize || pageSize);
 
 		// 根据视频库类型选择不同的API
-		const apiUrl = videoLibraryType === VideoLibraryType.平台视频库 ? getVideoContentListApi : getUploadVideoContentListApi;
+		const currentVideoLibraryType = isNil(_videoLibraryType) ? videoLibraryType : _videoLibraryType;
+		const apiUrl = currentVideoLibraryType === VideoLibraryType.平台视频库 ? getVideoContentListApi : getUploadVideoContentListApi;
 
 		const requestParams = {
-			category,
+			category: isNil(_category) ? category : _category,
 			title: searchTerm,
-			sort,
-			type: planType === WeComPlanType.社群 ? VideoSearchPlanType.企微社群 : VideoSearchPlanType.企微自动回复,
+			recentNotUsed: isNil(_recentNotUsed) ? recentNotUsed : _recentNotUsed,
+			sortType: isNil(_sortType) ? sortType : _sortType,
+			tags: isNil(_tags) ? tags : _tags,
+			type: getVideoListType(planType),
 			pageNum: pageNum || currentPage,
 			pageSize: _pageSize || pageSize,
 		};
@@ -80,21 +144,21 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 			setLoading(false);
 		});
 		if (res && res.code === 0) {
-			const mappedVideos = (res.data.objs || []).map(video => ({ ...video, scene: videoList.find(v => v.videoId === video.videoId)?.scene || 0 as 0 | 1 }));
-			setVideoList(mappedVideos);
-			setVideoListAll(old => [...old, ...mappedVideos]);
+			setVideoList([...selectedVideos, ...res.data.objs.filter(v => !selectedVideos.find(ov => ov.videoId === v.videoId))]);
+			setVideoListAll(old => [...old, ...res.data.objs.filter(v => !old.find(ov => ov.videoId === v.videoId))]);
 			setTotal(res.data.totalSize);
 		}
 	}
 
-	// 监听视频库类型变化,重新加载数据
 	useEffect(() => {
-		getVideoList();
-	}, [videoLibraryType]);
-
-	useEffect(() => {
-		getVideoList();
-	}, []);
+		if (visible) {
+			console.log('selectedVideos', selectedVideos);
+			// 初始化时设置 selectedVideos
+			setVideoList(selectedVideos);
+			setVideoListAll(selectedVideos);
+			getVideoList({});
+		}
+	}, [visible]);
 
 	useEffect(() => {
 		if (visible) {
@@ -106,7 +170,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		console.log('Searching for:', { category, searchTerm });
 		const newPageNum = 1;
 		setCurrentPage(newPageNum);
-		getVideoList(newPageNum);
+		getVideoList({pageNum: newPageNum});
 	};
 
 	const handleSelectVideo = (videoId: number) => {
@@ -131,10 +195,11 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		const uniqueSelectedVideos = selectedVideos.filter((video, index, self) =>
 			index === self.findIndex((t) => t.videoId === video.videoId)
 		);
+		console.log('uniqueSelectedVideos', uniqueSelectedVideos);
 		onOk(uniqueSelectedVideos);
 	};
 
-	const playVideo = (video: WeVideoItem) => {
+	const playVideo = (video: WeVideoItem | VideoItem) => {
 		setPlayingVideo(video);
 	};
 
@@ -142,26 +207,33 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		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 handleVideoLibraryTypeChange = (v: VideoLibraryType) => {
+		setVideoLibraryType(v);
+		getVideoList({pageNum: 1, _videoLibraryType: v});
+	}
+
+	const handleSortTypeChange = (v: RadioChangeEvent) => {
+		setSortType(v.target.value as SortTypeEnum);
+		console.log('handleSortTypeChange', v.target.value);
+		getVideoList({pageNum: 1, _sortType: v.target.value as SortTypeEnum});
+	}
+
+	const onCategoryChange = (v: string) => {
+		setCategory(v);
+		console.log('onCategoryChange', v);
+		getVideoList({pageNum: 1, _category: v || '' as string});
+	}
+
+	const onRecentNotUsedChange = (v: RecentNotUsedType) => {
+		setRecentNotUsed(v);
+		getVideoList({pageNum: 1, _recentNotUsed: v});
+	}
+
+	const handleTagChange = (tag: string, checked: boolean) => {
+		const newTags = checked ? [...tags, tag] : tags.filter(t => t !== tag);
+		setTags(newTags);
+		getVideoList({pageNum: 1, _tags: newTags});
+	}
 
 	return (
 		<>
@@ -183,7 +255,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 							onChange={(page, size) => {
 								setCurrentPage(page);
 								setPageSize(size);
-								getVideoList(page, size);
+								getVideoList({pageNum: page, _pageSize: size});
 							}}
 							pageSizeOptions={['10', '20', '50']}
 							size="small"
@@ -198,25 +270,17 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 					</div>
 				}
 			>
-				<div className="flex flex-wrap gap-2 mb-6">
+				<div className="flex flex-wrap gap-2 mb-2">
 					<div className="flex items-center gap-2">
 						<span className="text-gray-600">视频来源:</span>
 						<Select
 							style={{ width: 120 }}
 							value={videoLibraryType}
-							onChange={setVideoLibraryType}
+							onChange={handleVideoLibraryTypeChange}
 							options={enumToOptions(VideoLibraryType)}
 						/>
 					</div>
-					<div className="flex items-center gap-2">
-						<span className="text-gray-600">排序选项:</span>
-						<Select
-							style={{ width: 120 }}
-							value={sort}
-							onChange={setSort}
-							options={enumToOptions(VideoSortType)}
-						/>
-					</div>
+					
 					<div className="flex items-center gap-2">
 						<span className="text-gray-600">品类:</span>
 						<Select
@@ -224,15 +288,24 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 							style={{ width: 120 }}
 							value={category}
 							allowClear
-							onChange={setCategory}
+							onChange={onCategoryChange}
 							options={videoCategoryOptions.map(option => ({ label: option, value: option }))}
 						/>
 					</div>
+					<div className="flex items-center gap-2">
+						<span className="text-gray-600">近期未使用:</span>
+						<Select
+							style={{ width: 120 }}
+							value={recentNotUsed}
+							onChange={onRecentNotUsedChange}
+							options={enumToOptions(RecentNotUsedType)}
+						/>
+					</div>
 					<div className="flex items-center gap-2">
 						<span className="text-gray-600">视频标题:</span>
 						<Input
 							placeholder="搜索视频标题"
-							style={{ width: 120 }}
+							style={{ width: 130 }}
 							value={searchTerm}
 							allowClear
 							onChange={e => setSearchTerm(e.target.value)}
@@ -241,7 +314,31 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 					</div>
 					<Button type="primary" onClick={handleSearch}>搜索</Button>
 				</div>
-
+				<div className="flex">
+					<div className="flex flex-wrap gap-2 mb-6 items-center">
+						<span className="text-gray-600 ">排序选项:</span>
+						<Radio.Group
+							onChange={handleSortTypeChange}
+							value={sortType}
+							buttonStyle="solid"
+						>
+							<Radio.Button value={SortTypeEnum.推荐指数}>推荐指数 <Tooltip title="结合视频在票圈、同业务场景、本账号下的历史表现综合得分"><QuestionCircleOutlined /></Tooltip></Radio.Button>
+							<Radio.Button value={SortTypeEnum.更新时间}>更新时间</Radio.Button>
+						</Radio.Group>
+					</div>
+					<div className="flex flex-wrap gap-2 mb-6 ml-10 items-center">
+						<span className="text-gray-600 ">推荐标签:</span>
+						{Tags.map<React.ReactNode>((tag) => (
+							<Tag.CheckableTag
+								key={tag}
+								checked={tags.includes(tag)}
+								onChange={(checked) => handleTagChange(tag, checked)}
+							>
+								{tag}
+							</Tag.CheckableTag>
+						))}
+					</div>
+				</div>
 				<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
 					{videoList.map((video) => {
 						const isSelected = selectedVideoIds.has(video.videoId);
@@ -255,26 +352,25 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 							>
 								<div className="p-3">
 									<Text type="secondary" className="text-xs">票圈 | 3亿人喜欢的视频平台</Text>
-									<Paragraph className="mt-1 !mb-1" ellipsis={{ rows: 2, tooltip: true }} title={video.title}>{video.title}</Paragraph>
+									<Paragraph className="mt-1 !mb-1" ellipsis={{ rows: 1, tooltip: true }} title={video.customTitle || video.title}>{video.customTitle || video.title}</Paragraph>
 								</div>
 								<div
 									className="relative"
 									style={{ paddingBottom: '79.8%' }}
 									onClick={(e) => { e.stopPropagation(); playVideo(video); }}
 								>
-									<img src={video.cover} alt={video.title} referrerPolicy="no-referrer" className="absolute inset-0 w-full h-full object-cover" />
+									<img src={video.customCover || video.cover} alt={video.customTitle || video.title} 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">
-									<div className="flex flex-col gap-1">
+									<div className="flex flex-1 flex-col gap-1">
 										<Text type="secondary" className="text-xs">推荐指数: {video.recommendScore?.toFixed(2) || '无'}</Text>
-										<Text type="secondary" className="text-xs">平台传播得分: {video.score?.toFixed(2) || '无'}</Text>
-										<Text type="secondary" className="text-xs">行业裂变率: {video.industryFissionRate?.toFixed(2) || '无'}</Text>
-										<Text type="secondary" className="text-xs">本渠道传播率: {video.channelFissionRate?.toFixed(2) || '无'}</Text>
+										<Text type="secondary" className="text-xs">推荐标签: {'tags' in video && video.tags && video.tags.length > 0 ?
+											video.tags.map(tag => <Tag key={tag} className="text-xs">{tag}</Tag>) : '无'}</Text>
 									</div>
-								{isSelected ? (
+									{isSelected ? (
 										<CheckCircleFilled className="text-green-500 text-xl" />
 									) : (
 										<div className={`w-5 h-5 border-2 ${isDisabled ? 'border-gray-200 bg-gray-100' : 'border-gray-300' } rounded-full`}></div>

+ 1 - 1
src/views/publishContent/weCom/index.tsx

@@ -90,7 +90,7 @@ const WeGZHContent: React.FC = () => {
 			width: 100,
 			render: (_, record) => {
 				if (activeKey === WeComPlanType.社群) {
-					return record.scene === 0 ? '群发' : '单发';
+					return record.scene === 1 ? '单发' : '群发';
 				} else {
 					return '关注回复';
 				}

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

@@ -31,6 +31,9 @@ export enum VideoSearchPlanType {
 export interface WeVideoItem {
 	videoId: number;
 	video: string;
+	customCover?: string;
+	customCoverType?: number;
+	customTitle?: string;
 	title: string;
 	cover: string;
 	score: number,
@@ -38,6 +41,7 @@ export interface WeVideoItem {
 	industryFissionRate?: number;
 	channelFissionRate?: number;
 	recommendScore?: number;
+	tags?: string[];
 }
 
 export interface AddWeComPlanParam {

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

@@ -2,12 +2,13 @@ import React, { useEffect, useState } from 'react';
 import { useParams } from 'react-router-dom';
 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 '@src/views/publishContent/weCom/components/videoSelectModal';
 import EditTitleCoverModal from '../editTitleCoverModal';
 import { VideoItem } from '../types'; // Import from common types
 import { GzhPlanDataType, GzhPlanType } from '../../hooks/useGzhPlanList';
 import { useAccountOptions } from '../../hooks/useAccountOptions';
 import { VideoLibraryType } from '@src/views/publishContent/weCom/components/videoSelectModal';
+import { WeVideoItem } from '@src/views/publishContent/weCom/type';
 
 const { Option } = Select;
 const { Paragraph } = Typography;

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

@@ -13,6 +13,8 @@ export interface VideoItem {
 	channelFissionRate: number,
 	videoLibraryType?: number;
 	recommendScore: number,
+	tags?: string[],
+	scene?: 0 | 1,
 } 
 
 export interface VideoListResponse {

+ 6 - 6
src/views/weData/weData.router.tsx

@@ -1,4 +1,4 @@
-import { TeamOutlined } from '@ant-design/icons'
+import { OrderedListOutlined } from '@ant-design/icons'
 import { AdminRouterItem } from "../../router";
 import { Outlet } from "react-router-dom";
 import GZH from './gzh';
@@ -10,10 +10,10 @@ const demoRoutes: AdminRouterItem[] = [
 		element: <Outlet />,
 		sortKey: 3,
 		meta: {
-			label: "数据统计",
-			title: "数据统计",
+			label: "数据",
+			title: "数据",
 			key: "/weData",
-			icon: <TeamOutlined />,
+			icon: <OrderedListOutlined />,
 		},
 		children: [
 			{
@@ -29,8 +29,8 @@ const demoRoutes: AdminRouterItem[] = [
 				path: 'qw',
 				element: <QW />,
 				meta: {
-					label: "企微",
-					title: "企微",
+					label: "企",
+					title: "企",
 					key: "/weData/qw",
 				},
 			}