Prechádzať zdrojové kódy

refactor(tree): Refactor tree to support antv3.0

vben 3 rokov pred
rodič
commit
52257f061d

+ 2 - 1
.eslintrc.js

@@ -62,6 +62,7 @@ module.exports = defineConfig({
     'vue/singleline-html-element-content-newline': 'off',
     'vue/attribute-hyphenation': 'off',
     'vue/require-default-prop': 'off',
+    'vue/require-explicit-emits': 'off',
     'vue/html-self-closing': [
       'error',
       {
@@ -74,6 +75,6 @@ module.exports = defineConfig({
         math: 'always',
       },
     ],
-		'vue/multi-word-component-names': 'off'
+    'vue/multi-word-component-names': 'off',
   },
 });

+ 2 - 1
.vscode/settings.json

@@ -132,6 +132,7 @@
     "brotli",
     "tailwindcss",
     "sider",
-    "pnpm"
+    "pnpm",
+    "antd"
   ]
 }

+ 10 - 10
build/vite/plugin/hmr.ts

@@ -9,17 +9,17 @@ import type { Plugin } from 'vite';
 export function configHmrPlugin(): Plugin {
   return {
     name: 'singleHMR',
-    handleHotUpdate({ modules, file }) {
-      if (file.match(/xml$/)) return [];
+    // handleHotUpdate({ modules, file }) {
+    //   if (file.match(/xml$/)) return [];
 
-      modules.forEach((m) => {
-        if (!m.url.match(/\.(css|less)/)) {
-          m.importedModules = new Set();
-          m.importers = new Set();
-        }
-      });
+    //   modules.forEach((m) => {
+    //     if (!m.url.match(/\.(css|less)/)) {
+    //       m.importedModules = new Set();
+    //       m.importers = new Set();
+    //     }
+    //   });
 
-      return modules;
-    },
+    //   return modules;
+    // },
   };
 }

+ 5 - 4
build/vite/plugin/styleImport.ts

@@ -4,10 +4,10 @@
  */
 import styleImport from 'vite-plugin-style-import';
 
