lanjianrong пре 5 година
родитељ
комит
2aeebca7ac

+ 6 - 6
config/routes.js

@@ -64,13 +64,13 @@ export default [
         component: './Customer/Business',
         access: 'authRouteFilter',
         validatePerm: true
+      },
+      {
+        path: '/customer/test',
+        name: 'test',
+        icon: 'icon-usd-circle',
+        component: './Customer/Test'
       }
-      // {
-      //   path: '/customer/test',
-      //   name: 'test',
-      //   icon: 'icon-usd-circle',
-      //   component: './Customer/Test'
-      // }
     ]
   },
   {

+ 3 - 0
src/components/Table/index.tsx

@@ -0,0 +1,3 @@
+import BasicTable from './src/BasicTable'
+
+export default BasicTable

+ 120 - 0
src/components/Table/src/BasicTable.tsx

@@ -0,0 +1,120 @@
+/* eslint-disable no-param-reassign */
+import { PERMISSION_SELECT_KEY } from '@/utils/cache/cacheEnum'
+import PermSelect from '@/pages/Customer/Company/components/PermSelect'
+import { TABLE_COLUMNS_MAP, ColumnStateEnum } from '@/utils/cache/cacheEnum'
+import { isNullOrUnDef } from '@/utils/is'
+import ProTable from '@ant-design/pro-table'
+import { useModel } from 'umi'
+import { getAuthCache, setAuthCache } from '@/utils/auth'
+import React, { useRef, useState, useLayoutEffect } from 'react'
+import { useMount } from 'ahooks'
+import { useTableScroll } from './hooks/useTableScroll'
+import type { BasicTableProps } from '@/types/typing'
+import type { ParamsType } from '@ant-design/pro-provider'
+
+// type BasicTableProps<T, U extends ParamsType> = {
+//   /** @type 枚举表中的菜单常量 */
+//   mainMenuType?: MainMenuKeyEnum
+//   /** @type 枚举表中的表格列常量 */
+//   columnStateType?: ColumnStateEnum
+//   /** @name 左上角的 title */
+//   headerTitle?: React.ReactNode
+//   params: ParamsType
+//   columns: ProColumns<T>[]
+//   /** @name 选择项配置 */
+//   columnsStateMap?: Record<string, ColumnsState>
+//   onColumnsStateChange?: (map: Record<string, ColumnsState>) => void
+//   actionRef?: React.MutableRefObject<ActionType | undefined> | ((actionRef: ActionType) => void)
+// } & Omit<
+//   ProTableProps<T, U>,
+//   'columns' | 'params' | 'actionRef' | 'headerTitle' | 'columnsStateMap' | 'onColumnsStateChange'
+// >
+
+const BasicTable: {
+  <T extends Record<string, any>, U extends ParamsType = ParamsType, ValueType = 'text'>(
+    props: BasicTableProps<T, U, ValueType>
+  ): JSX.Element
+  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
+  Summary: typeof import('rc-table/lib/Footer/Summary').default
+} = ({
+  headerTitle,
+  params,
+  mainMenuType,
+  columnStateType,
+  columnsStateMap,
+  columns,
+  onColumnsStateChange,
+  ...resetProps
+}) => {
+  const {
+    initialState: { permData }
+  } = useModel('@@initialState')
+  const permAuthCache = getAuthCache(PERMISSION_SELECT_KEY)
+  const tableElRef = useRef<HTMLElement>(null)
+  const { getScrollRef, redoHeight } = useTableScroll(tableElRef, columns)
+
+  const [state, setState] = useState({
+    columnsStateMap: {},
+    params: { staffIds: null, dataPermission: permAuthCache[mainMenuType] || null },
+    dataSource: []
+  })
+
+  useLayoutEffect(() => {
+    if (state.dataSource?.length) redoHeight(state.dataSource)
+  }, [state.dataSource])
+
+  useMount(() => {
+    // 处理本地化存储
+    const authColumnsStateMap = getAuthCache(TABLE_COLUMNS_MAP)
+    if (authColumnsStateMap && authColumnsStateMap[ColumnStateEnum[columnStateType]]) {
+      setState({
+        ...state,
+        columnsStateMap: authColumnsStateMap[ColumnStateEnum[columnStateType]]
+      })
+    }
+  })
+
+  // 如果数据权限不存在,就不渲染
+  if (isNullOrUnDef(permData)) return null
+
+  /** 增加头部 */
+  if (mainMenuType) {
+    resetProps.headerTitle = (
+      <>
+        <PermSelect
+          dataType={mainMenuType}
+          onConfirm={({ staffIds, dataPermission }) =>
+            setState({ ...state, params: { ...state.params, staffIds, dataPermission } })
+          }
+        />
+        <div className="ml-4">{headerTitle}</div>
+      </>
+    )
+  } else {
+    resetProps.headerTitle = headerTitle
+  }
+
+  const handleOnColumnsChange = (map: Record<string, ColumnsState>) => {
+    // const columnKey = Object.keys(map)
+
+    setState({ ...state, columnsStateMap: map })
+    setAuthCache(TABLE_COLUMNS_MAP, { [ColumnStateEnum[columnStateType]]: map })
+  }
+
+  return (
+    <div id="table-content" ref={tableElRef}>
+      <ProTable
+        {...resetProps}
+        columns={columns}
+        tableClassName="zh-table-content"
+        params={state.params}
+        columnsStateMap={state.columnsStateMap}
+        onColumnsStateChange={handleOnColumnsChange}
+        scroll={getScrollRef}
+        onLoad={dataSource => setState({ ...state, dataSource })}
+      />
+    </div>
+  )
+}
+
+export default BasicTable

+ 108 - 0
src/components/Table/src/hooks/useTableScroll.ts

@@ -0,0 +1,108 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions */
+import type { ProColumns } from '@ant-design/pro-table'
+import type { RefObject } from 'react'
+import { useState, useMemo } from 'react'
+import { useDebounceFn, useUpdateLayoutEffect } from 'ahooks'
+import { getViewportOffset } from '@/utils/domUtils'
+import { useWindowSizeFn } from '@/hooks/event/useWindomSizeFn'
+
+const MutationObserver =
+  window.MutationObserver || window.webkitMutationObserver || window.MozMutationObserver
+export function useTableScroll(
+  tableElRef: RefObject<HTMLElement>,
+  columnsRef: ProColumns<any> = [],
+  selectKeysRef: string[] = []
+) {
+  // let bodyEl: HTMLElement | null
+  const [tableHeight, setTableHeight] = useState(null)
+
+  function calcTableHeight(dataSource: any[]) {
+    const tableEl = tableElRef.current?.querySelector('.ant-table-container')
+    if (!tableEl) return
+
+    // if (!bodyEl) {
+    //   bodyEl = tableEl?.querySelector('.ant-table-tbody')
+    //   console.log('bodyEl', tableEl.children)
+
+    //   if (!bodyEl) return
+    // }
+    // 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 = 24
+
+    let paginationHeight = 0
+    if (dataSource && dataSource?.length) {
+      paginationHeight += 56
+    }
+
+    let headerHeight = 0
+    if (headEl) {
+      headerHeight = headEl.offsetHeight
+    }
+
+    const height = bottomIncludeBody - paddingHeight - paginationHeight - headerHeight
+
+    setTableHeight(height)
+    // bodyEl!.style.height = `${height}px`
+    const mutationObserver = new MutationObserver(() => {
+      const tbodyEl: HTMLElement =
+        tableElRef.current?.querySelector('.ant-table-container')?.children[1]
+      tbodyEl.style.height = `${height}px`
+    })
+    mutationObserver.observe(tableEl, {
+      childList: true, // 子节点的变动(新增、删除或者更改)
+      subtree: true // 是否将观察器应用于该节点的所有后代节点
+    })
+  }
+
+  function redoHeight(dataSource?: any[]) {
+    calcTableHeight(dataSource)
+  }
+  const { run: debounceRedoHeight } = useDebounceFn(redoHeight, { wait: 300 })
+
+  // Greater than animation time 280
+  useWindowSizeFn(calcTableHeight, 280)
+
+  useUpdateLayoutEffect(() => {
+    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 => {
+      // eslint-disable-next-line radix
+      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 = tableElRef.current
+
+    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 }
+}

+ 0 - 0
src/components/ZHtable/index.tsx


+ 0 - 67
src/components/ZHtable/src/BasicTable.tsx

@@ -1,67 +0,0 @@
-/* eslint-disable no-param-reassign */
-import type { MainMenuKeyEnum } from '@/utils/cache/cacheEnum'
-import type { ActionType, ColumnsState, ProTableProps } from '@ant-design/pro-table'
-import PermSelect from '@/pages/Customer/Company/components/PermSelect'
-import { TABLE_COLUMNS_MAP, ColumnStateEnum } from '@/utils/cache/cacheEnum'
-import { isNullOrUnDef } from '@/utils/is'
-import ProTable from '@ant-design/pro-table'
-import { useModel } from 'umi'
-import { getAuthCache, setAuthCache } from '@/utils/auth'
-
-interface BasicTableProps<T, C>
-  extends Omit<
-    ProTableProps,
-    'actionRef' | 'headerTitle' | 'columnsStateMap' | 'onColumnsStateChange'
-  > {
-  /** @type 枚举表中的菜单常量 */
-  mainMenuType?: T
-  /** @type 枚举表中的表格列常量 */
-  columnStateType?: C
-  /** @name 左上角的 title */
-  headerTitle?: React.ReactNode
-  columnsStateMap?: Record<string, ColumnsState>
-  onColumnsStateChange?: (map: Record<string, ColumnsState>) => void
-  actionRef?: React.MutableRefObject<ActionType | undefined> | ((actionRef: ActionType) => void)
-}
-
-const BasicTable: React.FC<BasicTableProps<MainMenuKeyEnum, ColumnStateEnum>> = ({
-  mainMenuType,
-  columnStateType,
-  columnsStateMap,
-  onColumnsStateChange,
-  ...resetProps
-}) => {
-  const {
-    initialState: { permData }
-  } = useModel('@@initialState')
-  if (isNullOrUnDef(permData)) return null
-
-  /** 增加头部 */
-  if (headerTitle && mainMenuType) {
-    resetProps.headerTitle = (
-      <>
-        <PermSelect />
-        {headerTitle}
-      </>
-    )
-  } else {
-    resetProps.headerTitle = headerTitle
-  }
-
-  // 处理列的本地化存储
-  const authColumnsStateMap = getAuthCache(TABLE_COLUMNS_MAP)
-  if (authColumnsStateMap && authColumnsStateMap[ColumnStateEnum[columnStateType]]) {
-    resetProps.columnsStateMap = authColumnsStateMap[ColumnStateEnum[columnStateType]]
-  } else {
-    resetProps.columnsStateMap = columnsStateMap
-  }
-
-  const handleOnColumnsChange = (map: Record<string, ColumnsState>) => {
-    onColumnsStateChange(map)
-    setAuthCache(TABLE_COLUMNS_MAP, { [ColumnStateEnum[columnStateType]]: map })
-  }
-
-  return <ProTable {...resetProps} onColumnsStateChange={handleOnColumnsChange} />
-}
-
-export default BasicTable

+ 4 - 8
src/pages/Customer/Company/components/PermSelect.tsx

@@ -5,7 +5,7 @@ import CustomPerm from './CustomPerm'
 import { getAuthCache, setAuthCache } from '@/utils/auth'
 import { PERMISSION_SELECT_KEY } from '@/utils/cache/cacheEnum'
 import type { SelectProps } from 'antd'
-import type { MAIN_MENU_KEY } from '@/utils/cache/cacheEnum'
+import type { MainMenuKeyEnum } from '@/utils/cache/cacheEnum'
 
 export const PermDataTypeEunm = {
   CUSTOMER: 'customer',
@@ -13,16 +13,12 @@ export const PermDataTypeEunm = {
   HR: 'hr',
   WORKBENCH: 'workbench'
 }
-interface PermSelectProps<T> extends SelectProps {
-  dataType: T
+interface PermSelectProps extends SelectProps {
+  dataType: MainMenuKeyEnum
   onConfirm: () => void
 }
 
-const PermSelect: React.FC<PermSelectProps<MAIN_MENU_KEY>> = ({
-  dataType,
-  onConfirm,
-  ...resetProps
-}) => {
+const PermSelect: React.FC<PermSelectProps> = ({ dataType, onConfirm, ...resetProps }) => {
   const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
   const { toggleModal, setModalProps } = useModal()
   let defaultValue = 'oneself'

+ 101 - 13
src/pages/Customer/Test/index.jsx

@@ -1,19 +1,107 @@
-import ModalDragForm from '@/components/Modal/src/components/ModalDragForm'
-import { Button } from 'antd'
+import BasicTable from '@/components/Table'
+import consts from '@/consts'
+import { queryCompany } from '@/services/customer'
+import { refactorSortField } from '@/utils/utils'
 import React from 'react'
 
 export default function Index() {
+  const columns = [
+    {
+      title: '客户名称',
+      dataIndex: 'companyName',
+      width: 150,
+      fixed: 'left',
+      sorter: true
+    },
+    {
+      title: '地区',
+      dataIndex: 'districtName',
+      width: 100
+    },
+    {
+      title: '客户电话',
+      dataIndex: 'phone',
+      width: 80
+    },
+    {
+      title: '联系人',
+      dataIndex: 'clientTotal',
+      width: 80
+    },
+    {
+      title: '客户性质',
+      dataIndex: 'nature',
+      width: 80
+    },
+    {
+      title: '客户地址',
+      dataIndex: 'address',
+      width: 80
+    },
+    {
+      title: '客户传真',
+      dataIndex: 'fax',
+      width: 80
+    },
+    {
+      title: '网址',
+      dataIndex: 'webservice',
+      search: false,
+      width: 80
+    },
+    {
+      title: '乘车路线',
+      dataIndex: 'ride',
+      search: false,
+      width: 80
+    },
+    {
+      title: '地标建筑',
+      dataIndex: 'landmarks',
+      search: false,
+      width: 80
+    },
+    {
+      title: '参考住宿',
+      dataIndex: 'stay',
+      search: false,
+      width: 80
+    },
+    {
+      title: '备注',
+      dataIndex: 'remarks',
+      search: false,
+      width: 80
+    },
+    {
+      title: '创建人',
+      dataIndex: 'staffName',
+      search: false,
+      width: 80
+    }
+  ]
   return (
-    <div>
-      <ModalDragForm
-        title="aaaaa"
-        trigger={
-          <Button type="primary" className="mr-1">
-            1111111
-          </Button>
-        }>
-        <div>111</div>
-      </ModalDragForm>
-    </div>
+    <BasicTable
+      columns={columns}
+      rowKey={row => row.id}
+      search={false}
+      headerTitle="客户列表"
+      mainMenuType="customer"
+      columnStateType="COMPANY"
+      rowSelection={{
+        type: 'checkbox',
+        columnWidth: 25
+      }}
+      request={async (params, sort, filter) => {
+        const srotOrder = refactorSortField(sort)
+        const { code = -1, data: { customer = [], total = 0 } = { customer: [], total: 0 } } =
+          await queryCompany({ ...params, ...srotOrder, ...filter })
+        return {
+          data: customer,
+          success: code === consts.RET_CODE.SUCCESS,
+          total
+        }
+      }}
+    />
   )
 }

+ 128 - 0
src/types/typing.d.ts

@@ -0,0 +1,128 @@
+import type { ParamsType } from '@ant-design/pro-provider'
+import type { MainMenuKeyEnum, ColumnStateEnum } from '@/utils/cache/cacheEnum'
+import type {
+  ActionType,
+  ColumnsState,
+  ListToolBarProps,
+  ProColumns,
+  RequestData
+} from '@ant-design/pro-table'
+import type { AlertRenderType } from '@ant-design/pro-table/lib/components/Alert'
+import type { SearchConfig } from '@ant-design/pro-table/lib/components/Form/FormRender'
+import type { OptionConfig, ToolBarProps } from '@ant-design/pro-table/lib/components/ToolBar'
+import type { DensitySize } from '@ant-design/pro-table/lib/components/ToolBar/DensityIcon'
+import type { ProSchemaComponentTypes } from '@ant-design/pro-utils'
+import type { SpinProps, TableProps, CardProps } from 'antd'
+import type { LabelTooltipType } from 'antd/lib/form/FormItemLabel'
+import type { SortOrder } from 'antd/lib/table/interface'
+import type { ProFieldEmptyText } from '@ant-design/pro-field'
+import type { Bordered } from '@ant-design/pro-table/lib/typing'
+
+export type BasicTableProps<T, U extends ParamsType, ValueType = 'text'> = {
+  /** @type 枚举表中的菜单常量 */
+  mainMenuType?: MainMenuKeyEnum
+  /** @type 枚举表中的表格列常量 */
+  columnStateType?: ColumnStateEnum
+  columns?: ProColumns<T, ValueType>[]
+  /** @name ListToolBar 的属性 */
+  toolbar?: ListToolBarProps
+  params?: U
+  columnsStateMap?: Record<string, ColumnsState>
+  onColumnsStateChange?: (map: Record<string, ColumnsState>) => void
+  onSizeChange?: (size: DensitySize) => void
+  /** @name table 外面卡片的设置 */
+  cardProps?: CardProps
+  /** @name 渲染 table */
+
+  /** @name 一个获得 dataSource 的方法 */
+  request?: (
+    params: U & {
+      pageSize?: number
+      current?: number
+      keyword?: string
+    },
+    sort: Record<string, SortOrder>,
+    filter: Record<string, React.ReactText[] | null>
+  ) => Promise<Partial<RequestData<T>>>
+  /** @name 对数据进行一些处理 */
+  postData?: (data: any[]) => any[]
+  /** @name 默认的数据 */
+  defaultData?: T[]
+  /** @name 初始化的参数,可以操作 table */
+  actionRef?: React.MutableRefObject<ActionType | undefined> | ((actionRef: ActionType) => void)
+  /** @name 渲染操作栏 */
+  toolBarRender?: ToolBarProps<T>['toolBarRender'] | false
+  /** @name 数据加载完成后触发 */
+  onLoad?: (dataSource: T[]) => void
+  /** @name loading 被修改时触发,一般是网络请求导致的 */
+  onLoadingChange?: (loading: boolean | SpinProps | undefined) => void
+  /** @name 数据加载失败时触发 */
+  onRequestError?: (e: Error) => void
+  /**
+   * 是否轮询 ProTable 它不会自动提交表单,如果你想自动提交表单的功能,需要在 onValueChange 中调用 formRef.current?.submit()
+   *
+   * @param dataSource 返回当前的表单数据,你可以用它判断要不要打开轮询
+   */
+  polling?: number | ((dataSource: T[]) => number)
+  /** @name 给封装的 table 的 className */
+  tableClassName?: string
+  /** @name 给封装的 table 的 style */
+  tableStyle?: CSSProperties
+  /** @name 左上角的 title */
+  headerTitle?: React.ReactNode
+  /** @name 标题旁边的 tooltip */
+  tooltip?: string | LabelTooltipType
+  /** @name 操作栏配置 */
+  options?: OptionConfig | false
+  /**
+   * @type SearchConfig
+   * @name 是否显示搜索表单
+   */
+  search?: false | SearchConfig
+  /**
+   * 暂时只支持 moment - string 会格式化为 YYYY-DD-MM - number 代表时间戳
+   *
+   * @name 如何格式化日期
+   */
+  dateFormatter?: 'string' | 'number' | false
+  /** @name 格式化搜索表单提交数据 */
+  beforeSearchSubmit?: (params: Partial<U>) => any
+  /**
+   * 设置或者返回false 即可关闭
+   *
+   * @name 自定义 table 的 alert
+   */
+  tableAlertRender?: AlertRenderType<T>
+  /**
+   * 设置或者返回false 即可关闭
+   *
+   * @name 自定义 table 的 alert 的操作
+   */
+  tableAlertOptionRender?: AlertRenderType<T>
+  /** @name 选择项配置 */
+  rowSelection?: TableProps<T>['rowSelection'] | false
+  style?: React.CSSProperties
+  /** 支持 ProTable 的类型 */
+  type?: ProSchemaComponentTypes
+  /** @name 提交表单时触发 */
+  onSubmit?: (params: U) => void
+  /** @name 重置表单时触发 */
+  onReset?: () => void
+  /** @name 空值时显示 */
+  columnEmptyText?: ProFieldEmptyText
+  /** @name 是否手动触发请求 */
+  manualRequest?: boolean
+  /** @name 查询表单和 Table 的卡片 border 配置 */
+  cardBordered?: Bordered
+  /** Debounce time */
+  debounceTime?: number
+} & Omit<TableProps<T>, 'columns' | 'rowSelection'>
+
+declare const ProviderWarp: {
+  <T extends Record<string, any>, U extends ParamsType = ParamsType, ValueType = 'text'>(
+    props: BasicTableProps<T, U, ValueType>
+  ): JSX.Element
+  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
+  Summary: typeof import('rc-table/lib/Footer/Summary').default
+}
+export default ProviderWarp

+ 5 - 5
src/utils/cache/cacheEnum.ts

@@ -31,15 +31,15 @@ export enum MainMenuKeyEnum {
 // single table columnsState enum
 export enum ColumnStateEnum {
   /** @name 联系人 */
-  CLIENT = 'client',
+  CLIENT = 'CLIENT',
   /** @name 客户 */
-  COMPANY = 'company',
+  COMPANY = 'COMPANY',
   /** @name 商机 */
-  BUSINESS = 'business',
+  BUSINESS = 'BUSINESS',
   /** @name 公共锁库 */
-  LOCKSTORE = 'lockstore',
+  LOCKSTORE = 'LOCKSTORE',
   /** @name 部门与员工 */
-  EMPLOYEE = 'employee'
+  EMPLOYEE = 'EMPLOYEE'
 }
 export enum CacheTypeEnum {
   SESSION,