addComponents.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import React, { useState } from 'react';
  2. import '../../tencentAdPutIn/index.less'
  3. import { Button, Card, Dropdown, Empty, Form, message, Modal, Space, Table } from 'antd';
  4. import { CloseCircleOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
  5. import { DEFAULT_COMPONENT_SUB_IMAGE_TYPE, DEFAULT_COMPONENT_SUB_VIDEO_TYPE, IComponent } from './const';
  6. import style from './index.less'
  7. import SelectCloudComponent from '../../material/cloudNew/selectCloudComponent';
  8. import VideoNews from '@/pages/launchSystemNew/components/newsModal/videoNews';
  9. import { useAjax } from '@/Hook/useAjax';
  10. import { addBatchCreativeComponentApi, CreativeComponentDetailDTOS } from '@/services/adqV3/global';
  11. interface Props {
  12. type: 'IMAGE' | 'VIDEO'
  13. putInType: 'NOVEL' | 'GAME',
  14. accountId: number;
  15. visible?: boolean;
  16. onClose?: () => void;
  17. onChange?: () => void;
  18. }
  19. /**
  20. * 新建组件
  21. * @param param0
  22. * @returns
  23. */
  24. const AddComponents: React.FC<Props> = ({ type, putInType, accountId, visible, onChange, onClose }) => {
  25. /****************************************/
  26. const [form] = Form.useForm();
  27. const componentsGroup = Form.useWatch('componentsGroup', form)
  28. const [selectCloudData, setSelectCloudData] = useState<{
  29. defaultParams: {
  30. sizeQueries?: {
  31. width: number,
  32. height: number,
  33. relation: string
  34. }[],
  35. materialType: 'image' | 'video'
  36. fileSize: number
  37. }
  38. num: number
  39. }>()
  40. const [materialConfig, setMaterialConfig] = useState<{
  41. adcreativeTemplateId?: number,
  42. type: string,
  43. cloudSize: { relation: string, width: number, height: number }[],
  44. list: any[],
  45. index: number,
  46. max: number,
  47. sliderImgContent: any,
  48. isGroup?: boolean
  49. }>({
  50. type: '',//类型
  51. cloudSize: [],//素材搜索条件
  52. list: [],//素材
  53. index: 0, // 素材组下标
  54. max: 1,//素材数量
  55. sliderImgContent: undefined
  56. })//图片素材配置
  57. const [selectVideoVisible, setSelectVideoVisible] = useState(false)
  58. const addBatchCreativeComponent = useAjax((params) => addBatchCreativeComponentApi(params))
  59. /****************************************/
  60. const handleOk = (values: any) => {
  61. if (values?.componentsGroup?.length) {
  62. const params = values.componentsGroup.reduce((pre: CreativeComponentDetailDTOS[], cur: { componentSubType: string; list: any[] }) => {
  63. pre.push(...cur?.list?.map((item) => {
  64. if (cur?.componentSubType?.includes('IMAGE_LIST')) {
  65. return {
  66. componentSubType: cur.componentSubType,
  67. materialIdList: item.map((i: { id: any; }) => i.id),
  68. componentType: 'imageList'
  69. }
  70. } else if (cur?.componentSubType?.includes('VIDEO')) {
  71. return {
  72. componentSubType: cur.componentSubType,
  73. materialId: item.id,
  74. componentType: 'video',
  75. coverUrl: item.key_frame_image_url
  76. }
  77. }
  78. return {
  79. componentSubType: cur.componentSubType,
  80. materialId: item.id,
  81. componentType: 'image'
  82. }
  83. }))
  84. return pre
  85. }, [])
  86. console.log(params);
  87. addBatchCreativeComponent.run({
  88. adAccountId: accountId,
  89. creativeComponentDetailDTOS: params
  90. }).then((res) => {
  91. if (res?.length === 0) {
  92. message.success('创建成功')
  93. onChange?.()
  94. } else {
  95. console.log(res);
  96. Modal.confirm({
  97. okText: '关闭',
  98. cancelText: '继续',
  99. className: 'modalResetCss',
  100. icon: false,
  101. width: 750,
  102. content: <Table
  103. dataSource={res?.map((text: string, index: number) => ({ des: text, id: index + 1 }))}
  104. columns={[
  105. {
  106. title: '新增详情',
  107. dataIndex: 'des',
  108. key: 'des',
  109. ellipsis: true
  110. }
  111. ]}
  112. size="small"
  113. bordered
  114. rowKey={'id'}
  115. />,
  116. onOk() {
  117. onChange?.()
  118. },
  119. onCancel() {}
  120. });
  121. }
  122. })
  123. }
  124. }
  125. /** 删除单个 */
  126. const clearTem = (index: number, count: number) => {
  127. const newComponentsGroup = componentsGroup?.map((item: any, i: number) => {
  128. if (i === index) {
  129. const oldList = item?.list?.filter((_: any, li: number) => count !== li)
  130. return { ...item, list: oldList }
  131. }
  132. return item
  133. })
  134. form.setFieldsValue({ componentsGroup: newComponentsGroup })
  135. }
  136. const componentsGroupSubTypeList = componentsGroup?.map((item: { componentSubType: any; }) => item.componentSubType) || []
  137. return <Modal
  138. title={<strong style={{ fontSize: 20 }}>新建组件</strong>}
  139. open={visible}
  140. onCancel={onClose}
  141. footer={null}
  142. width={900}
  143. className={`modalResetCss`}
  144. bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
  145. maskClosable={false}
  146. >
  147. <Form
  148. form={form}
  149. name="newComponents"
  150. labelAlign='left'
  151. layout="vertical"
  152. colon={false}
  153. style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: 10, borderRadius: '0 0 8px 8px' }}
  154. scrollToFirstError={{
  155. behavior: 'smooth',
  156. block: 'center'
  157. }}
  158. onFinishFailed={({ errorFields }) => {
  159. message.error(errorFields?.[0]?.errors?.[0])
  160. }}
  161. onFinish={handleOk}
  162. initialValues={{
  163. // componentsGroup: [{ componentSubType: 'IMAGE_16X9' }, { componentSubType: 'IMAGE_LIST_1X1_3' }]
  164. }}
  165. >
  166. <Form.List name="componentsGroup">
  167. {(fields, { add, remove }) => (<>
  168. <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
  169. {fields.map((field, num) => {
  170. const componentData = (type === 'IMAGE' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE).find(item => item.value === componentsGroup?.[num]?.componentSubType) as IComponent
  171. const title = componentData?.label
  172. return <Card
  173. title={<Space>
  174. <strong>{title || '组件'} 组件</strong>
  175. <Button
  176. type="link"
  177. disabled={componentsGroup?.[num]?.['list']?.length >= 200}
  178. onClick={() => {
  179. let count = 200 - (componentsGroup?.[num]?.['list']?.length || 0)
  180. let isGroup = false
  181. if (componentData?.value?.includes('IMAGE_LIST')) {
  182. count = componentData?.restriction.minNumber || count
  183. isGroup = true
  184. }
  185. setSelectCloudData({
  186. num: count,
  187. defaultParams: {
  188. materialType: type === 'IMAGE' ? 'image' : 'video',
  189. sizeQueries: [
  190. { relation: '=', width: componentData?.restriction.width || 0, height: componentData?.restriction.height || 0 }
  191. ],
  192. fileSize: (componentData?.restriction.fileSize || 0) * 1024
  193. }
  194. })
  195. setMaterialConfig({
  196. ...materialConfig,
  197. type: type === 'IMAGE' ? 'image' : 'video',
  198. max: count,
  199. index: num,
  200. isGroup
  201. })
  202. setTimeout(() => {
  203. setSelectVideoVisible(true)
  204. }, 100)
  205. }}
  206. >添加</Button>
  207. </Space>}
  208. className="cardResetCss"
  209. key={field.key}
  210. style={{ width: '100%' }}
  211. extra={fields?.length > 1 && <DeleteOutlined className={style.clear} onClick={() => remove(field.name)} style={{ color: 'red' }} />}
  212. >
  213. <Form.Item
  214. {...field}
  215. label={<strong>素材</strong>}
  216. rules={[{ required: true, message: '请选择素材!' }]}
  217. name={[field.name, 'list']}
  218. >
  219. <div className={`${style.box} ${style.video}`} style={{ width: '100%', height: 'auto', backgroundColor: 'rgb(247, 249, 252)' }}>
  220. {componentsGroup?.length && componentsGroup?.[num]?.['list']?.length > 0 ? <div className={style.boxList}>
  221. <div className={style.boxList_title}>
  222. <span>上传素材 <span style={{ marginLeft: 5, color: '#999', fontWeight: 'normal' }}>已选:{componentsGroup?.[num]?.['list']?.length || 0} 个素材</span></span>
  223. <a onClick={() => {
  224. const newComponentsGroup = componentsGroup?.map((item: any, i: number) => {
  225. if (i === num) {
  226. return { ...item, list: [] }
  227. }
  228. return item
  229. })
  230. form.setFieldsValue({ componentsGroup: newComponentsGroup })
  231. }}>清空</a>
  232. </div>
  233. <div className={style.boxList_body}>
  234. {componentsGroup?.[num]?.['list']?.map((item: any, index: number) => {
  235. if (componentsGroup?.[num]?.componentSubType?.includes('IMAGE_LIST')) {
  236. let length = item.length
  237. return <div className={style.boxList_body_item} key={index}>
  238. <div className={style.tag}>{length}图</div>
  239. <div className={style.content}>
  240. {item?.map((l: { oss_url: string | undefined; }, i: number) => <img src={l?.oss_url} key={i} style={{ width: length >= 6 ? 33.3 : 49.9 }} />)}
  241. </div>
  242. <div className={style.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
  243. </div>
  244. } else if (componentsGroup?.[num]?.componentSubType?.includes('VIDEO')) {
  245. return <div className={style.boxList_body_item} key={index}>
  246. <div className={style.content}>
  247. <VideoNews src={item?.oss_url} keyFrameImageUrl={item?.key_frame_image_url} style={{ width: 100, height: 100 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
  248. </div>
  249. <div className={style.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
  250. </div>
  251. }
  252. return <div className={style.boxList_body_item} key={index}>
  253. <div className={style.content}><img src={item?.oss_url} /></div>
  254. <div className={style.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
  255. </div>
  256. })}
  257. </div>
  258. </div> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无素材内容,可通过上方添加" />}
  259. </div>
  260. </Form.Item>
  261. </Card>
  262. })}
  263. </div>
  264. <Form.Item style={{ marginBottom: 0, marginTop: 10 }}>
  265. <Dropdown
  266. menu={{
  267. items: (type === 'IMAGE' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE).map(item => ({ label: item.label, key: item.value, disabled: componentsGroupSubTypeList.includes(item.value) })),
  268. onClick: (e) => {
  269. setTimeout(() => add({ componentSubType: e.key }), 100)
  270. },
  271. style: { maxHeight: 260, overflowY: 'auto' }
  272. }}
  273. overlayStyle={{ minWidth: 350 }}
  274. trigger={['click']}
  275. placement='top'
  276. >
  277. <Button type="dashed" style={{ color: '#1890ff' }} block icon={<PlusOutlined />}>添加组件组</Button>
  278. </Dropdown>
  279. </Form.Item>
  280. </>)}
  281. </Form.List>
  282. {(componentsGroup?.length === 0 || !componentsGroup) && <Card className="cardResetCss" style={{ marginTop: 10 }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无组内容,可通过上方添加" /></Card>}
  283. <Form.Item className="submit_pull">
  284. <Space>
  285. <Button onClick={onClose}>取消</Button>
  286. <Button type="primary" htmlType="submit" className="modalResetCss" loading={addBatchCreativeComponent.loading}>
  287. 确定
  288. </Button>
  289. </Space>
  290. </Form.Item>
  291. </Form>
  292. {/* 选择素材 */}
  293. {(selectVideoVisible && selectCloudData) && <SelectCloudComponent
  294. {...selectCloudData}
  295. accountCreateLogs={[{ accountId: accountId }]}
  296. visible={selectVideoVisible}
  297. isGroup={materialConfig?.isGroup}
  298. onClose={() => {
  299. setSelectVideoVisible(false)
  300. setSelectCloudData(undefined)
  301. }}
  302. putInType={putInType}
  303. onChange={(content: any) => {
  304. console.log(content)
  305. if (content.length > 0) {
  306. const newComponentsGroup = componentsGroup?.map((item: any, index: number) => {
  307. if (materialConfig.index === index) {
  308. let oldList = item?.list || []
  309. if (materialConfig.isGroup) {
  310. oldList = oldList.concat([content])
  311. return { ...item, list: oldList }
  312. } else {
  313. oldList = oldList.concat(content)
  314. return { ...item, list: oldList }
  315. }
  316. }
  317. return item
  318. })
  319. form.setFieldsValue({ componentsGroup: newComponentsGroup })
  320. }
  321. setSelectVideoVisible(false)
  322. setSelectCloudData(undefined)
  323. }}
  324. />}
  325. </Modal>
  326. };
  327. export default AddComponents;