index.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import React, { useState, useEffect } from 'react';
  2. import {
  3. Drawer,
  4. Button,
  5. Select,
  6. Input,
  7. Row,
  8. Col,
  9. Card,
  10. Typography,
  11. Pagination,
  12. Checkbox,
  13. Space,
  14. message,
  15. Modal,
  16. } from 'antd';
  17. import { EditOutlined, PlayCircleOutlined, CheckCircleFilled, CaretRightFilled } from '@ant-design/icons';
  18. import { VideoItem } from '../types';
  19. const { Option } = Select;
  20. const { Text } = Typography;
  21. interface VideoSelectModalProps {
  22. visible: boolean;
  23. onClose: () => void;
  24. onOk: (selectedVideos: VideoItem[]) => void;
  25. initialSelectedIds?: number[];
  26. }
  27. // Mock data for demonstration - Ensure videoUrl is added
  28. const mockVideos: VideoItem[] = Array.from({ length: 15 }, (_, i) => ({
  29. id: i + 100,
  30. source: '票圈 | 3亿人喜欢的视频平台',
  31. title: `最美中国——上海之海 ${i + 1},带你领略航拍风景`,
  32. thumbnail: 'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png',
  33. spreadEfficiency: 8.56 + i * 0.1,
  34. videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4', // Example video URL
  35. }));
  36. const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, onOk, initialSelectedIds = [] }) => {
  37. const [category, setCategory] = useState<string>();
  38. const [searchTerm, setSearchTerm] = useState<string>('');
  39. const [currentPage, setCurrentPage] = useState(1);
  40. const [pageSize, setPageSize] = useState(10);
  41. const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
  42. const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
  43. const MAX_SELECTION = 3;
  44. useEffect(() => {
  45. if (visible) {
  46. setSelectedVideoIds(new Set(initialSelectedIds));
  47. }
  48. }, [visible, initialSelectedIds]);
  49. const handleSearch = () => {
  50. console.log('Searching for:', { category, searchTerm });
  51. setCurrentPage(1);
  52. };
  53. const handleSelectVideo = (videoId: number) => {
  54. setSelectedVideoIds(prev => {
  55. const newSet = new Set(prev);
  56. if (newSet.has(videoId)) {
  57. newSet.delete(videoId);
  58. } else {
  59. if (newSet.size >= MAX_SELECTION) {
  60. message.warning(`最多只能选择 ${MAX_SELECTION} 条视频`);
  61. return prev;
  62. }
  63. newSet.add(videoId);
  64. }
  65. return newSet;
  66. });
  67. };
  68. const handleOk = () => {
  69. const selectedVideos = mockVideos.filter(video => selectedVideoIds.has(video.id));
  70. onOk(selectedVideos);
  71. onClose();
  72. };
  73. const playVideo = (video: VideoItem) => {
  74. setPlayingVideo(video);
  75. };
  76. const closeVideoPlayer = () => {
  77. setPlayingVideo(null);
  78. };
  79. const paginatedData = mockVideos.slice((currentPage - 1) * pageSize, currentPage * pageSize);
  80. return (
  81. <>
  82. <Drawer
  83. title="内容选取"
  84. open={visible}
  85. onClose={onClose}
  86. width={800}
  87. placement="right"
  88. footerStyle={{ textAlign: 'right', padding: '10px 24px' }}
  89. footer={
  90. <div className="flex justify-between items-center">
  91. <Pagination
  92. current={currentPage}
  93. pageSize={pageSize}
  94. total={mockVideos.length}
  95. onChange={(page, size) => {
  96. setCurrentPage(page);
  97. setPageSize(size);
  98. }}
  99. pageSizeOptions={['10', '20', '50']}
  100. size="small"
  101. showSizeChanger
  102. showTotal={(total) => `共 ${total} 条`}
  103. />
  104. <Space>
  105. <Text>已选 {selectedVideoIds.size} / {MAX_SELECTION} 条视频</Text>
  106. <Button onClick={onClose}>取消</Button>
  107. <Button type="primary" onClick={handleOk}>确定</Button>
  108. </Space>
  109. </div>
  110. }
  111. >
  112. <div className="flex flex-wrap gap-4 mb-6">
  113. <div className="flex items-center gap-2">
  114. <span className="text-gray-600">品类:</span>
  115. <Select
  116. placeholder="选择品类"
  117. style={{ width: 180 }}
  118. value={category}
  119. onChange={setCategory}
  120. options={[{ label: '品类A', value: 'A' }, { label: '品类B', value: 'B' }]}
  121. />
  122. </div>
  123. <div className="flex items-center gap-2">
  124. <span className="text-gray-600">视频标题:</span>
  125. <Input
  126. placeholder="搜索视频标题"
  127. style={{ width: 200 }}
  128. value={searchTerm}
  129. onChange={e => setSearchTerm(e.target.value)}
  130. />
  131. </div>
  132. <Button type="primary" onClick={handleSearch}>搜索</Button>
  133. </div>
  134. <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
  135. {paginatedData.map((video) => {
  136. const isSelected = selectedVideoIds.has(video.id);
  137. const isDisabled = !isSelected && selectedVideoIds.size >= MAX_SELECTION;
  138. return (
  139. <Card
  140. key={video.id}
  141. className={`relative ${isDisabled ? 'opacity-50' : 'cursor-pointer'} ${isSelected ? 'border-blue-500 border-2' : ''}`}
  142. bodyStyle={{ padding: 0 }}
  143. onClick={() => !isDisabled && handleSelectVideo(video.id)}
  144. >
  145. <div className="p-3">
  146. <Text type="secondary" className="text-xs">{video.source}</Text>
  147. <Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.title}>{video.title}</Text>
  148. </div>
  149. <div
  150. className="relative h-[120px] bg-gray-200 group/thumb"
  151. onClick={(e) => { e.stopPropagation(); playVideo(video); }}
  152. >
  153. <img src={video.thumbnail} alt={video.title} className="w-full h-full object-cover" />
  154. <div className="absolute inset-0 flex justify-center items-center cursor-pointer">
  155. <CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
  156. </div>
  157. </div>
  158. <div className="p-3 flex justify-between items-center">
  159. <Text type="secondary" className="text-xs">传播效率: {video.spreadEfficiency.toFixed(2)}</Text>
  160. {isSelected ? (
  161. <CheckCircleFilled className="text-green-500 text-xl" />
  162. ) : (
  163. <div className={`w-5 h-5 border-2 ${isDisabled ? 'border-gray-200 bg-gray-100' : 'border-gray-300' } rounded-full`}></div>
  164. )}
  165. </div>
  166. </Card>
  167. );
  168. })}
  169. </div>
  170. </Drawer>
  171. <Modal
  172. open={!!playingVideo}
  173. onCancel={closeVideoPlayer}
  174. title={playingVideo?.title}
  175. footer={null}
  176. destroyOnClose
  177. width={720}
  178. bodyStyle={{ padding: 0, background: '#000' }}
  179. >
  180. {playingVideo && (
  181. <video controls autoPlay className="w-full h-auto max-h-[80vh] block" src={playingVideo.videoUrl}>
  182. Your browser does not support the video tag.
  183. </video>
  184. )}
  185. </Modal>
  186. </>
  187. );
  188. };
  189. export default VideoSelectModal;