adPlanList.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import { useAjax } from '@/Hook/useAjax'
  2. import { Col, Row, message, Space, Button, Table, Statistic } from 'antd'
  3. import React, { useEffect, useCallback, useState, useRef } from 'react'
  4. import { getPutUserApi, delUserTagApi } from '@/services/launchAdq/adq'
  5. import { LineChartOutlined, PauseCircleOutlined, PlayCircleOutlined, TransactionOutlined } from '@ant-design/icons'
  6. import { planAdConfig } from './config'
  7. import '../../launchSystemNew/adq/ad/index.less'
  8. import tablePlanConfig from './tablePlanListConfig'
  9. import { AdListProps, getAdV3ListApi } from '@/services/adMonitor/adMonitor'
  10. import FilterQuery from './FilterQuery'
  11. import AdExpandedRowRender from './adExpandedRowRender'
  12. import { useSize, useUpdateEffect } from 'ahooks'
  13. import PlanTag from '../../launchSystemNew/adq/ad/planTag'
  14. import moment from 'moment'
  15. import ColumnTrend from '@/pages/adMonitor/adMonitorList/columnTrend'
  16. import TableData from '../../launchSystemNew/components/TableData'
  17. import RuleAccountLog from '@/components/EarlyWarning/ruleAccountLog'
  18. import Details from './Details'
  19. import { modifyStatusBatchApi, syncBatchApi } from '@/services/launchAdq/adqv3'
  20. import UpdateAd from '../adqv3/ad/updateAd'
  21. const AdPlanList: React.FC<{ userId: string }> = (props) => {
  22. /***********************/
  23. const { userId } = props
  24. const [selectedRows, setSelectedRows] = useState<any[]>([])
  25. const [detailShow, setDetailShow] = useState<boolean>(false)
  26. const [detailData, setDetailData] = useState<any>({})
  27. const [update, setUpdate] = useState<{ visible: boolean }>({ visible: false })
  28. const [queryForm, setQueryForm] = useState<AdListProps>({ pageNum: 1, pageSize: 20, columns: [], isDeleted: false, dataTimeMin: moment().subtract(7, 'days').format('YYYY-MM-DD'), dataTimeMax: moment().format('YYYY-MM-DD') })
  29. const [filterForm, setFilterForm] = useState<AdListProps>()
  30. const getAdList = useAjax((params) => getAdV3ListApi(params), { formatResult: true })
  31. const getPutUser = useAjax((params) => getPutUserApi(params))
  32. const delUserTag = useAjax((params) => delUserTagApi(params))
  33. const [logVisible, setLogVisible] = useState<boolean>(false)
  34. const [adgroupId, setAdgroupId] = useState<string>('')
  35. const [adgroupName, setAdgroupName] = useState<string>('')
  36. const [accountIdRule, setAccountIdRule] = useState<string>('')
  37. const [tagVisible, setTagVisible] = useState<boolean>(false)
  38. const [tagData, setTagData] = useState<any>({})
  39. const configName = '广告列表3.0'
  40. const ref = useRef(null)
  41. const size = useSize(ref)
  42. const [scrollLeft, setScrollLeft] = useState<number>(0)
  43. const [totalData, setTotalData] = useState<any>({})
  44. const [tableField, setTableField] = useState<{ title: string, dataIndex: string }[]>([])
  45. const [trendVisible, setTrendVisible] = useState<boolean>(false)
  46. const [trendData, setTrendData] = useState<any>({})
  47. const syncBatch = useAjax((params) => syncBatchApi(params))
  48. const modifyStatusBatch = useAjax((params) => modifyStatusBatchApi(params))
  49. /************************/
  50. useEffect(() => {
  51. getList()
  52. }, [filterForm, queryForm])
  53. const getList = () => {
  54. let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
  55. if (message) {
  56. message = JSON.parse(message)
  57. }
  58. let columns: string[] = []
  59. if (message && Array.isArray(message)) {
  60. message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
  61. if (!['cz', 'cost_speed'].includes(item.dataIndex)) {
  62. columns.push(item?.serverIndex || 'adgroup_data.' + item.dataIndex)
  63. }
  64. })
  65. } else {
  66. planAdConfig.forEach((item: any) => {
  67. item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
  68. if (d.default && !['cz', 'cost_speed'].includes(d.dataIndex)) {
  69. columns.push(d?.serverIndex || 'adgroup_data.' + d.dataIndex)
  70. }
  71. })
  72. })
  73. }
  74. getAdList.run({ ...queryForm, ...filterForm, columns })
  75. }
  76. useUpdateEffect(() => {
  77. let data: any = {}
  78. if (getAdList.data?.data?.sumRecord) {
  79. data = getAdList.data?.data?.sumRecord
  80. }
  81. setTotalData(data)
  82. }, [getAdList.data])
  83. useEffect(() => {
  84. let localData = localStorage.getItem('myAdMonitorConfig1.0.1_广告列表3.0')
  85. let data: any[] = []
  86. if (localData) {
  87. data = JSON.parse(localData)
  88. } else {
  89. let newSelectData: any[] = [];
  90. (planAdConfig as any).forEach((item: { data: { default: any }[] }) => {
  91. item?.data?.forEach((d: { default: any }) => {
  92. if (d.default) {
  93. newSelectData[d.default - 1] = d
  94. }
  95. })
  96. })
  97. data = newSelectData
  98. }
  99. data.unshift({ title: '选择框', dataIndex: 'xzk' })
  100. data.unshift({ title: '总计', dataIndex: 'zj' })
  101. setTableField(data)
  102. }, [localStorage.getItem('myAdMonitorConfig1.0.1_广告列表3.0')])
  103. useEffect(() => {
  104. getPutUser.run({ userId })
  105. }, [userId])
  106. // 同步
  107. const sync = useCallback(() => {
  108. if (selectedRows?.length > 0) {
  109. let accountAdgroupMaps = [...new Set(selectedRows?.map(item => item.account_id + ',' + item.adgroup_id))]
  110. syncBatch.run({ accountAdgroupMaps }).then(res => {
  111. res && getAdList.refresh()
  112. res ? message.success('同步成功!') : message.error('同步失败!')
  113. })
  114. } else {
  115. message.error('请勾选')
  116. }
  117. }, [getAdList, selectedRows])
  118. // 单个启停
  119. const onChange = () => {
  120. getAdList.refresh()
  121. setSelectedRows([])
  122. }
  123. const details = (data: any) => {
  124. setDetailData(data)
  125. setDetailShow(true)
  126. }
  127. useEffect(() => {
  128. const headerBodyScroll = (e: any) => {
  129. let el = document.querySelector(`.expandClassname .expendTable .ant-table-body`);
  130. if (el) {
  131. setScrollLeft(e.target.scrollLeft)
  132. }
  133. }
  134. document.querySelector(`.expandClassname .expendTable .ant-table-body`)?.addEventListener('scroll', headerBodyScroll);
  135. () => {
  136. document.querySelector(`.expandClassname .expendTable .ant-table-body`)?.removeEventListener('scroll', headerBodyScroll);
  137. }
  138. }, [])
  139. const log = (value: any) => {
  140. setAccountIdRule(value.account_id)
  141. setAdgroupId(value.adgroup_id)
  142. setAdgroupName(value.adgroup_name)
  143. setLogVisible(true)
  144. }
  145. const handleTag = (value: any) => {
  146. setTagData(value)
  147. setTagVisible(true)
  148. }
  149. const delTag = (value: any) => {
  150. delUserTag.run({ accountId: value.account_id, adgroupId: value.adgroup_id }).then(res => {
  151. if (res) {
  152. message.success('删除成功')
  153. getAdList.refresh()
  154. }
  155. })
  156. }
  157. const handleColumnTrend = (value: string) => {
  158. let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
  159. if (message) {
  160. message = JSON.parse(message)
  161. }
  162. let columns: string[] = []
  163. if (message && Array.isArray(message)) {
  164. message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
  165. if (!['cz', 'cost_speed'].includes(item.dataIndex)) {
  166. columns.push(item?.serverIndex || 'adgroup_data.' + item.dataIndex)
  167. }
  168. })
  169. } else {
  170. planAdConfig.forEach((item: any) => {
  171. item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
  172. if (d.default && !['cz', 'cost_speed'].includes(d.dataIndex)) {
  173. columns.push(d?.serverIndex || 'adgroup_data.' + d.dataIndex)
  174. }
  175. })
  176. })
  177. }
  178. setTrendData({ ...queryForm, ...filterForm, columns, field: value.replace('_total', '') })
  179. setTrendVisible(true)
  180. }
  181. // 批量启停
  182. const adStatus = (type: boolean) => {
  183. let newSelectedRows = []
  184. if (type) {
  185. newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_SUSPEND')
  186. } else {
  187. newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_NORMAL')
  188. }
  189. if (newSelectedRows.length === 0) {
  190. message.warn(`所有广告都是${type ? '启动' : '暂停'}状态,无需${type ? '启动' : '暂停'}操作`)
  191. return
  192. }
  193. let accountAdgroupMaps = [...new Set(newSelectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
  194. modifyStatusBatch.run({ accountAdgroupMaps, suspend: !type }).then(res => {
  195. message.success(`${type ? '启动' : '暂停'}成功`)
  196. getAdList.refresh()
  197. setSelectedRows([])
  198. })
  199. }
  200. const countDecimals = (num: number) => {
  201. // 匹配数字的小数部分
  202. const match = String(num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
  203. // 返回小数位数
  204. return match ? Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)) : 0;
  205. }
  206. return <div>
  207. {/* 列表指标趋势 */}
  208. {trendVisible && <ColumnTrend visible={trendVisible} data={trendData} onClose={() => { setTrendVisible(false) }} />}
  209. {/* 打标签 */}
  210. {tagVisible && <PlanTag
  211. visible={tagVisible}
  212. data={tagData}
  213. onClose={() => setTagVisible(false)}
  214. onChange={() => {
  215. getAdList.refresh()
  216. setTagVisible(false)
  217. }}
  218. />}
  219. <Row gutter={[6, 6]} align='middle' style={{ marginBottom: 10 }}>
  220. <FilterQuery
  221. queryForm={queryForm}
  222. setQueryForm={setQueryForm}
  223. onChange={(value) => {
  224. setFilterForm({ ...value })
  225. }}
  226. />
  227. </Row>
  228. <div ref={ref} className='expandClassname'>
  229. <TableData
  230. refreshData={getList}
  231. isCard={false}
  232. className='expendTable'
  233. columns={() => tablePlanConfig(onChange, details, log, handleTag, delTag)}
  234. ajax={getAdList}
  235. syncAjax={sync}
  236. fixed={{ left: 2, right: 4 }}
  237. dataSource={getAdList?.data?.data?.records}
  238. loading={getAdList?.loading || syncBatch?.loading}
  239. scroll={{ y: 560 }}
  240. total={getAdList?.data?.data?.total}
  241. page={getAdList?.data?.data?.current}
  242. pageSize={getAdList?.data?.data?.size}
  243. myKey={'adgroup_id'}
  244. gutter={[0, 10]}
  245. config={planAdConfig}
  246. configName={configName}
  247. rowClassName={(record) => {
  248. if (record?.tag_value === 100) {
  249. return 'row_error'
  250. } else if (record?.tag_value === 90) {
  251. return 'row_warning'
  252. } else {
  253. return ''
  254. }
  255. }}
  256. leftChild={<Space direction='vertical'>
  257. <Row gutter={[10, 10]} align='middle'>
  258. <Col><Button type='primary' style={{ background: '#67c23a', borderColor: '#67c23a' }} loading={modifyStatusBatch.loading} icon={<PlayCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(true)}>启动</Button></Col>
  259. <Col><Button type='primary' style={{ background: '#e6a23c', borderColor: '#e6a23c' }} loading={modifyStatusBatch.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(false)}>暂停</Button></Col>
  260. <Col><Button type='primary' icon={<TransactionOutlined />} disabled={selectedRows.length === 0} onClick={() => setUpdate({ visible: true })}>修改出价</Button></Col>
  261. </Row>
  262. </Space>}
  263. rowSelection={{
  264. selectedRowKeys: selectedRows.map(item => item.adgroup_id.toString()),
  265. getCheckboxProps: (record: any) => ({
  266. disabled: record.status === 'STATUS_DELETED'
  267. }),
  268. onSelect: (record: { adgroup_id: number, mpName: string }, selected: boolean) => {
  269. if (selected) {
  270. selectedRows.push({ ...record })
  271. setSelectedRows([...selectedRows])
  272. } else {
  273. let newSelectAccData = selectedRows.filter((item: { adgroup_id: number }) => item.adgroup_id !== record.adgroup_id)
  274. setSelectedRows([...newSelectAccData])
  275. }
  276. },
  277. onSelectAll: (selected: boolean, selectedRowss: { adgroup_id: number }[], changeRows: { adgroup_id: number }[]) => {
  278. if (selected) {
  279. let newSelectAccData = [...selectedRows]
  280. changeRows.forEach((item: { adgroup_id: number }) => {
  281. let index = newSelectAccData.findIndex((ite: { adgroup_id: number }) => ite.adgroup_id === item.adgroup_id)
  282. if (index === -1) {
  283. newSelectAccData.push({ ...item })
  284. }
  285. })
  286. setSelectedRows([...newSelectAccData])
  287. } else {
  288. let newSelectAccData = selectedRows.filter((item: { adgroup_id: number }) => {
  289. let index = changeRows.findIndex((ite: { adgroup_id: number }) => ite.adgroup_id === item.adgroup_id)
  290. if (index !== -1) {
  291. return false
  292. } else {
  293. return true
  294. }
  295. })
  296. setSelectedRows([...newSelectAccData])
  297. }
  298. }
  299. }}
  300. onChange={(props: any) => {
  301. let { sortData, pagination } = props
  302. let { current, pageSize } = pagination
  303. let newQueryForm = JSON.parse(JSON.stringify(queryForm))
  304. newQueryForm.pageNum = current
  305. newQueryForm.pageSize = pageSize
  306. if (sortData && JSON.stringify('sortData') !== '{}') {
  307. let { field, order } = sortData // descend 降序 大到小 ascend 升序 小到大
  308. if (order) {
  309. newQueryForm.sortColumn = field
  310. newQueryForm.sortAsc = order === 'ascend'
  311. } else {
  312. Object.keys(newQueryForm).forEach(key => {
  313. if (key === 'sortColumn' || key === 'sortAsc') {
  314. delete newQueryForm[key]
  315. }
  316. })
  317. }
  318. } else {
  319. Object.keys(newQueryForm).forEach(key => {
  320. if (key === 'sortField' || key === 'sort') {
  321. delete newQueryForm[key]
  322. }
  323. })
  324. }
  325. setQueryForm({ ...newQueryForm })
  326. }}
  327. expandedRowRender={(data) => <AdExpandedRowRender data={data} scrollLeft={scrollLeft} width={size?.width} />}
  328. summary={() => (
  329. <Table.Summary fixed>
  330. <Table.Summary.Row className='s_summary'>
  331. {tableField.map((item, index) => {
  332. let data = totalData[item.dataIndex]
  333. let value = (data === 0 || data) ? countDecimals(data) > 2 ? data.toFixed(2) : data : '--'
  334. if (item.dataIndex === 'zj') {
  335. return <Table.Summary.Cell index={index} key={item.dataIndex} align="center"><strong>总计</strong></Table.Summary.Cell>
  336. } else if (['ctr_total', 'mp_follow_rate_total',
  337. 'add_quick_app_rate_total', 'scan_follow_rate_total',
  338. 'first_day_order_roi_total', 'order_rate_total',
  339. 'order_roi_total', 'conversions_rate_total'
  340. ].includes(item.dataIndex)) {
  341. return <Table.Summary.Cell index={index} key={item.dataIndex} align="center">
  342. <Space size={4}>
  343. <strong>
  344. {value !== '--' ? <Statistic value={value ? value * 100 : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" /> : '--'}
  345. </strong>
  346. {value !== '--' && <a onClick={() => handleColumnTrend(item.dataIndex)}><LineChartOutlined /></a>}
  347. </Space>
  348. </Table.Summary.Cell>
  349. } else {
  350. return <Table.Summary.Cell index={index} key={item.dataIndex} align="center">
  351. <Space size={4}>
  352. <strong><Statistic value={value} /></strong>
  353. {value !== '--' && <a onClick={() => handleColumnTrend(item.dataIndex)}><LineChartOutlined /></a>}
  354. </Space>
  355. </Table.Summary.Cell>
  356. }
  357. })}
  358. </Table.Summary.Row>
  359. </Table.Summary>
  360. )}
  361. />
  362. </div>
  363. {detailShow && <Details visible={detailShow} onClose={() => { setDetailShow(false) }} data={detailData} />}
  364. {logVisible && <RuleAccountLog accountId={accountIdRule} adgroupName={adgroupName} adgroupId={adgroupId} visible={logVisible} onClose={() => setLogVisible(false)} />}
  365. {/* 修改广告 */}
  366. {update.visible && <UpdateAd
  367. {...update}
  368. selectedRows={selectedRows.map(item => ({ ...item, accountId: item.account_id, adgroupId: item.adgroup_id }))}
  369. onChange={() => {
  370. setUpdate({ visible: false })
  371. getAdList.refresh()
  372. setSelectedRows([])
  373. }}
  374. onClose={() => { setUpdate({ visible: false }) }}
  375. />}
  376. </div>
  377. }
  378. export default AdPlanList