Преглед изворни кода

feat(table): add table component

陈文彬 пре 4 година
родитељ
комит
faf3f4602e
71 измењених фајлова са 3937 додато и 191 уклоњено
  1. 1 1
      .github/release-drafter.yml
  2. 8 18
      README.md
  3. 2 1
      mock/_createProductionServer.ts
  4. 10 3
      mock/_util.ts
  5. 30 0
      mock/demo/table-demo.ts
  6. 2 8
      src/App.vue
  7. 20 0
      src/api/demo/model/tableModel.ts
  8. 17 0
      src/api/demo/table.ts
  9. 9 0
      src/api/model/baseModel.ts
  10. BIN
      src/assets/images/lock-page.png
  11. BIN
      src/assets/images/page_null.png
  12. 0 1
      src/components/Basic/index.ts
  13. 7 2
      src/components/Basic/src/BasicArrow.vue
  14. 0 28
      src/components/Basic/src/BasicEmpty.vue
  15. 2 2
      src/components/Button/index.vue
  16. 1 1
      src/components/Container/src/collapse/CollapseContainer.vue
  17. 2 2
      src/components/Form/src/BasicForm.vue
  18. 13 0
      src/components/Table/index.ts
  19. 285 0
      src/components/Table/src/BasicTable.vue
  20. 26 0
      src/components/Table/src/componentMap.ts
  21. 72 0
      src/components/Table/src/components/CellResize.tsx
  22. 21 0
      src/components/Table/src/components/EditTableHeaderIcon.vue
  23. 144 0
      src/components/Table/src/components/TableAction.tsx
  24. 36 0
      src/components/Table/src/components/TableImg.vue
  25. 41 0
      src/components/Table/src/components/TableTitle.vue
  26. 153 0
      src/components/Table/src/components/renderEditableCell.tsx
  27. 15 0
      src/components/Table/src/components/renderExpandIcon.tsx
  28. 64 0
      src/components/Table/src/components/renderFooter.tsx
  29. 14 0
      src/components/Table/src/components/renderTitle.tsx
  30. 12 0
      src/components/Table/src/const.ts
  31. 123 0
      src/components/Table/src/hooks/useColumns.ts
  32. 148 0
      src/components/Table/src/hooks/useDataSource.ts
  33. 15 0
      src/components/Table/src/hooks/useLoading.ts
  34. 53 0
      src/components/Table/src/hooks/usePagination.tsx
  35. 29 0
      src/components/Table/src/hooks/useProps.ts
  36. 12 0
      src/components/Table/src/hooks/useProvinceTable.ts
  37. 63 0
      src/components/Table/src/hooks/useRowSelection.ts
  38. 88 0
      src/components/Table/src/hooks/useTable.ts
  39. 134 0
      src/components/Table/src/hooks/useTableScroll.ts
  40. 155 0
      src/components/Table/src/props.ts
  41. 41 0
      src/components/Table/src/style/editable-cell.less
  42. 228 0
      src/components/Table/src/style/index.less
  43. 8 0
      src/components/Table/src/types/componentType.ts
  44. 89 0
      src/components/Table/src/types/pagination.ts
  45. 315 0
      src/components/Table/src/types/table.ts
  46. 19 0
      src/components/Table/src/types/tableAction.ts
  47. 75 77
      src/design/ant/pagination.less
  48. 7 1
      src/layouts/default/LayoutContent.tsx
  49. 25 31
      src/layouts/page/index.tsx
  50. 3 3
      src/router/guard/progressGuard.ts
  51. 62 0
      src/router/menus/modules/demo/comp.ts
  52. 122 0
      src/router/routes/modules/demo/comp.ts
  53. 6 3
      src/store/index.ts
  54. 1 0
      src/types/source.d.ts
  55. 0 7
      src/useApp.tsx
  56. 2 2
      src/views/demo/comp/verify/index.vue
  57. 70 0
      src/views/demo/table/Basic.vue
  58. 70 0
      src/views/demo/table/CustomerCell.vue
  59. 60 0
      src/views/demo/table/EditCellTable.vue
  60. 34 0
      src/views/demo/table/ExpandTable.vue
  61. 44 0
      src/views/demo/table/FetchTable.vue
  62. 93 0
      src/views/demo/table/FixedColumn.vue
  63. 40 0
      src/views/demo/table/FixedHeight.vue
  64. 50 0
      src/views/demo/table/FooterTable.vue
  65. 27 0
      src/views/demo/table/FormTable.vue
  66. 27 0
      src/views/demo/table/MergeHeader.vue
  67. 26 0
      src/views/demo/table/MultipleHeader.vue
  68. 119 0
      src/views/demo/table/RefTable.vue
  69. 29 0
      src/views/demo/table/TreeTable.vue
  70. 126 0
      src/views/demo/table/UseTable.vue
  71. 292 0
      src/views/demo/table/tableData.tsx

+ 1 - 1
.github/release-drafter.yml

@@ -30,5 +30,5 @@ categories:
       - 'workflow'
 change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
 template: |
-  # Changes
+  # What's Changed
   $CHANGES

+ 8 - 18
README.md

@@ -7,20 +7,18 @@
 
 **中文**
 
-该分支为2.0新分支,使用vue3进行开发。
+该分支为 2.0 新分支,使用 vue3 进行开发。
 
-1.0分支请切换到`master`分支。1.0采用`vue2.6`+`vue-composition-api`+`vue-cli`开发
+1.0 分支请切换到`master`分支。1.0 采用`vue2.6`+`vue-composition-api`+`vue-cli`开发
 
 一个适合开发中大型项目的基础框架,需要对`vue`,`typescript`有一定的了解,也可以作为了解新写法的一个例子来看,提前适应后续新版本的开发方式
 
 项目基于`ant-design-vue`,`typescript`,`vue3.0`,`vite`,`tailwindcss`,`tsx`实现的 vue3 风格的后台管理系统,
 
