瀏覽代碼

feat(table): add expandAll/collapseAll function close #333

Vben 4 年之前
父節點
當前提交
391da9ec28
共有 28 個文件被更改,包括 286 次插入271 次删除
  1. 1 0
      CHANGELOG.zh_CN.md
  2. 1 1
      package.json
  3. 117 18
      src/components/Table/src/BasicTable.vue
  4. 4 5
      src/components/Table/src/components/EditTableHeaderIcon.vue
  5. 4 10
      src/components/Table/src/components/HeaderCell.vue
  6. 9 19
      src/components/Table/src/components/TableAction.vue
  7. 6 4
      src/components/Table/src/components/TableHeader.vue
  8. 2 2
      src/components/Table/src/components/TableImg.vue
  9. 0 12
      src/components/Table/src/components/editable/EditableCell.vue
  10. 0 1
      src/components/Table/src/components/settings/ColumnSetting.vue
  11. 5 9
      src/components/Table/src/components/settings/FullScreenSetting.vue
  12. 3 6
      src/components/Table/src/components/settings/RedoSetting.vue
  13. 4 3
      src/components/Table/src/components/settings/SizeSetting.vue
  14. 7 2
      src/components/Table/src/components/settings/index.vue
  15. 5 6
      src/components/Table/src/const.ts
  16. 11 15
      src/components/Table/src/hooks/useColumns.ts
  17. 0 5
      src/components/Table/src/hooks/useDataSource.ts
  18. 1 3
      src/components/Table/src/hooks/useLoading.ts
  19. 2 2
      src/components/Table/src/hooks/usePagination.tsx
  20. 16 6
      src/components/Table/src/hooks/useTable.ts
  21. 59 0
      src/components/Table/src/hooks/useTableExpand.ts
  22. 4 2
      src/components/Table/src/hooks/useTableHeader.ts
  23. 1 0
      src/components/Table/src/hooks/useTableStyle.ts
  24. 1 14
      src/components/Table/src/props.ts
  25. 0 107
      src/components/Table/src/style/index.less
  26. 3 1
      src/components/Table/src/types/table.ts
  27. 16 14
      src/views/demo/table/TreeTable.vue
  28. 4 4
      yarn.lock

+ 1 - 0
CHANGELOG.zh_CN.md

@@ -3,6 +3,7 @@
 ### ✨ Features
 
 - 路由新增 hideChildrenInMenu 配置。用于隐藏子菜单
+- 树形表格内置展开/折叠全部函数
 
 ### ✨ Refactor
 

+ 1 - 1
package.json

@@ -35,7 +35,7 @@
     "@iconify/iconify": "^2.0.0-rc.6",
     "@vueuse/core": "^4.4.1",
     "@zxcvbn-ts/core": "^0.3.0",
-    "ant-design-vue": "2.0.1",
+    "ant-design-vue": "2.1.0",
     "apexcharts": "^3.26.0",
     "axios": "^0.21.1",
     "crypto-js": "^4.0.0",

+ 117 - 18
src/components/Table/src/BasicTable.vue

@@ -1,15 +1,5 @@
 <template>
-  <div
-    ref="wrapRef"
-    :class="[
-      prefixCls,
-      $attrs.class,
-      {
-        [`${prefixCls}-form-container`]: getBindValues.useSearchForm,
-        [`${prefixCls}--inset`]: getBindValues.inset,
-      },
-    ]"
-  >
+  <div ref="wrapRef" :class="getWrapperClass">
     <BasicForm
       submitOnReset
       v-bind="getFormProps"
@@ -32,9 +22,10 @@
       v-show="getEmptyDataIsShowTable"
       @change="handleTableChange"
     >
-      <template #[item]="data" v-for="item in Object.keys($slots)">
+      <template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
         <slot :name="item" v-bind="data"></slot>
       </template>
+
       <template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
         <HeaderCell :column="column" />
       </template>
@@ -47,8 +38,8 @@
   import { defineComponent, ref, computed, unref } from 'vue';
   import { Table } from 'ant-design-vue';
   import { BasicForm, useForm } from '/@/components/Form/index';
-
-  import { omit } from 'lodash-es';
+  import expandIcon from './components/ExpandIcon';
+  import HeaderCell from './components/HeaderCell.vue';
 
   import { usePagination } from './hooks/usePagination';
   import { useColumns } from './hooks/useColumns';
