material.tsx 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. import { useAjax } from "@/Hook/useAjax"
  2. import SelectCloud from "@/pages/launchSystemNew/components/selectCloud"
  3. import { get_tools_video_capture } from "@/services/launchAdq/global"
  4. import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
  5. import { Button, Form, message, Modal, Radio, Space, Switch, Image, Popconfirm } from "antd"
  6. import React, { useEffect, useState } from "react"
  7. import { useModel } from "umi"
  8. import styles from './index.less'
  9. import { getVideoImgUrl } from "@/utils/utils"
  10. interface Props {
  11. value?: any[]
  12. material?: { label: string, name: string, restriction: any, arrayProperty?: any }[],
  13. sysAdcreative: any
  14. onChange?: (data: any) => void
  15. }
  16. /**
  17. * 选择素材组
  18. * @returns
  19. */
  20. const Material: React.FC<Props> = (props) => {
  21. /**************************/
  22. const { onChange, material = [], sysAdcreative, value } = props
  23. const { adcreativeTemplateId } = sysAdcreative
  24. const [visible, setVisible] = useState<boolean>(false)
  25. const [selectVideoVisible, set_selectVideoVisible] = useState(false)
  26. const [videoImgsVisbile, set_videoImgsVisbile] = useState(false)
  27. const [videoUploads, setVideoUploads] = useState<any>()
  28. const [imgUploads, setImgUploads] = useState<any>()
  29. const { init } = useModel('useLaunchAdq.useBdMediaPup')
  30. const [materialConfig, setMaterialConfig] = useState<{
  31. adcreativeTemplateId?: number,
  32. type: string,
  33. cloudSize: { relation: string, width: number, height: number }[],
  34. list: any[],
  35. index: number,
  36. max: number,
  37. sliderImgContent: any
  38. }>({
  39. type: '',//类型
  40. cloudSize: [],//素材搜索条件
  41. list: [],//素材
  42. index: 0, // 素材组下标
  43. max: 1,//素材数量
  44. sliderImgContent: undefined
  45. })//图片素材配置
  46. const [videoImgs, set_videoImgs] = useState<{//视频封面图设置
  47. activeUrl: string,//选中的视频封面图地址
  48. preview: boolean,//是否开启图片点击预览
  49. urlList: any[],//生成的视频封面列表
  50. }>({
  51. activeUrl: '',
  52. preview: false,
  53. urlList: [
  54. 'https://test-adq-media.oss-cn-hangzhou.aliyuncs.com/image/21D8D51AD98C4FF8BF41F1C2D28EA39F.jpg',
  55. 'https://test-adq-media.oss-cn-hangzhou.aliyuncs.com/image/80DBE1AB3EDE4E85ABAE5F1670D9FED0.jpg',
  56. 'https://test-adq-media.oss-cn-hangzhou.aliyuncs.com/image/BCB2DAB86BDB4549BCB8E493C4F29E82.jpg',
  57. 'https://test-adq-media.oss-cn-hangzhou.aliyuncs.com/image/545A4C2A5B874C82A9D1C0C063624AE5.jpg'
  58. ]
  59. })
  60. const [form] = Form.useForm();
  61. let materials = Form.useWatch('materials', form)
  62. const getVideoCapture = useAjax((params) => get_tools_video_capture(params))
  63. /**************************/
  64. // 回填
  65. useEffect(() => {
  66. if (visible) {
  67. if (value && value?.length > 0) {
  68. let data = value?.map((item: any) => {
  69. let data: any = {}
  70. for (const key in item) {
  71. switch (key) {
  72. case 'imageUrl'://图素材
  73. data.image = item[key]
  74. break;
  75. case 'videoUrl'://视频素材
  76. data.video = item[key]
  77. break;
  78. case 'imageUrlList'://图素材
  79. data.image_list = item[key]
  80. break;
  81. case 'shortVideo1Url'://视频素材
  82. data.short_video1 = item[key]
  83. break;
  84. case 'elementStory':
  85. data.element_story = item[key].map((item: { imageUrl: string }) => item.imageUrl)
  86. break;
  87. }
  88. }
  89. return data
  90. })
  91. form.setFieldsValue({ materials: data })
  92. } else {
  93. form.setFieldsValue({ materials: [undefined] })
  94. }
  95. }
  96. }, [value, visible])
  97. // 处理判断有哪些素材 展示按钮
  98. useEffect(() => {
  99. if (material?.length > 0) {
  100. setVideoUploads(material?.find((item: { name: string }) => item.name === 'short_video1' || item.name === 'video'))
  101. setImgUploads(material?.find((item: { name: string }) => item.name === 'image' || item.name === 'image_list' || item.name === 'element_story'))
  102. } else {
  103. setVideoUploads({})
  104. setImgUploads({})
  105. }
  106. }, [material])
  107. // 确定
  108. const handleOk = () => {
  109. form.validateFields().then(values => {
  110. let data = values?.materials?.map((item: any) => {
  111. let data: any = {}
  112. for (const key in item) {
  113. switch (key) {
  114. case 'image'://图素材
  115. data.imageUrl = item[key]
  116. break;
  117. case 'video'://视频素材
  118. data.videoUrl = item[key]
  119. break;
  120. case 'image_list'://图素材
  121. data.imageUrlList = item[key]
  122. break;
  123. case 'short_video1'://视频素材
  124. data.shortVideo1Url = item[key]
  125. break;
  126. case 'element_story':
  127. data.elementStory = item[key].map((url: string) => ({ imageUrl: url }))
  128. break;
  129. }
  130. }
  131. return data
  132. })
  133. onChange && onChange(data)
  134. setVisible(false)
  135. })
  136. }
  137. return <>
  138. <span onClick={() => { setVisible(true) }}>{value && value?.length > 0 ? '编辑' : '添加'}</span>
  139. {visible && <Modal
  140. visible={visible}
  141. onCancel={() => setVisible(false)}
  142. title={<>
  143. <span>创意素材</span>
  144. {videoUploads && Object.keys(videoUploads)?.length > 0 && <Button type="link" onClick={() => {
  145. init({ mediaType: 'VIDEO', num: 100, cloudSize: adcreativeTemplateId === 1708 ? [[{ relation: '=', width: 1280, height: 720 }]] : [[{ relation: '=', width: videoUploads.restriction.videoRestriction.minWidth, height: videoUploads.restriction.videoRestriction.minHeight }]], maxSize: videoUploads.restriction.videoRestriction.fileSize * 1024 })
  146. setMaterialConfig({
  147. ...materialConfig,
  148. type: videoUploads.name,
  149. max: 1,
  150. index: 99999,
  151. adcreativeTemplateId
  152. })
  153. setTimeout(() => {
  154. set_selectVideoVisible(true)
  155. }, 100)
  156. }}>批量添加视频素材</Button>}
  157. {imgUploads && Object.keys(imgUploads)?.length > 0 && <Button type="link" onClick={() => {
  158. init({ mediaType: 'IMG', num: 100, cloudSize: [[{ relation: '=', width: imgUploads.restriction.imageRestriction.width, height: imgUploads.restriction.imageRestriction.height }]], maxSize: imgUploads.restriction.imageRestriction.fileSize * 1024 })
  159. setMaterialConfig({
  160. ...materialConfig,
  161. type: imgUploads.name,
  162. max: (imgUploads.name === 'image_list' || imgUploads.name === 'element_story') ? imgUploads.arrayProperty.maxNumber : 1,
  163. index: 99999,
  164. adcreativeTemplateId
  165. })
  166. setTimeout(() => {
  167. set_selectVideoVisible(true)
  168. }, 100)
  169. }}>批量添加图片素材</Button>}
  170. {(materials && materials?.length > 1) && <Popconfirm
  171. title="是否清空?"
  172. onConfirm={() => form.setFieldsValue({ materials: [undefined] })}
  173. okText="是"
  174. cancelText="否"
  175. >
  176. <Button type="link" danger>一键清空</Button>
  177. </Popconfirm>}
  178. </>}
  179. width={930}
  180. bodyStyle={{
  181. maxHeight: 650,
  182. overflowY: 'scroll'
  183. }}
  184. onOk={handleOk}
  185. >
  186. <Form name="dynamic_form_item" form={form} labelAlign='left' >
  187. <Form.List name="materials">
  188. {(fields, { add, remove }) => (<>
  189. {fields.map((field, num) => (<div key={field.key}>
  190. <Space size={30} style={{ width: '100%' }} className={styles.space}>
  191. {material?.map((item, index) => {
  192. if (item.name === 'short_video1' || item.name === 'video') {
  193. return <Form.Item
  194. {...field}
  195. label={<strong>{item.label}</strong>}
  196. rules={[{ required: true, message: '请选择素材!' }]}
  197. name={[field.name, item.name]}
  198. key={index}
  199. >
  200. <div className={`${styles.box} ${styles.video}`} style={{ width: 300, height: 160 }} onClick={() => {
  201. init({ mediaType: 'VIDEO', cloudSize: adcreativeTemplateId === 1708 ? [[{ relation: '=', width: 1280, height: 720 }]] : [[{ relation: '=', width: item.restriction.videoRestriction.minWidth, height: item.restriction.videoRestriction.minHeight }]], maxSize: item.restriction.videoRestriction.fileSize * 1024 })
  202. setMaterialConfig({
  203. ...materialConfig,
  204. type: item.name,
  205. max: 1,
  206. index: num,
  207. adcreativeTemplateId
  208. })
  209. setTimeout(() => {
  210. set_selectVideoVisible(true)
  211. }, 100)
  212. }}>
  213. <p>
  214. {materials?.length > 0 && materials[num] && Object.keys(materials[num])?.includes(item.name) ? <video src={materials[num][item.name]} controls /> : <>
  215. <span>{`推荐尺寸(${adcreativeTemplateId === 1708 ? 1280 : item.restriction.videoRestriction.minWidth} x ${adcreativeTemplateId === 1708 ? 720 : item.restriction.videoRestriction.minHeight})`}</span>
  216. <span>{`${item.restriction.videoRestriction.fileFormat?.map((str: any) => str?.replace('MEDIA_TYPE_', ''))};< ${item.restriction.videoRestriction.fileSize / 1024}M;时长 ≥ ${item.restriction.videoRestriction.minDuration}s,≤ ${item.restriction.videoRestriction.maxDuration}s,必须带有声音`}</span>
  217. </>}
  218. </p>
  219. </div>
  220. </Form.Item>
  221. }
  222. if (item.name === 'image') {
  223. return <Form.Item
  224. {...field}
  225. label={<strong>{item.label}</strong>}
  226. rules={[{ required: true, message: '请选择素材!' }]}
  227. name={[field.name, item.name]}
  228. key={index}
  229. >
  230. <div className={`${styles.box} ${styles.image}`} style={{ width: 300, height: 160 }} onClick={() => {
  231. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
  232. setMaterialConfig({
  233. ...materialConfig,
  234. type: item.name,
  235. max: 1,
  236. index: num,
  237. adcreativeTemplateId
  238. })
  239. setTimeout(() => {
  240. set_selectVideoVisible(true)
  241. }, 100)
  242. }}>
  243. <p>
  244. {materials?.length > 0 && materials[num] && Object.keys(materials[num])?.includes(item.name) ? <img src={materials[num][item.name]} /> : <>
  245. <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
  246. <span>{`${item.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
  247. </>}
  248. </p>
  249. </div>
  250. </Form.Item>
  251. }
  252. if (item.name === 'image_list') {
  253. return <Form.Item
  254. {...field}
  255. label={<strong>{item.label}</strong>}
  256. rules={[{ required: true, message: '请选择素材!' }]}
  257. name={[field.name, item.name]}
  258. key={index}
  259. >
  260. <div className={`${styles.box} ${item.arrayProperty.maxNumber >= 3 ? styles.image_list : styles.imageMater}`} style={item.arrayProperty.maxNumber >= 3 ? { flexFlow: 'row', width: '100%' } : {}} onClick={() => {
  261. init({ mediaType: 'IMG', num: item.arrayProperty.maxNumber, cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
  262. setMaterialConfig({
  263. ...materialConfig,
  264. type: item.name,
  265. max: item.arrayProperty.maxNumber,
  266. index: num,
  267. adcreativeTemplateId
  268. })
  269. setTimeout(() => {
  270. set_selectVideoVisible(true)
  271. }, 100)
  272. }}>
  273. {Array(item.arrayProperty.maxNumber).fill('').map((arr, index1) => {
  274. return <p key={index1} style={item.arrayProperty.maxNumber >= 3 ? { width: 130, height: 130 } : { width: 130, height: 130, justifyContent: 'center' }}>
  275. {materials?.length > 0 && materials[num] && Object.keys(materials[num])?.includes(item.name) && materials[num][item.name][index1] ? <img src={materials[num][item.name][index1]} /> : <>
  276. <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
  277. <span>{`${item.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
  278. </>}
  279. </p>
  280. })}
  281. </div>
  282. </Form.Item>
  283. }
  284. if (item.name === 'element_story') {
  285. return <Form.Item
  286. {...field}
  287. label={<strong>{item.label}</strong>}
  288. rules={[{ required: true, message: '请选择素材!' }]}
  289. name={[field.name, item.name]}
  290. key={index}
  291. >
  292. <div className={`${styles.box} ${item.arrayProperty.maxNumber >= 3 ? styles.image_list : styles.imageMater}`} style={item.arrayProperty.maxNumber >= 3 ? { flexFlow: 'row', width: '100%' } : {}} onClick={() => {
  293. init({ mediaType: 'IMG', num: item.arrayProperty.maxNumber, cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
  294. setMaterialConfig({
  295. ...materialConfig,
  296. type: item.name,
  297. max: item.arrayProperty.maxNumber,
  298. index: num,
  299. adcreativeTemplateId
  300. })
  301. setTimeout(() => {
  302. set_selectVideoVisible(true)
  303. }, 100)
  304. }}>
  305. {Array(item.arrayProperty.maxNumber).fill('').map((arr, index1) => {
  306. return <p key={index1} style={item.arrayProperty.maxNumber >= 3 ? { width: 130, height: 130 } : { width: 130, height: 130, justifyContent: 'center' }}>
  307. {materials?.length > 0 && materials[num] && Object.keys(materials[num])?.includes(item.name) && materials[num][item.name][index1] ? <img style={item?.restriction.imageRestriction?.width > item?.restriction.imageRestriction?.height ? { width: '100%', height: 'auto' } : {}} src={materials[num][item.name][index1]} /> : <>
  308. <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
  309. <span>{`${item.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
  310. </>}
  311. </p>
  312. })}
  313. </div>
  314. </Form.Item>
  315. }
  316. return null
  317. })}
  318. {fields?.length > 1 && <MinusCircleOutlined className={styles.clear} onClick={() => remove(field.name)} style={{ marginBottom: 24, color: 'red' }} />}
  319. </Space>
  320. </div>))}
  321. <Form.Item>
  322. <Button type="link" onClick={() => add()} icon={<PlusOutlined />} style={{ padding: 0 }}>
  323. 新增素材组
  324. </Button>
  325. </Form.Item>
  326. </>)}
  327. </Form.List>
  328. </Form>
  329. </Modal>}
  330. {/* 选择视频素材 */}
  331. {selectVideoVisible && <SelectCloud
  332. visible={selectVideoVisible}
  333. onClose={() => set_selectVideoVisible(false)}
  334. sliderImgContent={materialConfig.index === 99999 ? undefined :
  335. materialConfig.type === 'element_story' ? (materials[materialConfig.index] && Object.keys(materials[materialConfig.index])?.includes('element_story')) ? materials[materialConfig.index]['element_story']?.map((item: string) => ({ url: item })) : undefined :
  336. materialConfig.type === 'image_list' ? (materials[materialConfig.index] && Object.keys(materials[materialConfig.index])?.includes('image_list')) ? materials[materialConfig.index]['image_list']?.map((item: string) => ({ url: item })) : undefined :
  337. (materials[materialConfig.index] && Object.keys(materials[materialConfig.index])?.includes(materialConfig.type)) ? [{ url: materials[materialConfig.index][materialConfig.type] }] : undefined
  338. }
  339. onChange={(content: any) => {
  340. if (content.length > 0) {
  341. if (materialConfig.index === 99999) {
  342. if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
  343. let urls = content?.map((item: any) => item?.url)
  344. let max = materialConfig.max
  345. let materialsNew = materials.map((item: any) => {
  346. let newItem = item || {}
  347. // 判断是否有字段,是否设置了值
  348. if (Object.keys(newItem).includes(materialConfig.type) && newItem[materialConfig.type]) {
  349. if (max > newItem[materialConfig.type].length && urls.length > 0) {
  350. let difference = max - newItem[materialConfig.type].length
  351. let material: any[] = []
  352. if (urls.length >= difference) {
  353. material = urls.splice(0, difference)
  354. } else {
  355. material = urls.splice(0, urls.length)
  356. }
  357. newItem[materialConfig.type] = [...newItem[materialConfig.type], ...material]
  358. return newItem
  359. } else {
  360. return newItem
  361. }
  362. } else {
  363. if (urls.length >= max) {
  364. let material = urls.splice(0, max)
  365. return { ...newItem, [materialConfig.type]: material }
  366. } else if (urls.length > 0) {
  367. let material = urls.splice(0, urls.length)
  368. return { ...newItem, [materialConfig.type]: material }
  369. } else {
  370. return newItem
  371. }
  372. }
  373. })
  374. if (urls.length > 0) {
  375. let data = Array(Math.ceil(urls.length / max)).fill(undefined).map(item => {
  376. if (urls.length >= max) {
  377. let material = urls.splice(0, max)
  378. return { [materialConfig.type]: material }
  379. } else {
  380. let material = urls.splice(0, urls.length)
  381. return { [materialConfig.type]: material }
  382. }
  383. })
  384. materialsNew = [...materialsNew, ...data]
  385. }
  386. form.setFieldsValue({ materials: materialsNew })
  387. } else {
  388. let newMaterials = content?.map((item: any) => {
  389. if (materialConfig.type === 'video' && material.length === 2) {
  390. return { [materialConfig.type]: item?.url, image: getVideoImgUrl(item?.url) }
  391. }
  392. return { [materialConfig.type]: item?.url }
  393. })
  394. if (newMaterials.length > 0) {
  395. if (materials?.every((item: any) => !item)) { // 没设置过
  396. form.setFieldsValue({ materials: newMaterials })
  397. } else { // 设置过
  398. let materialsNew = materials.map((item: any) => {
  399. let newItem = item || {}
  400. if (Object.keys(newItem).includes(materialConfig.type) && newItem[materialConfig.type]) {
  401. return item
  402. } else {
  403. if (newMaterials.length > 0) {
  404. let material = newMaterials.splice(0, 1)
  405. return { ...newItem, ...material[0] }
  406. } else {
  407. return item
  408. }
  409. }
  410. })
  411. if (newMaterials.length > 0) {
  412. materialsNew = [...materialsNew, ...newMaterials]
  413. }
  414. form.setFieldsValue({ materials: materialsNew })
  415. }
  416. }
  417. }
  418. } else {
  419. materials = materials?.map((item: any, index: number) => {
  420. if (materialConfig.index === index) {
  421. if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
  422. if (item) {
  423. item[materialConfig.type] = content?.map((item: any) => item?.url)
  424. return { ...item }
  425. } else {
  426. return { [materialConfig.type]: content?.map((item: any) => item?.url) }
  427. }
  428. } else {
  429. if (item) {
  430. item[materialConfig.type] = content[0]?.url
  431. return { ...item }
  432. } else {
  433. if (materialConfig.type === 'video' && material.length === 2) {
  434. return { [materialConfig.type]: content[0]?.url, image: getVideoImgUrl(content[0]?.url) }
  435. }
  436. return { [materialConfig.type]: content[0]?.url }
  437. }
  438. }
  439. }
  440. return item
  441. })
  442. form.setFieldsValue({ materials })
  443. }
  444. }
  445. set_selectVideoVisible(false)
  446. }}
  447. />}
  448. {/* 视频封面图弹窗 */}
  449. {videoImgsVisbile && <Modal
  450. visible={videoImgsVisbile}
  451. title={<div>生成封面图 <Switch checkedChildren="开启预览" unCheckedChildren="关闭预览" checked={videoImgs.preview} onChange={(checked) => { set_videoImgs({ ...videoImgs, preview: checked }) }} /></div>}
  452. onOk={() => {
  453. if (videoImgs.activeUrl) {
  454. // setImgMaterialConfig({ ...imgMaterialConfig, list: [{ url: videoImgs.activeUrl }] })
  455. set_videoImgsVisbile(false)
  456. } else {
  457. message.error('请选择图片,获取使用取消按钮关闭弹窗!')
  458. }
  459. }}
  460. onCancel={() => { set_videoImgsVisbile(false) }}
  461. confirmLoading={getVideoCapture.loading}
  462. width={600}
  463. >
  464. <Radio.Group className={styles.videoImgs} onChange={(e) => {
  465. let url = e.target.value
  466. set_videoImgs({ ...videoImgs, activeUrl: url })
  467. }}>
  468. {
  469. videoImgs?.urlList?.map((item: any, index: number) => {
  470. return <Radio.Button value={item} key={index}>
  471. <Image src={item} preview={videoImgs.preview} />
  472. </Radio.Button>
  473. })
  474. }
  475. </Radio.Group>
  476. </Modal>}
  477. </>
  478. }
  479. export default React.memo(Material)