index.tsx 21 KB

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