index.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. import DatePickePage from '@/components/DatePicker'
  2. import GroupWxTabs from '@/components/GroupWxTabs'
  3. import Tables from '@/components/Tables'
  4. import useEcharts from '@/Hook/useEcharts'
  5. import { FullscreenExitOutlined, FullscreenOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons'
  6. import { Button, Card, Col, Drawer, Input, InputNumber, Row, Select, Space, Tooltip, Transfer } from 'antd'
  7. import { RadioChangeEvent } from 'antd/lib/radio'
  8. import { Moment } from 'moment'
  9. import React, { useCallback, useEffect, useRef, useState } from 'react'
  10. import { useModel } from 'umi'
  11. import DailyReport from './components/DailyReport'
  12. import { dateColumns, weChatColumns } from './tableConfig'
  13. import style from './index.less'
  14. import { SelectValue } from 'antd/es/select'
  15. import HocError from '@/Hoc/HocError'
  16. import { quanpin } from '@/utils/fullScreen'
  17. import { TransferDirection } from 'antd/lib/transfer'
  18. import Sortable from 'sortablejs'
  19. const gutter = { xs: 24, sm: 24, md: 12, xl: 8 }
  20. function DataCenter() {
  21. const { state, getOverview, dispatch, getDaily, getChanne } = useModel('useData.useDataCenter')
  22. const { currentUser }: any = useModel('@@initialState', model => ({ currentUser: model.initialState?.currentUser }))
  23. const { state: userState } = useModel('useOperating.useUser')
  24. const { state: { groupActionWx } } = useModel('useOperating.useWxGroupList')
  25. const { selectdName, isFell } = userState
  26. const { state: { pitcher } } = useModel('useData.useTableData')
  27. const [total, setTotal] = useState<number>(0)
  28. const [size, setSize] = useState<'small' | 'middle' | 'large'>('small')
  29. const [page, setPage] = useState<number>(1)
  30. const [pageSize, setPageSize] = useState<number>(100)
  31. const [visible, setVisible] = useState<boolean>(false)
  32. const { LineData1, LineData2, LineData3, tableValue, date_range, channel, start, end, tableData, order_by, order } = state
  33. const [selectedKeys, setSelectedKeys] = useState<any[]>([])
  34. const [targetKeys, setTargetKeys] = useState<any[]>(localStorage?.getItem(`日期汇总`) ? JSON.parse(localStorage?.getItem(`日期汇总`) as string) : [])
  35. const [sortArr, setSortArr] = useState<any[]>([])
  36. const [fixedNum, setFixedNum] = useState<number | string>(localStorage.getItem(`日期汇总fixedNum`) || 1)
  37. const [newColumns, setNewColumns] = useState<any[]>([])
  38. const [moveArr, setMoveArr] = useState<any[]>([])
  39. const [statistical, setStatistical] = useState<any>({})
  40. const [tabKey, setTabKey] = useState<string>('1')
  41. const [showZj, setShowZj] = useState<boolean>(false)
  42. const [sortOrder, setSortOrder] = useState<{ columnKey: string, order: 'descend' | 'ascend' | undefined }>({ columnKey: '', order: undefined })
  43. const { Line } = useEcharts()
  44. const ref = useRef(null)
  45. //获取日报数据
  46. useEffect(() => {
  47. let obj: any = {}
  48. dispatch({ type: 'setState', params: { overview: obj } })
  49. let dataQuery: any = { user_id: currentUser.userId == 1 ? userState.selectdUserId : currentUser.userId }
  50. pitcher && (dataQuery['pitcher'] = pitcher)
  51. getOverview.run(dataQuery).then((res: any) => {
  52. let data = res?.data
  53. if (data?.length > 0) {
  54. Object.keys(data[0]).forEach((key: string) => {
  55. if (typeof data[0][key] === 'number') {
  56. obj[key] = parseFloat(data[0][key].toFixed(4))
  57. } else {
  58. obj[key] = data[0][key]
  59. }
  60. })
  61. }
  62. dispatch({ type: 'setState', params: { overview: obj } })
  63. })
  64. }, [pitcher, userState.selectdUserId, currentUser.userId])
  65. //获取tables表格数据
  66. useEffect(() => {
  67. tableSubmut({ current: 1, pageSize: 100 })
  68. }, [pitcher, tableValue, selectdName])
  69. /**图表日期选择 */
  70. const lineDayChange = useCallback((vlaue: '7' | '15') => {
  71. dispatch({ type: 'LineDay', params: { LineDay: vlaue } })
  72. console.log(vlaue)
  73. }, [])
  74. /**table切换事件 */
  75. const tableChange = useCallback((e: RadioChangeEvent) => {
  76. setTargetKeys([])//清空选中数组
  77. setMoveArr([])//清空移动数组
  78. setNewColumns([])//清空渲染列表
  79. dispatch({ type: 'setState', params: { tableData: [] } })//清空数据
  80. let value = e.target.value
  81. let name = value === '1' ? '日期汇总' : '公众号汇总'
  82. setFixedNum(localStorage.getItem(`${name}fixedNum`) || 1)
  83. setSort(value)//排序函数
  84. dispatch({ type: 'tableChange', params: { tableValue: value } })
  85. let newTargetKeys = localStorage?.getItem(`${name}`) ? JSON.parse(localStorage?.getItem(`${name}`) as string) : []
  86. setTargetKeys(newTargetKeys)//读取字段配置
  87. filterColumns(newTargetKeys, value)
  88. }, [fixedNum, targetKeys])
  89. /**table搜索事件 */
  90. const tableSubmut = useCallback((pagination?: { current?: number, pageSize?: number }, filters?: any, sortData?: { column: { dataIndex: string }, order?: "ascend" | "descend", columnKey: string }) => {
  91. let data = { page: pagination?.current || 1, page_size: pagination?.pageSize || 100 }
  92. pitcher && (data['pitcher'] = pitcher)
  93. setPageSize(pagination?.pageSize || 100)
  94. setPage(pagination?.current || 1)
  95. channel && (data['channel'] = channel)
  96. start && (data['start'] = start)
  97. end && (data['end'] = end)
  98. userState.selectdUserId && (data['user_id'] = currentUser.userId == 1 ? userState.selectdUserId : currentUser.userId)
  99. // date_range && (data['date_range'] = date_range);
  100. sortOrder?.order && (data['order'] = sortOrder?.order === 'ascend' ? 'asc' : 'desc');
  101. sortOrder?.columnKey && (data['order_by'] = sortOrder?.columnKey)
  102. sortData?.order && (data['order'] = sortData?.order === 'ascend' ? 'asc' : 'desc');
  103. sortData?.column?.dataIndex && (data['order_by'] = sortData?.column?.dataIndex)
  104. if (sortData) { // 返回排序
  105. setSortOrder({ columnKey: sortData.columnKey, order: sortData?.order })
  106. }
  107. let ajax = tableValue === '1' ? getDaily : getChanne
  108. ajax.run(data).then((res: any) => {
  109. let data: any = res?.data
  110. setStatistical(res?.total_data)
  111. if (tableValue === '1') {
  112. let LineData1 = [{ legendName: '消耗' }]
  113. let LineData2 = [{ legendName: '充值' }]
  114. let LineData3 = [{ legendName: 'ROI' }]
  115. let newData = data?.length > 0 ? (JSON.parse(JSON.stringify(data))).sort((a: { date: string }, b: { date: string }) => {
  116. return new Date(a.date).getTime() - new Date(b.date).getTime()
  117. }) : []
  118. newData?.map((item: { date: string, cost: number, first_roi: number, order_amount: number }) => {
  119. LineData1[0][item.date] = item.cost
  120. LineData2[0][item.date] = item.order_amount
  121. LineData3[0][item.date] = item.first_roi
  122. })
  123. dispatch({ type: 'setState', params: { LineData1, LineData2, LineData3 } })
  124. }
  125. dispatch({
  126. type: 'setState',
  127. params: {
  128. tableData: data || []
  129. }
  130. })
  131. setTotal(res?.total)
  132. })
  133. }, [channel, start, end, date_range, selectdName, sortOrder, tableValue, order_by, order, userState.selectdUserId, pitcher, currentUser.userId])
  134. /**tableDay事件 */
  135. const tableDayChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  136. let value = e.target.value
  137. dispatch({ type: 'setState', params: { date_range: value, start: '', end: '' } })
  138. }, [])
  139. /**tableAppid事件 */
  140. const tableAppidChange = useCallback((value: SelectValue) => {
  141. dispatch({ type: 'tableAppid', params: { channel: value as string } })
  142. }, [])
  143. /**tableDate事件 */
  144. const tableDateChange = useCallback((arr: Moment[], formatString: string | string[]) => {
  145. dispatch({ type: 'setState', params: { start: (formatString as string[])[0], end: (formatString as string[])[1], date_range: '' } })
  146. }, [])
  147. /**table字段设置 */
  148. const handleChange = useCallback((nextTargetKeys: string[], direction: TransferDirection, moveKeys: string[]) => {
  149. setTargetKeys(nextTargetKeys)
  150. filterColumns(nextTargetKeys, tableValue as '1' | '2')
  151. }, [tableValue, fixedNum])
  152. const handleSelectChange = useCallback((sourceSelectedKeys: string[], targetSelectedKeys: string[]) => {
  153. setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys])
  154. }, [])
  155. const handleScroll = (direction: TransferDirection, e: React.SyntheticEvent<HTMLUListElement, Event>) => {
  156. // console.log('direction:', direction);
  157. // console.log('target:', e.target);
  158. };
  159. //获取排序列表
  160. const setSort = useCallback((tableValue?: any) => {
  161. let arr: any[] = []
  162. let columns = tableValue === '1' ? dateColumns : weChatColumns
  163. columns().map((item: any) => {
  164. if (typeof item.title === 'string') {
  165. arr.push({ title: item.title, value: item.dataIndex, key: item.dataIndex })
  166. }
  167. if (typeof item.title === 'object' && typeof item.title.props.children[0] === 'string') {
  168. arr.push({ title: item.title, value: item.dataIndex, key: item.dataIndex })
  169. }
  170. })
  171. setSortArr(arr)
  172. }, [])
  173. /**首次加载 */
  174. useEffect(() => {
  175. setSort('1')
  176. filterColumns(localStorage?.getItem(`日期汇总`) ? JSON.parse(localStorage?.getItem(`日期汇总`) as string) : [], '1')
  177. }, [])
  178. /** 计算新的渲染Columns*/
  179. const filterColumns = (targetKeys: any[], tableValue: '1' | '2') => {
  180. let arr: any[] = []
  181. if (targetKeys && targetKeys.length > 0) {
  182. let fixedNum: any = localStorage.getItem(`${tableValue === '1' ? '日期汇总fixedNum' : '公众号汇总fixedNum'}`) ? localStorage.getItem(`${tableValue === '1' ? '日期汇总fixedNum' : '公众号汇总fixedNum'}`) : 1
  183. let columns = tableValue === '1' ? dateColumns() : weChatColumns()
  184. targetKeys?.forEach((v: string, index: number) => {
  185. columns?.forEach((item: any) => {
  186. if (item.dataIndex === v) {
  187. if (index < fixedNum) {
  188. item.fixed = true
  189. } else {
  190. item.fixed = false
  191. }
  192. arr.push(item)
  193. }
  194. })
  195. })
  196. localStorage.setItem(`${tableValue === '1' ? '日期汇总' : '公众号汇总'}`, JSON.stringify(targetKeys))//保存至本地浏览器
  197. setNewColumns(arr)
  198. }
  199. }
  200. /**拖动排序 */
  201. useEffect(() => {
  202. if (visible) {
  203. let ul: any = document.getElementsByClassName('ant-transfer-list-content-show-remove')[0]
  204. if (ul) {
  205. new Sortable(ul, {
  206. animation: 150, onEnd: () => {
  207. let arr: any = []
  208. for (let item of ul?.children) {
  209. arr.push(item.title)
  210. }
  211. setMoveArr(arr)
  212. }
  213. })
  214. }
  215. }
  216. }, [visible])
  217. /**计算当前UL的顺序生成新的key表 */
  218. useEffect(() => {
  219. if (moveArr.length > 0) {
  220. let newTargetKeys: any[] = []
  221. moveArr?.forEach((t: string) => {
  222. sortArr?.forEach((arr: any) => {
  223. if (t === arr?.title) {
  224. newTargetKeys.push(arr?.key)
  225. }
  226. })
  227. })
  228. filterColumns(newTargetKeys, tableValue as '1' | '2')
  229. setTargetKeys(newTargetKeys)
  230. }
  231. }, [moveArr, sortArr, tableValue])
  232. const content = <Row gutter={[20, 20]} >
  233. {/**日报 */}
  234. <div className={style.gktitle}>
  235. <div> {(pitcher ? pitcher : currentUser.userId == 1 ? selectdName : currentUser.name) + ' -- ' + '概况'} </div>
  236. </div>
  237. <Col span={24} >
  238. {
  239. state.overview && JSON.stringify(state.overview) !== '{}' ?
  240. <DailyReport {...state.overview} /> : <DailyReport {...{}} />
  241. }
  242. </Col>
  243. {/**图表 */}
  244. <Col span={24}>
  245. <Row className={style.chart} gutter={[20, 20]} >
  246. <Col span={24} >
  247. <Card
  248. hoverable
  249. title={(pitcher ? pitcher : currentUser.userId == 1 ? selectdName : currentUser.name) + '--' + (tabKey === '1' ? `消耗图表` : tabKey === '2' ? '充值图表' : 'ROI图表')}
  250. headStyle={{ textAlign: 'center' }}
  251. tabList={[{ key: '1', tab: '消耗图表' }, { key: '2', tab: '充值图表' }, { key: '3', tab: 'ROI图表' }]}
  252. activeTabKey={tabKey}
  253. onTabChange={(key: string) => {
  254. setTabKey(key)
  255. }}
  256. loading={getDaily?.loading}
  257. >
  258. {
  259. tabKey === '1' && <Line
  260. style={{ width: '100%', height: 300, display: 'flex', justifyContent: 'center' }}
  261. data={LineData1}
  262. areaStyle
  263. color={'#607d8b'}
  264. markPoint={[
  265. { type: 'max', name: '最大值' },
  266. { type: 'min', name: '最小值' }
  267. ]}
  268. />
  269. }
  270. {
  271. tabKey === '2' && <Line
  272. style={{ width: '100%', height: 300, display: 'flex', justifyContent: 'center' }}
  273. data={LineData2}
  274. areaStyle
  275. color={'#009688'}
  276. markPoint={[
  277. { type: 'max', name: '最大值' },
  278. { type: 'min', name: '最小值' }
  279. ]}
  280. />
  281. }
  282. {
  283. tabKey === '3' && <Line
  284. style={{ width: '100%', height: 300, display: 'flex', justifyContent: 'center' }}
  285. data={LineData3}
  286. areaStyle
  287. color={'#00bcd4'}
  288. markPoint={[
  289. { type: 'max', name: '最大值' },
  290. { type: 'min', name: '最小值' }
  291. ]}
  292. />
  293. }
  294. </Card>
  295. </Col>
  296. </Row>
  297. </Col>
  298. {/**table */}
  299. <Col span={24} ref={ref} style={isFell ? { background: '#fff' } : {}}>
  300. <Card
  301. hoverable
  302. title={(pitcher ? pitcher : currentUser.userId == 1 ? selectdName : currentUser.name) + '--' + (tableValue === '1' ? '日期汇总数据' : '公众号汇总数据')}
  303. headStyle={{ textAlign: 'center' }}
  304. >
  305. <Row gutter={[0, 20]}>
  306. {/**头部搜索 */}
  307. <Col span={24}>
  308. <Row>
  309. <Col style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
  310. <Space>
  311. {
  312. tableValue === '2' && <Select
  313. style={{ width: 150 }}
  314. placeholder="输入搜索公众号"
  315. onChange={tableAppidChange}
  316. showArrow
  317. showSearch
  318. allowClear
  319. value={channel}
  320. >
  321. {
  322. groupActionWx?.map((list: any, eq: number) => {
  323. return <Select.Option key={list?.id} value={list?.nickName}>
  324. <img src={list?.headImg || localStorage?.bookImg} className={style.item_img} /> {list?.nickName}
  325. </Select.Option>
  326. })
  327. })
  328. </Select>
  329. }
  330. <DatePickePage model='RangePicker' DatePickerChange={tableDateChange} allowClear date={start ? [start, end] : ['', '']} />
  331. {/* <Input
  332. style={{ width: 120 }}
  333. onChange={tableDayChange}
  334. allowClear
  335. placeholder='过去N天'
  336. value={date_range}
  337. >
  338. </Input> */}
  339. <Button type='primary' onClick={() => tableSubmut()}><SearchOutlined />搜索</Button>
  340. </Space>
  341. <Space style={{ float: 'right' }}>
  342. {/* {<Button
  343. size='small'
  344. type='link'
  345. onClick={() => {
  346. setShowZj(!showZj)
  347. }}>
  348. {showZj ? '隐藏总计' : '展示总计'}
  349. </Button>} */}
  350. <Button
  351. size='small'
  352. type='text'
  353. onClick={() => {
  354. setVisible(true)
  355. }}>
  356. <SettingOutlined />
  357. </Button>
  358. <Button
  359. type='text'
  360. onClick={() => {
  361. if (ref?.current) {
  362. quanpin(ref?.current)
  363. }
  364. }}>
  365. {
  366. <Tooltip title={!isFell ? '全屏' : '退出全屏'}>{!isFell ? <FullscreenOutlined /> : <FullscreenExitOutlined />}</Tooltip>
  367. }
  368. </Button>
  369. <Drawer
  370. title="table字段设置"
  371. width={420}
  372. visible={visible}
  373. getContainer={ref?.current || false}
  374. onClose={() => {
  375. setVisible(false)
  376. }}
  377. >
  378. <div>
  379. <span>请输入固定列数值:</span>
  380. <InputNumber
  381. size="small"
  382. min={1}
  383. max={100000}
  384. onChange={(v) => {
  385. setFixedNum(v as number)
  386. localStorage.setItem(`${tableValue === '1' ? '日期汇总fixedNum' : '公众号汇总fixedNum'}`, `${v}`)
  387. filterColumns(targetKeys, tableValue as '1' | '2')
  388. }}
  389. value={fixedNum}
  390. style={{ marginBottom: 10 }}
  391. placeholder='请输入固定列数值'
  392. />
  393. </div>
  394. <Transfer
  395. dataSource={sortArr || []}
  396. titles={['不展示', '展示']}
  397. showSearch
  398. targetKeys={targetKeys || []}
  399. selectedKeys={selectedKeys || []}
  400. onChange={handleChange}
  401. onSelectChange={handleSelectChange}
  402. onScroll={handleScroll}
  403. render={(item: any) => item?.title}
  404. oneWay
  405. style={{ marginBottom: 16 }}
  406. listStyle={{
  407. height: 'calc(100vh - 100px)',
  408. }}
  409. />
  410. </Drawer>
  411. </Space>
  412. </Col>
  413. </Row>
  414. </Col>
  415. {/**表格 */}
  416. <Col span={24}>
  417. <div className={style[size]}>
  418. <Tables
  419. columns={newColumns?.length > 0 ? newColumns : dateColumns()}
  420. dataSource={[{ ...statistical, id: 1 }]}
  421. pagination={false}
  422. hideOnSinglePage
  423. bordered
  424. size='small'
  425. scroll={isFell ? { y: document.body.clientHeight - 300, x: 1000 } : { y: 600, x: 1000 }}
  426. className='total_table'
  427. />
  428. <Tables
  429. bordered
  430. columns={newColumns?.length > 0 ? newColumns : dateColumns({ sortOrder })}
  431. dataSource={showZj ? [{ ...statistical, id: 1 }] : tableData || []}
  432. pageSize={pageSize}
  433. current={showZj ? 1 : page}
  434. // rowClassName={(record: { color: string }) => style[record['color']]}
  435. loading={getDaily?.loading}
  436. size={size}
  437. defaultPageSize={100}
  438. total={showZj ? 1 : total}
  439. scroll={isFell ? { y: document.body.clientHeight - 280, x: 1000 } : { y: 600, x: 1000 }}
  440. onChange={tableSubmut}
  441. className='all_table'
  442. // summary={() => {
  443. // let arr: any[] = []
  444. // if (targetKeys.length > 0) {
  445. // arr = targetKeys
  446. // } else {
  447. // dateColumns().forEach((item: any) => {
  448. // arr.push(item.dataIndex)
  449. // })
  450. // }
  451. // if (arr.length > 0 && JSON.stringify(statistical) !== '{}') {
  452. // return <Table.Summary.Row >
  453. // {
  454. // arr.map((key: string, index: number) => {
  455. // let str: any = ''
  456. // if (typeof statistical[key] === 'string') {
  457. // str = statistical[key]
  458. // }
  459. // if (typeof statistical[key] === 'number' && ((key as string)?.includes('roi') || (key as string)?.includes('rate'))) {
  460. // if (key === 'first_roi' && statistical[key] <= 8) {
  461. // let a = statistical[key] ? parseFloat((statistical[key] * 100).toFixed(2)) : 0
  462. // str = <span style={{ color: a <= 8 ? 'green' : '' }}>{a + '%'}</span>
  463. // } else {
  464. // str = statistical[key] ? `${parseFloat((statistical[key] * 100).toFixed(2))}%` : '0%'
  465. // }
  466. // } else if(typeof statistical[key] === 'number'){
  467. // str = <Statistic value={statistical[key] || 0} />
  468. // }
  469. // return <Table.Summary.Cell
  470. // index={index}
  471. // key={key}
  472. // align='center'
  473. // >{str}</Table.Summary.Cell>
  474. // })
  475. // }
  476. // </Table.Summary.Row>
  477. // }
  478. // return null
  479. // }}
  480. />
  481. {/* {
  482. tableValue === '2' && <Tables
  483. bordered
  484. columns={newColumns?.length > 0 ? newColumns : weChatColumns()}
  485. onChange={tableSubmut}
  486. dataSource={tableData || []}
  487. pageSize={pageSize}
  488. current={page}
  489. // rowClassName={(record: { color: string }) => style[record['color']]}
  490. loading={getDaily?.loading || getChanne?.loading}
  491. size={size}
  492. defaultPageSize={30}
  493. total={total}
  494. scroll={isFell ? { y: document.body.clientHeight - 200, x: 1000 } : { y: 600, x: 1000 }}
  495. />
  496. } */}
  497. </div>
  498. </Col>
  499. </Row>
  500. </Card>
  501. </Col>
  502. </Row>
  503. return <>
  504. <GroupWxTabs height='calc(100vh - 180px)' isUserSelect isFadminQTab>
  505. {content}
  506. </GroupWxTabs>
  507. </>
  508. }
  509. export default HocError(DataCenter)