@@ -59,17 +50,16 @@
   import { useCustomRow } from './hooks/useCustomRow';
   import { useTableStyle } from './hooks/useTableStyle';
   import { useTableHeader } from './hooks/useTableHeader';
+  import { useTableExpand } from './hooks/useTableExpand';
   import { createTableContext } from './hooks/useTableContext';
   import { useTableFooter } from './hooks/useTableFooter';
   import { useTableForm } from './hooks/useTableForm';
   import { useExpose } from '/@/hooks/core/useExpose';
   import { useDesign } from '/@/hooks/web/useDesign';
 
+  import { omit } from 'lodash-es';
   import { basicProps } from './props';
-  import expandIcon from './components/ExpandIcon';
-  import HeaderCell from './components/HeaderCell.vue';
 
-  import './style/index.less';
   export default defineComponent({
     components: {
       Table,
@@ -91,6 +81,7 @@
       'edit-cancel',
       'edit-row-end',
       'edit-change',
+      'expanded-rows-change',
     ],
     setup(props, { attrs, emit, slots }) {
       const tableElRef = ref<ComponentRef>(null);
@@ -175,6 +166,8 @@
 
       const { getRowClassName } = useTableStyle(getProps, prefixCls);
 
+      const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit);
+
       const { getHeaderProps } = useTableHeader(getProps, slots);
 
       const { getFooterProps } = useTableFooter(
@@ -208,6 +201,7 @@
           pagination: unref(getPaginationInfo),
           dataSource: unref(getDataSourceRef),
           footer: unref(getFooterProps),
+          ...unref(getExpandOption),
         };
         if (slots.expandedRowRender) {
           propsData = omit(propsData, 'scroll');
@@ -218,6 +212,18 @@
         return propsData;
       });
 
+      const getWrapperClass = computed(() => {
+        const values = unref(getBindValues);
+        return [
+          prefixCls,
+          attrs.class,
+          {
+            [`${prefixCls}-form-container`]: values.useSearchForm,
+            [`${prefixCls}--inset`]: values.inset,
+          },
+        ];
+      });
+
       const getEmptyDataIsShowTable = computed(() => {
         const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
         if (emptyDataIsShowTable || !useSearchForm) {
@@ -253,6 +259,8 @@
         setShowPagination,
         getShowPagination,
         setCacheColumnsByField,
+        expandAll,
+        collapseAll,
         getSize: () => {
           return unref(getBindValues).size as SizeType;
         },
@@ -278,9 +286,100 @@
         getFormProps,
         replaceFormSlotKey,
         getFormSlotKeys,
-        prefixCls,
+        getWrapperClass,
         columns: getViewColumns,
       };
     },
   });
 </script>
+<style lang="less">
+  @border-color: #cecece4d;
+
+  @prefix-cls: ~'@{namespace}-basic-table';
+
+  .@{prefix-cls} {
+    &-form-container {
+      padding: 16px;
+
+      .ant-form {
+        padding: 12px 10px 6px 10px;
+        margin-bottom: 16px;
+        background: #fff;
+        border-radius: 4px;
+      }
+    }
+
+    &-row__striped {
+      td {
+        background: #fafafa;
+      }
+    }
+
+    &--inset {
+      .ant-table-wrapper {
+        padding: 0;
+      }
+    }
+
+    .ant-tag {
+      margin-right: 0;
+    }
+
+    .ant-table-wrapper {
+      padding: 6px;
+      background: #fff;
+      border-radius: 2px;
+
+      .ant-table-title {
+        padding: 0 0 8px 0 !important;
+      }
+
+      .ant-table.ant-table-bordered .ant-table-title {
+        border: none !important;
+      }
+    }
+
+    //
+    .ant-table {
+      width: 100%;
+      overflow-x: hidden;
+
+      &-title {
+        display: flex;
+        padding: 8px 6px;
+        border-bottom: none;
+        justify-content: space-between;
+        align-items: center;
+      }
+
+      .ant-table-tbody > tr.ant-table-row-selected td {
+        background: fade(@primary-color, 8%) !important;
+      }
+    }
+
+    .ant-pagination {
+      margin: 10px 0 0 0;
+    }
+
+    .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;
+      }
+    }
+  }
+</style>

