Bläddra i källkod

refactor: 重构useAutoTable,更改逻辑计算方法,加入防抖处理

lanjianrong 5 år sedan
förälder
incheckning
1be30ced54

+ 34 - 0
src/hooks/event/useWindomSizeFn.ts

@@ -0,0 +1,34 @@
+import { useEffect } from 'react'
+import { useDebounceFn } from 'ahooks'
+
+interface WindowSizeOptions {
+  once?: boolean
+  immediate?: boolean
+  listenerOptions?: AddEventListenerOptions | boolean
+}
+
+export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
+  let handler = () => {
+    fn()
+  }
+  const handleSize = useDebounceFn(handler, wait)
+  handler = handleSize
+
+  const start = () => {
+    if (options && options.immediate) {
+      handler()
+    }
+    window.addEventListener('resize', handler)
+  }
+
+  const stop = () => {
+    window.removeEventListener('resize', handler)
+  }
+
+  useEffect(() => {
+    start()
+    return () => stop()
+  })
+
+  return [start, stop]
+}

+ 121 - 2
src/hooks/useAutoTable.js

@@ -1,8 +1,9 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useMemo, useLayoutEffect, useRef } from 'react'
 import { useDebounceFn } from 'ahooks'
 import { useStore } from 'dva'
+import { getViewportOffset } from '@/utils/domUtils'
+import { useWindowSizeFn } from './event/useWindomSizeFn'
 /**
- *
  * @param {number} needSubtractHeight 被裁去的高度
  * @param {number} needSubtractWidth 被裁去的宽度
  */
@@ -36,3 +37,121 @@ const useAutoTable = (needSubtractHeight, needSubtractWidth) => {
 }
 
 export default useAutoTable
+
+export function useTableScroll(columnsRef, selectKeysRef) {
+  let paginationEl = null
+  let footerEl = null
+  let bodyEl = null
+  const [tableHeight, setTableHeight] = useState(null)
+
+  // const toolbarEl = null
+  async function calcTableHeight() {
+    const tableEl = document.getElementsByClassName('ant-pro-table')[0]
+    if (!tableEl) return
+
+    if (!bodyEl) {
+      bodyEl = tableEl.querySelector('.ant-table-tbody')
+      bodyEl && (bodyEl.style.height = 'unset')
+    }
+
+    // Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
+
+    const headEl = tableEl.querySelector('.ant-table-thead ')
+
+    if (!headEl) return
+    // Table height from bottom
+    const { bottomIncludeBody } = getViewportOffset(headEl)
+
+    const paddingHeight = 32
+
+    // Pager height
+    let paginationHeight = 2
+
+    if (!paginationEl) {
+      paginationEl = tableEl.querySelector('.ant-pagination')
+      if (paginationEl) {
+        const { offsetHeight } = paginationEl
+        paginationHeight += offsetHeight || 0
+      } else {
+        // TODO First fix 24
+        paginationHeight += 24
+      }
+    } else {
+      paginationHeight = -8
+    }
+
+    let footerHeight = 0
+    if (!paginationEl) {
+      if (!footerEl) {
+        footerEl = tableEl.querySelector('.ant-table-footer')
+      } else {
+        const { offsetHeight } = footerEl
+        footerHeight += offsetHeight || 0
+      }
+    }
+
+    let headerHeight = 0
+    if (headEl) {
+      headerHeight = headEl.offsetHeight
+    }
+
+    const height =
+      bottomIncludeBody - paddingHeight - paginationHeight - footerHeight - headerHeight
+
+    console.log('bottomIncludeBody', bottomIncludeBody)
+    console.log('paddingHeight', paddingHeight)
+    console.log('paginationHeight', paginationHeight)
+    console.log('footerHeight', footerHeight)
+    console.log('headerHeight', headerHeight)
+    setTableHeight(height)
+    bodyEl && (bodyEl.style.height = `${height}px`)
+  }
+
+  function redoHeight() {
+    calcTableHeight()
+  }
+  const { run: debounceRedoHeight } = useDebounceFn(redoHeight, { wait: 100 })
+
+  // Greater than animation time 280
+  useWindowSizeFn(calcTableHeight, 280)
+
+  useLayoutEffect(() => {
+    debounceRedoHeight()
+  }, [])
+
+  useLayoutEffect(() => {
+    debounceRedoHeight()
+  }, [selectKeysRef.length])
+
+  const getSrollX = useMemo(() => {
+    let width = 0
+    if (selectKeysRef.length) {
+      width += 60
+    }
+
+    // TODO props ?? 0;
+    const NORMAL_WIDTH = 150
+
+    const columns = columnsRef.filter(item => !item.defaultHidden)
+    columns.forEach(item => {
+      width += Number.parseInt(item.width) || 0
+    })
+
+    const unsetWidthColumns = columns.filter(item => !Reflect.has(item, 'width'))
+
+    const len = unsetWidthColumns.length
+    if (len !== 0) {
+      width += len * NORMAL_WIDTH
+    }
+    const tableEl = document.getElementsByClassName('ant-pro-table')[0]
+
+    const tableWidth = tableEl && tableEl.offsetWidth ? tableEl.offsetWidth : 0
+
+    return tableWidth > width ? '100%' : width
+  }, [selectKeysRef])
+
+  const getScrollRef = useMemo(() => {
+    return { x: getSrollX, y: tableHeight || null }
+  }, [getSrollX, tableHeight])
+  return { getScrollRef, redoHeight }
+}

