index.tsx 21 KB

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