-export function configStyleImportPlugin(isBuild: boolean) {
-  if (!isBuild) {
-    return [];
-  }
+export function configStyleImportPlugin(_isBuild: boolean) {
+  // if (!isBuild) {
+  //   return [];
+  // }
   const styleImportPlugin = styleImport({
     libs: [
       {
@@ -19,6 +19,7 @@ export function configStyleImportPlugin(isBuild: boolean) {
             'anchor-link',
             'sub-menu',
             'menu-item',
+            'menu-divider',
             'menu-item-group',
             'breadcrumb-item',
             'breadcrumb-separator',

+ 4 - 4
package.json

@@ -72,7 +72,7 @@
   "devDependencies": {
     "@commitlint/cli": "^15.0.0",
     "@commitlint/config-conventional": "^15.0.0",
-    "@iconify/json": "^2.0.2",
+    "@iconify/json": "^2.0.3",
     "@purge-icons/generated": "^0.7.0",
     "@types/codemirror": "^5.60.5",
     "@types/crypto-js": "^4.0.2",
@@ -110,7 +110,7 @@
     "fs-extra": "^10.0.0",
     "husky": "^7.0.4",
     "inquirer": "^8.2.0",
-    "jest": "^27.3.1",
+    "jest": "^27.4.0",
     "less": "^4.1.2",
     "lint-staged": "12.1.2",
     "npm-run-all": "^4.1.5",
@@ -135,14 +135,14 @@
     "vite-plugin-imagemin": "^0.4.6",
     "vite-plugin-mock": "^2.9.6",
     "vite-plugin-purge-icons": "^0.7.0",
-    "vite-plugin-pwa": "^0.11.7",
+    "vite-plugin-pwa": "^0.11.8",
     "vite-plugin-style-import": "^1.4.0",
     "vite-plugin-svg-icons": "^1.0.5",
     "vite-plugin-theme": "^0.8.1",
     "vite-plugin-vue-setup-extend": "^0.1.0",
     "vite-plugin-windicss": "^1.5.3",
     "vue-eslint-parser": "^8.0.1",
-    "vue-tsc": "^0.29.6"
+    "vue-tsc": "^0.29.7"
   },
   "resolutions": {
     "//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 304 - 266
pnpm-lock.yaml


+ 2 - 1
src/components/Tree/index.ts

@@ -1,5 +1,6 @@
 import BasicTree from './src/Tree.vue';
+import './style';
 
 export { BasicTree };
 export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
-export * from './src/typing';
+export * from './src/tree';

+ 59 - 146
src/components/Tree/src/Tree.vue

@@ -1,6 +1,6 @@
 <script lang="tsx">
-  import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './typing';
-  import type { CheckEvent } from './typing';
+  import type { CSSProperties } from 'vue';
+  import type { FieldNames, TreeState, TreeItem, KeyType, CheckKeys, TreeActionType } from './tree';
 
   import {
     defineComponent,
@@ -11,43 +11,31 @@
     watchEffect,
     toRaw,
     watch,
-    CSSProperties,
     onMounted,
   } from 'vue';
   import { Tree, Empty } from 'ant-design-vue';
   import { TreeIcon } from './TreeIcon';
   import { ScrollContainer } from '/@/components/Container';
-  import { omit, get, difference } from 'lodash-es';
+  import { omit, get, difference, cloneDeep } from 'lodash-es';
   import { isArray, isBoolean, isEmpty, isFunction } from '/@/utils/is';
   import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper';
   import { filter, treeToList } from '/@/utils/helper/treeHelper';
   import { useTree } from './useTree';
   import { useContextMenu } from '/@/hooks/web/useContextMenu';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { basicProps } from './props';
   import { CreateContextOptions } from '/@/components/ContextMenu';
   import TreeHeader from './TreeHeader.vue';
+  import { treeEmits, treeProps } from './tree';
+  import { createBEM } from '/@/utils/bem';
 
-  interface State {
-    expandedKeys: Keys;
-    selectedKeys: Keys;
-    checkedKeys: CheckKeys;
-    checkStrictly: boolean;
-  }
   export default defineComponent({
     name: 'BasicTree',
     inheritAttrs: false,
-    props: basicProps,
-    emits: [
-      'update:expandedKeys',
-      'update:selectedKeys',
-      'update:value',
-      'change',
-      'check',
-      'update:searchValue',
-    ],
+    props: treeProps,
+    emits: treeEmits,
     setup(props, { attrs, slots, emit, expose }) {
-      const state = reactive<State>({
+      const [bem] = createBEM('tree');
+
+      const state = reactive<TreeState>({
         checkStrictly: props.checkStrictly,
         expandedKeys: props.expandedKeys || [],
         selectedKeys: props.selectedKeys || [],
@@ -63,15 +51,14 @@
       const treeDataRef = ref<TreeItem[]>([]);
 
       const [createContextMenu] = useContextMenu();
-      const { prefixCls } = useDesign('basic-tree');
 
-      const getReplaceFields = computed((): Required<ReplaceFields> => {
-        const { replaceFields } = props;
+      const getFieldNames = computed((): Required<FieldNames> => {
+        const { fieldNames } = props;
         return {
           children: 'children',
           title: 'title',
           key: 'key',
-          ...replaceFields,
+          ...fieldNames,
         };
       });
 
@@ -84,19 +71,19 @@
           selectedKeys: state.selectedKeys,
           checkedKeys: state.checkedKeys,
           checkStrictly: state.checkStrictly,
-          replaceFields: unref(getReplaceFields),
-          'onUpdate:expandedKeys': (v: Keys) => {
+          filedNames: unref(getFieldNames),
+          'onUpdate:expandedKeys': (v: KeyType[]) => {
             state.expandedKeys = v;
             emit('update:expandedKeys', v);
           },
-          'onUpdate:selectedKeys': (v: Keys) => {
+          'onUpdate:selectedKeys': (v: KeyType[]) => {
             state.selectedKeys = v;
             emit('update:selectedKeys', v);
           },
-          onCheck: (v: CheckKeys, e: CheckEvent) => {
-            let currentValue = toRaw(state.checkedKeys) as Keys;
+          onCheck: (v: CheckKeys, e) => {
+            let currentValue = toRaw(state.checkedKeys) as KeyType[];
             if (isArray(currentValue) && searchState.startSearch) {
-              const { key } = unref(getReplaceFields);
+              const { key } = unref(getFieldNames);
               currentValue = difference(currentValue, getChildrenKeys(e.node.$attrs.node[key]));
               if (e.checked) {
                 currentValue.push(e.node.$attrs.node[key]);
@@ -132,7 +119,7 @@
         getAllKeys,
         getChildrenKeys,
         getEnabledKeys,
-      } = useTree(treeDataRef, getReplaceFields);
+      } = useTree(treeDataRef, getFieldNames);
 
       function getIcon(params: Recordable, icon?: string) {
         if (!icon) {
@@ -161,14 +148,14 @@
         createContextMenu(contextMenuOptions);
       }
 
-      function setExpandedKeys(keys: Keys) {
+      function setExpandedKeys(keys: KeyType[]) {
         state.expandedKeys = keys;
       }
 
       function getExpandedKeys() {
         return state.expandedKeys;
       }
-      function setSelectedKeys(keys: Keys) {
+      function setSelectedKeys(keys: KeyType[]) {
         state.selectedKeys = keys;
       }
 
@@ -185,11 +172,11 @@
       }
 
       function checkAll(checkAll: boolean) {
-        state.checkedKeys = checkAll ? getEnabledKeys() : ([] as Keys);
+        state.checkedKeys = checkAll ? getEnabledKeys() : ([] as KeyType[]);
       }
 
       function expandAll(expandAll: boolean) {
-        state.expandedKeys = expandAll ? getAllKeys() : ([] as Keys);
+        state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]);
       }
 
       function onStrictlyChange(strictly: boolean) {
@@ -227,21 +214,21 @@
         const { filterFn, checkable, expandOnSearch, checkOnSearch, selectedOnSearch } =
           unref(props);
         searchState.startSearch = true;
-        const { title: titleField, key: keyField } = unref(getReplaceFields);
+        const { title: titleField, key: keyField } = unref(getFieldNames);
 
         const matchedKeys: string[] = [];
         searchState.searchData = filter(
           unref(treeDataRef),
           (node) => {
             const result = filterFn
-              ? filterFn(searchValue, node, unref(getReplaceFields))
+              ? filterFn(searchValue, node, unref(getFieldNames))
               : node[titleField]?.includes(searchValue) ?? false;
             if (result) {
               matchedKeys.push(node[keyField]);
             }
             return result;
           },
-          unref(getReplaceFields),
+          unref(getFieldNames),
         );
 
         if (expandOnSearch) {
@@ -317,15 +304,6 @@
         },
       );
 
-      // watchEffect(() => {
-      //   console.log('======================');
-      //   console.log(props.value);
-      //   console.log('======================');
-      //   if (props.value) {
-      //     state.checkedKeys = props.value;
-      //   }
-      // });
-
       watchEffect(() => {
         state.checkStrictly = props.checkStrictly;
       });
@@ -354,8 +332,6 @@
         },
       };
 
-      expose(instance);
-
       function renderAction(node: TreeItem) {
         const { actionList } = props;
         if (!actionList || actionList.length === 0) return;
@@ -370,29 +346,25 @@
           if (!nodeShow) return null;
 
           return (
-            <span key={index} class={`${prefixCls}__action`}>
+            <span key={index} class={bem('action')}>
               {item.render(node)}
             </span>
           );
         });
       }
 
-      function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {
-        if (!data) {
-          return null;
-        }
-        const searchText = searchState.searchText;
-        const { highlight } = unref(props);
-        return data.map((item) => {
+      const treeData = computed(() => {
+        const data = cloneDeep(getTreeData.value);
+        data.forEach((item) => {
+          const searchText = searchState.searchText;
+          const { highlight } = unref(props);
           const {
             title: titleField,
             key: keyField,
             children: childrenField,
-          } = unref(getReplaceFields);
+          } = unref(getFieldNames);
 
-          const propsData = omit(item, 'title');
-          const icon = getIcon({ ...item, level }, item.icon);
-          const children = get(item, childrenField) || [];
+          const icon = getIcon(item, item.icon);
           const title = get(item, titleField);
 
           const searchIdx = searchText ? title.indexOf(searchText) : -1;
@@ -401,7 +373,7 @@
           const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`;
 
           const titleDom = isHighlight ? (
-            <span class={unref(getBindValues)?.blockNode ? `${prefixCls}__content` : ''}>
+            <span class={unref(getBindValues)?.blockNode ? `${bem('content')}` : ''}>
               <span>{title.substr(0, searchIdx)}</span>
               <span style={highlightStyle}>{searchText}</span>
               <span>{title.substr(searchIdx + (searchText as string).length)}</span>
@@ -409,41 +381,34 @@
           ) : (
             title
           );
-
-          return (
-            <Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>
-              {{
-                title: () => (
-                  <span
-                    class={`${prefixCls}-title pl-2`}
-                    onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
-                  >
-                    {item.slots?.title ? (
-                      getSlot(slots, item.slots?.title, item)
-                    ) : (
-                      <>
-                        {icon && <TreeIcon icon={icon} />}
-                        {titleDom}
-                        {/*{get(item, titleField)}*/}
-                        <span class={`${prefixCls}__actions`}>
-                          {renderAction({ ...item, level })}
-                        </span>
-                      </>
-                    )}
-                  </span>
-                ),
-                default: () => renderTreeNode({ data: children, level: level + 1 }),
-              }}
-            </Tree.TreeNode>
+          item.title = (
+            <span
+              class={`${bem('title')} pl-2`}
+              onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
+            >
+              {item.slots?.title ? (
+                getSlot(slots, item.slots?.title, item)
+              ) : (
+                <>
+                  {icon && <TreeIcon icon={icon} />}
+                  {titleDom}
+                  <span class={bem('actions')}>{renderAction(item)}</span>
+                </>
+              )}
+            </span>
           );
         });
-      }
+        return data;
+      });
+
+      expose(instance);
+
       return () => {
         const { title, helpMessage, toolbar, search, checkable } = props;
         const showTitle = title || toolbar || search || slots.headerTitle;
         const scrollStyle: CSSProperties = { height: 'calc(100% - 38px)' };
         return (
-          <div class={[prefixCls, 'h-full', attrs.class]}>
+          <div class={[bem(), 'h-full', attrs.class]}>
             {showTitle && (
               <TreeHeader
                 checkable={checkable}
@@ -461,15 +426,10 @@
               </TreeHeader>
             )}
             <ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}>
-              <Tree {...unref(getBindValues)} showIcon={false}>
-                {{
-                  // switcherIcon: () => <DownOutlined />,
-                  default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
-                  ...extendSlots(slots),
-                }}
+              <Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value}>
+                {extendSlots(slots)}
               </Tree>
             </ScrollContainer>
-
             <Empty v-show={unref(getNotFound)} image={Empty.PRESENTED_IMAGE_SIMPLE} class="!mt-4" />
           </div>
         );
@@ -477,50 +437,3 @@
     },
   });
 </script>
-<style lang="less">
-  @prefix-cls: ~'@{namespace}-basic-tree';
-
-  .@{prefix-cls} {
-    background-color: @component-background;
-
-    .ant-tree-node-content-wrapper {
-      position: relative;
-
-      .ant-tree-title {
-        position: absolute;
-        left: 0;
-        width: 100%;
-      }
-    }
-
-    &-title {
-      position: relative;
-      display: flex;
-      align-items: center;
-      width: 100%;
-      padding-right: 10px;
-
-      &:hover {
-        .@{prefix-cls}__action {
-          visibility: visible;
-        }
-      }
-    }
-
-    &__content {
-      overflow: hidden;
-    }
-
-    &__actions {
-      position: absolute;
-      top: 2px;
-      right: 3px;
-      display: flex;
-    }
-
-    &__action {
-      margin-left: 4px;
-      visibility: hidden;
-    }
-  }
-</style>

+ 120 - 129
src/components/Tree/src/TreeHeader.vue

@@ -1,10 +1,9 @@
 <template>
-  <div class="flex px-2 py-1.5 items-center basic-tree-header">
-    <slot name="headerTitle" v-if="$slots.headerTitle"></slot>
-    <BasicTitle :helpMessage="helpMessage" v-if="!$slots.headerTitle && title">
+  <div :class="bem()" class="flex px-2 py-1.5 items-center">
+    <slot name="headerTitle" v-if="slots.headerTitle"></slot>
+    <BasicTitle :helpMessage="helpMessage" v-if="!slots.headerTitle && title">
       {{ title }}
     </BasicTitle>
-
     <div
       class="flex flex-1 justify-self-stretch items-center cursor-pointer"
       v-if="search || toolbar"
@@ -33,148 +32,140 @@
     </div>
   </div>
 </template>
-<script lang="ts">
-  import { PropType } from 'vue';
-  import { defineComponent, computed, ref, watch } from 'vue';
-
-  import { Dropdown, Menu, Input } from 'ant-design-vue';
+<script lang="ts" setup>
+  import { computed, ref, watch, useSlots } from 'vue';
+  import { Dropdown, Menu, MenuItem, MenuDivider, InputSearch } from 'ant-design-vue';
   import { Icon } from '/@/components/Icon';
   import { BasicTitle } from '/@/components/Basic';
-
-  import { propTypes } from '/@/utils/propTypes';
-
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useDebounceFn } from '@vueuse/core';
+  import { createBEM } from '/@/utils/bem';
+  import { ToolbarEnum } from './tree';
 
-  enum ToolbarEnum {
-    SELECT_ALL,
-    UN_SELECT_ALL,
-    EXPAND_ALL,
-    UN_EXPAND_ALL,
-    CHECK_STRICTLY,
-    CHECK_UN_STRICTLY,
-  }
+  const searchValue = ref('');
 
-  interface MenuInfo {
-    key: ToolbarEnum;
-  }
-  export default defineComponent({
-    name: 'BasicTreeHeader',
-    components: {
-      BasicTitle,
-      Icon,
-      Dropdown,
-      Menu,
-      MenuItem: Menu.Item,
-      MenuDivider: Menu.Divider,
-      InputSearch: Input.Search,
+  const [bem] = createBEM('tree-header');
+
+  // eslint-disable vue/valid-define-emits
+  const props = defineProps({
+    helpMessage: {
+      type: [String, Array] as PropType<string | string[]>,
+      default: '',
     },
-    props: {
-      helpMessage: {
-        type: [String, Array] as PropType<string | string[]>,
-        default: '',
-      },
-      title: propTypes.string,
-      toolbar: propTypes.bool,
-      checkable: propTypes.bool,
-      search: propTypes.bool,
-      checkAll: propTypes.func,
-      expandAll: propTypes.func,
-      searchText: propTypes.string,
+    title: {
+      type: String,
+      default: '',
     },
-    emits: ['strictly-change', 'search'],
-    setup(props, { emit, slots }) {
-      const { t } = useI18n();
-      const searchValue = ref('');
+    toolbar: {
+      type: Boolean,
+      default: false,
+    },
+    checkable: {
+      type: Boolean,
+      default: false,
+    },
+    search: {
+      type: Boolean,
+      default: false,
+    },
+    searchText: {
+      type: String,
+      default: '',
+    },
+    checkAll: {
+      type: Function,
+      default: undefined,
+    },
+    expandAll: {
+      type: Function,
+      default: undefined,
+    },
+  } as const);
+  const emit = defineEmits(['strictly-change', 'search']);
 
-      const getInputSearchCls = computed(() => {
-        const titleExists = slots.headerTitle || props.title;
-        return [
-          'mr-1',
-          'w-full',
-          // titleExists ? 'w-2/3' : 'w-full',
-          {
-            ['ml-5']: titleExists,
-          },
-        ];
-      });
+  const slots = useSlots();
+  const { t } = useI18n();
+
+  const getInputSearchCls = computed(() => {
+    const titleExists = slots.headerTitle || props.title;
+    return [
+      'mr-1',
+      'w-full',
+      {
+        ['ml-5']: titleExists,
+      },
+    ];
+  });
 
-      const toolbarList = computed(() => {
-        const { checkable } = props;
-        const defaultToolbarList = [
-          { label: t('component.tree.expandAll'), value: ToolbarEnum.EXPAND_ALL },
+  const toolbarList = computed(() => {
+    const { checkable } = props;
+    const defaultToolbarList = [
+      { label: t('component.tree.expandAll'), value: ToolbarEnum.EXPAND_ALL },
+      {
+        label: t('component.tree.unExpandAll'),
+        value: ToolbarEnum.UN_EXPAND_ALL,
+        divider: checkable,
+      },
+    ];
+
+    return checkable
+      ? [
+          { label: t('component.tree.selectAll'), value: ToolbarEnum.SELECT_ALL },
           {
-            label: t('component.tree.unExpandAll'),
-            value: ToolbarEnum.UN_EXPAND_ALL,
+            label: t('component.tree.unSelectAll'),
+            value: ToolbarEnum.UN_SELECT_ALL,
             divider: checkable,
           },
-        ];
+          ...defaultToolbarList,
+          { label: t('component.tree.checkStrictly'), value: ToolbarEnum.CHECK_STRICTLY },
+          { label: t('component.tree.checkUnStrictly'), value: ToolbarEnum.CHECK_UN_STRICTLY },
+        ]
+      : defaultToolbarList;
+  });
 
-        return checkable
-          ? [
-              { label: t('component.tree.selectAll'), value: ToolbarEnum.SELECT_ALL },
-              {
-                label: t('component.tree.unSelectAll'),
-                value: ToolbarEnum.UN_SELECT_ALL,
-                divider: checkable,
-              },
-              ...defaultToolbarList,
-              { label: t('component.tree.checkStrictly'), value: ToolbarEnum.CHECK_STRICTLY },
-              { label: t('component.tree.checkUnStrictly'), value: ToolbarEnum.CHECK_UN_STRICTLY },
-            ]
-          : defaultToolbarList;
-      });
+  function handleMenuClick(e: { key: ToolbarEnum }) {
+    const { key } = e;
+    switch (key) {
+      case ToolbarEnum.SELECT_ALL:
+        props.checkAll?.(true);
+        break;
+      case ToolbarEnum.UN_SELECT_ALL:
+        props.checkAll?.(false);
+        break;
+      case ToolbarEnum.EXPAND_ALL:
+        props.expandAll?.(true);
+        break;
+      case ToolbarEnum.UN_EXPAND_ALL:
+        props.expandAll?.(false);
+        break;
+      case ToolbarEnum.CHECK_STRICTLY:
+        emit('strictly-change', false);
+        break;
+      case ToolbarEnum.CHECK_UN_STRICTLY:
+        emit('strictly-change', true);
+        break;
+    }
+  }
 
-      function handleMenuClick(e: MenuInfo) {
-        const { key } = e;
-        switch (key) {
-          case ToolbarEnum.SELECT_ALL:
-            props.checkAll?.(true);
-            break;
-          case ToolbarEnum.UN_SELECT_ALL:
-            props.checkAll?.(false);
-            break;
-          case ToolbarEnum.EXPAND_ALL:
-            props.expandAll?.(true);
-            break;
-          case ToolbarEnum.UN_EXPAND_ALL:
-            props.expandAll?.(false);
-            break;
-          case ToolbarEnum.CHECK_STRICTLY:
-            emit('strictly-change', false);
-            break;
-          case ToolbarEnum.CHECK_UN_STRICTLY:
-            emit('strictly-change', true);
-            break;
-        }
-      }
+  function emitChange(value?: string): void {
+    emit('search', value);
+  }
 
-      function emitChange(value?: string): void {
-        emit('search', value);
-      }
-      const debounceEmitChange = useDebounceFn(emitChange, 200);
+  const debounceEmitChange = useDebounceFn(emitChange, 200);
 
-      watch(
-        () => searchValue.value,
-        (v) => {
-          debounceEmitChange(v);
-        },
-      );
-      watch(
-        () => props.searchText,
-        (v) => {
-          if (v !== searchValue.value) {
-            searchValue.value = v;
-          }
-        },
-      );
+  watch(
+    () => searchValue.value,
+    (v) => {
+      debounceEmitChange(v);
+    },
+  );
 
-      return { t, toolbarList, handleMenuClick, searchValue, getInputSearchCls };
+  watch(
+    () => props.searchText,
+    (v) => {
+      if (v !== searchValue.value) {
+        searchValue.value = v;
+      }
     },
-  });
+  );
 </script>
-<style lang="less" scoped>
-  .basic-tree-header {
-    border-bottom: 1px solid @border-color-base;
-  }
-</style>

+ 2 - 6
src/components/Tree/src/TreeIcon.ts

@@ -1,14 +1,10 @@
 import type { VNode, FunctionalComponent } from 'vue';
 
 import { h } from 'vue';
-import { isString } from '/@/utils/is';
+import { isString } from '@vue/shared';
 import { Icon } from '/@/components/Icon';
 
-export interface ComponentProps {
-  icon: VNode | string;
-}
-
-export const TreeIcon: FunctionalComponent = ({ icon }: ComponentProps) => {
+export const TreeIcon: FunctionalComponent = ({ icon }: { icon: VNode | string }) => {
   if (!icon) return null;
   if (isString(icon)) {
     return h(Icon, { icon, class: 'mr-1' });

+ 0 - 108
src/components/Tree/src/props.ts

@@ -1,108 +0,0 @@
-import type { PropType } from 'vue';
-import type {
-  ReplaceFields,
-  ActionItem,
-  Keys,
-  CheckKeys,
-  ContextMenuOptions,
-  TreeItem,
-} from './typing';
-import type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
-import type { TreeDataItem } from 'ant-design-vue/es/tree';
-import { propTypes } from '/@/utils/propTypes';
-
-export const basicProps = {
-  value: {
-    type: [Object, Array] as PropType<Keys | CheckKeys>,
-  },
-  renderIcon: {
-    type: Function as PropType<(params: Recordable) => string>,
-  },
-
-  helpMessage: {
-    type: [String, Array] as PropType<string | string[]>,
-    default: '',
-  },
-
-  title: propTypes.string,
-  toolbar: propTypes.bool,
-  search: propTypes.bool,
-  searchValue: propTypes.string,
-  checkStrictly: propTypes.bool,
-  clickRowToExpand: propTypes.bool.def(true),
-  checkable: propTypes.bool.def(false),
-  defaultExpandLevel: {
-    type: [String, Number] as PropType<string | number>,
-    default: '',
-  },
-  defaultExpandAll: propTypes.bool.def(false),
-
-  replaceFields: {
-    type: Object as PropType<ReplaceFields>,
-  },
-
-  treeData: {
-    type: Array as PropType<TreeDataItem[]>,
-  },
-
-  actionList: {
-    type: Array as PropType<ActionItem[]>,
-    default: () => [],
-  },
-
-  expandedKeys: {
-    type: Array as PropType<Keys>,
-    default: () => [],
-  },
-
-  selectedKeys: {
-    type: Array as PropType<Keys>,
-    default: () => [],
-  },
-
-  checkedKeys: {
-    type: Array as PropType<CheckKeys>,
-    default: () => [],
-  },
-
-  beforeRightClick: {
-    type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>,
-    default: null,
-  },
-
-  rightMenuList: {
-    type: Array as PropType<ContextMenuItem[]>,
-  },
-  // 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式)
-  filterFn: {
-    type: Function as PropType<
-      (searchValue: any, node: TreeItem, replaceFields: ReplaceFields) => boolean
-    >,
-    default: null,
-  },
-  // 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启
-  highlight: {
-    type: [Boolean, String] as PropType<Boolean | String>,
-    default: false,
-  },
-  // 搜索完成时自动展开结果
-  expandOnSearch: propTypes.bool.def(false),
-  // 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
-  checkOnSearch: propTypes.bool.def(false),
-  // 搜索完成自动select所有结果
-  selectedOnSearch: propTypes.bool.def(false),
-};
-
-export const treeNodeProps = {
-  actionList: {
-    type: Array as PropType<ActionItem[]>,
-    default: () => [],
-  },
-  replaceFields: {
-    type: Object as PropType<ReplaceFields>,
-  },
-  treeData: {
-    type: Array as PropType<TreeDataItem[]>,
-    default: () => [],
-  },
-};

+ 184 - 0
src/components/Tree/src/tree.ts

@@ -0,0 +1,184 @@
+import type { ExtractPropTypes } from 'vue';
+import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
+
+import { buildProps } from '/@/utils/props';
+
+export enum ToolbarEnum {
+  SELECT_ALL,
+  UN_SELECT_ALL,
+  EXPAND_ALL,
+  UN_EXPAND_ALL,
+  CHECK_STRICTLY,
+  CHECK_UN_STRICTLY,
+}
+
+export const treeEmits = [
+  'update:expandedKeys',
+  'update:selectedKeys',
+  'update:value',
+  'change',
+  'check',
+  'update:searchValue',
+];
+
+export interface TreeState {
+  expandedKeys: KeyType[];
+  selectedKeys: KeyType[];
+  checkedKeys: CheckKeys;
+  checkStrictly: boolean;
+}
+
+export interface FieldNames {
+  children?: string;
+  title?: string;
+  key?: string;
+}
+
+export type KeyType = string | number;
+
+export type CheckKeys =
+  | KeyType[]
+  | { checked: string[] | number[]; halfChecked: string[] | number[] };
+
+export const treeProps = buildProps({
+  value: {
+    type: [Object, Array] as PropType<KeyType[] | CheckKeys>,
+  },
+
+  renderIcon: {
+    type: Function as PropType<(params: Recordable) => string>,
+  },
+
+  helpMessage: {
+    type: [String, Array] as PropType<string | string[]>,
+    default: '',
+  },
+
+  title: {
+    type: String,
+    default: '',
+  },
+  toolbar: Boolean,
+  search: Boolean,
+  searchValue: {
+    type: String,
+    default: '',
+  },
+  checkStrictly: Boolean,
+  clickRowToExpand: {
+    type: Boolean,
+    default: false,
+  },
+  checkable: Boolean,
+  defaultExpandLevel: {
+    type: [String, Number] as PropType<string | number>,
+    default: '',
+  },
+  defaultExpandAll: Boolean,
+
+  fieldNames: {
+    type: Object as PropType<FieldNames>,
+  },
+
+  treeData: {
+    type: Array as PropType<TreeDataItem[]>,
+  },
+
+  actionList: {
+    type: Array as PropType<TreeActionItem[]>,
+    default: () => [],
+  },
+
+  expandedKeys: {
+    type: Array as PropType<KeyType[]>,
+    default: () => [],
+  },
+
+  selectedKeys: {
+    type: Array as PropType<KeyType[]>,
+    default: () => [],
+  },
+
+  checkedKeys: {
+    type: Array as PropType<CheckKeys>,
+    default: () => [],
+  },
+
+  beforeRightClick: {
+    type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>,
+    default: undefined,
+  },
+
+  rightMenuList: {
+    type: Array as PropType<ContextMenuItem[]>,
+  },
+  // 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式)
+  filterFn: {
+    type: Function as PropType<
+      (searchValue: any, node: TreeItem, replaceFields: FieldNames) => boolean
+    >,
+    default: undefined,
+  },
+  // 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启
+  highlight: {
+    type: [Boolean, String] as PropType<Boolean | String>,
+    default: false,
+  },
+  // 搜索完成时自动展开结果
+  expandOnSearch: Boolean,
+  // 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
+  checkOnSearch: Boolean,
+  // 搜索完成自动select所有结果
+  selectedOnSearch: Boolean,
+});
+
+export type TreeProps = ExtractPropTypes<typeof treeProps>;
+
+export interface ContextMenuItem {
+  label: string;
+  icon?: string;
+  disabled?: boolean;
+  handler?: Fn;
+  divider?: boolean;
+  children?: ContextMenuItem[];
+}
+
+export interface ContextMenuOptions {
+  icon?: string;
+  styles?: any;
+  items?: ContextMenuItem[];
+}
+
+export interface TreeItem extends TreeDataItem {
+  icon?: any;
+}
+
+export interface TreeActionItem {
+  render: (record: Recordable) => any;
+  show?: boolean | ((record: Recordable) => boolean);
+}
+
+export interface InsertNodeParams {
+  parentKey: string | null;
+  node: TreeDataItem;
+  list?: TreeDataItem[];
+  push?: 'push' | 'unshift';
+}
+
+export interface TreeActionType {
+  checkAll: (checkAll: boolean) => void;
+  expandAll: (expandAll: boolean) => void;
+  setExpandedKeys: (keys: KeyType[]) => void;
+  getExpandedKeys: () => KeyType[];
+  setSelectedKeys: (keys: KeyType[]) => void;
+  getSelectedKeys: () => KeyType[];
+  setCheckedKeys: (keys: CheckKeys) => void;
+  getCheckedKeys: () => CheckKeys;
+  filterByLevel: (level: number) => void;
+  insertNodeByKey: (opt: InsertNodeParams) => void;
+  insertNodesByKey: (opt: InsertNodeParams) => void;
+  deleteNodeByKey: (key: string) => void;
+  updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void;
+  setSearchValue: (value: string) => void;
+  getSearchValue: () => string;
+}

+ 0 - 56
src/components/Tree/src/typing.ts

@@ -1,56 +0,0 @@
-import type { TreeDataItem, CheckEvent as CheckEventOrigin } from 'ant-design-vue/es/tree/Tree';
-
-import { ContextMenuItem } from '/@/hooks/web/useContextMenu';
-
-export interface ActionItem {
-  render: (record: Recordable) => any;
-  show?: boolean | ((record: Recordable) => boolean);
-}
-
-export interface TreeItem extends TreeDataItem {
-  icon?: any;
-}
-
-export interface ReplaceFields {
-  children?: string;
-  title?: string;
-  key?: string;
-}
-
-export type Keys = (string | number)[];
-export type CheckKeys =
-  | (string | number)[]
-  | { checked: (string | number)[]; halfChecked: (string | number)[] };
-
-export interface TreeActionType {
-  checkAll: (checkAll: boolean) => void;
-  expandAll: (expandAll: boolean) => void;
-  setExpandedKeys: (keys: Keys) => void;
-  getExpandedKeys: () => Keys;
-  setSelectedKeys: (keys: Keys) => void;
-  getSelectedKeys: () => Keys;
-  setCheckedKeys: (keys: CheckKeys) => void;
-  getCheckedKeys: () => CheckKeys;
-  filterByLevel: (level: number) => void;
-  insertNodeByKey: (opt: InsertNodeParams) => void;
-  insertNodesByKey: (opt: InsertNodeParams) => void;
-  deleteNodeByKey: (key: string) => void;
-  updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void;
-  setSearchValue: (value: string) => void;
-  getSearchValue: () => string;
-}
-
-export interface InsertNodeParams {
-  parentKey: string | null;
-  node: TreeDataItem;
-  list?: TreeDataItem[];
-  push?: 'push' | 'unshift';
-}
-
-export interface ContextMenuOptions {
-  icon?: string;
-  styles?: any;
-  items?: ContextMenuItem[];
-}
-
-export type CheckEvent = CheckEventOrigin;

+ 15 - 18
src/components/Tree/src/useTree.ts

@@ -1,4 +1,4 @@
-import type { InsertNodeParams, Keys, ReplaceFields } from './typing';
+import type { InsertNodeParams, KeyType, FieldNames } from './tree';
 import type { Ref, ComputedRef } from 'vue';
 import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
 
@@ -6,14 +6,11 @@ import { cloneDeep } from 'lodash-es';
 import { unref } from 'vue';
 import { forEach } from '/@/utils/helper/treeHelper';
 
-export function useTree(
-  treeDataRef: Ref<TreeDataItem[]>,
-  getReplaceFields: ComputedRef<ReplaceFields>,
-) {
+export function useTree(treeDataRef: Ref<TreeDataItem[]>, getFieldNames: ComputedRef<FieldNames>) {
   function getAllKeys(list?: TreeDataItem[]) {
     const keys: string[] = [];
     const treeData = list || unref(treeDataRef);
-    const { key: keyField, children: childrenField } = unref(getReplaceFields);
+    const { key: keyField, children: childrenField } = unref(getFieldNames);
     if (!childrenField || !keyField) return keys;
 
     for (let index = 0; index < treeData.length; index++) {
@@ -24,14 +21,14 @@ export function useTree(
         keys.push(...(getAllKeys(children) as string[]));
       }
     }
-    return keys as Keys;
+    return keys as KeyType[];
   }
 
   // get keys that can be checked and selected
   function getEnabledKeys(list?: TreeDataItem[]) {
     const keys: string[] = [];
     const treeData = list || unref(treeDataRef);
-    const { key: keyField, children: childrenField } = unref(getReplaceFields);
+    const { key: keyField, children: childrenField } = unref(getFieldNames);
     if (!childrenField || !keyField) return keys;
 
     for (let index = 0; index < treeData.length; index++) {
@@ -42,13 +39,13 @@ export function useTree(
         keys.push(...(getEnabledKeys(children) as string[]));
       }
     }
-    return keys as Keys;
+    return keys as KeyType[];
   }
 
-  function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]): Keys {
-    const keys: Keys = [];
+  function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]) {
+    const keys: KeyType[] = [];
     const treeData = list || unref(treeDataRef);
-    const { key: keyField, children: childrenField } = unref(getReplaceFields);
+    const { key: keyField, children: childrenField } = unref(getFieldNames);
     if (!childrenField || !keyField) return keys;
     for (let index = 0; index < treeData.length; index++) {
       const node = treeData[index];
@@ -64,14 +61,14 @@ export function useTree(
         }
       }
     }
-    return keys as Keys;
+    return keys as KeyType[];
   }
 
   // Update node
   function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) {
     if (!key) return;
     const treeData = list || unref(treeDataRef);
-    const { key: keyField, children: childrenField } = unref(getReplaceFields);
+    const { key: keyField, children: childrenField } = unref(getFieldNames);
 
     if (!childrenField || !keyField) return;
 
@@ -98,7 +95,7 @@ export function useTree(
     for (let index = 0; index < data.length; index++) {
       const item = data[index];
 
-      const { key: keyField, children: childrenField } = unref(getReplaceFields);
+      const { key: keyField, children: childrenField } = unref(getFieldNames);
       const key = keyField ? item[keyField] : '';
       const children = childrenField ? item[childrenField] : [];
       res.push(key);
@@ -120,7 +117,7 @@ export function useTree(
       treeDataRef.value = treeData;
       return;
     }
-    const { key: keyField, children: childrenField } = unref(getReplaceFields);
+    const { key: keyField, children: childrenField } = unref(getFieldNames);
     if (!childrenField || !keyField) return;
 
     forEach(treeData, (treeItem) => {
@@ -145,7 +142,7 @@ export function useTree(
         treeData[push](list[i]);
       }
     } else {
-      const { key: keyField, children: childrenField } = unref(getReplaceFields);
+      const { key: keyField, children: childrenField } = unref(getFieldNames);
       if (!childrenField || !keyField) return;
 
       forEach(treeData, (treeItem) => {
@@ -164,7 +161,7 @@ export function useTree(
   function deleteNodeByKey(key: string, list?: TreeDataItem[]) {
     if (!key) return;
     const treeData = list || unref(treeDataRef);
-    const { key: keyField, children: childrenField } = unref(getReplaceFields);
+    const { key: keyField, children: childrenField } = unref(getFieldNames);
     if (!childrenField || !keyField) return;
 
     for (let index = 0; index < treeData.length; index++) {

+ 49 - 0
src/components/Tree/style/index.less

@@ -0,0 +1,49 @@
+@tree-prefix-cls: ~'@{namespace}-tree';
+
+.@{tree-prefix-cls} {
+  background-color: @component-background;
+
+  .ant-tree-node-content-wrapper {
+    position: relative;
+
+    .ant-tree-title {
+      position: absolute;
+      left: 0;
+      width: 100%;
+    }
+  }
+
+  &__title {
+    position: relative;
+    display: flex;
+    align-items: center;
+    width: 100%;
+    padding-right: 10px;
+
+    &:hover {
+      .@{tree-prefix-cls}__action {
+        visibility: visible;
+      }
+    }
+  }
+
+  &__content {
+    overflow: hidden;
+  }
+
+  &__actions {
+    position: absolute;
+    top: 2px;
+    right: 3px;
+    display: flex;
+  }
+
+  &__action {
+    margin-left: 4px;
+    visibility: hidden;
+  }
+
+  &-header {
+    border-bottom: 1px solid @border-color-base;
+  }
+}

+ 1 - 0
src/components/Tree/style/index.ts

@@ -0,0 +1 @@
+import './index.less';

+ 0 - 7
src/main.ts

@@ -15,13 +15,6 @@ import { setupGlobDirectives } from '/@/directives';
 import { setupI18n } from '/@/locales/setupI18n';
 import { registerGlobComp } from '/@/components/registerGlobComp';
 
-// Importing on demand in local development will increase the number of browser requests by around 20%.
-// This may slow down the browser refresh speed.
-// Therefore, only enable on-demand importing in production environments .
-if (import.meta.env.DEV) {
-  import('ant-design-vue/dist/antd.less');
-}
-
 async function bootstrap() {
   const app = createApp(App);
 

+ 52 - 0
src/utils/bem.ts

@@ -0,0 +1,52 @@
+import { prefixCls } from '/@/settings/designSetting';
+
+type Mod = string | { [key: string]: any };
+type Mods = Mod | Mod[];
+
+export type BEM = ReturnType<typeof createBEM>;
+
+function genBem(name: string, mods?: Mods): string {
+  if (!mods) {
+    return '';
+  }
+
+  if (typeof mods === 'string') {
+    return ` ${name}--${mods}`;
+  }
+
+  if (Array.isArray(mods)) {
+    return mods.reduce<string>((ret, item) => ret + genBem(name, item), '');
+  }
+
+  return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? genBem(name, key) : ''), '');
+}
+
+/**
+ * bem helper
+ * b() // 'button'
+ * b('text') // 'button__text'
+ * b({ disabled }) // 'button button--disabled'
+ * b('text', { disabled }) // 'button__text button__text--disabled'
+ * b(['disabled', 'primary']) // 'button button--disabled button--primary'
+ */
+export function buildBEM(name: string) {
+  return (el?: Mods, mods?: Mods): Mods => {
+    if (el && typeof el !== 'string') {
+      mods = el;
+      el = '';
+    }
+
+    el = el ? `${name}__${el}` : name;
+
+    return `${el}${genBem(el, mods)}`;
+  };
+}
+
+export function createBEM(name: string) {
+  return [buildBEM(`${prefixCls}-${name}`)];
+}
+
+export function createNamespace(name: string) {
+  const prefixedName = `${prefixCls}-${name}`;
+  return [prefixedName, buildBEM(prefixedName)] as const;
+}

+ 185 - 0
src/utils/props.ts

@@ -0,0 +1,185 @@
+// copy from element-plus
+
+import { warn } from 'vue';
+import { isObject } from '@vue/shared';
+import { fromPairs } from 'lodash-es';
+import type { ExtractPropTypes, PropType } from '@vue/runtime-core';
+import type { Mutable } from './types';
+
+const wrapperKey = Symbol();
+export type PropWrapper<T> = { [wrapperKey]: T };
+
+export const propKey = Symbol();
+
+type ResolveProp<T> = ExtractPropTypes<{
+  key: { type: T; required: true };
+}>['key'];
+type ResolvePropType<T> = ResolveProp<T> extends { type: infer V } ? V : ResolveProp<T>;
+type ResolvePropTypeWithReadonly<T> = Readonly<T> extends Readonly<Array<infer A>>
+  ? ResolvePropType<A[]>
+  : ResolvePropType<T>;
+
+type IfUnknown<T, V> = [unknown] extends [T] ? V : T;
+
+export type BuildPropOption<T, D extends BuildPropType<T, V, C>, R, V, C> = {
+  type?: T;
+  values?: readonly V[];
+  required?: R;
+  default?: R extends true
+    ? never
+    : D extends Record<string, unknown> | Array<any>
+    ? () => D
+    : (() => D) | D;
+  validator?: ((val: any) => val is C) | ((val: any) => boolean);
+};
+
+type _BuildPropType<T, V, C> =
+  | (T extends PropWrapper<unknown>
+      ? T[typeof wrapperKey]
+      : [V] extends [never]
+      ? ResolvePropTypeWithReadonly<T>
+      : never)
+  | V
+  | C;
+export type BuildPropType<T, V, C> = _BuildPropType<
+  IfUnknown<T, never>,
+  IfUnknown<V, never>,
+  IfUnknown<C, never>
+>;
+
+type _BuildPropDefault<T, D> = [T] extends [
+  // eslint-disable-next-line @typescript-eslint/ban-types
+  Record<string, unknown> | Array<any> | Function,
+]
+  ? D
+  : D extends () => T
+  ? ReturnType<D>
+  : D;
+
+export type BuildPropDefault<T, D, R> = R extends true
+  ? { readonly default?: undefined }
+  : {
+      readonly default: Exclude<D, undefined> extends never
+        ? undefined
+        : Exclude<_BuildPropDefault<T, D>, undefined>;
+    };
+export type BuildPropReturn<T, D, R, V, C> = {
+  readonly type: PropType<BuildPropType<T, V, C>>;
+  readonly required: IfUnknown<R, false>;
+  readonly validator: ((val: unknown) => boolean) | undefined;
+  [propKey]: true;
+} & BuildPropDefault<BuildPropType<T, V, C>, IfUnknown<D, never>, IfUnknown<R, false>>;
+
+/**
+ * @description Build prop. It can better optimize prop types
+ * @description 生成 prop,能更好地优化类型
+ * @example
+  // limited options
+  // the type will be PropType<'light' | 'dark'>
+  buildProp({
+    type: String,
+    values: ['light', 'dark'],
+  } as const)
+  * @example
+  // limited options and other types
+  // the type will be PropType<'small' | 'medium' | number>
+  buildProp({
+    type: [String, Number],
+    values: ['small', 'medium'],
+    validator: (val: unknown): val is number => typeof val === 'number',
+  } as const)
+  @link see more: https://github.com/element-plus/element-plus/pull/3341
+ */
+export function buildProp<
+  T = never,
+  D extends BuildPropType<T, V, C> = never,
+  R extends boolean = false,
+  V = never,
+  C = never,
+>(option: BuildPropOption<T, D, R, V, C>, key?: string): BuildPropReturn<T, D, R, V, C> {
+  // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
+  if (!isObject(option) || !!option[propKey]) return option as any;
+
+  const { values, required, default: defaultValue, type, validator } = option;
+
+  const _validator =
+    values || validator
+      ? (val: unknown) => {
+          let valid = false;
+          let allowedValues: unknown[] = [];
+
+          if (values) {
+            allowedValues = [...values, defaultValue];
+            valid ||= allowedValues.includes(val);
+          }
+          if (validator) valid ||= validator(val);
+
+          if (!valid && allowedValues.length > 0) {
+            const allowValuesText = [...new Set(allowedValues)]
+              .map((value) => JSON.stringify(value))
+              .join(', ');
+            warn(
+              `Invalid prop: validation failed${
+                key ? ` for prop "${key}"` : ''
+              }. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`,
+            );
+          }
+          return valid;
+        }
+      : undefined;
+
+  return {
+    type:
+      typeof type === 'object' && Object.getOwnPropertySymbols(type).includes(wrapperKey)
+        ? type[wrapperKey]
+        : type,
+    required: !!required,
+    default: defaultValue,
+    validator: _validator,
+    [propKey]: true,
+  } as unknown as BuildPropReturn<T, D, R, V, C>;
+}
+
+type NativePropType = [((...args: any) => any) | { new (...args: any): any } | undefined | null];
+
+export const buildProps = <
+  O extends {
+    [K in keyof O]: O[K] extends BuildPropReturn<any, any, any, any, any>
+      ? O[K]
+      : [O[K]] extends NativePropType
+      ? O[K]
+      : O[K] extends BuildPropOption<infer T, infer D, infer R, infer V, infer C>
+      ? D extends BuildPropType<T, V, C>
+        ? BuildPropOption<T, D, R, V, C>
+        : never
+      : never;
+  },
+>(
+  props: O,
+) =>
+  fromPairs(
+    Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)]),
+  ) as unknown as {
+    [K in keyof O]: O[K] extends { [propKey]: boolean }
+      ? O[K]
+      : [O[K]] extends NativePropType
+      ? O[K]
+      : O[K] extends BuildPropOption<
+          infer T,
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          infer _D,
+          infer R,
+          infer V,
+          infer C
+        >
+      ? BuildPropReturn<T, O[K]['default'], R, V, C>
+      : never;
+  };
+
+export const definePropType = <T>(val: any) => ({ [wrapperKey]: val } as PropWrapper<T>);
+
+export const keyOf = <T>(arr: T) => Object.keys(arr) as Array<keyof T>;
+export const mutable = <T extends readonly any[] | Record<string, unknown>>(val: T) =>
+  val as Mutable<typeof val>;
+
+export const componentSize = ['large', 'medium', 'small', 'mini'] as const;

+ 42 - 0
src/utils/types.ts

@@ -0,0 +1,42 @@
+// copy from element-plus
+
+import type { CSSProperties, Plugin } from 'vue';
+
+type OptionalKeys<T extends Record<string, unknown>> = {
+  [K in keyof T]: T extends Record<K, T[K]> ? never : K;
+}[keyof T];
+
+type RequiredKeys<T extends Record<string, unknown>> = Exclude<keyof T, OptionalKeys<T>>;
+
+type MonoArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg?: T[K]) => void;
+
+type BiArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg: T[K]) => void;
+
+export type EventEmitter<T extends Record<string, unknown>> = MonoArgEmitter<T, OptionalKeys<T>> &
+  BiArgEmitter<T, RequiredKeys<T>>;
+
+export type AnyFunction<T> = (...args: any[]) => T;
+
+export type PartialReturnType<T extends (...args: unknown[]) => unknown> = Partial<ReturnType<T>>;
+
+export type SFCWithInstall<T> = T & Plugin;
+
+export type Nullable<T> = T | null;
+
+export type RefElement = Nullable<HTMLElement>;
+
+export type CustomizedHTMLElement<T> = HTMLElement & T;
+
+export type Indexable<T> = {
+  [key: string]: T;
+};
+
+export type Hash<T> = Indexable<T>;
+
+export type TimeoutHandle = ReturnType<typeof global.setTimeout>;
+
+export type ComponentSize = 'large' | 'medium' | 'small' | 'mini';
+
+export type StyleValue = string | CSSProperties | Array<StyleValue>;
+
+export type Mutable<T> = { -readonly [P in keyof T]: T[P] };

+ 1 - 0
vite.config.ts

@@ -95,6 +95,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
     optimizeDeps: {
       // @iconify/iconify: The dependency is dynamically and virtually loaded by @purge-icons/generated, so it needs to be specified explicitly
       include: [
+        '@vue/shared',
         '@iconify/iconify',
         'ant-design-vue/es/locale/zh_CN',
         'ant-design-vue/es/locale/en_US',

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov