index.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import React, { useState, useEffect } from 'react';
  2. import {
  3. Modal,
  4. Form,
  5. Radio,
  6. Input,
  7. Upload,
  8. Image,
  9. Space,
  10. message,
  11. } from 'antd';
  12. import { PlusOutlined, CheckCircleFilled } from '@ant-design/icons';
  13. import type { RadioChangeEvent } from 'antd';
  14. import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
  15. import { VideoItem } from '../types';
  16. import { getAccessToken } from '@src/http/sso';
  17. import { adFileUpload, getVideoContentCoverFrameListApi } from '@src/http/api';
  18. import http from '@src/http';
  19. const { TextArea } = Input;
  20. interface EditTitleCoverModalProps {
  21. visible: boolean;
  22. onCancel: () => void;
  23. onOk: (updatedVideoData: Partial<VideoItem>) => void;
  24. video: VideoItem | null;
  25. }
  26. const EditTitleCoverModal: React.FC<EditTitleCoverModalProps> = ({ visible, onCancel, onOk, video }) => {
  27. const [form] = Form.useForm();
  28. const [titleType, setTitleType] = useState<'original' | 'custom'>('original');
  29. const [coverType, setCoverType] = useState<'original' | 'screenshot' | 'upload'>('original');
  30. const [selectedScreenshot, setSelectedScreenshot] = useState<string | null>(null);
  31. const [fileList, setFileList] = useState<UploadFile[]>([]);
  32. const [screenshotImagesList, setScreenshotImagesList] = useState<string[]>([]);
  33. useEffect(() => {
  34. if (visible) {
  35. getScreenshotImagesList()
  36. }
  37. }, [visible])
  38. useEffect(() => {
  39. if (video && visible) {
  40. // Reset form based on incoming video data
  41. const hasCustomTitle = video.customTitle && video.customTitle !== '';
  42. const isCustomCover = video.customCoverType === 1 || video.customCoverType === 2;
  43. const isScreenshotCover = video.customCoverType === 1;
  44. const initialTitleType = hasCustomTitle ? 'custom' : 'original';
  45. let initialCoverType: 'original' | 'screenshot' | 'upload' = 'original';
  46. if (isCustomCover) {
  47. if (isScreenshotCover) {
  48. initialCoverType = 'screenshot';
  49. setSelectedScreenshot(video.customCover || null);
  50. setFileList([]);
  51. } else {
  52. initialCoverType = 'upload';
  53. setSelectedScreenshot(null);
  54. // Assume customThumbnail for upload is a URL, create UploadFile structure
  55. setFileList([{ uid: '-1', name: 'custom_cover.png', status: 'done', url: video.customCover }]);
  56. }
  57. } else {
  58. initialCoverType = 'original';
  59. setSelectedScreenshot(null);
  60. setFileList([]);
  61. }
  62. setTitleType(initialTitleType);
  63. setCoverType(initialCoverType);
  64. form.setFieldsValue({
  65. titleType: initialTitleType,
  66. title: video.title,
  67. cover: video.cover,
  68. coverType: initialCoverType,
  69. customCover: video.customCover,
  70. customCoverType: video.customCoverType,
  71. customTitle: video.customTitle,
  72. });
  73. } else {
  74. // Reset form when modal closes or no video
  75. form.resetFields();
  76. setTitleType('original');
  77. setCoverType('original');
  78. setSelectedScreenshot(null);
  79. setFileList([]);
  80. }
  81. }, [video, visible, form, screenshotImagesList]);
  82. const getScreenshotImagesList = async () => {
  83. const res = await http.post<string[]>(getVideoContentCoverFrameListApi, {
  84. videoId: video?.videoId
  85. })
  86. if (res.code === 0) {
  87. setScreenshotImagesList(res.data || [])
  88. }
  89. }
  90. const handleOk = () => {
  91. form.validateFields().then(values => {
  92. const updatedData: Partial<VideoItem> = {};
  93. // Handle Title
  94. if (values.titleType === 'custom') {
  95. updatedData.customTitle = values.customTitle || '';
  96. } else {
  97. updatedData.customTitle = '';
  98. updatedData.title = video?.title; // Revert to original if selected
  99. }
  100. // Handle Cover
  101. if (values.coverType === 'screenshot') {
  102. if (!selectedScreenshot) {
  103. message.error('请选择一个视频截图作为封面');
  104. return;
  105. }
  106. updatedData.customCoverType = 1;
  107. updatedData.customCover = selectedScreenshot;
  108. } else if (values.coverType === 'upload') {
  109. if (fileList.length === 0 || !fileList[0].url) {
  110. // If using status, check for 'done' status and response URL
  111. message.error('请上传自定义封面图片');
  112. return;
  113. }
  114. // Assuming the upload process directly provides the final URL in fileList[0].url
  115. // In a real app, you might get this from upload response
  116. updatedData.customCover = fileList[0].url;
  117. updatedData.customCoverType = 2;
  118. } else { // Original
  119. updatedData.customCover = '';
  120. updatedData.customCoverType = 0;
  121. updatedData.cover = video?.cover; // Revert to original
  122. }
  123. onOk(updatedData);
  124. }).catch(info => {
  125. console.log('Validate Failed:', info);
  126. });
  127. };
  128. const handleUploadChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
  129. // Only keep the latest file
  130. setFileList(newFileList.slice(-1));
  131. // If upload is successful, manually set coverType to 'upload'
  132. if (newFileList.length > 0 && newFileList[0].status === 'done') {
  133. setCoverType('upload');
  134. form.setFieldsValue({ coverType: 'upload' });
  135. // In a real app, you would get the URL from the response
  136. // For demo: assuming the file object gets a url property after upload
  137. if (!newFileList[0].url) {
  138. newFileList[0].url = newFileList[0].response.data.fileUrl; // Placeholder URL
  139. }
  140. }
  141. };
  142. const handleTitleTypeChange = (e: RadioChangeEvent) => {
  143. setTitleType(e.target.value);
  144. };
  145. const handleCoverTypeChange = (e: RadioChangeEvent) => {
  146. setCoverType(e.target.value);
  147. // Reset other cover selections when type changes
  148. if (e.target.value !== 'screenshot') setSelectedScreenshot(null);
  149. if (e.target.value !== 'upload') setFileList([]);
  150. };
  151. const handleSelectScreenshot = (imgUrl: string) => {
  152. setSelectedScreenshot(imgUrl);
  153. setCoverType('screenshot');
  154. form.setFieldsValue({ coverType: 'screenshot' });
  155. setFileList([]); // Clear upload list if screenshot selected
  156. };
  157. const uploadButton = (
  158. <button style={{ border: 0, background: 'none' }} type="button">
  159. <PlusOutlined />
  160. <div style={{ marginTop: 8 }}>上传封面</div>
  161. </button>
  162. );
  163. const checkFile = (file:UploadFile) => {
  164. if ((file.size || 0) > 5 * 1024 * 1024) {
  165. message.error('图片大小不能超过5MB')
  166. return Upload.LIST_IGNORE // 阻止上传,如果返回false,该文件还是会出现在fileList中
  167. }
  168. return true // 允许上传
  169. }
  170. const headers = {
  171. token: getAccessToken()
  172. }
  173. return (
  174. <Modal
  175. title="编辑标题/封面"
  176. open={visible}
  177. onCancel={onCancel}
  178. onOk={handleOk}
  179. okText="确定"
  180. cancelText="取消"
  181. width={600}
  182. destroyOnClose
  183. zIndex={20}
  184. forceRender // Keep form instance even when closed
  185. >
  186. <Form form={form} layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} labelAlign="left">
  187. {/* Title Section */}
  188. <Form.Item label="标题" name="titleType" initialValue="original">
  189. <Radio.Group onChange={handleTitleTypeChange}>
  190. <Radio value="original">原始标题</Radio>
  191. <Radio value="custom">自定义标题</Radio>
  192. </Radio.Group>
  193. </Form.Item>
  194. {titleType === 'custom' ? (
  195. <Form.Item
  196. label="自定义标题"
  197. name="customTitle"
  198. rules={[{ required: true, message: '请输入自定义标题' }]}
  199. >
  200. <TextArea rows={2} maxLength={50} showCount placeholder="请输入标题" />
  201. </Form.Item>
  202. ) : (
  203. <Form.Item
  204. label="自定义标题"
  205. name="title"
  206. rules={[{ required: true, message: '请输入自定义标题' }]}
  207. >
  208. <TextArea disabled rows={2} maxLength={50} showCount placeholder="请输入标题" />
  209. </Form.Item>
  210. )}
  211. {/* Cover Section */}
  212. <Form.Item label="封面" name="coverType" initialValue="original">
  213. <Radio.Group onChange={handleCoverTypeChange}>
  214. <Radio value="original">原始封面</Radio>
  215. <Radio value="screenshot">视频截图</Radio>
  216. <Radio value="upload">自定义上传</Radio>
  217. </Radio.Group>
  218. </Form.Item>
  219. {
  220. coverType === 'original' && (
  221. <img src={video?.cover} referrerPolicy="no-referrer" className="w-200 h-100 object-cover" />
  222. )
  223. }
  224. {/* Screenshot Selection */}
  225. {coverType === 'screenshot' && (
  226. <Form.Item label="视频截图" wrapperCol={{ offset: 4, span: 20 }}>
  227. <Space wrap>
  228. {screenshotImagesList.map((imgUrl, index) => (
  229. <div
  230. key={index}
  231. className={`relative w-24 h-24 border-2 cursor-pointer ${selectedScreenshot === imgUrl ? 'border-blue-500' : 'border-transparent'}`}
  232. onClick={() => handleSelectScreenshot(imgUrl)}
  233. >
  234. <Image width="100%" height="100%" src={imgUrl} preview={false} style={{ objectFit: 'cover' }} />
  235. {selectedScreenshot === imgUrl && (
  236. <CheckCircleFilled className="absolute top-1 right-1 text-green-500 bg-white rounded-full text-lg" />
  237. )}
  238. </div>
  239. ))}
  240. </Space>
  241. </Form.Item>
  242. )}
  243. {/* Custom Upload */}
  244. {coverType === 'upload' && (
  245. <Form.Item label="自定义上传" wrapperCol={{ offset: 4, span: 20 }}>
  246. <Upload
  247. // Replace with your actual upload endpoint
  248. action={adFileUpload}
  249. headers={headers}
  250. accept="image/*"
  251. listType="picture-card"
  252. beforeUpload={checkFile}
  253. onChange={handleUploadChange}
  254. fileList={fileList}
  255. showUploadList={{ showPreviewIcon: false }}
  256. maxCount={1}
  257. data={{ fileType: 'PICTURE' }}
  258. >
  259. {fileList.length >= 1 ? null : uploadButton}
  260. </Upload>
  261. </Form.Item>
  262. )}
  263. </Form>
  264. </Modal>
  265. );
  266. };
  267. export default EditTitleCoverModal;