addDynamic.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. import { Button, Card, Drawer, Empty, Popconfirm, Space, Spin, Table, Tabs, Typography, message } from "antd"
  2. import React, { useEffect, useState } from "react"
  3. import '../index.less'
  4. import style from './index.less'
  5. import Dynamic from "./Dynamic";
  6. import Material from "./Material";
  7. import MaterialText from "./MaterialText";
  8. import PageList from "./PageList";
  9. import { DispatchAddelivery } from ".";
  10. import { CheckOutlined, SearchOutlined } from "@ant-design/icons";
  11. import WechatAccount from "../../components/WechatAccount";
  12. import { cartesianProduct, distributeArray } from "@/utils/utils";
  13. import { columnsAddDynamic } from "./tableConfig";
  14. import { useAjax } from "@/Hook/useAjax";
  15. import { createDynamicTaskApi } from "@/services/adqV3";
  16. import TacticsS from "./TacticsS";
  17. const { Text, Title } = Typography;
  18. /**
  19. * 新增创意
  20. * @returns
  21. */
  22. const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose, adData: selectData, tactics }) => {
  23. /****************************************/
  24. const [addelivery, setAddelivery] = useState<PULLIN.AddeliveryProps>({ adgroups: {}, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {}, mediaType: 0 })
  25. const { adgroups } = addelivery
  26. const [wechatVisible, setWechatVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
  27. const [materialData, setMaterialData] = useState<any>({}) // 素材数据
  28. const [textData, setTextData] = useState<any>({})
  29. const [accountCreateLogs, setAccountCreateLogs] = useState<PULLIN.AccountCreateLogsProps[]>([]) // 账户
  30. const [tableData, setTableData] = useState<any>({})
  31. const [activeKey, setActiveKey] = useState<string>()
  32. const [dynamicCount, setDynamicCount] = useState<number>(0)
  33. const [adData, setAdData] = useState<any[]>(selectData)
  34. const createDynamicTask = useAjax((params) => createDynamicTaskApi(params))
  35. /****************************************/
  36. useEffect(() => {
  37. if (tactics) {
  38. const {
  39. adData,
  40. addelivery,
  41. accountCreateLogs,
  42. materialData,
  43. textData
  44. } = JSON.parse(tactics.strategyValue)
  45. setAccountCreateLogs(accountCreateLogs)
  46. setAddelivery(addelivery)
  47. setAdData(adData)
  48. setMaterialData(materialData)
  49. setTextData(textData)
  50. } else if (selectData?.length > 0) {
  51. const { siteSet, marketingCarrierType, marketingGoal, marketingTargetType, sceneSpec, automaticSiteEnabled } = selectData[0]
  52. setAddelivery({ ...addelivery, adgroups: { marketingGoal, marketingCarrierType, siteSet, automaticSiteEnabled, sceneSpec, marketingAssetOuterSpec: { marketingTargetType } } })
  53. let AccountSet = new Set(selectData.map(item => item.accountId))
  54. setAccountCreateLogs([...AccountSet].map(accountId => ({ accountId })))
  55. }
  56. }, [selectData, tactics])
  57. const clearData = () => {
  58. setTableData([])
  59. }
  60. // 预览
  61. const preview = () => {
  62. if (accountCreateLogs?.length === 0) {
  63. message.error('请先选择媒体账户')
  64. return
  65. }
  66. const { adgroups, dynamic, dynamicMaterialDTos, dynamicCreativesTextDTOS, mediaType } = addelivery
  67. if (!(adgroups && Object.keys(adgroups).length)) {
  68. message.error('请先配置广告信息')
  69. return
  70. }
  71. if ((['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(adgroups?.marketingAssetOuterSpec?.marketingTargetType) || adgroups?.marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') && !accountCreateLogs?.some(item => item?.wechatChannelList?.length)) {
  72. message.error('请先选择公众号')
  73. return
  74. }
  75. if (!(dynamic && Object.keys(dynamic).length)) {
  76. message.error('请先配置创意')
  77. return
  78. }
  79. if ((materialData && Object.keys(materialData).length) && !(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length)) {
  80. message.error('请先配置创意素材')
  81. return
  82. }
  83. if ((textData && Object.keys(textData).length) && !(dynamicCreativesTextDTOS && Object.keys(dynamicCreativesTextDTOS).length)) {
  84. message.error('请先配置创意文案')
  85. return
  86. }
  87. if (!accountCreateLogs?.some(item => item?.pageList?.length)) {
  88. message.error('请先选择落地页')
  89. return
  90. }
  91. let newTableData = {}, newDynamicCount = 0
  92. let textType = dynamicCreativesTextDTOS.type
  93. let textDto = dynamicCreativesTextDTOS?.dynamicCreativesTextDetailDTOList || []
  94. let textDtoLenth = textDto.length
  95. let dynamicGroupLength = dynamicMaterialDTos?.dynamicGroup?.length || 0
  96. let newDynamicGroup: any = []
  97. if (![910].includes(dynamic.creativeTemplateId)) {
  98. newDynamicGroup = dynamicMaterialDTos?.dynamicGroup || []
  99. if (newDynamicGroup.length > 0 && [0, 1, 2, 3].includes(textType)) {
  100. if (textType === 0) {
  101. newDynamicGroup = newDynamicGroup.map((item: any) => ({ ...item, textDto: textDto?.[0] }))
  102. } else if (textType === 1) {
  103. newDynamicGroup = newDynamicGroup.map((item: any, index: number) => ({ ...item, textDto: textDto?.[index] }))
  104. } else if (textType === 2) {
  105. newDynamicGroup = newDynamicGroup.map((item: any, index: number) => ({ ...item, textDto: textDto?.[index % textDtoLenth] }))
  106. } else if (textType === 3) {
  107. if (mediaType === 0) {
  108. newDynamicGroup = cartesianProduct(newDynamicGroup, textDto || [{}]).map((item) => {
  109. let [dynamicGroup, textDtoData] = item
  110. return {
  111. ...dynamicGroup as any,
  112. textDto: textDtoData
  113. }
  114. })
  115. }
  116. }
  117. }
  118. }
  119. // 创意组平均分配到广告逻辑
  120. let averageAdDynamicList: any[] = []
  121. if ((mediaType === 1 || mediaType === 2) && newDynamicGroup.length) {
  122. let adLength = adData.length
  123. if (mediaType === 1) {
  124. if (adLength > dynamicGroupLength) {
  125. message.error(`创意组分配规则选择“平均分配到广告”时,创意组总数必须大于等于广告总数。当前创意组数量:${dynamicGroupLength},广告数量:${adLength}`)
  126. return
  127. }
  128. averageAdDynamicList = distributeArray(newDynamicGroup, adLength)
  129. } else if (mediaType === 2) {
  130. if (adLength < dynamicGroupLength) {
  131. message.error(`创意组分配规则选择“顺序分配到广告”时,创意组总数必须小于等于广告总数。当前创意组数量:${dynamicGroupLength},广告数量:${adLength}`)
  132. return
  133. }
  134. }
  135. }
  136. let accountIndex1 = 0
  137. adData.forEach((ad, index) => {
  138. let item = accountCreateLogs.find(a => a.accountId === ad.accountId) as PULLIN.AccountCreateLogsProps
  139. let averageAdDynamic = averageAdDynamicList?.[index]
  140. let data = [{
  141. id: ad.adgroupId + '_' + index,
  142. pageListDto: item.pageList, // 落地页
  143. adgroupsDto: ad,
  144. dynamicDto: dynamic, // 创意信息
  145. averageAdDynamic,
  146. rowSpan: mediaType === 1 ? averageAdDynamic.length : ([910].includes(dynamic.creativeTemplateId) ? item.pageList?.length : (textType === 3 ? textDtoLenth * dynamicGroupLength : dynamicGroupLength)) || 1
  147. }]
  148. let newData: any[] = []
  149. if ([910].includes(dynamic.creativeTemplateId)) {
  150. newData = cartesianProduct(data, item.pageList).map((item, index) => {
  151. let [d1, pageList, num] = item
  152. return {
  153. ...d1,
  154. id: d1.id + '_' + index,
  155. pageListDto: [pageList],
  156. dynamicDto: {
  157. ...d1.dynamicDto,
  158. dynamicCreativeName: d1.dynamicDto.dynamicCreativeName + num
  159. }
  160. }
  161. })
  162. } else {
  163. if (mediaType === 1) {
  164. data.forEach(item => {
  165. const { averageAdDynamic, ...ad } = item
  166. if (textType === 3) {
  167. let rowSpan = textDtoLenth * averageAdDynamic.length
  168. cartesianProduct(textDto, averageAdDynamic).forEach((taad: any, index) => {
  169. let [textValue, aad] = taad
  170. newData.push({
  171. ...ad,
  172. id: ad.id + '_' + index,
  173. dynamicGroup: aad,
  174. textDto: textValue,
  175. rowSpan
  176. })
  177. })
  178. } else {
  179. averageAdDynamic.forEach((aad: any, index: number) => {
  180. newData.push({
  181. ...ad,
  182. id: ad.id + '_' + index,
  183. dynamicGroup: aad,
  184. textDto: aad?.textDto
  185. })
  186. })
  187. }
  188. })
  189. } else if (mediaType === 2) {
  190. data.forEach((item) => {
  191. const { averageAdDynamic, ...ad } = item
  192. if (textType === 3) {
  193. cartesianProduct(textDto, [newDynamicGroup[accountIndex1 % newDynamicGroup.length]]).forEach((taad: any) => {
  194. let [textValue, aad, index] = taad
  195. newData.push({
  196. ...ad,
  197. id: ad.id + '_' + index,
  198. dynamicGroup: aad,
  199. textDto: textValue,
  200. rowSpan: textDto.length
  201. })
  202. })
  203. } else {
  204. let { textDto, ...dynamicGroup } = newDynamicGroup[accountIndex1 % newDynamicGroup.length]
  205. newData.push({
  206. ...ad,
  207. dynamicGroup,
  208. textDto,
  209. rowSpan: 1
  210. })
  211. }
  212. accountIndex1 += 1
  213. })
  214. } else {
  215. newData = cartesianProduct(data, newDynamicGroup.length > 0 ? newDynamicGroup : [{}]).map((item, index) => {
  216. let [d1, group] = item
  217. return {
  218. ...d1,
  219. id: d1.id + '_' + index,
  220. dynamicGroup: group,
  221. textDto: (group as any)?.textDto
  222. }
  223. })
  224. }
  225. }
  226. newDynamicCount += newData.length
  227. newTableData[ad.adgroupId] = newData
  228. })
  229. setDynamicCount(newDynamicCount)
  230. setActiveKey(adData?.[0].adgroupId?.toString())
  231. console.log('newTableData-->', newTableData)
  232. setTableData(newTableData)
  233. }
  234. // 提交
  235. const onSubmit = () => {
  236. const { dynamic, dynamicMaterialDTos, dynamicCreativesTextDTOS, mediaType } = addelivery
  237. let dynamicMaterialDTOS = []
  238. if (dynamic.deliveryMode === 'DELIVERY_MODE_CUSTOMIZE') {
  239. if ((materialData && Object.keys(materialData).length && dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length)) {
  240. let mType = Object.keys(materialData)[0];
  241. dynamicMaterialDTOS = dynamicMaterialDTos.dynamicGroup?.map((item: any) => {
  242. if (mType === 'image') {
  243. return [{
  244. type: mType,
  245. valueJson: JSON.stringify({
  246. value: {
  247. imageUrl: item?.image_id?.url,
  248. imageId: item?.image_id?.id,
  249. materialType: item?.image_id?.materialType
  250. }
  251. })
  252. }]
  253. } else if (mType === 'image_list' || mType === 'element_story') {
  254. let key = 'image_list'
  255. if (mType === 'element_story') {
  256. key = 'element_story'
  257. }
  258. let list = item?.[key]?.map((l: any) => {
  259. return {
  260. imageUrl: l?.url,
  261. imageId: l?.id,
  262. materialType: l?.materialType
  263. }
  264. })
  265. return [{
  266. type: mType,
  267. valueJson: JSON.stringify({
  268. value: {
  269. list
  270. }
  271. })
  272. }]
  273. } else if (['short_video', 'video'].includes(mType)) {
  274. let value: any = {
  275. materialType: item?.video_id?.materialType || item?.short_video1?.materialType || 0,
  276. videoUrl: item?.video_id?.url || item?.short_video1?.url,
  277. videoId: item?.video_id?.id || item?.short_video1?.id
  278. }
  279. if (item?.cover_id?.url) {
  280. value.imageUrl = item?.cover_id?.url
  281. value.imageId = item?.cover_id?.id
  282. value.materialCoverType = item?.cover_id?.materialType
  283. }
  284. return [{
  285. type: mType,
  286. valueJson: JSON.stringify({
  287. value
  288. })
  289. }]
  290. }
  291. return [{
  292. type: mType,
  293. valueJson: ''
  294. }]
  295. })
  296. }
  297. } else {
  298. if (dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length) {
  299. dynamicMaterialDTOS = dynamicMaterialDTos.dynamicGroup?.map((item: { list: any[] }) => {
  300. return item.list.map(l => {
  301. if (Array.isArray(l)) {
  302. return {
  303. type: 'image_list',
  304. valueJson: JSON.stringify({
  305. value: {
  306. list: l?.map((i: any) => {
  307. return {
  308. imageUrl: i?.url,
  309. imageId: i?.id,
  310. materialType: i?.materialType
  311. }
  312. })
  313. }
  314. })
  315. }
  316. } else if (l?.url?.includes('mp4')) {
  317. return {
  318. type: 'video',
  319. valueJson: JSON.stringify({
  320. value: {
  321. materialType: l?.materialType,
  322. videoUrl: l?.url,
  323. videoId: l?.id
  324. }
  325. })
  326. }
  327. } else {
  328. return {
  329. type: 'image',
  330. valueJson: JSON.stringify({
  331. value: {
  332. imageUrl: l?.url,
  333. imageId: l?.id,
  334. materialType: l?.materialType
  335. }
  336. })
  337. }
  338. }
  339. })
  340. })
  341. }
  342. }
  343. let accountIdParamDTOMap: any = {}
  344. accountCreateLogs.forEach(item => {
  345. let { pageList, productList, userActionSetsList, accountId, wechatChannelList } = item
  346. let userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
  347. let map: any = {
  348. userActionSetsList: userActionSetsListDto,
  349. pageList: pageList?.map((item: { pageId: any }) => item.pageId)
  350. }
  351. if (productList) {
  352. map.productDTOS = productList?.map(item => {
  353. return { productId: item.marketingAssetId }
  354. })
  355. }
  356. if (wechatChannelList) {
  357. map.wechatChannelId = wechatChannelList?.[0]?.wechatOfficialAccountId
  358. }
  359. accountIdParamDTOMap[accountId] = map
  360. })
  361. let dynamicCreativesDTO = { ...dynamic, mediaType }
  362. if (dynamic.deliveryMode === 'DELIVERY_MODE_COMPONENT') {
  363. dynamicCreativesDTO.creativeTemplateId = 711
  364. }
  365. let params = {
  366. accountAdgroupMaps: adData.map(item => `${item.accountId},${item.adgroupId}`),
  367. dynamicCreativesDTO,
  368. dynamicCreativesTextDTOS,
  369. dynamicMaterialDTOS,
  370. accountIdParamDTOMap
  371. }
  372. createDynamicTask.run(params).then(res => {
  373. if (res) {
  374. message.success('创建任务提交成功')
  375. onChange?.()
  376. }
  377. })
  378. }
  379. return <Drawer
  380. title={<strong>添加创意</strong>}
  381. visible={visible}
  382. width={1500}
  383. onClose={onClose}
  384. bodyStyle={{ backgroundColor: '#f1f4fc', padding: '0 10px 10px' }}
  385. >
  386. <Space direction="vertical" style={{ width: '100%' }}>
  387. <Spin spinning={false}>
  388. <Card
  389. size="small"
  390. title={<div className={style.cardTitle}>配置区</div>}
  391. className={style.createAd}
  392. >
  393. <Space wrap>
  394. {(adgroups?.marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT' || adgroups?.marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.wechatChannelList?.length)} onClick={() => { setWechatVisible(true) }}>{accountCreateLogs?.some(item => item?.wechatChannelList?.length) ? <>重新选择公众号 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择公众号'}</Button>}
  395. </Space>
  396. <div className={style.settingsBody}>
  397. <div className={style.settingsBody_content}>
  398. <DispatchAddelivery.Provider
  399. value={{
  400. addelivery,
  401. setAddelivery,
  402. accountCreateLogs,
  403. setAccountCreateLogs,
  404. materialData,
  405. setMaterialData,
  406. textData,
  407. setTextData,
  408. clearData
  409. }}>
  410. <div className={style.settingsBody_content_right}>
  411. <div className={`${style.settingsBody_content_row} ${style.row1}`}>
  412. <div className={style.title}>
  413. <span>广告信息</span>
  414. </div>
  415. <div className={style.detail}>
  416. <div className={style.detail_body}>
  417. <Title level={5} style={{ fontSize: 12 }}>已选广告</Title>
  418. {adData?.map(item => {
  419. return <div key={item.adgroupId}>
  420. <div className={style.text}><Text ellipsis={{ tooltip: true }}>{item.adgroupName}</Text></div>
  421. </div>
  422. })}
  423. </div>
  424. </div>
  425. </div>
  426. {/* 创意 */}
  427. <Dynamic />
  428. {/* 创意素材 */}
  429. <Material adData={adData} />
  430. </div>
  431. <div className={style.settingsBody_content_left}>
  432. {/* 创意文案 */}
  433. <MaterialText />
  434. {/* 落地页 */}
  435. <PageList />
  436. </div>
  437. </DispatchAddelivery.Provider>
  438. </div>
  439. </div>
  440. <Space className={style.bts} wrap>
  441. <TacticsS strategyValue={{
  442. adData: adData.map(item => {
  443. const { accountId, adgroupName, adgroupId, siteSet, marketingCarrierType, marketingGoal, marketingTargetType, sceneSpec, automaticSiteEnabled } = item
  444. return {
  445. siteSet, marketingCarrierType, marketingGoal, marketingTargetType, sceneSpec, automaticSiteEnabled,
  446. accountId,
  447. adgroupId,
  448. adgroupName
  449. }
  450. }),
  451. addelivery,
  452. accountCreateLogs,
  453. materialData,
  454. textData
  455. }} />
  456. <Button type='primary' onClick={preview}><SearchOutlined />预览广告</Button>
  457. </Space>
  458. {/* 选择公众号 */}
  459. {wechatVisible && <WechatAccount
  460. visible={wechatVisible}
  461. data={accountCreateLogs}
  462. onClose={() => setWechatVisible(false)}
  463. onChange={(e) => {
  464. setAccountCreateLogs(e);
  465. setWechatVisible(false);
  466. clearData()
  467. }}
  468. />}
  469. </Card>
  470. </Spin>
  471. <Card
  472. className={style.createAd}
  473. >
  474. {activeKey && tableData && Object.keys(tableData)?.length > 0 ? <div className={style.cardBody}>
  475. <Tabs
  476. onChange={(e) => { setActiveKey(e) }}
  477. type="card"
  478. activeKey={activeKey}
  479. tabBarExtraContent={<Space>
  480. <span>创意总数:{dynamicCount}</span>
  481. <Popconfirm
  482. title="确定提交?"
  483. onConfirm={onSubmit}
  484. >
  485. <Button type='primary' loading={createDynamicTask.loading}>提交创建</Button>
  486. </Popconfirm>
  487. </Space>}
  488. >
  489. {adData.map(item => <Tabs.TabPane tab={item.adgroupId} key={item.adgroupId} />)}
  490. </Tabs>
  491. <div className={style.content} style={{ marginTop: 20 }}>
  492. <Table
  493. columns={columnsAddDynamic()}
  494. dataSource={tableData[activeKey]}
  495. size="small"
  496. bordered
  497. scroll={{ x: 1200 }}
  498. rowKey={'id'}
  499. pagination={{
  500. defaultPageSize: 50
  501. }}
  502. />
  503. </div>
  504. </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  505. <Empty description="请先完成模块配置后,再预览广告计划" />
  506. </div>}
  507. </Card>
  508. </Space>
  509. </Drawer>
  510. }
  511. export default React.memo(AddDynamic)