1
0
Эх сурвалжийг харах

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

vben 3 жил өмнө
parent
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",

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 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',

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно