ソースを参照

wip(table): perf table #136,146,134

vben 4 年 前
コミット
116a1f7745
64 ファイル変更1859 行追加930 行削除
  1. 6 0
      CHANGELOG.zh_CN.md
  2. 5 5
      package.json
  3. 2 10
      src/App.vue
  4. 3 2
      src/components/Button/index.ts
  5. 34 0
      src/components/Button/src/PopConfirmButton.vue
  6. 1 7
      src/components/Container/src/ScrollContainer.vue
  7. 5 3
      src/components/Dropdown/src/Dropdown.vue
  8. 1 0
      src/components/Dropdown/src/types.ts
  9. 25 11
      src/components/Form/src/components/ApiSelect.vue
  10. 2 1
      src/components/Form/src/hooks/useFormEvents.ts
  11. 3 0
      src/components/Preview/index.ts
  12. 69 0
      src/components/Preview/src/index.vue
  13. 18 0
      src/components/Preview/src/types.ts
  14. 7 2
      src/components/Table/index.ts
  15. 66 115
      src/components/Table/src/BasicTable.vue
  16. 0 131
      src/components/Table/src/components/TableAction.tsx
  17. 126 0
      src/components/Table/src/components/TableAction.vue
  18. 85 0
      src/components/Table/src/components/TableFooter.vue
  19. 64 0
      src/components/Table/src/components/TableHeader.vue
  20. 30 16
      src/components/Table/src/components/TableImg.vue
  21. 0 283
      src/components/Table/src/components/TableSetting.vue
  22. 17 5
      src/components/Table/src/components/TableTitle.vue
  23. 0 64
      src/components/Table/src/components/renderFooter.tsx
  24. 0 29
      src/components/Table/src/components/renderTitle.tsx
  25. 430 0
      src/components/Table/src/components/settings/ColumnSetting.vue
  26. 43 0
      src/components/Table/src/components/settings/FullScreenSetting.vue
  27. 37 0
      src/components/Table/src/components/settings/RedoSetting.vue
  28. 65 0
      src/components/Table/src/components/settings/SizeSetting.vue
  29. 61 0
      src/components/Table/src/components/settings/index.vue
  30. 67 16
      src/components/Table/src/hooks/useColumns.ts
  31. 5 0
      src/components/Table/src/hooks/useRowSelection.ts
  32. 23 0
      src/components/Table/src/hooks/useTableContext.ts
  33. 57 0
      src/components/Table/src/hooks/useTableFooter.ts
  34. 46 0
      src/components/Table/src/hooks/useTableForm.ts
  35. 46 0
      src/components/Table/src/hooks/useTableHeader.ts
  36. 44 11
      src/components/Table/src/hooks/useTableScroll.ts
  37. 2 2
      src/components/Table/src/hooks/useTableStyle.ts
  38. 2 2
      src/components/Table/src/props.ts
  39. 0 2
      src/components/Table/src/style/editable-cell.less
  40. 20 43
      src/components/Table/src/style/index.less
  41. 8 3
      src/components/Table/src/types/table.ts
  42. 2 0
      src/components/Table/src/types/tableAction.ts
  43. 4 0
      src/design/ant/index.less
  44. 3 0
      src/design/config.less
  45. 2 2
      src/design/index.less
  46. 6 0
      src/design/var/index.less
  47. 6 1
      src/hooks/component/useFormItem.ts
  48. 0 1
      src/hooks/core/useContext.ts
  49. 5 3
      src/hooks/web/useFullScreen.ts
  50. 19 0
      src/hooks/web/useSortable.ts
  51. 18 29
      src/layouts/default/tabs/useMultipleTabs.ts
  52. 3 0
      src/locales/lang/en/component/app.ts
  53. 4 0
      src/locales/lang/en/component/table.ts
  54. 2 0
      src/locales/lang/zh_CN/component/app.ts
  55. 4 0
      src/locales/lang/zh_CN/component/table.ts
  56. 0 14
      src/setup/App.ts
  57. 1 1
      src/store/modules/tab.ts
  58. 3 2
      src/store/modules/user.ts
  59. 1 4
      src/utils/index.ts
  60. 1 0
      src/views/demo/form/index.vue
  61. 4 1
      src/views/demo/table/FormTable.vue
  62. 7 1
      src/views/demo/table/tableData.tsx
  63. 5 1
      vite.config.ts
  64. 234 107
      yarn.lock

+ 6 - 0
CHANGELOG.zh_CN.md

@@ -14,6 +14,12 @@
 - useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
 - table: 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选狂
 - table: 监听行点击事件
+- table: 表格列配置按钮增加 列拖拽,列固定功能。
+- table:表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
+
+### ✨ Refactor
+
+- 重构表单,解决已知 bug
 
 ### ⚡ Performance Improvements
 

+ 5 - 5
package.json

@@ -63,11 +63,11 @@
     "@types/sortablejs": "^1.10.6",
     "@types/yargs": "^15.0.12",
     "@types/zxcvbn": "^4.4.0",
-    "@typescript-eslint/eslint-plugin": "^4.11.0",
-    "@typescript-eslint/parser": "^4.11.0",
+    "@typescript-eslint/eslint-plugin": "^4.11.1",
+    "@typescript-eslint/parser": "^4.11.1",
     "@vue/compiler-sfc": "^3.0.4",
-    "@vuedx/typecheck": "^0.2.4-0",
-    "@vuedx/typescript-plugin-vue": "^0.2.4-0",
+    "@vuedx/typecheck": "^0.4.0",
+    "@vuedx/typescript-plugin-vue": "^0.4.0",
     "autoprefixer": "^9.8.6",
     "commitizen": "^4.2.2",
     "conventional-changelog-cli": "^2.1.1",
@@ -102,7 +102,7 @@
     "vite-plugin-html": "^1.0.0-beta.2",
     "vite-plugin-mock": "^1.0.9",
     "vite-plugin-purge-icons": "^0.4.5",
-    "vite-plugin-pwa": "^0.2.0",
+    "vite-plugin-pwa": "^0.2.1",
     "vue-eslint-parser": "^7.3.0",
     "yargs": "^16.2.0"
   },

+ 2 - 10
src/App.vue

@@ -1,9 +1,5 @@
 <template>
-  <ConfigProvider
-    v-bind="lockEvent"
-    :locale="antConfigLocale"
-    :transform-cell-text="transformCellText"
-  >
+  <ConfigProvider v-bind="lockEvent" :locale="antConfigLocale">
     <AppProvider>
       <router-view />
     </AppProvider>
@@ -14,7 +10,7 @@
   import { defineComponent } from 'vue';
   import { ConfigProvider } from 'ant-design-vue';
 
-  import { getConfigProvider, initAppConfigStore } from '/@/setup/App';
+  import { initAppConfigStore } from '/@/setup/App';
 
   import { useLockPage } from '/@/hooks/web/useLockPage';
   import { useLocale } from '/@/hooks/web/useLocale';
@@ -28,9 +24,6 @@
       // Initialize vuex internal system configuration
       initAppConfigStore();
 
-      // Get ConfigProvider configuration
-      const { transformCellText } = getConfigProvider();
-
       // Create a lock screen monitor
       const lockEvent = useLockPage();
 
@@ -38,7 +31,6 @@
       const { antConfigLocale } = useLocale();
 
       return {
-        transformCellText,
         antConfigLocale,
         lockEvent,
       };

+ 3 - 2
src/components/Button/index.ts

@@ -1,5 +1,6 @@
 import Button from './src/BasicButton.vue';
+import PopConfirmButton from './src/PopConfirmButton.vue';
 import { withInstall } from '../util';
 
-withInstall(Button);
-export { Button };
+withInstall(Button, PopConfirmButton);
+export { Button, PopConfirmButton };

+ 34 - 0
src/components/Button/src/PopConfirmButton.vue

@@ -0,0 +1,34 @@
+<script lang="ts">
+  import { defineComponent, h, unref } from 'vue';
+
+  import { Popconfirm } from 'ant-design-vue';
+  import BasicButton from './BasicButton.vue';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { extendSlots } from '/@/utils/helper/tsxHelper';
+  import { omit } from 'lodash-es';
+  const { t } = useI18n();
+
+  export default defineComponent({
+    name: 'PopButton',
+    inheritAttrs: false,
+    components: { Popconfirm, BasicButton },
+    props: {
+      enable: propTypes.bool.def(true),
+      okText: propTypes.string.def(t('component.drawer.okText')),
+      cancelText: propTypes.string.def(t('component.drawer.cancelText')),
+    },
+    setup(props, { slots, attrs }) {
+      return () => {
+        const popValues = { ...props, ...unref(attrs) };
+
+        const Button = h(BasicButton, omit(unref(attrs), 'icon'), extendSlots(slots));
+        if (!props.enable) {
+          return Button;
+        }
+
+        return h(Popconfirm, omit(popValues, 'icon'), { default: () => Button });
+      };
+    },
+  });
+</script>

+ 1 - 7
src/components/Container/src/ScrollContainer.vue

@@ -1,11 +1,5 @@
 <template>
-  <Scrollbar
-    ref="scrollbarRef"
-    :wrapClass="`scrollbar__wrap`"
-    :viewClass="`scrollbar__view`"
-    class="scroll-container"
-    v-bind="$attrs"
-  >
+  <Scrollbar ref="scrollbarRef" class="scroll-container" v-bind="$attrs">
     <slot />
   </Scrollbar>
 </template>

+ 5 - 3
src/components/Dropdown/src/Dropdown.vue

@@ -6,7 +6,7 @@
     <template #overlay>
       <a-menu :selectedKeys="selectedKeys">
         <template v-for="item in getMenuList" :key="`${item.event}`">
-          <a-menu-item @click="handleClickMenu({ key: item.event })" :disabled="item.disabled">
+          <a-menu-item @click="handleClickMenu(item)" :disabled="item.disabled">
             <Icon :icon="item.icon" v-if="item.icon" />
             <span class="ml-1">{{ item.text }}</span>
           </a-menu-item>
