monitor.tsx 22 KB

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