index.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import CustomListModel from '@/components/CustomList'
  2. import Tables from '@/components/Tables'
  3. import { FullscreenExitOutlined, FullscreenOutlined, QuestionCircleOutlined, RedoOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons'
  4. import { useDebounceFn, useFullscreen, useThrottleFn, useUpdateEffect } from 'ahooks'
  5. import { Button, Card, Col, Row, Space, Tag, Tooltip, } from 'antd'
  6. import React, { useEffect, useRef, useState, useCallback } from 'react'
  7. import style from './index.less'
  8. import moment from 'moment'
  9. import lodash from 'lodash';
  10. import BaseTable from '@/components/BaseTable'
  11. import { getBaseTableColumn } from '@/components/BaseTable/utils'
  12. import IsVirtually from './isVirtually'
  13. interface Prosp {
  14. isZj?: boolean,//是否查总计
  15. tableTotal?: { [key: string]: string },//是个开启总计
  16. scroll?: { x?: number, y?: number },//开启行滑动并设置容器最大宽度
  17. title?: string,//tabel的标题
  18. tooltip?: JSX.Element,//是否在标题后加问号展示说明
  19. dataSource: any[],//table的数据
  20. expandedRowRender?: (data: any) => JSX.Element,
  21. className?: string,//自定义class
  22. isdownload?: boolean,
  23. leftChild?: JSX.Element,
  24. config?: any,
  25. configName?: any,
  26. page?: number,
  27. pageSize?: number,
  28. size?: 'small' | 'middle' | 'large',
  29. total?: number,
  30. onChange?: (props: {
  31. pagination?: { current?: number, pageSize?: number, gzh?: string },
  32. filters?: any,
  33. sortData?: {
  34. column: { dataIndex: string },
  35. order?: "ascend" | "descend"
  36. }
  37. }) => void,
  38. ajax?: any,//接口刷新
  39. fixed?: {
  40. left: number,
  41. right: number
  42. },
  43. totalData?: any[]
  44. summary?: ((data: readonly any[]) => React.ReactNode)
  45. rowClassName?: string | ((record: any, index: any) => string)
  46. sortData?: {
  47. field?: string,
  48. order?: "descend" | "ascend"
  49. }
  50. estimatedRowHeight?: number | (() => number)
  51. headerHeight?: number
  52. isVirtually?: boolean
  53. }
  54. export const version = '1.0.0'
  55. function TableData(props: Prosp) {
  56. /*************************/
  57. const { isZj, totalData, scroll, title, dataSource, expandedRowRender, isVirtually = true, sortData, headerHeight, className, rowClassName, leftChild, page = undefined, pageSize = undefined, size = 'small', total = 0, onChange, config, configName, ajax, fixed = { left: 0, right: 1 }, summary, estimatedRowHeight } = props
  58. const [visible, setVisible] = useState<boolean>(false)
  59. const [isFullscreen, setIsFullscreen] = useState<boolean>(true)
  60. const [oldSelectData, setoldSelectData] = useState<any[]>([])
  61. const [oldFixed, setoldFixed] = useState<any>({ left: '0', right: '0' })
  62. const [newColumns, setNewColumns] = useState<any[]>([])
  63. const [originalColumns, setOriginalColumns] = useState<any[]>([])
  64. const [timer, setTimer] = useState<string>(moment().format('HH:mm:ss'))
  65. const [selectData, setSelectData] = useState<{ selectData: any[], fixed: { left: number, right: number } }>({ selectData: [], fixed: { left: fixed.left, right: fixed.right } })
  66. const ref = useRef(null)
  67. const [isFull, { toggleFull }] = useFullscreen(ref);
  68. const oldName = useRef(null)
  69. /*************************/
  70. useEffect(() => {
  71. if (originalColumns.length > 0) {
  72. if (sortData?.field && sortData.order) {
  73. setNewColumns(originalColumns.map((item: any) => {
  74. if (item.dataIndex === sortData?.field) {
  75. item.sortOrder = sortData.order
  76. } else if (item?.sortOrder) {
  77. delete item.sortOrder
  78. }
  79. return item
  80. }))
  81. } else {
  82. setNewColumns(originalColumns.map((item: any) => {
  83. const { sortOrder, ...ite } = item
  84. return ite
  85. }))
  86. }
  87. }
  88. }, [originalColumns, sortData])
  89. const { run: runSet } = useThrottleFn((newArr, newConfig, fixedData) => {
  90. console.log('设置配置改变重新赋值')
  91. setoldSelectData(selectData.selectData)
  92. setoldFixed(selectData.fixed)
  93. if (newArr.length > 0) {
  94. console.log('改变---->')
  95. let c: any[] = []
  96. newArr.forEach((newItem: any, index: number) => {
  97. let oldItem = newConfig.find((c: { dataIndex: any }) => c.dataIndex === newItem.dataIndex)
  98. if (oldItem) {
  99. // if (newItem?.width !== oldItem?.width) {
  100. oldItem.width = newItem?.width
  101. // }
  102. if (index < Number(fixedData.left)) {//设置左悬浮
  103. oldItem['fixed'] = 'left'
  104. } else if (index > (newArr?.length - Number(fixedData.right) - 1)) {//设置右悬浮
  105. oldItem['fixed'] = 'right'
  106. if (oldItem?.children?.length > 0) {
  107. oldItem['children'] = oldItem?.children.map((item: { [x: string]: string }) => {
  108. item['fixed'] = 'right'
  109. return item
  110. })
  111. }
  112. } else {
  113. oldItem['fixed'] = false
  114. if (oldItem?.children?.length > 0) {
  115. oldItem['children'] = oldItem?.children.map((item: { [x: string]: any }) => {
  116. item['fixed'] = false
  117. return item
  118. })
  119. }
  120. }
  121. let { label, default: a, tips, title, ...ite } = oldItem
  122. if (tips) {
  123. ite.title = <Space size={2}>
  124. {title}
  125. <Tooltip title={tips} placement='bottom'>
  126. <QuestionCircleOutlined />
  127. </Tooltip>
  128. </Space>
  129. } else {
  130. ite.title = title
  131. }
  132. c.push(ite)
  133. }
  134. })
  135. setOriginalColumns(c)
  136. }
  137. }, { wait: 500 });
  138. /**重组选中的字段 */
  139. useEffect(() => {
  140. let oldConfigName = oldName.current || ''
  141. let oldSelectDataString = JSON.stringify(oldSelectData)
  142. if (configName !== oldConfigName && oldSelectData.length > 0) {
  143. oldSelectDataString = ''
  144. }
  145. if (configName) {
  146. const defSelectData = localStorage.getItem(`myAdMonitorConfig${version}_` + configName)
  147. const defFixed = localStorage.getItem(`myAdMonitorConfigFixed${version}_` + configName)
  148. let newArr: any = [], fixedData: { left: number, right: number } = selectData.fixed
  149. const newConfig = config?.map((item: { data: any }) => item.data)?.flat()
  150. if (defSelectData) {
  151. newArr = JSON.parse(defSelectData)
  152. }
  153. if (defSelectData && (selectData?.selectData?.length === 0 || configName !== oldConfigName)) {//首次查找个人配置是否存在,并且selectData为空,存在用个人配置设置selectData
  154. console.log('首次使用个人配置赋值')
  155. let newDefSelectData = JSON.parse(defSelectData)
  156. newDefSelectData = newDefSelectData.filter((item: any) => !!item && newConfig.some((c: { dataIndex: any }) => c.dataIndex === item.dataIndex))//去除空项
  157. setSelectData(() => ({ selectData: newDefSelectData, fixed: defFixed ? JSON.parse(defFixed) : { left: 0, right: 0 } }))
  158. fixedData = defFixed ? JSON.parse(defFixed) : { left: 0, right: 0 }
  159. newArr = newDefSelectData
  160. }
  161. if (!defSelectData && (selectData?.selectData?.length === 0 || configName !== oldConfigName)) {//首次查找个人配置是否存在,并且selectData为空,不存在默认配置设置selectData
  162. let newSelectData: any[] = []
  163. config?.forEach((item: { data: { default: any }[] }) => {
  164. item?.data?.forEach((d: { default: any }) => {
  165. if (d.default) {
  166. newSelectData[d.default - 1] = d
  167. }
  168. })
  169. })
  170. console.log('首次使用默认配置赋值')
  171. setSelectData(() => ({ ...selectData, selectData: newSelectData }))
  172. newArr = newSelectData
  173. }
  174. if ((JSON.stringify(oldSelectDataString) !== JSON.stringify(selectData?.selectData)) || (JSON.stringify(selectData.fixed) !== JSON.stringify(oldFixed))) {
  175. runSet(newArr, newConfig, fixedData)
  176. }
  177. }
  178. if (configName !== oldConfigName) {
  179. oldName.current = configName
  180. }
  181. }, [selectData, oldSelectData, dataSource, oldFixed, configName, config, timer])
  182. const { run: runResize } = useDebounceFn((columns, type: string) => {
  183. if (configName) {
  184. let newSelectData = selectData?.selectData?.map((item, index) => {
  185. if (type === 'vir') {
  186. if (item.dataIndex === columns.key) {
  187. item['width'] = Math.ceil(columns['width'])
  188. }
  189. } else {
  190. item['width'] = columns[index]['width']
  191. }
  192. return item
  193. })
  194. localStorage.setItem(`myAdMonitorConfig${version}_` + configName, JSON.stringify(newSelectData))
  195. }
  196. }, { wait: 100 });
  197. //拖动宽度设置设置保存
  198. const handelResize = useCallback((columns: any, type: string) => {
  199. runResize(columns, type)
  200. }, [configName, selectData])
  201. const header = <Col span={24}>
  202. <Row gutter={[0, 10]} align='bottom'>
  203. <Col flex='1 1 150px'>
  204. {isFullscreen && leftChild}
  205. </Col>
  206. <Col flex='0 1 150px'>
  207. {/*紧凑*/}
  208. <Space style={{ float: 'right', marginBottom: 4 }}>
  209. <Button size='small' type='text' style={{ color: '#F56C6C' }} onClick={() => { setIsFullscreen(!isFullscreen) }}>
  210. <Tooltip title={isFullscreen ? '隐藏搜索' : '显示搜索'}><SearchOutlined /></Tooltip>
  211. </Button>
  212. {ajax && <Button
  213. size='small'
  214. type='text'
  215. style={{ color: '#67C23A' }}
  216. onClick={() => {
  217. ajax.refresh()
  218. }}>
  219. {/* <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{ajax?.data?.reqTime}</span> */}
  220. <Tooltip title='刷新'><RedoOutlined /></Tooltip>
  221. </Button>}
  222. <Button
  223. size='small'
  224. type='text'
  225. style={{ color: '#E6A23C' }}
  226. onClick={() => {
  227. setVisible(true)
  228. }}>
  229. <Tooltip title='设置'><SettingOutlined /></Tooltip>
  230. </Button>
  231. <Button
  232. type='text'
  233. size='small'
  234. style={{ color: '#409EFF' }}
  235. onClick={toggleFull}>
  236. {<Tooltip title={!isFull ? '全屏' : '退出全屏'}>{!isFull ? <FullscreenOutlined /> : <FullscreenExitOutlined />}</Tooltip>}
  237. </Button>
  238. {visible && <CustomListModel
  239. sysFixed={fixed}
  240. version={version}
  241. config={config}
  242. configName={configName}
  243. visible={visible}
  244. onClose={() => { setVisible(false) }}
  245. onChange={(arr: any) => {
  246. setTimer(moment().format('HH:mm:ss'));
  247. if (arr) {
  248. setSelectData({ selectData: [], fixed: { left: fixed.left, right: fixed.right } })
  249. } else {
  250. setSelectData({ selectData: [], fixed: { left: fixed.left, right: fixed.right } })
  251. }
  252. }}
  253. columns={newColumns}
  254. />}
  255. </Space>
  256. </Col>
  257. </Row>
  258. </Col>
  259. const content = <Row gutter={[0, 20]} ref={ref} style={isFull ? { background: '#fff' } : {}}>
  260. {/**table */}
  261. <Col span={24}>
  262. <Card
  263. style={{ borderRadius: 8 }}
  264. // title={<div style={{ textAlign: 'center', fontWeight: 'bold' }}>{title}</div>}
  265. headStyle={{ textAlign: 'left' }}
  266. bodyStyle={{ padding: '5px 10px' }}
  267. >
  268. <div style={{ textAlign: 'center', fontWeight: 'bold', padding: '4px 6px 6px', fontSize: 16, marginBottom: 4, position: 'relative' }}>
  269. {title}
  270. {isVirtually && <IsVirtually title={title} />}
  271. </div>
  272. <Row gutter={[0, 16]}>
  273. {header}
  274. <Tab {...{ size, newColumns, handelResize, className, isZj, totalData, rowClassName, scroll, isFull, page, pageSize, dataSource, onChange, expandedRowRender, total, ajax, summary }} />
  275. </Row>
  276. </Card>
  277. </Col>
  278. </Row>
  279. const content1 = <Row gutter={[0, 20]} ref={ref} style={isFull ? { background: '#fff' } : {}}>
  280. {/**table */}
  281. <Col span={24}>
  282. <Card
  283. style={{ borderRadius: 8 }}
  284. headStyle={{ textAlign: 'left' }}
  285. bodyStyle={{ padding: '5px 10px' }}
  286. >
  287. <div style={{ textAlign: 'center', fontWeight: 'bold', padding: '4px 6px 6px', fontSize: 16, marginBottom: 4, position: 'relative' }}>
  288. {title}
  289. {isVirtually && <IsVirtually title={title} />}
  290. </div>
  291. <Row gutter={[0, 16]}>
  292. {header}
  293. <Tab1 {...{ size, newColumns, className, handelResize, headerHeight, page, pageSize, totalData, dataSource, onChange, total, ajax, estimatedRowHeight }} />
  294. </Row>
  295. </Card>
  296. </Col>
  297. </Row >
  298. const tableNames = localStorage.getItem('TABLENAMES')
  299. if (tableNames && title && tableNames.split(',').includes(title)) {
  300. return <>
  301. {content1}
  302. </>
  303. } else {
  304. return <>
  305. {content}
  306. </>
  307. }
  308. }
  309. /**表格 */
  310. const Tab = React.memo((props: any) => {
  311. const { size, newColumns, className, handelResize, scroll, isFull, page, isZj, rowClassName, pageSize, totalData, dataSource, onChange, expandedRowRender, total, ajax, summary } = props
  312. let ran = Math.ceil(Math.random() * 100)
  313. useEffect(() => {
  314. const contentBodyScroll = (e: any) => {
  315. let el = document.querySelector(`.header_table_body_${ran} .ant-table-body`);
  316. if (el) {
  317. el.scrollLeft = e.target.scrollLeft
  318. }
  319. }
  320. const headerBodyScroll = (e: any) => {
  321. let el = document.querySelector(`.content_table_body_${ran} .ant-table-body`);
  322. if (el) {
  323. el.scrollLeft = e.target.scrollLeft
  324. }
  325. }
  326. if (isZj) {
  327. document.querySelector(`.content_table_body_${ran} .ant-table-body`)?.addEventListener('scroll', contentBodyScroll);
  328. document.querySelector(`.header_table_body_${ran} .ant-table-body`)?.addEventListener('scroll', headerBodyScroll);
  329. }
  330. () => {
  331. if (isZj) {
  332. document.querySelector(`.content_table_body_${ran} .ant-table-body`)?.removeEventListener('scroll', contentBodyScroll);
  333. document.querySelector(`.header_table_body_${ran} .ant-table-body`)?.removeEventListener('scroll', headerBodyScroll);
  334. }
  335. }
  336. }, [isZj, ran])
  337. return < Col span={24} >
  338. <div className={`${style[size]} ${className ? style[className] : ''} `}>
  339. {
  340. isZj && <Tables
  341. bordered
  342. columns={newColumns}
  343. dataSource={totalData?.length > 0 ? [...totalData] : [{ id: 1 }]}
  344. scroll={scroll ? isFull ? { ...scroll, y: document.body.clientHeight - 300 } : scroll : {}}
  345. size={size}
  346. pagination={false}
  347. hideOnSinglePage
  348. current={page}
  349. pageSize={pageSize}
  350. loading={ajax?.loading}
  351. className={`all_table header_table_body header_table_body_${ran} ${className ? className : ''}`}
  352. sortDirections={['ascend', 'descend', null]}
  353. handelResize={((columns: any) => handelResize(columns, 'antd'))}
  354. onChange={(pagination: any, filters: any, sorter: any) => onChange && onChange({ pagination, filters, sortData: sorter })}
  355. />
  356. }
  357. <Tables
  358. showHeader={!isZj}
  359. className={`all_table content_table_body content_table_body_${ran} ${className ? className : ''}`}
  360. bordered
  361. sortDirections={['ascend', 'descend', null]}
  362. current={page}
  363. pageSize={pageSize}
  364. columns={newColumns}
  365. dataSource={dataSource}
  366. scroll={scroll ? isFull ? { ...scroll, y: document.body.clientHeight - 300 } : scroll : {}}
  367. onChange={(pagination: any, filters: any, sorter: any) => onChange && onChange({ pagination, filters, sortData: sorter })}
  368. rowClassName={rowClassName}
  369. expandedRowRender={expandedRowRender ? expandedRowRender : undefined}
  370. size={size}
  371. total={total}
  372. loading={ajax?.loading}
  373. defaultPageSize={20}
  374. handelResize={((columns: any) => handelResize(columns, 'antd'))}
  375. summary={summary}
  376. />
  377. </div>
  378. </Col >
  379. })
  380. const Tab1 = React.memo((props: any) => {
  381. const { size, newColumns, className, handelResize, page, pageSize, headerHeight = 65, totalData, dataSource, onChange, total, ajax, estimatedRowHeight } = props
  382. let ww = document.body.clientWidth < 415
  383. return < Col span={24} >
  384. <div className={`${style[size]} ${className ? style[className] : ''} `}>
  385. <BaseTable
  386. columns={getBaseTableColumn(newColumns)}
  387. data={dataSource}
  388. loading={ajax?.loading}
  389. onColumnResizeEnd={({ column }: any) => handelResize(column, 'vir')}
  390. onChange={(data) => onChange?.(data)}
  391. estimatedRowHeight={estimatedRowHeight}
  392. frozenData={totalData}
  393. headerHeight={headerHeight}
  394. pagination={
  395. {
  396. size: 'small',
  397. total: total || 0,//总共多少条数据,服务器给,设置后分页自动计算页数
  398. current: page,//当前页数,需state控制配合翻页
  399. pageSize: pageSize,
  400. defaultCurrent: 1,//默认初始的当前页数
  401. defaultPageSize: 20,//默认初始的每页条数
  402. pageSizeOptions: ['10', '20', '30', '40', '50', '60', '70', '80', '90', '100'],
  403. showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
  404. showSizeChanger: true, //手动开启条数筛选器,默认超过50条开启
  405. simple: ww ? true : false,//开启简单分页
  406. hideOnSinglePage: false,//只有一页数据隐藏分页
  407. showLessItems: true
  408. }
  409. }
  410. />
  411. </div>
  412. </Col >
  413. })
  414. export default React.memo(TableData, (prevProps, nextProps) => {
  415. return lodash.isEqual(nextProps, prevProps); // 假设 deepEqual 是一个深层比较函数
  416. })