@@ -59,9 +59,11 @@
     setup(props, { emit }) {
       const getMenuList = computed(() => props.dropMenuList);
 
-      function handleClickMenu({ key }: { key: string }) {
-        const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`);
+      function handleClickMenu(item: DropMenu) {
+        const { event } = item;
+        const menu = unref(getMenuList).find((item) => `${item.event}` === `${event}`);
         emit('menuEvent', menu);
+        item.onClick?.();
       }
 
       return { handleClickMenu, getMenuList };

+ 1 - 0
src/components/Dropdown/src/types.ts

@@ -1,4 +1,5 @@
 export interface DropMenu {
+  onClick?: Fn;
   to?: string;
   icon?: string;
   event: string | number;

+ 25 - 11
src/components/Form/src/components/ApiSelect.vue

@@ -1,5 +1,5 @@
 <template>
-  <Select v-bind="attrs" :options="options" v-model:value="state">
+  <Select v-bind="attrs" :options="getOptions" v-model:value="state">
     <template #[item]="data" v-for="item in Object.keys($slots)">
       <slot :name="item" v-bind="data" />
     </template>
@@ -15,7 +15,7 @@
   </Select>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, ref, watchEffect } from 'vue';
+  import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue';
   import { Select } from 'ant-design-vue';
   import { isFunction } from '/@/utils/is';
   import { useRuleFormItem } from '/@/hooks/component/useFormItem';
@@ -24,31 +24,31 @@
 
   import { LoadingOutlined } from '@ant-design/icons-vue';
   import { useI18n } from '/@/hooks/web/useI18n';
+  import { propTypes } from '/@/utils/propTypes';
 
   type OptionsItem = { label: string; value: string; disabled?: boolean };
 
   export default defineComponent({
-    name: 'RadioButtonGroup',
+    name: 'ApiSelect',
     components: {
       Select,
       LoadingOutlined,
     },
     props: {
-      value: {
-        type: String as PropType<string>,
-      },
+      value: propTypes.string,
       api: {
         type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
         default: null,
       },
+      // api params
       params: {
         type: Object as PropType<Recordable>,
         default: () => {},
       },
-      resultField: {
-        type: String as PropType<string>,
-        default: '',
-      },
+      // support xxx.xxx.xx
+      resultField: propTypes.string.def(''),
+      labelField: propTypes.string.def('label'),
+      valueField: propTypes.string.def('value'),
     },
     setup(props) {
       const options = ref<OptionsItem[]>([]);
@@ -59,6 +59,20 @@
       // Embedded in the form, just use the hook binding to perform form verification
       const [state] = useRuleFormItem(props);
 
+      const getOptions = computed(() => {
+        const { labelField, valueField } = props;
+
+        return unref(options).reduce((prev, next: Recordable) => {
+          if (next) {
+            prev.push({
+              label: next[labelField],
+              value: next[valueField],
+            });
+          }
+          return prev;
+        }, [] as OptionsItem[]);
+      });
+
       watchEffect(() => {
         fetch();
       });
@@ -83,7 +97,7 @@
           loading.value = false;
         }
       }
-      return { state, attrs, options, loading, t };
+      return { state, attrs, getOptions, loading, t };
     },
   });
 </script>

+ 2 - 1
src/components/Form/src/hooks/useFormEvents.ts

@@ -117,7 +117,7 @@ export function useFormEvents({
     const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
 
     const index = schemaList.findIndex((schema) => schema.field === prefixField);
-    const hasInList = schemaList.some((item) => item.field === prefixField);
+    const hasInList = schemaList.some((item) => item.field === prefixField || schema.field);
 
     if (!hasInList) return;
 
@@ -147,6 +147,7 @@ export function useFormEvents({
       error(
         'All children of the form Schema array that need to be updated must contain the `field` field'
       );
+      return;
     }
     const schema: FormSchema[] = [];
     updateData.forEach((item) => {

+ 3 - 0
src/components/Preview/index.ts

@@ -1 +1,4 @@
 export { createImgPreview } from './src/functional';
+
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+export const ImagePreview = createAsyncComponent(() => import('./src/index.vue'));

+ 69 - 0
src/components/Preview/src/index.vue

@@ -0,0 +1,69 @@
+<template>
+  <PreviewGroup :class="prefixCls">
+    <slot v-if="!imageList || $slots.default" />
+    <template v-else>
+      <template v-for="item in getImageList" :key="item.src">
+        <Image v-bind="item">
+          <template #placeholder v-if="item.placeholder">
+            <Image v-bind="item" :src="item.placeholder" :preview="false" />
+          </template>
+        </Image>
+      </template>
+    </template>
+  </PreviewGroup>
+</template>
+<script lang="ts">
+  import type { PropType } from 'vue';
+  import { defineComponent, computed } from 'vue';
+
+  import { Image } from 'ant-design-vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { propTypes } from '/@/utils/propTypes';
+  import { ImageItem } from './types';
+  import { isString } from '/@/utils/is';
+
+  export default defineComponent({
+    name: 'ImagePreview',
+    components: {
+      Image,
+      PreviewGroup: Image.PreviewGroup,
+    },
+    props: {
+      functional: propTypes.bool,
+      imageList: {
+        type: Array as PropType<ImageItem[]>,
+      },
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('image-preview');
+
+      const getImageList = computed(() => {
+        const { imageList } = props;
+        if (!imageList) {
+          return [];
+        }
+        return imageList.map((item) => {
+          if (isString(item)) {
+            return {
+              src: item,
+              placeholder: false,
+            };
+          }
+          return item;
+        });
+      });
+
+      return { prefixCls, getImageList };
+    },
+  });
+</script>
+<style lang="less">
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-image-preview';
+
+  .@{prefix-cls} {
+    .ant-image-preview-operations {
+      background: rgba(0, 0, 0, 0.4);
+    }
+  }
+</style>

+ 18 - 0
src/components/Preview/src/types.ts

@@ -10,3 +10,21 @@ export interface Props {
   imageList: string[];
   index: number;
 }
+
+export interface ImageProps {
+  alt?: string;
+  fallback?: string;
+  src: string;
+  width: string | number;
+  height?: string | number;
+  placeholder?: string | boolean;
+  preview?:
+    | boolean
+    | {
+        visible?: boolean;
+        onVisibleChange?: (visible: boolean, prevVisible: boolean) => void;
+        getContainer: string | HTMLElement | (() => HTMLElement);
+      };
+}
+
+export type ImageItem = string | ImageProps;

+ 7 - 2
src/components/Table/index.ts

@@ -1,9 +1,14 @@
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+
 export { default as BasicTable } from './src/BasicTable.vue';
-export { default as TableAction } from './src/components/TableAction';
-export { default as TableImg } from './src/components/TableImg.vue';
+export { default as TableAction } from './src/components/TableAction.vue';
+// export { default as TableImg } from './src/components/TableImg.vue';
 export { renderEditableCell, renderEditableRow } from './src/components/renderEditable';
 export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
 
+export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue'));
+// export const TableAction = createAsyncComponent(() => import('./src/components/TableAction.vue'));
+
 export * from './src/types/table';
 export * from './src/types/pagination';
 export * from './src/types/tableAction';

+ 66 - 115
src/components/Table/src/BasicTable.vue

@@ -1,14 +1,16 @@
 <template>
   <div
     ref="wrapRef"
-    class="basic-table"
-    :class="{
-      'table-form-container': getBindValues.useSearchForm,
-      inset: getBindValues.inset,
-    }"
+    :class="[
+      prefixCls,
+      {
+        [`${prefixCls}-form-container`]: getBindValues.useSearchForm,
+        [`${prefixCls}--inset`]: getBindValues.inset,
+      },
+    ]"
   >
     <BasicForm
-      :submitOnReset="true"
+      submitOnReset
       v-bind="getFormProps"
       v-if="getBindValues.useSearchForm"
       :submitButtonOptions="{ loading: getLoading }"
@@ -17,10 +19,11 @@
       @submit="handleSearchInfoChange"
       @advanced-change="redoHeight"
     >
-      <template #[item]="data" v-for="item in Object.keys($slots)">
-        <slot :name="`form-${item}`" v-bind="data" />
+      <template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
+        <slot :name="item" v-bind="data" />
       </template>
     </BasicForm>
+
     <Table
       ref="tableElRef"
       v-bind="getBindValues"
@@ -38,15 +41,12 @@
   import type { BasicTableProps, TableActionType, SizeType, SorterResult } from './types/table';
   import { PaginationProps } from './types/pagination';
 
-  import { defineComponent, ref, computed, unref, watch, nextTick } from 'vue';
+  import { defineComponent, ref, computed, unref } from 'vue';
   import { Table } from 'ant-design-vue';
-  import renderTitle from './components/renderTitle';
-  import renderFooter from './components/renderFooter';
-  import renderExpandIcon from './components/renderExpandIcon';
-  import { BasicForm, FormProps, useForm } from '/@/components/Form/index';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+
+  import { isFunction } from '/@/utils/is';
 
-  import { isFunction, isString } from '/@/utils/is';
-  import { deepMerge } from '/@/utils';
   import { omit } from 'lodash-es';
 
   import { usePagination } from './hooks/usePagination';
@@ -55,15 +55,18 @@
   import { useLoading } from './hooks/useLoading';
   import { useRowSelection } from './hooks/useRowSelection';
   import { useTableScroll } from './hooks/useTableScroll';
-  import { provideTable } from './hooks/useProvinceTable';
   import { useCustomRow } from './hooks/useCustomRow';
   import { useTableStyle } from './hooks/useTableStyle';
+  import { useTableHeader } from './hooks/useTableHeader';
+  import { createTableContext } from './hooks/useTableContext';
+  import { useTableFooter } from './hooks/useTableFooter';
+  import { useTableForm } from './hooks/useTableForm';
 
-  import { useEventListener } from '/@/hooks/event/useEventListener';
   import { basicProps } from './props';
   import { useExpose } from '/@/hooks/core/useExpose';
 
   import './style/index.less';
+  import { useDesign } from '/@/hooks/web/useDesign';
   export default defineComponent({
     props: basicProps,
     components: { Table, BasicForm },
@@ -84,7 +87,8 @@
       const wrapRef = ref<Nullable<HTMLDivElement>>(null);
       const innerPropsRef = ref<Partial<BasicTableProps>>();
 
-      const [registerForm, { getFieldsValue }] = useForm();
+      const { prefixCls } = useDesign('basic-table');
+      const [registerForm, formActions] = useForm();
 
       const getProps = computed(() => {
         return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
@@ -92,7 +96,14 @@
 
       const { getLoading, setLoading } = useLoading(getProps);
       const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
-      const { getColumnsRef, getColumns, setColumns } = useColumns(getProps, getPaginationInfo);
+      const {
+        getSortFixedColumns,
+        getColumns,
+        setColumns,
+        getColumnsRef,
+        getCacheColumns,
+      } = useColumns(getProps, getPaginationInfo);
+
       const {
         getDataSourceRef,
         getDataSource,
@@ -107,14 +118,13 @@
           getPaginationInfo,
           setLoading,
           setPagination,
-          getFieldsValue,
+          getFieldsValue: formActions.getFieldsValue,
         },
         emit
       );
 
-      const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef);
-
       const {
+        getRowSelection,
         getRowSelectionRef,
         getSelectRows,
         clearSelectedRowKeys,
@@ -123,6 +133,13 @@
         setSelectedRowKeys,
       } = useRowSelection(getProps, emit);
 
+      const { getScrollRef, redoHeight } = useTableScroll(
+        getProps,
+        tableElRef,
+        getColumnsRef,
+        getRowSelectionRef
+      );
+
       const { customRow } = useCustomRow(getProps, {
         setSelectedRowKeys,
         getSelectRowKeys,
@@ -131,74 +148,47 @@
         emit,
       });
 
-      const { getRowClassName } = useTableStyle(getProps);
+      const { getRowClassName } = useTableStyle(getProps, prefixCls);
 
-      const getTitleProps = computed(
-        (): Recordable => {
-          const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(getProps);
-          const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
-          if (hideTitle && !isString(title)) {
-            return {};
-          }
-          return {
-            title: hideTitle
-              ? null
-              : renderTitle.bind(
-                  null,
-                  title,
-                  titleHelpMessage,
-                  slots,
-                  showTableSetting,
-                  tableSetting
-                ),
-          };
-        }
+      const { getHeaderProps } = useTableHeader(getProps, slots);
+
+      const { getFooterProps } = useTableFooter(
+        getProps,
+        getScrollRef,
+        tableElRef,
+        getDataSourceRef
       );
 
-      const getBindValues = computed(() => {
-        const { showSummary } = unref(getProps);
+      const {
+        getFormProps,
+        replaceFormSlotKey,
+        getFormSlotKeys,
+        handleSearchInfoChange,
+      } = useTableForm(getProps, slots, fetch);
 
+      const getBindValues = computed(() => {
         let propsData: Recordable = {
           size: 'middle',
-          ...(slots.expandedRowRender ? { expandIcon: renderExpandIcon() } : {}),
           ...attrs,
           customRow,
           ...unref(getProps),
-          ...unref(getTitleProps),
+          ...unref(getHeaderProps),
           scroll: unref(getScrollRef),
           loading: unref(getLoading),
           tableLayout: 'fixed',
           rowSelection: unref(getRowSelectionRef),
           rowKey: unref(getRowKey),
-          columns: unref(getColumnsRef),
+          columns: unref(getSortFixedColumns),
           pagination: unref(getPaginationInfo),
           dataSource: unref(getDataSourceRef),
+          footer: unref(getFooterProps),
         };
         if (slots.expandedRowRender) {
           propsData = omit(propsData, 'scroll');
         }
-        if (showSummary) {
-          propsData.footer = renderFooter.bind(null, {
-            scroll: scroll as any,
-            columnsRef: getColumnsRef,
-            summaryFunc: unref(getProps).summaryFunc,
-            dataSourceRef: getDataSourceRef,
-            rowSelectionRef: getRowSelectionRef,
-          });
-        }
         return propsData;
       });
 
-      const getFormProps = computed(() => {
-        const { formConfig } = unref(getProps);
-        const formProps: Partial<FormProps> = {
-          showAdvancedButton: true,
-          ...formConfig,
-          compact: true,
-        };
-        return formProps;
-      });
-
       const getEmptyDataIsShowTable = computed(() => {
         const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
         if (emptyDataIsShowTable || !useSearchForm) {
@@ -207,22 +197,6 @@
         return !!unref(getDataSourceRef).length;
       });
 
-      watch(
-        () => unref(getDataSourceRef),
-        () => {
-          handleSummary();
-        },
-        { immediate: true }
-      );
-
-      function handleSearchInfoChange(info: any) {
-        const { handleSearchInfoFn } = unref(getProps);
-        if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
-          info = handleSearchInfoFn(info) || info;
-        }
-        fetch({ searchInfo: info, page: 1 });
-      }
-
       function handleTableChange(
         pagination: PaginationProps,
         // @ts-ignore
@@ -243,32 +217,8 @@
         fetch();
       }
 
-      function handleSummary() {
-        if (unref(getProps).showSummary) {
-          nextTick(() => {
-            const tableEl = unref(tableElRef);
-            if (!tableEl) return;
-            const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
-            const bodyDom = bodyDomList[0];
-            useEventListener({
-              el: bodyDom,
-              name: 'scroll',
-              listener: () => {
-                const footerBodyDom = tableEl.$el.querySelector(
-                  '.ant-table-footer .ant-table-body'
-                ) as HTMLDivElement;
-                if (!footerBodyDom || !bodyDom) return;
-                footerBodyDom.scrollLeft = bodyDom.scrollLeft;
-              },
-              wait: 0,
-              options: true,
-            });
-          });
-        }
-      }
-
       function setProps(props: Partial<BasicTableProps>) {
-        innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
+        innerPropsRef.value = { ...unref(innerPropsRef), ...props };
       }
 
       const tableAction: TableActionType = {
@@ -285,21 +235,19 @@
         setLoading,
         getDataSource,
         setProps,
+        getRowSelection,
         getPaginationRef: getPagination,
         getColumns,
+        getCacheColumns,
         getSize: () => {
           return unref(getBindValues).size as SizeType;
         },
       };
-
-      provideTable({
-        ...tableAction,
-        wrapRef,
-      });
+      createTableContext({ ...tableAction, wrapRef, getBindValues });
 
       useExpose<TableActionType>(tableAction);
 
-      emit('register', tableAction);
+      emit('register', tableAction, formActions);
 
       return {
         tableElRef,
@@ -307,13 +255,16 @@
         getLoading,
         registerForm,
         handleSearchInfoChange,
-        getFormProps,
         getEmptyDataIsShowTable,
         handleTableChange,
         getRowClassName,
         wrapRef,
         tableAction,
         redoHeight,
+        getFormProps,
+        replaceFormSlotKey,
+        getFormSlotKeys,
+        prefixCls,
       };
     },
   });

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

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

+ 126 - 0
src/components/Table/src/components/TableAction.vue

@@ -0,0 +1,126 @@
+<template>
+  <div :class="[prefixCls, getAlign]">
+    <template v-for="(action, index) in getActions" :key="`${index}`">
+      <PopConfirmButton v-bind="action">
+        <Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
+        {{ action.label }}
+      </PopConfirmButton>
+      <Divider type="vertical" v-if="divider && index < getActions.length" />
+    </template>
+
+    <Dropdown :trigger="['hover']" :dropMenuList="getDropList">
+      <slot name="more" />
+      <a-button type="link" size="small" v-if="!$slots.more">
+        <MoreOutlined class="icon-more" />
+      </a-button>
+    </Dropdown>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, computed } from 'vue';
+  import Icon from '/@/components/Icon/index';
+  import { ActionItem } from '/@/components/Table';
+  import { PopConfirmButton } from '/@/components/Button';
+  import { Divider } from 'ant-design-vue';
+  import { Dropdown } from '/@/components/Dropdown';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { MoreOutlined } from '@ant-design/icons-vue';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useTableContext } from '../hooks/useTableContext';
+  import { ACTION_COLUMN_FLAG } from '../const';
+  export default defineComponent({
+    name: 'TableAction',
+    components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined },
+    props: {
+      actions: {
+        type: Array as PropType<ActionItem[]>,
+        default: null,
+      },
+      dropDownActions: {
+        type: Array as PropType<ActionItem[]>,
+        default: null,
+      },
+      divider: propTypes.bool.def(true),
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('basic-table-action');
+      const table = useTableContext();
+      const getActions = computed(() => {
+        return props.actions.map((action) => {
+          const { popConfirm } = action;
+          return {
+            ...action,
+            ...(popConfirm || {}),
+            onConfirm: popConfirm?.confirm,
+            onCancel: popConfirm?.cancel,
+            enable: !!popConfirm,
+            type: 'link',
+            size: 'small',
+          };
+        });
+      });
+
+      const getDropList = computed(() => {
+        return props.dropDownActions.map((action, index) => {
+          const { label } = action;
+          return {
+            ...action,
+            text: label,
+            divider: index < props.dropDownActions.length - 1 ? props.divider : false,
+          };
+        });
+      });
+
+      const getAlign = computed(() => {
+        const columns = table.getColumns();
+        const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
+        return actionColumn?.align ?? 'left';
+      });
+
+      return { prefixCls, getActions, getDropList, getAlign };
+    },
+  });
+</script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-basic-table-action';
+
+  .@{prefix-cls} {
+    display: flex;
+    align-items: center;
+
+    &.left {
+      justify-content: flex-start;
+    }
+
+    &.center {
+      justify-content: center;
+    }
+
+    &.right {
+      justify-content: flex-end;
+    }
+
+    button {
+      display: flex;
+      align-items: center;
+
+      span {
+        margin-left: 0 !important;
+      }
+    }
+
+    .ant-divider,
+    .ant-divider-vertical {
+      margin: 0 2px;
+    }
+
+    .icon-more {
+      transform: rotate(90deg);
+
+      svg {
+        font-size: 1.1em;
+        font-weight: 700;
+      }
+    }
+  }
+</style>

+ 85 - 0
src/components/Table/src/components/TableFooter.vue

@@ -0,0 +1,85 @@
+<template>
+  <Table
+    v-if="summaryFunc"
+    :showHeader="false"
+    :bordered="false"
+    :pagination="false"
+    :dataSource="getDataSource"
+    :rowKey="(r) => r[rowKey]"
+    :columns="getColumns"
+    tableLayout="fixed"
+    :scroll="scroll"
+  />
+</template>
+<script lang="ts">
+  import type { PropType } from 'vue';
+
+  import { defineComponent, 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 { INDEX_COLUMN_FLAG } from '../const';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useTableContext } from '../hooks/useTableContext';
+
+  const SUMMARY_ROW_KEY = '_row';
+  const SUMMARY_INDEX_KEY = '_index';
+  export default defineComponent({
+    name: 'BasicTableFooter',
+    components: { Table },
+    props: {
+      summaryFunc: {
+        type: Function as PropType<Fn>,
+      },
+      scroll: {
+        type: Object as PropType<Recordable>,
+      },
+      rowKey: propTypes.string.def('key'),
+    },
+    setup(props) {
+      const table = useTableContext();
+
+      const getDataSource = computed((): Recordable[] => {
+        const { summaryFunc } = props;
+        if (!isFunction(summaryFunc)) {
+          return [];
+        }
+        let dataSource = toRaw(unref(table.getDataSource()));
+        dataSource = summaryFunc(dataSource);
+        dataSource.forEach((item, i) => {
+          item[props.rowKey] = `${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));
+
+        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) {
+          columns.unshift({
+            width: 60,
+            title: 'selection',
+            key: 'selectionKey',
+            align: 'center',
+            customRender: ({ record }) => record[SUMMARY_ROW_KEY],
+          });
+        }
+        return columns;
+      });
+      return { getColumns, getDataSource };
+    },
+  });
+</script>

+ 64 - 0
src/components/Table/src/components/TableHeader.vue

@@ -0,0 +1,64 @@
+<template>
+  <slot name="tableTitle" v-if="$slots.tableTitle" />
+  <TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" />
+
+  <div :class="`${prefixCls}__toolbar`">
+    <slot name="toolbar" />
+    <Divider type="vertical" v-if="$slots.toolbar" />
+    <TableSetting :setting="tableSetting" v-if="showTableSetting" />
+  </div>
+</template>
+<script lang="ts">
+  import type { TableSetting } from '../types/table';
+  import type { PropType } from 'vue';
+  import { Divider } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import TableSettingComp from './settings/index.vue';
+  import TableTitle from './TableTitle.vue';
+
+  export default defineComponent({
+    name: 'BasicTableHeader',
+    components: {
+      Divider,
+      TableTitle,
+      TableSetting: TableSettingComp,
+    },
+    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: '',
+      },
+    },
+    setup() {
+      const { prefixCls } = useDesign('basic-table-header');
+      return { prefixCls };
+    },
+  });
+</script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-basic-table-header';
+
+  .@{prefix-cls} {
+    &__toolbar {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+
+      > * {
+        margin-right: 8px;
+      }
+    }
+  }
+</style>

+ 30 - 16
src/components/Table/src/components/TableImg.vue

@@ -1,16 +1,21 @@
 <template>
-  <div class="basic-table-img__preview" v-if="imgList && imgList.length">
-    <template v-for="(img, index) in imgList" :key="img">
-      <img :width="size" @click="handlePreview(index)" :src="img" />
-    </template>
+  <div :class="prefixCls" v-if="imgList && imgList.length">
+    <PreviewGroup>
+      <template v-for="img in imgList" :key="img">
+        <Image :width="size" :src="img" />
+      </template>
+    </PreviewGroup>
   </div>
 </template>
 <script lang="ts">
   import { defineComponent, PropType } from 'vue';
-  import { createImgPreview } from '/@/components/Preview/index';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { Image } from 'ant-design-vue';
 
   export default defineComponent({
-    name: 'TableAction',
+    name: 'TableImage',
+    components: { Image, PreviewGroup: Image.PreviewGroup },
     props: {
       imgList: {
         type: Array as PropType<string[]>,
@@ -21,16 +26,25 @@
         default: 40,
       },
     },
-    setup(props) {
-      function handlePreview(index: number) {
-        const { imgList } = props;
-
-        createImgPreview({
-          imageList: imgList as string[],
-          index: index,
-        });
-      }
-      return { handlePreview };
+    setup() {
+      const { prefixCls } = useDesign('basic-table-img');
+      return { prefixCls };
     },
   });
 </script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-basic-table-img';
+
+  .@{prefix-cls} {
+    display: flex;
+
+    .ant-image {
+      margin-right: 4px;
+      cursor: zoom-in;
+
+      img {
+        border-radius: 2px;
+      }
+    }
+  }
+</style>

+ 0 - 283
src/components/Table/src/components/TableSetting.vue

@@ -1,283 +0,0 @@
-<template>
-  <div class="table-settings">
-    <Divider type="vertical" />
-
-    <Tooltip placement="top" v-if="getSetting.redo">
-      <template #title>
-        <span>{{ t('component.table.settingRedo') }}</span>
-      </template>
-      <RedoOutlined @click="redo" />
-    </Tooltip>
-
-    <Tooltip placement="top" v-if="getSetting.size">
-      <template #title>
-        <span>{{ t('component.table.settingDens') }}</span>
-      </template>
-      <Dropdown placement="bottomCenter" :trigger="['click']">
-        <ColumnHeightOutlined />
-        <template #overlay>
-          <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
-            <MenuItem key="default">
-              <span>{{ t('component.table.settingDensDefault') }}</span>
-            </MenuItem>
-            <MenuItem key="middle">
-              <span>{{ t('component.table.settingDensMiddle') }}</span>
-            </MenuItem>
-            <MenuItem key="small">
-              <span>{{ t('component.table.settingDensSmall') }}</span>
-            </MenuItem>
-          </Menu>
-        </template>
-      </Dropdown>
-    </Tooltip>
-
-    <Tooltip placement="top" v-if="getSetting.setting">
-      <template #title>
-        <span>{{ t('component.table.settingColumn') }}</span>
-      </template>
-      <Popover
-        placement="bottomLeft"
-        trigger="click"
-        overlayClassName="table-settings__cloumn-list"
-      >
-        <template #content>
-          <CheckboxGroup v-model:value="checkedList" @change="onChange">
-            <template v-for="item in plainOptions" :key="item.value">
-              <div class="table-settings__check-item">
-                <Checkbox :value="item.value">
-                  {{ item.label }}
-                </Checkbox>
-              </div>
-            </template>
-          </CheckboxGroup>
-        </template>
-        <template #title>
-          <div class="table-settings__popover-title">
-            <Checkbox
-              :indeterminate="indeterminate"
-              v-model:checked="checkAll"
-              @change="onCheckAllChange"
-            >
-              {{ t('component.table.settingColumnShow') }}
-            </Checkbox>
-            <a-button size="small" type="link" @click="reset">
-              {{ t('component.table.settingReset') }}</a-button
-            >
-          </div>
-        </template>
-        <SettingOutlined />
-      </Popover>
-    </Tooltip>
-
-    <Tooltip placement="top" v-if="getSetting.fullScreen">
-      <template #title>
-        <span>{{ t('component.table.settingFullScreen') }}</span>
-      </template>
-      <FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" />
-      <FullscreenExitOutlined @click="handleFullScreen" v-else />
-    </Tooltip>
-  </div>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, reactive, toRefs, PropType, computed, watchEffect } from 'vue';
-  import { injectTable } from '../hooks/useProvinceTable';
-  import { Tooltip, Divider, Dropdown, Menu, Popover, Checkbox } from 'ant-design-vue';
-  import {
-    RedoOutlined,
-    ColumnHeightOutlined,
-    FullscreenOutlined,
-    FullscreenExitOutlined,
-    SettingOutlined,
-  } from '@ant-design/icons-vue';
-  import { useFullscreen } from '/@/hooks/web/useFullScreen';
-  import type { SizeType, TableSetting } from '../types/table';
-  import { useI18n } from '/@/hooks/web/useI18n';
-
-  interface Options {
-    label: string;
-    value: string;
-  }
-  interface State {
-    indeterminate: boolean;
-    checkAll: boolean;
-    // defaultColumns: BasicColumn[];
-    // columns: BasicColumn[];
-    checkedList: string[];
-    defaultCheckList: string[];
-  }
-  export default defineComponent({
-    name: 'TableSetting',
-    components: {
-      RedoOutlined,
-      ColumnHeightOutlined,
-      FullscreenExitOutlined,
-      FullscreenOutlined,
-      SettingOutlined,
-      Popover,
-      Tooltip,
-      Divider,
-      Dropdown,
-      Checkbox,
-      CheckboxGroup: Checkbox.Group,
-      Menu,
-      MenuItem: Menu.Item,
-    },
-    props: {
-      setting: {
-        type: Object as PropType<TableSetting>,
-        default: {},
-      },
-    },
-    setup(props) {
-      const table = injectTable();
-      const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef);
-      const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
-
-      const plainOptions = ref<Options[]>([]);
-      const state = reactive<State>({
-        indeterminate: false,
-        checkAll: true,
-        checkedList: [],
-        defaultCheckList: [],
-      });
-
-      const { t } = useI18n();
-
-      watchEffect(() => {
-        const columns = table.getColumns();
-        if (columns.length) {
-          init();
-        }
-      });
-
-      function init() {
-        let ret: Options[] = [];
-        table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
-          ret.push({
-            label: item.title as string,
-            value: (item.dataIndex || item.title) as string,
-          });
-        });
-        if (!plainOptions.value.length) {
-          plainOptions.value = ret;
-        }
-        const checkList = table
-          .getColumns()
-          .map((item) => item.dataIndex || item.title) as string[];
-        state.checkedList = checkList;
-        state.defaultCheckList = checkList;
-      }
-
-      function handleTitleClick({ key }: { key: SizeType }) {
-        selectedKeysRef.value = [key];
-        table.setProps({
-          size: key,
-        });
-      }
-
-      function handleFullScreen() {
-        toggleFullscreen();
-      }
-
-      function onCheckAllChange(e: ChangeEvent) {
-        state.indeterminate = false;
-        const checkList = plainOptions.value.map((item) => item.value);
-        if (e.target.checked) {
-          state.checkedList = checkList;
-          table.setColumns(checkList);
-        } else {
-          state.checkedList = [];
-          table.setColumns([]);
-        }
-      }
-
-      function onChange(checkedList: string[]) {
-        const len = plainOptions.value.length;
-        state.indeterminate = !!checkedList.length && checkedList.length < len;
-        state.checkAll = checkedList.length === len;
-        table.setColumns(checkedList);
-      }
-
-      function reset() {
-        if (state.checkAll) return;
-        state.checkedList = [...state.defaultCheckList];
-        state.checkAll = true;
-        state.indeterminate = false;
-        table.setColumns(state.defaultCheckList);
-      }
-
-      const getSetting = computed(
-        (): TableSetting => {
-          return {
-            redo: true,
-            size: true,
-            setting: true,
-            fullScreen: true,
-            ...props.setting,
-          };
-        }
-      );
-
-      return {
-        redo: () => table.reload(),
-        handleTitleClick,
-        selectedKeysRef,
-        handleFullScreen,
-        isFullscreenRef,
-        onCheckAllChange,
-        onChange,
-        plainOptions,
-        reset,
-        getSetting,
-        ...toRefs(state),
-        t,
-      };
-    },
-  });
-</script>
-<style lang="less">
-  @import (reference) '../../../../design/index.less';
-
-  .table-settings {
-    & > * {
-      margin-right: 12px;
-    }
-
-    svg {
-      width: 1.2em;
-      height: 1.2em;
-    }
-
-    &__popover-title {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-    }
-
-    &__check-item {
-      width: 100%;
-      padding: 4px 16px 4px 16px;
-
-      .ant-checkbox-wrapper {
-        width: 100%;
-      }
-
-      &:hover {
-        background: fade(@primary-color, 10%);
-      }
-    }
-
-    &__cloumn-list {
-      .ant-popover-inner-content {
-        max-height: 360px;
-        padding-right: 0;
-        padding-left: 0;
-        overflow: auto;
-      }
-
-      .ant-checkbox-group {
-        width: 100%;
-      }
-    }
-  }
-</style>

+ 17 - 5
src/components/Table/src/components/TableTitle.vue

@@ -1,15 +1,16 @@
 <template>
-  <BasicTitle class="basic-table-title" v-if="tableTitle" :helpMessage="helpMessage">
-    {{ tableTitle }}
+  <BasicTitle :class="prefixCls" v-if="getTitle" :helpMessage="helpMessage">
+    {{ getTitle }}
   </BasicTitle>
 </template>
 <script lang="ts">
   import { computed, defineComponent, PropType } from 'vue';
 
   import { BasicTitle } from '/@/components/Basic/index';