+ 4 - 5
src/components/Table/src/components/EditTableHeaderIcon.vue

@@ -6,16 +6,15 @@
   </span>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType } from 'vue';
+  import { defineComponent } from 'vue';
   import { FormOutlined } from '@ant-design/icons-vue';
+
+  import { propTypes } from '/@/utils/propTypes';
   export default defineComponent({
     name: 'EditTableHeaderIcon',
     components: { FormOutlined },
     props: {
-      title: {
-        type: String as PropType<string>,
-        default: '',
-      },
+      title: propTypes.string.def(''),
     },
   });
 </script>

+ 4 - 10
src/components/Table/src/components/HeaderCell.vue

@@ -12,6 +12,7 @@
   import { defineComponent, computed } from 'vue';
   import BasicHelp from '/@/components/Basic/src/BasicHelp.vue';
   import EditTableHeaderCell from './EditTableHeaderIcon.vue';
+
   import { useDesign } from '/@/hooks/web/useDesign';
   export default defineComponent({
     name: 'TableHeaderCell',
@@ -27,17 +28,10 @@
     },
     setup(props) {
       const { prefixCls } = useDesign('basic-table-header-cell');
-      const getIsEdit = computed(() => {
-        return !!props.column?.edit;
-      });
-
-      const getTitle = computed(() => {
-        return props.column?.customTitle;
-      });
 
-      const getHelpMessage = computed(() => {
-        return props.column?.helpMessage;
-      });
+      const getIsEdit = computed(() => !!props.column?.edit);
+      const getTitle = computed(() => props.column?.customTitle);
+      const getHelpMessage = computed(() => props.column?.helpMessage);
 
       return { prefixCls, getIsEdit, getTitle, getHelpMessage };
     },

+ 9 - 19
src/components/Table/src/components/TableAction.vue

@@ -19,17 +19,21 @@
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, computed } from 'vue';
+  import { defineComponent, PropType, computed, toRaw } from 'vue';
+
+  import { MoreOutlined } from '@ant-design/icons-vue';
   import Icon from '/@/components/Icon/index';
   import { ActionItem, TableActionType } from '/@/components/Table';
   import { PopConfirmButton } from '/@/components/Button';
   import { Divider } from 'ant-design-vue';
   import { Dropdown } from '/@/components/Dropdown';
+
   import { useDesign } from '/@/hooks/web/useDesign';
-  import { MoreOutlined } from '@ant-design/icons-vue';
-  import { propTypes } from '/@/utils/propTypes';
   import { useTableContext } from '../hooks/useTableContext';
+
+  import { propTypes } from '/@/utils/propTypes';
   import { ACTION_COLUMN_FLAG } from '../const';
+
   export default defineComponent({
     name: 'TableAction',
     components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined },
@@ -52,26 +56,12 @@
         table = useTableContext();
       }
 
-      // const getSize = computed(() => {
-      //   const size = table?.getSize?.();
-      //   if (size === 'middle' || !size) {
-      //     return;
-      //   }
-
-      //   if (size === 'default') {
-      //     return 'large';
-      //   }
-      //   return size;
-      // });
-
       const getActions = computed(() => {
-        return (props.actions || []).map((action) => {
+        return (toRaw(props.actions) || []).map((action) => {
           const { popConfirm } = action;
-          // const size = unref(getSize);
           return {
             type: 'link',
             size: 'small',
-            // ...(size ? { size } : {}),
             ...action,
             ...(popConfirm || {}),
             onConfirm: popConfirm?.confirm,
@@ -82,7 +72,7 @@
       });
 
       const getDropList = computed(() => {
-        return (props.dropDownActions || []).map((action, index) => {
+        return (toRaw(props.dropDownActions) || []).map((action, index) => {
           const { label } = action;
           return {
             ...action,

+ 6 - 4
src/components/Table/src/components/TableHeader.vue

@@ -1,5 +1,6 @@
 <template>
   <slot name="tableTitle" v-if="$slots.tableTitle"></slot>
+
   <TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" />
 
   <div :class="`${prefixCls}__toolbar`">
@@ -11,19 +12,20 @@
 <script lang="ts">
   import type { TableSetting } from '../types/table';
   import type { PropType } from 'vue';
-  import { Divider } from 'ant-design-vue';
+
   import { defineComponent } from 'vue';
+  import { Divider } from 'ant-design-vue';
+  import TableSettingComponent from './settings/index.vue';
+  import TableTitle from './TableTitle.vue';
 
   import { useDesign } from '/@/hooks/web/useDesign';
-  import TableSettingComp from './settings/index.vue';
-  import TableTitle from './TableTitle.vue';
 
   export default defineComponent({
     name: 'BasicTableHeader',
     components: {
       Divider,
       TableTitle,
-      TableSetting: TableSettingComp,
+      TableSetting: TableSettingComponent,
     },
     props: {
       title: {

+ 2 - 2
src/components/Table/src/components/TableImg.vue

@@ -31,8 +31,8 @@
       const getWrapStyle = computed(
         (): CSSProperties => {
           const { size } = props;
-          const wh = `${size}px`;
-          return { height: wh, width: wh };
+          const s = `${size}px`;
+          return { height: s, width: s };
         }
       );
 

+ 0 - 12
src/components/Table/src/components/editable/EditableCell.vue

@@ -83,18 +83,6 @@
         return unref(ruleMessage) && unref(ruleVisible);
       });
 
-      // const getSize = computed(() => {
-      //   const size = table?.getSize?.();
-      //   if (size === 'middle' || !size) {
-      //     return;
-      //   }
-
-      //   if (size === 'default') {
-      //     return 'large';
-      //   }
-      //   return size;
-      // });
-
       const getIsCheckComp = computed(() => {
         const component = unref(getComponent);
         return ['Checkbox', 'Switch'].includes(component);

+ 0 - 1
src/components/Table/src/components/settings/ColumnSetting.vue

@@ -3,7 +3,6 @@
     <template #title>
       <span>{{ t('component.table.settingColumn') }}</span>
     </template>
-    <!-- :getPopupContainer="getPopupContainer" -->
     <Popover
       placement="bottomLeft"
       trigger="click"

+ 5 - 9
src/components/Table/src/components/settings/FullScreenSetting.vue

@@ -3,17 +3,18 @@
     <template #title>
       <span>{{ t('component.table.settingFullScreen') }}</span>
     </template>
-    <FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" />
-    <FullscreenExitOutlined @click="handleFullScreen" v-else />
+    <FullscreenOutlined @click="toggleFullscreen" v-if="!isFullscreenRef" />
+    <FullscreenExitOutlined @click="toggleFullscreen" v-else />
   </Tooltip>
 </template>
 <script lang="ts">
   import { defineComponent } from 'vue';
-  import { useTableContext } from '../../hooks/useTableContext';
   import { Tooltip } from 'ant-design-vue';
   import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
+
   import { useFullscreen } from '/@/hooks/web/useFullScreen';
   import { useI18n } from '/@/hooks/web/useI18n';
+  import { useTableContext } from '../../hooks/useTableContext';
 
   export default defineComponent({
     name: 'FullScreenSetting',
@@ -26,15 +27,10 @@
     setup() {
       const table = useTableContext();
       const { t } = useI18n();
-
       const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef);
 
-      function handleFullScreen() {
-        toggleFullscreen();
-      }
-
       return {
-        handleFullScreen,
+        toggleFullscreen,
         isFullscreenRef,
         t,
       };

+ 3 - 6
src/components/Table/src/components/settings/RedoSetting.vue

@@ -8,10 +8,11 @@
 </template>
 <script lang="ts">
   import { defineComponent } from 'vue';
-  import { useTableContext } from '../../hooks/useTableContext';
   import { Tooltip } from 'ant-design-vue';
   import { RedoOutlined } from '@ant-design/icons-vue';
+
   import { useI18n } from '/@/hooks/web/useI18n';
+  import { useTableContext } from '../../hooks/useTableContext';
 
   export default defineComponent({
     name: 'RedoSetting',
@@ -19,7 +20,6 @@
       RedoOutlined,
       Tooltip,
     },
-
     setup() {
       const table = useTableContext();
       const { t } = useI18n();
@@ -28,10 +28,7 @@
         table.reload();
       }
 
-      return {
-        redo,
-        t,
-      };
+      return { redo, t };
     },
   });
 </script>

+ 4 - 3
src/components/Table/src/components/settings/SizeSetting.vue

@@ -23,15 +23,16 @@
   </Tooltip>
 </template>
 <script lang="ts">
+  import type { SizeType } from '../../types/table';
+
   import { defineComponent, ref } from 'vue';
-  import { useTableContext } from '../../hooks/useTableContext';
   import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
   import { ColumnHeightOutlined } from '@ant-design/icons-vue';
+
   import { useI18n } from '/@/hooks/web/useI18n';
+  import { useTableContext } from '../../hooks/useTableContext';
   import { getPopupContainer } from '/@/utils';
 
-  import type { SizeType } from '../../types/table';
-
   export default defineComponent({
     name: 'SizeSetting',
     components: {

+ 7 - 2
src/components/Table/src/components/settings/index.vue

@@ -7,13 +7,18 @@
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, computed } from 'vue';
+  import type { PropType } from 'vue';
   import type { TableSetting } from '../../types/table';
-  import { useI18n } from '/@/hooks/web/useI18n';
+
+  import { defineComponent, computed } from 'vue';
+
   import ColumnSetting from './ColumnSetting.vue';
   import SizeSetting from './SizeSetting.vue';
   import RedoSetting from './RedoSetting.vue';
   import FullScreenSetting from './FullScreenSetting.vue';
+
+  import { useI18n } from '/@/hooks/web/useI18n';
+
   export default defineComponent({
     name: 'TableSetting',
     components: {

+ 5 - 6
src/components/Table/src/const.ts

@@ -6,22 +6,21 @@ const { pageSizeOptions, defaultPageSize, fetchSetting, defaultSortFn, defaultFi
 
 export const ROW_KEY = 'key';
 
-// 可选的每页显示条数;
+// Optional display number per page;
 export const PAGE_SIZE_OPTIONS = pageSizeOptions;
 
-// 每页显示条数
+// Number of items displayed per page
 export const PAGE_SIZE = defaultPageSize;
 
-// 通用接口字段设置
+// Common interface field settings
 export const FETCH_SETTING = fetchSetting;
 
-// 配置通用排序函数
+// Configure general sort function
 export const DEFAULT_SORT_FN = defaultSortFn;
 
 export const DEFAULT_FILTER_FN = defaultFilterFn;
 
-//  表格单元格默认布局
+//  Default layout of table cells
 export const DEFAULT_ALIGN = 'center';
-
 export const INDEX_COLUMN_FLAG = 'INDEX';
 export const ACTION_COLUMN_FLAG = 'ACTION';

+ 11 - 15
src/components/Table/src/hooks/useColumns.ts

@@ -1,15 +1,18 @@
 import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
 import type { PaginationProps } from '../types/pagination';
-import { unref, ComputedRef, Ref, computed, watch, ref, toRaw } from 'vue';
-import { isBoolean, isArray, isString, isObject } from '/@/utils/is';
-import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
+import type { ComputedRef } from 'vue';
+
+import { unref, Ref, computed, watch, ref, toRaw } from 'vue';
+
+import { renderEditCell } from '../components/editable';
+
 import { useI18n } from '/@/hooks/web/useI18n';
+
+import { isBoolean, isArray, isString, isObject, isFunction } from '/@/utils/is';
 import { isEqual, cloneDeep } from 'lodash-es';
-import { isFunction } from '/@/utils/is';
 import { formatToDate } from '/@/utils/dateUtil';
-import { renderEditCell } from '../components/editable';
 
-const { t } = useI18n();
+import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
 
 function handleItem(item: BasicColumn, ellipsis: boolean) {
   const { key, dataIndex, children } = item;
@@ -43,6 +46,8 @@ function handleIndexColumn(
   getPaginationRef: ComputedRef<boolean | PaginationProps>,
   columns: BasicColumn[]
 ) {
+  const { t } = useI18n();
+
   const { showIndexColumn, indexColumnProps, isTreeTable } = unref(propsRef);
 
   let pushIndexColumns = false;
@@ -163,15 +168,6 @@ export function useColumns(
     }
   );
 
-  // watchEffect(() => {
-  //   const columns = toRaw(unref(propsRef).columns);
-  //   console.log('======================');
-  //   console.log(111);
-  //   console.log('======================');
-  //   columnsRef.value = columns;
-  //   cacheColumns = columns?.filter((item) => !item.flag) ?? [];
-  // });
-
   function setCacheColumnsByField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
     if (!dataIndex || !value) {
       return;

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

@@ -52,11 +52,6 @@ export function useDataSource(
   });
   const dataSourceRef = ref<Recordable[]>([]);
 
-  // watchEffect(() => {
-  //   const { dataSource, api } = unref(propsRef);
-  //   !api && dataSource && (dataSourceRef.value = dataSource);
-  // });
-
   watchEffect(() => {
     tableData.value = unref(dataSourceRef);
   });

+ 1 - 3
src/components/Table/src/hooks/useLoading.ts

@@ -11,9 +11,7 @@ export function useLoading(props: ComputedRef<BasicTableProps>) {
     }
   );
 
-  const getLoading = computed(() => {
-    return unref(loadingRef);
-  });
+  const getLoading = computed(() => unref(loadingRef));
 
   function setLoading(loading: boolean) {
     loadingRef.value = loading;

+ 2 - 2
src/components/Table/src/hooks/usePagination.tsx

@@ -25,11 +25,11 @@ function itemRender({ page, type, originalElement }: ItemRender) {
 }
 
 export function usePagination(refProps: ComputedRef<BasicTableProps>) {
-  const configRef = ref<PaginationProps>({});
+  const { t } = useI18n();
 
+  const configRef = ref<PaginationProps>({});
   const show = ref(true);
 
-  const { t } = useI18n();
   const getPaginationInfo = computed((): PaginationProps | boolean => {
     const { pagination } = unref(refProps);
 

+ 16 - 6
src/components/Table/src/hooks/useTable.ts

@@ -1,12 +1,13 @@
 import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
 import type { PaginationProps } from '../types/pagination';
 import type { DynamicProps } from '/#/utils';
-import { getDynamicProps } from '/@/utils';
+import type { FormActionType } from '/@/components/Form';
+import type { WatchStopHandle } from 'vue';
 
+import { getDynamicProps } from '/@/utils';
 import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
 import { isProdMode } from '/@/utils/env';
 import { error } from '/@/utils/log';
-import type { FormActionType } from '/@/components/Form';
 
 type Props = Partial<DynamicProps<BasicTableProps>>;
 
@@ -21,6 +22,8 @@ export function useTable(
   const loadedRef = ref<Nullable<boolean>>(false);
   const formRef = ref<Nullable<UseTableMethod>>(null);
 
+  let stopWatch: WatchStopHandle;
+
   function register(instance: TableActionType, formInstance: UseTableMethod) {
     isProdMode() &&
       onUnmounted(() => {
@@ -28,15 +31,16 @@ export function useTable(
         loadedRef.value = null;
       });
 
-    if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) {
-      return;
-    }
+    if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return;
+
     tableRef.value = instance;
     formRef.value = formInstance;
     tableProps && instance.setProps(getDynamicProps(tableProps));
     loadedRef.value = true;
 
-    watch(
+    stopWatch?.();
+
+    stopWatch = watch(
       () => tableProps,
       () => {
         tableProps && instance.setProps(getDynamicProps(tableProps));
@@ -128,6 +132,12 @@ export function useTable(
     getShowPagination: () => {
       return toRaw(getTableInstance().getShowPagination());
     },
+    expandAll: () => {
+      getTableInstance().expandAll();
+    },
+    collapseAll: () => {
+      getTableInstance().collapseAll();
+    },
   };
 
   return [register, methods];

+ 59 - 0
src/components/Table/src/hooks/useTableExpand.ts

@@ -0,0 +1,59 @@
+import type { ComputedRef, Ref } from 'vue';
+import type { BasicTableProps } from '../types/table';
+
+import { computed, unref, ref, toRaw } from 'vue';
+import { ROW_KEY } from '../const';
+
+export function useTableExpand(
+  propsRef: ComputedRef<BasicTableProps>,
+  tableData: Ref<Recordable[]>,
+  emit: EmitType
+) {
+  const expandedRowKeys = ref<string[]>([]);
+
+  const getAutoCreateKey = computed(() => {
+    return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
+  });
+
+  const getRowKey = computed(() => {
+    const { rowKey } = unref(propsRef);
+    return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
+  });
+
+  const getExpandOption = computed(() => {
+    const { isTreeTable } = unref(propsRef);
+    if (!isTreeTable) return {};
+
+    return {
+      expandedRowKeys: unref(expandedRowKeys),
+      onExpandedRowsChange: (keys: string[]) => {
+        expandedRowKeys.value = keys;
+        emit('expanded-rows-change', keys);
+      },
+    };
+  });
+
+  function expandAll() {
+    const keys = getAllKeys();
+    expandedRowKeys.value = keys;
+  }
+
+  function getAllKeys(data?: Recordable[]) {
+    const keys: string[] = [];
+    const { childrenColumnName } = unref(propsRef);
+    toRaw(data || unref(tableData)).forEach((item) => {
+      keys.push(item[unref(getRowKey) as string]);
+      const children = item[childrenColumnName || 'children'];
+      if (children?.length) {
+        keys.push(...getAllKeys(children));
+      }
+    });
+    return keys;
+  }
+
+  function collapseAll() {
+    expandedRowKeys.value = [];
+  }
+
+  return { getExpandOption, expandAll, collapseAll };
+}

+ 4 - 2
src/components/Table/src/hooks/useTableHeader.ts

@@ -1,9 +1,11 @@
 import type { ComputedRef, Slots } from 'vue';
 import type { BasicTableProps } from '../types/table';
+
 import { unref, computed, h } from 'vue';
-import { isString } from '/@/utils/is';
 import TableHeader from '../components/TableHeader.vue';
-import { getSlot } from '../../../../utils/helper/tsxHelper';
+
+import { isString } from '/@/utils/is';
+import { getSlot } from '/@/utils/helper/tsxHelper';
 
 export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots) {
   const getHeaderProps = computed(

+ 1 - 0
src/components/Table/src/hooks/useTableStyle.ts

@@ -1,5 +1,6 @@
 import type { ComputedRef } from 'vue';
 import type { BasicTableProps, TableCustomRecord } from '../types/table';
+
 import { unref } from 'vue';
 import { isFunction } from '/@/utils/is';
 export function useTableStyle(propsRef: ComputedRef<BasicTableProps>, prefixCls: string) {

+ 1 - 14
src/components/Table/src/props.ts

@@ -12,45 +12,32 @@ import type { FormProps } from '/@/components/Form';
 import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING } from './const';
 import { propTypes } from '/@/utils/propTypes';
 
-// 注释看 types/table
 export const basicProps = {
   clickToRowSelect: propTypes.bool.def(true),
-
   isTreeTable: propTypes.bool.def(false),
-
-  tableSetting: {
-    type: Object as PropType<TableSetting>,
-  },
-
+  tableSetting: propTypes.shape<TableSetting>({}),
   inset: propTypes.bool,
-
   sortFn: {
     type: Function as PropType<(sortInfo: SorterResult) => any>,
     default: DEFAULT_SORT_FN,
   },
-
   filterFn: {
     type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
     default: DEFAULT_FILTER_FN,
   },
-
   showTableSetting: propTypes.bool,
   autoCreateKey: propTypes.bool.def(true),
   striped: propTypes.bool.def(true),
   showSummary: propTypes.bool,
-
   summaryFunc: {
     type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
     default: null,
   },
-
   summaryData: {
     type: Array as PropType<Recordable[]>,
     default: null,
   },
-
   indentSize: propTypes.number.def(24),
-
   canColDrag: propTypes.bool.def(true),
   api: {
     type: Function as PropType<(...arg: any[]) => Promise<any>>,

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

@@ -1,107 +0,0 @@
-@border-color: #cecece4d;
-
-@prefix-cls: ~'@{namespace}-basic-table';
-
-.@{prefix-cls} {
-  &-form-container {
-    padding: 16px;
-
-    .ant-form {
-      padding: 12px 10px 6px 10px;
-      margin-bottom: 16px;
-      background: #fff;
-      border-radius: 4px;
-    }
-
-    // .ant-table-wrapper {
-    //   border-radius: 2px;
-    // }
-  }
-
-  &-row__striped {
-    td {
-      background: #fafafa;
-    }
-  }
-
-  &--inset {
-    .ant-table-wrapper {
-      padding: 0;
-    }
-  }
-
-  .ant-tag {
-    margin-right: 0;
-  }
-
-  .ant-table-wrapper {
-    padding: 6px;
-    background: #fff;
-    border-radius: 2px;
-
-    .ant-table-title {
-      padding: 0 0 8px 0 !important;
-    }
-
-    .ant-table.ant-table-bordered .ant-table-title {
-      border: none !important;
-    }
-  }
-
-  //
-  .ant-table {
-    width: 100%;
-    overflow-x: hidden;
-    // border: none;
-
-    &-title {
-      display: flex;
-      padding: 8px 6px;
-      border-bottom: none;
-      justify-content: space-between;
-      align-items: center;
-    }
-
-    // .ant-table-thead > tr > th,
-    // .ant-table-header {
-    //   background: #f1f3f4;
-    //   background-color: #f1f3f4 !important;
-    // }
-
-    .ant-table-tbody > tr.ant-table-row-selected td {
-      background: fade(@primary-color, 8%) !important;
-    }
-  }
-
-  // .ant-table-tbody > tr > td,
-  // .ant-table-tbody > tr > th,
-  // .ant-table-thead > tr > td,
-  // .ant-table-thead > tr > th {
-  //   white-space: pre;
-  // }
-
-  .ant-pagination {
-    margin: 10px 0 0 0;
-  }
-
-  .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;
-    }
-  }
-}

+ 3 - 1
src/components/Table/src/types/table.ts

@@ -87,6 +87,8 @@ export interface TableActionType {
   reload: (opt?: FetchParams) => Promise<void>;
   getSelectRows: <T = Recordable>() => T[];
   clearSelectedRowKeys: () => void;
+  expandAll: () => void;
+  collapseAll: () => void;
   getSelectRowKeys: () => string[];
   deleteSelectRowByKey: (key: string) => void;
   setPagination: (info: Partial<PaginationProps>) => void;
@@ -208,7 +210,7 @@ export interface BasicTableProps<T = any> {
    * @default 'children'
    * @type string | string[]
    */
-  childrenColumnName?: string | string[];
+  childrenColumnName?: string;
 
   /**
    * Override default table elements

+ 16 - 14
src/views/demo/table/TreeTable.vue

@@ -1,29 +1,31 @@
 <template>
   <div class="p-4">
-    <BasicTable
-      :rowSelection="{ type: 'checkbox' }"
-      title="树形表格"
-      titleHelpMessage="树形组件不能和序列号列同时存在"
-      :columns="columns"
-      :dataSource="data"
-      rowKey="id"
-      :indentSize="20"
-      isTreeTable
-    />
+    <BasicTable @register="register">
+      <template #toolbar>
+        <a-button type="primary" @click="expandAll">展开全部</a-button>
+        <a-button type="primary" @click="collapseAll">折叠全部</a-button>
+      </template>
+    </BasicTable>
   </div>
 </template>
 <script lang="ts">
   import { defineComponent } from 'vue';
-  import { BasicTable } from '/@/components/Table';
+  import { BasicTable, useTable } from '/@/components/Table';
   import { getBasicColumns, getTreeTableData } from './tableData';
 
   export default defineComponent({
     components: { BasicTable },
     setup() {
-      return {
+      const [register, { expandAll, collapseAll }] = useTable({
+        title: '树形表格',
+        isTreeTable: true,
+        rowSelection: { type: 'checkbox' },
+        titleHelpMessage: '树形组件不能和序列号列同时存在',
         columns: getBasicColumns(),
-        data: getTreeTableData(),
-      };
+        dataSource: getTreeTableData(),
+        rowKey: 'id',
+      });
+      return { register, expandAll, collapseAll };
     },
   });
 </script>

+ 4 - 4
yarn.lock

@@ -2180,10 +2180,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
   dependencies:
     color-convert "^2.0.1"
 
-ant-design-vue@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.1.tgz#3a5964523aac10fd2b16d84d651145cd2b65f1d5"
-  integrity sha512-CFIF+srTui4ZwdKPBXNoFA9/0fkSpypanQeOts0PAq1vEuMLxUoZHapDDn7wzsxZH3sYLF+mvMp8gYMRkaNn+w==
+ant-design-vue@2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.1.0.tgz#2489240f638f39874281e237544b857ebce52d18"
+  integrity sha512-wzgwHRuwZrSvixccNlvas2gTWBkmfMrifbSsP+ga8VV6F0C6DdlimeFo+P99AxnVgpNVk8OUq9RVDQjb1UGk6g==
   dependencies:
     "@ant-design-vue/use" "^0.0.1-0"
     "@ant-design/icons-vue" "^6.0.0"