index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import React, { useEffect, useState } from 'react';
  2. import { useParams } from 'react-router-dom';
  3. import { Modal, Form, Select, Button, Card, Typography, message } from 'antd';
  4. import { CloseOutlined, PlusOutlined, EditOutlined, CaretRightFilled } from '@ant-design/icons';
  5. import VideoSelectModal from '../videoSelectModal';
  6. import EditTitleCoverModal from '../editTitleCoverModal';
  7. import { VideoItem } from '../types'; // Import from common types
  8. import { GzhPlanDataType, GzhPlanType } from '../../hooks/useGzhPlanList';
  9. import { useAccountOptions } from '../../hooks/useAccountOptions';
  10. import { VideoLibraryType } from '@src/views/publishContent/weCom/components/videoSelectModal';
  11. const { Option } = Select;
  12. const { Paragraph } = Typography;
  13. interface AddPunlishPlanModalProps {
  14. visible: boolean;
  15. onCancel: () => void;
  16. onOk: (values: GzhPlanDataType) => void; // Pass form values on OK
  17. actionType: 'add' | 'edit';
  18. planType: GzhPlanType;
  19. editPlanData?: GzhPlanDataType;
  20. isSubmiting?: boolean;
  21. videoId?: string;
  22. }
  23. const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, isSubmiting, onCancel, onOk, actionType, planType, editPlanData, videoId }) => {
  24. const [form] = Form.useForm();
  25. const type = Form.useWatch('type', form);
  26. const { code } = useParams<{ code?: string }>();
  27. const selectVideoType = Form.useWatch('selectVideoType', form);
  28. const [selectedVideos, setSelectedVideos] = useState<VideoItem[]>([]);
  29. const [isVideoSelectVisible, setIsVideoSelectVisible] = useState(false);
  30. const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null); // State for video player modal
  31. const [editingVideo, setEditingVideo] = useState<VideoItem | null>(null); // State for editing modal
  32. const [initialSelectedVideoId, setInitialSelectedVideoId] = useState<string | null>(null); // State for initial video selection
  33. const [videoLibraryType, setVideoLibraryType] = useState<VideoLibraryType>(VideoLibraryType.平台视频库);
  34. const { accountOptions, getAccountList } = useAccountOptions();
  35. // 处理code参数
  36. useEffect(() => {
  37. if (code) {
  38. setVideoLibraryType(VideoLibraryType.我的上传);
  39. }
  40. }, [code]);
  41. useEffect(() => {
  42. if (actionType === 'edit') {
  43. form.setFieldsValue({...editPlanData, type: editPlanData?.type.toString()});
  44. setSelectedVideos(editPlanData?.videoList || []);
  45. } else {
  46. setSelectedVideos([]);
  47. form.setFieldsValue({
  48. id: undefined,
  49. type: planType,
  50. publishStage: 1,
  51. selectVideoType: 0,
  52. accountId: undefined,
  53. scene: 0,
  54. videoList: []
  55. });
  56. }
  57. }, [actionType, editPlanData, visible]);
  58. useEffect(() => {
  59. getAccountList({accountType: planType});
  60. }, [planType]);
  61. // 处理videoId参数
  62. useEffect(() => {
  63. if (visible && videoId) {
  64. setInitialSelectedVideoId(videoId);
  65. setIsVideoSelectVisible(true);
  66. }
  67. }, [visible, videoId]);
  68. // 重置状态
  69. useEffect(() => {
  70. if (!visible) {
  71. setInitialSelectedVideoId(null);
  72. }
  73. }, [visible]);
  74. const onTypeChange = (value: string) => {
  75. form.setFieldsValue({ accountId: undefined });
  76. getAccountList({ accountType: value });
  77. }
  78. const handleOk = () => {
  79. form
  80. .validateFields()
  81. .then((values) => {
  82. // Ensure at least one video is selected before submitting
  83. if (selectedVideos.length === 0) {
  84. message.error('请至少选择一个视频'); // Use Antd message
  85. return;
  86. }
  87. const formData = { ...values };
  88. if (formData.type === GzhPlanType.自动回复) {
  89. formData.scene = 0;
  90. }
  91. formData.videoList = selectedVideos;
  92. onOk(formData);
  93. })
  94. .catch((info) => {
  95. console.log('Validate Failed:', info);
  96. });
  97. };
  98. const removeVideo = (idToRemove: number) => {
  99. setSelectedVideos(currentVideos => currentVideos.filter(video => video.videoId !== idToRemove));
  100. };
  101. const openVideoSelector = () => {
  102. setIsVideoSelectVisible(true);
  103. };
  104. const handleVideoSelectionOk = (newlySelectedVideos: VideoItem[]) => {
  105. // Merge existing custom data with newly selected videos
  106. const currentCustomData = new Map<number, Partial<VideoItem>>();
  107. selectedVideos.forEach(v => {
  108. if (v.videoId) {
  109. currentCustomData.set(v.videoId, { customTitle: v.customTitle, customCover: v.customCover, customCoverType: v.customCoverType });
  110. }
  111. });
  112. const mergedVideos = newlySelectedVideos.map(newVideo => {
  113. const customData = currentCustomData.get(newVideo.videoId);
  114. return { ...newVideo, ...customData };
  115. });
  116. setSelectedVideos(mergedVideos);
  117. setIsVideoSelectVisible(false);
  118. };
  119. const handleVideoSelectionCancel = () => {
  120. setIsVideoSelectVisible(false);
  121. };
  122. const playVideo = (video: VideoItem) => {
  123. setPlayingVideo(video);
  124. };
  125. const closeVideoPlayer = () => {
  126. setPlayingVideo(null);
  127. };
  128. const openEditModal = (video: VideoItem) => {
  129. setEditingVideo(video);
  130. };
  131. const handleEditOk = (updatedData: Partial<VideoItem>) => {
  132. console.log('updatedData', updatedData);
  133. setSelectedVideos(currentVideos =>
  134. currentVideos.map(v =>
  135. v.videoId === editingVideo?.videoId ? { ...v, ...updatedData } : v
  136. )
  137. );
  138. setEditingVideo(null); // Close modal
  139. };
  140. const handleEditCancel = () => {
  141. setEditingVideo(null);
  142. };
  143. return (
  144. <>
  145. <Modal
  146. title={actionType === 'add' ? "创建发布计划" : "编辑发布计划"}
  147. open={visible}
  148. destroyOnClose
  149. onCancel={onCancel}
  150. width={900} // Adjust width as needed
  151. footer={[
  152. <Button key="back" onClick={onCancel}>
  153. 取消
  154. </Button>,
  155. <Button key="submit" type="primary" loading={isSubmiting} onClick={handleOk}>
  156. 确定
  157. </Button>,
  158. ]}
  159. zIndex={10}
  160. >
  161. <Form form={form} layout="vertical">
  162. <Form.Item
  163. name="id"
  164. label="计划ID"
  165. hidden
  166. >
  167. </Form.Item>
  168. <Form.Item
  169. name="type"
  170. label="发布计划类型"
  171. layout="horizontal"
  172. labelCol={{ span: 4 }}
  173. labelAlign='left'
  174. required
  175. >
  176. <Select placeholder="选择计划类型" onChange={onTypeChange} className='!w-50' disabled={actionType === 'edit'}>
  177. <Option value={GzhPlanType.自动回复}>自动回复</Option>
  178. <Option value={GzhPlanType.服务号推送}>服务号推送</Option>
  179. <Option value={GzhPlanType.公众号推送}>公众号推送</Option>
  180. </Select>
  181. </Form.Item>
  182. <Form.Item
  183. name="scene"
  184. label="发布场景"
  185. layout="horizontal"
  186. labelCol={{ span: 4 }}
  187. labelAlign='left'
  188. initialValue={0}
  189. hidden={type !== GzhPlanType.自动回复}
  190. >
  191. <Select
  192. placeholder="发布场景"
  193. className='!w-50'
  194. options={[{ label: '关注回复', value: 0 }]}>
  195. </Select>
  196. </Form.Item>
  197. <Form.Item
  198. name="publishStage"
  199. label="发布方式"
  200. labelCol={{ span: 4 }}
  201. labelAlign='left'
  202. layout="horizontal"
  203. rules={[{ required: true, message: '请选择发布方式' }]}
  204. >
  205. <Select placeholder="选择发布方式" allowClear className='!w-50'>
  206. <Option value={0} disabled={type !== GzhPlanType.自动回复}>平台发布</Option>
  207. <Option value={1} >用户获取路径</Option>
  208. </Select>
  209. </Form.Item>
  210. <Form.Item
  211. name="accountId"
  212. label="公众号名称"
  213. labelCol={{ span: 4 }}
  214. labelAlign='left'
  215. layout="horizontal"
  216. rules={[{ required: true, message: '请选择公众号' }]}
  217. >
  218. <Select
  219. placeholder="选择公众号"
  220. allowClear
  221. disabled={actionType === 'edit'}
  222. className='!w-50'
  223. showSearch
  224. filterOption={(input, option) =>
  225. (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
  226. }
  227. options={accountOptions?.map(option => ({ label: option.name, value: option.id }))}>
  228. </Select>
  229. </Form.Item>
  230. <Form.Item
  231. name="selectVideoType"
  232. label="视频选取方式"
  233. labelCol={{ span: 4 }}
  234. labelAlign='left'
  235. layout="horizontal"
  236. rules={[{ required: true, message: '请选择视频选取方式' }]}>
  237. <Select placeholder="选择视频选取方式" allowClear className='!w-50'>
  238. <Option value={0} >手动选取</Option>
  239. <Option value={1} disabled>自动选取</Option>
  240. </Select>
  241. </Form.Item>
  242. <Form.Item label="发布内容" required>
  243. {
  244. selectVideoType === 0 ?
  245. (<div className="flex flex-wrap gap-4">
  246. {selectedVideos.map((video) => (
  247. <Card
  248. key={video.videoId}
  249. className="w-[240px] relative group"
  250. >
  251. <Button
  252. shape="circle"
  253. icon={<CloseOutlined />}
  254. 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"
  255. size="small"
  256. onClick={() => removeVideo(video.videoId)}
  257. />
  258. <div className="p-0">
  259. <Paragraph className="mt-1 !mb-1" ellipsis={{ rows: 2, tooltip: true }} title={video.customTitle || video.title}>{video.customTitle || video.title}</Paragraph>
  260. </div>
  261. <div
  262. className="relative"
  263. style={{ paddingBottom: '79.8%' }}
  264. onClick={(e) => {
  265. e.stopPropagation(); // Prevent card selection if clicking thumbnail/play
  266. playVideo(video);
  267. }}
  268. >
  269. <img src={video.customCover || video.cover} referrerPolicy="no-referrer" className="absolute inset-0 w-full h-full object-cover" />
  270. <div className="absolute inset-0 flex justify-center items-center cursor-pointer">
  271. <CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
  272. </div>
  273. </div>
  274. <div className="p-3">
  275. <Button
  276. icon={<EditOutlined />}
  277. className="w-full mt-2"
  278. onClick={() => openEditModal(video)} // Open edit modal
  279. >
  280. 编辑标题/封面
  281. </Button>
  282. </div>
  283. </Card>
  284. ))}
  285. {/* Add Video Button - Conditionally Rendered */}
  286. {selectedVideos.length < 3 && (
  287. <div
  288. 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"
  289. onClick={openVideoSelector} // Open the drawer on click
  290. >
  291. <PlusOutlined className="text-2xl mb-2" />
  292. <Typography.Text>添加视频</Typography.Text>
  293. </div>
  294. )}
  295. </div>)
  296. : (<div>
  297. <Paragraph>视频由系统自动选取,不可手动选择</Paragraph>
  298. </div>)
  299. }
  300. </Form.Item>
  301. </Form>
  302. </Modal>
  303. {/* Video Selection Drawer */}
  304. <VideoSelectModal
  305. planType={planType}
  306. visible={isVideoSelectVisible}
  307. onClose={handleVideoSelectionCancel}
  308. onOk={handleVideoSelectionOk}
  309. selectedVideos={selectedVideos}
  310. initialSelectedIds={initialSelectedVideoId ? [Number(initialSelectedVideoId)] : selectedVideos.map(v => v.videoId)}
  311. defaultVideoLibraryType={videoLibraryType}
  312. />
  313. {/* Video Player Modal */}
  314. <Modal
  315. open={!!playingVideo}
  316. onCancel={closeVideoPlayer}
  317. title={playingVideo?.customTitle || playingVideo?.title}
  318. footer={null}
  319. destroyOnClose // Unmount video element when closed
  320. width={720} // Adjust as needed
  321. styles={{ body: { padding: 0, background: '#000' } }}
  322. zIndex={20}
  323. >
  324. {playingVideo && (
  325. <video
  326. controls
  327. autoPlay
  328. className="w-full h-auto max-h-[80vh] block"
  329. src={playingVideo.video}
  330. >
  331. Your browser does not support the video tag.
  332. </video>
  333. )}
  334. </Modal>
  335. {/* Edit Title/Cover Modal */}
  336. <EditTitleCoverModal
  337. visible={!!editingVideo}
  338. onCancel={handleEditCancel}
  339. onOk={handleEditOk}
  340. video={editingVideo}
  341. />
  342. </>
  343. );
  344. };
  345. export default AddPunlishPlanModal;