+  import { useDesign } from '/@/hooks/web/useDesign';
   import { isFunction } from '/@/utils/is';
   export default defineComponent({
-    name: 'TableTitle',
+    name: 'BasicTableTitle',
     components: { BasicTitle },
     props: {
       title: {
@@ -23,7 +24,9 @@
       },
     },
     setup(props) {
-      const tableTitle = computed(() => {
+      const { prefixCls } = useDesign('basic-table-title');
+
+      const getTitle = computed(() => {
         const { title, getSelectRows = () => {} } = props;
         let tit = title;
 
@@ -35,7 +38,16 @@
         return tit;
       });
 
-      return { tableTitle };
+      return { getTitle, prefixCls };
     },
   });
 </script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-basic-table-title';
+
+  .@{prefix-cls} {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+</style>

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

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

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

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

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

@@ -0,0 +1,430 @@
+<template>
+  <Tooltip placement="top">
+    <template #title>
+      <span>{{ t('component.table.settingColumn') }}</span>
+    </template>
+    <Popover
+      :getPopupContainer="getPopupContainer"
+      placement="bottomLeft"
+      trigger="click"
+      @visibleChange="handleVisibleChange"
+      :overlayClassName="`${prefixCls}__cloumn-list`"
+    >
+      <template #title>
+        <div :class="`${prefixCls}__popover-title`">
+          <Checkbox
+            :indeterminate="indeterminate"
+            v-model:checked="checkAll"
+            @change="onCheckAllChange"
+          >
+            {{ t('component.table.settingColumnShow') }}
+          </Checkbox>
+
+          <Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
+            {{ t('component.table.settingIndexColumnShow') }}
+          </Checkbox>
+
+          <Checkbox
+            v-model:checked="checkSelect"
+            @change="handleSelectCheckChange"
+            :disabled="!defaultRowSelection"
+          >
+            {{ t('component.table.settingSelectColumnShow') }}
+          </Checkbox>
+
+          <a-button size="small" type="link" @click="reset">
+            {{ t('component.table.settingReset') }}
+          </a-button>
+        </div>
+      </template>
+
+      <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`">
+                <DragOutlined class="table-coulmn-drag-icon" />
+                <Checkbox :value="item.value"> {{ item.label }} </Checkbox>
+
+                <Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4">
+                  <template #title> {{ t('component.table.settingFixedLeft') }}</template>
+                  <Icon
+                    icon="line-md:arrow-align-left"
+                    :class="[
+                      `${prefixCls}__fixed-left`,
+                      {
+                        active: item.fixed === 'left',
+                        disabled: !checkedList.includes(item.value),
+                      },
+                    ]"
+                    @click="handleColumnFixed(item, 'left')"
+                  />
+                </Tooltip>
+                <Divider type="vertical" />
+                <Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4">
+                  <template #title> {{ t('component.table.settingFixedRight') }}</template>
+                  <Icon
+                    icon="line-md:arrow-align-left"
+                    :class="[
+                      `${prefixCls}__fixed-right`,
+                      {
+                        active: item.fixed === 'right',
+                        disabled: !checkedList.includes(item.value),
+                      },
+                    ]"
+                    @click="handleColumnFixed(item, 'right')"
+                  />
+                </Tooltip>
+              </div>
+            </template>
+          </CheckboxGroup>
+        </ScrollContainer>
+      </template>
+      <SettingOutlined />
+    </Popover>
+  </Tooltip>
+</template>
+<script lang="ts">
+  import {
+    defineComponent,
+    ref,
+    reactive,
+    toRefs,
+    watchEffect,
+    nextTick,
+    unref,
+    computed,
+  } from 'vue';
+  import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
+  import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
+  import { Icon } from '/@/components/Icon';
+  import { ScrollContainer } from '/@/components/Container';
+
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useTableContext } from '../../hooks/useTableContext';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useSortable } from '/@/hooks/web/useSortable';
+
+  import { isNullAndUnDef } from '/@/utils/is';
+  import { getPopupContainer } from '/@/utils';
+
+  import type { BasicColumn } from '../../types/table';
+
+  interface State {
+    indeterminate: boolean;
+    checkAll: boolean;
+    checkedList: string[];
+    defaultCheckList: string[];
+  }
+
+  interface Options {
+    label: string;
+    value: string;
+    fixed?: boolean | 'left' | 'right';
+  }
+
+  export default defineComponent({
+    name: 'ColumnSetting',
+    components: {
+      SettingOutlined,
+      Popover,
+      Tooltip,
+      Checkbox,
+      CheckboxGroup: Checkbox.Group,
+      DragOutlined,
+      ScrollContainer,
+      Divider,
+      Icon,
+    },
+
+    setup() {
+      const { t } = useI18n();
+      const table = useTableContext();
+
+      const defaultRowSelection = table.getRowSelection();
+      let inited = false;
+
+      const cachePlainOptions = ref<Options[]>([]);
+      const plainOptions = ref<Options[]>([]);
+
+      const plainSortOptions = ref<Options[]>([]);
+
+      const columnListRef = ref<ComponentRef>(null);
+
+      const state = reactive<State>({
+        indeterminate: false,
+        checkAll: true,
+        checkedList: [],
+        defaultCheckList: [],
+      });
+
+      const checkIndex = ref(false);
+      const checkSelect = ref(false);
+
+      const { prefixCls } = useDesign('basic-column-setting');
+
+      const getValues = computed(() => {
+        return unref(table?.getBindValues) || {};
+      });
+
+      watchEffect(() => {
+        const columns = table.getColumns();
+        if (columns.length) {
+          init();
+        }
+      });
+
+      watchEffect(() => {
+        const values = unref(getValues);
+        checkIndex.value = !!values.showIndexColumn;
+        checkSelect.value = !!values.rowSelection;
+      });
+
+      function getColumns() {
+        const ret: Options[] = [];
+        table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
+          ret.push({
+            label: item.title as string,
+            value: (item.dataIndex || item.title) as string,
+            ...item,
+          });
+        });
+        return ret;
+      }
+
+      function init() {
+        const columns = getColumns();
+
+        const checkList = table
+          .getColumns()
+          .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 = fixedColumns.find((fCol) => fCol.dataIndex === item.dataIndex);
+            if (findItem) {
+              item.fixed = findItem.fixed;
+            }
+          });
+        }
+
+        state.checkedList = checkList;
+      }
+
+      // checkAll change
+      function onCheckAllChange(e: ChangeEvent) {
+        state.indeterminate = false;
+        const checkList = plainOptions.value.map((item) => item.value);
+        if (e.target.checked) {
+          state.checkedList = checkList;
+          table.setColumns(checkList);
+        } else {
+          state.checkedList = [];
+          table.setColumns([]);
+        }
+      }
+
+      // Trigger when check/uncheck a column
+      function onChange(checkedList: string[]) {
+        const len = plainOptions.value.length;
+        state.indeterminate = !!checkedList.length && checkedList.length < len;
+        state.checkAll = checkedList.length === len;
+
+        const sortList = unref(plainSortOptions).map((item) => item.value);
+        checkedList.sort((prev, next) => {
+          return sortList.indexOf(prev) - sortList.indexOf(next);
+        });
+        table.setColumns(checkedList);
+      }
+
+      // reset columns
+      function reset() {
+        state.checkedList = [...state.defaultCheckList];
+        state.checkAll = true;
+        state.indeterminate = false;
+        plainOptions.value = unref(cachePlainOptions);
+        plainSortOptions.value = unref(cachePlainOptions);
+        table.setColumns(table.getCacheColumns());
+      }
+
+      // 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.$el;
+          if (!el) return;
+          // Drag and drop sort
+          const { initSortable } = useSortable(el, {
+            handle: '.table-coulmn-drag-icon ',
+            onEnd: (evt) => {
+              const { oldIndex, newIndex } = evt;
+              if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
+                return;
+              }
+              // Sort column
+              const columns = getColumns();
+
+              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;
+              plainOptions.value = columns;
+              table.setColumns(columns);
+            },
+          });
+          initSortable();
+          inited = true;
+        });
+      }
+
+      // Control whether the serial number column is displayed
+      function handleIndexCheckChange(e: ChangeEvent) {
+        table.setProps({
+          showIndexColumn: e.target.checked,
+        });
+      }
+
+      // Control whether the check box is displayed
+      function handleSelectCheckChange(e: ChangeEvent) {
+        table.setProps({
+          rowSelection: e.target.checked ? defaultRowSelection : undefined,
+        });
+      }
+
+      function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
+        if (!state.checkedList.includes(item.dataIndex as string)) return;
+
+        const columns = getColumns() 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;
+
+        if (isFixed && !item.width) {
+          item.width = 100;
+        }
+
+        table.setColumns(columns);
+      }
+
+      return {
+        t,
+        ...toRefs(state),
+        onCheckAllChange,
+        onChange,
+        plainOptions,
+        reset,
+        prefixCls,
+        columnListRef,
+        handleVisibleChange,
+        checkIndex,
+        checkSelect,
+        handleIndexCheckChange,
+        handleSelectCheckChange,
+        defaultRowSelection,
+        handleColumnFixed,
+        getPopupContainer,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-basic-column-setting';
+
+  .table-coulmn-drag-icon {
+    margin: 0 5px;
+    cursor: move;
+  }
+
+  .@{prefix-cls} {
+    &__popover-title {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+    }
+
+    &__check-item {
+      display: flex;
+      align-items: center;
+      min-width: 100%;
+      padding: 4px 16px 8px 0;
+
+      .ant-checkbox-wrapper {
+        width: 100%;
+
+        &:hover {
+          color: @primary-color;
+        }
+      }
+    }
+
+    &__fixed-left,
+    &__fixed-right {
+      color: rgba(0, 0, 0, 0.45);
+      cursor: pointer;
+
+      &.active,
+      &:hover {
+        color: @primary-color;
+      }
+
+      &.disabled {
+        color: @disabled-color;
+        cursor: not-allowed;
+      }
+    }
+
+    &__fixed-right {
+      transform: rotate(180deg);
+    }
+
+    &__cloumn-list {
+      svg {
+        width: 1em !important;
+        height: 1em !important;
+      }
+
+      .ant-popover-inner-content {
+        // max-height: 360px;
+        padding-right: 0;
+        padding-left: 0;
+        // overflow: auto;
+      }
+
+      .ant-checkbox-group {
+        width: 100%;
+        min-width: 260px;
+        // flex-wrap: wrap;
+      }
+
+      .scroll-container {
+        height: 220px;
+      }
+    }
+  }
+</style>

+ 43 - 0
src/components/Table/src/components/settings/FullScreenSetting.vue

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

+ 37 - 0
src/components/Table/src/components/settings/RedoSetting.vue

@@ -0,0 +1,37 @@
+<template>
+  <Tooltip placement="top">
+    <template #title>
+      <span>{{ t('component.table.settingRedo') }}</span>
+    </template>
+    <RedoOutlined @click="redo" />
+  </Tooltip>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { useTableContext } from '../../hooks/useTableContext';
+  import { Tooltip } from 'ant-design-vue';
+  import { RedoOutlined } from '@ant-design/icons-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  export default defineComponent({
+    name: 'RedoSetting',
+    components: {
+      RedoOutlined,
+      Tooltip,
+    },
+
+    setup() {
+      const table = useTableContext();
+      const { t } = useI18n();
+
+      function redo() {
+        table.reload();
+      }
+
+      return {
+        redo,
+        t,
+      };
+    },
+  });
+</script>

+ 65 - 0
src/components/Table/src/components/settings/SizeSetting.vue

@@ -0,0 +1,65 @@
+<template>
+  <Tooltip placement="top">
+    <template #title>
+      <span>{{ t('component.table.settingDens') }}</span>
+    </template>
+
+    <Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer">
+      <ColumnHeightOutlined />
+      <template #overlay>
+        <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
+          <MenuItem key="default">
+            <span>{{ t('component.table.settingDensDefault') }}</span>
+          </MenuItem>
+          <MenuItem key="middle">
+            <span>{{ t('component.table.settingDensMiddle') }}</span>
+          </MenuItem>
+          <MenuItem key="small">
+            <span>{{ t('component.table.settingDensSmall') }}</span>
+          </MenuItem>
+        </Menu>
+      </template>
+    </Dropdown>
+  </Tooltip>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { useTableContext } from '../../hooks/useTableContext';
+  import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
+  import { ColumnHeightOutlined } from '@ant-design/icons-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { getPopupContainer } from '/@/utils';
+
+  import type { SizeType } from '../../types/table';
+
+  export default defineComponent({
+    name: 'SizeSetting',
+    components: {
+      ColumnHeightOutlined,
+      Tooltip,
+      Dropdown,
+      Menu,
+      MenuItem: Menu.Item,
+    },
+    setup() {
+      const table = useTableContext();
+      const { t } = useI18n();
+
+      const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
+
+      function handleTitleClick({ key }: { key: SizeType }) {
+        selectedKeysRef.value = [key];
+        table.setProps({
+          size: key,
+        });
+      }
+
+      return {
+        handleTitleClick,
+        selectedKeysRef,
+        getPopupContainer,
+        t,
+      };
+    },
+  });
+</script>

+ 61 - 0
src/components/Table/src/components/settings/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <div class="table-settings">
+    <RedoSetting v-if="getSetting.size" />
+    <SizeSetting v-if="getSetting.redo" />
+
+    <ColumnSetting v-if="getSetting.setting" />
+
+    <FullScreenSetting v-if="getSetting.fullScreen" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, computed } from 'vue';
+  import type { TableSetting } from '../../types/table';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+  import ColumnSetting from './ColumnSetting.vue';
+  export default defineComponent({
+    name: 'TableSetting',
+    components: {
+      ColumnSetting,
+      SizeSetting: createAsyncComponent(() => import('./SizeSetting.vue')),
+      RedoSetting: createAsyncComponent(() => import('./RedoSetting.vue')),
+      FullScreenSetting: createAsyncComponent(() => import('./FullScreenSetting.vue')),
+    },
+    props: {
+      setting: {
+        type: Object as PropType<TableSetting>,
+        default: {},
+      },
+    },
+    setup(props) {
+      const { t } = useI18n();
+
+      const getSetting = computed(
+        (): TableSetting => {
+          return {
+            redo: true,
+            size: true,
+            setting: true,
+            fullScreen: true,
+            ...props.setting,
+          };
+        }
+      );
+
+      return { getSetting, t };
+    },
+  });
+</script>
+<style lang="less">
+  .table-settings {
+    & > * {
+      margin-right: 12px;
+    }
+
+    svg {
+      width: 1.3em;
+      height: 1.3em;
+    }
+  }
+</style>

+ 67 - 16
src/components/Table/src/hooks/useColumns.ts

@@ -1,9 +1,10 @@
 import { BasicColumn, BasicTableProps, GetColumnsParams } from '../types/table';
 import { PaginationProps } from '../types/pagination';
 import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue';
-import { isBoolean, isArray, isObject } from '/@/utils/is';
+import { isBoolean, isArray, isString } from '/@/utils/is';
 import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
 import { useI18n } from '/@/hooks/web/useI18n';
+import { isEqual, cloneDeep } from 'lodash-es';
 
 const { t } = useI18n();
 
@@ -107,27 +108,31 @@ export function useColumns(
 
   const getColumnsRef = computed(() => {
     const columns = unref(columnsRef);
-    if (!columns) {
-      return [];
-    }
 
     handleIndexColumn(propsRef, getPaginationRef, columns);
     handleActionColumn(propsRef, columns);
-
+    if (!columns) {
+      return [];
+    }
     return columns;
   });
 
+  const getSortFixedColumns = computed(() => {
+    return useFixedColumn(unref(getColumnsRef));
+  });
+
   watchEffect(() => {
     const columns = toRaw(unref(propsRef).columns);
     columnsRef.value = columns;
-    cacheColumns = columns;
+    cacheColumns = columns?.filter((item) => !item.flag) ?? [];
   });
 
   /**
    * set columns
-   * @param columns key|column
+   * @param columnList key|column
    */
-  function setColumns(columns: Partial<BasicColumn>[] | string[]) {
+  function setColumns(columnList: Partial<BasicColumn>[] | string[]) {
+    const columns = cloneDeep(columnList);
     if (!isArray(columns)) return;
 
     if (columns.length <= 0) {
@@ -137,20 +142,36 @@ export function useColumns(
 
     const firstColumn = columns[0];
 
-    if (isObject(firstColumn)) {
+    const cacheKeys = cacheColumns.map((item) => item.dataIndex);
+
+    if (!isString(firstColumn)) {
       columnsRef.value = columns as BasicColumn[];
     } else {
-      const newColumns = cacheColumns.filter(
-        (item) =>
-          (item.dataIndex || `${item.key}`) &&
-          (columns as string[]).includes(`${item.key}`! || item.dataIndex!)
-      );
+      const columnKeys = columns as string[];
+      const newColumns: BasicColumn[] = [];
+      cacheColumns.forEach((item) => {
+        if (columnKeys.includes(`${item.key}`! || item.dataIndex!)) {
+          newColumns.push({
+            ...item,
+            defaultHidden: false,
+          });
+        }
+      });
+      // Sort according to another array
+      if (!isEqual(cacheKeys, columns)) {
+        newColumns.sort((prev, next) => {
+          return (
+            columnKeys.indexOf(prev.dataIndex as string) -
+            columnKeys.indexOf(next.dataIndex as string)
+          );
+        });
+      }
       columnsRef.value = newColumns;
     }
   }
 
   function getColumns(opt?: GetColumnsParams) {
-    const { ignoreIndex, ignoreAction } = opt || {};
+    const { ignoreIndex, ignoreAction, sort } = opt || {};
     let columns = toRaw(unref(getColumnsRef));
     if (ignoreIndex) {
       columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
@@ -158,8 +179,38 @@ export function useColumns(
     if (ignoreAction) {
       columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG);
     }
+
+    if (sort) {
+      columns = useFixedColumn(columns);
+    }
+
     return columns;
   }
+  function getCacheColumns() {
+    return cacheColumns;
+  }
+
+  return { getColumnsRef, getCacheColumns, getColumns, setColumns, getSortFixedColumns };
+}
+
+export function useFixedColumn(columns: BasicColumn[]) {
+  const fixedLeftColumns: BasicColumn[] = [];
+  const fixedRightColumns: BasicColumn[] = [];
+  const defColumns: BasicColumn[] = [];
+  for (const column of columns) {
+    if (column.fixed === 'left') {
+      fixedLeftColumns.push(column);
+      continue;
+    }
+    if (column.fixed === 'right') {
+      fixedRightColumns.push(column);
+      continue;
+    }
+    defColumns.push(column);
+  }
+  const resultColumns = [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(
+    (item) => !item.defaultHidden
+  );
 
-  return { getColumnsRef, getColumns, setColumns };
+  return resultColumns;
 }

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

@@ -53,7 +53,12 @@ export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, emit: Em
     return unref(selectedRowRef) as T[];
   }
 
+  function getRowSelection() {
+    return unref(getRowSelectionRef)!;
+  }
+
   return {
+    getRowSelection,
     getRowSelectionRef,
     getSelectRows,
     getSelectRowKeys,

+ 23 - 0
src/components/Table/src/hooks/useTableContext.ts

@@ -0,0 +1,23 @@
+import type { Ref } from 'vue';
+import type { BasicTableProps, TableActionType } from '../types/table';
+
+import { provide, inject, ComputedRef } from 'vue';
+
+const key = Symbol('basic-table');
+
+type Instance = TableActionType & {
+  wrapRef: Ref<Nullable<HTMLElement>>;
+  getBindValues: ComputedRef<Recordable>;
+};
+
+type RetInstance = Omit<Instance, 'getBindValues'> & {
+  getBindValues: ComputedRef<BasicTableProps>;
+};
+
+export function createTableContext(instance: Instance) {
+  provide(key, instance);
+}
+
+export function useTableContext(): RetInstance {
+  return inject(key) as RetInstance;
+}

+ 57 - 0
src/components/Table/src/hooks/useTableFooter.ts

@@ -0,0 +1,57 @@
+import type { ComputedRef, Ref } from 'vue';
+import type { BasicTableProps } from '../types/table';
+import { unref, computed, h, nextTick, watchEffect } from 'vue';
+import TableFooter from '../components/TableFooter.vue';
+import { useEventListener } from '/@/hooks/event/useEventListener';
+
+export function useTableFooter(
+  propsRef: ComputedRef<BasicTableProps>,
+  scrollRef: ComputedRef<{
+    x: string | number | true;
+    y: Nullable<number>;
+    scrollToFirstRowOnChange: boolean;
+  }>,
+  tableElRef: Ref<ComponentRef>,
+  getDataSourceRef: ComputedRef<Recordable>
+) {
+  const getIsEmptyData = computed(() => {
+    return (unref(getDataSourceRef) || []).length === 0;
+  });
+
+  const getFooterProps = computed((): Recordable | undefined => {
+    const { summaryFunc, showSummary } = unref(propsRef);
+    return showSummary && !unref(getIsEmptyData)
+      ? () => h(TableFooter, { summaryFunc, scroll: unref(scrollRef) })
+      : undefined;
+  });
+
+  watchEffect(() => {
+    handleSummary();
+  });
+
+  function handleSummary() {
+    const { showSummary } = unref(propsRef);
+    if (!showSummary || unref(getIsEmptyData)) return;
+
+    nextTick(() => {
+      const tableEl = unref(tableElRef);
+      if (!tableEl) return;
+      const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
+      const bodyDom = bodyDomList[0];
+      useEventListener({
+        el: bodyDom,
+        name: 'scroll',
+        listener: () => {
+          const footerBodyDom = tableEl.$el.querySelector(
+            '.ant-table-footer .ant-table-body'
+          ) as HTMLDivElement;
+          if (!footerBodyDom || !bodyDom) return;
+          footerBodyDom.scrollLeft = bodyDom.scrollLeft;
+        },
+        wait: 0,
+        options: true,
+      });
+    });
+  }
+  return { getFooterProps };
+}

+ 46 - 0
src/components/Table/src/hooks/useTableForm.ts

@@ -0,0 +1,46 @@
+import type { ComputedRef, Slots } from 'vue';
+import type { BasicTableProps, FetchParams } from '../types/table';
+import { unref, computed } from 'vue';
+import type { FormProps } from '/@/components/Form';
+import { isFunction } from '/@/utils/is';
+export function useTableForm(
+  propsRef: ComputedRef<BasicTableProps>,
+  slots: Slots,
+  fetch: (opt?: FetchParams | undefined) => Promise<void>
+) {
+  const getFormProps = computed(
+    (): Partial<FormProps> => {
+      const { formConfig } = unref(propsRef);
+      return {
+        showAdvancedButton: true,
+        ...formConfig,
+        compact: true,
+      };
+    }
+  );
+
+  const getFormSlotKeys = computed(() => {
+    const keys = Object.keys(slots);
+    return keys.map((item) => (item.startsWith('form-') ? item : null)).filter(Boolean);
+  });
+
+  function replaceFormSlotKey(key: string) {
+    if (!key) return '';
+    return key?.replace?.(/form\-/, '') ?? '';
+  }
+
+  function handleSearchInfoChange(info: Recordable) {
+    const { handleSearchInfoFn } = unref(propsRef);
+    if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
+      info = handleSearchInfoFn(info) || info;
+    }
+    fetch({ searchInfo: info, page: 1 });
+  }
+
+  return {
+    getFormProps,
+    replaceFormSlotKey,
+    getFormSlotKeys,
+    handleSearchInfoChange,
+  };
+}

+ 46 - 0
src/components/Table/src/hooks/useTableHeader.ts

@@ -0,0 +1,46 @@
+import type { ComputedRef, Slots } from 'vue';
+import type { BasicTableProps } from '../types/table';
+import { unref, computed, h } from 'vue';
+import { isString } from '/@/utils/is';
+import TableHeader from '../components/TableHeader.vue';
+import { getSlot } from '../../../../utils/helper/tsxHelper';
+
+export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots) {
+  const getHeaderProps = computed(
+    (): Recordable => {
+      const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
+      const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
+      if (hideTitle && !isString(title)) {
+        return {};
+      }
+
+      return {
+        title: hideTitle
+          ? null
+          : () =>
+              h(
+                TableHeader,
+                {
+                  title,
+                  titleHelpMessage,
+                  showTableSetting,
+                  tableSetting,
+                },
+                {
+                  ...(slots.toolbar
+                    ? {
+                        toolbar: () => getSlot(slots, 'toolbar'),
+                      }
+                    : {}),
+                  ...(slots.tableTitle
+                    ? {
+                        tableTitle: () => getSlot(slots, 'tableTitle'),
+                      }
+                    : {}),
+                }
+              ),
+      };
+    }
+  );
+  return { getHeaderProps };
+}

+ 44 - 11
src/components/Table/src/hooks/useTableScroll.ts

@@ -1,4 +1,4 @@
-import type { BasicTableProps } from '../types/table';
+import type { BasicTableProps, TableRowSelection } from '../types/table';
 import type { Ref, ComputedRef } from 'vue';
 import { computed, unref, ref, nextTick, watchEffect } from 'vue';
 
@@ -7,22 +7,29 @@ import { isBoolean } from '/@/utils/is';
 
 import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
 import { useModalContext } from '/@/components/Modal';
+import { useDebounce } from '/@/hooks/core/useDebounce';
+import type { BasicColumn } from '/@/components/Table';
 
 export function useTableScroll(
   propsRef: ComputedRef<BasicTableProps>,
-  tableElRef: Ref<ComponentRef>
+  tableElRef: Ref<ComponentRef>,
+  columnsRef: ComputedRef<BasicColumn[]>,
+  rowSelectionRef: ComputedRef<TableRowSelection<any> | null>
 ) {
   const tableHeightRef: Ref<Nullable<number>> = ref(null);
 
   const modalFn = useModalContext();
 
+  // const [debounceCalcTableHeight] = useDebounce(calcTableHeight, 80);
+  const [debounceRedoHeight] = useDebounce(redoHeight, 250);
+
   const getCanResize = computed(() => {
     const { canResize, scroll } = unref(propsRef);
     return canResize && !(scroll || {}).y;
   });
 
   watchEffect(() => {
-    redoHeight();
+    unref(getCanResize) && debounceRedoHeight();
   });
 
   function redoHeight() {
@@ -33,6 +40,12 @@ export function useTableScroll(
     }
   }
 
+  function setHeight(heigh: number) {
+    tableHeightRef.value = heigh;
+    //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
+    modalFn?.redoModalHeight?.();
+  }
+
   // No need to repeat queries
   let paginationEl: HTMLElement | null;
   let footerEl: HTMLElement | null;
@@ -87,7 +100,7 @@ export function useTableScroll(
       headerHeight = (headEl as HTMLElement).offsetHeight;
     }
 
-    const height =
+    let height =
       bottomIncludeBody -
       (resizeHeightOffset || 0) -
       paddingHeight -
@@ -96,21 +109,41 @@ export function useTableScroll(
       footerHeight -
       headerHeight;
 
-    setTimeout(() => {
-      tableHeightRef.value = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
-      //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
-      modalFn?.redoModalHeight?.();
-    }, 0);
+    height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
+    setHeight(height);
   }
 
-  useWindowSizeFn(calcTableHeight, 100);
+  useWindowSizeFn(calcTableHeight, 200);
+
+  const getScrollX = computed(() => {
+    let width = 0;
+    if (unref(rowSelectionRef)) {
+      width += 60;
+    }
+
+    // TODO props
+    const NORMAL_WIDTH = 150;
+
+    const columns = unref(columnsRef);
+
+    columns.forEach((item) => {
+      width += Number.parseInt(item.width as string) || 0;
+    });
+    const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
+
+    const len = unsetWidthColumns.length;
+    if (len !== 0) {
+      width += len * NORMAL_WIDTH;
+    }
+    return width;
+  });
 
   const getScrollRef = computed(() => {
     const tableHeight = unref(tableHeightRef);
     const { canResize, scroll } = unref(propsRef);
 
     return {
-      x: '100%',
+      x: unref(getScrollX),
       y: canResize ? tableHeight : null,
       scrollToFirstRowOnChange: false,
       ...scroll,

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

@@ -2,14 +2,14 @@ import type { ComputedRef } from 'vue';
 import type { BasicTableProps, TableCustomRecord } from '../types/table';
 import { unref } from 'vue';
 import { isFunction } from '/@/utils/is';
-export function useTableStyle(propsRef: ComputedRef<BasicTableProps>) {
+export function useTableStyle(propsRef: ComputedRef<BasicTableProps>, prefixCls: string) {
   function getRowClassName(record: TableCustomRecord, index: number) {
     const { striped, rowClassName } = unref(propsRef);
     if (!striped) return;
     if (rowClassName && isFunction(rowClassName)) {
       return rowClassName(record);
     }
-    return (index || 0) % 2 === 1 ? 'basic-table-row__striped' : '';
+    return (index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : '';
   }
 
   return {

+ 2 - 2
src/components/Table/src/props.ts

@@ -75,7 +75,7 @@ export const basicProps = {
   },
   columns: {
     type: [Array] as PropType<BasicColumn[]>,
-    default: null,
+    default: () => [],
   },
   showIndexColumn: propTypes.bool.def(true),
   indexColumnProps: {
@@ -95,7 +95,7 @@ export const basicProps = {
     default: null,
   },
   title: {
-    type: [String, Function] as PropType<string | ((data: any) => any)>,
+    type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
     default: null,
   },
   titleHelpMessage: {

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

@@ -1,5 +1,3 @@
-@import (reference) '../../../../design/index.less';
-
 @prefix-cls: ~'editable-cell';
 
 .@{prefix-cls} {

+ 20 - 43
src/components/Table/src/style/index.less

@@ -1,11 +1,21 @@
-@import (reference) '../../../../design/index.less';
 @border-color: #cecece4d;
 
-.basic-table {
-  &-title {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
+@prefix-cls: ~'@{namespace}-basic-table';
+
+.@{prefix-cls} {
+  &-form-container {
+    padding: 16px;
+
+    .ant-form {
+      padding: 20px 20px 4px 12px;
+      margin-bottom: 18px;
+      background: #fff;
+      border-radius: 4px;
+    }
+
+    .ant-table-wrapper {
+      border-radius: 2px;
+    }
   }
 
   &-row__striped {
@@ -22,28 +32,16 @@
     }
   }
 
-  &-action {
-    display: flex;
-
-    button {
-      display: flex;
-      align-items: center;
-    }
-  }
-
-  &-toolbar {
-    display: flex;
-    align-items: center;
-
-    > * {
-      margin-right: 10px;
+  &--inset {
+    .ant-table-wrapper {
+      padding: 0;
     }
   }
 
   .ant-table-wrapper {
     padding: 8px;
     background: #fff;
-    border-radius: 2px;
+    border-radius: 4px;
 
     .ant-table-title {
       padding: 0 0 8px 0 !important;
@@ -54,12 +52,6 @@
     }
   }
 
-  &.inset {
-    .ant-table-wrapper {
-      padding: 0;
-    }
-  }
-
   //
   .ant-table {
     border: none;
@@ -194,18 +186,3 @@
     }
   }
 }
-
-.table-form-container {
-  padding: 16px;
-
-  .ant-form {
-    padding: 20px 20px 4px 12px;
-    margin-bottom: 18px;
-    background: #fff;
-    border-radius: 2px;
-  }
-
-  .ant-table-wrapper {
-    border-radius: 2px;
-  }
-}

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

@@ -82,6 +82,7 @@ export interface FetchParams {
 export interface GetColumnsParams {
   ignoreIndex?: boolean;
   ignoreAction?: boolean;
+  sort?: boolean;
 }
 
 export type SizeType = 'default' | 'middle' | 'small' | 'large';
@@ -93,16 +94,18 @@ export interface TableActionType {
   getSelectRowKeys: () => string[];
   deleteSelectRowByKey: (key: string) => void;
   setPagination: (info: Partial<PaginationProps>) => void;
-  setTableData: <T = any>(values: T[]) => void;
+  setTableData: <T = Recordable>(values: T[]) => void;
   getColumns: (opt?: GetColumnsParams) => BasicColumn[];
   setColumns: (columns: BasicColumn[] | string[]) => void;
-  getDataSource: <T = any>() => T[];
+  getDataSource: <T = Recordable>() => T[];
   setLoading: (loading: boolean) => void;
   setProps: (props: Partial<BasicTableProps>) => void;
   redoHeight: () => void;
   setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
   getPaginationRef: () => PaginationProps | boolean;
   getSize: () => SizeType;
+  getRowSelection: () => TableRowSelection<Recordable>;
+  getCacheColumns: () => BasicColumn[];
 }
 
 export interface FetchSetting {
@@ -308,7 +311,7 @@ export interface BasicTableProps<T = any> {
    * Table title renderer
    * @type Function | ScopedSlot
    */
-  title?: VNodeChild | JSX.Element;
+  title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string);
 
   /**
    * Set props on per header row
@@ -378,4 +381,6 @@ export interface BasicColumn extends ColumnProps {
   flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
 
   slots?: Indexable;
+
+  defaultHidden?: boolean;
 }

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

@@ -5,6 +5,8 @@ export interface ActionItem extends ButtonProps {
   color?: 'success' | 'error' | 'warning';
   icon?: string;
   popConfirm?: PopConfirm;
+  disabled?: boolean;
+  divider?: boolean;
 }
 
 export interface PopConfirm {

+ 4 - 0
src/design/ant/index.less

@@ -18,6 +18,10 @@
   }
 }
 
+.ant-image-preview-operations {
+  background: rgba(0, 0, 0, 0.3);
+}
+
 // =================================
 // ==============descriptions=======
 // =================================

+ 3 - 0
src/design/config.less

@@ -0,0 +1,3 @@
+@import 'color.less';
+@import 'var/index.less';
+@import 'mixins.less';

+ 2 - 2
src/design/index.less

@@ -1,9 +1,9 @@
-@import './transition/index.less';
+@import 'transition/index.less';
 @import 'var/index.less';
 @import 'public.less';
 @import 'mixins.less';
 @import 'ant/index.less';
-@import './global.less';
+@import 'global.less';
 
 *,
 *::before,

+ 6 - 0
src/design/var/index.less

@@ -34,3 +34,9 @@
 
 // left-menu
 @app-menu-item-height: 42px;
+
+.bem(@n;@content) {
+  @{namespace}-@{n} {
+    @content();
+  }
+}

+ 6 - 1
src/hooks/component/useFormItem.ts

@@ -1,5 +1,5 @@
 import type { UnwrapRef } from 'vue';
-import { reactive, readonly, computed, getCurrentInstance } from 'vue';
+import { reactive, readonly, computed, getCurrentInstance, watchEffect } from 'vue';
 
 import { isEqual } from 'lodash-es';
 
@@ -20,6 +20,11 @@ export function useRuleFormItem<T extends Indexable>(
   const setState = (val: UnwrapRef<T[keyof T]>) => {
     innerState.value = val as T[keyof T];
   };
+
+  watchEffect(() => {
+    innerState.value = props[key];
+  });
+
   const state = computed({
     get() {
       return innerState.value;

+ 0 - 1
src/hooks/core/useContext.ts

@@ -25,7 +25,6 @@ export function createContext<T>(
   const { readonly = true, createProvider = false } = options;
 
   const state = reactive(context);
-
   const provideData = readonly ? defineReadonly(state) : state;
   !createProvider && provide(key, provideData);
 

+ 5 - 3
src/hooks/web/useFullScreen.ts

@@ -17,7 +17,7 @@ type FSEPropName =
   | 'fullscreenElement';
 
 export function useFullscreen(
-  target: Ref<Nullable<HTMLElement>> = ref(document.documentElement),
+  target: Ref<Nullable<HTMLElement>> | Nullable<HTMLElement> = ref(document.documentElement),
   options?: FullscreenOptions
 ) {
   const isFullscreenRef = ref(false);
@@ -43,7 +43,7 @@ export function useFullscreen(
   }
   function enterFullscreen(): Promise<void> {
     isFullscreenRef.value = true;
-    return (target.value as any)[RFC_METHOD_NAME](options);
+    return (unref(target) as any)[RFC_METHOD_NAME](options);
   }
 
   function exitFullscreen(): Promise<void> {
@@ -55,7 +55,9 @@ export function useFullscreen(
     return unref(target) === (document as any)[FSE_PROP_NAME];
   }
 
-  function toggleFullscreen(): Promise<void> {
+  async function toggleFullscreen(): Promise<void> {
+    if (!unref(target)) return;
+
     if (isFullscreen()) {
       return exitFullscreen();
     } else {

+ 19 - 0
src/hooks/web/useSortable.ts

@@ -0,0 +1,19 @@
+import Sortable from 'sortablejs';
+import { nextTick, unref } from 'vue';
+import type { Ref } from 'vue';
+
+export function useSortable(el: HTMLElement | Ref<HTMLElement>, options?: Sortable.Options) {
+  function initSortable() {
+    nextTick(() => {
+      if (!el) return;
+      Sortable.create(unref(el), {
+        animation: 500,
+        delay: 400,
+        delayOnTouchOnly: true,
+        ...options,
+      });
+    });
+  }
+
+  return { initSortable };
+}

+ 18 - 29
src/layouts/default/tabs/useMultipleTabs.ts

@@ -1,8 +1,8 @@
-import Sortable from 'sortablejs';
-import { toRaw, ref, nextTick, onMounted } from 'vue';
+import { toRaw, ref, nextTick } from 'vue';
 import { RouteLocationNormalized } from 'vue-router';
 import { useProjectSetting } from '/@/hooks/setting';
 import { useDesign } from '/@/hooks/web/useDesign';
+import { useSortable } from '/@/hooks/web/useSortable';
 import router from '/@/router';
 import { tabStore } from '/@/store/modules/tab';
 import { isNullAndUnDef } from '/@/utils/is';
@@ -50,36 +50,25 @@ export function useTabsDrag(affixTextList: string[]) {
   const { multiTabsSetting } = useProjectSetting();
 
   const { prefixCls } = useDesign('multiple-tabs');
-
-  function initSortableTabs() {
+  nextTick(() => {
     if (!multiTabsSetting.canDrag) return;
-    nextTick(() => {
-      const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement;
-
-      if (!el) return;
-      Sortable.create(el, {
-        animation: 500,
-        delay: 400,
-        delayOnTouchOnly: true,
-        filter: (e: ChangeEvent) => {
-          const text = e?.target?.innerText;
-          if (!text) return false;
-          return affixTextList.includes(text);
-        },
-        onEnd: (evt) => {
-          const { oldIndex, newIndex } = evt;
+    const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement;
+    const { initSortable } = useSortable(el, {
+      filter: (e: ChangeEvent) => {
+        const text = e?.target?.innerText;
+        if (!text) return false;
+        return affixTextList.includes(text);
+      },
+      onEnd: (evt) => {
+        const { oldIndex, newIndex } = evt;
 
-          if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
-            return;
-          }
+        if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
+          return;
+        }
 
-          tabStore.commitSortTabs({ oldIndex, newIndex });
-        },
-      });
+        tabStore.commitSortTabs({ oldIndex, newIndex });
+      },
     });
-  }
-
-  onMounted(() => {
-    initSortableTabs();
+    initSortable();
   });
 }

+ 3 - 0
src/locales/lang/en/component/app.ts

@@ -5,4 +5,7 @@ export default {
   toSearch: 'to search',
   toNavigate: 'to navigate',
   toClose: 'to close',
+
+  okText: 'Confirm',
+  cancelText: 'Cancel',
 };

+ 4 - 0
src/locales/lang/en/component/table.ts

@@ -6,7 +6,11 @@ export default {
   settingDensSmall: 'Compact',
   settingColumn: 'Column settings',
   settingColumnShow: 'Column display',
+  settingIndexColumnShow: 'Index Column',
+  settingSelectColumnShow: 'Selection Column',
   settingReset: 'Reset',
+  settingFixedLeft: 'Fixed Left',
+  settingFixedRight: 'Fixed Right',
   settingFullScreen: 'Full Screen',
 
   index: 'Index',

+ 2 - 0
src/locales/lang/zh_CN/component/app.ts

@@ -5,4 +5,6 @@ export default {
   toSearch: '确认',
   toNavigate: '切换',
   toClose: '关闭',
+  okText: '确认',
+  cancelText: '取消',
 };

+ 4 - 0
src/locales/lang/zh_CN/component/table.ts

@@ -7,6 +7,10 @@ export default {
   settingColumn: '列设置',
   settingColumnShow: '列展示',
   settingReset: '重置',
+  settingIndexColumnShow: '序号列',
+  settingSelectColumnShow: '勾选列',
+  settingFixedLeft: '固定到左侧',
+  settingFixedRight: '固定到右侧',
   settingFullScreen: '全屏',
 
   index: '序号',

+ 0 - 14
src/setup/App.ts

@@ -11,7 +11,6 @@ import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
 
 import projectSetting from '/@/settings/projectSetting';
 import { getLocal } from '/@/utils/helper/persistent';
-import { isUnDef, isNull } from '/@/utils/is';
 import {
   updateGrayMode,
   updateColorWeak,
@@ -76,16 +75,3 @@ export function initAppConfigStore() {
   }
   appStore.commitProjectConfigState(projCfg);
 }
-
-// antdv Config Provider
-export function getConfigProvider() {
-  function transformCellText({ text }: { text: string }) {
-    if (isNull(text) || isUnDef(text)) {
-      return ' - ';
-    }
-    return text;
-  }
-  return {
-    transformCellText,
-  };
-}

+ 1 - 1
src/store/modules/tab.ts

@@ -182,7 +182,7 @@ class Tab extends VuexModule {
   @Action
   addTabAction(route: RouteLocationNormalized) {
     const { path, name } = route;
-    // 404  页面不需要添加tab
+    // 404  The page does not need to add a tab
     if (
       path === PageEnum.ERROR_PAGE ||
       !name ||

+ 3 - 2
src/store/modules/user.ts

@@ -106,12 +106,13 @@ class User extends VuexModule {
       const data = await loginApi(loginParams, mode);
 
       const { token, userId } = data;
-      // get user info
-      const userInfo = await this.getUserInfoAction({ userId });
 
       // save token
       this.commitTokenState(token);
 
+      // get user info
+      const userInfo = await this.getUserInfoAction({ userId });
+
       // const name = FULL_PAGE_NOT_FOUND_ROUTE.name;
       // name && router.removeRoute(name);
       goHome && (await router.replace(PageEnum.BASE_HOME));

+ 1 - 4
src/utils/index.ts

@@ -8,10 +8,7 @@ export const now = () => Date.now();
  * @description:  Set ui mount node
  */
 export function getPopupContainer(node?: HTMLElement): HTMLElement {
-  if (node) {
-    return node.parentNode as HTMLElement;
-  }
-  return document.body;
+  return (node?.parentNode as HTMLElement) ?? document.body;
 }
 
 /**

+ 1 - 0
src/views/demo/form/index.vue

@@ -224,6 +224,7 @@
       colProps: {
         span: 8,
       },
+      defaultValue: '0',
     },
     {
       field: 'field20',

+ 4 - 1
src/views/demo/table/FormTable.vue

@@ -1,5 +1,7 @@
 <template>
-  <BasicTable @register="registerTable" />
+  <BasicTable @register="registerTable">
+    <template #form-custom> custom-slot</template>
+  </BasicTable>
 </template>
 <script lang="ts">
   import { defineComponent } from 'vue';
@@ -18,6 +20,7 @@
         useSearchForm: true,
         formConfig: getFormConfig(),
         showTableSetting: true,
+        rowSelection: { type: 'checkbox' },
       });
 
       return {

+ 7 - 1
src/views/demo/table/tableData.tsx

@@ -6,7 +6,8 @@ export function getBasicColumns(): BasicColumn[] {
     {
       title: 'ID',
       dataIndex: 'id',
-      width: 150,
+      fixed: 'left',
+      width: 400,
     },
     {
       title: '姓名',
@@ -21,6 +22,7 @@ export function getBasicColumns(): BasicColumn[] {
       title: '编号',
       dataIndex: 'no',
       width: 150,
+      defaultHidden: true,
     },
     {
       title: '开始时间',
@@ -42,6 +44,8 @@ export function getBasicShortColumns(): BasicColumn[] {
       title: 'ID',
       width: 150,
       dataIndex: 'id',
+      sorter: true,
+      sortOrder: 'ascend',
     },
     {
       title: '姓名',
@@ -118,6 +122,7 @@ export function getCustomHeaderColumns(): BasicColumn[] {
     {
       // title: '地址',
       dataIndex: 'address',
+      width: 120,
       slots: { title: 'customAddress' },
       sorter: true,
     },
@@ -236,6 +241,7 @@ export function getFormConfig(): Partial<FormProps> {
         label: `字段33`,
         component: 'Select',
         defaultValue: '1',
+        slot: 'custom',
         componentProps: {
           options: [
             {

+ 5 - 1
vite.config.ts

@@ -78,7 +78,11 @@ export default (mode: 'development' | 'production'): UserConfig => {
 
     cssPreprocessOptions: {
       less: {
-        modifyVars: modifyVars,
+        modifyVars: {
+          // reference : Avoid repeated references
+          hack: `true; @import (reference) "${resolve('src/design/config.less')}";`,
+          ...modifyVars,
+        },
         javascriptEnabled: true,
       },
     },

+ 234 - 107
yarn.lock

@@ -68,6 +68,15 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
+"@babel/generator@^7.12.1":
+  version "7.12.11"
+  resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af"
+  integrity sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==
+  dependencies:
+    "@babel/types" "^7.12.11"
+    jsesc "^2.5.1"
+    source-map "^0.5.0"
+
 "@babel/generator@^7.12.10":
   version "7.12.10"
   resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz#2b188fc329fb8e4f762181703beffc0fe6df3460"
@@ -289,11 +298,21 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.10.5", "@babel/parser@^7.11.0", "@babel/parser@^7.12.0", "@babel/parser@^7.12.10", "@babel/parser@^7.12.7":
+"@babel/parser@7.12.3":
+  version "7.12.3"
+  resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd"
+  integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==
+
+"@babel/parser@^7.12.0", "@babel/parser@^7.12.10", "@babel/parser@^7.12.7":
   version "7.12.10"
   resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz#824600d59e96aea26a5a2af5a9d812af05c3ae81"
   integrity sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA==
 
+"@babel/parser@^7.12.1", "@babel/parser@^7.12.3":
+  version "7.12.11"
+  resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79"
+  integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==
+
 "@babel/plugin-proposal-async-generator-functions@^7.12.1":
   version "7.12.1"
   resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e"
@@ -833,7 +852,22 @@
     "@babel/parser" "^7.12.7"
     "@babel/types" "^7.12.7"
 
-"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5":
+"@babel/traverse@7.12.1":
+  version "7.12.1"
+  resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e"
+  integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==
+  dependencies:
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.12.1"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.11.0"
+    "@babel/parser" "^7.12.1"
+    "@babel/types" "^7.12.1"
+    debug "^4.1.0"
+    globals "^11.1.0"
+    lodash "^4.17.19"
+
+"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5":
   version "7.12.10"
   resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz#2d1f4041e8bf42ea099e5b2dc48d6a594c00017a"
   integrity sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==
@@ -848,6 +882,15 @@
     globals "^11.1.0"
     lodash "^4.17.19"
 
+"@babel/types@7.12.1":
+  version "7.12.1"
+  resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae"
+  integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.10.4"
+    lodash "^4.17.19"
+    to-fast-properties "^2.0.0"
+
 "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.0", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.4.4":
   version "7.12.10"
   resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz#7965e4a7260b26f09c56bcfcb0498af1f6d9b260"
@@ -1091,6 +1134,23 @@
     "@intlify/runtime" "9.0.0-beta.14"
     "@intlify/shared" "9.0.0-beta.14"
 
+"@intlify/core-base@9.0.0-beta.16":
+  version "9.0.0-beta.16"
+  resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.0.0-beta.16.tgz#ab35802b982f52db20d4758d020c2dcd1724e7f9"
+  integrity sha512-PJLDVYy3x8Mf9+XtWljEfk4Lo6mudopYlRvB89NQR3TkR+Tqkbcsegj09XdXpTKBYiq+yQrlZKZ0KEHb7l5Zuw==
+  dependencies:
+    "@intlify/message-compiler" "9.0.0-beta.16"
+    "@intlify/message-resolver" "9.0.0-beta.16"
+    "@intlify/runtime" "9.0.0-beta.16"
+    "@intlify/shared" "9.0.0-beta.16"
+
+"@intlify/core@^9.0.0-beta.15":
+  version "9.0.0-beta.16"
+  resolved "https://registry.npmjs.org/@intlify/core/-/core-9.0.0-beta.16.tgz#d74d4678868b37b641bdf999552b237d84dacb88"
+  integrity sha512-tPXf9rr+ZzG1zXgdLo8rCO2jws6eIXzJSaTvgnanZpfyyMKE+T8Ra5vVu3f/Sm0J7flT+z/Q3kLfnbpOMQ1UiQ==
+  dependencies:
+    "@intlify/core-base" "9.0.0-beta.16"
+
 "@intlify/message-compiler@9.0.0-beta.14":
   version "9.0.0-beta.14"
   resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.14.tgz#4b5a4467459c402e71652075e9d95e5d85e85588"
@@ -1100,11 +1160,25 @@
     "@intlify/shared" "9.0.0-beta.14"
     source-map "0.6.1"
 
+"@intlify/message-compiler@9.0.0-beta.16":
+  version "9.0.0-beta.16"
+  resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.16.tgz#359993251a303f148b3a325eca055cdbaf0cd95f"
+  integrity sha512-dE4UZsbVl5TKogYdfrJ6nQKdin1R4XMKVBVa9dE1A8HVvVHBSLy6iQiYpcw8TwcEHIa+rFjuuHuh+IdN3eCw+g==
+  dependencies:
+    "@intlify/message-resolver" "9.0.0-beta.16"
+    "@intlify/shared" "9.0.0-beta.16"
+    source-map "0.6.1"
+
 "@intlify/message-resolver@9.0.0-beta.14":
   version "9.0.0-beta.14"
   resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.14.tgz#f964706650d71ef06669c17c29cb60500ef2617a"
   integrity sha512-/PPLMHX0w/ECkG+Fmne8L3WVVVwAp3tpdisf5G775b49Fspy4dKXqkLXM2NZKhdguJbXWHXXXiRkr+mlhq8G9Q==
 
+"@intlify/message-resolver@9.0.0-beta.16":
+  version "9.0.0-beta.16"
+  resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.16.tgz#f8960344201050d17560f8d01f63e3cd0b9bf59c"
+  integrity sha512-xwjsFuDDYEv7g1KE5QZRbrPgfsrNsDhYLtNYR7Tn4inzbmB6ipak2UlDzDcQGLieSFbe1WwAoNL0IXy4sUKboQ==
+
 "@intlify/runtime@9.0.0-beta.14":
   version "9.0.0-beta.14"
   resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.14.tgz#367f6b09c991c71905b73224e238aa8382976b2d"
@@ -1114,11 +1188,25 @@
     "@intlify/message-resolver" "9.0.0-beta.14"
     "@intlify/shared" "9.0.0-beta.14"
 
+"@intlify/runtime@9.0.0-beta.16":
+  version "9.0.0-beta.16"
+  resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.16.tgz#6a210a5b0984f9e295025e3dde5262108e0e69d9"
+  integrity sha512-py+stHrbkBoEB2OsBB+rySevR+54uhybF54LToGjErr740R/AVuOVTJEKRS/LF9VvinGZZTu/WVOXcPpMfqt8Q==
+  dependencies:
+    "@intlify/message-compiler" "9.0.0-beta.16"
+    "@intlify/message-resolver" "9.0.0-beta.16"
+    "@intlify/shared" "9.0.0-beta.16"
+
 "@intlify/shared@9.0.0-beta.14":
   version "9.0.0-beta.14"
   resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.14.tgz#c221a45f666a40935f998d2e3c14451a516b9f56"
   integrity sha512-f+Gev5GnrNLyieJCB6sB/qqe03XaMf6yJiAXG3IamZ4mp45oj2Lw9Oj3hAepDW36VUZK2wCIDmWy53pxJNIFpQ==
 
+"@intlify/shared@9.0.0-beta.16":
+  version "9.0.0-beta.16"
+  resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.16.tgz#51a80ca4705c93cb14c8f06398dfc550df09d67d"
+  integrity sha512-A7GSOovcZn/NMoAmDc8FG9uRcFv6iygriK8+C6HFeOnMQ9X+T9f5A9bPtXhCOCiRpQm9SUtGqXedxO5Y8rz9/A==
+
 "@koa/cors@^3.1.0":
   version "3.1.0"
   resolved "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz#618bb073438cfdbd3ebd0e648a76e33b84f3a3b2"
@@ -1342,6 +1430,11 @@
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/braces@*":
+  version "3.0.0"
+  resolved "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb"
+  integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==
+
 "@types/connect@*":
   version "3.4.34"
   resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901"
@@ -1499,6 +1592,13 @@
   dependencies:
     "@types/unist" "*"
 
+"@types/micromatch@^4.0.1":
+  version "4.0.1"
+  resolved "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7"
+  integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==
+  dependencies:
+    "@types/braces" "*"
+
 "@types/mime@*":
   version "2.0.3"
   resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"
@@ -1606,61 +1706,61 @@
   resolved "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.0.tgz#fbc1d941cc6d9d37d18405c513ba6b294f89b609"
   integrity sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg==
 
-"@typescript-eslint/eslint-plugin@^4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.0.tgz#bc6c1e4175c0cf42083da4314f7931ad12f731cc"
-  integrity sha512-x4arJMXBxyD6aBXLm3W7mSDZRiABzy+2PCLJbL7OPqlp53VXhaA1HKK7R2rTee5OlRhnUgnp8lZyVIqjnyPT6g==
+"@typescript-eslint/eslint-plugin@^4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.1.tgz#7579c6d17ad862154c10bc14b40e5427b729e209"
+  integrity sha512-fABclAX2QIEDmTMk6Yd7Muv1CzFLwWM4505nETzRHpP3br6jfahD9UUJkhnJ/g2m7lwfz8IlswcwGGPGiq9exw==
   dependencies:
-    "@typescript-eslint/experimental-utils" "4.11.0"
-    "@typescript-eslint/scope-manager" "4.11.0"
+    "@typescript-eslint/experimental-utils" "4.11.1"
+    "@typescript-eslint/scope-manager" "4.11.1"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.0.tgz#d1a47cc6cfe1c080ce4ead79267574b9881a1565"
-  integrity sha512-1VC6mSbYwl1FguKt8OgPs8xxaJgtqFpjY/UzUYDBKq4pfQ5lBvN2WVeqYkzf7evW42axUHYl2jm9tNyFsb8oLg==
+"@typescript-eslint/experimental-utils@4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.11.1.tgz#2dad3535b878c25c7424e40bfa79d899f3f485bc"
+  integrity sha512-mAlWowT4A6h0TC9F+J5pdbEhjNiEMO+kqPKQ4sc3fVieKL71dEqfkKgtcFVSX3cjSBwYwhImaQ/mXQF0oaI38g==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/scope-manager" "4.11.0"
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/typescript-estree" "4.11.0"
+    "@typescript-eslint/scope-manager" "4.11.1"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/typescript-estree" "4.11.1"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@^4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.11.0.tgz#1dd3d7e42708c10ce9f3aa64c63c0ab99868b4e2"
-  integrity sha512-NBTtKCC7ZtuxEV5CrHUO4Pg2s784pvavc3cnz6V+oJvVbK4tH9135f/RBP6eUA2KHiFKAollSrgSctQGmHbqJQ==
+"@typescript-eslint/parser@^4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.11.1.tgz#981e18de2e019d6ca312596615f92e8f6f6598ed"
+  integrity sha512-BJ3jwPQu1jeynJ5BrjLuGfK/UJu6uwHxJ/di7sanqmUmxzmyIcd3vz58PMR7wpi8k3iWq2Q11KMYgZbUpRoIPw==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.11.0"
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/typescript-estree" "4.11.0"
+    "@typescript-eslint/scope-manager" "4.11.1"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/typescript-estree" "4.11.1"
     debug "^4.1.1"
 
-"@typescript-eslint/scope-manager@4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.0.tgz#2d906537db8a3a946721699e4fc0833810490254"
-  integrity sha512-6VSTm/4vC2dHM3ySDW9Kl48en+yLNfVV6LECU8jodBHQOhO8adAVizaZ1fV0QGZnLQjQ/y0aBj5/KXPp2hBTjA==
+"@typescript-eslint/scope-manager@4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.11.1.tgz#72dc2b60b0029ab0888479b12bf83034920b4b69"
+  integrity sha512-Al2P394dx+kXCl61fhrrZ1FTI7qsRDIUiVSuN6rTwss6lUn8uVO2+nnF4AvO0ug8vMsy3ShkbxLu/uWZdTtJMQ==
   dependencies:
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/visitor-keys" "4.11.0"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/visitor-keys" "4.11.1"
 
-"@typescript-eslint/types@4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.0.tgz#86cf95e7eac4ccfd183f9fcf1480cece7caf4ca4"
-  integrity sha512-XXOdt/NPX++txOQHM1kUMgJUS43KSlXGdR/aDyEwuAEETwuPt02Nc7v+s57PzuSqMbNLclblQdv3YcWOdXhQ7g==
+"@typescript-eslint/types@4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.11.1.tgz#3ba30c965963ef9f8ced5a29938dd0c465bd3e05"
+  integrity sha512-5kvd38wZpqGY4yP/6W3qhYX6Hz0NwUbijVsX2rxczpY6OXaMxh0+5E5uLJKVFwaBM7PJe1wnMym85NfKYIh6CA==
 
-"@typescript-eslint/typescript-estree@4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.0.tgz#1144d145841e5987d61c4c845442a24b24165a4b"
-  integrity sha512-eA6sT5dE5RHAFhtcC+b5WDlUIGwnO9b0yrfGa1mIOIAjqwSQCpXbLiFmKTdRbQN/xH2EZkGqqLDrKUuYOZ0+Hg==
+"@typescript-eslint/typescript-estree@4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.11.1.tgz#a4416b4a65872a48773b9e47afabdf7519eb10bc"
+  integrity sha512-tC7MKZIMRTYxQhrVAFoJq/DlRwv1bnqA4/S2r3+HuHibqvbrPcyf858lNzU7bFmy4mLeIHFYr34ar/1KumwyRw==
   dependencies:
-    "@typescript-eslint/types" "4.11.0"
-    "@typescript-eslint/visitor-keys" "4.11.0"
+    "@typescript-eslint/types" "4.11.1"
+    "@typescript-eslint/visitor-keys" "4.11.1"
     debug "^4.1.1"
     globby "^11.0.1"
     is-glob "^4.0.1"
@@ -1668,15 +1768,15 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/visitor-keys@4.11.0":
-  version "4.11.0"
-  resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.0.tgz#906669a50f06aa744378bb84c7d5c4fdbc5b7d51"
-  integrity sha512-tRYKyY0i7cMk6v4UIOCjl1LhuepC/pc6adQqJk4Is3YcC6k46HvsV9Wl7vQoLbm9qADgeujiT7KdLrylvFIQ+A==
+"@typescript-eslint/visitor-keys@4.11.1":
+  version "4.11.1"
+  resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.11.1.tgz#4c050a4c1f7239786e2dd4e69691436143024e05"
+  integrity sha512-IrlBhD9bm4bdYcS8xpWarazkKXlE7iYb1HzRuyBP114mIaj5DJPo11Us1HgH60dTt41TCZXMaTCAW+OILIYPOg==
   dependencies:
-    "@typescript-eslint/types" "4.11.0"
+    "@typescript-eslint/types" "4.11.1"
     eslint-visitor-keys "^2.0.0"
 
-"@vue/compiler-core@*", "@vue/compiler-core@3.0.4", "@vue/compiler-core@^3.0.0-rc.5":
+"@vue/compiler-core@3.0.4", "@vue/compiler-core@^3.0.0", "@vue/compiler-core@^3.0.1", "@vue/compiler-core@^3.0.2":
   version "3.0.4"
   resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.4.tgz#0122aca6eada4cb28b39ed930af917444755e330"
   integrity sha512-snpMICsbWTZqBFnPB03qr4DtiSxVYfDF3DvbDSkN9Z9NTM8Chl8E/lYhKBSsvauq91DAWAh8PU3lr9vrLyQsug==
@@ -1695,7 +1795,7 @@
     "@vue/compiler-core" "3.0.4"
     "@vue/shared" "3.0.4"
 
-"@vue/compiler-sfc@*", "@vue/compiler-sfc@^3.0.0-rc.5", "@vue/compiler-sfc@^3.0.3", "@vue/compiler-sfc@^3.0.4":
+"@vue/compiler-sfc@^3.0.3", "@vue/compiler-sfc@^3.0.4":
   version "3.0.4"
   resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.0.4.tgz#2119fe1e68d2c268aafa20461c82c139a9adf8e0"
   integrity sha512-brDn6HTuK6R3oBCjtMPPsIpyJEZFinlnxjtBXww/goFJOJBAU9CrsdegwyZItNnixCFUIg4CLv4Nj1Eg/eKlfg==
@@ -1759,75 +1859,97 @@
   resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.0.4.tgz#6dc50f593bdfdeaa6183d1dbc15e2d45e7c6b8b3"
   integrity sha512-Swfbz31AaMX48CpFl+YmIrqOH9MgJMTrltG9e26A4ZxYx9LjGuMV+41WnxFzS3Bc9nbrc6sDPM37G6nIT8NJSg==
 
-"@vuedx/analyze@0.2.4-0":
-  version "0.2.4-0"
-  resolved "https://registry.npmjs.org/@vuedx/analyze/-/analyze-0.2.4-0.tgz#52766a6dcd2867320409fe517540fd0bf0394d48"
-  integrity sha512-GX5lqZPwyHLrJaL36L5qcBLBGcvTz/LiGwv1nwxuZ2HpO4rr2mH6CemA0mOKXiQ5aaqbAYqeAzMQf+i/AEViQg==
+"@vuedx/analyze@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/analyze/-/analyze-0.4.0.tgz#a5ae608e8fedd0d9125e4ddb88b75a4a8165c991"
+  integrity sha512-JHr5xjbzHNlAXY6pQER2SXzpnpDPINpfg9H1+eQo7e3dMwMdXj0f1nnJ9l3b3QCVR11jyr+xsWrqfuvNo2RJXg==
   dependencies:
     "@babel/code-frame" "^7.10.4"
-    "@babel/parser" "^7.10.5"
-    "@babel/traverse" "^7.11.0"
-    "@babel/types" "^7.10.5"
-    "@vue/compiler-core" "^3.0.0-rc.5"
-    "@vue/compiler-sfc" "^3.0.0-rc.5"
+    "@babel/generator" "^7.12.1"
+    "@babel/parser" "^7.12.3"
+    "@babel/template" "^7.12.7"
+    "@babel/traverse" "7.12.1"
+    "@babel/types" "7.12.1"
+    "@types/micromatch" "^4.0.1"
+    "@vuedx/compiler-sfc" "0.4.0"
+    "@vuedx/compiler-tsx" "0.4.0"
+    "@vuedx/projectconfig" "0.4.0"
+    "@vuedx/template-ast-types" "0.4.0"
     cli-highlight "^2.1.4"
-    commander "^6.0.0"
+    commander "^6.1.0"
     fast-glob "^3.2.4"
     hash-sum "^2.0.0"
+    micromatch "^4.0.2"
 
-"@vuedx/compiler-tsx@0.2.4-0":
-  version "0.2.4-0"
-  resolved "https://registry.npmjs.org/@vuedx/compiler-tsx/-/compiler-tsx-0.2.4-0.tgz#a4d734519f689e82287d5bbd8048cdcf1932aee6"
-  integrity sha512-XzJdijqmHPiNNV555TgBfv/7iMmwmpQYCimpTtccrVch7m1R+64YZqtgg43lkWZLwwANhPVborJh/Pw8wCUokQ==
+"@vuedx/compiler-sfc@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/compiler-sfc/-/compiler-sfc-0.4.0.tgz#2c3c85fdcd45b4f0e759179ea87d23c09a188c6d"
+  integrity sha512-AXBi6V2wofbLUff8q2KAAOkfTgWnHcpB9Yqd3KpLhXsKXwGnt4Gt0NW2sjHTantnbAQWB4qxZohbNikHscNXuQ==
   dependencies:
-    "@babel/parser" "^7.11.0"
-    "@babel/types" "^7.11.0"
-    "@vue/compiler-core" "^3.0.0-rc.5"
-    "@vuedx/template-ast-types" "0.2.4-0"
-    lodash.camelcase "^4.3.0"
+    "@vue/compiler-core" "^3.0.2"
+    lru-cache "^6.0.0"
+    source-map "^0.6.1"
 
-"@vuedx/template-ast-types@0.2.4-0":
-  version "0.2.4-0"
-  resolved "https://registry.npmjs.org/@vuedx/template-ast-types/-/template-ast-types-0.2.4-0.tgz#a1cead2e9631f8fa710a9c5622412f600e290a1f"
-  integrity sha512-9xFfTPH4rTxNsITOJMinlzDJf3+8VS/ao02gfHZMs57XgHszghYzVFKpfs2G0056xYAKrfoJSEJw51eGSB9KQA==
+"@vuedx/compiler-tsx@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/compiler-tsx/-/compiler-tsx-0.4.0.tgz#f35c1ebbe4bb006cca12509882f6941d4c06408e"
+  integrity sha512-L3zS8RXBVDdITCGih7wGbOURtgGyE5YN/HgKkmSsWLW/KrrUWldOdn9pe/31yixLRdqgpG+9TePauMw7n6HoNw==
   dependencies:
-    "@vue/compiler-core" "^3.0.0-rc.5"
+    "@babel/parser" "7.12.3"
+    "@babel/types" "7.12.1"
+    "@vue/compiler-core" "^3.0.1"
+    "@vuedx/template-ast-types" "0.4.0"
 
-"@vuedx/typecheck@^0.2.4-0":
-  version "0.2.4-0"
-  resolved "https://registry.npmjs.org/@vuedx/typecheck/-/typecheck-0.2.4-0.tgz#d88e942b9b62d31cc8df3ffcfc16265b6da37665"
-  integrity sha512-pOOgz5epvks6lcSfqCotm7fcen4UHl/h1pUiabtV/EKoNKh3OXMB8IBzRHwWv0h1SrkMuKVqMzj52liZIVZRfw==
+"@vuedx/projectconfig@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/projectconfig/-/projectconfig-0.4.0.tgz#a3c39f24e18fef99260480c50214f9266b06ea52"
+  integrity sha512-bBWEpG5pQHJuTYUEau0dvYdwakoGyphheq268FN8/EppzpbSOSligVDtS84AYCC8IKqyPf8I7f1Mma6kDyuBnQ==
+
+"@vuedx/template-ast-types@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/template-ast-types/-/template-ast-types-0.4.0.tgz#9a9ce25846273f8b578e00db6d50c5fdc1d3b33c"
+  integrity sha512-WCUEJwEzYL7WSeA8zNXVlYtVK5aN8gbMAAt2/HZRGXKTcI3awawFrQv1c6wkAsQP4czvcdpz//y2Gmuh43KfNA==
+  dependencies:
+    "@vue/compiler-core" "^3.0.0"
+
+"@vuedx/typecheck@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/typecheck/-/typecheck-0.4.0.tgz#331124e2ae96cac066943e56d4eb67d44da1274e"
+  integrity sha512-2GOXVpyD/bDPjz+ZQK/ltN06XuA+2s3Tq2n7wpUx7ws9vzyhVJQzgUv6SL1LKu04x+qZMFkBX2Q0vMfaQqh5Eg==
   dependencies:
-    "@vue/compiler-core" "*"
-    "@vue/compiler-sfc" "*"
-    "@vuedx/typescript-plugin-vue" "0.2.4-0"
-    "@vuedx/vue-virtual-textdocument" "0.2.4-0"
+    "@vuedx/typescript-plugin-vue" "0.4.0"
+    "@vuedx/vue-virtual-textdocument" "0.4.0"
     chalk "^4.1.0"
+    fast-glob "^3.2.4"
     minimist "^1.2.5"
+    typescript "^4.0.3"
 
-"@vuedx/typescript-plugin-vue@0.2.4-0", "@vuedx/typescript-plugin-vue@^0.2.4-0":
-  version "0.2.4-0"
-  resolved "https://registry.npmjs.org/@vuedx/typescript-plugin-vue/-/typescript-plugin-vue-0.2.4-0.tgz#e07485139e28de823e5153d96a2f71e82d0f75f2"
-  integrity sha512-BB5S29B2JRmmaglM8gJD/VT0Ika9RUnZYUCOMCopHCItyTbFC7RLJkIZg9rZS77R87/alD5W316WxqgE8C9XYA==
-  dependencies:
-    "@vue/compiler-core" "^3.0.0-rc.5"
-    "@vuedx/analyze" "0.2.4-0"
-    "@vuedx/template-ast-types" "0.2.4-0"
-    "@vuedx/vue-virtual-textdocument" "0.2.4-0"
+"@vuedx/typescript-plugin-vue@0.4.0", "@vuedx/typescript-plugin-vue@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/typescript-plugin-vue/-/typescript-plugin-vue-0.4.0.tgz#4780912f5915e65adfcae4764229918366723ac9"
+  integrity sha512-3VTo19ZhnqZbnu3RGRxDJmEw7KI4e+CvXGhrlrjTgqsxgS10yfqn44cgYXF7et4iv/5RRQJjFXUO6Uvla52e8g==
+  dependencies:
+    "@intlify/core" "^9.0.0-beta.15"
+    "@vuedx/analyze" "0.4.0"
+    "@vuedx/compiler-sfc" "0.4.0"
+    "@vuedx/projectconfig" "0.4.0"
+    "@vuedx/template-ast-types" "0.4.0"
+    "@vuedx/vue-virtual-textdocument" "0.4.0"
     de-indent "^1.0.2"
+    json5 "^2.1.3"
     quick-lru "^5.1.1"
-    typescript "^3.9.7"
     vscode-uri "^2.1.2"
+    vscode-web-custom-data "^0.3.2"
 
-"@vuedx/vue-virtual-textdocument@0.2.4-0":
-  version "0.2.4-0"
-  resolved "https://registry.npmjs.org/@vuedx/vue-virtual-textdocument/-/vue-virtual-textdocument-0.2.4-0.tgz#ea5eb9d17b83b1ebf1e5b4e7313634bc2c4c2a54"
-  integrity sha512-eCgyvN5/O27UWlAohwuHzlPftoxEIrEz6zEDncXKFb7Gzzp5EoNlptXN16tJoYGHi1duz4aMnEg6p5xoBSqArQ==
+"@vuedx/vue-virtual-textdocument@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.npmjs.org/@vuedx/vue-virtual-textdocument/-/vue-virtual-textdocument-0.4.0.tgz#e10bcff51f43f3f64790e39f09dfa767e91b8e16"
+  integrity sha512-z4yR5CTG3HtrctAPosbbI3+xldPGzJ4nJHXA7s4k60iIxKruLzlzWbjHtvl9VSekAzRcAr0slJifr5wE47L1QQ==
   dependencies:
-    "@vue/compiler-core" "^3.0.0-rc.5"
-    "@vue/compiler-sfc" "^3.0.0-rc.5"
-    "@vuedx/analyze" "0.2.4-0"
-    "@vuedx/compiler-tsx" "0.2.4-0"
+    "@vuedx/analyze" "0.4.0"
+    "@vuedx/compiler-sfc" "0.4.0"
+    "@vuedx/compiler-tsx" "0.4.0"
+    source-map "^0.6.1"
     vscode-languageserver-textdocument "^1.0.1"
     vscode-uri "^2.1.2"
 
@@ -2621,7 +2743,7 @@ colorette@^1.2.1:
   resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
   integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
 
-commander@*, commander@^6.0.0, commander@^6.2.0:
+commander@*, commander@^6.2.0:
   version "6.2.0"
   resolved "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
   integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
@@ -2636,6 +2758,11 @@ commander@^4.1.1:
   resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
   integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
 
+commander@^6.1.0:
+  version "6.2.1"
+  resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
+  integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
+
 commander@~2.14.1:
   version "2.14.1"
   resolved "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
@@ -4929,7 +5056,7 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.2:
+json5@^2.1.2, json5@^2.1.3:
   version "2.1.3"
   resolved "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
   integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
@@ -7870,12 +7997,7 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@^3.9.7:
-  version "3.9.7"
-  resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
-  integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
-
-typescript@^4.1.3:
+typescript@^4.0.3, typescript@^4.1.3:
   version "4.1.3"
   resolved "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
   integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
@@ -8096,10 +8218,10 @@ vite-plugin-purge-icons@^0.4.5:
     "@purge-icons/generated" "^0.4.1"
     rollup-plugin-purge-icons "^0.4.5"
 
-vite-plugin-pwa@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.2.0.tgz#e9368530c97537bdad7279f05de061ab9b024cce"
-  integrity sha512-OBNhlSqvqH9af9i8HsetmaRTrUjit3UP0rx33Sr0iBapM0gtuAmTjS4JPdSM54cGC1aVaIC3Rn3sY9wL0uxBrw==
+vite-plugin-pwa@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.2.1.tgz#67e569ac7d5af4b094c6c41b50d06955dd09ef6a"
+  integrity sha512-d1Tw5O3dZjAVVS9wZTI1V6gw7m2wK0LJQ/K+D5I00i6kpuJObPcX5nIrPyPJsKYAm53AU2VwnAMB5thL7MGSMw==
   dependencies:
     debug "^4.3.2"
     fast-glob "^3.2.4"
@@ -8176,6 +8298,11 @@ vscode-uri@^2.1.2:
   resolved "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c"
   integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==
 
+vscode-web-custom-data@^0.3.2:
+  version "0.3.2"
+  resolved "https://registry.npmjs.org/vscode-web-custom-data/-/vscode-web-custom-data-0.3.2.tgz#62a5a924397d8056c5524ff0ff8f14eb815b7066"
+  integrity sha512-GGZ99dJbARyh6rv03dXZImGlP5WvNG382A3nIt0yX1uyqBa558L/klHWcgEJzcVkG4t16OQWwPedMR3JkeD2Qg==
+
 vue-demi@latest:
   version "0.4.5"
   resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.4.5.tgz#ea422a4468cb6321a746826a368a770607f87791"