monitor.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import { Card, Input, Radio, Select, Space, Spin, Tag, TimePicker, Tooltip, message } from "antd";
  2. import React, { useCallback, useEffect, useState } from "react";
  3. import { ColumnHeightOutlined, ColumnWidthOutlined, EyeInvisibleOutlined, EyeOutlined, RedoOutlined } from "@ant-design/icons";
  4. import useEcharts from '@/Hook/useEcharts'
  5. import { useModel } from 'umi'
  6. import { ListHourProps, ListType } from '@/services/adMonitor/adMonitor'
  7. import '../../adMonitor/adMonitorList/table.less'
  8. import moment from "moment";
  9. import TableData from "@/pages/launchSystemNew/components/TableData";
  10. import RuleAccountLog from "@/components/EarlyWarning/ruleAccountLog";
  11. import { LineField } from "@/pages/adMonitor/adMonitorList/config";
  12. import FilterQuery from "@/pages/adMonitor/adMonitorList/components/FilterQuery";
  13. import Details from "./Details";
  14. import { columnsMonitor } from "./tableMonitorConfig";
  15. import { qiliangpaihanghour } from "./config";
  16. import { syncBatchApi } from "@/services/launchAdq/adqv3";
  17. import { useAjax } from "@/Hook/useAjax";
  18. interface newListType extends ListType {
  19. totalTimeUnit: 'total' | 'minute' | 'hour' | 'day',
  20. planTimeUnit: 'minute' | 'hour' | 'day'
  21. }
  22. /**
  23. * 今日起量监控
  24. * @param props
  25. * @returns
  26. */
  27. function Monitor(props: { onChange: () => void }) {
  28. const { onChange } = props
  29. const { getCostTrendV3List, getCostTopV3List, getListForHourV3, getAdqAccountList } = useModel('useAdMonitor.useMonitor')
  30. const { getPicherList } = useModel('useOperating.useWxGroupList')
  31. // 变量开始
  32. const [queryForm, setQueryForm] = useState<newListType>({ totalTimeUnit: 'day', planTimeUnit: 'hour', sysUserId: JSON.parse(sessionStorage.getItem('SYSUSERID') || '[]'), pageNum: 1, pageSize: 20 }) // 搜索变量//startTime: moment().format('YYYY-MM-DD'), endTime: moment().format('YYYY-MM-DD'),
  33. const { BarMonitor, LineMonitor } = useEcharts()
  34. const [barDis, setBarDis] = useState<any[]>([])
  35. const [lineDis, setLineDis] = useState<any[]>([])
  36. const [lineTitle, setLineTitle] = useState<string>('广告总消耗趋势')
  37. const [px, setPx] = useState<boolean>(false)//设置顶部图形的排列方式
  38. const [visible, setVisible] = useState<boolean>(false) // 详情弹窗控制
  39. const [aId, setAId] = useState<any>()
  40. const [showEacharts, setShowEacharts] = useState<boolean>(true)
  41. const [queryForHour, setQueryForHour] = useState<ListHourProps>({ pageNum: 1, pageSize: 20 })
  42. const [filterQuery, setFilterQuery] = useState<any>({})
  43. const [logVisible, setLogVisible] = useState<boolean>(false)
  44. const [adgroupId, setAdgroupId] = useState<string>('')
  45. const [adgroupName, setAdgroupName] = useState<string>('')
  46. const [accountIdRule, setAccountIdRule] = useState<string>('')
  47. const [trendColumns, setTrendColumns] = useState<string[]>(['cost'])
  48. const { totalTimeUnit, planTimeUnit, adgroup, accountId, sysUserId, groupAccountIds } = queryForm
  49. const configName = '起量广告排行明细3.0'
  50. const { getGroupList, groupListInit } = useModel('useLaunchAdq.useAdAuthorize')
  51. const syncBatch = useAjax((params) => syncBatchApi(params))
  52. useEffect(() => {
  53. groupListInit()
  54. }, [])
  55. useEffect(() => {
  56. getList()
  57. }, [queryForHour, filterQuery, queryForm?.sysUserId, queryForm?.accountId, queryForm?.adgroup, queryForm?.groupAccountIds])
  58. const getList = () => {
  59. let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
  60. if (message) {
  61. message = JSON.parse(message)
  62. }
  63. let isAccountId = true
  64. let isAdgroupId = true
  65. let isAdgroupName = true
  66. let columns: string[] = []
  67. if (message && Array.isArray(message)) {
  68. message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
  69. if (!['event'].includes(item.dataIndex)) {
  70. if (item.dataIndex === 'account_id') {
  71. isAccountId = false
  72. } else if (item.dataIndex === 'adgroup_id') {
  73. isAdgroupId = false
  74. } else if (item.dataIndex === 'adgroup_name') {
  75. isAdgroupName = false
  76. }
  77. columns.push(item?.serverIndex || 'adgroup_data.' + item.dataIndex)
  78. }
  79. })
  80. } else {
  81. qiliangpaihanghour.forEach((item: any) => {
  82. item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
  83. if (d.default && !['event'].includes(d.dataIndex)) {
  84. if (d.dataIndex === 'account_id') {
  85. isAccountId = false
  86. } else if (d.dataIndex === 'adgroup_id') {
  87. isAdgroupId = false
  88. } else if (item.dataIndex === 'adgroup_name') {
  89. isAdgroupName = false
  90. }
  91. columns.push(d?.serverIndex || 'adgroup_data.' + d.dataIndex)
  92. }
  93. })
  94. })
  95. }
  96. if (isAccountId) {
  97. columns.push('adgroup_data.account_id')
  98. }
  99. if (isAdgroupId) {
  100. columns.push('adgroup_data.adgroup_id')
  101. }
  102. if (isAdgroupName) {
  103. columns.push('adgroup_data.adgroup_name')
  104. }
  105. let params = { ...queryForHour, ...filterQuery }
  106. if (queryForm?.sysUserId) {
  107. params.sysUserIds = queryForm?.sysUserId
  108. }
  109. if (queryForm?.accountId && queryForm?.accountId?.length > 0) {
  110. params.accountIdStr = queryForm?.accountId.toString()
  111. }
  112. if (queryForm?.adgroup) {
  113. params.adgroupIdStr = queryForm?.adgroup
  114. }
  115. if (queryForm?.groupAccountIds) {
  116. params.groupAccountIds = queryForm?.groupAccountIds
  117. }
  118. params.columns = columns
  119. getListForHourV3.run(params)
  120. }
  121. // 获取投手
  122. useEffect(() => {
  123. !getPicherList.data && getPicherList.run()
  124. }, [])
  125. // 获取广告账号
  126. useEffect(() => {
  127. !getAdqAccountList.data && getAdqAccountList.run()
  128. }, [])
  129. // // 获取排行数据,柱图
  130. useEffect(() => {
  131. getPlanCostList()
  132. }, [totalTimeUnit, accountId, sysUserId, groupAccountIds])
  133. // 获取今日计划总消耗图谱,折线
  134. useEffect(() => {
  135. getTootalCostList()
  136. }, [planTimeUnit, adgroup, accountId, sysUserId, groupAccountIds, trendColumns])
  137. /** 获取折线图 */
  138. const getTootalCostList = useCallback(async () => {
  139. let { totalTimeUnit, planTimeUnit, pageNum, pageSize, adgroup, sysUserId, accountId, ...newQueryForm } = queryForm
  140. let params = adgroup ? { ...newQueryForm, adgroupIdStr: adgroup } : newQueryForm
  141. let res = await getCostTrendV3List.run({ ...params, timeUnit: planTimeUnit, sysUserIds: sysUserId, accountIdStr: accountId?.join(), trendColumns })
  142. if (res?.data) {
  143. let data = trendColumns.map((field) => {
  144. let value: any = {}
  145. res?.data?.forEach((item: any, index: number) => {
  146. if (index === 0) value.legendName = LineField[field as keyof typeof LineField];
  147. value[item?.trend_unit] = item?.[field]
  148. });
  149. return value
  150. })
  151. setLineDis(() => data)
  152. setLineTitle(() => `广告总${LineField[trendColumns[0] as keyof typeof LineField]}趋势`)
  153. }
  154. }, [queryForm, lineDis, trendColumns])
  155. /** 获取柱状图 */
  156. const getPlanCostList = useCallback(async () => {
  157. let { totalTimeUnit, planTimeUnit, pageNum, pageSize, sysUserId, accountId, ...newQueryForm } = queryForm
  158. let { adgroup, ...planQueryFrom } = newQueryForm
  159. let res = await getCostTopV3List.run({ ...planQueryFrom, timeUnit: totalTimeUnit, sysUserIds: sysUserId, accountId: accountId?.join(), topN: 10 })
  160. let data = res?.data?.map((item: { adgroupId: number, cost: number, adgroupName: string, accountId: number }) => {
  161. return { name: item.adgroupId.toString(), value: item.cost, adName: item.adgroupName, accountId: item.accountId }
  162. })
  163. data = data?.sort((a: any, b: any) => {
  164. var value1 = a['value'];
  165. var value2 = b['value'];
  166. return value2 - value1;
  167. })
  168. setBarDis(() => data)
  169. }, [queryForm, barDis])
  170. // 详情
  171. const details = (data: any) => {
  172. setAId(data)
  173. setVisible(true)
  174. }
  175. // 计划详情
  176. const planDetail = (data: any) => {
  177. sessionStorage.setItem('ADIDORNAME', JSON.stringify(data))
  178. onChange && onChange()
  179. }
  180. //全部接口刷新
  181. const refresh = () => {
  182. getCostTrendV3List.refresh()
  183. getCostTopV3List.refresh()
  184. getListForHourV3.refresh()
  185. }
  186. // 接口自动刷新10分钟一次
  187. useEffect(() => {
  188. let time = setInterval(() => {
  189. refresh()
  190. }, 1000 * 60 * 10)
  191. return () => {
  192. clearInterval(time)
  193. }
  194. }, [])
  195. //图形排列样式改变重新获取数据刷新图形
  196. const set = useCallback((b: any) => {
  197. setPx(b)
  198. getCostTrendV3List.refresh()
  199. getCostTopV3List.refresh()
  200. }, [getCostTopV3List, getCostTrendV3List, trendColumns])
  201. // 处理折线图数据
  202. const timePickerHandle = async (values: any, formatString: [string, string]) => {
  203. let res = await getCostTrendV3List.data
  204. let data = trendColumns.map((field) => {
  205. let value: any = {}
  206. res?.data?.forEach((item: any, index: number) => {
  207. if (index === 0) value.legendName = LineField[field as keyof typeof LineField];
  208. value[item?.trend_unit] = item?.[field]
  209. });
  210. return value
  211. })
  212. if (values) {
  213. let date = moment().format('YYYY-MM-DD')
  214. let strTime = formatString[0]
  215. let endTime = formatString[1]
  216. let dateTimeStr = `${date} ${strTime}:00`
  217. let dateTimeEnd = `${date} ${endTime}:00`
  218. let newData: any[] = data.map(item => {
  219. let { legendName, ...otherData } = item
  220. let newItem: any = { legendName }
  221. for (const key in otherData) {
  222. if (Object.prototype.hasOwnProperty.call(otherData, key)) {
  223. const value = otherData[key];
  224. if (moment(dateTimeStr) <= moment(key) && moment(key) <= moment(dateTimeEnd)) {
  225. newItem[key] = value
  226. }
  227. }
  228. }
  229. return newItem
  230. })
  231. setLineDis(() => newData)
  232. } else {
  233. setLineDis(() => data)
  234. }
  235. }
  236. const log = (value: any) => {
  237. setAccountIdRule(value.account_id)
  238. setAdgroupId(value.adgroup_id)
  239. setAdgroupName(value.adgroup_name)
  240. setLogVisible(true)
  241. }
  242. const sync = (data: any) => {
  243. const hide = message.loading(`${data?.adgroup_name}广告同步中`, 0, () => {
  244. message.success('修改成功');
  245. });
  246. let accountAdgroupMaps = [data.account_id + ',' + data.adgroup_id]
  247. syncBatch.run({ accountAdgroupMaps }).then(res => {
  248. hide()
  249. getListForHourV3.refresh()
  250. message.success('同步完成!')
  251. }).catch(() => hide())
  252. }
  253. return <Space direction='vertical' style={{ width: '100%' }} className="monitor">
  254. <Card hoverable bodyStyle={{ padding: '12px 16px' }}>
  255. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  256. <Space>
  257. <Select
  258. showSearch
  259. value={queryForm.sysUserId}
  260. style={{ minWidth: 180, maxWidth: 250 }}
  261. mode='multiple'
  262. maxTagCount={1}
  263. allowClear
  264. placeholder="请选择投手"
  265. disabled={queryForm?.adgroup || queryForm?.accountId?.length > 0}
  266. onChange={(value: number[]) => {
  267. setQueryForm({ ...queryForm, sysUserId: value, pageNum: 1 })
  268. sessionStorage.setItem('SYSUSERID', value ? JSON.stringify(value) : '')
  269. }}
  270. filterOption={(input, option) =>
  271. (option?.children as any).toLowerCase().indexOf(input.toLowerCase()) >= 0
  272. }
  273. >
  274. {getPicherList?.data?.map((item: { nickname: string, userId: number }, index: number) =>
  275. <Select.Option
  276. value={item.userId}
  277. key={item.userId + '' + index}
  278. >
  279. {item.nickname}
  280. </Select.Option>
  281. )}
  282. </Select>
  283. <Select
  284. showSearch
  285. mode='multiple'
  286. maxTagCount={1}
  287. value={queryForm.accountId}
  288. style={{ minWidth: 220, maxWidth: 250 }}
  289. allowClear
  290. placeholder="请选择广告账号"
  291. onChange={(value: number[]) => {
  292. setQueryForm({ ...queryForm, accountId: value, pageNum: 1 })
  293. }}
  294. >
  295. {getAdqAccountList?.data?.data?.map((item: { id: number, accountId: number }) => <Select.Option
  296. value={item.accountId}
  297. key={item.id}
  298. >
  299. {item.accountId}
  300. </Select.Option>)}
  301. </Select>
  302. <Input
  303. value={queryForm?.accountId?.toString()}
  304. placeholder="输入广告账号"
  305. onChange={(e) => {
  306. setQueryForm({ ...queryForm, accountId: e.target.value ? e.target.value?.split(/[,,\s\n]+/) : undefined })
  307. }}
  308. allowClear
  309. />
  310. <Input
  311. value={queryForm.adgroup}
  312. placeholder="广告ID"
  313. onChange={(e) => {
  314. setQueryForm({ ...queryForm, adgroup: e.target.value })
  315. }}
  316. allowClear
  317. />
  318. <Select
  319. showSearch
  320. mode='multiple'
  321. maxTagCount={1}
  322. value={queryForm.groupAccountIds}
  323. style={{ minWidth: 150 }}
  324. allowClear
  325. placeholder="请选择账号分组"
  326. onChange={(value: number[]) => {
  327. setQueryForm({ ...queryForm, groupAccountIds: value, pageNum: 1 })
  328. }}
  329. >
  330. {getGroupList?.data?.map((item: { groupId: number, groupName: number }) => <Select.Option
  331. value={item.groupId}
  332. key={item.groupId}
  333. >
  334. {item.groupName}
  335. </Select.Option>)}
  336. </Select>
  337. </Space>
  338. <Space>
  339. <Tag onClick={() => setShowEacharts(!showEacharts)}>{showEacharts ? <><EyeInvisibleOutlined /> 隐藏</> : <><EyeOutlined /> 显示</>}</Tag>
  340. <Tag color="#2db7f5" onClick={refresh}><RedoOutlined /> 刷新</Tag>
  341. </Space>
  342. </div>
  343. </Card>
  344. {showEacharts && <Card hoverable bodyStyle={{ padding: '12px 16px' }}>
  345. <span style={{ position: 'absolute', top: 10, zIndex: 10 }}>
  346. {px ?
  347. <Tooltip title='左右排列'><Tag color='#f50' onClick={() => { set(false) }}><ColumnWidthOutlined /><span style={{ fontSize: 10 }}>左右排列</span></Tag></Tooltip>
  348. :
  349. <Tooltip title='上下排列'><Tag color='#f50' onClick={() => { set(true) }} ><ColumnHeightOutlined /><span style={{ fontSize: 10 }}>上下排列</span></Tag></Tooltip>
  350. }
  351. </span>
  352. <div className={!px ? 'charts' : 'charts charts100'}>
  353. <div>
  354. <div className="selectTime">
  355. <Space>
  356. <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getCostTopV3List?.data?.reqTime}</span>
  357. <Radio.Group value={queryForm.totalTimeUnit} buttonStyle="solid" size='small' onChange={(e) => { setQueryForm({ ...queryForm, totalTimeUnit: e.target.value }) }}>
  358. <Radio.Button value="total">总</Radio.Button>
  359. <Radio.Button value="day">天</Radio.Button>
  360. <Radio.Button value="hour">小时</Radio.Button>
  361. <Radio.Button value="minute">5min</Radio.Button>
  362. </Radio.Group>
  363. </Space>
  364. </div>
  365. {getCostTopV3List?.loading ? <Spin /> : <BarMonitor style={{ width: '100%', height: '100%' }} data={barDis} xName="今日消耗" yName="广告名称" onChange={(value: string, accountId) => { console.log(value, accountId); setQueryForm({ ...queryForm, adgroup: value }) }} planID={queryForm?.adgroup} />}
  366. </div>
  367. <div>
  368. <div className="selectTime">
  369. <Space>
  370. <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getCostTrendV3List?.data?.reqTime}</span>
  371. <Radio.Group value={queryForm.planTimeUnit} buttonStyle="solid" size='small' onChange={(e) => { setQueryForm({ ...queryForm, planTimeUnit: e.target.value }) }}>
  372. <Radio.Button value="hour">小时</Radio.Button>
  373. <Radio.Button value="minute">5min</Radio.Button>
  374. </Radio.Group>
  375. {queryForm.planTimeUnit === 'minute' && <TimePicker.RangePicker
  376. size="small"
  377. format='HH:mm'
  378. minuteStep={5}
  379. onChange={timePickerHandle}
  380. />}
  381. <Select
  382. showSearch
  383. mode='multiple'
  384. maxTagCount={1}
  385. value={trendColumns}
  386. style={{ minWidth: 130 }}
  387. allowClear
  388. placeholder="请选择图表字段"
  389. onChange={(value: string[]) => {
  390. setTrendColumns(value?.length ? value : ['cost'])
  391. }}
  392. size="small"
  393. >
  394. {Object.keys(LineField).map((key) => <Select.Option value={key} key={key}>
  395. {LineField[key as keyof typeof LineField]}
  396. </Select.Option>)}
  397. </Select>
  398. </Space>
  399. </div>
  400. {getCostTrendV3List?.loading ? <Spin /> : <LineMonitor style={{ width: '100%', height: '100%' }} series smooth data={lineDis} title={trendColumns?.length > 1 ? undefined : lineTitle} />}
  401. </div>
  402. </div>
  403. </Card>}
  404. <div className={'MYtable'}>
  405. <TableData
  406. refreshData={getList}
  407. bodyStyle={{ padding: '12px 16px' }}
  408. gutter={[0, 12]}
  409. columns={columnsMonitor(planDetail, details, (value) => { setQueryForm({ ...queryForm, adgroup: value }); }, log, sync)}
  410. dataSource={getListForHourV3?.data?.data?.records}
  411. loading={getListForHourV3?.loading}
  412. ajax={getListForHourV3}
  413. leftChild={
  414. <Space>
  415. <FilterQuery onChange={(data) => {
  416. setFilterQuery(data)
  417. }} />
  418. </Space>
  419. }
  420. myKey={'adgroup_id'}
  421. fixed={{ left: 0, right: 2 }}
  422. total={getListForHourV3?.data?.data?.total}
  423. onChange={(props: any) => {
  424. let { sortData, pagination } = props
  425. let { current, pageSize } = pagination
  426. let newQueryForm = JSON.parse(JSON.stringify(queryForHour))
  427. newQueryForm.pageNum = current
  428. newQueryForm.pageSize = pageSize
  429. if (sortData && JSON.stringify('sortData') !== '{}') {
  430. let { field, order } = sortData // descend 降序 大到小 ascend 升序 小到大
  431. if (order) {
  432. newQueryForm.sortColumn = field
  433. newQueryForm.sortAsc = order === 'ascend'
  434. } else {
  435. Object.keys(newQueryForm).forEach(key => {
  436. if (key === 'sortColumn' || key === 'sortAsc') {
  437. delete newQueryForm[key]
  438. }
  439. })
  440. }
  441. } else {
  442. Object.keys(newQueryForm).forEach(key => {
  443. if (key === 'sortField' || key === 'sort') {
  444. delete newQueryForm[key]
  445. }
  446. })
  447. }
  448. setQueryForHour({ ...newQueryForm })
  449. }}
  450. page={queryForHour.pageNum}
  451. pageSize={queryForHour.pageSize}
  452. scroll={{ y: 750 }}
  453. config={qiliangpaihanghour}
  454. configName={configName}
  455. />
  456. </div>
  457. {visible && <Details visible={visible} onClose={() => { setVisible(false) }} data={aId} />}
  458. {logVisible && <RuleAccountLog accountId={accountIdRule} adgroupName={adgroupName} adgroupId={adgroupId} visible={logVisible} onClose={() => setLogVisible(false)} />}
  459. </Space>
  460. }
  461. export default React.memo(Monitor)