monitor.tsx 23 KB

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