index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. import { useAjax } from '@/Hook/useAjax'
  2. import { AdStatusEnum, PromotedObjectType } from '@/services/launchAdq/enum'
  3. import { Col, Row, Input, Select, message, Space, Button, Popconfirm, Switch, notification, Modal, Tooltip } from 'antd'
  4. import React, { useEffect, useCallback, useState } from 'react'
  5. import TableData from '../../components/TableData'
  6. import tableConfig from './tableConfig'
  7. import { putAdqAdgroupsSync, getAdqAdgroupsList, delListAdqAdgroupsApi, newEditAdqAdgroupsDataApi, editAdqAdgroupsDataApi, putAdqAdgroupsSyncBatch, putModifyCustomAudienceApi } from '@/services/launchAdq/adq'
  8. import { CopyOutlined, DeleteOutlined, FieldTimeOutlined, PauseCircleOutlined, PlayCircleOutlined, SyncOutlined, TransactionOutlined } from '@ant-design/icons'
  9. import UpdateAd from './updateAd'
  10. import Copy from './copy'
  11. import PlanDetail from '@/pages/adMonitor/adMonitorList/components/planDetail'
  12. import { txAdConfig } from '../config'
  13. import Log from '../log'
  14. import EarlyWarning from '@/components/EarlyWarning'
  15. import SetEarlyWarning from '@/components/EarlyWarning/setEarlyWarning'
  16. import CrowdPackModal from '../../components/crowdPackModal'
  17. type Props = {
  18. accountId?: string,
  19. adAccountId?: string,
  20. userId: string,
  21. Ts?: any,
  22. queryParmas?: {
  23. accountId?: string,//账户ID
  24. campaignId?: string,//计划ID
  25. adgroupId?: string,//广告ID
  26. adcreativeId?: string,//创意ID
  27. pageId?: string,//落地页ID
  28. targetingId?: string,//定向ID}
  29. },
  30. tableIdClick?: (props: {
  31. activeKey: string, parma: {
  32. accountId?: string,//账户ID
  33. campaignId?: string,//计划ID
  34. adgroupId?: string,//广告ID
  35. adcreativeId?: string,//创意ID
  36. pageId?: string,//落地页ID
  37. targetingId?: string,//定向ID
  38. }
  39. }) => void
  40. }
  41. const Ad: React.FC<Props> = (props) => {
  42. /***********************/
  43. let { accountId, adAccountId, userId, tableIdClick, queryParmas, Ts } = props
  44. const [selectedRows, setSelectedRows] = useState<any[]>([])
  45. const [update, setUpdate] = useState<{ visible: boolean, title: string }>({ visible: false, title: '' })
  46. const [model, setModel] = useState(true)
  47. const [copyData, setCopyData] = useState<{ visible: boolean }>({ visible: false })
  48. const [detailShow, setDetailShow] = useState<boolean>(false)
  49. const [detailData, setDetailData] = useState<any>({})
  50. const [czjlShow, setCzjlShow] = useState(false)
  51. const [cpVisible, setCpVisible] = useState(false)
  52. const [accountCreateLogs, setAccountCreateLogs] = useState<{ adAccountId: number, id: number, customAudienceList?: any[], excludedCustomAudienceList?: any[] }[]>([])
  53. const [queryFrom, set_queryFrom] = useState<{
  54. pageNum: number;
  55. pageSize: number;
  56. accountIdList?: any[];
  57. adgroupName?: string;
  58. adgroupIdList?: any[];
  59. promotedObjectType?: string;
  60. isDeleted?: boolean
  61. campaignIdList?: any[]
  62. statusList?: any[],
  63. memoList?: any[]
  64. remarkList?: any[]
  65. }>({ pageNum: 1, pageSize: 20 })
  66. const listAjax = useAjax((params) => getAdqAdgroupsList(params), { formatResult: true })
  67. const syncAjax = useAjax((adAccountId) => putAdqAdgroupsSync(adAccountId))
  68. const delListAdqAdgroups = useAjax((params) => delListAdqAdgroupsApi(params))
  69. const editAdqAdgroupsData = useAjax((params) => newEditAdqAdgroupsDataApi(params))
  70. const editAdqAdgroups = useAjax((params) => editAdqAdgroupsDataApi(params))
  71. const putModifyCustomAudience = useAjax((params) => putModifyCustomAudienceApi(params))
  72. const putAdqAdgroupsSyncBatchApi = useAjax((params) => putAdqAdgroupsSyncBatch(params))
  73. /************************/
  74. useEffect(() => {
  75. // let { accountId, campaignId, adgroupId, ...obj } = queryParmas
  76. // let new_queryParmas = {
  77. // ...obj,
  78. // accountIdList: accountId ? [accountId] : [],
  79. // campaignIdList: campaignId ? [campaignId] : [],
  80. // adgroupIdList: adgroupId ? [adgroupId] : []
  81. // }
  82. getList({ pageNum: 1, pageSize: 20 })
  83. }, [accountId, userId, queryParmas])
  84. // 获取列表
  85. const getList = useCallback((params: {
  86. pageNum: number;
  87. pageSize: number;
  88. accountIdList?: any[];
  89. adgroupName?: string;
  90. adgroupIdList?: any[];
  91. promotedObjectType?: string;
  92. isDeleted?: boolean
  93. campaignIdList?: any[]
  94. statusList?: any[],
  95. memoList?: any[]
  96. remarkList?: any[]
  97. }) => {
  98. listAjax.run({ ...params, userId })
  99. }, [userId, listAjax])
  100. // 同步
  101. const sync = useCallback(() => {
  102. // if (selectedRows?.length === 0) {
  103. // message.error('请先勾选要同步的广点通账号!')
  104. // return
  105. // }
  106. let arr = [...new Set(selectedRows?.map(item => item.accountId))]
  107. syncAjax.run({ accountIdList: arr }).then(res => {
  108. res && listAjax.refresh()
  109. res ? message.success('同步成功!') : message.error('同步失败!')
  110. })
  111. }, [listAjax, selectedRows])
  112. /** 删除 */
  113. const deleteHandle = (type: 0 | 1, adgroupId?: number) => {
  114. delListAdqAdgroups.run({ adgroupIds: type === 1 ? selectedRows.map(item => item.adgroupId) : [adgroupId] }).then(res => {
  115. message.success('删除成功')
  116. setSelectedRows([])
  117. listAjax.refresh()
  118. })
  119. }
  120. /** 修改排期出价 */
  121. const editScheduling = () => {
  122. setUpdate({ visible: true, title: '批量修改' })
  123. }
  124. /** 修改排期出价 */
  125. const editDeepConversion = () => {
  126. setUpdate({ visible: true, title: '批量修改深度优化' })
  127. }
  128. // 单个启停
  129. const onChange = () => {
  130. listAjax.refresh()
  131. setSelectedRows([])
  132. }
  133. // 批量启停
  134. const adStatus = (type: 'play' | 'suspend') => {
  135. let params: any = {}
  136. if (type === 'play') {
  137. params.configuredStatus = 'AD_STATUS_NORMAL'
  138. params.adgroupIds = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_SUSPEND').map(item => item.adgroupId)
  139. } else {
  140. params.configuredStatus = 'AD_STATUS_SUSPEND'
  141. params.adgroupIds = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_NORMAL').map(item => item.adgroupId)
  142. }
  143. if (params.adgroupIds.length === 0) {
  144. message.warn(`所以账号都是${type === 'play' ? '启动' : '暂停'}状态,无需${type === 'play' ? '启动' : '暂停'}操作`)
  145. return
  146. }
  147. editAdqAdgroupsData.run(params).then(res => {
  148. message.success(`${type === 'play' ? '启动' : '暂停'}成功: ${res.success},失败: ${res.fail}`)//
  149. if (res?.fail) {
  150. notification.error({
  151. message: `${type === 'play' ? '启动' : '暂停'}失败`,
  152. description: `成功: ${res.success},修改失败${res.fail}条,失败的请到任务列表查看`,
  153. duration: 0
  154. });
  155. }
  156. listAjax.refresh()
  157. setSelectedRows([])
  158. })
  159. }
  160. //同步广告
  161. const syncAd = useCallback(() => {
  162. putAdqAdgroupsSyncBatchApi.run({ adgroupIds: selectedRows?.map(item => item.adgroupId) }).then(res => {
  163. if (res) {
  164. message.success('同步成功!')
  165. listAjax.refresh()
  166. }
  167. })
  168. }, [selectedRows])
  169. // 批量复制
  170. const copyHandle = () => {
  171. setCopyData({ visible: true })
  172. }
  173. const handleSave = (row: any) => {
  174. const hide = message.loading(`广告“${row.adgroupId}”广告名称修改成<${row.adgroupName}>,修改中`, 0, () => {
  175. message.success('修改成功');
  176. });
  177. editAdqAdgroups.run({ adgroupIds: [row.adgroupId], adgroupName: row.adgroupName }).then(res => {
  178. message.success('修改广告名称成功')
  179. listAjax.refresh()
  180. hide()
  181. })
  182. }
  183. const details = (data: any) => {
  184. setDetailData(data)
  185. setDetailShow(true)
  186. }
  187. // 设置人群包
  188. const setRqb = () => {
  189. const { accountId } = selectedRows[0]
  190. setAccountCreateLogs([{ id: accountId, adAccountId: accountId }])
  191. setCpVisible(true)
  192. }
  193. // 确认提交人群包
  194. const handleRqb = (value: any[]) => {
  195. if ((value[0]?.customAudienceList && value[0]?.customAudienceList?.length > 0) || (value[0]?.excludedCustomAudienceList && value[0]?.excludedCustomAudienceList?.length > 0)) {
  196. let { adAccountId, customAudienceList = [], excludedCustomAudienceList = [] } = value[0]
  197. let adgroupIds = selectedRows.map((item: { adgroupId: number }) => item.adgroupId)
  198. let customAudienceIds = customAudienceList.map((item: { id: number }) => item.id)
  199. let excludedCustomAudienceIds = excludedCustomAudienceList.map((item: { id: number }) => item.id)
  200. const hide = message.loading('正在修改。。。', 0)
  201. setAccountCreateLogs([])
  202. setCpVisible(false)
  203. let params: any = { adAccountId, adgroupIds };
  204. if (excludedCustomAudienceIds.length > 0) {
  205. params['excludedCustomAudienceIds'] = excludedCustomAudienceIds
  206. }
  207. if (customAudienceIds?.length > 0) {
  208. params['customAudienceIds'] = customAudienceIds
  209. }
  210. putModifyCustomAudience.run(params).then(res => {
  211. hide()
  212. setSelectedRows([])
  213. message.success('修改成功,请到腾讯广告平台查看')
  214. }).catch(err => hide())
  215. } else {
  216. message.error('请选择用户群')
  217. }
  218. }
  219. return <div>
  220. {/* 修改广告 */}
  221. {update.visible && <UpdateAd
  222. {...update}
  223. selectedRows={selectedRows}
  224. onChange={() => {
  225. setUpdate({ visible: false, title: '' })
  226. listAjax.refresh()
  227. setSelectedRows([])
  228. }}
  229. onClose={() => { setUpdate({ visible: false, title: '' }) }}
  230. />}
  231. {/* 复制广告 */}
  232. {copyData.visible && <Copy selectedRows={selectedRows} {...copyData} onClose={() => setCopyData({ visible: false })} onChange={() => { setCopyData({ visible: false }); listAjax.refresh(); setSelectedRows([]) }} />}
  233. <Row gutter={[6, 6]} align='middle' style={{ marginBottom: 15 }}>
  234. <Col>
  235. {Ts && Ts()}
  236. </Col>
  237. <Col>
  238. <Input
  239. placeholder='广告账号'
  240. allowClear
  241. style={{ width: 120 }}
  242. onChange={(e) => {
  243. let value = e.target.value
  244. let arr: any = []
  245. if (value) {
  246. value = value.replace(/[,,\s]/g, ',')
  247. arr = value.split(',').filter((a: any) => a)
  248. }
  249. set_queryFrom({ ...queryFrom, accountIdList: arr })
  250. }}
  251. />
  252. </Col>
  253. <Col>
  254. <Input
  255. placeholder='广告名称'
  256. allowClear
  257. style={{ width: 120 }}
  258. onChange={(e) => {
  259. let value = e.target.value
  260. set_queryFrom({ ...queryFrom, adgroupName: value })
  261. }}
  262. />
  263. </Col>
  264. <Col>
  265. <Input
  266. placeholder='广告ID'
  267. allowClear
  268. style={{ width: 120 }}
  269. onChange={(e) => {
  270. let value = e.target.value
  271. let arr: any = []
  272. if (value) {
  273. value = value.replace(/[,,\s]/g, ',')
  274. arr = value.split(',').filter((a: any) => a)
  275. }
  276. set_queryFrom({ ...queryFrom, adgroupIdList: arr })
  277. }}
  278. />
  279. </Col>
  280. <Col>
  281. <Input
  282. placeholder='计划ID'
  283. allowClear
  284. style={{ width: 120 }}
  285. onChange={(e) => {
  286. let value = e.target.value
  287. let arr: any = []
  288. if (value) {
  289. value = value.replace(/[,,\s]/g, ',')
  290. arr = value.split(',').filter((a: any) => a)
  291. }
  292. set_queryFrom({ ...queryFrom, campaignIdList: arr })
  293. }}
  294. />
  295. </Col>
  296. <Col>
  297. <Select
  298. placeholder='推广目标选择'
  299. style={{ width: 120 }}
  300. showSearch
  301. filterOption={(input: any, option: any) =>
  302. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  303. }
  304. allowClear
  305. onChange={(value: any) => {
  306. set_queryFrom({ ...queryFrom, promotedObjectType: value })
  307. }}
  308. >
  309. {Object.keys(PromotedObjectType).map(key => {
  310. return <Select.Option value={key} key={key}>{PromotedObjectType[key]}</Select.Option>
  311. })}
  312. </Select>
  313. </Col>
  314. <Col>
  315. <Select
  316. placeholder='是否已删除'
  317. style={{ width: 120 }}
  318. showSearch
  319. filterOption={(input: any, option: any) =>
  320. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  321. }
  322. allowClear
  323. onChange={(value: any) => {
  324. set_queryFrom({ ...queryFrom, isDeleted: value })
  325. }}
  326. >
  327. <Select.Option value={true}>已删除</Select.Option>
  328. <Select.Option value={false}>未删除</Select.Option>
  329. </Select>
  330. </Col>
  331. <Col>
  332. <Select
  333. placeholder='广告状态'
  334. mode="multiple"
  335. style={{ width: 120 }}
  336. showSearch
  337. filterOption={(input: any, option: any) =>
  338. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  339. }
  340. allowClear
  341. onChange={(value: any) => {
  342. set_queryFrom({ ...queryFrom, statusList: value })
  343. }}
  344. >
  345. {
  346. Object.keys(AdStatusEnum).map(key => {
  347. return <Select.Option value={key} key={key}>{AdStatusEnum[key]}</Select.Option>
  348. })
  349. }
  350. </Select>
  351. </Col>
  352. <Col>
  353. <Input
  354. placeholder='腾讯备注'
  355. allowClear
  356. style={{ width: 120 }}
  357. onChange={(e) => {
  358. let value = e.target.value
  359. let arr: any = []
  360. if (value) {
  361. value = value.replace(/[,,\s]/g, ',')
  362. arr = value.split(',').filter((a: any) => a)
  363. }
  364. set_queryFrom({ ...queryFrom, memoList: arr })
  365. }}
  366. />
  367. </Col>
  368. <Col>
  369. <Input
  370. placeholder='本地备注'
  371. allowClear
  372. style={{ width: 120 }}
  373. onChange={(e) => {
  374. let value = e.target.value
  375. let arr: any = []
  376. if (value) {
  377. value = value.replace(/[,,\s]/g, ',')
  378. arr = value.split(',').filter((a: any) => a)
  379. }
  380. set_queryFrom({ ...queryFrom, remarkList: arr })
  381. }}
  382. />
  383. </Col>
  384. <Col>
  385. <Button type='primary' onClick={() => getList({ ...queryFrom, pageNum: 1 })}> 搜索</Button>
  386. </Col>
  387. </Row>
  388. <TableData
  389. isCard={false}
  390. columns={() => tableConfig(onChange, details, handleSave)}
  391. ajax={listAjax}
  392. syncAjax={sync}
  393. fixed={{ left: 2, right: 4 }}
  394. dataSource={listAjax?.data?.data?.records}
  395. loading={listAjax?.loading || syncAjax?.loading}
  396. scroll={{ y: 560 }}
  397. total={listAjax?.data?.data?.total}
  398. page={listAjax?.data?.data?.current}
  399. pageSize={listAjax?.data?.data?.size}
  400. myKey={'adgroupId'}
  401. gutter={[0, 10]}
  402. config={txAdConfig}
  403. configName="腾讯广告"
  404. leftChild={<Space direction='vertical'>
  405. <Row gutter={[10, 10]} align='middle'>
  406. <Col>
  407. <Switch checkedChildren="普通" unCheckedChildren="ROI" checked={model} onChange={(checked) => { setModel(checked); setSelectedRows([]) }} style={model ? {} : { background: '#67c23a' }} />
  408. </Col>
  409. {model ? <>
  410. <Col><Button type='primary' style={{ background: '#1890ff' }} icon={<FieldTimeOutlined />} disabled={selectedRows.length === 0} onClick={editScheduling}>修改排期出价名称</Button></Col>
  411. <Col><Button type='primary' style={{ background: '#1890ff' }} icon={<CopyOutlined />} disabled={selectedRows.length === 0} onClick={copyHandle}>复制</Button></Col>
  412. <Col><Button type='primary' style={{ background: '#67c23a', borderColor: '#67c23a' }} loading={editAdqAdgroupsData.loading} icon={<PlayCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus('play')}>启动</Button></Col>
  413. <Col><Button type='primary' style={{ background: '#e6a23c', borderColor: '#e6a23c' }} loading={editAdqAdgroupsData.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus('suspend')}>暂停</Button></Col>
  414. <Col><Tooltip title="2023/4/20 16:20:00刷新页面后平台创建的广告可修改,或者其它平台使用定向包创建广告可用">
  415. <Button type='primary' loading={putModifyCustomAudience.loading} disabled={selectedRows.length > 0 ? !selectedRows.every((item: { accountId: number }) => item.accountId === selectedRows[0].accountId) : true} onClick={() => setRqb()}>修改人群包</Button>
  416. </Tooltip></Col>
  417. <Col><Button type='primary' style={{ background: '#00bcd4', borderColor: '#00bcd4' }} loading={putAdqAdgroupsSyncBatchApi.loading} icon={<SyncOutlined />} disabled={selectedRows.length === 0} onClick={syncAd}>同步广告</Button></Col>
  418. <Col><SetEarlyWarning selectedRows={selectedRows} onChange={() => listAjax.refresh()} /></Col>
  419. <Col>
  420. <Popconfirm
  421. title="确定删除?"
  422. onConfirm={() => deleteHandle(1)}
  423. okText="是"
  424. cancelText="否"
  425. disabled={selectedRows.length === 0}
  426. >
  427. <Button danger type='primary' loading={delListAdqAdgroups.loading} icon={<DeleteOutlined />} disabled={selectedRows.length === 0}>删除</Button>
  428. </Popconfirm>
  429. </Col>
  430. </> : <Col><Button type='primary' icon={<TransactionOutlined />} disabled={selectedRows.length === 0} onClick={editDeepConversion}>修改深度优化ROI</Button></Col>}
  431. <Col>
  432. <Button type='dashed' onClick={() => { setCzjlShow(true) }}>操作记录</Button>
  433. </Col>
  434. <Col>
  435. <EarlyWarning />
  436. </Col>
  437. </Row>
  438. </Space>}
  439. rowSelection={{
  440. selectedRowKeys: selectedRows.map(item => item.adgroupId.toString()),
  441. getCheckboxProps: (record: any) => ({
  442. disabled: model ?
  443. record.status === 'STATUS_DELETED' :
  444. record.status === 'STATUS_DELETED' ||
  445. !(!model &&
  446. record?.promotedObjectType === 'PROMOTED_OBJECT_TYPE_WECHAT_OFFICIAL_ACCOUNT' &&
  447. record?.optimizationGoal === 'OPTIMIZATIONGOAL_FOLLOW' &&
  448. record?.deepConversionSpec?.deepConversionWorthSpec?.goal === 'GOAL_1DAY_PURCHASE_ROAS'
  449. )
  450. }),
  451. onSelect: (record: { adgroupId: number, mpName: string }, selected: boolean) => {
  452. if (selected) {
  453. selectedRows.push({ ...record })
  454. setSelectedRows([...selectedRows])
  455. } else {
  456. let newSelectAccData = selectedRows.filter((item: { adgroupId: number }) => item.adgroupId !== record.adgroupId)
  457. setSelectedRows([...newSelectAccData])
  458. }
  459. },
  460. onSelectAll: (selected: boolean, selectedRowss: { adgroupId: number }[], changeRows: { adgroupId: number }[]) => {
  461. if (selected) {
  462. let newSelectAccData = [...selectedRows]
  463. changeRows.forEach((item: { adgroupId: number }) => {
  464. let index = newSelectAccData.findIndex((ite: { adgroupId: number }) => ite.adgroupId === item.adgroupId)
  465. if (index === -1) {
  466. newSelectAccData.push({ ...item })
  467. }
  468. })
  469. setSelectedRows([...newSelectAccData])
  470. } else {
  471. let newSelectAccData = selectedRows.filter((item: { adgroupId: number }) => {
  472. let index = changeRows.findIndex((ite: { adgroupId: number }) => ite.adgroupId === item.adgroupId)
  473. if (index !== -1) {
  474. return false
  475. } else {
  476. return true
  477. }
  478. })
  479. setSelectedRows([...newSelectAccData])
  480. }
  481. }
  482. }}
  483. onChange={(props: any) => {
  484. let { sortData, pagination } = props
  485. let { current, pageSize } = pagination
  486. // console.log(pagination)
  487. // console.log({...queryFrom, pageNum: current, pageSize })
  488. set_queryFrom({ ...queryFrom, pageNum: current, pageSize })
  489. getList({ ...queryFrom, pageNum: current, pageSize })
  490. }}
  491. />
  492. {detailShow && <PlanDetail visible={detailShow} onClose={() => { setDetailShow(false) }} data={detailData} />}
  493. {czjlShow && <Modal
  494. visible={czjlShow}
  495. onCancel={() => { setCzjlShow(false) }}
  496. onOk={() => { setCzjlShow(false) }}
  497. width={1200}
  498. footer={null}
  499. title={"广告操作记录"}
  500. >
  501. <Log {...props} />
  502. </Modal>}
  503. {cpVisible && <CrowdPackModal visible={cpVisible} data={accountCreateLogs} onClose={() => setCpVisible(false)} onChange={(e) => { handleRqb(e) }} />}
  504. </div>
  505. }
  506. export default Ad