addComponents.tsx 18 KB

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