-
 ### gitHub 地址
 
 [vue-vben-admin2.0](https://github.com/anncwb/vue-vben-admin)
 
-
 <p align="center">
     <img alt="VbenAdmin Logo" width="100%" src="./build/docs/imgs/preview1.png">
     <img alt="VbenAdmin Logo" width="100%" src="./build/docs/imgs/preview2.png">
@@ -29,11 +27,7 @@
 
 ### 文档
 
-2.0文档还没开始写。后续补上。。
-
-
-
-
+2.0 文档还没开始写。后续补上。。
 
 ## 使用到的技术
 
@@ -84,7 +78,6 @@ VSCode 插件
 - `stylelint`: 样式代码检查
 - `Prettier - Code formatter`:代码格式化
 
-
 ## 安装
 
 ```js
@@ -122,7 +115,6 @@ yarn build:no-cache # 打包 不会使用hardSource进行打包
 yarn report # 生成构建包表表预览
 ```
 
-
 ### 格式化
 
 ```bash
@@ -159,7 +151,6 @@ yarn log # 生成CHANGELOG
   - `mod` 不确定分类的修改
   - `wip` 删除文件
 
-
 ## 代码贡献
 
 1. Fork 代码!
@@ -205,20 +196,19 @@ yarn log # 生成CHANGELOG
 - [x] 树组件
 - [x] 系统性能优化
 - [x] 兼容最新`vuex`,`vue-router`
-- [] 图片预览组件
+- [x] 图片预览组件
+- [ ] 表格组件
+- [ ] 可编辑表格
 - [ ] 图表库
 - [ ] 数字动画
 - [ ] 主题配置
-- [ ] 表格组件
 - [ ] 富文本组件
 - [ ] 首屏加载等待动画
 - [ ] 上传组件
-- [ ] 可编辑表格
 - [ ] 数据导入导出
-- [ ] 搭建`vite`版本
-- [ ] 懒加载组件
 - [ ] 黑暗主题
-- [ ] 更多组件/功能/建议/bug/欢迎提交 pr 或者 issue
+
+更多组件/功能/建议/bug/欢迎提交 pr 或者 issue
 
 ## 加入我们
 

+ 2 - 1
mock/_createProductionServer.ts

@@ -1,7 +1,8 @@
 import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
 import userMock from './sys/user';
 import menuMock from './sys/menu';
+import tableDemoMock from './demo/table-demo';
 
 export function setupProdMockServer() {
-  createProdMockServer([...userMock, ...menuMock]);
+  createProdMockServer([...userMock, ...menuMock, ...tableDemoMock]);
 }

+ 10 - 3
mock/_util.ts

@@ -9,12 +9,19 @@ export function resultSuccess<T = any>(result: T, { message = 'ok' } = {}) {
   };
 }
 
-export function resultPageSuccess<T = any>(items: T[], total: number, { message = 'ok' } = {}) {
+export function resultPageSuccess<T = any>(
+  page: number,
+  pageSize: number,
+  list: T[],
+  { message = 'ok' } = {}
+) {
+  const pageData = pagination(page, pageSize, list);
+
   return {
     code: 0,
     result: {
-      items,
-      total,
+      items: pageData,
+      total: list.length,
     },
     message,
     type: 'success',

+ 30 - 0
mock/demo/table-demo.ts

@@ -0,0 +1,30 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { resultPageSuccess } from '../_util';
+
+const demoList = (() => {
+  const result: any[] = [];
+  for (let index = 0; index < 60; index++) {
+    result.push({
+      id: `${index}`,
+      beginTime: '@datetime',
+      endTime: '@datetime',
+      address: '@city()',
+      name: '@cname()',
+      'no|100000-10000000': 100000,
+      'status|1': ['正常', '启用', '停用'],
+    });
+  }
+  return result;
+})();
+
+export default [
+  {
+    url: '/api/table/getDemoList',
+    timeout: 1000,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, demoList);
+    },
+  },
+] as MockMethod[];

+ 2 - 8
src/App.vue

@@ -1,10 +1,5 @@
 <template>
-  <ConfigProvider
-    :locale="zhCN"
-    :renderEmpty="renderEmpty"
-    :transformCellText="transformCellText"
-    v-bind="lockOn"
-  >
+  <ConfigProvider :locale="zhCN" :transformCellText="transformCellText" v-bind="lockOn">
     <router-view />
   </ConfigProvider>
 </template>
@@ -28,10 +23,9 @@
       useInitAppConfigStore();
       useListenerNetWork();
       createBreakpointListen();
-      const { renderEmpty, transformCellText } = useConfigProvider();
+      const { transformCellText } = useConfigProvider();
       const { on: lockOn } = useLockPage();
       return {
-        renderEmpty,
         transformCellText,
         zhCN,
         lockOn,

+ 20 - 0
src/api/demo/model/tableModel.ts

@@ -0,0 +1,20 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: 请求列表接口参数
+ */
+export type DemoParams = BasicPageParams;
+
+export interface DemoListItem {
+  id: string;
+  beginTime: string;
+  endTime: string;
+  address: string;
+  name: string;
+  no: number;
+  status: number;
+}
+
+/**
+ * @description: 请求列表返回值
+ */
+export type DemoListGetResultModel = BasicFetchResult<DemoListItem>;

+ 17 - 0
src/api/demo/table.ts

@@ -0,0 +1,17 @@
+import { defHttp } from '/@/utils/http/axios';
+import { DemoParams, DemoListGetResultModel } from './model/tableModel';
+
+enum Api {
+  DEMO_LIST = '/table/getDemoList',
+}
+
+/**
+ * @description: 获取示例列表值
+ */
+export function demoListApi(params: DemoParams) {
+  return defHttp.request<DemoListGetResultModel>({
+    url: Api.DEMO_LIST,
+    method: 'GET',
+    params,
+  });
+}

+ 9 - 0
src/api/model/baseModel.ts

@@ -0,0 +1,9 @@
+export interface BasicPageParams {
+  page: number;
+  pageSize: number;
+}
+
+export interface BasicFetchResult<T extends any> {
+  items: T;
+  total: number;
+}

BIN
src/assets/images/lock-page.png


BIN
src/assets/images/page_null.png


+ 0 - 1
src/components/Basic/index.ts

@@ -1,4 +1,3 @@
 export { default as BasicArrow } from './src/BasicArrow.vue';
 export { default as BasicHelp } from './src/BasicHelp';
 export { default as BasicTitle } from './src/BasicTitle.vue';
-export { default as BasicEmpty } from './src/BasicEmpty.vue';

+ 7 - 2
src/components/Basic/src/BasicArrow.vue

@@ -43,11 +43,16 @@
 
     &.right {
       transform: rotate(0deg);
+
+      > span {
+        transition: all 0.3s ease 0.1s !important;
+      }
     }
 
     &__active {
-      transform: rotate(90deg) !important;
-      transition: all 0.3s ease 0.1s !important;
+      > span {
+        transform: rotate(90deg) !important;
+      }
     }
   }
 </style>

+ 0 - 28
src/components/Basic/src/BasicEmpty.vue

@@ -1,28 +0,0 @@
-<template>
-  <Empty :image="image" :description="description" />
-</template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
-  import { Empty } from 'ant-design-vue';
-
-  import emptySrc from '/@/assets/images/page_null.png';
-
-  export default defineComponent({
-    extends: Empty as any,
-    components: { Empty },
-    props: {
-      description: {
-        type: String,
-        default: '暂无内容',
-      },
-      image: {
-        type: String,
-        default: emptySrc,
-        required: false,
-      },
-    },
-    setup() {
-      return {};
-    },
-  });
-</script>

+ 2 - 2
src/components/Button/index.vue

@@ -1,7 +1,7 @@
 <template>
   <Button v-bind="getBindValue" :class="[getColor, $attrs.class]">
-    <template v-slot:[item] v-for="item in Object.keys($slots)">
-      <slot :name="item" />
+    <template #[item]="data" v-for="item in Object.keys($slots)">
+      <slot :name="item" v-bind="data" />
     </template>
   </Button>
 </template>

+ 1 - 1
src/components/Container/src/collapse/CollapseContainer.vue

@@ -6,7 +6,7 @@
       <div class="collapse-container__body" v-else v-show="show">
         <LazyContainer :timeout="lazyTime" v-if="lazy">
           <slot />
-          <template v-slot:skeleton>
+          <template #skeleton>
             <slot name="lazySkeleton" />
           </template>
         </LazyContainer>

+ 2 - 2
src/components/Form/src/BasicForm.vue

@@ -9,8 +9,8 @@
           :allDefaultValues="getAllDefaultValues"
           :formModel="formModel"
         >
-          <template v-slot:[item] v-for="item in Object.keys($slots)">
-            <slot :name="item" />
+          <template #[item]="data" v-for="item in Object.keys($slots)">
+            <slot :name="item" v-bind="data" />
           </template>
         </FormItem>
       </template>

+ 13 - 0
src/components/Table/index.ts

@@ -0,0 +1,13 @@
+export { default as BasicTable } from './src/BasicTable.vue';
+export { default as TableAction } from './src/components/TableAction';
+export { default as TableImg } from './src/components/TableImg.vue';
+export { renderEditableCell } from './src/components/renderEditableCell';
+export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
+
+export * from './src/types/table';
+export * from './src/types/pagination';
+export * from './src/types/tableAction';
+
+export { useTable } from './src/hooks/useTable';
+
+export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';

+ 285 - 0
src/components/Table/src/BasicTable.vue

@@ -0,0 +1,285 @@
+<template>
+  <div
+    class="basic-table"
+    :class="{
+      'table-form-container': getBindValues.useSearchForm,
+    }"
+  >
+    <BasicForm
+      v-bind="getFormProps"
+      v-if="getBindValues.useSearchForm"
+      :submitButtonOptions="{ loading }"
+      @register="registerForm"
+      @submit="handleSearchInfoChange"
+      @advanced-change="redoHeight"
+    >
+      <template #[item]="data" v-for="item in Object.keys($slots)">
+        <slot :name="`form-${item}`" v-bind="data" />
+      </template>
+    </BasicForm>
+    <Table
+      ref="tableElRef"
+      v-bind="getBindValues"
+      :rowClassName="getRowClassName"
+      :class="{
+        hidden: !getEmptyDataIsShowTable,
+      }"
+      @change="handleTableChange"
+    >
+      <template #[item]="data" v-for="item in Object.keys($slots)">
+        <slot :name="item" v-bind="data" />
+      </template>
+    </Table>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed, unref, watch, nextTick } from 'vue';
+  import { Table } from 'ant-design-vue';
+  import { basicProps } from './props';
+  import type {
+    BasicTableProps,
+    FetchParams,
+    GetColumnsParams,
+    TableActionType,
+  } from './types/table';
+  import { isFunction, isString } from '/@/utils/is';
+
+  import renderTitle from './components/renderTitle';
+  import renderFooter from './components/renderFooter';
+  import renderExpandIcon from './components/renderExpandIcon';
+
+  import { usePagination } from './hooks/usePagination';
+  import { useColumns } from './hooks/useColumns';
+  import { useDataSource } from './hooks/useDataSource';
+  import { useLoading } from './hooks/useLoading';
+  import { useRowSelection } from './hooks/useRowSelection';
+  import { useTableScroll } from './hooks/useTableScroll';
+  import { provideTable } from './hooks/useProvinceTable';
+  import { BasicForm, FormProps, useForm } from '/@/components/Form/index';
+  import { omit } from 'lodash-es';
+  import './style/index.less';
+  import { ROW_KEY } from './const';
+  import { PaginationProps } from './types/pagination';
+  import { deepMerge } from '/@/utils';
+  import { TableCustomRecord } from 'ant-design-vue/types/table/table';
+  import { useEvent } from '/@/hooks/event/useEvent';
+  export default defineComponent({
+    props: basicProps,
+    components: { Table, BasicForm },
+    emits: ['fetch-success', 'fetch-error', 'selection-change', 'register'],
+    setup(props, { attrs, emit, slots }) {
+      const tableElRef = ref<any>(null);
+      const innerPropsRef = ref<Partial<BasicTableProps>>();
+      const [registerForm, { getFieldsValue }] = useForm();
+
+      const getMergeProps = computed(
+        (): BasicTableProps => {
+          return {
+            ...props,
+            ...unref(innerPropsRef),
+          } as BasicTableProps;
+        }
+      );
+      const { loadingRef } = useLoading(getMergeProps);
+      const { getPaginationRef, setPagination } = usePagination(getMergeProps);
+      const { getColumnsRef, setColumns } = useColumns(getMergeProps, getPaginationRef);
+      const { getDataSourceRef, setTableData, fetch, getAutoCreateKey } = useDataSource(
+        getMergeProps,
+        {
+          getPaginationRef,
+          loadingRef,
+          setPagination,
+          getFieldsValue,
+        },
+        emit
+      );
+      const { getScrollRef, redoHeight } = useTableScroll(getMergeProps, tableElRef);
+      const {
+        getRowSelectionRef,
+        getSelectRows,
+        clearSelectedRowKeys,
+        getSelectRowKeys,
+        deleteSelectRowByKey,
+        setSelectedRowKeys,
+      } = useRowSelection(getMergeProps, emit);
+
+      const getRowKey = computed(() => {
+        const { rowKey } = unref(getMergeProps);
+
+        return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
+      });
+      const getBindValues = computed(() => {
+        const { title, titleHelpMessage, showSummary } = unref(getMergeProps);
+        const titleData: any =
+          !slots.tableTitle && !isString(title) && !title && !slots.toolbar
+            ? {}
+            : {
+                title:
+                  !slots.tableTitle && !title && !slots.toolbar
+                    ? null
+                    : renderTitle.bind(null, title, titleHelpMessage, slots),
+              };
+        const pagination = unref(getPaginationRef);
+        const rowSelection = unref(getRowSelectionRef);
+        const scroll = unref(getScrollRef);
+        const loading = unref(loadingRef);
+        const rowKey = unref(getRowKey);
+        const columns = unref(getColumnsRef);
+        const dataSource = unref(getDataSourceRef);
+        let propsData = {
+          size: 'middle',
+          ...(slots.expandedRowRender ? { expandIcon: renderExpandIcon() } : {}),
+          ...attrs,
+          ...unref(getMergeProps),
+          ...titleData,
+          scroll,
+          loading,
+          tableLayout: 'fixed',
+          rowSelection,
+          rowKey,
+          columns,
+          pagination,
+          dataSource,
+        };
+        if (slots.expandedRowRender) {
+          propsData = omit(propsData, 'scroll');
+        }
+        if (showSummary) {
+          propsData.footer = renderFooter.bind(null, {
+            scroll,
+            columnsRef: getColumnsRef,
+            summaryFunc: unref(getMergeProps).summaryFunc,
+            dataSourceRef: getDataSourceRef,
+            rowSelectionRef: getRowSelectionRef,
+          });
+        }
+        return propsData;
+      });
+      const getFormProps = computed(() => {
+        const { formConfig } = unref(getBindValues);
+        const formProps: FormProps = {
+          showAdvancedButton: true,
+          ...(formConfig as FormProps),
+          compact: true,
+        };
+        return formProps;
+      });
+
+      const getEmptyDataIsShowTable = computed(() => {
+        const { emptyDataIsShowTable, useSearchForm } = unref(getMergeProps);
+        if (emptyDataIsShowTable || !useSearchForm) {
+          return true;
+        }
+        return !!unref(getDataSourceRef).length;
+      });
+
+      function getRowClassName(record: TableCustomRecord<any>, index: number) {
+        const { striped, rowClassName } = unref(getMergeProps);
+        if (!striped) return;
+        if (rowClassName && isFunction(rowClassName)) {
+          return rowClassName(record);
+        }
+        return (index || 0) % 2 === 1 ? 'basic-table-row__striped' : '';
+      }
+
+      function handleSearchInfoChange(info: any) {
+        const { handleSearchInfoFn } = unref(getMergeProps);
+        if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
+          info = handleSearchInfoFn(info) || info;
+        }
+        fetch({ searchInfo: info, page: 1 });
+      }
+
+      function handleTableChange(pagination: PaginationProps) {
+        const { clearSelectOnPageChange } = unref(getMergeProps);
+        if (clearSelectOnPageChange) {
+          clearSelectedRowKeys();
+        }
+        setPagination(pagination);
+        fetch();
+      }
+      watch(
+        () => unref(getDataSourceRef),
+        () => {
+          if (unref(getMergeProps).showSummary) {
+            nextTick(() => {
+              const tableEl = unref(tableElRef);
+              if (!tableEl) {
+                return;
+              }
+              const bodyDomList = tableEl.$el.querySelectorAll(
+                '.ant-table-body'
+              ) as HTMLDivElement[];
+              const bodyDom = bodyDomList[0];
+              useEvent({
+                el: bodyDom,
+                name: 'scroll',
+                listener: () => {
+                  const footerBodyDom = tableEl.$el.querySelector(
+                    '.ant-table-footer .ant-table-body'
+                  ) as HTMLDivElement;
+                  if (!footerBodyDom || !bodyDom) return;
+                  footerBodyDom.scrollLeft = bodyDom.scrollLeft;
+                },
+                wait: 0,
+                options: true,
+              });
+            });
+          }
+        },
+        { immediate: true }
+      );
+
+      const tableAction: TableActionType = {
+        reload: async (opt?: FetchParams) => {
+          await fetch(opt);
+        },
+        getSelectRows,
+        clearSelectedRowKeys,
+        getSelectRowKeys,
+        deleteSelectRowByKey,
+        setPagination,
+        setTableData,
+        redoHeight,
+        setSelectedRowKeys,
+        setColumns,
+        getPaginationRef: () => {
+          return unref(getPaginationRef);
+        },
+        getColumns: (opt?: GetColumnsParams) => {
+          const { ignoreIndex } = opt || {};
+          let columns = unref(getColumnsRef);
+          if (ignoreIndex) {
+            columns = columns.filter((item) => item.flag !== 'INDEX');
+          }
+          return columns;
+        },
+        getDataSource: () => {
+          return unref(getDataSourceRef);
+        },
+        setLoading: (loading: boolean) => {
+          loadingRef.value = loading;
+        },
+        setProps: (props: Partial<BasicTableProps>) => {
+          innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
+        },
+      };
+
+      provideTable(tableAction);
+
+      emit('register', tableAction);
+      return {
+        tableElRef,
+        getBindValues,
+        loading: loadingRef,
+        registerForm,
+        handleSearchInfoChange,
+        getFormProps,
+        getEmptyDataIsShowTable,
+        handleTableChange,
+        getRowClassName,
+        ...tableAction,
+      };
+    },
+  });
+</script>

+ 26 - 0
src/components/Table/src/componentMap.ts

@@ -0,0 +1,26 @@
+import { Component } from 'vue';
+
+import { Input, Select, Checkbox, InputNumber, Switch } from 'ant-design-vue';
+
+import { ComponentType } from './types/componentType';
+
+const componentMap = new Map<ComponentType, Component>();
+
+componentMap.set('Input', Input);
+componentMap.set('InputPassword', Input.Password);
+componentMap.set('InputNumber', InputNumber);
+
+componentMap.set('Select', Select);
+componentMap.set('Switch', Switch);
+componentMap.set('Checkbox', Checkbox);
+componentMap.set('CheckboxGroup', Checkbox.Group);
+
+export function add(compName: ComponentType, component: Component) {
+  componentMap.set(compName, component);
+}
+
+export function del(compName: ComponentType) {
+  componentMap.delete(compName);
+}
+
+export { componentMap };

+ 72 - 0
src/components/Table/src/components/CellResize.tsx

@@ -0,0 +1,72 @@
+import { defineComponent, ref, computed, unref } from 'vue';
+import { injectTable } from '../hooks/useProvinceTable';
+import { getSlot } from '/@/utils/helper/tsxHelper';
+
+import VueDraggableResizable from 'vue-draggable-resizable';
+export default defineComponent({
+  name: 'DragResize',
+  setup(props, { slots, attrs }) {
+    const elRef = ref<HTMLTableRowElement | null>(null);
+    const draggingMapRef = ref<{ [key in string]: number | string }>({});
+
+    const tableInstance = injectTable();
+
+    const getColumnsRef = computed(() => {
+      const columns = tableInstance.getColumns();
+      columns.forEach((col) => {
+        const { key } = col;
+        if (key) {
+          draggingMapRef.value[key] = col.width as number;
+        }
+      });
+      return columns;
+    });
+
+    return () => {
+      const { key = '', ...restProps } = { ...attrs };
+      const col = unref(getColumnsRef).find((col) => {
+        const k = col.dataIndex || col.key;
+        return k === key;
+      });
+      if (!col || !col.width) {
+        return <th {...restProps}>{getSlot(slots, 'default')}</th>;
+      }
+      const onDrag = (x: number) => {
+        draggingMapRef.value[key] = 0;
+        col.width = Math.max(x, 1);
+      };
+
+      const onDragstop = () => {
+        const el = unref(elRef);
+        if (!el) {
+          return;
+        }
+        draggingMapRef.value[key] = el.getBoundingClientRect().width;
+      };
+      return (
+        <th
+          {...restProps}
+          class="resize-table-th"
+          ref={elRef}
+          style={{
+            width: `${col.width}px`,
+          }}
+        >
+          {getSlot(slots, 'default')}
+          <VueDraggableResizable
+            key={col.key}
+            class="table-draggable-handle"
+            w={10}
+            x={draggingMapRef.value[key] || col.width}
+            z={1}
+            axis="x"
+            draggable={true}
+            resizable={false}
+            onDragging={onDrag}
+            onDragstop={onDragstop}
+          />
+        </th>
+      );
+    };
+  },
+});

+ 21 - 0
src/components/Table/src/components/EditTableHeaderIcon.vue

@@ -0,0 +1,21 @@
+<template>
+  <span>
+    {{ title }}
+    <FormOutlined class="ml-2" />
+  </span>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+  import { FormOutlined } from '@ant-design/icons-vue';
+  export default defineComponent({
+    name: 'EditTableHeaderIcon',
+    components: { FormOutlined },
+    props: {
+      title: {
+        type: String as PropType<string>,
+        default: '',
+      },
+    },
+    setup() {},
+  });
+</script>

+ 144 - 0
src/components/Table/src/components/TableAction.tsx

@@ -0,0 +1,144 @@
+import { defineComponent, PropType } from 'vue';
+import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
+import Icon from '/@/components/Icon/index';
+import { DownOutlined } from '@ant-design/icons-vue';
+import { ActionItem } from '../types/tableAction';
+import Button from '/@/components/Button/index.vue';
+const prefixCls = 'basic-table-action';
+export default defineComponent({
+  name: 'TableAction',
+  props: {
+    actions: {
+      type: Array as PropType<ActionItem[]>,
+      default: null,
+    },
+    dropDownActions: {
+      type: Array as PropType<ActionItem[]>,
+      default: null,
+    },
+  },
+  setup(props) {
+    // 增加按钮的TYPE和COLOR
+    return () => {
+      const { dropDownActions = [], actions } = props;
+      return (
+        <div class={prefixCls}>
+          {actions &&
+            actions.length &&
+            actions.map((action, index) => {
+              const {
+                disabled = false,
+                label,
+                props,
+                icon,
+                color = '',
+                type = 'link',
+                popConfirm = null,
+              } = action;
+              const button = (
+                <Button
+                  type={type}
+                  size="small"
+                  disabled={disabled}
+                  color={color}
+                  {...props}
+                  key={index}
+                >
+                  {() => (
+                    <>
+                      {label}
+                      {icon && <Icon icon={icon} />}
+                    </>
+                  )}
+                </Button>
+              );
+              if (popConfirm !== null) {
+                const {
+                  title,
+                  okText = '确定',
+                  cancelText = '取消',
+                  confirm = () => {},
+                  cancel = () => {},
+                  icon = '',
+                } = popConfirm;
+                return (
+                  <Popconfirm
+                    key={`P-${index}`}
+                    title={title}
+                    onConfirm={confirm}
+                    onCancel={cancel}
+                    okText={okText}
+                    cancelText={cancelText}
+                    icon={icon}
+                  >
+                    {() => button}
+                  </Popconfirm>
+                );
+              }
+              return button;
+            })}
+          {dropDownActions && dropDownActions.length && (
+            <Dropdown>
+              {{
+                default: () => (
+                  <Button type="link" size="small">
+                    {{
+                      default: () => (
+                        <>
+                          更多
+                          <DownOutlined />
+                        </>
+                      ),
+                    }}
+                  </Button>
+                ),
+                overlay: () => {
+                  return (
+                    <Menu>
+                      {{
+                        default: () => {
+                          return dropDownActions.map((action, index) => {
+                            const {
+                              disabled = false,
+                              label,
+                              props,
+                              icon,
+                              color = '',
+                              type = 'link',
+                            } = action;
+                            return (
+                              <Menu.Item key={`${index}`} disabled={disabled}>
+                                {() => (
+                                  <Button
+                                    type={type}
+                                    size="small"
+                                    {...props}
+                                    disabled={disabled}
+                                    color={color}
+                                  >
+                                    {{
+                                      default: () => (
+                                        <>
+                                          {label}
+                                          {icon && <Icon icon={icon} />}
+                                        </>
+                                      ),
+                                    }}
+                                  </Button>
+                                )}
+                              </Menu.Item>
+                            );
+                          });
+                        },
+                      }}
+                    </Menu>
+                  );
+                },
+              }}
+            </Dropdown>
+          )}
+        </div>
+      );
+    };
+  },
+});

+ 36 - 0
src/components/Table/src/components/TableImg.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="basic-table-img__preview" v-if="imgList && imgList.length">
+    <template v-for="(img, index) in imgList" :key="img">
+      <img :width="size" @click="handlePreview(index)" :src="img" />
+    </template>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+  import { createImgPreview } from '/@/components/Preview/index';
+
+  export default defineComponent({
+    name: 'TableAction',
+    props: {
+      imgList: {
+        type: Array as PropType<string[]>,
+        default: null,
+      },
+      size: {
+        type: Number as PropType<number>,
+        default: 40,
+      },
+    },
+    setup(props) {
+      function handlePreview(index: number) {
+        const { imgList } = props;
+
+        createImgPreview({
+          imageList: imgList as string[],
+          index: index,
+        });
+      }
+      return { handlePreview };
+    },
+  });
+</script>

+ 41 - 0
src/components/Table/src/components/TableTitle.vue

@@ -0,0 +1,41 @@
+<template>
+  <BasicTitle class="basic-table-title" v-if="tableTitle" :helpMessage="helpMessage">
+    {{ tableTitle }}
+  </BasicTitle>
+</template>
+<script lang="ts">
+  import { computed, defineComponent, PropType } from 'vue';
+
+  import { BasicTitle } from '/@/components/Basic/index';
+  import { isFunction } from '/@/utils/is';
+  export default defineComponent({
+    name: 'TableTitle',
+    components: { BasicTitle },
+    props: {
+      title: {
+        type: [Function, String] as PropType<string | ((data: any) => string)>,
+      },
+      getSelectRows: {
+        type: Function as PropType<() => any[]>,
+      },
+      helpMessage: {
+        type: [String, Array] as PropType<string | string[]>,
+      },
+    },
+    setup(props) {
+      const tableTitle = computed(() => {
+        const { title, getSelectRows = () => {} } = props;
+        let tit = title;
+
+        if (isFunction(title)) {
+          tit = title({
+            selectRows: getSelectRows(),
+          });
+        }
+        return tit;
+      });
+
+      return { tableTitle };
+    },
+  });
+</script>

+ 153 - 0
src/components/Table/src/components/renderEditableCell.tsx

@@ -0,0 +1,153 @@
+import { defineComponent, PropType, ref, unref, nextTick } from 'vue';
+import { injectTable } from '../hooks/useProvinceTable';
+import ClickOutSide from '/@/components/ClickOutSide/index.vue';
+
+import { RenderEditableCellParams } from '../types/table';
+import { ComponentType } from '../types/componentType';
+
+import { componentMap } from '../componentMap';
+import '../style/editable-cell.less';
+import { isString, isBoolean } from '/@/utils/is';
+import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
+
+const prefixCls = 'editable-cell';
+const EditableCell = defineComponent({
+  name: 'EditableCell',
+  props: {
+    value: {
+      type: String as PropType<string>,
+      default: '',
+    },
+    componentProps: {
+      type: Object as PropType<any>,
+      default: null,
+    },
+
+    dataKey: {
+      type: String as PropType<string>,
+      default: '',
+    },
+
+    dataIndex: {
+      type: String as PropType<string>,
+      default: '',
+    },
+
+    component: {
+      type: String as PropType<ComponentType>,
+      default: 'Input',
+    },
+  },
+  setup(props, { attrs }) {
+    const table = injectTable();
+    const elRef = ref<any>(null);
+
+    const isEditRef = ref(false);
+    const currentValueRef = ref<string | boolean>('');
+
+    function handleChange(e: ChangeEvent | string | boolean) {
+      if ((e as ChangeEvent).target && Reflect.has((e as ChangeEvent).target, 'value')) {
+        currentValueRef.value = (e as ChangeEvent).target.value;
+      }
+      if (isString(e) || isBoolean(e)) {
+        currentValueRef.value = e;
+      }
+    }
+
+    function handleEdit() {
+      isEditRef.value = true;
+      nextTick(() => {
+        const el = unref(elRef);
+        el && el.focus && el.focus();
+      });
+    }
+
+    function handleCancel() {
+      isEditRef.value = false;
+    }
+
+    function handleSubmit() {
+      const { dataKey, dataIndex } = props;
+      if (!dataKey || !dataIndex) {
+        return;
+      }
+      isEditRef.value = false;
+
+      const { getDataSource } = table;
+      const dataSource = getDataSource();
+      const target = dataSource.find((item) => item.key === dataKey);
+      if (target) {
+        target[dataIndex] = unref(currentValueRef);
+      }
+    }
+
+    function onClickOutside() {
+      const { component } = props;
+
+      if (component?.includes('Input')) {
+        handleCancel();
+      }
+    }
+    return () => {
+      const { value, component, componentProps = {} } = props;
+
+      const Comp = componentMap.get(component!) as any;
+      // const propsData: any = {};
+      return (
+        <div class={prefixCls}>
+          {unref(isEditRef) && (
+            <ClickOutSide onClickOutside={onClickOutside}>
+              {() => (
+                <div class={`${prefixCls}__wrapper`}>
+                  <Comp
+                    {...{
+                      ...attrs,
+                      ...componentProps,
+                    }}
+                    style={{ width: 'calc(100% - 48px)' }}
+                    ref={elRef}
+                    value={value}
+                    size="small"
+                    onChange={handleChange}
+                    onPressEnter={handleSubmit}
+                  />
+                  <div class={`${prefixCls}__action`}>
+                    <CheckOutlined class={[`${prefixCls}__icon`, 'mx-2']} onClick={handleSubmit} />
+                    <CloseOutlined class={[`${prefixCls}__icon `]} onClick={handleCancel} />
+                  </div>
+                </div>
+              )}
+            </ClickOutSide>
+          )}
+
+          {!unref(isEditRef) && (
+            <div class={`${prefixCls}__normal`} onClick={handleEdit}>
+              {value}
+              <FormOutlined class={`${prefixCls}__normal-icon`} />
+            </div>
+          )}
+        </div>
+      );
+    };
+  },
+});
+
+export function renderEditableCell({
+  dataIndex,
+  component,
+  componentOn = {},
+  componentProps = {},
+}: RenderEditableCellParams) {
+  return ({ text, record }: { text: string; record: any }) => {
+    return (
+      <EditableCell
+        value={text}
+        dataKey={record.key}
+        dataIndex={dataIndex}
+        component={component}
+        on={componentOn}
+        componentProps={componentProps}
+      />
+    );
+  };
+}

+ 15 - 0
src/components/Table/src/components/renderExpandIcon.tsx

@@ -0,0 +1,15 @@
+import { BasicArrow } from '/@/components/Basic';
+
+export default () => {
+  return (props: any) => {
+    return (
+      <BasicArrow
+        onClick={(e: Event) => {
+          props.onExpand(props.record, e);
+        }}
+        expand={props.expanded}
+        class="right"
+      />
+    );
+  };
+};

+ 64 - 0
src/components/Table/src/components/renderFooter.tsx

@@ -0,0 +1,64 @@
+import { Table } from 'ant-design-vue';
+import { TableRowSelection } from 'ant-design-vue/types/table/table';
+import { cloneDeep } from 'lodash-es';
+import { unref, ComputedRef } from 'vue';
+import { BasicColumn } from '../types/table';
+import { isFunction } from '/@/utils/is';
+export default ({
+  scroll = {},
+  columnsRef,
+  summaryFunc,
+  rowKey = 'key',
+  dataSourceRef,
+  rowSelectionRef,
+}: {
+  scroll: { x?: number | true; y?: number };
+  columnsRef: ComputedRef<BasicColumn[]>;
+  summaryFunc: any;
+  rowKey?: string;
+  dataSourceRef: ComputedRef<any[]>;
+  rowSelectionRef: ComputedRef<TableRowSelection<any> | null>;
+}) => {
+  if (!summaryFunc) {
+    return;
+  }
+  const dataSource: any[] = isFunction(summaryFunc) ? summaryFunc(unref(dataSourceRef)) : [];
+  const columns: BasicColumn[] = cloneDeep(unref(columnsRef));
+  const index = columns.findIndex((item) => item.flag === 'INDEX');
+  const hasRowSummary = dataSource.some((item) => Reflect.has(item, '_row'));
+  const hasIndexSummary = dataSource.some((item) => Reflect.has(item, '_index'));
+
+  if (index !== -1) {
+    if (hasIndexSummary) {
+      columns[index].customRender = ({ record }) => record._index;
+      columns[index].ellipsis = false;
+    } else {
+      Reflect.deleteProperty(columns[index], 'customRender');
+    }
+  }
+  if (unref(rowSelectionRef) && hasRowSummary) {
+    columns.unshift({
+      width: 60,
+      title: 'selection',
+      key: 'selectionKey',
+      align: 'center',
+      customRender: ({ record }) => record._row,
+    });
+  }
+
+  dataSource.forEach((item, i) => {
+    item[rowKey] = i;
+  });
+  return (
+    <Table
+      showHeader={false}
+      bordered={false}
+      pagination={false}
+      dataSource={dataSource}
+      rowKey={rowKey}
+      columns={columns}
+      tableLayout="fixed"
+      scroll={scroll as any}
+    />
+  );
+};

+ 14 - 0
src/components/Table/src/components/renderTitle.tsx

@@ -0,0 +1,14 @@
+import { Slots } from 'vue';
+import TableTitle from './TableTitle.vue';
+import { getSlot } from '/@/utils/helper/tsxHelper';
+export default (title: any, titleHelpMessage: string | string[], slots: Slots) => {
+  return (
+    <>
+      {getSlot(slots, 'tableTitle') ||
+        (title && <TableTitle helpMessage={titleHelpMessage} title={title} />) || (
+          <span>&nbsp;</span>
+        )}
+      {slots.toolbar && <div class="basic-table-toolbar">{getSlot(slots, 'toolbar')}</div>}
+    </>
+  );
+};

+ 12 - 0
src/components/Table/src/const.ts

@@ -0,0 +1,12 @@
+export const ROW_KEY = 'key';
+
+export const PAGE_SIZE_OPTIONS = ['10', '50', '80', '100'];
+
+export const PAGE_SIZE = ~~PAGE_SIZE_OPTIONS[0];
+
+export const FETCH_SETTING = {
+  pageField: 'page',
+  sizeField: 'pageSize',
+  listField: 'items',
+  totalField: 'total',
+};

+ 123 - 0
src/components/Table/src/hooks/useColumns.ts

@@ -0,0 +1,123 @@
+import { BasicColumn, BasicTableProps } from '../types/table';
+import { PaginationProps } from '../types/pagination';
+import { unref, ComputedRef, Ref, computed, watch, ref } from 'vue';
+import { isBoolean, isArray, isObject } from '/@/utils/is';
+import { PAGE_SIZE } from '../const';
+import { useProps } from './useProps';
+
+export function useColumns(
+  refProps: ComputedRef<BasicTableProps>,
+  getPaginationRef: ComputedRef<false | PaginationProps>
+) {
+  const { propsRef } = useProps(refProps);
+
+  const columnsRef = (ref(unref(propsRef).columns) as unknown) as Ref<BasicColumn[]>;
+  const cacheColumnsRef = (ref(unref(propsRef).columns) as unknown) as Ref<BasicColumn[]>;
+
+  watch(
+    () => unref(propsRef).columns,
+    (columns) => {
+      columnsRef.value = columns;
+      cacheColumnsRef.value = columns;
+    },
+    {
+      immediate: true,
+    }
+  );
+  const getColumnsRef = computed(() => {
+    const props = unref(propsRef);
+    const { showIndexColumn, indexColumnProps, ellipsis, actionColumn, isTreeTable } = props;
+
+    const columns = unref(columnsRef);
+    if (!columns) {
+      return [];
+    }
+    let pushIndexColumns = false;
+    columns.forEach((item) => {
+      const { key, dataIndex } = item;
+      item.align = item.align || 'center';
+      if (ellipsis) {
+        if (!key) {
+          item.key = dataIndex;
+        }
+        if (!isBoolean(item.ellipsis)) {
+          Object.assign(item, {
+            ellipsis,
+          });
+        }
+      }
+      const indIndex = columns.findIndex((column) => column.flag === 'INDEX');
+      if (showIndexColumn && !isTreeTable) {
+        pushIndexColumns = indIndex === -1;
+      } else if (!showIndexColumn && !isTreeTable && indIndex !== -1) {
+        columns.splice(indIndex, 1);
+      }
+    });
+
+    if (pushIndexColumns) {
+      const isFixedLeft = columns.some((item) => item.fixed === 'left');
+
+      columns.unshift({
+        flag: 'INDEX',
+        width: 50,
+        title: '序号',
+        align: 'center',
+        customRender: ({ index }) => {
+          const getPagination = unref(getPaginationRef);
+          if (isBoolean(getPagination)) {
+            return `${index + 1}`;
+          }
+          const { current = 1, pageSize = PAGE_SIZE } = getPagination;
+          const currentIndex = (current - 1) * pageSize + index + 1;
+          return currentIndex;
+        },
+        ...(isFixedLeft
+          ? {
+              fixed: 'left',
+            }
+          : {}),
+        ...indexColumnProps,
+      });
+    }
+    if (actionColumn) {
+      const hasIndex = columns.findIndex((column) => column.flag === 'ACTION');
+      if (hasIndex === -1) {
+        columns.push({
+          fixed: 'right',
+          ...actionColumn,
+          flag: 'ACTION',
+        });
+      } else {
+        columns[hasIndex] = {
+          ...columns[hasIndex],
+          fixed: 'right',
+          ...actionColumn,
+          flag: 'ACTION',
+        };
+      }
+    }
+    return columns;
+  });
+
+  function setColumns(columns: BasicColumn[] | string[]) {
+    if (!isArray(columns)) {
+      return;
+    }
+    if (columns.length <= 0) {
+      columnsRef.value = [];
+      return;
+    }
+
+    const firstColumn = columns[0];
+    if (isObject(firstColumn)) {
+      columnsRef.value = columns as any;
+    } else {
+      const newColumns = unref(cacheColumnsRef).filter((item) =>
+        (columns as string[]).includes(item.key! || item.dataIndex!)
+      );
+      columnsRef.value = newColumns;
+    }
+  }
+
+  return { getColumnsRef, setColumns };
+}

+ 148 - 0
src/components/Table/src/hooks/useDataSource.ts

@@ -0,0 +1,148 @@
+import { useTimeout } from '/@/hooks/core/useTimeout';
+import { BasicTableProps, FetchParams } from '../types/table';
+import { PaginationProps } from '../types/pagination';
+import { watch, ref, unref, ComputedRef, computed, onMounted, Ref } from 'vue';
+import { buildUUID } from '/@/utils/uuid';
+import { isFunction, isBoolean } from '/@/utils/is';
+import { FETCH_SETTING, ROW_KEY } from '../const';
+import { get } from 'lodash-es';
+import { useProps } from './useProps';
+
+interface ActionType {
+  getPaginationRef: ComputedRef<false | PaginationProps>;
+  setPagination: (info: Partial<PaginationProps>) => void;
+  loadingRef: Ref<boolean | undefined>;
+  getFieldsValue: () => {
+    [field: string]: any;
+  };
+}
+export function useDataSource(
+  refProps: ComputedRef<BasicTableProps>,
+  { getPaginationRef, setPagination, loadingRef, getFieldsValue }: ActionType,
+  emit: EmitType
+) {
+  const { propsRef } = useProps(refProps);
+
+  const dataSourceRef = ref<any[]>([]);
+
+  watch(
+    () => unref(propsRef).dataSource,
+    (data: any[]) => {
+      const { api } = unref(propsRef);
+      !api && (dataSourceRef.value = data);
+    },
+    { immediate: true }
+  );
+
+  function setTableKey(items: any[]) {
+    if (!items || !Array.isArray(items)) {
+      return;
+    }
+    items.forEach((item) => {
+      if (!item[ROW_KEY]) {
+        item[ROW_KEY] = buildUUID();
+      }
+      if (item.children && item.children.length) {
+        setTableKey(item.children);
+      }
+    });
+  }
+  const getAutoCreateKey = computed(() => {
+    return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
+  });
+
+  const getDataSourceRef = computed(() => {
+    const dataSource = unref(dataSourceRef);
+    if (!dataSource || dataSource.length === 0) {
+      return [];
+    }
+    if (unref(getAutoCreateKey)) {
+      const firstItem = dataSource[0];
+      const lastItem = dataSource[dataSource.length - 1];
+
+      if (firstItem && lastItem) {
+        if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
+          unref(dataSourceRef).forEach((item) => {
+            if (!item[ROW_KEY]) {
+              item[ROW_KEY] = buildUUID();
+            }
+            if (item.children && item.children.length) {
+              setTableKey(item.children);
+            }
+          });
+        }
+      }
+    }
+    return unref(dataSourceRef);
+  });
+
+  async function fetch(opt?: FetchParams) {
+    const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref(
+      propsRef
+    );
+    if (!api && !isFunction(api)) return;
+    try {
+      loadingRef.value = true;
+      const { pageField, sizeField, listField, totalField } = fetchSetting || FETCH_SETTING;
+      let pageParams: any = {};
+      if (isBoolean(getPaginationRef)) {
+        pageParams = {};
+      } else {
+        const { current, pageSize } = unref(getPaginationRef) as PaginationProps;
+        pageParams[pageField] = opt?.page || current;
+        pageParams[sizeField] = pageSize;
+      }
+
+      let params: any = {
+        ...pageParams,
+        ...(useSearchForm ? getFieldsValue() : {}),
+        ...searchInfo,
+        ...(opt ? opt.searchInfo : {}),
+      };
+      if (beforeFetch && isFunction(beforeFetch)) {
+        params = beforeFetch(params) || params;
+      }
+
+      const res = await api(params);
+      let resultItems: any[] = get(res, listField);
+      const resultTotal: number = get(res, totalField);
+      if (afterFetch && isFunction(afterFetch)) {
+        resultItems = afterFetch(resultItems) || resultItems;
+      }
+
+      dataSourceRef.value = resultItems;
+      setPagination({
+        total: resultTotal || 0,
+      });
+      if (opt && opt.page) {
+        setPagination({
+          current: opt.page || 1,
+        });
+      }
+      emit('fetch-success', {
+        items: unref(resultItems),
+        total: resultTotal,
+      });
+    } catch (error) {
+      emit('fetch-error', error);
+      dataSourceRef.value = [];
+      setPagination({
+        total: 0,
+      });
+    } finally {
+      loadingRef.value = false;
+    }
+  }
+
+  function setTableData(values: any[]) {
+    dataSourceRef.value = values;
+  }
+  onMounted(() => {
+    // 转异步任务
+    useTimeout(() => {
+      unref(propsRef).immediate && fetch();
+    }, 0);
+  });
+
+  return { getDataSourceRef, setTableData, getAutoCreateKey, fetch: fetch };
+}

+ 15 - 0
src/components/Table/src/hooks/useLoading.ts

@@ -0,0 +1,15 @@
+import { watch, ref, ComputedRef, unref } from 'vue';
+import { BasicTableProps } from '../types/table';
+import { useProps } from './useProps';
+export function useLoading(refProps: ComputedRef<BasicTableProps>) {
+  const { propsRef } = useProps(refProps);
+
+  const loadingRef = ref(unref(propsRef).loading);
+  watch(
+    () => unref(propsRef).loading,
+    (v: boolean) => {
+      loadingRef.value = v;
+    }
+  );
+  return { loadingRef };
+}

+ 53 - 0
src/components/Table/src/hooks/usePagination.tsx

@@ -0,0 +1,53 @@
+import { computed, unref, ref, ComputedRef } from 'vue';
+import { PaginationProps } from '../types/pagination';
+import { isBoolean } from '/@/utils/is';
+import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
+
+import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
+import { useProps } from './useProps';
+import { BasicTableProps } from '../..';
+export function usePagination(refProps: ComputedRef<BasicTableProps>) {
+  const configRef = ref<PaginationProps>({});
+  const { propsRef } = useProps(refProps);
+
+  const getPaginationRef = computed((): PaginationProps | false => {
+    const { pagination } = unref(propsRef);
+    if (isBoolean(pagination) && !pagination) {
+      return false;
+    }
+    return {
+      current: 1,
+      pageSize: PAGE_SIZE,
+      size: 'small',
+      defaultPageSize: PAGE_SIZE,
+      showTotal: (total) => `共 ${total} 条数据`,
+      showSizeChanger: true,
+      pageSizeOptions: PAGE_SIZE_OPTIONS,
+      itemRender: ({ page, type, originalElement }) => {
+        if (type === 'prev') {
+          if (page === 0) {
+            return null;
+          }
+          return <LeftOutlined />;
+        } else if (type === 'next') {
+          if (page === 1) {
+            return null;
+          }
+          return <RightOutlined />;
+        }
+        return originalElement;
+      },
+      showQuickJumper: true,
+      ...(isBoolean(pagination) ? {} : pagination),
+      ...unref(configRef),
+    };
+  });
+
+  function setPagination(info: Partial<PaginationProps>) {
+    configRef.value = {
+      ...unref(getPaginationRef),
+      ...info,
+    };
+  }
+  return { getPaginationRef, setPagination };
+}

+ 29 - 0
src/components/Table/src/hooks/useProps.ts

@@ -0,0 +1,29 @@
+/*
+ * @description:
+ * @author: wenbin.chen
+ * @Date: 2020-05-12 13:20:26
+ * @LastEditors: vben
+ * @LastEditTime: 2020-10-07 14:52:34
+ * @email: 190848757@qq.com
+ */
+
+import { Ref, ref, watch, unref } from 'vue';
+import { BasicTableProps } from '../types/table';
+
+/**
+ * @description:
+ * @Date: 2020-05-12 13:20:37
+ */
+export function useProps(props: Readonly<Ref<BasicTableProps>>) {
+  const propsRef = (ref<BasicTableProps>(unref(props)) as unknown) as Ref<BasicTableProps>;
+  watch(
+    () => props.value,
+    (v) => {
+      propsRef.value = unref(v);
+    },
+    {
+      immediate: false,
+    }
+  );
+  return { propsRef };
+}

+ 12 - 0
src/components/Table/src/hooks/useProvinceTable.ts

@@ -0,0 +1,12 @@
+import { provide, inject } from 'vue';
+import { TableActionType } from '../types/table';
+
+const key = Symbol('table');
+
+export function provideTable(instance: TableActionType) {
+  provide(key, instance);
+}
+
+export function injectTable(): TableActionType {
+  return inject(key) as TableActionType;
+}

+ 63 - 0
src/components/Table/src/hooks/useRowSelection.ts

@@ -0,0 +1,63 @@
+import { computed, ref, unref, ComputedRef } from 'vue';
+import { BasicTableProps } from '../types/table';
+import { TableRowSelection } from 'ant-design-vue/types/table/table';
+import { useProps } from './useProps';
+
+/* eslint-disable */
+export function useRowSelection(refProps: ComputedRef<BasicTableProps>, emit: EmitType) {
+  const { propsRef } = useProps(refProps);
+
+  const selectedRowKeysRef = ref<string[]>([]);
+  const selectedRowRef = ref<any[]>([]);
+
+  const getRowSelectionRef = computed((): TableRowSelection<any> | null => {
+    const rowSelection = unref(propsRef).rowSelection;
+    if (!rowSelection) {
+      return null;
+    }
+    return {
+      selectedRowKeys: unref(selectedRowKeysRef),
+      hideDefaultSelections: false,
+      onChange: (selectedRowKeys: string[], selectedRows: any[]) => {
+        selectedRowKeysRef.value = selectedRowKeys;
+        selectedRowRef.value = selectedRows;
+        emit('selection-change', {
+          keys: selectedRowKeys,
+          rows: selectedRows,
+        });
+      },
+      ...rowSelection,
+    };
+  });
+  function setSelectedRowKeys(rowKeys: string[]) {
+    selectedRowKeysRef.value = rowKeys;
+  }
+
+  function clearSelectedRowKeys() {
+    selectedRowRef.value = [];
+    selectedRowKeysRef.value = [];
+  }
+
+  function deleteSelectRowByKey(key: string) {
+    const selectedRowKeys = unref(selectedRowKeysRef);
+    const index = selectedRowKeys.findIndex((item) => item === key);
+    if (index !== -1) {
+      unref(selectedRowKeysRef).splice(index, 1);
+    }
+  }
+  function getSelectRowKeys() {
+    return unref(selectedRowKeysRef);
+  }
+  function getSelectRows() {
+    return unref(selectedRowRef);
+  }
+
+  return {
+    getRowSelectionRef,
+    getSelectRows,
+    getSelectRowKeys,
+    setSelectedRowKeys,
+    clearSelectedRowKeys,
+    deleteSelectRowByKey,
+  };
+}

+ 88 - 0
src/components/Table/src/hooks/useTable.ts

@@ -0,0 +1,88 @@
+import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
+import type { PaginationProps } from '../types/pagination';
+import { ref, getCurrentInstance, onUnmounted, unref } from 'vue';
+import { isProdMode } from '/@/utils/env';
+
+export function useTable(
+  tableProps?: Partial<BasicTableProps>
+): [(instance: TableActionType) => void, TableActionType] {
+  if (!getCurrentInstance()) {
+    throw new Error('Please put useTable function in the setup function!');
+  }
+
+  const tableRef = ref<TableActionType | null>(null);
+  const loadedRef = ref<boolean | null>(false);
+
+  function register(instance: TableActionType) {
+    onUnmounted(() => {
+      tableRef.value = null;
+      loadedRef.value = null;
+    });
+    if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) {
+      return;
+    }
+    tableRef.value = instance;
+    tableProps && instance.setProps(tableProps);
+    loadedRef.value = true;
+  }
+
+  function getTableInstance(): TableActionType {
+    const table = unref(tableRef);
+    if (!table) {
+      throw new Error('table is undefined!');
+    }
+    return table;
+  }
+
+  const methods: TableActionType = {
+    reload: (opt?: FetchParams) => {
+      getTableInstance().reload(opt);
+    },
+    setProps: (props: Partial<BasicTableProps>) => {
+      getTableInstance().setProps(props);
+    },
+    redoHeight: () => {
+      getTableInstance().redoHeight();
+    },
+    setLoading: (loading: boolean) => {
+      getTableInstance().setLoading(loading);
+    },
+    getDataSource: () => {
+      return getTableInstance().getDataSource();
+    },
+    getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
+      const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
+
+      return columns;
+    },
+    setColumns: (columns: BasicColumn[]) => {
+      getTableInstance().setColumns(columns);
+    },
+    setTableData: (values: any[]) => {
+      return getTableInstance().setTableData(values);
+    },
+    setPagination: (info: Partial<PaginationProps>) => {
+      return getTableInstance().setPagination(info);
+    },
+    deleteSelectRowByKey: (key: string) => {
+      getTableInstance().deleteSelectRowByKey(key);
+    },
+    getSelectRowKeys: () => {
+      return getTableInstance().getSelectRowKeys();
+    },
+    getSelectRows: () => {
+      return getTableInstance().getSelectRows();
+    },
+    clearSelectedRowKeys: () => {
+      getTableInstance().clearSelectedRowKeys();
+    },
+    setSelectedRowKeys: (keys: string[] | number[]) => {
+      getTableInstance().setSelectedRowKeys(keys);
+    },
+    getPaginationRef: () => {
+      return getTableInstance().getPaginationRef();
+    },
+  } as TableActionType;
+
+  return [register, methods];
+}

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

@@ -0,0 +1,134 @@
+import { BasicTableProps } from '../types/table';
+import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue';
+import { getViewportOffset } from '/@/utils/domUtils';
+import { triggerWindowResize } from '/@/utils/event/triggerWindowResizeEvent';
+import { isBoolean } from '/@/utils/is';
+import { useTimeout } from '/@/hooks/core/useTimeout';
+import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
+import { useProps } from './useProps';
+
+export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) {
+  const { propsRef } = useProps(refProps);
+
+  const tableHeightRef: Ref<number | null> = ref(null);
+
+  watch(
+    () => unref(propsRef).canResize,
+    () => {
+      redoHeight();
+    }
+  );
+  function redoHeight() {
+    const { canResize } = unref(propsRef);
+
+    if (!canResize) {
+      return;
+    }
+    calcTableHeight();
+  }
+
+  async function calcTableHeight(cb?: () => void) {
+    const { canResize, resizeHeightOffset, pagination, maxHeight } = unref(propsRef);
+    if (!canResize) {
+      return;
+    }
+    await nextTick();
+    const table = unref(tableElRef) as any;
+
+    if (!table) {
+      return;
+    }
+    const tableEl: Element = table.$el;
+    if (!tableEl) {
+      return;
+    }
+    const el: HTMLElement | null = tableEl.querySelector('.ant-table-thead ');
+    // const layoutMain: Element | null = document.querySelector('.default-layout__main ');
+    if (!el) {
+      return;
+    }
+    // 表格距离底部高度
+    const { bottomIncludeBody } = getViewportOffset(el);
+    // 表格高度+距离底部高度-自定义偏移量
+
+    const paddingHeight = 32;
+    const borderHeight = 2 * 2;
+    // 分页器高度
+
+    // TODO 先固定20
+    const paginationHeight = 20;
+    // if (!isBoolean(pagination)) {
+    //   const paginationDom = tableEl.querySelector('.ant-pagination') as HTMLElement;
+    //   if (paginationDom) {
+    //     const offsetHeight = paginationDom.offsetHeight;
+    //     paginationHeight += offsetHeight || 0;
+    //   }
+    // }
+
+    let footerHeight = 0;
+    if (!isBoolean(pagination)) {
+      const footerEl = tableEl.querySelector('.ant-table-footer') as HTMLElement;
+      if (footerEl) {
+        const offsetHeight = footerEl.offsetHeight;
+        footerHeight += offsetHeight || 0;
+      }
+    }
+    let headerHeight = 0;
+    if (el) {
+      headerHeight = (el as HTMLElement).offsetHeight;
+    }
+    tableHeightRef.value =
+      bottomIncludeBody -
+      (resizeHeightOffset || 0) -
+      paddingHeight -
+      borderHeight -
+      paginationHeight -
+      footerHeight -
+      headerHeight;
+    useTimeout(() => {
+      tableHeightRef.value =
+        tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
+      cb && cb();
+    }, 0);
+  }
+
+  const getCanResize = computed(() => {
+    const { canResize, scroll } = unref(propsRef);
+    return canResize && !(scroll || {}).y;
+  });
+
+  useWindowSizeFn(calcTableHeight, 100);
+
+  // function clear() {
+  //   window.clearInterval(timer);
+  // }
+
+  onMounted(() => {
+    if (unref(getCanResize)) {
+      calcTableHeight();
+      const hasFixedLeft = (unref(propsRef).columns || []).some((item) => item.fixed === 'left');
+      // TODO antv table问题情况太多,只能先用下面方式定时器hack
+      useTimeout(() => {
+        calcTableHeight(() => {
+          // 有左侧固定列的时候才有问题
+          hasFixedLeft &&
+            useTimeout(() => {
+              triggerWindowResize();
+            }, 300);
+        });
+      }, 200);
+    }
+  });
+  const getScrollRef = computed(() => {
+    const tableHeight = unref(tableHeightRef);
+    const { canResize, scroll } = unref(propsRef);
+
+    return {
+      x: '100%',
+      y: canResize ? tableHeight : null,
+      scrollToFirstRowOnChange: false,
+      ...scroll,
+    };
+  });
+  return { getScrollRef, redoHeight };
+}

+ 155 - 0
src/components/Table/src/props.ts

@@ -0,0 +1,155 @@
+import { PropType } from 'vue';
+import { PaginationProps } from './types/pagination';
+import { BasicColumn, FetchSetting } from './types/table';
+import { TableCustomRecord, TableRowSelection } from 'ant-design-vue/types/table/table';
+import { FormProps } from '/@/components/Form/index';
+import { FETCH_SETTING } from './const';
+
+// 注释看 types/table
+export const basicProps = {
+  autoCreateKey: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  striped: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  showSummary: {
+    type: Boolean as PropType<boolean>,
+    default: false,
+  },
+
+  summaryFunc: {
+    type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
+    default: null,
+  },
+
+  canColDrag: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  isTreeTable: {
+    type: Boolean as PropType<boolean>,
+    default: false,
+  },
+  api: {
+    type: Function as PropType<(...arg: any[]) => Promise<any>>,
+    default: null,
+  },
+  beforeFetch: {
+    type: Function as PropType<Fn>,
+    default: null,
+  },
+  afterFetch: {
+    type: Function as PropType<Fn>,
+    default: null,
+  },
+  handleSearchInfoFn: {
+    type: Function as PropType<Fn>,
+    default: null,
+  },
+  fetchSetting: {
+    type: Object as PropType<FetchSetting>,
+    default: () => {
+      return FETCH_SETTING;
+    },
+  },
+  // 立即请求接口
+  immediate: { type: Boolean as PropType<boolean>, default: true },
+
+  emptyDataIsShowTable: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  // 额外的请求参数
+  searchInfo: {
+    type: Object as PropType<any>,
+    default: null,
+  },
+  // 使用搜索表单
+  useSearchForm: {
+    type: Boolean as PropType<boolean>,
+    default: false,
+  },
+  // 表单配置
+  formConfig: {
+    type: Object as PropType<Partial<FormProps>>,
+    default: null,
+  },
+  columns: {
+    type: [Array] as PropType<BasicColumn[]>,
+    default: null,
+  },
+  showIndexColumn: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  indexColumnProps: {
+    type: Object as PropType<BasicColumn>,
+    default: null,
+  },
+  actionColumn: {
+    type: Object as PropType<BasicColumn>,
+    default: null,
+  },
+  ellipsis: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  canResize: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  clearSelectOnPageChange: {
+    type: Boolean as PropType<boolean>,
+    default: false,
+  },
+  resizeHeightOffset: {
+    type: Number as PropType<number>,
+    default: 0,
+  },
+  rowSelection: {
+    type: Object as PropType<TableRowSelection<any> | null>,
+    default: null,
+  },
+  title: {
+    type: [String, Function] as PropType<string | ((data: any) => any)>,
+    default: null,
+  },
+  titleHelpMessage: {
+    type: [String, Array] as PropType<string | string[]>,
+  },
+  maxHeight: {
+    type: Number as PropType<number>,
+  },
+  dataSource: {
+    type: Array as PropType<any[]>,
+    default: null,
+  },
+  rowKey: {
+    type: [String, Function] as PropType<string | ((record: any) => string)>,
+    default: '',
+  },
+  bordered: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  pagination: {
+    type: [Object, Boolean] as PropType<PaginationProps | boolean>,
+    default: null,
+  },
+
+  loading: {
+    type: Boolean as PropType<boolean>,
+    default: false,
+  },
+  rowClassName: {
+    type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>,
+  },
+
+  scroll: {
+    type: Object as PropType<{ x: number | true; y: number }>,
+    default: null,
+  },
+};

+ 41 - 0
src/components/Table/src/style/editable-cell.less

@@ -0,0 +1,41 @@
+@import (reference) '../../../../design/index.less';
+
+@prefix-cls: ~'editable-cell';
+
+.@{prefix-cls} {
+  position: relative;
+
+  &__wrapper {
+    display: flex;
+    align-items: center;
+  }
+
+  &__icon {
+    &:hover {
+      transform: scale(1.2);
+
+      svg {
+        color: @primary-color;
+      }
+    }
+  }
+
+  &__normal {
+    padding-right: 48px;
+
+    &-icon {
+      position: absolute;
+      top: 4px;
+      right: 0;
+      display: none;
+      width: 20px;
+      cursor: pointer;
+    }
+  }
+
+  &:hover {
+    .@{prefix-cls}__normal-icon {
+      display: inline-block;
+    }
+  }
+}

+ 228 - 0
src/components/Table/src/style/index.less

@@ -0,0 +1,228 @@
+@import (reference) '../../../../design/index.less';
+@border-color: hsla(0, 0%, 80.8%, 0.25);
+
+.basic-table {
+  &-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  &-row__striped {
+    td {
+      background: #fafafa;
+    }
+  }
+
+  &-img__preview {
+    display: flex;
+
+    img {
+      margin-right: 4px;
+    }
+  }
+
+  &-action {
+    display: flex;
+  }
+
+  &-toolbar {
+    > * {
+      margin-right: 10px;
+    }
+  }
+
+  .resize-table-th {
+    position: relative !important;
+
+    .table-draggable-handle {
+      position: absolute;
+      right: -5px;
+      bottom: 0;
+      left: auto !important;
+      height: 100% !important;
+      cursor: col-resize;
+      transform: none !important;
+      touch-action: none;
+    }
+  }
+
+  &-drag-body {
+    position: relative;
+    cursor: move;
+  }
+
+  .drag-line td {
+    border-top: 2px dashed @primary-color;
+  }
+
+  .ant-table-wrapper {
+    padding: 8px;
+    background: #fff;
+    border-radius: 2px;
+
+    .ant-table-title {
+      padding: 0 0 10px 0 !important;
+    }
+
+    .ant-table.ant-table-bordered .ant-table-title {
+      border: none !important;
+    }
+  }
+
+  //
+  .ant-table {
+    &-title {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 8px 6px;
+    }
+
+    .ant-table-thead > tr > th,
+    .ant-table-header {
+      background: #f1f3f4;
+    }
+
+    .ant-table-tbody > tr.ant-table-row-selected td {
+      background: fade(@primary-color, 8%) !important;
+    }
+  }
+
+  .ant-table-bordered .ant-table-header > table,
+  .ant-table-bordered .ant-table-body > table,
+  .ant-table-bordered .ant-table-fixed-left table,
+  .ant-table-bordered .ant-table-fixed-right table {
+    border: 1px solid @border-color;
+  }
+
+  .ant-table-thead {
+    th {
+      border: none;
+    }
+  }
+
+  .ant-table-bordered .ant-table-tbody > tr > td {
+    border-bottom: 1px solid @border-color;
+
+    &:last-child {
+      border-right: none !important;
+    }
+  }
+
+  .ant-table.ant-table-bordered .ant-table-footer,
+  .ant-table.ant-table-bordered .ant-table-title {
+    border: 1px solid @border-color !important;
+  }
+
+  .ant-table-bordered.ant-table-empty .ant-table-placeholder {
+    border: 1px solid @border-color !important;
+  }
+
+  .ant-table td {
+    white-space: nowrap;
+  }
+
+  .ant-table-row-cell-last {
+    border-right: none !important;
+  }
+
+  .ant-table-bordered .ant-table-thead > tr > th,
+  .ant-table-bordered .ant-table-tbody > tr > td {
+    border-right: 1px solid @border-color;
+  }
+
+  .ant-table-thead > tr > th,
+  .ant-table-tbody > tr > td {
+    padding: 9px 8px !important;
+  }
+
+  .ant-pagination {
+    margin: 10px 0 0 0;
+  }
+
+  .ant-table-body {
+    overflow-x: auto !important;
+    overflow-y: scroll !important;
+  }
+
+  .ant-table-header {
+    margin-bottom: 0 !important;
+    overflow-x: hidden !important;
+    overflow-y: scroll !important;
+  }
+
+  .ant-table-fixed-right .ant-table-header {
+    border-left: 1px solid @border-color;
+
+    .ant-table-fixed {
+      border-bottom: none;
+    }
+  }
+
+  .ant-table-fixed-left {
+    .ant-table-header {
+      overflow-y: hidden !important;
+    }
+
+    .ant-table-fixed {
+      border-bottom: none;
+    }
+  }
+
+  .ant-radio {
+    &-inner {
+      border-color: @text-color-base;
+    }
+  }
+
+  .ant-checkbox {
+    &:not(.ant-checkbox-checked) {
+      .ant-checkbox-inner {
+        border-color: @text-color-base;
+      }
+    }
+  }
+
+  .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
+  .ant-table-tbody > tr > td {
+    word-break: break-word;
+    border-color: @border-color;
+  }
+
+  .ant-table-footer {
+    padding: 0;
+
+    .ant-table-wrapper {
+      padding: 0;
+    }
+
+    table {
+      border: none !important;
+    }
+
+    .ant-table-body {
+      overflow-x: hidden !important;
+      overflow-y: scroll !important;
+    }
+
+    td {
+      padding: 12px 8px;
+    }
+  }
+}
+
+.table-form-container {
+  padding: 16px;
+
+  .ant-form {
+    padding: 12px 12px 4px 12px;
+    margin-bottom: 12px;
+    background: #fff;
+    border-radius: 2px;
+  }
+
+  .ant-table-wrapper {
+    border-radius: 2px;
+  }
+}

+ 8 - 0
src/components/Table/src/types/componentType.ts

@@ -0,0 +1,8 @@
+export type ComponentType =
+  | 'Input'
+  | 'InputPassword'
+  | 'InputNumber'
+  | 'Select'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'Switch';

+ 89 - 0
src/components/Table/src/types/pagination.ts

@@ -0,0 +1,89 @@
+import { VNodeChild } from 'vue';
+import { PaginationRenderProps } from 'ant-design-vue/types/pagination';
+export interface PaginationProps {
+  /**
+   * total number of data items
+   * @default 0
+   * @type number
+   */
+  total?: number;
+
+  /**
+   * default initial page number
+   * @default 1
+   * @type number
+   */
+  defaultCurrent?: number;
+
+  /**
+   * current page number
+   * @type number
+   */
+  current?: number;
+
+  /**
+   * default number of data items per page
+   * @default 10
+   * @type number
+   */
+  defaultPageSize?: number;
+
+  /**
+   * number of data items per page
+   * @type number
+   */
+  pageSize?: number;
+
+  /**
+   * Whether to hide pager on single page
+   * @default false
+   * @type boolean
+   */
+  hideOnSinglePage?: boolean;
+
+  /**
+   * determine whether pageSize can be changed
+   * @default false
+   * @type boolean
+   */
+  showSizeChanger?: boolean;
+
+  /**
+   * specify the sizeChanger options
+   * @default ['10', '20', '30', '40']
+   * @type string[]
+   */
+  pageSizeOptions?: string[];
+
+  /**
+   * determine whether you can jump to pages directly
+   * @default false
+   * @type boolean
+   */
+  showQuickJumper?: boolean | object;
+
+  /**
+   * to display the total number and range
+   * @type Function
+   */
+  showTotal?: (total: number, range: [number, number]) => any;
+
+  /**
+   * specify the size of Pagination, can be set to small
+   * @default ''
+   * @type string
+   */
+  size?: string;
+
+  /**
+   * whether to use simple mode
+   * @type boolean
+   */
+  simple?: boolean;
+
+  /**
+   * to customize item innerHTML
+   * @type Function
+   */
+  itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
+}

+ 315 - 0
src/components/Table/src/types/table.ts

@@ -0,0 +1,315 @@
+import { VNodeChild } from 'vue';
+import { PaginationProps } from './pagination';
+import { FormProps } from '/@/components/Form/index';
+import {
+  ExpandedRowRenderRecord,
+  PaginationConfig,
+  SorterResult,
+  TableCurrentDataSource,
+  TableCustomRecord,
+  TableRowSelection,
+} from 'ant-design-vue/types/table/table';
+import { ColumnProps } from 'ant-design-vue/types/table/column';
+import { ComponentType } from './componentType';
+export declare type SortOrder = 'ascend' | 'descend';
+export interface ColumnFilterItem {
+  text?: string;
+  value?: string;
+  children?: any;
+}
+
+export interface RenderEditableCellParams {
+  dataIndex: string;
+  component?: ComponentType;
+  componentOn?: { [key: string]: Fn };
+  componentProps?: any;
+}
+
+export interface FetchParams {
+  searchInfo?: any;
+  page?: number;
+}
+
+export interface GetColumnsParams {
+  ignoreIndex?: boolean;
+}
+export interface TableActionType {
+  reload: (opt?: FetchParams) => Promise<void>;
+  getSelectRows: () => any[];
+  clearSelectedRowKeys: () => void;
+  getSelectRowKeys: () => string[];
+  deleteSelectRowByKey: (key: string) => void;
+  setPagination: (info: Partial<PaginationProps>) => void;
+  setTableData: (values: any[]) => void;
+  getColumns: ({ ignoreIndex }?: GetColumnsParams) => BasicColumn[];
+  setColumns: (columns: BasicColumn[] | string[]) => void;
+  getDataSource: () => any[];
+  setLoading: (loading: boolean) => void;
+  setProps: (props: Partial<BasicTableProps>) => void;
+  redoHeight: () => void;
+  setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
+  getPaginationRef: () => PaginationProps | boolean;
+}
+
+export interface FetchSetting {
+  // 请求接口当前页数
+  pageField: string;
+  // 每页显示多少条
+  sizeField: string;
+  // 请求结果列表字段  支持 a.b.c
+  listField: string;
+  // 请求结果总数字段  支持 a.b.c
+  totalField: string;
+}
+export interface BasicTableProps<T = any> {
+  // 斑马纹
+  striped?: boolean;
+  // 是否自动生成key
+  autoCreateKey?: boolean;
+  // 计算合计行的方法
+  summaryFunc?: (...arg: any) => any[];
+  // 是否显示合计行
+  showSummary?: boolean;
+  // 是否可拖拽列
+  canColDrag?: boolean;
+  // 是否树表
+  isTreeTable?: boolean;
+  // 接口请求对象
+  api?: (...arg: any) => Promise<any>;
+  // 请求之前处理参数
+  beforeFetch?: Fn;
+  // 自定义处理接口返回参数
+  afterFetch?: Fn;
+  // 查询条件请求之前处理
+  handleSearchInfoFn?: Fn;
+  // 请求接口配置
+  fetchSetting?: FetchSetting;
+  // 立即请求接口
+  immediate?: boolean;
+  // 在开起搜索表单的时候,如果没有数据是否显示表格
+  emptyDataIsShowTable?: boolean;
+  // 额外的请求参数
+  searchInfo?: any;
+
+  // 使用搜索表单
+  useSearchForm?: boolean;
+  // 表单配置
+  formConfig?: FormProps;
+  // 列配置
+  columns: BasicColumn[];
+  // 是否显示序号列
+  showIndexColumn?: boolean;
+  // 序号列配置
+  indexColumnProps?: BasicColumn;
+  actionColumn?: BasicColumn;
+  // 文本超过宽度是否显示。。。
+  ellipsis?: boolean;
+  // 是否可以自适应高度
+  canResize?: boolean;
+  // 自适应高度偏移, 计算结果-偏移量
+  resizeHeightOffset?: number;
+
+  // 在分页改变的时候清空选项
+  clearSelectOnPageChange?: boolean;
+  //
+  rowKey?: string | ((record: any) => string);
+  // 数据
+  dataSource?: any[];
+  // 标题右侧提示
+  titleHelpMessage?: string | string[];
+  // 表格滚动最大高度
+  maxHeight?: number;
+  // 是否显示边框
+  bordered?: boolean;
+  // 分页配置
+  pagination?: PaginationProps | boolean;
+  // loading加载
+  loading?: boolean;
+
+  /**
+   * The column contains children to display
+   * @default 'children'
+   * @type string | string[]
+   */
+  childrenColumnName?: string | string[];
+
+  /**
+   * Override default table elements
+   * @type object
+   */
+  components?: object;
+
+  /**
+   * Expand all rows initially
+   * @default false
+   * @type boolean
+   */
+  defaultExpandAllRows?: boolean;
+
+  /**
+   * Initial expanded row keys
+   * @type string[]
+   */
+  defaultExpandedRowKeys?: string[];
+
+  /**
+   * Current expanded row keys
+   * @type string[]
+   */
+  expandedRowKeys?: string[];
+
+  /**
+   * Expanded container render for each row
+   * @type Function
+   */
+  expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element;
+
+  /**
+   * Customize row expand Icon.
+   * @type Function | VNodeChild
+   */
+  expandIcon?: Function | VNodeChild | JSX.Element;
+
+  /**
+   * Whether to expand row by clicking anywhere in the whole row
+   * @default false
+   * @type boolean
+   */
+  expandRowByClick?: boolean;
+
+  /**
+   * The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
+   */
+  expandIconColumnIndex?: number;
+
+  /**
+   * Table footer renderer
+   * @type Function | VNodeChild
+   */
+  footer?: Function | VNodeChild | JSX.Element;
+
+  /**
+   * Indent size in pixels of tree data
+   * @default 15
+   * @type number
+   */
+  indentSize?: number;
+
+  /**
+   * i18n text including filter, sort, empty text, etc
+   * @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
+   * @type object
+   */
+  locale?: object;
+
+  /**
+   * Row's className
+   * @type Function
+   */
+  rowClassName?: (record: TableCustomRecord<T>) => string;
+
+  /**
+   * Row selection config
+   * @type object
+   */
+  rowSelection?: TableRowSelection<T>;
+
+  /**
+   * Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area.
+   * It is recommended to set a number for x, if you want to set it to true,
+   * you need to add style .ant-table td { white-space: nowrap; }.
+   * @type object
+   */
+  scroll?: { x?: number | true; y?: number };
+
+  /**
+   * Whether to show table header
+   * @default true
+   * @type boolean
+   */
+  showHeader?: boolean;
+
+  /**
+   * Size of table
+   * @default 'default'
+   * @type string
+   */
+  size?: 'default' | 'middle' | 'small' | 'large';
+
+  /**
+   * Table title renderer
+   * @type Function | ScopedSlot
+   */
+  title?: VNodeChild | JSX.Element;
+
+  /**
+   * Set props on per header row
+   * @type Function
+   */
+  customHeaderRow?: (column: ColumnProps<T>, index: number) => object;
+
+  /**
+   * Set props on per row
+   * @type Function
+   */
+  customRow?: (record: T, index: number) => object;
+
+  /**
+   * `table-layout` attribute of table element
+   * `fixed` when header/columns are fixed, or using `column.ellipsis`
+   *
+   * @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout
+   * @version 1.5.0
+   */
+  tableLayout?: 'auto' | 'fixed' | string;
+
+  /**
+   * the render container of dropdowns in table
+   * @param triggerNode
+   * @version 1.5.0
+   */
+  getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
+
+  /**
+   * Data can be changed again before rendering.
+   * The default configuration of general user empty data.
+   * You can configured globally through [ConfigProvider](https://antdv.com/components/config-provider-cn/)
+   *
+   * @version 1.5.4
+   */
+  transformCellText?: Function;
+
+  /**
+   * Callback executed when pagination, filters or sorter is changed
+   * @param pagination
+   * @param filters
+   * @param sorter
+   * @param currentDataSource
+   */
+  onChange?: (
+    pagination: PaginationConfig,
+    filters: Partial<Record<keyof T, string[]>>,
+    sorter: SorterResult<T>,
+    extra: TableCurrentDataSource<T>
+  ) => void;
+
+  /**
+   * Callback executed when the row expand icon is clicked
+   *
+   * @param expanded
+   * @param record
+   */
+  onExpand?: (expande: boolean, record: T) => void;
+
+  /**
+   * Callback executed when the expanded rows change
+   * @param expandedRows
+   */
+  onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
+}
+
+export interface BasicColumn<T = any> extends ColumnProps<T> {
+  children?: BasicColumn[];
+  //
+  flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
+}

+ 19 - 0
src/components/Table/src/types/tableAction.ts

@@ -0,0 +1,19 @@
+export interface ActionItem {
+  on?: any;
+  label: string;
+  disabled?: boolean;
+  color?: 'success' | 'error' | 'warning';
+  type?: string;
+  props?: any;
+  icon?: string;
+  popConfirm?: PopConfirm;
+}
+
+export interface PopConfirm {
+  title: string;
+  okText?: string;
+  cancelText?: string;
+  confirm: any;
+  cancel?: any;
+  icon?: string;
+}

+ 75 - 77
src/design/ant/pagination.less

@@ -1,98 +1,96 @@
-body {
-  .ant-pagination {
-    &.mini {
+.ant-pagination {
+  &.mini {
+    height: 20px;
+    font-size: 13px;
+
+    .ant-pagination-prev,
+    .ant-pagination-next {
+      width: 20px;
       height: 20px;
-      font-size: 13px;
-
-      .ant-pagination-prev,
-      .ant-pagination-next {
-        width: 20px;
-        height: 20px;
-        min-width: 20px;
-        line-height: 17px;
-        color: @border-color-shallow-dark;
-        border: 1px solid;
-      }
+      min-width: 20px;
+      line-height: 20px;
+      color: @border-color-shallow-dark;
+      border: 1px solid;
+    }
 
-      .ant-pagination-prev:hover,
-      .ant-pagination-next:hover,
-      .ant-pagination-item:focus,
-      .ant-pagination-item:hover {
-        color: @primary-color;
-        border: 1px solid @primary-color;
-      }
+    .ant-pagination-prev:hover,
+    .ant-pagination-next:hover,
+    .ant-pagination-item:focus,
+    .ant-pagination-item:hover {
+      color: @primary-color;
+      border: 1px solid @primary-color;
+    }
 
-      .ant-pagination-item {
-        height: 20px;
-        min-width: 20px;
-        margin: 0 3px;
-        line-height: 20px;
+    .ant-pagination-item {
+      height: 20px;
+      min-width: 20px;
+      margin: 0 3px;
+      line-height: 20px;
 
-        &:last-child {
-          margin-right: 0 !important;
-        }
+      &:last-child {
+        margin-right: 0 !important;
       }
+    }
 
-      .ant-pagination-item-active {
-        background: @primary-color;
+    .ant-pagination-item-active {
+      background: @primary-color;
 
-        a {
-          color: @white;
-        }
-      }
-
-      .ant-pagination-options {
-        margin-left: 20px;
+      a {
+        color: @white;
       }
+    }
 
-      .ant-select-sm .ant-select-selection--single {
-        height: 20px;
-      }
+    .ant-pagination-options {
+      margin-left: 20px;
+    }
 
-      .ant-pagination-options,
-      .ant-pagination-total-text,
-      .ant-pagination-options-quick-jumper {
-        height: 20px;
-        line-height: 20px;
-      }
+    .ant-select-sm .ant-select-selection--single {
+      height: 20px;
+    }
 
-      .ant-select-selection__rendered {
-        height: 18px;
-        line-height: 18px;
-      }
+    .ant-pagination-options,
+    .ant-pagination-total-text,
+    .ant-pagination-options-quick-jumper {
+      height: 20px;
+      line-height: 20px;
+    }
 
-      .ant-pagination-total-text,
-      .ant-select-selection__rendered,
-      .ant-select-dropdown-menu-item,
-      .ant-pagination-options-quick-jumper {
-        font-size: 13px;
-      }
+    .ant-select-selection__rendered {
+      height: 18px;
+      line-height: 18px;
+    }
 
-      .ant-pagination-options-quick-jumper input {
-        width: 40px;
-        height: 20px;
-        margin: 0 6px;
-        line-height: 20px;
-        text-align: center;
-      }
+    .ant-pagination-total-text,
+    .ant-select-selection__rendered,
+    .ant-select-dropdown-menu-item,
+    .ant-pagination-options-quick-jumper {
+      font-size: 13px;
+    }
 
-      .ant-pagination-jump-prev,
-      .ant-pagination-jump-next {
-        height: 20px;
-        line-height: 20px;
-      }
+    .ant-pagination-options-quick-jumper input {
+      width: 40px;
+      height: 20px;
+      margin: 0 6px;
+      line-height: 20px;
+      text-align: center;
+    }
 
-      .ant-pagination-options-size-changer.ant-select {
-        margin-right: 20px;
-      }
+    .ant-pagination-jump-prev,
+    .ant-pagination-jump-next {
+      height: 20px;
+      line-height: 20px;
+    }
 
-      .ant-select-arrow {
-        color: @border-color-shallow-dark;
-      }
+    .ant-pagination-options-size-changer.ant-select {
+      margin-right: 20px;
     }
 
-    &-disabled {
-      display: none;
+    .ant-select-arrow {
+      color: @border-color-shallow-dark;
     }
   }
+
+  &-disabled {
+    display: none;
+  }
 }

+ 7 - 1
src/layouts/default/LayoutContent.tsx

@@ -6,17 +6,23 @@ import { ContentEnum } from '/@/enums/appEnum';
 import { appStore } from '/@/store/modules/app';
 // import { RouterView } from 'vue-router';
 import PageLayout from '/@/layouts/page/index';
+import FrameLayout from '/@/layouts/iframe/index.vue';
+
+import { useSetting } from '/@/hooks/core/useSetting';
 export default defineComponent({
   name: 'DefaultLayoutContent',
   setup() {
+    const { projectSetting } = useSetting();
+
     return () => {
       const { getProjectConfig } = appStore;
       const { contentMode } = getProjectConfig;
+
       const wrapClass = contentMode === ContentEnum.FULL ? 'full' : 'fixed';
       return (
         <Layout.Content class={`layout-content ${wrapClass} `}>
           {{
-            default: () => <PageLayout />,
+            default: () => [<PageLayout />, projectSetting.canEmbedIFramePage && <FrameLayout />],
           }}
         </Layout.Content>
       );

+ 25 - 31
src/layouts/page/index.tsx

@@ -6,9 +6,7 @@ import { useTransition } from './useTransition';
 
 import { RouterView, RouteLocation } from 'vue-router';
 import { tabStore } from '/@/store/modules/tab';
-import FrameLayout from '/@/layouts/iframe/index.vue';
 
-import { useSetting } from '/@/hooks/core/useSetting';
 // import { useRouter } from 'vue-router';
 export default defineComponent({
   name: 'PageLayout',
@@ -24,7 +22,6 @@ export default defineComponent({
       const { on: transitionOn } = useTransition();
       on = transitionOn;
     }
-    const { projectSetting } = useSetting();
     return () => {
       const {
         routerTransition,
@@ -35,35 +32,32 @@ export default defineComponent({
 
       const openCache = openKeepAlive && show;
       const cacheTabs = toRaw(tabStore.getKeepAliveTabsState) as string[];
-      return (
-        <div>
-          <RouterView>
-            {{
-              default: ({ Component, route }: { Component: any; route: RouteLocation }) => {
-                const Content = openCache ? (
-                  <KeepAlive max={max} include={cacheTabs}>
-                    <Component {...route.params} />
-                  </KeepAlive>
-                ) : (
+      return [
+        <RouterView>
+          {{
+            default: ({ Component, route }: { Component: any; route: RouteLocation }) => {
+              const Content = openCache ? (
+                <KeepAlive max={max} include={cacheTabs}>
                   <Component {...route.params} />
-                );
-                return openRouterTransition ? (
-                  <Transition
-                    {...on}
-                    name={route.meta.transitionName || routerTransition}
-                    mode="out-in"
-                  >
-                    {() => Content}
-                  </Transition>
-                ) : (
-                  Content
-                );
-              },
-            }}
-          </RouterView>
-          {projectSetting.canEmbedIFramePage && <FrameLayout />}
-        </div>
-      );
+                </KeepAlive>
+              ) : (
+                <Component {...route.params} />
+              );
+              return openRouterTransition ? (
+                <Transition
+                  {...on}
+                  name={route.meta.transitionName || routerTransition}
+                  mode="out-in"
+                >
+                  {() => Content}
+                </Transition>
+              ) : (
+                Content
+              );
+            },
+          }}
+        </RouterView>,
+      ];
     };
   },
 });

+ 3 - 3
src/router/guard/progressGuard.ts

@@ -3,10 +3,10 @@ import type { Router } from 'vue-router';
 import NProgress from 'nprogress';
 import 'nprogress/nprogress.css';
 
-NProgress.inc(0.4);
-NProgress.configure({ easing: 'ease', speed: 1000, showSpinner: false });
-
 export function createProgressGuard(router: Router) {
+  NProgress.inc(0.1);
+  NProgress.configure({ easing: 'ease', speed: 200, showSpinner: false });
+
   router.beforeEach(async () => {
     NProgress.start();
     return true;

+ 62 - 0
src/router/menus/modules/demo/comp.ts

@@ -18,6 +18,68 @@ const menu: MenuModule = {
         name: 'ClickOutSide组件',
       },
       {
+        path: '/table',
+        name: '表格组件',
+        children: [
+          {
+            path: '/basic',
+            name: '基础表格',
+          },
+          {
+            path: '/treeTable',
+            name: '树形表格',
+          },
+          {
+            path: '/fetchTable',
+            name: '远程加载',
+          },
+          {
+            path: '/fixedColumn',
+            name: '固定列',
+          },
+          {
+            path: '/customerCell',
+            name: '自定义列',
+          },
+          {
+            path: '/formTable',
+            name: '开启搜索区域',
+          },
+          {
+            path: '/useTable',
+            name: 'UseTable',
+          },
+          {
+            path: '/refTable',
+            name: 'RefTable',
+          },
+          {
+            path: '/multipleHeader',
+            name: '多级表头',
+          },
+          {
+            path: '/mergeHeader',
+            name: '合并表头',
+          },
+          {
+            path: '/expandTable',
+            name: '可展开表格',
+          },
+          {
+            path: '/fixedHeight',
+            name: '定高/头部自定义',
+          },
+          {
+            path: '/footerTable',
+            name: '表尾行合计',
+          },
+          {
+            path: '/editCellTable',
+            name: '可编辑单元格',
+          },
+        ],
+      },
+      {
         path: '/form',
         name: '表单组件',
         children: [

+ 122 - 0
src/router/routes/modules/demo/comp.ts

@@ -99,6 +99,128 @@ export default {
       ],
     },
     {
+      path: '/table',
+      name: 'TableDemo',
+      redirect: '/comp/table/basic',
+      meta: {
+        title: '表格组件',
+      },
+      children: [
+        {
+          path: 'basic',
+          name: 'TableBasicDemo',
+          component: () => import('/@/views/demo/table/Basic.vue'),
+          meta: {
+            title: '基础表格',
+          },
+        },
+        {
+          path: 'treeTable',
+          name: 'TreeTableDemo',
+          component: () => import('/@/views/demo/table/TreeTable.vue'),
+          meta: {
+            title: '树形表格',
+          },
+        },
+        {
+          path: 'fetchTable',
+          name: 'FetchTableDemo',
+          component: () => import('/@/views/demo/table/FetchTable.vue'),
+          meta: {
+            title: '远程加载示例',
+          },
+        },
+        {
+          path: 'fixedColumn',
+          name: 'FixedColumnDemo',
+          component: () => import('/@/views/demo/table/FixedColumn.vue'),
+          meta: {
+            title: '固定列',
+          },
+        },
+        {
+          path: 'customerCell',
+          name: 'CustomerCellDemo',
+          component: () => import('/@/views/demo/table/CustomerCell.vue'),
+          meta: {
+            title: '自定义列',
+          },
+        },
+        {
+          path: 'formTable',
+          name: 'FormTableDemo',
+          component: () => import('/@/views/demo/table/FormTable.vue'),
+          meta: {
+            title: '开启搜索区域',
+          },
+        },
+        {
+          path: 'useTable',
+          name: 'UseTableDemo',
+          component: () => import('/@/views/demo/table/UseTable.vue'),
+          meta: {
+            title: 'UseTable',
+          },
+        },
+        {
+          path: 'refTable',
+          name: 'RefTableDemo',
+          component: () => import('/@/views/demo/table/RefTable.vue'),
+          meta: {
+            title: 'RefTable',
+          },
+        },
+        {
+          path: 'multipleHeader',
+          name: 'MultipleHeaderDemo',
+          component: () => import('/@/views/demo/table/MultipleHeader.vue'),
+          meta: {
+            title: '多级表头',
+          },
+        },
+        {
+          path: 'mergeHeader',
+          name: 'MergeHeaderDemo',
+          component: () => import('/@/views/demo/table/MergeHeader.vue'),
+          meta: {
+            title: '合并表头',
+          },
+        },
+        {
+          path: 'expandTable',
+          name: 'ExpandTableDemo',
+          component: () => import('/@/views/demo/table/ExpandTable.vue'),
+          meta: {
+            title: '可展开表格',
+          },
+        },
+        {
+          path: 'fixedHeight',
+          name: 'FixedHeightDemo',
+          component: () => import('/@/views/demo/table/FixedHeight.vue'),
+          meta: {
+            title: '定高/头部自定义',
+          },
+        },
+        {
+          path: 'footerTable',
+          name: 'FooterTableDemo',
+          component: () => import('/@/views/demo/table/FooterTable.vue'),
+          meta: {
+            title: '表尾行合计',
+          },
+        },
+        {
+          path: 'editCellTable',
+          name: 'EditCellTableDemo',
+          component: () => import('/@/views/demo/table/EditCellTable.vue'),
+          meta: {
+            title: '可编辑单元格',
+          },
+        },
+      ],
+    },
+    {
       path: '/tree',
       name: 'TreeDemo',
       redirect: '/comp/tree/basic',

+ 6 - 3
src/store/index.ts

@@ -1,16 +1,19 @@
 import type { App } from 'vue';
-import { createStore, createLogger, Plugin } from 'vuex';
+import {
+  createStore,
+  // createLogger, Plugin
+} from 'vuex';
 import { config } from 'vuex-module-decorators';
 import { isDevMode } from '/@/utils/env';
 
 config.rawError = true;
 const isDev = isDevMode();
-const plugins: Plugin<any>[] = isDev ? [createLogger()] : [];
+// const plugins: Plugin<any>[] = isDev ? [createLogger()] : [];
 
 const store = createStore({
   modules: {},
   strict: isDev,
-  plugins,
+  // plugins,
 });
 export function setupStore(app: App<Element>) {
   app.use(store);

+ 1 - 0
src/types/source.d.ts

@@ -1,4 +1,5 @@
 declare module 'ant-design-vue/es/locale/zh_CN';
+declare module 'vue-draggable-resizable';
 declare const React: string;
 declare module '*.bmp' {
   const src: string;

+ 0 - 7
src/useApp.tsx

@@ -2,8 +2,6 @@ import type { ProjectConfig } from '/@/types/config';
 
 import { computed, ref } from 'vue';
 
-import { BasicEmpty } from '/@/components/Basic';
-
 import { ThemeModeEnum } from '/@/enums/appEnum';
 import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
 
@@ -59,10 +57,6 @@ export function useInitAppConfigStore() {
 
 // Config Provider
 export function useConfigProvider() {
-  function renderEmpty() {
-    return <BasicEmpty />;
-  }
-
   function transformCellText({ text }: { text: string }) {
     if (isNull(text) || isUnDef(text)) {
       return ' - ';
@@ -70,7 +64,6 @@ export function useConfigProvider() {
     return text;
   }
   return {
-    renderEmpty,
     transformCellText,
   };
 }

+ 2 - 2
src/views/demo/comp/verify/index.vue

@@ -25,7 +25,7 @@
 
     <div class="flex justify-center p-4 items-center bg-gray-700">
       <BasicDragVerify ref="el4" @success="handleSuccess">
-        <template v-slot:actionIcon="isPassing">
+        <template #actionIcon="isPassing">
           <BugOutlined v-if="isPassing" />
           <RightOutlined v-else />
         </template>
@@ -35,7 +35,7 @@
 
     <div class="flex justify-center p-4 items-center bg-gray-700">
       <BasicDragVerify ref="el5" @success="handleSuccess">
-        <template v-slot:text="isPassing">
+        <template #text="isPassing">
           <div v-if="isPassing">
             <BugOutlined />
             成功

+ 70 - 0
src/views/demo/table/Basic.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="p-4">
+    <BasicTable
+      title="基础示例"
+      titleHelpMessage="温馨提醒"
+      :columns="columns"
+      :dataSource="data"
+      :canResize="canResize"
+      :loading="loading"
+      :striped="striped"
+      :bordered="border"
+      :pagination="{ pageSize: 20 }"
+    >
+      <template #toolbar>
+        <a-button type="primary" @click="toggleCanResize">
+          {{ !canResize ? '自适应高度' : '取消自适应' }}
+        </a-button>
+        <a-button type="primary" @click="toggleBorder">
+          {{ !border ? '显示边框' : '隐藏边框' }}
+        </a-button>
+        <a-button type="primary" @click="toggleLoading"> 开启loading </a-button>
+        <a-button type="primary" @click="toggleStriped">
+          {{ !striped ? '显示斑马纹' : '隐藏斑马纹' }}
+        </a-button>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicTable } from '/@/components/Table';
+  import { getBasicColumns, getBasicData } from './tableData';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const canResize = ref(false);
+      const loading = ref(false);
+      const striped = ref(true);
+      const border = ref(true);
+      function toggleCanResize() {
+        canResize.value = !canResize.value;
+      }
+      function toggleStriped() {
+        striped.value = !striped.value;
+      }
+      function toggleLoading() {
+        loading.value = true;
+        setTimeout(() => {
+          loading.value = false;
+        }, 3000);
+      }
+      function toggleBorder() {
+        border.value = !border.value;
+      }
+      return {
+        columns: getBasicColumns(),
+        data: getBasicData(),
+        canResize,
+        loading,
+        striped,
+        border,
+        toggleStriped,
+        toggleCanResize,
+        toggleLoading,
+        toggleBorder,
+      };
+    },
+  });
+</script>

+ 70 - 0
src/views/demo/table/CustomerCell.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #id="{ record }"> ID: {{ record.id }} </template>
+      <template #no="{ record }"
+        ><Tag color="green">{{ record.no }}</Tag>
+      </template>
+      <template #img>
+        <TableImg
+          :imgList="['https://picsum.photos/id/66/346/216', 'https://picsum.photos/id/67/346/216']"
+        />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableImg } from '/@/components/Table';
+  import { Tag } from 'ant-design-vue';
+  import { demoListApi } from '/@/api/demo/table';
+  const columns: BasicColumn[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      slots: { customRender: 'id' },
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 120,
+    },
+    {
+      title: '头像',
+      dataIndex: 'img',
+      width: 120,
+      slots: { customRender: 'img' },
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      slots: { customRender: 'no' },
+    },
+    {
+      title: '开始时间',
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableImg, Tag },
+    setup() {
+      const [registerTable] = useTable({
+        title: '自定义列内容',
+        api: demoListApi,
+        columns: columns,
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 60 - 0
src/views/demo/table/EditCellTable.vue

@@ -0,0 +1,60 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #customId>
+        <EditTableHeaderIcon title="Id" />
+      </template>
+      <template #customName>
+        <EditTableHeaderIcon title="姓名" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import {
+    BasicTable,
+    useTable,
+    BasicColumn,
+    renderEditableCell,
+    EditTableHeaderIcon,
+  } from '/@/components/Table';
+
+  import { demoListApi } from '/@/api/demo/table';
+  const columns: BasicColumn[] = [
+    {
+      // title: 'ID',
+      dataIndex: 'id',
+      slots: { title: 'customId' },
+      customRender: renderEditableCell({ dataIndex: 'id' }),
+    },
+    {
+      // title: '姓名',
+      dataIndex: 'name',
+      slots: { title: 'customName' },
+      customRender: renderEditableCell({
+        dataIndex: 'name',
+      }),
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      sorter: true,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, EditTableHeaderIcon },
+    setup() {
+      const [registerTable] = useTable({
+        title: '可编辑单元格示例',
+        api: demoListApi,
+        columns: columns,
+        showIndexColumn: false,
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 34 - 0
src/views/demo/table/ExpandTable.vue

@@ -0,0 +1,34 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #expandedRowRender="{ record }">
+        <span>No: {{ record.no }} </span>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getBasicColumns } from './tableData';
+
+  import { demoListApi } from '/@/api/demo/table';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [registerTable] = useTable({
+        title: '可展开表格',
+        api: demoListApi,
+        titleHelpMessage: '不能与scroll共用',
+        columns: getBasicColumns(),
+        rowKey: 'id',
+        canResize: false,
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 44 - 0
src/views/demo/table/FetchTable.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleReloadCurrent"> 刷新当前页 </a-button>
+        <a-button type="primary" @click="handleReload"> 刷新并返回第一页 </a-button>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getBasicColumns } from './tableData';
+
+  import { demoListApi } from '/@/api/demo/table';
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [registerTable, { reload }] = useTable({
+        title: '远程加载示例',
+        api: demoListApi,
+        columns: getBasicColumns(),
+      });
+      function handleReloadCurrent() {
+        reload();
+        // reload({
+        //   searchInfo: 'xxx',
+        // });
+      }
+
+      function handleReload() {
+        reload({
+          page: 1,
+        });
+      }
+      return {
+        registerTable,
+        handleReloadCurrent,
+        handleReload,
+      };
+    },
+  });
+</script>

+ 93 - 0
src/views/demo/table/FixedColumn.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #action>
+        <TableAction
+          :actions="[
+            {
+              label: '删除',
+              props: {
+                onClick: handleDelete,
+              },
+            },
+          ]"
+          :dropDownActions="[
+            {
+              label: '启用',
+              props: {
+                onClick: handleOpen,
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';
+
+  import { demoListApi } from '/@/api/demo/table';
+  const columns: BasicColumn[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      fixed: 'left',
+      width: 280,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 260,
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      width: 260,
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 300,
+    },
+    {
+      title: '开始时间',
+      width: 200,
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+      width: 200,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableAction },
+    setup() {
+      const [registerTable] = useTable({
+        title: 'TableAction组件及固定列示例',
+        api: demoListApi,
+        columns: columns,
+        rowSelection: { type: 'radio' },
+        actionColumn: {
+          width: 160,
+          title: 'Action',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+      });
+      function handleDelete() {
+        console.log('点击了删除');
+      }
+      function handleOpen() {
+        console.log('点击了启用');
+      }
+      return {
+        registerTable,
+        handleDelete,
+        handleOpen,
+      };
+    },
+  });
+</script>

+ 40 - 0
src/views/demo/table/FixedHeight.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #customTitle>
+        <span>
+          姓名
+          <BaseHelp class="ml-2" text="姓名" />
+        </span>
+      </template>
+      <template #customAddress>
+        地址
+        <FormOutlined class="ml-2" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getCustomHeaderColumns } from './tableData';
+  import { FormOutlined } from '@ant-design/icons-vue';
+  import { demoListApi } from '/@/api/demo/table';
+
+  export default defineComponent({
+    components: { BasicTable, FormOutlined },
+    setup() {
+      const [registerTable] = useTable({
+        title: '定高/头部自定义',
+        api: demoListApi,
+        columns: getCustomHeaderColumns(),
+        canResize: false,
+        scroll: { y: 100 },
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 50 - 0
src/views/demo/table/FooterTable.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getBasicColumns } from './tableData';
+
+  import { demoListApi } from '/@/api/demo/table';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      function handleSummary(tableData: any[]) {
+        const totalNo = tableData.reduce((prev, next) => {
+          prev += next.no;
+          return prev;
+        }, 0);
+        return [
+          {
+            _row: '合计',
+            _index: '平均值',
+            no: totalNo,
+          },
+          {
+            _row: '合计',
+            _index: '平均值',
+            no: totalNo,
+          },
+        ];
+      }
+      const [registerTable] = useTable({
+        title: '表尾行合计示例',
+        api: demoListApi,
+        rowSelection: { type: 'checkbox' },
+        columns: getBasicColumns(),
+        showSummary: true,
+        summaryFunc: handleSummary,
+        scroll: { x: 2000 },
+        canResize: false,
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 27 - 0
src/views/demo/table/FormTable.vue

@@ -0,0 +1,27 @@
+<template>
+  <BasicTable @register="registerTable" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getBasicColumns, getFormConfig } from './tableData';
+
+  import { demoListApi } from '/@/api/demo/table';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [registerTable] = useTable({
+        title: '开启搜索区域',
+        api: demoListApi,
+        columns: getBasicColumns(),
+        useSearchForm: true,
+        formConfig: getFormConfig(),
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 27 - 0
src/views/demo/table/MergeHeader.vue

@@ -0,0 +1,27 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getMergeHeaderColumns } from './tableData';
+
+  import { demoListApi } from '/@/api/demo/table';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [registerTable] = useTable({
+        title: '多级表头示例',
+        api: demoListApi,
+        columns: getMergeHeaderColumns(),
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 26 - 0
src/views/demo/table/MultipleHeader.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getMultipleHeaderColumns } from './tableData';
+
+  import { demoListApi } from '/@/api/demo/table';
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [registerTable] = useTable({
+        title: '多级表头示例',
+        api: demoListApi,
+        columns: getMultipleHeaderColumns(),
+      });
+
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 119 - 0
src/views/demo/table/RefTable.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="p-4">
+    <div class="mb-4">
+      <a-button class="mr-2" @click="reloadTable">还原</a-button>
+      <a-button class="mr-2" @click="changeLoading">开启loading</a-button>
+      <a-button class="mr-2" @click="changeColumns">更改Columns</a-button>
+      <a-button class="mr-2" @click="getColumn">获取Columns</a-button>
+      <a-button class="mr-2" @click="getTableData">获取表格数据</a-button>
+      <a-button class="mr-2" @click="setPaginationInfo">跳转到第2页</a-button>
+    </div>
+    <div class="mb-4">
+      <a-button class="mr-2" @click="getSelectRowList">获取选中行</a-button>
+      <a-button class="mr-2" @click="getSelectRowKeyList">获取选中行Key</a-button>
+      <a-button class="mr-2" @click="setSelectedRowKeyList">设置选中行</a-button>
+      <a-button class="mr-2" @click="clearSelect">清空选中行</a-button>
+      <a-button class="mr-2" @click="getPagination">获取分页信息</a-button>
+    </div>
+    <BasicTable
+      :canResize="false"
+      title="RefTable示例"
+      titleHelpMessage="使用Ref调用表格内方法"
+      ref="tableRef"
+      :api="api"
+      :columns="columns"
+      rowKey="id"
+      :rowSelection="{ type: 'checkbox' }"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { BasicTable, TableActionType } from '/@/components/Table';
+  import { getBasicColumns, getBasicShortColumns } from './tableData';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { demoListApi } from '/@/api/demo/table';
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const tableRef = ref<Nullable<TableActionType>>(null);
+      const { createMessage } = useMessage();
+
+      function getTableAction() {
+        const tableAction = unref(tableRef);
+        if (!tableAction) {
+          throw new Error('tableAction is null');
+        }
+        return tableAction;
+      }
+      function changeLoading() {
+        getTableAction().setLoading(true);
+        setTimeout(() => {
+          getTableAction().setLoading(false);
+        }, 1000);
+      }
+      function changeColumns() {
+        getTableAction().setColumns(getBasicShortColumns());
+      }
+      function reloadTable() {
+        getTableAction().setColumns(getBasicColumns());
+
+        getTableAction().reload({
+          page: 1,
+        });
+      }
+      function getColumn() {
+        createMessage.info('请在控制台查看!');
+        console.log(getTableAction().getColumns());
+      }
+
+      function getTableData() {
+        createMessage.info('请在控制台查看!');
+        console.log(getTableAction().getDataSource());
+      }
+
+      function getPagination() {
+        createMessage.info('请在控制台查看!');
+        console.log(getTableAction().getPaginationRef());
+      }
+
+      function setPaginationInfo() {
+        getTableAction().setPagination({
+          current: 2,
+        });
+        getTableAction().reload();
+      }
+      function getSelectRowList() {
+        createMessage.info('请在控制台查看!');
+        console.log(getTableAction().getSelectRows());
+      }
+      function getSelectRowKeyList() {
+        createMessage.info('请在控制台查看!');
+        console.log(getTableAction().getSelectRowKeys());
+      }
+      function setSelectedRowKeyList() {
+        getTableAction().setSelectedRowKeys(['0', '1', '2']);
+      }
+      function clearSelect() {
+        getTableAction().clearSelectedRowKeys();
+      }
+
+      return {
+        tableRef,
+        api: demoListApi,
+        columns: getBasicColumns(),
+        changeLoading,
+        changeColumns,
+        reloadTable,
+        getColumn,
+        getTableData,
+        getPagination,
+        setPaginationInfo,
+        getSelectRowList,
+        getSelectRowKeyList,
+        setSelectedRowKeyList,
+        clearSelect,
+      };
+    },
+  });
+</script>

+ 29 - 0
src/views/demo/table/TreeTable.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="p-4">
+    <BasicTable
+      :rowSelection="{ type: 'checkbox' }"
+      :isTreeTable="true"
+      title="树形表格"
+      titleHelpMessage="树形组件不能和序列号列同时存在"
+      :columns="columns"
+      :dataSource="data"
+      rowKey="id"
+      :indentSize="20"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable } from '/@/components/Table';
+  import { getBasicColumns, getTreeTableData } from './tableData';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      return {
+        columns: getBasicColumns(),
+        data: getTreeTableData(),
+      };
+    },
+  });
+</script>

+ 126 - 0
src/views/demo/table/UseTable.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="p-4">
+    <div class="mb-4">
+      <a-button class="mr-2" @click="reloadTable">还原</a-button>
+      <a-button class="mr-2" @click="changeLoading">开启loading</a-button>
+      <a-button class="mr-2" @click="changeColumns">更改Columns</a-button>
+      <a-button class="mr-2" @click="getColumn">获取Columns</a-button>
+      <a-button class="mr-2" @click="getTableData">获取表格数据</a-button>
+      <a-button class="mr-2" @click="setPaginationInfo">跳转到第2页</a-button>
+    </div>
+    <div class="mb-4">
+      <a-button class="mr-2" @click="getSelectRowList">获取选中行</a-button>
+      <a-button class="mr-2" @click="getSelectRowKeyList">获取选中行Key</a-button>
+      <a-button class="mr-2" @click="setSelectedRowKeyList">设置选中行</a-button>
+      <a-button class="mr-2" @click="clearSelect">清空选中行</a-button>
+      <a-button class="mr-2" @click="getPagination">获取分页信息</a-button>
+    </div>
+    <BasicTable @register="registerTable" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getBasicColumns, getBasicShortColumns } from './tableData';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { demoListApi } from '/@/api/demo/table';
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const { createMessage } = useMessage();
+      const [
+        registerTable,
+        {
+          setLoading,
+          setColumns,
+          getColumns,
+          getDataSource,
+          reload,
+          getPaginationRef,
+          setPagination,
+          getSelectRows,
+          getSelectRowKeys,
+          setSelectedRowKeys,
+          clearSelectedRowKeys,
+        },
+      ] = useTable({
+        canResize: false,
+        title: 'useTable示例',
+        titleHelpMessage: '使用useTable调用表格内方法',
+        api: demoListApi,
+        columns: getBasicColumns(),
+        rowKey: 'id',
+        rowSelection: {
+          type: 'checkbox',
+        },
+      });
+
+      function changeLoading() {
+        setLoading(true);
+        setTimeout(() => {
+          setLoading(false);
+        }, 1000);
+      }
+      function changeColumns() {
+        setColumns(getBasicShortColumns());
+      }
+      function reloadTable() {
+        setColumns(getBasicColumns());
+
+        reload({
+          page: 1,
+        });
+      }
+      function getColumn() {
+        createMessage.info('请在控制台查看!');
+        console.log(getColumns());
+      }
+
+      function getTableData() {
+        createMessage.info('请在控制台查看!');
+        console.log(getDataSource());
+      }
+
+      function getPagination() {
+        createMessage.info('请在控制台查看!');
+        console.log(getPaginationRef());
+      }
+
+      function setPaginationInfo() {
+        setPagination({
+          current: 2,
+        });
+        reload();
+      }
+      function getSelectRowList() {
+        createMessage.info('请在控制台查看!');
+        console.log(getSelectRows());
+      }
+      function getSelectRowKeyList() {
+        createMessage.info('请在控制台查看!');
+        console.log(getSelectRowKeys());
+      }
+      function setSelectedRowKeyList() {
+        setSelectedRowKeys(['0', '1', '2']);
+      }
+      function clearSelect() {
+        clearSelectedRowKeys();
+      }
+
+      return {
+        registerTable,
+        changeLoading,
+        changeColumns,
+        reloadTable,
+        getColumn,
+        getTableData,
+        getPagination,
+        setPaginationInfo,
+        getSelectRowList,
+        getSelectRowKeyList,
+        setSelectedRowKeyList,
+        clearSelect,
+      };
+    },
+  });
+</script>

+ 292 - 0
src/views/demo/table/tableData.tsx

@@ -0,0 +1,292 @@
+import { FormProps, FormSchema } from '/@/components/Table';
+import { BasicColumn } from '/@/components/Table/src/types/table';
+
+export function getBasicColumns(): BasicColumn[] {
+  return [
+    {
+      title: 'ID',
+      width: 150,
+      dataIndex: 'id',
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 120,
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 80,
+    },
+    {
+      title: '开始时间',
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      sorter: true,
+      dataIndex: 'endTime',
+    },
+  ];
+}
+
+export function getBasicShortColumns(): BasicColumn[] {
+  return [
+    {
+      title: 'ID',
+      width: 150,
+      dataIndex: 'id',
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 120,
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 80,
+    },
+  ];
+}
+
+export function getMultipleHeaderColumns(): BasicColumn[] {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      width: 200,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 120,
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      sorter: true,
+      children: [
+        {
+          title: '编号',
+          dataIndex: 'no',
+          width: 120,
+          filters: [
+            { text: 'Male', value: 'male' },
+            { text: 'Female', value: 'female' },
+          ],
+        },
+
+        {
+          title: '开始时间',
+          dataIndex: 'beginTime',
+          width: 120,
+        },
+        {
+          title: '结束时间',
+          dataIndex: 'endTime',
+          width: 120,
+        },
+      ],
+    },
+  ];
+}
+
+export function getCustomHeaderColumns(): BasicColumn[] {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      width: 200,
+    },
+    {
+      // title: '姓名',
+      dataIndex: 'name',
+      width: 120,
+      slots: { title: 'customTitle' },
+    },
+    {
+      // title: '地址',
+      dataIndex: 'address',
+      slots: { title: 'customAddress' },
+      sorter: true,
+    },
+
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 120,
+      filters: [
+        { text: 'Male', value: 'male' },
+        { text: 'Female', value: 'female' },
+      ],
+    },
+    {
+      title: '开始时间',
+      dataIndex: 'beginTime',
+      width: 120,
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+      width: 120,
+    },
+  ];
+}
+const renderContent = ({ text, index }: { text: any; index: number }) => {
+  const obj: any = {
+    children: text,
+    attrs: {},
+  };
+  if (index === 9) {
+    obj.attrs.colSpan = 0;
+  }
+  return obj;
+};
+export function getMergeHeaderColumns(): BasicColumn[] {
+  return [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      width: 300,
+      customRender: renderContent,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 300,
+      customRender: renderContent,
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      colSpan: 2,
+      width: 120,
+      sorter: true,
+      customRender: ({ text, index }: { text: any; index: number }) => {
+        const obj: any = {
+          children: text,
+          attrs: {},
+        };
+        if (index === 2) {
+          obj.attrs.rowSpan = 2;
+        }
+        if (index === 3) {
+          obj.attrs.colSpan = 0;
+        }
+        return obj;
+      },
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      colSpan: 0,
+      filters: [
+        { text: 'Male', value: 'male' },
+        { text: 'Female', value: 'female' },
+      ],
+      customRender: renderContent,
+    },
+    {
+      title: '开始时间',
+      dataIndex: 'beginTime',
+      width: 200,
+      customRender: renderContent,
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+      width: 200,
+      customRender: renderContent,
+    },
+  ];
+}
+export const getAdvanceSchema = (itemNumber = 6): FormSchema[] => {
+  const arr: any = [];
+  for (let index = 0; index < itemNumber; index++) {
+    arr.push({
+      field: `field${index}`,
+      label: `字段${index}`,
+      component: 'Input',
+      colProps: {
+        xl: 12,
+        xxl: 8,
+      },
+    });
+  }
+  return arr;
+};
+export function getFormConfig(): Partial<FormProps> {
+  return {
+    labelWidth: 100,
+    schemas: getAdvanceSchema(6),
+  };
+}
+export function getBasicData() {
+  const data: any = (() => {
+    const arr: any = [];
+    for (let index = 0; index < 40; index++) {
+      arr.push({
+        id: `${index}`,
+        name: 'John Brown',
+        age: `1${index}`,
+        no: `${index + 10}`,
+        address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+        beginTime: new Date().toLocaleString(),
+        endTime: new Date().toLocaleString(),
+      });
+    }
+    return arr;
+  })();
+  return data;
+}
+
+export function getTreeTableData() {
+  const data: any = (() => {
+    const arr: any = [];
+    for (let index = 0; index < 40; index++) {
+      arr.push({
+        id: `${index}`,
+        name: 'John Brown',
+        age: `1${index}`,
+        no: `${index + 10}`,
+        address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+        beginTime: new Date().toLocaleString(),
+        endTime: new Date().toLocaleString(),
+        children: [
+          {
+            id: `l2-${index}`,
+            name: 'John Brown',
+            age: `1${index}`,
+            no: `${index + 10}`,
+            address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+            beginTime: new Date().toLocaleString(),
+            endTime: new Date().toLocaleString(),
+            children: [
+              {
+                id: `l3-${index}`,
+                name: 'John Brown',
+                age: `1${index}`,
+                no: `${index + 10}`,
+                address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+                beginTime: new Date().toLocaleString(),
+                endTime: new Date().toLocaleString(),
+              },
+            ],
+          },
+        ],
+      });
+    }
+    return arr;
+  })();
+
+  return data;
+}