tangning 9 月之前
父节点
当前提交
911bf1c627
共有 49 个文件被更改,包括 2490 次插入1416 次删除
  1. 278 285
      src/components/Table/src/BasicTable.vue
  2. 7 1
      src/components/Table/src/componentMap.ts
  3. 7 7
      src/components/Table/src/components/EditTableHeaderIcon.vue
  4. 26 14
      src/components/Table/src/components/HeaderCell.vue
  5. 90 95
      src/components/Table/src/components/TableAction.vue
  6. 66 68
      src/components/Table/src/components/TableFooter.vue
  7. 39 32
      src/components/Table/src/components/TableHeader.vue
  8. 37 34
      src/components/Table/src/components/TableImg.vue
  9. 56 0
      src/components/Table/src/components/TableSelectionBar.vue
  10. 26 31
      src/components/Table/src/components/TableTitle.vue
  11. 3 3
      src/components/Table/src/components/editable/CellComponent.ts
  12. 167 95
      src/components/Table/src/components/editable/EditableCell.vue
  13. 1 1
      src/components/Table/src/components/editable/helper.ts
  14. 3 3
      src/components/Table/src/components/editable/index.ts
  15. 488 285
      src/components/Table/src/components/settings/ColumnSetting.vue
  16. 5 21
      src/components/Table/src/components/settings/FullScreenSetting.vue
  17. 7 17
      src/components/Table/src/components/settings/RedoSetting.vue
  18. 33 36
      src/components/Table/src/components/settings/SizeSetting.vue
  19. 31 38
      src/components/Table/src/components/settings/index.vue
  20. 28 0
      src/components/Table/src/helper.ts
  21. 68 44
      src/components/Table/src/hooks/useColumns.ts
  22. 19 34
      src/components/Table/src/hooks/useCustomRow.ts
  23. 107 102
      src/components/Table/src/hooks/useDataSource.ts
  24. 84 0
      src/components/Table/src/hooks/usePagination.ts
  25. 88 16
      src/components/Table/src/hooks/useRowSelection.ts
  26. 55 0
      src/components/Table/src/hooks/useScrollTo.ts
  27. 27 11
      src/components/Table/src/hooks/useTable.ts
  28. 90 16
      src/components/Table/src/hooks/useTableExpand.ts
  29. 3 8
      src/components/Table/src/hooks/useTableFooter.ts
  30. 2 2
      src/components/Table/src/hooks/useTableForm.ts
  31. 9 2
      src/components/Table/src/hooks/useTableHeader.ts
  32. 228 56
      src/components/Table/src/hooks/useTableScroll.ts
  33. 22 23
      src/components/Table/src/props.ts
  34. 1 1
      src/components/Table/src/types/column.ts
  35. 5 1
      src/components/Table/src/types/componentType.ts
  36. 17 1
      src/components/Table/src/types/pagination.ts
  37. 78 19
      src/components/Table/src/types/table.ts
  38. 14 0
      src/components/Table/src/types/tableAction.ts
  39. 2 0
      src/enums/cacheEnum.ts
  40. 1 0
      src/enums/pageEnum.ts
  41. 9 0
      src/settings/designSetting.ts
  42. 109 0
      src/store/modules/tableSetting.ts
  43. 35 0
      src/utils/is.ts
  44. 2 1
      src/views/mediaLibrary/list.vue
  45. 1 1
      src/views/mediaLibrary/modal/detailModal.vue
  46. 2 1
      src/views/mediaLibrary/modal/grouping.vue
  47. 4 1
      src/views/mediaLibrary/modal/uploadModal.vue
  48. 7 7
      src/views/productOperation/cameraScene.vue
  49. 3 3
      src/views/productOperation/modal/PowersModal.vue

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

@@ -1,6 +1,7 @@
 <template>
   <div ref="wrapRef" :class="getWrapperClass">
     <BasicForm
+      ref="formRef"
       submitOnReset
       v-bind="getFormProps"
       v-if="getBindValues.useSearchForm"
@@ -20,40 +21,43 @@
       :rowClassName="getRowClassName"
       v-show="getEmptyDataIsShowTable"
       @change="handleTableChange"
-      @resizeColumn="handleResizeColumn"
+      @resize-column="setColumnWidth"
+      @expand="handleTableExpand"
     >
       <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 #headerCell="{ column }">
+        <slot name="headerCell" v-bind="{ column }">
+          <HeaderCell :column="column" />
+        </slot>
+      </template>
+      <template #bodyCell="data">
+        <slot name="bodyCell" v-bind="data || {}"></slot>
       </template>
     </Table>
   </div>
 </template>
-<script lang="ts">
+<script lang="ts" setup>
   import type {
     BasicTableProps,
     TableActionType,
     SizeType,
     ColumnChangeParam,
   } from './types/table';
-
-  import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect } from 'vue';
+  import { ref, computed, unref, toRaw, inject, watch, useAttrs, useSlots } from 'vue';
   import { Table } from 'ant-design-vue';
-  import { BasicForm, useForm } from '/@/components/Form/index';
-  import { PageWrapperFixedHeightKey } from '/@/components/Page';
-  import expandIcon from './components/ExpandIcon';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { PageWrapperFixedHeightKey } from '/@/enums/pageEnum';
   import HeaderCell from './components/HeaderCell.vue';
-  import { InnerHandlers } from './types/table';
-
+  import { InnerHandlers, InnerMethods } from './types/table';
   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 { useTableScrollTo } from './hooks/useScrollTo';
   import { useCustomRow } from './hooks/useCustomRow';
   import { useTableStyle } from './hooks/useTableStyle';
   import { useTableHeader } from './hooks/useTableHeader';
@@ -62,278 +66,263 @@
   import { useTableFooter } from './hooks/useTableFooter';
   import { useTableForm } from './hooks/useTableForm';
   import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { omit } from 'lodash-es';
+  import { omit, debounce } from 'lodash-es';
+  import { useElementSize } from '@vueuse/core';
   import { basicProps } from './props';
   import { isFunction } from '/@/utils/is';
-  import { warn } from '/@/utils/log';
 
-  export default defineComponent({
-    components: {
-      Table,
-      BasicForm,
-      HeaderCell,
+  // defineOptions({ name: 'BasicTable' });
+
+  const props = defineProps(basicProps);
+
+  const emit = defineEmits([
+    'fetch-success',
+    'fetch-error',
+    'selection-change',
+    'register',
+    'row-click',
+    'row-dbClick',
+    'row-contextmenu',
+    'row-mouseenter',
+    'row-mouseleave',
+    'edit-end',
+    'edit-cancel',
+    'edit-row-end',
+    'edit-change',
+    'expanded-rows-change',
+    'change',
+    'columns-change',
+  ]);
+
+  const attrs = useAttrs();
+  const slots = useSlots();
+
+  const tableElRef = ref(null);
+  const tableData = ref([]);
+
+  const wrapRef = ref(null);
+  const formRef = ref(null);
+  const innerPropsRef = ref<Partial<BasicTableProps>>();
+
+  const { height } = useElementSize(wrapRef);
+
+  const { prefixCls } = useDesign('basic-table');
+  const [registerForm, formActions] = useForm();
+
+  const getProps = computed(() => {
+    return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
+  });
+
+  const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
+
+  const { getLoading, setLoading } = useLoading(getProps);
+  const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } =
+    usePagination(getProps);
+
+  const {
+    getRowSelection,
+    getRowSelectionRef,
+    getSelectRows,
+    setSelectedRows,
+    clearSelectedRowKeys,
+    getSelectRowKeys,
+    deleteSelectRowByKey,
+    setSelectedRowKeys,
+  } = useRowSelection(getProps, tableData, emit);
+
+  const {
+    handleTableChange: onTableChange,
+    getDataSourceRef,
+    getDataSource,
+    getRawDataSource,
+    getSearchInfo,
+    setTableData,
+    updateTableDataRecord,
+    deleteTableDataRecord,
+    insertTableDataRecord,
+    findTableDataRecord,
+    fetch,
+    getRowKey,
+    reload,
+    getAutoCreateKey,
+    updateTableData,
+  } = useDataSource(
+    getProps,
+    {
+      tableData,
+      getPaginationInfo,
+      setLoading,
+      setPagination,
+      getFieldsValue: formActions.getFieldsValue,
+      clearSelectedRowKeys,
     },
-    props: basicProps,
-    emits: [
-      'fetch-success',
-      'fetch-error',
-      'selection-change',
-      'register',
-      'row-click',
-      'row-dbClick',
-      'row-contextmenu',
-      'row-mouseenter',
-      'row-mouseleave',
-      'edit-end',
-      'edit-cancel',
-      'edit-row-end',
-      'edit-change',
-      'expanded-rows-change',
-      'change',
-      'columns-change',
-    ],
-    setup(props, { attrs, emit, slots, expose }) {
-      const tableElRef = ref(null);
-      const tableData = ref<Recordable[]>([]);
-
-      const wrapRef = ref(null);
-      const innerPropsRef = ref<Partial<BasicTableProps>>();
-      const { prefixCls } = useDesign('basic-table');
-      const [registerForm, formActions] = useForm();
-
-      const getProps = computed(() => {
-        return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
-      });
-
-      const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
-      watchEffect(() => {
-        unref(isFixedHeightPage) &&
-          props.canResize &&
-          warn(
-            "'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)",
-          );
-      });
-
-      const { getLoading, setLoading } = useLoading(getProps);
-      const {
-        getPaginationInfo,
-        getPagination,
-        setPagination,
-        setShowPagination,
-        getShowPagination,
-      } = usePagination(getProps);
-
-      const {
-        getRowSelection,
-        getRowSelectionRef,
-        getSelectRows,
-        clearSelectedRowKeys,
-        getSelectRowKeys,
-        deleteSelectRowByKey,
-        setSelectedRowKeys,
-      } = useRowSelection(getProps, tableData, emit);
-
-      const {
-        handleTableChange: onTableChange,
-        getDataSourceRef,
-        getDataSource,
-        getRawDataSource,
-        setTableData,
-        updateTableDataRecord,
-        deleteTableDataRecord,
-        insertTableDataRecord,
-        findTableDataRecord,
-        fetch,
-        getRowKey,
-        reload,
-        getAutoCreateKey,
-        updateTableData,
-      } = useDataSource(
-        getProps,
-        {
-          tableData,
-          getPaginationInfo,
-          setLoading,
-          setPagination,
-          getFieldsValue: formActions.getFieldsValue,
-          clearSelectedRowKeys,
-        },
-        emit,
-      );
-
-      function handleTableChange(...args) {
-        onTableChange.call(undefined, ...args);
-        emit('change', ...args);
-        // 解决通过useTable注册onChange时不起作用的问题
-        const { onChange } = unref(getProps);
-        onChange && isFunction(onChange) && onChange.call(undefined, ...args);
-      }
+    emit,
+  );
+
+  function handleTableChange(pagination: any, filters: any, sorter: any, extra: any) {
+    onTableChange(pagination, filters, sorter);
+    emit('change', pagination, filters, sorter);
+    // 解决通过useTable注册onChange时不起作用的问题
+    const { onChange } = unref(getProps);
+    onChange && isFunction(onChange) && onChange(pagination, filters, sorter, extra);
+  }
 
-      const {
-        getViewColumns,
-        getColumns,
-        setCacheColumnsByField,
-        setColumns,
-        getColumnsRef,
-        getCacheColumns,
-      } = useColumns(getProps, getPaginationInfo);
-
-      const { getScrollRef, redoHeight } = useTableScroll(
-        getProps,
-        tableElRef,
-        getColumnsRef,
-        getRowSelectionRef,
-        getDataSourceRef,
-      );
-
-      const { customRow } = useCustomRow(getProps, {
-        setSelectedRowKeys,
-        getSelectRowKeys,
-        clearSelectedRowKeys,
-        getAutoCreateKey,
-        emit,
-      });
-
-      const { getRowClassName } = useTableStyle(getProps, prefixCls);
-
-      const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit);
-
-      const handlers: InnerHandlers = {
-        onColumnsChange: (data: ColumnChangeParam[]) => {
-          emit('columns-change', data);
-          // support useTable
-          unref(getProps).onColumnsChange?.(data);
-        },
-      };
-
-      const { getHeaderProps } = useTableHeader(getProps, slots, handlers);
-
-      const { getFooterProps } = useTableFooter(
-        getProps,
-        getScrollRef,
-        tableElRef,
-        getDataSourceRef,
-      );
-
-      const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } =
-        useTableForm(getProps, slots, fetch, getLoading);
-      const getBindValues = computed(() => {
-        const dataSource = unref(getDataSourceRef);
-        let propsData: Recordable = {
-          // ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
-          ...attrs,
-          customRow,
-          expandIcon: slots.expandIcon ? null : expandIcon(),
-          ...unref(getProps),
-          ...unref(getHeaderProps),
-          scroll: unref(getScrollRef),
-          loading: unref(getLoading),
-          tableLayout: 'fixed',
-          rowSelection: unref(getRowSelectionRef),
-          rowKey: unref(getRowKey),
-          columns: toRaw(unref(getViewColumns)),
-          pagination: toRaw(unref(getPaginationInfo)),
-          dataSource,
-          footer: unref(getFooterProps),
-          ...unref(getExpandOption),
-        };
-        if (slots.expandedRowRender) {
-          propsData = omit(propsData, 'scroll');
-        }
-
-        propsData = omit(propsData, ['class', 'onChange']);
-        return propsData;
-      });
-      console.log('getFormSlotKeys', attrs, 'getBindValues', getBindValues.value);
-      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) {
-          return true;
-        }
-        return !!unref(getDataSourceRef).length;
-      });
-
-      function setProps(props: Partial<BasicTableProps>) {
-        innerPropsRef.value = { ...unref(innerPropsRef), ...props };
-      }
+  const {
+    getViewColumns,
+    getColumns,
+    setCacheColumnsByField,
+    setCacheColumns,
+    setColumnWidth,
+    setColumns,
+    getColumnsRef,
+    getCacheColumns,
+  } = useColumns(getProps, getPaginationInfo);
+
+  const { getScrollRef, redoHeight } = useTableScroll(
+    getProps,
+    tableElRef,
+    getColumnsRef,
+    getRowSelectionRef,
+    getDataSourceRef,
+    wrapRef,
+    formRef,
+  );
+  const debounceRedoHeight = debounce(redoHeight, 50);
+
+  const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef);
+
+  const { customRow } = useCustomRow(getProps, {
+    setSelectedRowKeys,
+    getSelectRowKeys,
+    clearSelectedRowKeys,
+    getAutoCreateKey,
+    emit,
+  });
+
+  const { getRowClassName } = useTableStyle(getProps, prefixCls);
+
+  const { getExpandOption, expandAll, expandRows, collapseRows, collapseAll, handleTableExpand } =
+    useTableExpand(getProps, tableData, emit);
 
-      const tableAction: TableActionType = {
-        reload,
-        getSelectRows,
-        clearSelectedRowKeys,
-        getSelectRowKeys,
-        deleteSelectRowByKey,
-        setPagination,
-        setTableData,
-        updateTableDataRecord,
-        deleteTableDataRecord,
-        insertTableDataRecord,
-        findTableDataRecord,
-        redoHeight,
-        setSelectedRowKeys,
-        setColumns,
-        setLoading,
-        getDataSource,
-        getRawDataSource,
-        setProps,
-        getRowSelection,
-        getPaginationRef: getPagination,
-        getColumns,
-        getCacheColumns,
-        emit,
-        updateTableData,
-        setShowPagination,
-        getShowPagination,
-        setCacheColumnsByField,
-        expandAll,
-        collapseAll,
-        getSize: () => {
-          return unref(getBindValues).size as SizeType;
-        },
-      };
-      createTableContext({ ...tableAction, wrapRef, getBindValues });
-
-      expose(tableAction);
-
-      emit('register', tableAction, formActions);
-
-      return {
-        tableElRef,
-        getBindValues,
-        getLoading,
-        registerForm,
-        handleSearchInfoChange,
-        getEmptyDataIsShowTable,
-        handleTableChange,
-        getRowClassName,
-        wrapRef,
-        tableAction,
-        redoHeight,
-        getFormProps: getFormProps as any,
-        replaceFormSlotKey,
-        getFormSlotKeys,
-        getWrapperClass,
-        handleResizeColumn: (w, col) => {
-          // col.width = w;
-          setCacheColumnsByField(col.dataIndex, { width: w });
-        },
-        columns: getViewColumns,
-      };
+  const handlers: InnerHandlers = {
+    onColumnsChange: (data: ColumnChangeParam[]) => {
+      emit('columns-change', data);
+      // support useTable
+      unref(getProps).onColumnsChange?.(data);
     },
+  };
+
+  const methods: InnerMethods = {
+    clearSelectedRowKeys,
+    getSelectRowKeys,
+  };
+
+  const { getHeaderProps } = useTableHeader(getProps, slots, handlers, methods);
+
+  const { getFooterProps } = useTableFooter(getProps, getScrollRef, tableElRef, getDataSourceRef);
+
+  const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } =
+    useTableForm(getProps, slots, fetch, getLoading);
+
+  const getBindValues = computed(() => {
+    const dataSource = unref(getDataSourceRef);
+    let propsData: any = {
+      ...attrs,
+      customRow,
+      ...unref(getProps),
+      ...unref(getHeaderProps),
+      scroll: unref(getScrollRef),
+      loading: unref(getLoading),
+      tableLayout: 'fixed',
+      rowSelection: unref(getRowSelectionRef),
+      rowKey: unref(getRowKey),
+      columns: toRaw(unref(getViewColumns)),
+      pagination: toRaw(unref(getPaginationInfo)),
+      dataSource,
+      footer: unref(getFooterProps),
+      ...unref(getExpandOption),
+    };
+    // if (slots.expandedRowRender) {
+    //   propsData = omit(propsData, 'scroll');
+    // }
+
+    propsData = omit(propsData, ['class', 'onChange']);
+    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) {
+      return true;
+    }
+    return !!unref(getDataSourceRef).length;
+  });
+
+  watch(height, () => {
+    unref(isFixedHeightPage) && props.canResize && debounceRedoHeight();
+  });
+
+  function setProps(props: Partial<BasicTableProps>) {
+    innerPropsRef.value = { ...unref(innerPropsRef), ...props };
+  }
+
+  const tableAction: TableActionType = {
+    reload,
+    getSelectRows,
+    setSelectedRows,
+    clearSelectedRowKeys,
+    getSelectRowKeys,
+    deleteSelectRowByKey,
+    setPagination,
+    setTableData,
+    updateTableDataRecord,
+    deleteTableDataRecord,
+    insertTableDataRecord,
+    findTableDataRecord,
+    redoHeight,
+    setSelectedRowKeys,
+    setColumns,
+    setLoading,
+    getDataSource,
+    getRawDataSource,
+    getSearchInfo,
+    setProps,
+    getRowSelection,
+    getPaginationRef: getPagination,
+    getColumns,
+    getCacheColumns,
+    emit,
+    updateTableData,
+    setShowPagination,
+    getShowPagination,
+    setCacheColumnsByField,
+    expandAll,
+    collapseAll,
+    expandRows,
+    collapseRows,
+    scrollTo,
+    getSize: () => {
+      return unref(getBindValues).size as SizeType;
+    },
+    setCacheColumns,
+  };
+  createTableContext({ ...tableAction, wrapRef, getBindValues });
+
+  emit('register', tableAction, formActions);
+
+  defineExpose({ tableElRef, ...tableAction });
 </script>
 <style lang="less">
   @border-color: #cecece4d;
@@ -349,10 +338,11 @@
 
   .@{prefix-cls} {
     max-width: 100%;
+    height: 100%;
 
     &-row__striped {
       td {
-        background-color: @app-content-background;
+        background-color: @app-content-background !important;
       }
     }
 
@@ -360,21 +350,24 @@
       padding: 16px;
 
       .ant-form {
-        padding: 12px 10px 6px;
+        width: 100%;
         margin-bottom: 16px;
-        background-color: @component-background;
+        padding: 12px 10px 6px;
         border-radius: 2px;
+        background-color: @component-background;
       }
     }
 