+ 5 - 0
src/pages/Customer/Contact/components/AlertOption/index.jsx

@@ -0,0 +1,5 @@
+const AlertOption = () => {
+  return <div />
+}
+
+export default AlertOption

+ 16 - 9
src/pages/Customer/Contact/index.jsx

@@ -5,7 +5,7 @@ import ProTable from '@ant-design/pro-table'
 import { message, Tag } from 'antd'
 import { connect } from 'dva'
 import React, { useEffect, useState } from 'react'
-import useAutoTable from '@/hooks/useAutoTable'
+import useAutoTable, { useTableScroll } from '@/hooks/useAutoTable'
 import consts from '@/consts'
 import { queryContact, apiAddClient } from '@/services/contact'
 import styles from './index.less'
@@ -21,6 +21,11 @@ const Contact = () => {
 
   const [x, y] = useAutoTable(needSubtractHeight, needSubtractWidth)
 
+  const [selectKeys, setSelectKeys] = useState([])
+  const hanleRowSelectChange = selectedRowKeys => {
+    setSelectKeys(selectedRowKeys)
+  }
+
   const [state, setState] = useState({
     data: [],
     journal: [],
@@ -75,7 +80,7 @@ const Contact = () => {
       title: '联系人',
       dataIndex: 'clientName',
       key: 'clientName',
-      width: 80,
+      width: 50,
       fixed: 'left',
       render: (clientName, record) => <a onClick={() => showDrawer(record.id)}>{clientName}</a>
     },
@@ -83,7 +88,7 @@ const Contact = () => {
       title: '客户名称',
       dataIndex: 'companyName',
       key: 'companyName',
-      width: 420,
+      width: 150,
       ellipsis: true,
       render: companyName => <a>{companyName}</a>
     },
@@ -328,10 +333,8 @@ const Contact = () => {
     }
   })
 
-  // useEffect(() => {
-  //   if (shouldUpdate) {
-  //     initData()
-  // }, [shouldUpdate])
+  const { getScrollRef } = useTableScroll(columns, selectKeys)
+  console.log(getScrollRef)
 
   // 默认刷新一次列表请求数据
 
@@ -353,13 +356,13 @@ const Contact = () => {
     const [province = '', city = '', area = ''] = value
     initData({ province, city, area })
   }
+
   return (
     <>
       <div className={styles.tableContent}>
         <ProTable
           rowKey={record => record.id}
           bordered
-          rowSelection={true}
           columns={columns}
           dataSource={state.data}
           summary={() => (
@@ -379,6 +382,10 @@ const Contact = () => {
           tableAlertRender={({ selectedRowKeys }) => {
             return <div>2222</div>
           }}
+          rowSelection={{
+            onChange: hanleRowSelectChange,
+            type: 'checkbox'
+          }}
           search={false}
           toolbar={{
             search: {
@@ -391,7 +398,7 @@ const Contact = () => {
               <Addcontact onConfirm={handleAddClient} key="addContactBtn" />
             ]
           }}
-          scroll={{ x, y }}
+          scroll={getScrollRef}
           columnsStateMap={columnsStateMap}
           onColumnsStateChange={map => setColumnsStateMap(map)}
           headerTitle="联系人"

+ 17 - 9
src/pages/Customer/Test/index.jsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useRef } from 'react'
+import React, { useEffect, useRef, useMemo, useState, useCallback } from 'react'
 import { Button, Card, Cascader, Dropdown, Input, Menu, Select } from 'antd'
 import EditableForm from '@/components/EditableForm'
 import { useModal } from '@/components/Modal'
@@ -10,6 +10,7 @@ const { Option } = Select
 const Test = ({ natures, dispatch }) => {
   const { toggleModal, setModalProps } = useModal()
 
+  const [state, setState] = useState(null)
   const iRef = useRef(null)
   const options = [
     {
@@ -85,9 +86,12 @@ const Test = ({ natures, dispatch }) => {
     !natures.length && initNatures()
   }, [])
 
-  const customOptions = originNode => {
-    return originNode
-  }
+  const a = useMemo(() => {
+    console.log('依赖项更新了')
+  }, [state])
+  // const customOptions = useCallback(originNode => {
+  //   return originNode
+  // }, [])
 
   const menu = (
     <Menu>
@@ -97,6 +101,11 @@ const Test = ({ natures, dispatch }) => {
     </Menu>
   )
 
+  // const measuredRef = useCallback(node => {
+  //   if (node) {
+  //     console.log(node.getBoundingClientRect)
+  //   }
+  // })
   return (
     <Card bordered={false}>
       {/* <EditableForm dataSource={record} columns={columns} /> */}
@@ -110,19 +119,18 @@ const Test = ({ natures, dispatch }) => {
       </div>
       <Button
         onClick={() => {
-          console.log(iRef)
-          iRef.current.focus()
-          iRef.current.selectRef.current.focus()
+          setState('123')
         }}>
         AAA
       </Button>
-      <Select
+
+      {/* <Select
         ref={iRef}
         onBlur={() => console.log(22)}
         // onChange={() => console.log(iRef)}
         options={options}
         style={{ width: '200px' }}
-      />
+      /> */}
     </Card>
   )
 }

+ 52 - 0
src/utils/domUtils.ts

@@ -0,0 +1,52 @@
+export function getBoundingClientRect(element: Element): DOMRect | number {
+  if (!element || !element.getBoundingClientRect) {
+    return 0
+  }
+  return element.getBoundingClientRect()
+}
+
+/**
+ * Get the left and top offset of the current element
+ * left: the distance between the leftmost element and the left side of the document
+ * top: the distance from the top of the element to the top of the document
+ * right: the distance from the far right of the element to the right of the document
+ * bottom: the distance from the bottom of the element to the bottom of the document
+ * rightIncludeBody: the distance between the leftmost element and the right side of the document
+ * bottomIncludeBody: the distance from the bottom of the element to the bottom of the document
+ *
+ * @description:
+ */
+export function getViewportOffset(element: Element): ViewportOffsetResult {
+  const doc = document.documentElement
+
+  const docScrollLeft = doc.scrollLeft
+  const docScrollTop = doc.scrollTop
+  const docClientLeft = doc.clientLeft
+  const docClientTop = doc.clientTop
+
+  const { pageXOffset } = window
+  const { pageYOffset } = window
+
+  const box = getBoundingClientRect(element)
+
+  const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect
+
+  const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)
+  const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)
+  const offsetLeft = retLeft + pageXOffset
+  const offsetTop = rectTop + pageYOffset
+
+  const left = offsetLeft - scrollLeft
+  const top = offsetTop - scrollTop
+
+  const { clientWidth } = window.document.documentElement
+  const { clientHeight } = window.document.documentElement
+  return {
+    left,
+    top,
+    right: clientWidth - rectWidth - left,
+    bottom: clientHeight - rectHeight - top,
+    rightIncludeBody: clientWidth - left,
+    bottomIncludeBody: clientHeight - top
+  }
+}