-    .ant-tag {
-      margin-right: 0;
+    .ant-table-cell {
+      .ant-tag {
+        margin-right: 0;
+      }
     }
 
     .ant-table-wrapper {
       padding: 6px;
-      background-color: @component-background;
       border-radius: 2px;
+      background-color: @component-background;
 
       .ant-table-title {
         min-height: 40px;
@@ -392,10 +385,10 @@
 
       &-title {
         display: flex;
+        align-items: center;
+        justify-content: space-between;
         padding: 8px 6px;
         border-bottom: none;
-        justify-content: space-between;
-        align-items: center;
       }
 
       //.ant-table-tbody > tr.ant-table-row-selected td {
@@ -403,7 +396,7 @@
       //}
     }
 
-    .ant-pagination {
+    .ant-table-wrapper .ant-pagination {
       margin: 10px 0 0;
     }
 

+ 7 - 1
src/components/Table/src/componentMap.ts

@@ -7,9 +7,11 @@ import {
   Switch,
   DatePicker,
   TimePicker,
+  AutoComplete,
+  Radio,
 } from 'ant-design-vue';
 import type { ComponentType } from './types/componentType';
-import { ApiSelect, ApiTreeSelect } from '/@/components/Form';
+import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form';
 
 const componentMap = new Map<ComponentType, Component>();
 
@@ -17,11 +19,15 @@ componentMap.set('Input', Input);
 componentMap.set('InputNumber', InputNumber);
 componentMap.set('Select', Select);
 componentMap.set('ApiSelect', ApiSelect);
+componentMap.set('AutoComplete', AutoComplete);
 componentMap.set('ApiTreeSelect', ApiTreeSelect);
 componentMap.set('Switch', Switch);
 componentMap.set('Checkbox', Checkbox);
 componentMap.set('DatePicker', DatePicker);
 componentMap.set('TimePicker', TimePicker);
+componentMap.set('RadioGroup', Radio.Group);
+componentMap.set('RadioButtonGroup', RadioButtonGroup);
+componentMap.set('ApiRadioGroup', ApiRadioGroup);
 
 export function add(compName: ComponentType, component: Component) {
   componentMap.set(compName, component);

+ 7 - 7
src/components/Table/src/components/EditTableHeaderIcon.vue

@@ -1,16 +1,16 @@
 <template>
-  <span>
+  <span class="edit-header-cell">
     <slot></slot>
     {{ title }}
     <FormOutlined />
   </span>
 </template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
+<script lang="ts" setup>
   import { FormOutlined } from '@ant-design/icons-vue';
-  export default defineComponent({
-    name: 'EditTableHeaderIcon',
-    components: { FormOutlined },
-    props: { title: { type: String, default: '' } },
+
+  // defineOptions({ name: 'EditTableHeaderIcon' });
+
+  defineProps({
+    title: { type: String, default: '' },
   });
 </script>

+ 26 - 14
src/components/Table/src/components/HeaderCell.vue

@@ -1,17 +1,11 @@
-<template>
-  <EditTableHeaderCell v-if="getIsEdit">
-    {{ getTitle }}
-  </EditTableHeaderCell>
-  <span v-else>{{ getTitle }}</span>
-  <BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
-</template>
-<script lang="ts">
+<script lang="tsx">
   import type { PropType } from 'vue';
   import type { BasicColumn } from '../types/table';
   import { defineComponent, computed } from 'vue';
   import BasicHelp from '/@/components/Basic/src/BasicHelp.vue';
   import EditTableHeaderCell from './EditTableHeaderIcon.vue';
   import { useDesign } from '/@/hooks/web/useDesign';
+  import { ColumnType } from 'ant-design-vue/lib/table/interface';
 
   export default defineComponent({
     name: 'TableHeaderCell',
@@ -21,18 +15,37 @@
     },
     props: {
       column: {
-        type: Object as PropType<BasicColumn>,
+        type: Object as PropType<ColumnType<any>>,
         default: () => ({}),
       },
     },
     setup(props) {
       const { prefixCls } = useDesign('basic-table-header-cell');
 
-      const getIsEdit = computed(() => !!props.column?.edit);
-      const getTitle = computed(() => props.column?.customTitle);
-      const getHelpMessage = computed(() => props.column?.helpMessage);
+      const getIsEdit = computed(() => !!(props.column as BasicColumn)?.edit);
+      const getTitle = computed(() => {
+        const column = props.column as BasicColumn;
+        if (typeof column.customHeaderRender === 'function') {
+          return column.customHeaderRender(column);
+        }
+        return column?.customTitle || props.column?.title;
+      });
+      const getHelpMessage = computed(() => (props.column as BasicColumn)?.helpMessage);
 
-      return { prefixCls, getIsEdit, getTitle, getHelpMessage };
+      return () => {
+        return (
+          <div>
+            {getIsEdit.value ? (
+              <EditTableHeaderCell>{getTitle.value}</EditTableHeaderCell>
+            ) : (
+              <span class="default-header-cell">{getTitle.value}</span>
+            )}
+            {getHelpMessage.value && (
+              <BasicHelp text={getHelpMessage.value} class={`${prefixCls}__help`} />
+            )}
+          </div>
+        );
+      };
     },
   });
 </script>
@@ -42,7 +55,6 @@
   .@{prefix-cls} {
     &__help {
       margin-left: 8px;
-      color: rgb(0 0 0 / 65%) !important;
     }
   }
 </style>

+ 90 - 95
src/components/Table/src/components/TableAction.vue

@@ -2,12 +2,12 @@
   <div :class="[prefixCls, getAlign]" @click="onCellClick">
     <template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
       <Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)">
-        <PopConfirmButton v-bind="action">
+        <PopConfirmButton v-bind="omit(action, 'icon')">
           <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
           <template v-if="action.label">{{ action.label }}</template>
         </PopConfirmButton>
       </Tooltip>
-      <PopConfirmButton v-else v-bind="action">
+      <PopConfirmButton v-else v-bind="omit(action, 'icon')">
         <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
         <template v-if="action.label">{{ action.label }}</template>
       </PopConfirmButton>
@@ -30,8 +30,8 @@
     </Dropdown>
   </div>
 </template>
-<script lang="ts">
-  import { defineComponent, PropType, computed, toRaw, unref } from 'vue';
+<script lang="ts" setup>
+  import { PropType, computed, toRaw, unref } from 'vue';
   import { MoreOutlined } from '@ant-design/icons-vue';
   import { Divider, Tooltip, TooltipProps } from 'ant-design-vue';
   import Icon from '/@/components/Icon/index';
@@ -44,109 +44,104 @@
   import { isBoolean, isFunction, isString } from '/@/utils/is';
   import { propTypes } from '/@/utils/propTypes';
   import { ACTION_COLUMN_FLAG } from '../const';
+  import { omit } from 'lodash-es';
 
-  export default defineComponent({
-    name: 'TableAction',
-    components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined, Tooltip },
-    props: {
-      actions: {
-        type: Array as PropType<ActionItem[]>,
-        default: null,
-      },
-      dropDownActions: {
-        type: Array as PropType<ActionItem[]>,
-        default: null,
-      },
-      divider: propTypes.bool.def(true),
-      outside: propTypes.bool,
-      stopButtonPropagation: propTypes.bool.def(false),
-    },
-    setup(props) {
-      const { prefixCls } = useDesign('basic-table-action');
-      let table: Partial<TableActionType> = {};
-      if (!props.outside) {
-        table = useTableContext();
-      }
-
-      const { hasPermission } = usePermission();
-      function isIfShow(action: ActionItem): boolean {
-        const ifShow = action.ifShow;
+  // defineOptions({ name: 'TableAction' });
 
-        let isIfShow = true;
+  const props = defineProps({
+    actions: {
+      type: Array as PropType<ActionItem[]>,
+      default: null,
+    },
+    dropDownActions: {
+      type: Array as PropType<ActionItem[]>,
+      default: null,
+    },
+    divider: propTypes.bool.def(true),
+    outside: propTypes.bool,
+    stopButtonPropagation: propTypes.bool.def(false),
+  });
 
-        if (isBoolean(ifShow)) {
-          isIfShow = ifShow;
-        }
-        if (isFunction(ifShow)) {
-          isIfShow = ifShow(action);
-        }
-        return isIfShow;
-      }
+  const { prefixCls } = useDesign('basic-table-action');
+  let table: Partial<TableActionType> = {};
+  if (!props.outside) {
+    table = useTableContext();
+  }
 
-      const getActions = computed(() => {
-        return (toRaw(props.actions) || [])
-          .filter((action) => {
-            return hasPermission(action.auth) && isIfShow(action);
-          })
-          .map((action) => {
-            const { popConfirm } = action;
-            return {
-              getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
-              type: 'link',
-              size: 'small',
-              ...action,
-              ...(popConfirm || {}),
-              onConfirm: popConfirm?.confirm,
-              onCancel: popConfirm?.cancel,
-              enable: !!popConfirm,
-            };
-          });
-      });
+  const { hasPermission } = usePermission();
+  function isIfShow(action: ActionItem): boolean {
+    const ifShow = action.ifShow;
 
-      const getDropdownList = computed((): any[] => {
-        return (toRaw(props.dropDownActions) || [])
-          .filter((action) => {
-            return hasPermission(action.auth) && isIfShow(action);
-          })
-          .map((action, index) => {
-            const { label, popConfirm } = action;
-            return {
-              ...action,
-              ...popConfirm,
-              onConfirm: popConfirm?.confirm,
-              onCancel: popConfirm?.cancel,
-              text: label,
-              divider: index < props.dropDownActions.length - 1 ? props.divider : false,
-            };
-          });
-      });
+    let isIfShow = true;
 
-      const getAlign = computed(() => {
-        const columns = (table as TableActionType)?.getColumns?.() || [];
-        const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
-        return actionColumn?.align ?? 'left';
-      });
+    if (isBoolean(ifShow)) {
+      isIfShow = ifShow;
+    }
+    if (isFunction(ifShow)) {
+      isIfShow = ifShow(action);
+    }
+    return isIfShow;
+  }
 
-      function getTooltip(data: string | TooltipProps): TooltipProps {
+  const getActions = computed(() => {
+    return (toRaw(props.actions) || [])
+      .filter((action) => {
+        return hasPermission(action.auth) && isIfShow(action);
+      })
+      .map((action) => {
+        const { popConfirm } = action;
         return {
-          getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
-          placement: 'bottom',
-          ...(isString(data) ? { title: data } : data),
+          getPopupContainer: () => unref((table as any)?.wrapRef) ?? document.body,
+          type: 'link',
+          size: 'small',
+          ...action,
+          ...(popConfirm || {}),
+          onConfirm: popConfirm?.confirm,
+          onCancel: popConfirm?.cancel,
+          enable: !!popConfirm,
         };
-      }
+      });
+  });
 
-      function onCellClick(e: MouseEvent) {
-        if (!props.stopButtonPropagation) return;
-        const path = e.composedPath() as HTMLElement[];
-        const isInButton = path.find((ele) => {
-          return ele.tagName?.toUpperCase() === 'BUTTON';
-        });
-        isInButton && e.stopPropagation();
-      }
+  const getDropdownList = computed((): any[] => {
+    const list = (toRaw(props.dropDownActions) || []).filter((action) => {
+      return hasPermission(action.auth) && isIfShow(action);
+    });
+    return list.map((action, index) => {
+      const { label, popConfirm } = action;
+      return {
+        ...action,
+        ...popConfirm,
+        onConfirm: popConfirm?.confirm,
+        onCancel: popConfirm?.cancel,
+        text: label,
+        divider: index < list.length - 1 ? props.divider : false,
+      };
+    });
+  });
 
-      return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip };
-    },
+  const getAlign = computed(() => {
+    const columns = (table as TableActionType)?.getColumns?.() || [];
+    const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
+    return actionColumn?.align ?? 'left';
   });
+
+  function getTooltip(data: string | TooltipProps): TooltipProps {
+    return {
+      getPopupContainer: () => unref((table as any)?.wrapRef) ?? document.body,
+      placement: 'bottom',
+      ...(isString(data) ? { title: data } : data),
+    };
+  }
+
+  function onCellClick(e: MouseEvent) {
+    if (!props.stopButtonPropagation) return;
+    const path = e.composedPath() as HTMLElement[];
+    const isInButton = path.find((ele) => {
+      return ele.tagName?.toUpperCase() === 'BUTTON';
+    });
+    isInButton && e.stopPropagation();
+  }
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-basic-table-action';

+ 66 - 68
src/components/Table/src/components/TableFooter.vue

@@ -1,94 +1,92 @@
 <template>
   <Table
-    v-if="summaryFunc || summaryData"
+    v-if="!!props.summaryFunc || props.summaryData"
     :showHeader="false"
     :bordered="false"
     :pagination="false"
     :dataSource="getDataSource"
-    :rowKey="(r) => r[rowKey]"
+    :rowKey="props.rowKey"
     :columns="getColumns"
     tableLayout="fixed"
-    :scroll="scroll"
+    :scroll="props.scroll"
   />
 </template>
-<script lang="ts">
-  import type { PropType } from 'vue';
-  import { defineComponent, unref, computed, toRaw } from 'vue';
+<script lang="ts" setup>
+  import { unref, computed, toRaw } from 'vue';
   import { Table } from 'ant-design-vue';
   import { cloneDeep } from 'lodash-es';
   import { isFunction } from '/@/utils/is';
-  import type { BasicColumn } from '../types/table';
+  import type { BasicColumn, BasicTableProps } from '../types/table';
   import { INDEX_COLUMN_FLAG } from '../const';
-  import { propTypes } from '/@/utils/propTypes';
   import { useTableContext } from '../hooks/useTableContext';
+  import { ColumnType } from 'ant-design-vue/es/table/interface';
+  import { parseRowKey } from '../helper';
+
+  // defineOptions({ name: 'BasicTableFooter' });
+
+  const props = withDefaults(
+    defineProps<{
+      summaryFunc?: Fn | null;
+      summaryData?: Recordable[] | null;
+      scroll?: BasicTableProps['scroll'];
+      rowKey?: BasicTableProps['rowKey'];
+    }>(),
+    {
+      summaryFunc: null,
+      summaryData: null,
+      rowKey: '',
+    },
+  );
 
   const SUMMARY_ROW_KEY = '_row';
   const SUMMARY_INDEX_KEY = '_index';
-  export default defineComponent({
-    name: 'BasicTableFooter',
-    components: { Table },
-    props: {
-      summaryFunc: {
-        type: Function as PropType<Fn>,
-      },
-      summaryData: {
-        type: Array as PropType<Recordable[]>,
-      },
-      scroll: {
-        type: Object as PropType<Recordable>,
-      },
-      rowKey: propTypes.string.def('key'),
-    },
-    setup(props) {
-      const table = useTableContext();
+  const table = useTableContext();
 
-      const getDataSource = computed((): Recordable[] => {
-        const { summaryFunc, summaryData } = props;
-        if (summaryData?.length) {
-          summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`));
-          return summaryData;
-        }
-        if (!isFunction(summaryFunc)) {
-          return [];
-        }
-        let dataSource = toRaw(unref(table.getDataSource()));
-        dataSource = summaryFunc(dataSource);
-        dataSource.forEach((item, i) => {
-          item[props.rowKey] = `${i}`;
-        });
-        return dataSource;
+  const getDataSource = computed((): Recordable[] => {
+    if (props.summaryData?.length) {
+      props.summaryData.forEach((item, i) => {
+        item[parseRowKey(props.rowKey, item)] = `${i}`;
       });
+      return props.summaryData;
+    }
+    if (!isFunction(props.summaryFunc)) {
+      return [];
+    }
+    let dataSource = toRaw(unref(table.getDataSource()));
+    dataSource = props.summaryFunc(dataSource);
+    dataSource.forEach((item, i) => {
+      item[parseRowKey(props.rowKey, item)] = `${i}`;
+    });
+    return dataSource;
+  });
 
-      const getColumns = computed(() => {
-        const dataSource = unref(getDataSource);
-        const columns: BasicColumn[] = cloneDeep(table.getColumns());
-        const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
-        const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
-        const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
+  const getColumns = computed(() => {
+    const dataSource = unref(getDataSource);
+    const columns: BasicColumn[] = cloneDeep(table.getColumns());
+    const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
+    const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
+    const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
 
-        if (index !== -1) {
-          if (hasIndexSummary) {
-            columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
-            columns[index].ellipsis = false;
-          } else {
-            Reflect.deleteProperty(columns[index], 'customRender');
-          }
-        }
+    if (index !== -1) {
+      if (hasIndexSummary) {
+        columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
+        columns[index].ellipsis = false;
+      } else {
+        Reflect.deleteProperty(columns[index], 'customRender');
+      }
+    }
 
-        if (table.getRowSelection() && hasRowSummary) {
-          const isFixed = columns.some((col) => col.fixed === 'left');
-          columns.unshift({
-            width: 60,
-            title: 'selection',
-            key: 'selectionKey',
-            align: 'center',
-            ...(isFixed ? { fixed: 'left' } : {}),
-            customRender: ({ record }) => record[SUMMARY_ROW_KEY],
-          });
-        }
-        return columns;
+    if (table.getRowSelection() && hasRowSummary) {
+      const isFixed = columns.some((col) => col.fixed === 'left');
+      columns.unshift({
+        width: 60,
+        title: 'selection',
+        key: 'selectionKey',
+        align: 'center',
+        ...(isFixed ? { fixed: 'left' } : {}),
+        customRender: ({ record }) => record[SUMMARY_ROW_KEY],
       });
-      return { getColumns, getDataSource };
-    },
+    }
+    return columns as unknown as ColumnType[];
   });
 </script>

+ 39 - 32
src/components/Table/src/components/TableHeader.vue

@@ -3,6 +3,9 @@
     <div v-if="$slots.headerTop" style="margin: 5px">
       <slot name="headerTop"></slot>
     </div>
+    <div v-if="showSelectionBar" style="margin: 5px">
+      <TableSelectionBar :clearSelectedRowKeys="props.clearSelectedRowKeys!" :count="props.count" />
+    </div>
     <div class="flex items-center">
       <slot name="tableTitle" v-if="$slots.tableTitle"></slot>
       <TableTitle
@@ -13,7 +16,7 @@
       <div :class="`${prefixCls}__toolbar`">
         <slot name="toolbar"></slot>
         <Divider type="vertical" v-if="$slots.toolbar && showTableSetting" />
-        <TableSetting
+        <TableSettingComponent
           :setting="tableSetting"
           v-if="showTableSetting"
           @columns-change="handleColumnChange"
@@ -22,54 +25,58 @@
     </div>
   </div>
 </template>
-<script lang="ts">
-  import type { TableSetting, ColumnChangeParam } from '../types/table';
+<script lang="ts" setup>
+  import type { TableSetting, ColumnChangeParam, TableActionType } from '../types/table';
   import type { PropType } from '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 TableSelectionBar from '../components/TableSelectionBar.vue';
+
+  // defineOptions({ name: 'BasicTableHeader' });
 
-  export default defineComponent({
-    name: 'BasicTableHeader',
-    components: {
-      Divider,
-      TableTitle,
-      TableSetting: TableSettingComponent,
+  const props = defineProps({
+    title: {
+      type: [Function, String] as PropType<string | ((data) => string)>,
     },
-    props: {
-      title: {
-        type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
-      },
-      tableSetting: {
-        type: Object as PropType<TableSetting>,
-      },
-      showTableSetting: {
-        type: Boolean,
-      },
-      titleHelpMessage: {
-        type: [String, Array] as PropType<string | string[]>,
-        default: '',
-      },
+    tableSetting: {
+      type: Object as PropType<TableSetting>,
     },
-    emits: ['columns-change'],
-    setup(_, { emit }) {
-      const { prefixCls } = useDesign('basic-table-header');
-      function handleColumnChange(data: ColumnChangeParam[]) {
-        emit('columns-change', data);
-      }
-      return { prefixCls, handleColumnChange };
+    showTableSetting: {
+      type: Boolean,
+    },
+    titleHelpMessage: {
+      type: [String, Array] as PropType<string | string[]>,
+      default: '',
+    },
+    //
+    clearSelectedRowKeys: {
+      type: Function as PropType<TableActionType['clearSelectedRowKeys']>,
+    },
+    count: {
+      type: Number,
+      default: 0,
+    },
+    showSelectionBar: {
+      type: Boolean,
+      default: false,
     },
   });
+
+  const emit = defineEmits(['columns-change']);
+  const { prefixCls } = useDesign('basic-table-header');
+  function handleColumnChange(data: ColumnChangeParam[]) {
+    emit('columns-change', data);
+  }
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-basic-table-header';
 
   .@{prefix-cls} {
     &__toolbar {
-      flex: 1;
       display: flex;
+      flex: 1;
       align-items: center;
       justify-content: flex-end;
 

文件差异内容过多而无法显示
+ 37 - 34
src/components/Table/src/components/TableImg.vue


+ 56 - 0
src/components/Table/src/components/TableSelectionBar.vue

@@ -0,0 +1,56 @@
+<template>
+  <a-alert type="info" showIcon :class="[prefixCls]">
+    <template #message>
+      <span v-if="props.count > 0">
+        {{ t('component.table.selectionBarTips', { count: props.count }) }}
+      </span>
+      <span v-else>
+        {{ t('component.table.selectionBarEmpty') }}
+      </span>
+      <a-button type="link" @click="clearSelectedRowKeys" size="small" v-show="props.count > 0">
+        {{ t('component.table.selectionBarClear') }}
+      </a-button>
+    </template>
+  </a-alert>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import type { TableActionType } from '../types/table';
+  import { Alert as AAlert } from 'ant-design-vue';
+
+  const { t } = useI18n();
+
+  const { prefixCls } = useDesign('table-select-bar');
+
+  // defineOptions({
+  //   name: 'TableSelectBar',
+  // });
+
+  const props = withDefaults(
+    defineProps<{
+      count?: number;
+      //
+      clearSelectedRowKeys: TableActionType['clearSelectedRowKeys'];
+    }>(),
+    {
+      count: () => 0,
+    },
+  );
+</script>
+
+<style lang="less" scoped>
+  @prefix-cls: ~'@{namespace}-table-select-bar';
+
+  .@{prefix-cls} {
+    flex-grow: 1;
+    padding: 2px 8px;
+
+    :deep(.ant-btn-link) {
+      height: 20px;
+      line-height: 20px;
+    }
+  }
+</style>

+ 26 - 31
src/components/Table/src/components/TableTitle.vue

@@ -3,43 +3,38 @@
     {{ getTitle }}
   </BasicTitle>
 </template>
-<script lang="ts">
-  import { computed, defineComponent, PropType } from 'vue';
-  import { BasicTitle } from '/@/components/Basic/index';
+<script lang="ts" setup>
+  import { computed, PropType } from 'vue';
+  import { BasicTitle } from '/@/components/Basic';
   import { useDesign } from '/@/hooks/web/useDesign';
   import { isFunction } from '/@/utils/is';
 
-  export default defineComponent({
-    name: 'BasicTableTitle',
-    components: { BasicTitle },
-    props: {
-      title: {
-        type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
-      },
-      getSelectRows: {
-        type: Function as PropType<() => Recordable[]>,
-      },
-      helpMessage: {
-        type: [String, Array] as PropType<string | string[]>,
-      },
+  // defineOptions({ name: 'BasicTableTitle' });
+
+  const props = defineProps({
+    title: {
+      type: [Function, String] as PropType<string | ((data) => string)>,
+    },
+    getSelectRows: {
+      type: Function as PropType<() => any[]>,
+    },
+    helpMessage: {
+      type: [String, Array] as PropType<string | string[]>,
     },
-    setup(props) {
-      const { prefixCls } = useDesign('basic-table-title');
+  });
 
-      const getTitle = computed(() => {
-        const { title, getSelectRows = () => {} } = props;
-        let tit = title;
+  const { prefixCls } = useDesign('basic-table-title');
 
-        if (isFunction(title)) {
-          tit = title({
-            selectRows: getSelectRows(),
-          });
-        }
-        return tit;
-      });
+  const getTitle = computed(() => {
+    const { title, getSelectRows = () => {} } = props;
+    let tit = title;
 
-      return { getTitle, prefixCls };
-    },
+    if (isFunction(title)) {
+      tit = title({
+        selectRows: getSelectRows(),
+      });
+    }
+    return tit;
   });
 </script>
 <style lang="less">
@@ -47,7 +42,7 @@
 
   .@{prefix-cls} {
     display: flex;
-    justify-content: space-between;
     align-items: center;
+    justify-content: space-between;
   }
 </style>

+ 3 - 3
src/components/Table/src/components/editable/CellComponent.ts

@@ -1,4 +1,4 @@
-import type { FunctionalComponent, defineComponent } from 'vue';
+import type { defineComponent } from 'vue';
 import type { ComponentType } from '../../types/componentType';
 import { componentMap } from '/@/components/Table/src/componentMap';
 
@@ -13,7 +13,7 @@ export interface ComponentProps {
   getPopupContainer?: Fn;
 }
 
-export const CellComponent: FunctionalComponent = (
+export const CellComponent = (
   {
     component = 'Input',
     rule = true,
@@ -33,7 +33,7 @@ export const CellComponent: FunctionalComponent = (
     Popover,
     {
       overlayClassName: 'edit-cell-rule-popover',
-      visible: !!popoverVisible,
+      open: !!popoverVisible,
       ...(getPopupContainer ? { getPopupContainer } : {}),
     },
     {

+ 167 - 95
src/components/Table/src/components/editable/EditableCell.vue

@@ -1,44 +1,7 @@
-<template>
-  <div :class="prefixCls">
-    <div
-      v-show="!isEdit"
-      :class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }"
-      @click="handleEdit"
-    >
-      <div class="cell-content" :title="column.ellipsis ? getValues ?? '' : ''">
-        {{ getValues ? getValues : '&nbsp;' }}
-      </div>
-      <FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
-    </div>
-
-    <a-spin v-if="isEdit" :spinning="spinning">
-      <div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
-        <CellComponent
-          v-bind="getComponentProps"
-          :component="getComponent"
-          :style="getWrapperStyle"
-          :popoverVisible="getRuleVisible"
-          :rule="getRule"
-          :ruleMessage="ruleMessage"
-          :class="getWrapperClass"
-          ref="elRef"
-          @change="handleChange"
-          @options-change="handleOptionsChange"
-          @pressEnter="handleEnter"
-        />
-        <div :class="`${prefixCls}__action`" v-if="!getRowEditable">
-          <CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />
-          <CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
-        </div>
-      </div>
-    </a-spin>
-  </div>
-</template>
-<script lang="ts">
+<script lang="tsx">
   import type { CSSProperties, PropType } from 'vue';
   import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue';
   import type { BasicColumn } from '../../types/table';
-  import type { EditRecordRow } from './index';
   import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue';
   import { CellComponent } from './CellComponent';
 
@@ -50,23 +13,27 @@
   import { propTypes } from '/@/utils/propTypes';
   import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is';
   import { createPlaceholderMessage } from './helper';
-  import { omit, pick, set } from 'lodash-es';
+  import { pick, set } from 'lodash-es';
   import { treeToList } from '/@/utils/helper/treeHelper';
   import { Spin } from 'ant-design-vue';
+  import { parseRowKey } from '../../helper';
+  import { warn } from '/@/utils/log';
 
   export default defineComponent({
     name: 'EditableCell',
-    components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin },
+    components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, Spin },
     directives: {
       clickOutside,
     },
     props: {
       value: {
-        type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
+        type: [String, Number, Boolean, Object] as PropType<
+          string | number | boolean | Record<string, any>
+        >,
         default: '',
       },
       record: {
-        type: Object as PropType<EditRecordRow>,
+        type: Object as any,
       },
       column: {
         type: Object as PropType<BasicColumn>,
@@ -80,7 +47,7 @@
       const elRef = ref();
       const ruleVisible = ref(false);
       const ruleMessage = ref('');
-      const optionsRef = ref<LabelValueOptions>([]);
+      const optionsRef = ref([]);
       const currentValueRef = ref<any>(props.value);
       const defaultValueRef = ref<any>(props.value);
       const spinning = ref<boolean>(false);
@@ -99,34 +66,74 @@
         return ['Checkbox', 'Switch'].includes(component);
       });
 
-      const getComponentProps = computed(() => {
-        const compProps = props.column?.editComponentProps ?? {};
-        const component = unref(getComponent);
-        const apiSelectProps: Recordable = {};
-        if (component === 'ApiSelect') {
-          apiSelectProps.cache = true;
+      const getDisable = computed(() => {
+        const { editDynamicDisabled } = props.column;
+        let disabled = false;
+        if (isBoolean(editDynamicDisabled)) {
+          disabled = editDynamicDisabled;
+        }
+        if (isFunction(editDynamicDisabled)) {
+          const { record } = props;
+          disabled = editDynamicDisabled({ record, currentValue: currentValueRef.value });
         }
+        return disabled;
+      });
 
+      const getComponentProps = computed(() => {
         const isCheckValue = unref(getIsCheckComp);
+        let compProps = props.column?.editComponentProps ?? ({} as any);
+        const { checkedValue, unCheckedValue } = compProps;
 
         const valueField = isCheckValue ? 'checked' : 'value';
         const val = unref(currentValueRef);
 
-        const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
+        let value = val;
+        if (isCheckValue) {
+          if (typeof checkedValue !== 'undefined') {
+            value = val === checkedValue ? checkedValue : unCheckedValue;
+          } else if (typeof unCheckedValue !== 'undefined') {
+            value = val === unCheckedValue ? unCheckedValue : checkedValue;
+          } else {
+            value = isNumber(val) || isBoolean(val) ? val : !!val;
+          }
+        }
+
+        const { record, column, index } = props;
+
+        if (isFunction(compProps)) {
+          compProps = compProps({ text: val, record, column, index }) ?? {};
+        }
+
+        // 用临时变量存储 onChange方法 用于 handleChange方法 获取,并删除原始onChange, 防止存在两个 onChange
+        compProps.onChangeTemp = compProps.onChange;
+        delete compProps.onChange;
 
+        const component = unref(getComponent);
+        const apiSelectProps: Record<string, any> = {};
+        if (component === 'ApiSelect') {
+          apiSelectProps.cache = true;
+        }
+        upEditDynamicDisabled(record, column, value);
         return {
           size: 'small',
           getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
-          getCalendarContainer: () => unref(table?.wrapRef.value) ?? document.body,
           placeholder: createPlaceholderMessage(unref(getComponent)),
           ...apiSelectProps,
-          ...omit(compProps, 'onChange'),
+          ...compProps,
           [valueField]: value,
-        };
+          disabled: unref(getDisable),
+        } as any;
       });
+      function upEditDynamicDisabled(record, column, value) {
+        if (!record) return false;
+        const { key, dataIndex } = column;
+        if (!key && !dataIndex) return;
+        const dataKey = (dataIndex || key) as string;
+        set(record, dataKey, value);
+      }
 
       const getValues = computed(() => {
-        const { editComponentProps, editValueMap } = props.column;
+        const { editValueMap } = props.column;
 
         const value = unref(currentValueRef);
 
@@ -135,16 +142,21 @@
         }
 
         const component = unref(getComponent);
-        if (!component.includes('Select')) {
+        if (!component.includes('Select') && !component.includes('Radio')) {
           return value;
         }
 
-        const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
+        const options = unref(getComponentProps)?.options ?? (unref(optionsRef) || []);
         const option = options.find((item) => `${item.value}` === `${value}`);
 
         return option?.label ?? value;
       });
 
+      const getRowEditable = computed(() => {
+        const { editable } = props.record || {};
+        return !!editable;
+      });
+
       const getWrapperStyle = computed((): CSSProperties => {
         if (unref(getIsCheckComp) || unref(getRowEditable)) {
           return {};
@@ -159,13 +171,8 @@
         return `edit-cell-align-${align}`;
       });
 
-      const getRowEditable = computed(() => {
-        const { editable } = props.record || {};
-        return !!editable;
-      });
-
       watchEffect(() => {
-        defaultValueRef.value = props.value;
+        // defaultValueRef.value = props.value;
         currentValueRef.value = props.value;
       });
 
@@ -176,8 +183,9 @@
         }
       });
 
-      function handleEdit() {
-        if (unref(getRowEditable) || unref(props.column?.editRow)) return;
+      function handleEdit(e) {
+        e.stopPropagation();
+        if (unref(getRowEditable) || unref(props.column?.editRow) || unref(getDisable)) return;
         ruleMessage.value = '';
         isEdit.value = true;
         nextTick(() => {
@@ -186,29 +194,31 @@
         });
       }
 
-      async function handleChange(e: any) {
+      async function handleChange(e: any, ...rest: any[]) {
         const component = unref(getComponent);
         if (!e) {
           currentValueRef.value = e;
-        } else if (e?.target && Reflect.has(e.target, 'value')) {
-          currentValueRef.value = (e as ChangeEvent).target.value;
         } else if (component === 'Checkbox') {
-          currentValueRef.value = (e as ChangeEvent).target.checked;
-        } else if (isString(e) || isBoolean(e) || isNumber(e)) {
+          currentValueRef.value = e.target.checked;
+        } else if (component === 'Switch') {
+          currentValueRef.value = e;
+        } else if (e?.target && Reflect.has(e.target, 'value')) {
+          currentValueRef.value = e.target.value;
+        } else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
           currentValueRef.value = e;
         }
-        const onChange = props.column?.editComponentProps?.onChange;
-        if (onChange && isFunction(onChange)) onChange(...arguments);
+        const onChange = unref(getComponentProps)?.onChangeTemp;
+        if (onChange && isFunction(onChange)) onChange(e, ...rest);
 
         table.emit?.('edit-change', {
           column: props.column,
           value: unref(currentValueRef),
           record: toRaw(props.record),
         });
-        handleSubmiRule();
+        handleSubmitRule();
       }
 
-      async function handleSubmiRule() {
+      async function handleSubmitRule() {
         const { column, record } = props;
         const { editRule } = column;
         const currentValue = unref(currentValueRef);
@@ -221,8 +231,8 @@
             return false;
           }
           if (isFunction(editRule)) {
-            const res = await editRule(currentValue, record as Recordable);
-            if (!!res) {
+            const res = await editRule(currentValue, record);
+            if (res) {
               ruleMessage.value = res;
               ruleVisible.value = true;
               return false;
@@ -238,7 +248,7 @@
 
       async function handleSubmit(needEmit = true, valid = true) {
         if (valid) {
-          const isPass = await handleSubmiRule();
+          const isPass = await handleSubmitRule();
           if (!isPass) return false;
         }
 
@@ -253,23 +263,27 @@
         if (!record.editable) {
           const { getBindValues } = table;
 
-          const { beforeEditSubmit, columns } = unref(getBindValues);
+          const { beforeEditSubmit, columns, rowKey } = unref(getBindValues);
+
+          const rowKeyParsed = parseRowKey(rowKey, record);
 
           if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
             spinning.value = true;
             const keys: string[] = columns
               .map((_column) => _column.dataIndex)
               .filter((field) => !!field) as string[];
+
             let result: any = true;
             try {
               result = await beforeEditSubmit({
-                record: pick(record, keys),
+                record: pick(record, [rowKeyParsed, ...keys]),
                 index,
-                key: key as string,
+                key: dataKey as string,
                 value,
               });
             } catch (e) {
               result = false;
+              warn(e);
             } finally {
               spinning.value = false;
             }
@@ -278,10 +292,10 @@
             }
           }
         }
-
         set(record, dataKey, value);
+        defaultValueRef.value = value;
         //const record = await table.updateTableData(index, dataKey, value);
-        needEmit && table.emit?.('edit-end', { record, index, key, value });
+        needEmit && table.emit?.('edit-end', { record, index, key: dataKey, value });
         isEdit.value = false;
       }
 
@@ -321,25 +335,25 @@
       }
 
       // only ApiSelect or TreeSelect
-      function handleOptionsChange(options: LabelValueOptions) {
-        const { replaceFields } = props.column?.editComponentProps ?? {};
+      function handleOptionsChange(options) {
+        const { replaceFields } = unref(getComponentProps);
         const component = unref(getComponent);
         if (component === 'ApiTreeSelect') {
           const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
-          let listOptions: Recordable[] = treeToList(options, { children });
+          let listOptions = treeToList(options, { children });
           listOptions = listOptions.map((item) => {
             return {
               label: item[title],
               value: item[value],
             };
           });
-          optionsRef.value = listOptions as LabelValueOptions;
+          optionsRef.value = listOptions;
         } else {
           optionsRef.value = options;
         }
       }
 
-      function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
+      function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle) {
         if (props.record) {
           /* eslint-disable  */
           isArray(props.record[cbs])
@@ -350,18 +364,16 @@
 
       if (props.record) {
         initCbs('submitCbs', handleSubmit);
-        initCbs('validCbs', handleSubmiRule);
+        initCbs('validCbs', handleSubmitRule);
         initCbs('cancelCbs', handleCancel);
 
         if (props.column.dataIndex) {
           if (!props.record.editValueRefs) props.record.editValueRefs = {};
-          props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
+          props.record.editValueRefs[props.column.dataIndex as any] = currentValueRef;
         }
-        /* eslint-disable  */
         props.record.onCancelEdit = () => {
           isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
         };
-        /* eslint-disable */
         props.record.onSubmitEdit = async () => {
           if (isArray(props.record?.submitCbs)) {
             if (!props.record?.onValid?.()) return;
@@ -396,8 +408,67 @@
         handleEnter,
         handleSubmitClick,
         spinning,
+        getDisable,
       };
     },
+    render() {
+      return (
+        <div class={this.prefixCls}>
+          <div
+            v-show={!this.isEdit}
+            class={{ [`${this.prefixCls}__normal`]: true, 'ellipsis-cell': this.column.ellipsis }}
+            onClick={this.handleEdit}
+          >
+            <div class="cell-content" title={this.column.ellipsis ? this.getValues ?? '' : ''}>
+              {this.column.editRender
+                ? this.column.editRender({
+                    text: this.value,
+                    record: this.record as Recordable,
+                    column: this.column,
+                    index: this.index,
+                    currentValue: this.currentValueRef,
+                  })
+                : this.getValues ?? '\u00A0'}
+            </div>
+            {!this.column.editRow && !this.getDisable && (
+              <FormOutlined class={`${this.prefixCls}__normal-icon`} />
+            )}
+          </div>
+          {this.isEdit && (
+            <Spin spinning={this.spinning} onClick={(e) => e.stopPropagation()}>
+              <div
+                class={`${this.prefixCls}__wrapper`}
+                v-click-outside={this.onClickOutside}
+                onClick={(e) => e.stopPropagation()}
+              >
+                <CellComponent
+                  {...this.getComponentProps}
+                  component={this.getComponent}
+                  style={this.getWrapperStyle}
+                  popoverVisible={this.getRuleVisible}
+                  rule={this.getRule}
+                  ruleMessage={this.ruleMessage}
+                  class={this.getWrapperClass}
+                  ref="elRef"
+                  onChange={this.handleChange}
+                  onOptionsChange={this.handleOptionsChange}
+                  onPressEnter={this.handleEnter}
+                />
+                {!this.getRowEditable && (
+                  <div class={`${this.prefixCls}__action`}>
+                    <CheckOutlined
+                      class={[`${this.prefixCls}__icon`, 'mx-2']}
+                      onClick={this.handleSubmitClick}
+                    />
+                    <CloseOutlined class={`${this.prefixCls}__icon `} onClick={this.handleCancel} />
+                  </div>
+                )}
+              </div>
+            </Spin>
+          )}
+        </div>
+      );
+    },
   });
 </script>
 <style lang="less">
@@ -430,13 +501,14 @@
   .edit-cell-rule-popover {
     .ant-popover-inner-content {
       padding: 4px 8px;
-      color: @error-color;
       // border: 1px solid @error-color;
       border-radius: 2px;
+      color: @error-color;
     }
   }
   .@{prefix-cls} {
     position: relative;
+    min-height: 24px; // 设置高度让其始终可被hover
 
     &__wrapper {
       display: flex;
@@ -460,20 +532,20 @@
 
     .ellipsis-cell {
       .cell-content {
-        overflow-wrap: break-word;
-        word-break: break-word;
         overflow: hidden;
-        white-space: nowrap;
         text-overflow: ellipsis;
+        word-break: break-word;
+        white-space: nowrap;
+        overflow-wrap: break-word;
       }
     }
 
     &__normal {
       &-icon {
+        display: none;
         position: absolute;
         top: 4px;
         right: 0;
-        display: none;
         width: 20px;
         cursor: pointer;
       }

+ 1 - 1
src/components/Table/src/components/editable/helper.ts

@@ -7,7 +7,7 @@ const { t } = useI18n();
  * @description: 生成placeholder
  */
 export function createPlaceholderMessage(component: ComponentType) {
-  if (component.includes('Input')) {
+  if (component.includes('Input') || component.includes('AutoComplete')) {
     return t('common.inputText');
   }
   if (component.includes('Picker')) {

+ 3 - 3
src/components/Table/src/components/editable/index.ts

@@ -1,6 +1,6 @@
 import type { BasicColumn } from '/@/components/Table/src/types/table';
 
-import { h, Ref } from 'vue';
+import { h, Ref, toRaw } from 'vue';
 
 import EditableCell from './EditableCell.vue';
 import { isArray } from '/@/utils/is';
@@ -13,7 +13,7 @@ interface Params {
 
 export function renderEditCell(column: BasicColumn) {
   return ({ text: value, record, index }: Params) => {
-    record.onValid = async () => {
+    toRaw(record).onValid = async () => {
       if (isArray(record?.validCbs)) {
         const validFns = (record?.validCbs || []).map((fn) => fn());
         const res = await Promise.all(validFns);
@@ -23,7 +23,7 @@ export function renderEditCell(column: BasicColumn) {
       }
     };
 
-    record.onEdit = async (edit: boolean, submit = false) => {
+    toRaw(record).onEdit = async (edit: boolean, submit = false) => {
       if (!submit) {
         record.editable = edit;
       }

+ 488 - 285
src/components/Table/src/components/settings/ColumnSetting.vue

@@ -6,33 +6,33 @@
     <Popover
       placement="bottomLeft"
       trigger="click"
-      @visible-change="handleVisibleChange"
-      :overlayClassName="`${prefixCls}__cloumn-list`"
+      @open-change="onOpenChange"
+      :overlayClassName="`${prefixCls}__column-list`"
       :getPopupContainer="getPopupContainer"
     >
       <template #title>
         <div :class="`${prefixCls}__popover-title`">
           <Checkbox
             :indeterminate="indeterminate"
-            v-model:checked="checkAll"
-            @change="onCheckAllChange"
+            v-model:checked="isColumnAllSelected"
+            @change="onColumnAllSelectChange"
           >
             {{ t('component.table.settingColumnShow') }}
           </Checkbox>
 
-          <Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
+          <Checkbox v-model:checked="isIndexColumnShow" @change="onIndexColumnShowChange">
             {{ t('component.table.settingIndexColumnShow') }}
           </Checkbox>
-
+          <!-- 设置了 rowSelection 才出现 -->
           <Checkbox
-            v-model:checked="checkSelect"
-            @change="handleSelectCheckChange"
-            :disabled="!defaultRowSelection"
+            v-model:checked="isRowSelectionShow"
+            @change="onRowSelectionShowChange"
+            v-if="defaultIsRowSelectionShow"
           >
             {{ t('component.table.settingSelectColumnShow') }}
           </Checkbox>
 
-          <a-button size="small" type="link" @click="reset">
+          <a-button size="small" type="link" @click="onReset">
             {{ t('common.resetText') }}
           </a-button>
         </div>
@@ -40,12 +40,12 @@
 
       <template #content>
         <ScrollContainer>
-          <CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef">
-            <template v-for="item in plainOptions" :key="item.value">
-              <div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)">
+          <Checkbox.Group v-model:value="columnCheckedOptions" ref="columnOptionsRef">
+            <template v-for="opt in columnOptions" :key="opt.value">
+              <div :class="`${prefixCls}__check-item`" :data-no="opt.value">
                 <DragOutlined class="table-column-drag-icon" />
-                <Checkbox :value="item.value">
-                  {{ item.label }}
+                <Checkbox :value="opt.value">
+                  {{ opt.label }}
                 </Checkbox>
 
                 <Tooltip
@@ -61,11 +61,11 @@
                     :class="[
                       `${prefixCls}__fixed-left`,
                       {
-                        active: item.fixed === 'left',
-                        disabled: !checkedList.includes(item.value),
+                        active: opt.fixed === 'left',
+                        disabled: opt.value ? !columnCheckedOptions.includes(opt.value) : true,
                       },
                     ]"
-                    @click="handleColumnFixed(item, 'left')"
+                    @click="onColumnFixedChange(opt, 'left')"
                   />
                 </Tooltip>
                 <Divider type="vertical" />
@@ -82,34 +82,25 @@
                     :class="[
                       `${prefixCls}__fixed-right`,
                       {
-                        active: item.fixed === 'right',
-                        disabled: !checkedList.includes(item.value),
+                        active: opt.fixed === 'right',
+                        disabled: opt.value ? !columnCheckedOptions.includes(opt.value) : true,
                       },
                     ]"
-                    @click="handleColumnFixed(item, 'right')"
+                    @click="onColumnFixedChange(opt, 'right')"
                   />
                 </Tooltip>
               </div>
             </template>
-          </CheckboxGroup>
+          </Checkbox.Group>
         </ScrollContainer>
       </template>
       <SettingOutlined />
     </Popover>
   </Tooltip>
 </template>
-<script lang="ts">
-  import type { BasicColumn, ColumnChangeParam } from '../../types/table';
-  import {
-    defineComponent,
-    ref,
-    reactive,
-    toRefs,
-    watchEffect,
-    nextTick,
-    unref,
-    computed,
-  } from 'vue';
+<script lang="ts" setup>
+  import type { BasicColumn, ColumnOptionsType, ColumnChangeParam } from '../../types/table';
+  import { ref, nextTick, unref, computed, useAttrs, watch, onMounted } from 'vue';
   import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
   import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
   import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
@@ -118,292 +109,503 @@
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useTableContext } from '../../hooks/useTableContext';
   import { useDesign } from '/@/hooks/web/useDesign';
-  // import { useSortable } from '/@/hooks/web/useSortable';
-  import { isFunction, isNullAndUnDef } from '/@/utils/is';
+  import { isFunction, isNil } from '/@/utils/is';
   import { getPopupContainer as getParentContainer } from '/@/utils';
   import { cloneDeep, omit } from 'lodash-es';
   import Sortablejs from 'sortablejs';
-  import type Sortable from 'sortablejs';
+  import { INDEX_COLUMN_FLAG } from '/@/components/Table/src/const';
 
-  interface State {
-    checkAll: boolean;
-    isInit?: boolean;
-    checkedList: string[];
-    defaultCheckList: string[];
-  }
+  // 列表设置缓存
+  import { useTableSettingStore } from '/@/store/modules/tableSetting';
+  import { useRoute } from 'vue-router';
+  import { TableRowSelection } from '/@/components/Table/src/types/table';
 
-  interface Options {
-    label: string;
-    value: string;
-    fixed?: boolean | 'left' | 'right';
-  }
+  const tableSettingStore = useTableSettingStore();
 
-  export default defineComponent({
-    name: 'ColumnSetting',
-    components: {
-      SettingOutlined,
-      Popover,
-      Tooltip,
-      Checkbox,
-      CheckboxGroup: Checkbox.Group,
-      DragOutlined,
-      ScrollContainer,
-      Divider,
-      Icon,
-    },
-    emits: ['columns-change'],
+  // defineOptions({ name: 'ColumnSetting' });
+  const emit = defineEmits(['columns-change']);
 
-    setup(_, { emit, attrs }) {
-      const { t } = useI18n();
-      const table = useTableContext();
+  const route = useRoute();
 
-      const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
-      let inited = false;
+  const { t } = useI18n();
+  const { prefixCls } = useDesign('basic-column-setting');
 
-      const cachePlainOptions = ref<Options[]>([]);
-      const plainOptions = ref<Options[] | any>([]);
+  const attrs = useAttrs();
+  const table = useTableContext();
 
-      const plainSortOptions = ref<Options[]>([]);
+  const props = withDefaults(
+    defineProps<{
+      /**
+       * 是否缓存列的设置
+       */
+      cache?: boolean;
+    }>(),
+    {
+      cache: () => false,
+    },
+  );
+
+  const getPopupContainer = () => {
+    return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer();
+  };
+
+  // 默认值
+  let defaultIsIndexColumnShow: boolean = false;
+  let defaultIsRowSelectionShow: boolean = false;
+  let defaultRowSelection: TableRowSelection<Recordable<any>>;
+  let defaultColumnOptions: ColumnOptionsType[] = [];
+
+  // 是否已经从缓存恢复
+  let isRestored = false;
+  let isInnerChange = false;
+
+  // 列可选项
+  const columnOptions = ref<ColumnOptionsType[]>([]);
+  const columnOptionsRef = ref(null);
+  // 已选列
+  const columnCheckedOptions = ref<string[]>([]);
+  // 已选变化
+  watch(columnCheckedOptions, () => {
+    // 恢复缓存后生效
+    if (isRestored) {
+      // 显示
+      columnOptions.value
+        .filter((o) => columnCheckedOptions.value.includes(o.value))
+        .forEach((o) => {
+          o.column.defaultHidden = false;
+        });
+      // 隐藏
+      columnOptions.value
+        .filter((o) => !columnCheckedOptions.value.includes(o.value))
+        .forEach((o) => {
+          o.column.defaultHidden = true;
+          o.fixed = undefined;
+        });
+      // 从 列可选项 更新 全选状态
+      isColumnAllSelectedUpdate();
 
-      const columnListRef = ref(null);
+      // 列表列更新
+      tableColumnsUpdate();
+      // 更新列缓存
+      props.cache && columnOptionsSave();
+    }
+  });
 
-      const state = reactive<State>({
-        checkAll: true,
-        checkedList: [],
-        defaultCheckList: [],
-      });
+  // 全选
+  const isColumnAllSelected = ref<boolean>(false);
+  const onColumnAllSelectChange = () => {
+    if (columnCheckedOptions.value.length < columnOptions.value.length) {
+      columnCheckedOptions.value = columnOptions.value.map((o) => o.value);
+    } else {
+      columnCheckedOptions.value = [];
+    }
+  };
+
+  // 半选状态
+  const indeterminate = computed(() => {
+    return (
+      columnCheckedOptions.value.length > 0 &&
+      columnCheckedOptions.value.length < columnOptions.value.length
+    );
+  });
+
+  // 是否显示序号列
+  const isIndexColumnShow = ref<boolean>(false);
+  // 序号列更新
+  const onIndexColumnShowChange = (e: CheckboxChangeEvent) => {
+    // 更新 showIndexColumn
+    showIndexColumnUpdate(e.target.checked);
+    // 更新 showIndexColumn 缓存
+    props.cache &&
+      typeof route.name === 'string' &&
+      tableSettingStore.setShowIndexColumn(route.name, e.target.checked);
+  };
+
+  // 是否显示选择列
+  const isRowSelectionShow = ref<boolean>(false);
+  // 选择列更新
+  const onRowSelectionShowChange = (e: CheckboxChangeEvent) => {
+    // 更新 showRowSelection
+    showRowSelectionUpdate(e.target.checked);
+    // 更新 showRowSelection 缓存
+    props.cache &&
+      typeof route.name === 'string' &&
+      tableSettingStore.setShowRowSelection(route.name, e.target.checked);
+  };
+
+  // 更新列缓存
+  const columnOptionsSave = () => {
+    if (typeof route.name === 'string') {
+      // 按路由 name 作为缓存的key(若一个路由内存在多个表格,需自行调整缓存key来源)
+      tableSettingStore.setColumns(route.name, columnOptions.value);
+    }
+  };
+
+  // 重置
+  const onReset = () => {
+    // 重置默认值
+    isIndexColumnShow.value = defaultIsIndexColumnShow;
+    // 序号列更新
+    onIndexColumnShowChange({
+      target: { checked: defaultIsIndexColumnShow },
+    } as CheckboxChangeEvent);
+    // 重置默认值
+    isRowSelectionShow.value = defaultIsRowSelectionShow;
+    // 选择列更新
+    onRowSelectionShowChange({
+      target: { checked: defaultIsRowSelectionShow },
+    } as CheckboxChangeEvent);
+    // 重置默认值
+    columnOptions.value = cloneDeep(defaultColumnOptions);
+    // 更新表单状态
+    formUpdate();
+  };
+
+  // 设置列的 fixed
+  const onColumnFixedChange = (opt: ColumnOptionsType, type: 'left' | 'right') => {
+    if (type === 'left') {
+      if (!opt.fixed || opt.fixed === 'right') {
+        opt.fixed = 'left';
+      } else {
+        opt.fixed = undefined;
+      }
+    } else if (type === 'right') {
+      if (!opt.fixed || opt.fixed === 'left') {
+        opt.fixed = 'right';
+      } else {
+        opt.fixed = undefined;
+      }
+    }
 
-      const checkIndex = ref(false);
-      const checkSelect = ref(false);
+    // 列表列更新
+    tableColumnsUpdate();
+    // 更新列缓存
+    props.cache && columnOptionsSave();
+  };
+
+  // 沿用逻辑
+  const sortableFix = async () => {
+    // Sortablejs存在bug,不知道在哪个步骤中会向el append了一个childNode,因此这里先清空childNode
+    // 有可能复现上述问题的操作:拖拽一个元素,快速的上下移动,最后放到最后的位置中松手
+    if (columnOptionsRef.value) {
+      const el = (columnOptionsRef.value as InstanceType<typeof Checkbox.Group>).$el;
+      Array.from(el.children).forEach((item) => el.removeChild(item));
+    }
+    await nextTick();
+  };
+
+  // 列是否显示逻辑
+  const columnIfShow = (column?: Partial<Omit<BasicColumn, 'children'>>) => {
+    if (column) {
+      if ('ifShow' in column) {
+        if (typeof column.ifShow === 'boolean') {
+          return column.ifShow;
+        } else if (column.ifShow) {
+          return column.ifShow(column);
+        }
+      }
+      return true;
+    }
+    return false;
+  };
+
+  // 获取数据列
+  const getTableColumns = () => {
+    return table
+      .getColumns({ ignoreIndex: true, ignoreAction: true })
+      .filter((col) => columnIfShow(col));
+  };
+
+  // 设置列表列
+  const tableColumnsSet = (columns: BasicColumn[]) => {
+    isInnerChange = true;
+    table?.setColumns(columns);
+
+    // 沿用逻辑
+    const columnChangeParams: ColumnChangeParam[] = columns.map((col) => ({
+      dataIndex: col.dataIndex ? col.dataIndex.toString() : '',
+      fixed: col.fixed,
+      visible: !col.defaultHidden,
+    }));
+    emit('columns-change', columnChangeParams);
+  };
+
+  // 列表列更新
+  const tableColumnsUpdate = () => {
+    // 考虑了所有列
+    const columns = cloneDeep(table.getColumns());
+
+    // 从左 fixed 最一列开始排序(除了 序号列)
+    let count = columns.filter(
+      (o) => o.flag !== INDEX_COLUMN_FLAG && (o.fixed === 'left' || o.fixed === true),
+    ).length;
+
+    // 序号列提前
+    if (isIndexColumnShow.value) {
+      count++;
+    }
 
-      const { prefixCls } = useDesign('basic-column-setting');
+    // 按 columnOptions 的排序 调整 table.getColumns() 的顺序和值
+    for (const opt of columnOptions.value) {
+      const colIdx = columns.findIndex((o) => o.dataIndex === opt.value);
+      //
+      if (colIdx > -1) {
+        const target = columns[colIdx];
+        target.defaultHidden = opt.column?.defaultHidden;
+        target.fixed = opt.fixed;
+        columns.splice(colIdx, 1);
+        columns.splice(count++, 0, target); // 递增插入
+      }
+    }
 
-      const getValues = computed(() => {
-        return unref(table?.getBindValues) || {};
-      });
+    // 是否存在 action
+    const actionIndex = columns.findIndex((o) => o.dataIndex === 'action');
+    if (actionIndex > -1) {
+      const actionCol = columns.splice(actionIndex, 1);
+      columns.push(actionCol[0]);
+    }
 
-      watchEffect(() => {
-        const columns = table.getColumns();
-        setTimeout(() => {
-          if (columns.length && !state.isInit) {
-            init();
+    // 设置列表列
+    tableColumnsSet(columns);
+  };
+
+  // 打开浮窗
+  const onOpenChange = async () => {
+    await nextTick();
+
+    if (columnOptionsRef.value) {
+      // 注册排序实例
+      const el = (columnOptionsRef.value as InstanceType<typeof Checkbox.Group>).$el;
+      Sortablejs.create(unref(el), {
+        animation: 500,
+        delay: 400,
+        delayOnTouchOnly: true,
+        handle: '.table-column-drag-icon ',
+        dataIdAttr: 'data-no',
+        onEnd: (evt) => {
+          const { oldIndex, newIndex } = evt;
+          if (isNil(oldIndex) || isNil(newIndex) || oldIndex === newIndex) {
+            return;
           }
-        }, 0);
-      });
 
-      watchEffect(() => {
-        const values = unref(getValues);
-        checkIndex.value = !!values.showIndexColumn;
-        checkSelect.value = !!values.rowSelection;
-      });
+          const options = cloneDeep(columnOptions.value);
 
-      function getColumns() {
-        const ret: Options[] = [];
-        table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
-          ret.push({
-            label: (item.title as string) || (item.customTitle as string),
-            value: (item.dataIndex || item.title) as string,
-            ...item,
-          });
-        });
-        return ret;
-      }
+          // 排序
+          if (oldIndex > newIndex) {
+            options.splice(newIndex, 0, options[oldIndex]);
+            options.splice(oldIndex + 1, 1);
+          } else {
+            options.splice(newIndex + 1, 0, options[oldIndex]);
+            options.splice(oldIndex, 1);
+          }
 
-      function init() {
-        const columns = getColumns();
-
-        const checkList = table
-          .getColumns({ ignoreAction: true, ignoreIndex: true })
-          .map((item) => {
-            if (item.defaultHidden) {
-              return '';
-            }
-            return item.dataIndex || item.title;
-          })
-          .filter(Boolean) as string[];
-
-        if (!plainOptions.value.length) {
-          plainOptions.value = columns;
-          plainSortOptions.value = columns;
-          cachePlainOptions.value = columns;
-          state.defaultCheckList = checkList;
-        } else {
-          // const fixedColumns = columns.filter((item) =>
-          //   Reflect.has(item, 'fixed')
-          // ) as BasicColumn[];
-
-          unref(plainOptions).forEach((item: BasicColumn) => {
-            const findItem = columns.find((col: BasicColumn) => col.dataIndex === item.dataIndex);
-            if (findItem) {
-              item.fixed = findItem.fixed;
-            }
-          });
-        }
-        state.isInit = true;
-        state.checkedList = checkList;
-      }
+          // 更新 列可选项
+          columnOptions.value = options;
 
-      // checkAll change
-      function onCheckAllChange(e: CheckboxChangeEvent) {
-        const checkList = plainOptions.value.map((item) => item.value);
-        if (e.target.checked) {
-          state.checkedList = checkList;
-          setColumns(checkList);
-        } else {
-          state.checkedList = [];
-          setColumns([]);
+          // 列表列更新
+          tableColumnsUpdate();
+          // 更新列缓存
+          props.cache && columnOptionsSave();
+        },
+      });
+    }
+  };
+
+  // remove消失的列、push新出现的列
+  const diff = () => {
+    if (typeof route.name === 'string') {
+      let cache = tableSettingStore.getColumns(route.name);
+      if (cache) {
+        // value、label是否一致
+        if (
+          JSON.stringify(columnOptions.value.map((o) => ({ value: o.value, label: o.label }))) !==
+          JSON.stringify(cache.map((o) => ({ value: o.value, label: o.label })))
+        ) {
+          const map = columnOptions.value.reduce((map, item) => {
+            map[item.value] = item.label;
+            return map;
+          }, {});
+          if (Array.isArray(cache)) {
+            // remove消失的列
+            cache = cache.filter((o) => map[o.value]);
+            // 更新label
+            cache.forEach((o) => {
+              o.label = map[o.value];
+            });
+            const cacheKeys = cache.map((o) => o.value);
+            // push新出现的列
+            cache = cache.concat(columnOptions.value.filter((o) => !cacheKeys.includes(o.value)));
+            // 更新缓存
+            tableSettingStore.setColumns(route.name, cache);
+          }
         }
       }
-
-      const indeterminate = computed(() => {
-        const len = plainOptions.value.length;
-        let checkedLen = state.checkedList.length;
-        // unref(checkIndex) && checkedLen--;
-        return checkedLen > 0 && checkedLen < len;
-      });
-
-      // Trigger when check/uncheck a column
-      function onChange(checkedList: string[]) {
-        const len = plainSortOptions.value.length;
-        state.checkAll = checkedList.length === len;
-        const sortList = unref(plainSortOptions).map((item) => item.value);
-        checkedList.sort((prev, next) => {
-          return sortList.indexOf(prev) - sortList.indexOf(next);
-        });
-        setColumns(checkedList);
+    }
+  };
+
+  // 从缓存恢复
+  const restore = () => {
+    if (typeof route.name === 'string') {
+      const isIndexColumnShowCache = tableSettingStore.getShowIndexColumn(route.name);
+      // 设置过才恢复
+      if (typeof isIndexColumnShowCache === 'boolean') {
+        isIndexColumnShow.value = defaultIsIndexColumnShow && isIndexColumnShowCache;
       }
 
-      let sortable: Sortable;
-      let sortableOrder: string[] = [];
-      // reset columns
-      function reset() {
-        state.checkedList = [...state.defaultCheckList];
-        state.checkAll = true;
-        plainOptions.value = unref(cachePlainOptions);
-        plainSortOptions.value = unref(cachePlainOptions);
-        setColumns(table.getCacheColumns());
-        sortable.sort(sortableOrder);
+      const isRowSelectionShowCache = tableSettingStore.getShowRowSelection(route.name);
+      // 设置过才恢复
+      if (typeof isRowSelectionShowCache === 'boolean') {
+        isRowSelectionShow.value = defaultIsRowSelectionShow && isRowSelectionShowCache;
       }
-
-      // Open the pop-up window for drag and drop initialization
-      function handleVisibleChange() {
-        if (inited) return;
-        nextTick(() => {
-          const columnListEl = unref(columnListRef);
-          if (!columnListEl) return;
-          const el = (columnListEl as any).$el;
-          if (!el) return;
-          // Drag and drop sort
-          sortable = Sortablejs.create(unref(el), {
-            animation: 500,
-            delay: 400,
-            delayOnTouchOnly: true,
-            handle: '.table-column-drag-icon ',
-            onEnd: (evt) => {
-              const { oldIndex, newIndex } = evt;
-              if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
-                return;
-              }
-              // Sort column
-              const columns = cloneDeep(plainSortOptions.value);
-
-              if (oldIndex > newIndex) {
-                columns.splice(newIndex, 0, columns[oldIndex]);
-                columns.splice(oldIndex + 1, 1);
-              } else {
-                columns.splice(newIndex + 1, 0, columns[oldIndex]);
-                columns.splice(oldIndex, 1);
-              }
-
-              plainSortOptions.value = columns;
-
-              setColumns(
-                columns
-                  .map((col: Options) => col.value)
-                  .filter((value: string) => state.checkedList.includes(value)),
-              );
-            },
-          });
-          // 记录原始order 序列
-          sortableOrder = sortable.toArray();
-          inited = true;
-        });
+    }
+    // 序号列更新
+    onIndexColumnShowChange({
+      target: { checked: isIndexColumnShow.value },
+    } as CheckboxChangeEvent);
+    // 选择列更新
+    onRowSelectionShowChange({
+      target: { checked: isRowSelectionShow.value },
+    } as CheckboxChangeEvent);
+
+    if (typeof route.name === 'string') {
+      const cache = tableSettingStore.getColumns(route.name);
+      // 设置过才恢复
+      if (Array.isArray(cache)) {
+        columnOptions.value = cache;
       }
-
-      // Control whether the serial number column is displayed
-      function handleIndexCheckChange(e: CheckboxChangeEvent) {
-        table.setProps({
-          showIndexColumn: e.target.checked,
+    }
+  };
+
+  // 从 列可选项 更新 已选列
+  const columnCheckedOptionsUpdate = () => {
+    columnCheckedOptions.value = columnOptions.value
+      .filter((o) => !o.column?.defaultHidden)
+      .map((o) => o.value);
+  };
+  // 从 列可选项 更新 全选状态
+  const isColumnAllSelectedUpdate = () => {
+    isColumnAllSelected.value = columnOptions.value.length === columnCheckedOptions.value.length;
+  };
+  // 更新 showIndexColumn
+  const showIndexColumnUpdate = (showIndexColumn) => {
+    isInnerChange = true;
+    table.setProps({
+      showIndexColumn,
+    });
+  };
+  // 更新 rowSelection
+  const showRowSelectionUpdate = (showRowSelection) => {
+    isInnerChange = true;
+    table.setProps({
+      rowSelection: showRowSelection
+        ? {
+            ...omit(defaultRowSelection, ['selectedRowKeys']),
+            fixed: true,
+          }
+        : undefined,
+    });
+  };
+
+  // 更新表单状态
+  const formUpdate = () => {
+    // 从 列可选项 更新 已选列
+    columnCheckedOptionsUpdate();
+
+    // 从 列可选项 更新 全选状态
+    isColumnAllSelectedUpdate();
+
+    // 更新 showIndexColumn
+    showIndexColumnUpdate(isIndexColumnShow.value);
+
+    // 更新 showRowSelection
+    showRowSelectionUpdate(isRowSelectionShow.value);
+
+    // 列表列更新
+    tableColumnsUpdate();
+  };
+
+  const init = async () => {
+    if (!isRestored) {
+      // 获取数据列
+      const columns = getTableColumns();
+
+      // 沿用逻辑
+      table.setCacheColumns?.(columns);
+
+      // 生成 默认值
+      const options: ColumnOptionsType[] = [];
+      for (const col of columns) {
+        // 只缓存 string 类型的列
+        options.push({
+          label:
+            typeof col.title === 'string'
+              ? col.title
+              : typeof col.customTitle === 'string'
+                ? col.customTitle
+                : '',
+          value:
+            typeof col.dataIndex === 'string'
+              ? col.dataIndex
+              : typeof col.title === 'string'
+                ? col.title
+                : '',
+          column: {
+            defaultHidden: col.defaultHidden,
+          },
+          fixed: col.fixed,
         });
       }
 
-      // Control whether the check box is displayed
-      function handleSelectCheckChange(e: CheckboxChangeEvent) {
-        table.setProps({
-          rowSelection: e.target.checked ? defaultRowSelection : undefined,
-        });
-      }
+      // 默认值 缓存
+      defaultIsIndexColumnShow = table.getBindValues.value.showIndexColumn || false;
+      defaultRowSelection = table.getRowSelection();
+      defaultIsRowSelectionShow = !!defaultRowSelection; // 设置了 rowSelection 才出现
+      defaultColumnOptions = options;
 
-      function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
-        if (!state.checkedList.includes(item.dataIndex as string)) return;
+      // 默认值 赋值
+      isIndexColumnShow.value = defaultIsIndexColumnShow;
+      isRowSelectionShow.value = defaultIsRowSelectionShow;
+      columnOptions.value = cloneDeep(options);
 
-        const columns = getColumns().filter((c: BasicColumn) =>
-          state.checkedList.includes(c.dataIndex as string),
-        ) as BasicColumn[];
-        const isFixed = item.fixed === fixed ? false : fixed;
-        const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
-        if (index !== -1) {
-          columns[index].fixed = isFixed;
-        }
-        item.fixed = isFixed;
+      // remove消失的列、push新出现的列
+      props.cache && diff();
 
-        if (isFixed && !item.width) {
-          item.width = 100;
-        }
-        table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
-        setColumns(columns);
-      }
+      // 从缓存恢复
+      props.cache && restore();
 
-      function setColumns(columns: BasicColumn[] | string[]) {
-        table.setColumns(columns);
-        const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
-          const visible =
-            columns.findIndex(
-              (c: BasicColumn | string) =>
-                c === col.value || (typeof c !== 'string' && c.dataIndex === col.value),
-            ) !== -1;
-          return { dataIndex: col.value, fixed: col.fixed, visible };
-        });
+      // 更新表单状态
+      formUpdate();
 
-        emit('columns-change', data);
-      }
+      isRestored = true;
+    }
+  };
+
+  // 初始化
+  const once = async () => {
+    // 仅执行一次
+    await sortableFix();
+    init();
+  };
+  once();
+
+  // 外部列改变
+  const getColumns = computed(() => {
+    return table?.getColumns();
+  });
+  const getValues = computed(() => {
+    return table?.getBindValues;
+  });
 
-      function getPopupContainer() {
-        return isFunction(attrs.getPopupContainer)
-          ? attrs.getPopupContainer()
-          : getParentContainer();
+  onMounted(() => {
+    watch([getColumns, getValues], () => {
+      if (!isInnerChange) {
+        isRestored = false;
+        console.log('onMounted isRestored');
+        init();
+      } else {
+        isInnerChange = false;
       }
-
-      return {
-        t,
-        ...toRefs(state),
-        indeterminate,
-        onCheckAllChange,
-        onChange,
-        plainOptions,
-        reset,
-        prefixCls,
-        columnListRef,
-        handleVisibleChange,
-        checkIndex,
-        checkSelect,
-        handleIndexCheckChange,
-        handleSelectCheckChange,
-        defaultRowSelection,
-        handleColumnFixed,
-        getPopupContainer,
-      };
-    },
+    });
   });
 </script>
 <style lang="less">
@@ -457,7 +659,7 @@
       transform: rotate(180deg);
     }
 
-    &__cloumn-list {
+    &__column-list {
       svg {
         width: 1em !important;
         height: 1em !important;
@@ -471,6 +673,7 @@
       }
 
       .ant-checkbox-group {
+        display: inline-block;
         width: 100%;
         min-width: 260px;
         // flex-wrap: wrap;
@@ -481,4 +684,4 @@
       }
     }
   }
-</style>
+</style>

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

@@ -7,32 +7,16 @@
     <FullscreenExitOutlined @click="toggle" v-else />
   </Tooltip>
 </template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
+<script lang="ts" setup>
   import { Tooltip } from 'ant-design-vue';
   import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
   import { useFullscreen } from '@vueuse/core';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useTableContext } from '../../hooks/useTableContext';
 
-  export default defineComponent({
-    name: 'FullScreenSetting',
-    components: {
-      FullscreenExitOutlined,
-      FullscreenOutlined,
-      Tooltip,
-    },
+  // defineOptions({ name: 'FullScreenSetting' });
 
-    setup() {
-      const table = useTableContext();
-      const { t } = useI18n();
-      const { toggle, isFullscreen } = useFullscreen(table.wrapRef);
-
-      return {
-        toggle,
-        isFullscreen,
-        t,
-      };
-    },
-  });
+  const table = useTableContext();
+  const { t } = useI18n();
+  const { toggle, isFullscreen } = useFullscreen(table.wrapRef);
 </script>

+ 7 - 17
src/components/Table/src/components/settings/RedoSetting.vue

@@ -6,28 +6,18 @@
     <RedoOutlined @click="redo" />
   </Tooltip>
 </template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
+<script lang="ts" setup>
   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',
-    components: {
-      RedoOutlined,
-      Tooltip,
-    },
-    setup() {
-      const table = useTableContext();
-      const { t } = useI18n();
+  // defineOptions({ name: 'RedoSetting' });
 
-      function redo() {
-        table.reload();
-      }
+  const table = useTableContext();
+  const { t } = useI18n();
 
-      return { redo, t };
-    },
-  });
+  function redo() {
+    table.reload();
+  }
 </script>

+ 33 - 36
src/components/Table/src/components/settings/SizeSetting.vue

@@ -4,61 +4,58 @@
       <span>{{ t('component.table.settingDens') }}</span>
     </template>
 
-    <Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer">
+    <Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
       <ColumnHeightOutlined />
       <template #overlay>
         <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
-          <MenuItem key="default">
+          <Menu.Item key="default">
             <span>{{ t('component.table.settingDensDefault') }}</span>
-          </MenuItem>
-          <MenuItem key="middle">
+          </Menu.Item>
+          <Menu.Item key="middle">
             <span>{{ t('component.table.settingDensMiddle') }}</span>
-          </MenuItem>
-          <MenuItem key="small">
+          </Menu.Item>
+          <Menu.Item key="small">
             <span>{{ t('component.table.settingDensSmall') }}</span>
-          </MenuItem>
+          </Menu.Item>
         </Menu>
       </template>
     </Dropdown>
   </Tooltip>
 </template>
-<script lang="ts">
+<script lang="ts" setup>
   import type { SizeType } from '../../types/table';
-  import { defineComponent, ref } from 'vue';
-  import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
+  import { ref, onMounted } from 'vue';
+  import { Tooltip, Dropdown, Menu, type MenuProps } 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';
 
-  export default defineComponent({
-    name: 'SizeSetting',
-    components: {
-      ColumnHeightOutlined,
-      Tooltip,
-      Dropdown,
-      Menu,
-      MenuItem: Menu.Item,
-    },
-    setup() {
-      const table = useTableContext();
-      const { t } = useI18n();
+  import { useTableSettingStore } from '/@/store/modules/tableSetting';
 
-      const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
+  const tableSettingStore = useTableSettingStore();
 
-      function handleTitleClick({ key }: { key: SizeType }) {
-        selectedKeysRef.value = [key];
-        table.setProps({
-          size: key,
-        });
-      }
+  // defineOptions({ name: 'SizeSetting' });
 
-      return {
-        handleTitleClick,
-        selectedKeysRef,
-        getPopupContainer,
-        t,
-      };
-    },
+  const table = useTableContext();
+  const { t } = useI18n();
+
+  const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
+
+  const handleTitleClick: MenuProps['onClick'] = ({ key }) => {
+    selectedKeysRef.value = [key as SizeType];
+
+    tableSettingStore.setTableSize(key as SizeType);
+
+    table.setProps({
+      size: key as SizeType,
+    });
+  };
+
+  onMounted(() => {
+    selectedKeysRef.value = [tableSettingStore.getTableSize];
+    table.setProps({
+      size: selectedKeysRef.value[0],
+    });
   });
 </script>

+ 31 - 38
src/components/Table/src/components/settings/index.vue

@@ -6,64 +6,57 @@
       v-if="getSetting.setting"
       @columns-change="handleColumnChange"
       :getPopupContainer="getTableContainer"
+      :cache="getSetting.settingCache"
     />
     <FullScreenSetting v-if="getSetting.fullScreen" :getPopupContainer="getTableContainer" />
   </div>
 </template>
-<script lang="ts">
+<script lang="ts" setup>
   import type { PropType } from 'vue';
   import type { TableSetting, ColumnChangeParam } from '../../types/table';
-  import { defineComponent, computed, unref } from 'vue';
+  import { computed, unref } 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';
   import { useTableContext } from '../../hooks/useTableContext';
 
-  export default defineComponent({
-    name: 'TableSetting',
-    components: {
-      ColumnSetting,
-      SizeSetting,
-      RedoSetting,
-      FullScreenSetting,
-    },
-    props: {
-      setting: {
-        type: Object as PropType<TableSetting>,
-        default: () => ({}),
-      },
-    },
-    emits: ['columns-change'],
-    setup(props, { emit }) {
-      const { t } = useI18n();
-      const table = useTableContext();
+  // defineOptions({ name: 'TableSetting' });
 
-      const getSetting = computed((): TableSetting => {
-        return {
-          redo: true,
-          size: true,
-          setting: true,
-          fullScreen: false,
-          ...props.setting,
-        };
-      });
+  const props = defineProps({
+    setting: {
+      type: Object as PropType<TableSetting>,
+      default: () => ({}),
+    },
+  });
 
-      function handleColumnChange(data: ColumnChangeParam[]) {
-        emit('columns-change', data);
-      }
+  const emit = defineEmits(['columns-change']);
 
-      function getTableContainer() {
-        return table ? unref(table.wrapRef) : document.body;
-      }
+  const table = useTableContext();
 
-      return { getSetting, t, handleColumnChange, getTableContainer };
-    },
+  const getSetting = computed((): TableSetting => {
+    return {
+      redo: true,
+      size: true,
+      setting: true,
+      settingCache: !import.meta.env.DEV,
+      fullScreen: false,
+      ...props.setting,
+    };
   });
+
+  function handleColumnChange(data: ColumnChangeParam[]) {
+    emit('columns-change', data);
+  }
+
+  function getTableContainer() {
+    return table ? unref(table.wrapRef) : document.body;
+  }
 </script>
 <style lang="less">
   .table-settings {
+    display: flex;
+
     & > * {
       margin-right: 12px;
     }

+ 28 - 0
src/components/Table/src/helper.ts

@@ -0,0 +1,28 @@
+import { ROW_KEY } from './const';
+import type { BasicTableProps } from './types/table';
+
+export function parseRowKey<RecordType = any>(
+  rowKey: BasicTableProps['rowKey'],
+  record: RecordType,
+  autoCreateKey?: boolean,
+): number | string {
+  if (autoCreateKey) {
+    return ROW_KEY;
+  } else {
+    if (typeof rowKey === 'string') {
+      return rowKey;
+    } else if (rowKey) {
+      return rowKey(record);
+    } else {
+      return ROW_KEY;
+    }
+  }
+}
+
+export function parseRowKeyValue<RecordType = any>(
+  rowKey: BasicTableProps['rowKey'],
+  record: RecordType,
+  autoCreateKey?: boolean,
+): number | string {
+  return record[parseRowKey(rowKey, record, autoCreateKey)];
+}

+ 68 - 44
src/components/Table/src/hooks/useColumns.ts

@@ -1,7 +1,7 @@
 import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
 import type { PaginationProps } from '../types/pagination';
 import type { ComputedRef } from 'vue';
-import { computed, Ref, ref, toRaw, unref, watch } from 'vue';
+import { computed, Ref, ref, reactive, toRaw, unref, watch } from 'vue';
 import { renderEditCell } from '../components/editable';
 import { usePermission } from '/@/hooks/web/usePermission';
 import { useI18n } from '/@/hooks/web/useI18n';
@@ -9,13 +9,14 @@ import { isArray, isBoolean, isFunction, isMap, isString } from '/@/utils/is';
 import { cloneDeep, isEqual } from 'lodash-es';
 import { formatToDate } from '/@/utils/dateUtil';
 import { ACTION_COLUMN_FLAG, DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../const';
+import { ColumnType } from 'ant-design-vue/es/table';
 
 function handleItem(item: BasicColumn, ellipsis: boolean) {
   const { key, dataIndex, children } = item;
   item.align = item.align || DEFAULT_ALIGN;
   if (ellipsis) {
     if (!key) {
-      item.key = dataIndex;
+      item.key = typeof dataIndex == 'object' ? dataIndex.join('-') : dataIndex;
     }
     if (!isBoolean(item.ellipsis)) {
       Object.assign(item, {
@@ -65,7 +66,7 @@ function handleIndexColumn(
 
   columns.unshift({
     flag: INDEX_COLUMN_FLAG,
-    width: 50,
+    width: 60,
     title: t('component.table.index'),
     align: 'center',
     customRender: ({ index }) => {
@@ -146,31 +147,36 @@ export function useColumns(
   const getViewColumns = computed(() => {
     const viewColumns = sortFixedColumn(unref(getColumnsRef));
 
+    const mapFn = (column) => {
+      const { slots, customRender, format, edit, editRow, flag } = column;
+
+      if (!slots || !slots?.title) {
+        column.customTitle = column.title;
+      }
+      const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
+      if (!customRender && format && !edit && !isDefaultAction) {
+        column.customRender = ({ text, record, index }) => {
+          return formatCell(text, format, record, index);
+        };
+      }
+
+      // edit table
+      if ((edit || editRow) && !isDefaultAction) {
+        column.customRender = renderEditCell(column);
+      }
+      return reactive(column);
+    };
+
     const columns = cloneDeep(viewColumns);
     return columns
-      .filter((column) => {
-        return hasPermission(column.auth) && isIfShow(column);
-      })
+      .filter((column) => hasPermission(column.auth) && isIfShow(column))
       .map((column) => {
-        const { slots, dataIndex, customRender, format, edit, editRow, flag } = column;
-
-        if (!slots || !slots?.title) {
-          column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
-          column.customTitle = column.title;
-          Reflect.deleteProperty(column, 'title');
-        }
-        const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
-        if (!customRender && format && !edit && !isDefaultAction) {
-          column.customRender = ({ text, record, index }) => {
-            return formatCell(text, format, record, index);
-          };
+        // Support table multiple header editable
+        if (column.children?.length) {
+          column.children = column.children.map(mapFn);
         }
 
-        // edit table
-        if ((edit || editRow) && !isDefaultAction) {
-          column.customRender = renderEditCell(column);
-        }
-        return column;
+        return mapFn(column);
       });
   });
 
@@ -197,7 +203,7 @@ export function useColumns(
    * set columns
    * @param columnList key|column
    */
-  function setColumns(columnList: Partial<BasicColumn>[] | string[]) {
+  function setColumns(columnList: Partial<BasicColumn>[] | (string | string[])[]) {
     const columns = cloneDeep(columnList);
     if (!isArray(columns)) return;
 
@@ -210,31 +216,23 @@ export function useColumns(
 
     const cacheKeys = cacheColumns.map((item) => item.dataIndex);
 
-    if (!isString(firstColumn)) {
+    if (!isString(firstColumn) && !isArray(firstColumn)) {
       columnsRef.value = columns as BasicColumn[];
     } else {
-      const columnKeys = columns as string[];
+      const columnKeys = (columns as (string | string[])[]).map((m) => m.toString());
       const newColumns: BasicColumn[] = [];
       cacheColumns.forEach((item) => {
-        if (columnKeys.includes(item.dataIndex! || (item.key as string))) {
-          newColumns.push({
-            ...item,
-            defaultHidden: false,
-          });
-        } else {
-          newColumns.push({
-            ...item,
-            defaultHidden: true,
-          });
-        }
+        newColumns.push({
+          ...item,
+          defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)),
+        });
       });
-
       // Sort according to another array
       if (!isEqual(cacheKeys, columns)) {
         newColumns.sort((prev, next) => {
           return (
-            cacheKeys.indexOf(prev.dataIndex as string) -
-            cacheKeys.indexOf(next.dataIndex as string)
+            columnKeys.indexOf(prev.dataIndex?.toString() as string) -
+            columnKeys.indexOf(next.dataIndex?.toString() as string)
           );
         });
       }
@@ -261,14 +259,26 @@ export function useColumns(
   function getCacheColumns() {
     return cacheColumns;
   }
+  function setCacheColumns(columns: BasicColumn[]) {
+    if (!isArray(columns)) return;
+    cacheColumns = columns.filter((item) => !item.flag);
+  }
+  /**
+   * 拖拽列宽修改列的宽度
+   */
+  function setColumnWidth(w: number, col: ColumnType<BasicColumn>) {
+    col.width = w;
+  }
 
   return {
     getColumnsRef,
     getCacheColumns,
     getColumns,
     setColumns,
+    setColumnWidth,
     getViewColumns,
     setCacheColumnsByField,
+    setCacheColumns,
   };
 }
 
@@ -287,9 +297,23 @@ function sortFixedColumn(columns: BasicColumn[]) {
     }
     defColumns.push(column);
   }
-  return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(
-    (item) => !item.defaultHidden,
-  );
+  // 筛选逻辑
+  const filterFunc = (item) => !item.defaultHidden;
+  // 筛选首层显示列(1级表头)
+  const viewColumns = [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(filterFunc);
+  // 筛选>=2级表头(深度优先)
+  const list = [...viewColumns];
+  while (list.length) {
+    const current = list[0];
+    if (Array.isArray(current.children)) {
+      current.children = current.children.filter(filterFunc);
+      list.shift();
+      list.unshift(...current.children);
+    } else {
+      list.shift();
+    }
+  }
+  return viewColumns;
 }
 
 // format cell
@@ -306,7 +330,7 @@ export function formatCell(text: string, format: CellFormat, record: Recordable,
   try {
     // date type
     const DATE_FORMAT_PREFIX = 'date|';
-    if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
+    if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX) && text) {
       const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
 
       if (!dateFormat) {

+ 19 - 34
src/components/Table/src/hooks/useCustomRow.ts

@@ -1,34 +1,18 @@
 import type { ComputedRef } from 'vue';
 import type { BasicTableProps } from '../types/table';
 import { unref } from 'vue';
-import { ROW_KEY } from '../const';
-import { isString, isFunction } from '/@/utils/is';
+import type { Key } from 'ant-design-vue/lib/table/interface';
+
+import { parseRowKeyValue } from '../helper';
 
 interface Options {
-  setSelectedRowKeys: (keys: string[]) => void;
-  getSelectRowKeys: () => string[];
+  setSelectedRowKeys: (keyValues: Key[]) => void;
+  getSelectRowKeys: () => Key[];
   clearSelectedRowKeys: () => void;
   emit: EmitType;
   getAutoCreateKey: ComputedRef<boolean | undefined>;
 }
 
-function getKey(
-  record: Recordable,
-  rowKey: string | ((record: Record<string, any>) => string) | undefined,
-  autoCreateKey?: boolean,
-) {
-  if (!rowKey || autoCreateKey) {
-    return record[ROW_KEY];
-  }
-  if (isString(rowKey)) {
-    return record[rowKey];
-  }
-  if (isFunction(rowKey)) {
-    return record[rowKey(record)];
-  }
-  return null;
-}
-
 export function useCustomRow(
   propsRef: ComputedRef<BasicTableProps>,
   { setSelectedRowKeys, getSelectRowKeys, getAutoCreateKey, clearSelectedRowKeys, emit }: Options,
@@ -40,37 +24,38 @@ export function useCustomRow(
         function handleClick() {
           const { rowSelection, rowKey, clickToRowSelect } = unref(propsRef);
           if (!rowSelection || !clickToRowSelect) return;
-          const keys = getSelectRowKeys();
-          const key = getKey(record, rowKey, unref(getAutoCreateKey));
-          if (!key) return;
+          const keyValues = getSelectRowKeys() || [];
+          const keyValue = parseRowKeyValue(rowKey, record, unref(getAutoCreateKey));
+          if (!keyValue) return;
 
           const isCheckbox = rowSelection.type === 'checkbox';
           if (isCheckbox) {
             // 找到tr
-            const tr: HTMLElement = (e as MouseEvent)
+            const tr = (e as MouseEvent)
               .composedPath?.()
-              .find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement;
+              .find((dom) => (dom as HTMLElement).tagName === 'TR') as HTMLElement;
             if (!tr) return;
             // 找到Checkbox,检查是否为disabled
             const checkBox = tr.querySelector('input[type=checkbox]');
             if (!checkBox || checkBox.hasAttribute('disabled')) return;
-            if (!keys.includes(key)) {
-              setSelectedRowKeys([...keys, key]);
+            if (!keyValues.includes(keyValue)) {
+              keyValues.push(keyValue);
+              setSelectedRowKeys(keyValues);
               return;
             }
-            const keyIndex = keys.findIndex((item) => item === key);
-            keys.splice(keyIndex, 1);
-            setSelectedRowKeys(keys);
+            const keyIndex = keyValues.findIndex((item) => item === keyValue);
+            keyValues.splice(keyIndex, 1);
+            setSelectedRowKeys(keyValues);
             return;
           }
 
           const isRadio = rowSelection.type === 'radio';
           if (isRadio) {
-            if (!keys.includes(key)) {
-              if (keys.length) {
+            if (!keyValues.includes(keyValue)) {
+              if (keyValues.length) {
                 clearSelectedRowKeys();
               }
-              setSelectedRowKeys([key]);
+              setSelectedRowKeys([keyValue]);
               return;
             }
             clearSelectedRowKeys();

+ 107 - 102
src/components/Table/src/hooks/useDataSource.ts

@@ -13,9 +13,11 @@ import {
 } from 'vue';
 import { useTimeoutFn } from '/@/hooks/core/useTimeout';
 import { buildUUID } from '/@/utils/uuid';
-import { isFunction, isBoolean } from '/@/utils/is';
-import { get, cloneDeep } from 'lodash-es';
+import { isFunction, isBoolean, isObject } from '/@/utils/is';
+import { get, cloneDeep, merge } from 'lodash-es';
 import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const';
+import { parseRowKeyValue } from '../helper';
+import type { Key } from 'ant-design-vue/lib/table/interface';
 
 interface ActionType {
   getPaginationInfo: ComputedRef<boolean | PaginationProps>;
@@ -48,6 +50,7 @@ export function useDataSource(
   });
   const dataSourceRef = ref<Recordable[]>([]);
   const rawDataSourceRef = ref<Recordable>({});
+  const searchInfoRef = ref<Recordable>({});
 
   watchEffect(() => {
     tableData.value = unref(dataSourceRef);
@@ -111,34 +114,43 @@ export function useDataSource(
     return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
   });
 
-  const getDataSourceRef = computed(() => {
-    const dataSource = unref(dataSourceRef);
-    if (!dataSource || dataSource.length === 0) {
-      return unref(dataSourceRef);
-    }
-    if (unref(getAutoCreateKey)) {
-      const firstItem = dataSource[0];
-      const lastItem = dataSource[dataSource.length - 1];
-
-      if (firstItem && lastItem) {
-        if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
-          const data = cloneDeep(unref(dataSourceRef));
-          data.forEach((item) => {
-            if (!item[ROW_KEY]) {
-              item[ROW_KEY] = buildUUID();
-            }
-            if (item.children && item.children.length) {
-              setTableKey(item.children);
-            }
-          });
-          dataSourceRef.value = data;
+  const getDataSourceRef: Ref<Recordable<any>[]> = ref([]);
+
+  watch(
+    () => dataSourceRef.value,
+    () => {
+      const dataSource = unref(dataSourceRef);
+      if (!dataSource || dataSource.length === 0) {
+        getDataSourceRef.value = unref(dataSourceRef);
+      }
+      if (unref(getAutoCreateKey)) {
+        const firstItem = dataSource[0];
+        const lastItem = dataSource[dataSource.length - 1];
+
+        if (firstItem && lastItem) {
+          if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
+            const data = cloneDeep(unref(dataSourceRef));
+            data.forEach((item) => {
+              if (!item[ROW_KEY]) {
+                item[ROW_KEY] = buildUUID();
+              }
+              if (item.children && item.children.length) {
+                setTableKey(item.children);
+              }
+            });
+            dataSourceRef.value = data;
+          }
         }
       }
-    }
-    return unref(dataSourceRef);
-  });
+      getDataSourceRef.value = unref(dataSourceRef);
+    },
+    {
+      deep: true,
+      immediate: true,
+    },
+  );
 
-  async function updateTableData(index: number, key: string, value: any) {
+  async function updateTableData(index: number, key: Key, value: any) {
     const record = dataSourceRef.value[index];
     if (record) {
       dataSourceRef.value[index][key] = value;
@@ -146,11 +158,8 @@ export function useDataSource(
     return dataSourceRef.value[index];
   }
 
-  function updateTableDataRecord(
-    rowKey: string | number,
-    record: Recordable,
-  ): Recordable | undefined {
-    const row = findTableDataRecord(rowKey);
+  function updateTableDataRecord(keyValue: Key, record: Recordable): Recordable | undefined {
+    const row = findTableDataRecord(keyValue);
 
     if (row) {
       for (const field in row) {
@@ -160,83 +169,73 @@ export function useDataSource(
     }
   }
 
-  function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
+  function deleteTableDataRecord(keyValues: Key | Key[]) {
     if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
-    const rowKeyName = unref(getRowKey);
-    if (!rowKeyName) return;
-    const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
-    for (const key of rowKeys) {
-      let index: number | undefined = dataSourceRef.value.findIndex((row) => {
-        let targetKeyName: string;
-        if (typeof rowKeyName === 'function') {
-          targetKeyName = rowKeyName(row);
-        } else {
-          targetKeyName = rowKeyName as string;
-        }
-        return row[targetKeyName] === key;
-      });
-      if (index >= 0) {
-        dataSourceRef.value.splice(index, 1);
+    const delKeyValues = !Array.isArray(keyValues) ? [keyValues] : keyValues;
+
+    function deleteRow(data, keyValue) {
+      const row: { index: number; data: [] } = findRow(data, keyValue);
+      if (row === null || row.index === -1) {
+        return;
       }
-      index = unref(propsRef).dataSource?.findIndex((row) => {
-        let targetKeyName: string;
-        if (typeof rowKeyName === 'function') {
-          targetKeyName = rowKeyName(row);
-        } else {
-          targetKeyName = rowKeyName as string;
+      row.data.splice(row.index, 1);
+
+      function findRow(data, keyValue) {
+        if (data === null || data === undefined) {
+          return null;
         }
-        return row[targetKeyName] === key;
-      });
-      if (typeof index !== 'undefined' && index !== -1)
-        unref(propsRef).dataSource?.splice(index, 1);
+        for (let i = 0; i < data.length; i++) {
+          const row = data[i];
+          if (parseRowKeyValue(unref(getRowKey), row) === keyValue) {
+            return { index: i, data };
+          }
+          if (row.children?.length > 0) {
+            const result = findRow(row.children, keyValue);
+            if (result != null) {
+              return result;
+            }
+          }
+        }
+        return null;
+      }
+    }
+
+    for (const keyValue of delKeyValues) {
+      deleteRow(dataSourceRef.value, keyValue);
+      deleteRow(unref(propsRef).dataSource, keyValue);
     }
     setPagination({
       total: unref(propsRef).dataSource?.length,
     });
   }
 
-  function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
-    if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
+  function insertTableDataRecord(
+    record: Recordable | Recordable[],
+    index?: number,
+  ): Recordable[] | undefined {
+    // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
     index = index ?? dataSourceRef.value?.length;
-    unref(dataSourceRef).splice(index, 0, record);
-    unref(propsRef).dataSource?.splice(index, 0, record);
-    return unref(propsRef).dataSource;
+    const _record = isObject(record) ? [record as Recordable] : (record as Recordable[]);
+    unref(dataSourceRef).splice(index, 0, ..._record);
+    return unref(dataSourceRef);
   }
 
-  function findTableDataRecord(rowKey: string | number) {
-    if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
-
-    const rowKeyName = unref(getRowKey);
-    if (!rowKeyName) return;
-
+  function findTableDataRecord(keyValue: Key) {
+    if (!dataSourceRef.value || dataSourceRef.value.length === 0) return;
     const { childrenColumnName = 'children' } = unref(propsRef);
 
     const findRow = (array: any[]) => {
       let ret;
       array.some(function iter(r) {
-        if (typeof rowKeyName === 'function') {
-          if ((rowKeyName(r) as string) === rowKey) {
-            ret = r;
-            return true;
-          }
-        } else {
-          if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
-            ret = r;
-            return true;
-          }
+        if (parseRowKeyValue(unref(getRowKey), r) === keyValue) {
+          ret = r;
+          return true;
         }
         return r[childrenColumnName] && r[childrenColumnName].some(iter);
       });
       return ret;
     };
 
-    // const row = dataSourceRef.value.find(r => {
-    //   if (typeof rowKeyName === 'function') {
-    //     return (rowKeyName(r) as string) === rowKey
-    //   } else {
-    //     return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
-    //   }
-    // })
     return findRow(dataSourceRef.value);
   }
 
@@ -272,31 +271,31 @@ export function useDataSource(
 
       const { sortInfo = {}, filterInfo } = searchState;
 
-      let params: Recordable = {
-        ...pageParams,
-        ...(useSearchForm ? getFieldsValue() : {}),
-        ...searchInfo,
-        ...(opt?.searchInfo ?? {}),
-        ...defSort,
-        ...sortInfo,
-        ...filterInfo,
-        ...(opt?.sortInfo ?? {}),
-        ...(opt?.filterInfo ?? {}),
-      };
+      let params: Recordable = merge(
+        pageParams,
+        useSearchForm ? getFieldsValue() : {},
+        searchInfo,
+        opt?.searchInfo ?? {},
+        defSort,
+        sortInfo,
+        filterInfo,
+        opt?.sortInfo ?? {},
+        opt?.filterInfo ?? {},
+      );
       if (beforeFetch && isFunction(beforeFetch)) {
         params = (await beforeFetch(params)) || params;
       }
-
+      searchInfoRef.value = params;
       const res = await api(params);
       rawDataSourceRef.value = res;
 
       const isArrayResult = Array.isArray(res);
 
       let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
-      const resultTotal: number = isArrayResult ? 0 : get(res, totalField);
+      const resultTotal: number = isArrayResult ? res.length : get(res, totalField);
 
       // 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行
-      if (resultTotal) {
+      if (Number(resultTotal)) {
         const currentTotalPage = Math.ceil(resultTotal / pageSize);
         if (current > currentTotalPage) {
           setPagination({
@@ -335,7 +334,7 @@ export function useDataSource(
   }
 
   function setTableData<T = Recordable>(values: T[]) {
-    dataSourceRef.value = values;
+    dataSourceRef.value = values as Recordable[];
   }
 
   function getDataSource<T = Recordable>() {
@@ -350,6 +349,10 @@ export function useDataSource(
     return await fetch(opt);
   }
 
+  function getSearchInfo<T = Recordable>() {
+    return searchInfoRef.value as T;
+  }
+
   onMounted(() => {
     useTimeoutFn(() => {
       unref(propsRef).immediate && fetch();
@@ -357,9 +360,11 @@ export function useDataSource(
   });
 
   return {
-    getDataSourceRef,
+    getDataSourceRef: computed(() => getDataSourceRef.value),
     getDataSource,
     getRawDataSource,
+    searchInfoRef,
+    getSearchInfo,
     getRowKey,
     setTableData,
     getAutoCreateKey,

+ 84 - 0
src/components/Table/src/hooks/usePagination.ts

@@ -0,0 +1,84 @@
+import type { PaginationProps } from '../types/pagination';
+import type { BasicTableProps } from '../types/table';
+import { computed, unref, ref, ComputedRef, watch, h } from 'vue';
+import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
+import { isBoolean } from '/@/utils/is';
+import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
+import { useI18n } from '/@/hooks/web/useI18n';
+
+interface ItemRender {
+  page: number;
+  type: 'page' | 'prev' | 'next';
+  originalElement: any;
+}
+
+function itemRender({ page, type, originalElement }: ItemRender) {
+  if (type === 'prev') {
+    return page === 0 ? null : h(LeftOutlined);
+  } else if (type === 'next') {
+    return page === 1 ? null : h(RightOutlined);
+  }
+  return originalElement;
+}
+
+export function usePagination(refProps: ComputedRef<BasicTableProps>) {
+  const { t } = useI18n();
+
+  const configRef = ref<PaginationProps>({});
+  const show = ref(true);
+
+  watch(
+    () => unref(refProps).pagination,
+    (pagination) => {
+      if (!isBoolean(pagination) && pagination) {
+        configRef.value = {
+          ...unref(configRef),
+          ...(pagination ?? {}),
+        };
+      }
+    },
+  );
+
+  const getPaginationInfo = computed((): PaginationProps | boolean => {
+    const { pagination } = unref(refProps);
+
+    if (!unref(show) || (isBoolean(pagination) && !pagination)) {
+      return false;
+    }
+
+    return {
+      current: 1,
+      size: 'small',
+      defaultPageSize: PAGE_SIZE,
+      showTotal: (total) => t('component.table.total', { total }),
+      showSizeChanger: true,
+      pageSizeOptions: PAGE_SIZE_OPTIONS,
+      itemRender: itemRender,
+      showQuickJumper: true,
+      ...(isBoolean(pagination) ? {} : pagination),
+      ...unref(configRef),
+    };
+  });
+
+  function setPagination(info: Partial<PaginationProps>) {
+    const paginationInfo = unref(getPaginationInfo);
+    configRef.value = {
+      ...(!isBoolean(paginationInfo) ? paginationInfo : {}),
+      ...info,
+    };
+  }
+
+  function getPagination() {
+    return unref(getPaginationInfo);
+  }
+
+  function getShowPagination() {
+    return unref(show);
+  }
+
+  async function setShowPagination(flag: boolean) {
+    show.value = flag;
+  }
+
+  return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination };
+}

+ 88 - 16
src/components/Table/src/hooks/useRowSelection.ts

@@ -4,13 +4,15 @@ import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from '
 import { ROW_KEY } from '../const';
 import { omit } from 'lodash-es';
 import { findNodeAll } from '/@/utils/helper/treeHelper';
+import type { Key } from 'ant-design-vue/lib/table/interface';
+import { parseRowKey, parseRowKeyValue } from '../helper';
 
 export function useRowSelection(
   propsRef: ComputedRef<BasicTableProps>,
   tableData: Ref<Recordable[]>,
   emit: EmitType,
 ) {
-  const selectedRowKeysRef = ref<string[]>([]);
+  const selectedRowKeysRef = ref<Key[]>([]);
   const selectedRowRef = ref<Recordable[]>([]);
 
   const getRowSelectionRef = computed((): TableRowSelection | null => {
@@ -21,11 +23,55 @@ export function useRowSelection(
 
     return {
       selectedRowKeys: unref(selectedRowKeysRef),
-      hideDefaultSelections: false,
-      onChange: (selectedRowKeys: string[]) => {
-        setSelectedRowKeys(selectedRowKeys);
-        // selectedRowKeysRef.value = selectedRowKeys;
-        // selectedRowRef.value = selectedRows;
+      onChange: (selectedRowKeys: Key[], selectedRows: any[], isClickCustomRow?: boolean) => {
+        if (isClickCustomRow) {
+          // 点击行触发
+
+          // 维持外部定义的 onChange 回调
+          rowSelection.onChange?.(selectedRowKeys, selectedRows);
+        } else {
+          // 点击 checkbox/radiobox 触发
+
+          // 取出【当前页】所有 keyValues
+          const currentPageKeys = getCcurrentPageKeys();
+
+          // 从【所有分页】已选的 keyValues,且属于【当前页】的部分
+          for (const selectedKey of selectedRowKeysRef.value.filter((k) =>
+            currentPageKeys.includes(k),
+          )) {
+            // 判断是否已经不存在于【当前页】
+            if (selectedRowKeys.findIndex((k) => k === selectedKey) < 0) {
+              // 不存在 = 取消勾选
+              const removeIndex = selectedRowKeysRef.value.findIndex((k) => k === selectedKey);
+              if (removeIndex > -1) {
+                // 取消勾选
+                selectedRowKeysRef.value.splice(removeIndex, 1);
+                selectedRowRef.value.splice(removeIndex, 1);
+              }
+            }
+          }
+
+          // 存在于【当前页】,但不存在于【所有分页】,则认为是新增的
+          for (const selectedKey of selectedRowKeys) {
+            const existIndex = selectedRowKeysRef.value.findIndex((k) => k === selectedKey);
+            if (existIndex < 0) {
+              // 新增勾选
+              selectedRowKeysRef.value.push(selectedKey);
+              const record = selectedRows.find(
+                (o) => parseRowKeyValue(unref(getRowKey), o) === selectedKey,
+              );
+              if (record) {
+                selectedRowRef.value.push(record);
+              }
+            }
+          }
+
+          // 赋值调整过的值
+          setSelectedRowKeys(selectedRowKeysRef.value);
+
+          // 维持外部定义的onChange回调
+          rowSelection.onChange?.(selectedRowKeysRef.value, selectedRowRef.value);
+        }
       },
       ...omit(rowSelection, ['onChange']),
     };
@@ -33,7 +79,7 @@ export function useRowSelection(
 
   watch(
     () => unref(propsRef).rowSelection?.selectedRowKeys,
-    (v: string[]) => {
+    (v?: Key[]) => {
       setSelectedRowKeys(v);
     },
   );
@@ -45,7 +91,7 @@ export function useRowSelection(
         const { rowSelection } = unref(propsRef);
         if (rowSelection) {
           const { onChange } = rowSelection;
-          if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows());
+          if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows(), true);
         }
         emit('selection-change', {
           keys: getSelectRowKeys(),
@@ -64,26 +110,52 @@ export function useRowSelection(
     const { rowKey } = unref(propsRef);
     return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
   });
+	function getCcurrentPageKeys() {
+		const { childrenColumnName = 'children' } = unref(propsRef);
+		const keys: Key[] = [];
+		const extractKeys = (record: Recordable) => {
+			keys.push(parseRowKeyValue(unref(getRowKey), record));
+			if (record[childrenColumnName]?.length) {
+				record[childrenColumnName].forEach(extractKeys);
+			}
+		};
+		tableData.value.forEach(extractKeys);
+		return keys;
+	}
 
-  function setSelectedRowKeys(rowKeys: string[]) {
-    selectedRowKeysRef.value = rowKeys;
+  function setSelectedRowKeys(keyValues?: Key[]) {
+    selectedRowKeysRef.value = keyValues || [];
+    const rows = toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef)));
     const allSelectedRows = findNodeAll(
-      toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
-      (item) => rowKeys.includes(item[unref(getRowKey) as string]),
+      rows,
+      (item) => keyValues?.includes(parseRowKeyValue(unref(getRowKey), item)),
       {
         children: propsRef.value.childrenColumnName ?? 'children',
       },
     );
     const trueSelectedRows: any[] = [];
-    rowKeys.forEach((key: string) => {
-      const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key);
-      found && trueSelectedRows.push(found);
+    keyValues?.forEach((keyValue: Key) => {
+      const found = allSelectedRows.find(
+        (item) => parseRowKeyValue(unref(getRowKey), item) === keyValue,
+      );
+      if (found) {
+        trueSelectedRows.push(found);
+      } else {
+        // 跨页的时候,非本页数据无法得到,暂如此处理
+        // tableData or selectedRowRef 总有数据
+        if (rows[0]) {
+          trueSelectedRows.push({ [parseRowKey(unref(getRowKey), rows[0])]: keyValue });
+        }
+      }
     });
     selectedRowRef.value = trueSelectedRows;
   }
 
   function setSelectedRows(rows: Recordable[]) {
     selectedRowRef.value = rows;
+    selectedRowKeysRef.value = selectedRowRef.value.map((o) =>
+      parseRowKeyValue(unref(getRowKey), o),
+    );
   }
 
   function clearSelectedRowKeys() {
@@ -91,7 +163,7 @@ export function useRowSelection(
     selectedRowKeysRef.value = [];
   }
 
-  function deleteSelectRowByKey(key: string) {
+  function deleteSelectRowByKey(key: Key) {
     const selectedRowKeys = unref(selectedRowKeysRef);
     const index = selectedRowKeys.findIndex((item) => item === key);
     if (index !== -1) {

+ 55 - 0
src/components/Table/src/hooks/useScrollTo.ts

@@ -0,0 +1,55 @@
+import type { ComputedRef, Ref } from 'vue';
+import { nextTick, unref } from 'vue';
+import { warn } from '/@/utils/log';
+
+export function useTableScrollTo(
+  tableElRef: Ref<ComponentRef>,
+  getDataSourceRef: ComputedRef<Recordable[]>,
+) {
+  let bodyEl: HTMLElement | null;
+
+  async function findTargetRowToScroll(targetRowData: Recordable) {
+    const { id } = targetRowData;
+    const targetRowEl: HTMLElement | null | undefined = bodyEl?.querySelector(
+      `[data-row-key="${id}"]`,
+    );
+    //Add a delay to get new dataSource
+    await nextTick();
+    bodyEl?.scrollTo({
+      top: targetRowEl?.offsetTop ?? 0,
+      behavior: 'smooth',
+    });
+  }
+
+  function scrollTo(pos: string): void {
+    const table = unref(tableElRef);
+    if (!table) return;
+
+    const tableEl: Element = table.$el;
+    if (!tableEl) return;
+
+    if (!bodyEl) {
+      bodyEl = tableEl.querySelector('.ant-table-body');
+      if (!bodyEl) return;
+    }
+
+    const dataSource = unref(getDataSourceRef);
+    if (!dataSource) return;
+
+    // judge pos type
+    if (pos === 'top') {
+      findTargetRowToScroll(dataSource[0]);
+    } else if (pos === 'bottom') {
+      findTargetRowToScroll(dataSource[dataSource.length - 1]);
+    } else {
+      const targetRowData = dataSource.find((data) => data.id === pos);
+      if (targetRowData) {
+        findTargetRowToScroll(targetRowData);
+      } else {
+        warn(`id: ${pos} doesn't exist`);
+      }
+    }
+  }
+
+  return { scrollTo };
+}

+ 27 - 11
src/components/Table/src/hooks/useTable.ts

@@ -7,6 +7,7 @@ import { getDynamicProps } from '/@/utils';
 import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
 import { isProdMode } from '/@/utils/env';
 import { error } from '/@/utils/log';
+import type { Key } from 'ant-design-vue/lib/table/interface';
 
 type Props = Partial<DynamicProps<BasicTableProps>>;
 
@@ -76,6 +77,9 @@ export function useTable(tableProps?: Props): [
     redoHeight: () => {
       getTableInstance().redoHeight();
     },
+    setSelectedRows: (rows: Recordable[]) => {
+      return toRaw(getTableInstance().setSelectedRows(rows));
+    },
     setLoading: (loading: boolean) => {
       getTableInstance().setLoading(loading);
     },
@@ -85,11 +89,14 @@ export function useTable(tableProps?: Props): [
     getRawDataSource: () => {
       return getTableInstance().getRawDataSource();
     },
+    getSearchInfo: () => {
+      return getTableInstance().getSearchInfo();
+    },
     getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
       const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
       return toRaw(columns);
     },
-    setColumns: (columns: BasicColumn[]) => {
+    setColumns: (columns: BasicColumn[] | string[]) => {
       getTableInstance().setColumns(columns);
     },
     setTableData: (values: any[]) => {
@@ -98,8 +105,8 @@ export function useTable(tableProps?: Props): [
     setPagination: (info: Partial<PaginationProps>) => {
       return getTableInstance().setPagination(info);
     },
-    deleteSelectRowByKey: (key: string) => {
-      getTableInstance().deleteSelectRowByKey(key);
+    deleteSelectRowByKey: (keyValue: Key) => {
+      getTableInstance().deleteSelectRowByKey(keyValue);
     },
     getSelectRowKeys: () => {
       return toRaw(getTableInstance().getSelectRowKeys());
@@ -110,8 +117,8 @@ export function useTable(tableProps?: Props): [
     clearSelectedRowKeys: () => {
       getTableInstance().clearSelectedRowKeys();
     },
-    setSelectedRowKeys: (keys: string[] | number[]) => {
-      getTableInstance().setSelectedRowKeys(keys);
+    setSelectedRowKeys: (keyValues: Key[]) => {
+      getTableInstance().setSelectedRowKeys(keyValues);
     },
     getPaginationRef: () => {
       return getTableInstance().getPaginationRef();
@@ -122,17 +129,17 @@ export function useTable(tableProps?: Props): [
     updateTableData: (index: number, key: string, value: any) => {
       return getTableInstance().updateTableData(index, key, value);
     },
-    deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
-      return getTableInstance().deleteTableDataRecord(rowKey);
+    deleteTableDataRecord: (keyValues: Key | Key[]) => {
+      return getTableInstance().deleteTableDataRecord(keyValues);
     },
     insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
       return getTableInstance().insertTableDataRecord(record, index);
     },
-    updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
-      return getTableInstance().updateTableDataRecord(rowKey, record);
+    updateTableDataRecord: (keyValue: Key, record: Recordable) => {
+      return getTableInstance().updateTableDataRecord(keyValue, record);
     },
-    findTableDataRecord: (rowKey: string | number) => {
-      return getTableInstance().findTableDataRecord(rowKey);
+    findTableDataRecord: (keyValue: Key) => {
+      return getTableInstance().findTableDataRecord(keyValue);
     },
     getRowSelection: () => {
       return toRaw(getTableInstance().getRowSelection());
@@ -155,6 +162,15 @@ export function useTable(tableProps?: Props): [
     collapseAll: () => {
       getTableInstance().collapseAll();
     },
+    expandRows: (keyValues: Key[]) => {
+      getTableInstance().expandRows(keyValues);
+    },
+    collapseRows: (keyValues: Key[]) => {
+      getTableInstance().collapseRows(keyValues);
+    },
+    scrollTo: (pos: string) => {
+      getTableInstance().scrollTo(pos);
+    },
   };
 
   return [register, methods];

+ 90 - 16
src/components/Table/src/hooks/useTableExpand.ts

@@ -1,14 +1,16 @@
 import type { ComputedRef, Ref } from 'vue';
 import type { BasicTableProps } from '../types/table';
-import { computed, unref, ref, toRaw } from 'vue';
+import { computed, unref, ref, toRaw, nextTick } from 'vue';
 import { ROW_KEY } from '../const';
+import { parseRowKeyValue } from '../helper';
+import type { Key } from 'ant-design-vue/lib/table/interface';
 
 export function useTableExpand(
   propsRef: ComputedRef<BasicTableProps>,
   tableData: Ref<Recordable[]>,
   emit: EmitType,
 ) {
-  const expandedRowKeys = ref<string[]>([]);
+  const expandedRowKeys = ref<Key[]>([]);
 
   const getAutoCreateKey = computed(() => {
     return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
@@ -20,39 +22,111 @@ export function useTableExpand(
   });
 
   const getExpandOption = computed(() => {
-    const { isTreeTable } = unref(propsRef);
-    if (!isTreeTable) return {};
+    const { isTreeTable, expandRowByClick } = unref(propsRef);
+    if (!isTreeTable && !expandRowByClick) return {};
 
     return {
       expandedRowKeys: unref(expandedRowKeys),
-      onExpandedRowsChange: (keys: string[]) => {
-        expandedRowKeys.value = keys;
-        emit('expanded-rows-change', keys);
+      onExpandedRowsChange: (keyValues: string[]) => {
+        expandedRowKeys.value = keyValues;
+        emit('expanded-rows-change', keyValues);
       },
     };
   });
 
   function expandAll() {
-    const keys = getAllKeys();
-    expandedRowKeys.value = keys;
+    const keyValues = getAllKeys();
+    expandedRowKeys.value = keyValues;
+  }
+
+  function collapseAll() {
+    expandedRowKeys.value = [];
+  }
+
+  function expandRows(keyValues: Key[]) {
+    // use row ID expands the specified table row
+    const { isTreeTable, expandRowByClick } = unref(propsRef);
+    if (!isTreeTable && !expandRowByClick) return;
+    expandedRowKeys.value = [...expandedRowKeys.value, ...keyValues];
+  }
+
+  function collapseRows(keyValues: Key[]) {
+    // use row ID collapses the specified table row
+    const { isTreeTable, expandRowByClick } = unref(propsRef);
+    if (!isTreeTable && !expandRowByClick) return;
+    expandedRowKeys.value = unref(expandedRowKeys).filter(
+      (keyValue) => !keyValues.includes(keyValue),
+    );
   }
 
   function getAllKeys(data?: Recordable[]) {
-    const keys: string[] = [];
+    const keyValues: Array<number | string> = [];
     const { childrenColumnName } = unref(propsRef);
     toRaw(data || unref(tableData)).forEach((item) => {
-      keys.push(item[unref(getRowKey) as string]);
+      keyValues.push(parseRowKeyValue(unref(getRowKey), item));
       const children = item[childrenColumnName || 'children'];
       if (children?.length) {
-        keys.push(...getAllKeys(children));
+        keyValues.push(...getAllKeys(children));
       }
     });
-    return keys;
+    return keyValues;
   }
 
-  function collapseAll() {
-    expandedRowKeys.value = [];
+  // 获取展开路径 keyValues
+  function getKeyPaths(
+    records: Recordable[],
+    childrenColumnName: string,
+    keyValue: Key,
+    paths: Array<Key>,
+  ): boolean {
+    if (
+      records.findIndex((record) => parseRowKeyValue(unref(getRowKey), record) === keyValue) > -1
+    ) {
+      paths.push(keyValue);
+      return true;
+    } else {
+      for (const record of records) {
+        const children = record[childrenColumnName];
+        if (Array.isArray(children) && getKeyPaths(children, childrenColumnName, keyValue, paths)) {
+          paths.push(parseRowKeyValue(unref(getRowKey), record));
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  // 手风琴展开
+  function expandRowAccordion(keyValue: Key) {
+    const { childrenColumnName } = unref(propsRef);
+    const paths: Array<Key> = [];
+    getKeyPaths(tableData.value, childrenColumnName || 'children', keyValue, paths);
+    expandedRowKeys.value = paths;
+  }
+
+  // 监听展开事件,用于支持手风琴展开效果
+  function handleTableExpand(expanded: boolean, record: Recordable) {
+    // 手风琴开关
+    // isTreeTable 或 expandRowByClick 时支持
+    // 展开操作
+    if (
+      propsRef.value.accordion &&
+      (propsRef.value.isTreeTable || propsRef.value.expandRowByClick) &&
+      expanded
+    ) {
+      nextTick(() => {
+        expandRowAccordion(parseRowKeyValue(unref(getRowKey), record));
+      });
+    }
   }
 
-  return { getExpandOption, expandAll, collapseAll };
+  return {
+    getExpandOption,
+    expandAll,
+    collapseAll,
+    expandRows,
+    collapseRows,
+    expandRowAccordion,
+    handleTableExpand,
+  };
 }

+ 3 - 8
src/components/Table/src/hooks/useTableFooter.ts

@@ -6,11 +6,7 @@ import { useEventListener } from '/@/hooks/event/useEventListener';
 
 export function useTableFooter(
   propsRef: ComputedRef<BasicTableProps>,
-  scrollRef: ComputedRef<{
-    x: string | number | true;
-    y: Nullable<number>;
-    scrollToFirstRowOnChange: boolean;
-  }>,
+  scrollRef: ComputedRef<BasicTableProps['scroll']>,
   tableElRef: Ref<ComponentRef>,
   getDataSourceRef: ComputedRef<Recordable>,
 ) {
@@ -36,14 +32,13 @@ export function useTableFooter(
     nextTick(() => {
       const tableEl = unref(tableElRef);
       if (!tableEl) return;
-      const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
-      const bodyDom = bodyDomList[0];
+      const bodyDom = tableEl.$el.querySelector(' .ant-table-content,  .ant-table-body');
       useEventListener({
         el: bodyDom,
         name: 'scroll',
         listener: () => {
           const footerBodyDom = tableEl.$el.querySelector(
-            '.ant-table-footer .ant-table-body',
+            '.ant-table-footer .ant-table-container  [class^="ant-table-"]',
           ) as HTMLDivElement;
           if (!footerBodyDom || !bodyDom) return;
           footerBodyDom.scrollLeft = bodyDom.scrollLeft;

+ 2 - 2
src/components/Table/src/hooks/useTableForm.ts

@@ -7,7 +7,7 @@ import { isFunction } from '/@/utils/is';
 export function useTableForm(
   propsRef: ComputedRef<BasicTableProps>,
   slots: Slots,
-  fetch: (opt?: FetchParams | undefined) => Promise<void>,
+  fetch: (opt?: FetchParams | undefined) => Promise<Recordable<any>[] | undefined>,
   getLoading: ComputedRef<boolean | undefined>,
 ) {
   const getFormProps = computed((): Partial<FormProps> => {
@@ -30,7 +30,7 @@ export function useTableForm(
 
   function replaceFormSlotKey(key: string) {
     if (!key) return '';
-    return key?.replace?.(/form\-/, '') ?? '';
+    return key?.replace?.(/form-/, '') ?? '';
   }
 
   function handleSearchInfoChange(info: Recordable) {

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

@@ -1,5 +1,5 @@
 import type { ComputedRef, Slots } from 'vue';
-import type { BasicTableProps, InnerHandlers } from '../types/table';
+import type { BasicTableProps, InnerHandlers, InnerMethods } from '../types/table';
 import { unref, computed, h } from 'vue';
 import TableHeader from '../components/TableHeader.vue';
 import { isString } from '/@/utils/is';
@@ -9,9 +9,12 @@ export function useTableHeader(
   propsRef: ComputedRef<BasicTableProps>,
   slots: Slots,
   handlers: InnerHandlers,
+  //
+  methods: InnerMethods,
 ) {
   const getHeaderProps = computed((): Recordable => {
-    const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
+    const { title, showTableSetting, titleHelpMessage, tableSetting, showSelectionBar } =
+      unref(propsRef);
     const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
     if (hideTitle && !isString(title)) {
       return {};
@@ -29,6 +32,10 @@ export function useTableHeader(
                 showTableSetting,
                 tableSetting,
                 onColumnsChange: handlers.onColumnsChange,
+                //
+                clearSelectedRowKeys: methods.clearSelectedRowKeys,
+                count: methods.getSelectRowKeys().length,
+                showSelectionBar,
               } as Recordable,
               {
                 ...(slots.toolbar

+ 228 - 56
src/components/Table/src/hooks/useTableScroll.ts

@@ -1,22 +1,31 @@
 import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
-import type { Ref, ComputedRef } from 'vue';
-import { computed, unref, ref, nextTick, watch } from 'vue';
+import { Ref, ComputedRef, ref, computed, unref, nextTick, watch } from 'vue';
 import { getViewportOffset } from '/@/utils/domUtils';
 import { isBoolean } from '/@/utils/is';
 import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
-import { useModalContext } from '/@/components/Modal';
 import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
-import { useDebounceFn } from '@vueuse/core';
+import { useModalContext } from '/@/components/Modal';
+import { useDebounceFn, promiseTimeout } from '@vueuse/core';
+
+import {
+  footerHeight as layoutFooterHeight,
+  layoutMultipleHeadePlaceholderTime,
+} from '/@/settings/designSetting';
+
+import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+
+const { getShowFooter, getFullContent } = useRootSetting();
 
 export function useTableScroll(
   propsRef: ComputedRef<BasicTableProps>,
   tableElRef: Ref<ComponentRef>,
   columnsRef: ComputedRef<BasicColumn[]>,
-  rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
+  rowSelectionRef: ComputedRef<TableRowSelection | null>,
   getDataSourceRef: ComputedRef<Recordable[]>,
+  wrapRef: Ref<HTMLElement | null>,
+  formRef: Ref<ComponentRef>,
 ) {
-  const tableHeightRef: Ref<Nullable<number>> = ref(null);
-
+  const tableHeightRef: Ref<Nullable<number | string>> = ref(167);
   const modalFn = useModalContext();
 
   // Greater than animation time 280
@@ -28,7 +37,7 @@ export function useTableScroll(
   });
 
   watch(
-    () => [unref(getCanResize), unref(getDataSourceRef)?.length],
+    () => [unref(getCanResize), unref(getDataSourceRef)?.length, unref(getShowFooter)],
     () => {
       debounceRedoHeight();
     },
@@ -37,14 +46,26 @@ export function useTableScroll(
     },
   );
 
+  watch(
+    () => [unref(getFullContent)],
+    async () => {
+      // 等待动画结束后200毫秒
+      await promiseTimeout(layoutMultipleHeadePlaceholderTime * 1000 + 200);
+      debounceRedoHeight();
+    },
+    {
+      flush: 'post',
+    },
+  );
+
   function redoHeight() {
     nextTick(() => {
       calcTableHeight();
     });
   }
 
-  function setHeight(heigh: number) {
-    tableHeightRef.value = heigh;
+  function setHeight(height: number) {
+    tableHeightRef.value = height;
     //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
     modalFn?.redoModalHeight?.();
   }
@@ -54,21 +75,13 @@ export function useTableScroll(
   let footerEl: HTMLElement | null;
   let bodyEl: HTMLElement | null;
 
-  async function calcTableHeight() {
-    const { resizeHeightOffset, pagination, maxHeight } = unref(propsRef);
-    const tableData = unref(getDataSourceRef);
-
-    const table = unref(tableElRef);
-    if (!table) return;
-
-    const tableEl: Element = table.$el;
-    if (!tableEl) return;
-
-    if (!bodyEl) {
-      bodyEl = tableEl.querySelector('.ant-table-body');
-      if (!bodyEl) return;
-    }
+  /**
+   * table wrapper padding 的高度
+   * @description 来自于 .vben-basic-table .ant-table-wrapper
+   */
+  const tableWrapperPadding = 6;
 
+  function handleScrollBar(bodyEl: HTMLElement, tableEl: Element) {
     const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
     const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
 
@@ -85,38 +98,42 @@ export function useTableScroll(
     } else {
       !tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
     }
+  }
 
-    bodyEl!.style.height = 'unset';
-
-    if (!unref(getCanResize) || tableData.length === 0) return;
-
-    await nextTick();
-    //Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
-
-    const headEl = tableEl.querySelector('.ant-table-thead ');
-
-    if (!headEl) return;
-
-    // Table height from bottom
-    const { bottomIncludeBody } = getViewportOffset(headEl);
-    // Table height from bottom height-custom offset
+  /**
+   * 计算分页器高度
+   * @param tableEl table element
+   * @returns number
+   */
+  function caclPaginationHeight(tableEl: Element): number {
+    const { pagination } = unref(propsRef);
 
-    const paddingHeight = 32;
-    // Pager height
-    let paginationHeight = 2;
+    let paginationHeight = 0;
     if (!isBoolean(pagination)) {
-      paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
+      // 从 Dom 获取
+      if (!paginationEl) {
+        paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
+      }
       if (paginationEl) {
+        // 分页 margin-top
+        const paginationElMarginTop =
+          parseInt(getComputedStyle(paginationEl)?.marginTop) || 10 + 24;
+        // 分页高度
         const offsetHeight = paginationEl.offsetHeight;
-        paginationHeight += offsetHeight || 0;
+        paginationHeight = offsetHeight + paginationElMarginTop;
       } else {
-        // TODO First fix 24
-        paginationHeight += 24;
+        // 找不到分页组件,缺省给予默认分页 margin-top + 高度
+        paginationHeight = 10 + 24;
       }
     } else {
-      paginationHeight = -8;
+      // 不显示分页,pagination 为 false 的时候
+      paginationHeight = 0;
     }
+    return paginationHeight;
+  }
 
+  function caclFooterHeight(tableEl: Element): number {
+    const { pagination } = unref(propsRef);
     let footerHeight = 0;
     if (!isBoolean(pagination)) {
       if (!footerEl) {
@@ -126,26 +143,179 @@ export function useTableScroll(
         footerHeight += offsetHeight || 0;
       }
     }
+    return footerHeight;
+  }
 
+  function calcHeaderHeight(headEl: Element): number {
     let headerHeight = 0;
     if (headEl) {
       headerHeight = (headEl as HTMLElement).offsetHeight;
     }
+    return headerHeight;
+  }
+
+  /**
+   * 计算从表头一直到body底部的总高度
+   * @param tableEl table element
+   * @param headEl table 页头 element
+   * @returns number
+   */
+  function calcBottomAndPaddingHeight(tableEl: Element, headEl: Element) {
+    const { isCanResizeParent } = unref(propsRef);
+    let bottomIncludeBody = 0;
+    if (unref(wrapRef) && isCanResizeParent) {
+      // 继承父元素高度
+      const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0;
+
+      let formHeight = unref(formRef)?.$el.offsetHeight ?? 0;
+      if (formHeight) {
+        // 来自于 .vben-basic-table-form-container .ant-form 以及 .vben-basic-table-form-container
+        formHeight += 16 + 16 * 2;
+      }
+
+      bottomIncludeBody = wrapHeight - tableWrapperPadding - formHeight;
+    } else {
+      // 缺省 wrapRef 情况下
+      bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody;
+    }
+
+    return bottomIncludeBody;
+  }
+
+  /**
+   * 计算 table 在 modal 内 modal 所占用的高度
+   * @param tableEl table element
+   * @returns number
+   */
+  function calcModalHeight(tableEl: Element) {
+    // 找一下 table 是否在 modal 内,获得 modal、wrap、footer,并考虑 fullscreen 的情况
+    let modalEl: Nullable<HTMLElement> = null;
+    let modalWrapEl: Nullable<HTMLElement> = null;
+    let modalFooterEl: Nullable<HTMLElement> = null;
+    let modalElIterator: HTMLElement = tableEl.parentElement!;
+    let modalIsFullscreen = false;
+    while (modalElIterator !== document.body) {
+      if (!modalElIterator) break;
+      if (modalElIterator.classList.contains('ant-modal')) {
+        modalEl = modalElIterator;
+        modalWrapEl = modalEl.parentElement;
+        modalFooterEl = modalElIterator.querySelector('.ant-modal-content>.ant-modal-footer');
+        modalIsFullscreen = modalWrapEl?.classList.contains('fullscreen-modal') ?? false;
+        break;
+      }
+      modalElIterator = modalElIterator.parentElement!;
+    }
+
+    if (modalEl) {
+      // table 在 modal 内
+
+      // modal top
+      const { top: modalTop = 0 } = modalEl ? getViewportOffset(modalEl) : {};
+
+      // 来自于 .ant-modal,非全屏为 24,全屏为 0
+      const modalBottom = modalIsFullscreen ? 0 : 24;
+
+      //  modal footer 高度
+      const modalFooterHeight = modalFooterEl?.offsetHeight ?? 0;
+
+      // modal footer 边距,来自于 .ant-modal .ant-modal-footer
+      const modalFooterMarginTop = modalFooterEl
+        ? modalIsFullscreen
+          ? 0
+          : parseInt(getComputedStyle(modalFooterEl).marginTop)
+        : 0;
+
+      // 来自于 .ant-modal .ant-modal-body > .scrollbar
+      const modalScrollBarHeight = 14;
+
+      return (
+        (modalTop > modalBottom ? modalTop : modalBottom) +
+        modalFooterHeight +
+        modalFooterMarginTop +
+        modalScrollBarHeight
+      );
+    }
+
+    // table 不住 modal 内
+    return 0;
+  }
+
+  /**
+   * 根据样式返回一些间距高度
+   * @returns number
+   */
+  function getMarginPaddingHeight() {
+    const { isCanResizeParent } = unref(propsRef);
+
+    if (unref(wrapRef) && isCanResizeParent) {
+      // 继承父元素高度
+      return tableWrapperPadding;
+    }
+    return (
+      tableWrapperPadding + 16 // 来自于 .vben-basic-table-form-container 或是 .p-4
+    );
+  }
+
+  async function calcTableHeight() {
+    const { resizeHeightOffset, maxHeight } = unref(propsRef);
+    const tableData = unref(getDataSourceRef);
+
+    const table = unref(tableElRef);
+    if (!table) return;
+
+    const tableEl: Element = table.$el;
+    if (!tableEl) return;
+
+    if (!bodyEl) {
+      bodyEl = tableEl.querySelector('.ant-table-body');
+      if (!bodyEl) return;
+    }
+
+    handleScrollBar(bodyEl, tableEl);
+
+    bodyEl!.style.height = 'unset';
+
+    if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return;
+
+    await nextTick();
+    // Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
+
+    const headEl = tableEl.querySelector('.ant-table-thead ');
+
+    if (!headEl) return;
+
+    const paginationHeight = caclPaginationHeight(tableEl);
+    const footerHeight = caclFooterHeight(tableEl);
+    const headerHeight = calcHeaderHeight(headEl);
+    const bottomIncludeBody = calcBottomAndPaddingHeight(tableEl, headEl);
+
+    const modalHeight = calcModalHeight(tableEl);
+
+    const marginPaddingHeight = getMarginPaddingHeight();
 
-    let height =
+    // Math.floor 宁愿小1px,也不溢出
+    let height = Math.floor(
       bottomIncludeBody -
-      (resizeHeightOffset || 0) -
-      paddingHeight -
-      paginationHeight -
-      footerHeight -
-      headerHeight;
+        (resizeHeightOffset || 0) -
+        paginationHeight -
+        footerHeight -
+        headerHeight -
+        // 弹窗(如果有)相关高度
+        modalHeight -
+        // 页面 footer 高度(非弹窗的时候)
+        (getShowFooter.value && modalHeight <= 0 ? layoutFooterHeight : 0) -
+        // 样式间距高度
+        marginPaddingHeight -
+        // 预留非整数高度溢出(如实际高度为100.5,offsetHeight 的值为101)
+        1,
+    );
 
     height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
     setHeight(height);
 
     bodyEl!.style.height = `${height}px`;
   }
-  useWindowSizeFn(calcTableHeight, 280);
+  useWindowSizeFn(calcTableHeight, { wait: 280 });
   onMountedOrActivated(() => {
     calcTableHeight();
     nextTick(() => {
@@ -164,9 +334,11 @@ export function useTableScroll(
 
     const columns = unref(columnsRef).filter((item) => !item.defaultHidden);
     columns.forEach((item) => {
-      width += Number.parseInt(item.width as string) || 0;
+      width += Number.parseFloat(item.width as string) || 0;
     });
-    const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
+    const unsetWidthColumns = columns.filter(
+      (item) => !Reflect.has(item, 'width') && item.ifShow !== false,
+    );
 
     const len = unsetWidthColumns.length;
     if (len !== 0) {
@@ -186,7 +358,7 @@ export function useTableScroll(
       y: canResize ? tableHeight : null,
       scrollToFirstRowOnChange: false,
       ...scroll,
-    };
+    } as BasicTableProps['scroll'];
   });
 
   return { getScrollRef, redoHeight };

+ 22 - 23
src/components/Table/src/props.ts

@@ -8,16 +8,19 @@ import type {
   TableCustomRecord,
   TableRowSelection,
   SizeType,
+  BasicTableProps,
 } from './types/table';
 import type { FormProps } from '/@/components/Form';
+
 import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
 import { propTypes } from '/@/utils/propTypes';
+import type { Key } from 'ant-design-vue/lib/table/interface';
 
 export const basicProps = {
-  clickToRowSelect: propTypes.bool.def(true),
-  isTreeTable: propTypes.bool.def(false),
+  clickToRowSelect: { type: Boolean, default: true },
+  isTreeTable: Boolean,
   tableSetting: propTypes.shape<TableSetting>({}),
-  inset: propTypes.bool,
+  inset: Boolean,
   sortFn: {
     type: Function as PropType<(sortInfo: SorterResult) => any>,
     default: DEFAULT_SORT_FN,
@@ -26,10 +29,10 @@ export const basicProps = {
     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,
+  showTableSetting: Boolean,
+  autoCreateKey: { type: Boolean, default: true },
+  striped: { type: Boolean, default: true },
+  showSummary: Boolean,
   summaryFunc: {
     type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
     default: null,
@@ -39,7 +42,7 @@ export const basicProps = {
     default: null,
   },
   indentSize: propTypes.number.def(24),
-  canColDrag: propTypes.bool.def(true),
+  canColDrag: { type: Boolean, default: true },
   api: {
     type: Function as PropType<(...arg: any[]) => Promise<any>>,
     default: null,
@@ -63,8 +66,8 @@ export const basicProps = {
     },
   },
   // 立即请求接口
-  immediate: propTypes.bool.def(true),
-  emptyDataIsShowTable: propTypes.bool.def(true),
+  immediate: { type: Boolean, default: true },
+  emptyDataIsShowTable: { type: Boolean, default: true },
   // 额外的请求参数
   searchInfo: {
     type: Object as PropType<Recordable>,
@@ -83,10 +86,10 @@ export const basicProps = {
     default: null,
   },
   columns: {
-    type: [Array] as PropType<BasicColumn[]>,
+    type: Array as PropType<BasicColumn[]>,
     default: () => [],
   },
-  showIndexColumn: propTypes.bool.def(true),
+  showIndexColumn: { type: Boolean, default: true },
   indexColumnProps: {
     type: Object as PropType<BasicColumn>,
     default: null,
@@ -95,14 +98,16 @@ export const basicProps = {
     type: Object as PropType<BasicColumn>,
     default: null,
   },
-  ellipsis: propTypes.bool.def(true),
-  canResize: propTypes.bool.def(true),
+  ellipsis: { type: Boolean, default: true },
+  isCanResizeParent: { type: Boolean, default: false },
+  canResize: { type: Boolean, default: true },
   clearSelectOnPageChange: propTypes.bool,
   resizeHeightOffset: propTypes.number.def(0),
   rowSelection: {
     type: Object as PropType<TableRowSelection | null>,
     default: null,
   },
+  showSelectionBar: propTypes.bool,
   title: {
     type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
     default: null,
@@ -116,7 +121,7 @@ export const basicProps = {
     default: null,
   },
   rowKey: {
-    type: [String, Function] as PropType<string | ((record: Recordable) => string)>,
+    type: [String, Function] as PropType<BasicTableProps['rowKey']>,
     default: '',
   },
   bordered: propTypes.bool,
@@ -129,17 +134,11 @@ export const basicProps = {
     type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>,
   },
   scroll: {
-    type: Object as PropType<{ x: number | true; y: number }>,
-    default: null,
+    type: Object as PropType<PropType<BasicTableProps['scroll']>>,
   },
   beforeEditSubmit: {
     type: Function as PropType<
-      (data: {
-        record: Recordable;
-        index: number;
-        key: string | number;
-        value: any;
-      }) => Promise<any>
+      (data: { record: Recordable; index: number; key: Key; value: any }) => Promise<any>
     >,
   },
   size: {

+ 1 - 1
src/components/Table/src/types/column.ts

@@ -80,7 +80,7 @@ export interface ColumnProps<T> {
    * Whether filterDropdown is visible
    * @type boolean
    */
-  filterDropdownVisible?: boolean;
+  filterDropdownOpen?: boolean;
 
   /**
    * Whether the dataSource is filtered

+ 5 - 1
src/components/Table/src/types/componentType.ts

@@ -3,8 +3,12 @@ export type ComponentType =
   | 'InputNumber'
   | 'Select'
   | 'ApiSelect'
+  | 'AutoComplete'
   | 'ApiTreeSelect'
   | 'Checkbox'
   | 'Switch'
   | 'DatePicker'
-  | 'TimePicker';
+  | 'TimePicker'
+  | 'RadioGroup'
+  | 'RadioButtonGroup'
+  | 'ApiRadioGroup';

+ 17 - 1
src/components/Table/src/types/pagination.ts

@@ -7,9 +7,18 @@ interface PaginationRenderProps {
   originalElement: any;
 }
 
+type PaginationPositon =
+  | 'topLeft'
+  | 'topCenter'
+  | 'topRight'
+  | 'bottomLeft'
+  | 'bottomCenter'
+  | 'bottomRight';
+
 export declare class PaginationConfig extends Pagination {
-  position?: 'top' | 'bottom' | 'both';
+  position?: PaginationPositon[];
 }
+
 export interface PaginationProps {
   /**
    * total number of data items
@@ -96,4 +105,11 @@ export interface PaginationProps {
    * @type Function
    */
   itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
+
+  /**
+   * specify the position of Pagination
+   * @default ['bottomRight']
+   * @type string[]
+   */
+  position?: PaginationPositon[];
 }

+ 78 - 19
src/components/Table/src/types/table.ts

@@ -2,13 +2,18 @@ import type { VNodeChild } from 'vue';
 import type { PaginationProps } from './pagination';
 import type { FormProps } from '/@/components/Form';
 import type {
-  ColumnProps,
   TableRowSelection as ITableRowSelection,
+  Key,
 } from 'ant-design-vue/lib/table/interface';
 
+import type { ColumnProps } from 'ant-design-vue/lib/table';
+
 import { ComponentType } from './componentType';
 import { VueNode } from '/@/utils/propTypes';
 import { RoleEnum } from '/@/enums/roleEnum';
+import { FixedType } from 'ant-design-vue/es/vc-table/interface';
+
+import AntDesignVueTable from 'ant-design-vue/es/table';
 
 export declare type SortOrder = 'ascend' | 'descend';
 
@@ -19,9 +24,12 @@ export interface TableCurrentDataSource<T = Recordable> {
 export interface TableRowSelection<T = any> extends ITableRowSelection {
   /**
    * Callback executed when selected rows change
-   * @type Function
+   * @param selectedRowKeys 已选的 keyValues
+   * @param selectedRows 已选的 records
+   * @param isClickCustomRow 是否是点击行触发(反之,就是点击checkbox/radiobox)
+   * @returns void
    */
-  onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
+  onChange?: (selectedRowKeys: Key[], selectedRows: T[], isClickCustomRow?: boolean) => void;
 
   /**
    * Callback executed when select/deselect one row
@@ -39,7 +47,7 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
    * Callback executed when row selection is inverted
    * @type Function
    */
-  onSelectInvert?: (selectedRows: string[] | number[]) => any;
+  onSelectInvert?: (selectedRows: Key[]) => any;
 }
 
 export interface TableCustomRecord<T> {
@@ -85,27 +93,32 @@ export interface GetColumnsParams {
 export type SizeType = 'default' | 'middle' | 'small' | 'large';
 
 export interface TableActionType {
-  reload: (opt?: FetchParams) => Promise<void>;
+  reload: (opt?: FetchParams) => Promise<Recordable<any>[] | undefined>;
+  setSelectedRows: (rows: Recordable[]) => void;
   getSelectRows: <T = Recordable>() => T[];
   clearSelectedRowKeys: () => void;
   expandAll: () => void;
   collapseAll: () => void;
-  getSelectRowKeys: () => string[];
-  deleteSelectRowByKey: (key: string) => void;
+  expandRows: (keyValues: Key[]) => void;
+  collapseRows: (keyValues: Key[]) => void;
+  scrollTo: (pos: string) => void; // pos: id | "top" | "bottom"
+  getSelectRowKeys: () => Key[];
+  deleteSelectRowByKey: (keyValue: Key) => void;
   setPagination: (info: Partial<PaginationProps>) => void;
   setTableData: <T = Recordable>(values: T[]) => void;
-  updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
-  deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
-  insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
-  findTableDataRecord: (rowKey: string | number) => Recordable | void;
+  updateTableDataRecord: (keyValue: Key, record: Recordable) => Recordable | void;
+  deleteTableDataRecord: (keyValues: Key | Key[]) => void;
+  insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => Recordable[] | void;
+  findTableDataRecord: (keyValue: Key) => Recordable | void;
   getColumns: (opt?: GetColumnsParams) => BasicColumn[];
   setColumns: (columns: BasicColumn[] | string[]) => void;
   getDataSource: <T = Recordable>() => T[];
   getRawDataSource: <T = Recordable>() => T;
+  getSearchInfo: <T = Recordable>() => T;
   setLoading: (loading: boolean) => void;
   setProps: (props: Partial<BasicTableProps>) => void;
   redoHeight: () => void;
-  setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
+  setSelectedRowKeys: (keyValues: Key[]) => void;
   getPaginationRef: () => PaginationProps | boolean;
   getSize: () => SizeType;
   getRowSelection: () => TableRowSelection<Recordable>;
@@ -115,6 +128,7 @@ export interface TableActionType {
   setShowPagination: (show: boolean) => Promise<void>;
   getShowPagination: () => boolean;
   setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
+  setCacheColumns?: (columns: BasicColumn[]) => void;
 }
 
 export interface FetchSetting {
@@ -132,6 +146,7 @@ export interface TableSetting {
   redo?: boolean;
   size?: boolean;
   setting?: boolean;
+  settingCache?: boolean;
   fullScreen?: boolean;
 }
 
@@ -139,6 +154,7 @@ export interface BasicTableProps<T = any> {
   // 点击行选中
   clickToRowSelect?: boolean;
   isTreeTable?: boolean;
+  accordion?: boolean; // isTreeTable 或 expandRowByClick 时支持
   // 自定义排序方法
   sortFn?: (sortInfo: SorterResult) => any;
   // 排序方法
@@ -191,6 +207,8 @@ export interface BasicTableProps<T = any> {
   actionColumn?: BasicColumn;
   // 文本超过宽度是否显示。。。
   ellipsis?: boolean;
+  // 是否继承父级高度(父级高度-表单高度-padding高度)
+  isCanResizeParent?: boolean;
   // 是否可以自适应高度
   canResize?: boolean;
   // 自适应高度偏移, 计算结果-偏移量
@@ -199,7 +217,7 @@ export interface BasicTableProps<T = any> {
   // 在分页改变的时候清空选项
   clearSelectOnPageChange?: boolean;
   //
-  rowKey?: string | ((record: Recordable) => string);
+  rowKey?: InstanceType<typeof AntDesignVueTable>['$props']['rowKey'];
   // 数据
   dataSource?: Recordable[];
   // 标题右侧提示
@@ -302,12 +320,18 @@ export interface BasicTableProps<T = any> {
   rowSelection?: TableRowSelection;
 
   /**
+   * Show table selection bar(显示多选状态栏)
+   * @type boolean
+   */
+  showSelectionBar?: boolean;
+
+  /**
    * 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 };
+  scroll?: InstanceType<typeof AntDesignVueTable>['$props']['scroll'];
 
   /**
    * Whether to show table header
@@ -374,7 +398,7 @@ export interface BasicTableProps<T = any> {
   beforeEditSubmit?: (data: {
     record: Recordable;
     index: number;
-    key: string | number;
+    key: Key;
     value: any;
   }) => Promise<any>;
 
@@ -393,7 +417,7 @@ export interface BasicTableProps<T = any> {
    * @param expanded
    * @param record
    */
-  onExpand?: (expande: boolean, record: T) => void;
+  onExpand?: (expanded: boolean, record: T) => void;
 
   /**
    * Callback executed when the expanded rows change
@@ -410,7 +434,7 @@ export type CellFormat =
   | Map<string | number, any>;
 
 // @ts-ignore
-export interface BasicColumn extends ColumnProps {
+export interface BasicColumn extends ColumnProps<Recordable> {
   children?: BasicColumn[];
   filters?: {
     text: string;
@@ -426,11 +450,13 @@ export interface BasicColumn extends ColumnProps {
 
   slots?: Recordable;
 
+  // 自定义header渲染
+  customHeaderRender?: (column: BasicColumn) => string | VNodeChild | JSX.Element;
   // Whether to hide the column by default, it can be displayed in the column configuration
   defaultHidden?: boolean;
 
   // Help text for table column header
-  helpMessage?: string | string[];
+  helpMessage?: string | string[] | VNodeChild | JSX.Element;
 
   format?: CellFormat;
 
@@ -439,7 +465,14 @@ export interface BasicColumn extends ColumnProps {
   editRow?: boolean;
   editable?: boolean;
   editComponent?: ComponentType;
-  editComponentProps?: Recordable;
+  editComponentProps?:
+    | ((opt: {
+        text: string | number | boolean | Recordable;
+        record: Recordable;
+        column: BasicColumn;
+        index: number;
+      }) => Recordable)
+    | Recordable;
   editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
   editValueMap?: (value: any) => string;
   onEditRow?: () => void;
@@ -447,6 +480,16 @@ export interface BasicColumn extends ColumnProps {
   auth?: RoleEnum | RoleEnum[] | string | string[];
   // 业务控制是否显示
   ifShow?: boolean | ((column: BasicColumn) => boolean);
+  // 自定义修改后显示的内容
+  editRender?: (opt: {
+    text: string | number | boolean | Recordable;
+    record: Recordable;
+    column: BasicColumn;
+    index: number;
+    currentValue: string | number | boolean | Recordable;
+  }) => VNodeChild | JSX.Element;
+  // 动态 Disabled
+  editDynamicDisabled?: boolean | ((record: Recordable) => boolean);
 }
 
 export type ColumnChangeParam = {
@@ -458,3 +501,19 @@ export type ColumnChangeParam = {
 export interface InnerHandlers {
   onColumnsChange: (data: ColumnChangeParam[]) => void;
 }
+
+export interface InnerMethods {
+  clearSelectedRowKeys: TableActionType['clearSelectedRowKeys'];
+  getSelectRowKeys: TableActionType['getSelectRowKeys'];
+}
+
+export interface ColumnOptionsType {
+  value: string;
+  label: string;
+  //
+  column: {
+    defaultHidden?: boolean;
+  };
+  //
+  fixed?: FixedType;
+}

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

@@ -1,6 +1,7 @@
 import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
 import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
 import { RoleEnum } from '/@/enums/roleEnum';
+
 export interface ActionItem extends ButtonProps {
   onClick?: Fn;
   label?: string;
@@ -23,4 +24,17 @@ export interface PopConfirm {
   confirm: Fn;
   cancel?: Fn;
   icon?: string;
+  placement?:
+    | 'top'
+    | 'left'
+    | 'right'
+    | 'bottom'
+    | 'topLeft'
+    | 'topRight'
+    | 'leftTop'
+    | 'leftBottom'
+    | 'rightTop'
+    | 'rightBottom'
+    | 'bottomLeft'
+    | 'bottomRight';
 }

+ 2 - 0
src/enums/cacheEnum.ts

@@ -25,6 +25,8 @@ export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__';
 // base global session key
 export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';
 
+// table 列设置
+export const TABLE_SETTING_KEY = 'TABLE__SETTING__KEY__';
 export enum CacheTypeEnum {
   SESSION,
   LOCAL,

+ 1 - 0
src/enums/pageEnum.ts

@@ -8,3 +8,4 @@ export enum PageEnum {
   // error log page path
   ERROR_LOG_PAGE = '/error-log/list',
 }
+export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';

+ 9 - 0
src/settings/designSetting.ts

@@ -2,8 +2,17 @@ import { ThemeEnum } from '../enums/appEnum';
 
 export const prefixCls = 'vben';
 
+export const multipleTabHeight = 30;
+
 export const darkMode = ThemeEnum.LIGHT;
 
+// 页脚固定高度
+export const footerHeight = 75;
+
+// .@{namespace}-layout-multiple-header__placeholder
+// 全屏页头动画时长
+export const layoutMultipleHeadePlaceholderTime = 0.6;
+
 // app theme preset color
 export const APP_PRESET_COLOR_LIST: string[] = [
   '#0960bd',

+ 109 - 0
src/store/modules/tableSetting.ts

@@ -0,0 +1,109 @@
+import { defineStore } from 'pinia';
+
+import { TABLE_SETTING_KEY } from '/@/enums/cacheEnum';
+
+import { Persistent } from '/@/utils/cache/persistent';
+
+import type { TableSetting } from '#/store';
+import type { SizeType, ColumnOptionsType } from '/@/components/Table/src/types/table';
+
+interface TableSettingState {
+  setting: Nullable<Partial<TableSetting>>;
+}
+
+export const useTableSettingStore = defineStore({
+  id: 'table-setting',
+  state: (): TableSettingState => ({
+    setting: Persistent.getLocal(TABLE_SETTING_KEY),
+  }),
+  getters: {
+    getTableSetting(state): Nullable<Partial<TableSetting>> {
+      return state.setting;
+    },
+    //
+    getTableSize(state) {
+      return state.setting?.size || 'middle';
+    },
+    //
+    getShowIndexColumn(state) {
+      return (routerName: string) => {
+        return state.setting?.showIndexColumn?.[routerName];
+      };
+    },
+    //
+    getShowRowSelection(state) {
+      return (routerName: string) => {
+        return state.setting?.showRowSelection?.[routerName];
+      };
+    },
+    //
+    getColumns(state) {
+      return (routerName: string) => {
+        return state.setting?.columns && state.setting?.columns[routerName]
+          ? state.setting?.columns[routerName]
+          : null;
+      };
+    },
+  },
+  actions: {
+    setTableSetting(setting: Partial<TableSetting>) {
+      this.setting = Object.assign({}, this.setting, setting);
+      Persistent.setLocal(TABLE_SETTING_KEY, this.setting, true);
+    },
+    resetTableSetting() {
+      Persistent.removeLocal(TABLE_SETTING_KEY, true);
+      this.setting = null;
+    },
+    //
+    setTableSize(size: SizeType) {
+      this.setTableSetting(
+        Object.assign({}, this.setting, {
+          size,
+        }),
+      );
+    },
+    //
+    setShowIndexColumn(routerName: string, show: boolean) {
+      this.setTableSetting(
+        Object.assign({}, this.setting, {
+          showIndexColumn: {
+            ...this.setting?.showIndexColumn,
+            [routerName]: show,
+          },
+        }),
+      );
+    },
+    //
+    setShowRowSelection(routerName: string, show: boolean) {
+      this.setTableSetting(
+        Object.assign({}, this.setting, {
+          showRowSelection: {
+            ...this.setting?.showRowSelection,
+            [routerName]: show,
+          },
+        }),
+      );
+    },
+    //
+    setColumns(routerName: string, columns: Array<ColumnOptionsType>) {
+      this.setTableSetting(
+        Object.assign({}, this.setting, {
+          columns: {
+            ...this.setting?.columns,
+            [routerName]: columns,
+          },
+        }),
+      );
+    },
+    clearColumns(routerName: string) {
+      this.setTableSetting(
+        Object.assign({}, this.setting, {
+          columns: {
+            ...this.setting?.columns,
+            [routerName]: undefined,
+          },
+        }),
+      );
+    },
+  },
+});

+ 35 - 0
src/utils/is.ts

@@ -1,3 +1,38 @@
+export {
+  isArguments,
+  isArrayBuffer,
+  isArrayLike,
+  isArrayLikeObject,
+  isBuffer,
+  // isBoolean,
+  // isDate,
+  // isElement,
+  // isEmpty,
+  isEqual,
+  isEqualWith,
+  isError,
+  // isFunction,
+  isFinite,
+  isLength,
+  // isMap,
+  isMatch,
+  isMatchWith,
+  isNative,
+  isNil,
+  // isNumber,
+  // isNull,
+  isObjectLike,
+  isPlainObject,
+  // isRegExp,
+  isSafeInteger,
+  isSet,
+  // isString,
+  isSymbol,
+  isTypedArray,
+  isUndefined,
+  isWeakMap,
+  isWeakSet,
+} from 'lodash-es';
 const toString = Object.prototype.toString;
 
 export function is(val: unknown, type: string) {

+ 2 - 1
src/views/mediaLibrary/list.vue

@@ -224,6 +224,7 @@
             componentProps: {
               style: { maxWidth: '250px' },
               api: getByKey,
+              immediate: false,
               labelField: 'dictName',
               valueField: 'id',
               params: {
@@ -426,7 +427,7 @@
       }
       function floadileUrl(record) {
         if (record.fileType == 3) {
-          let url = `/code/index.html?title=${record.fileName}$type=${record.fileFormat}&fileUrl=${record.fileUrl}#/sign-model`
+          let url = `/code/index.html?title=${record.fileName}&type=${record.fileFormat}&fileUrl=${record.fileUrl}#/sign-model`
           return window.open(url);
         }else{
           return window.open(record.fileUrl);

+ 1 - 1
src/views/mediaLibrary/modal/detailModal.vue

@@ -112,7 +112,7 @@
           console.log('res', res);
           closeModal();
           resetFields();
-          createMessage.success('场景迁移成功。');
+          createMessage.success('操作成功。');
           emit('update');
         } catch (error) {
           console.log('not passing', error);

+ 2 - 1
src/views/mediaLibrary/modal/grouping.vue

@@ -5,7 +5,7 @@
     title="分组管理"
     @visible-change="handleVisibleChange"
     @ok="handleSubmit"
-    @cancel="handleCancel"
+    @cancel="handleSubmit"
     :min-height="0"
   >
     <div class="pt-2px pr-3px">
@@ -149,6 +149,7 @@
         });
       }
       const handleSubmit = async () => {
+        emit('update');
         closeModal();
       };
       function handleVisibleChange(v) {

+ 4 - 1
src/views/mediaLibrary/modal/uploadModal.vue

@@ -19,7 +19,7 @@
         <div style="margin-bottom: 10px"
           >支持jpg、png、jpeg、mp4、wav、mp3 、shp格式文件上传。文件大小 ≤ 2G</div
         >
-        <span>注意:模型需使用zip包上传。包含贴图、模型、mtl文件,包内不得包含文件夹。</span>
+        <!-- <span>注意:模型需使用zip包上传。包含贴图、模型、mtl文件,包内不得包含文件夹。</span> -->
         <div style="margin-bottom: 10px">
           <div> 上传 obj:需使用zip包上传。包含贴图、模型、mtl文件,包内不得包含文件夹,文件名不得使用中文。如图:</div>
           <img style="width: 150px" :src="obj" alt="" />
@@ -100,6 +100,9 @@
               type: 1,
             },
           },
+          itemProps: {
+            autoLink: false,
+          },
           colProps: {
             xl: 18,
             xxl: 18,

+ 7 - 7
src/views/productOperation/cameraScene.vue

@@ -83,7 +83,7 @@
                   disabled: !(record.status == 1 || record.status == -2),
                   ifShow:
                     getTypeCheckPerm('scenes-move') &&
-                    tableType != 3 &&
+                    tableType != 1 &&
                     (record.userName == userInfo.userName ||
                       userInfo.roleId == 1 ||
                       userInfo.roleId == 45 ||
@@ -94,7 +94,7 @@
                   label: '下载',
                   ifShow:
                     getTypeCheckPerm('scenes-download') &&
-                    tableType != 3 &&
+                    tableType != 1 &&
                     (record.userName == userInfo.userName ||
                       userInfo.roleId == 1 ||
                       userInfo.roleId == 45 ||
@@ -108,7 +108,7 @@
                   disabled: record.status == 0 || (record.status == -2 && record.payStatus != 1),
                   ifShow:
                     getTypeCheckPerm('scenes-recalculate') &&
-                    tableType != 3 &&
+                    tableType != 1 &&
                     (userInfo.roleId == 1 || userInfo.roleId == 45 || userInfo.roleId == 48),
                   popConfirm: {
                     title: '是否重算?',
@@ -120,7 +120,7 @@
                   disabled: !(record.status == 1 || (record.status == -2 && record.payStatus == 1)),
                   ifShow:
                     getTypeCheckPerm('scenes-copy') &&
-                    tableType != 3 &&
+                    tableType != 1 &&
                     (record.userName == userInfo.userName ||
                       userInfo.roleId == 1 ||
                       userInfo.roleId == 45 ||
@@ -132,7 +132,7 @@
                   ifShow:
                     getTypeCheckPerm('scenes-creatobj') &&
                     record.status == -2 &&
-                    (tableType == 2 || tableType == 6) &&
+                    tableType != 1 &&
                     (record.userName == userInfo.userName ||
                       userInfo.roleId == 1 ||
                       userInfo.roleId == 45 ||
@@ -148,7 +148,7 @@
                     (record.userName == userInfo.userName ||
                       userInfo.roleId == 1 ||
                       userInfo.roleId == 45 ||
-                      userInfo.roleId == 48),
+                      userInfo.roleId == 48) && tableType != 1,
                   disabled: record.status == 0,
                   //onClick: handleDelete.bind(null, record),
                   popConfirm: {
@@ -439,7 +439,7 @@
             },
           },
           {
-            field: 'ryNo',
+            field: 'userName',
             label: '人员编号',
             component: 'Input',
             componentProps: {

+ 3 - 3
src/views/productOperation/modal/PowersModal.vue

@@ -2,14 +2,14 @@
   <BasicModal
     v-bind="$attrs"
     @register="register"
-    title="设置场景权限"
+    :title="caseId?'设置权限':'设置场景权限'"
     @cancel="resetFields"
     @ok="handleSubmit"
-    :width="1000"
+    :width="1100"
     :min-height="0"
   >
     <div class="pt-2px pr-3px">
-      <div style="margin-left: 17px">提示:若添加的用户已有部分场景权限,新的权限覆盖原有的权限。{{ caseId }}</div>
+      <div style="margin-left: 17px">提示:若添加的用户已有部分场景权限,新的权限覆盖原有的权限。</div>
       <BasicForm @register="registerForm" :model="model">
         <template #text="{ model, field }">
           {{ model[field] }}