Procházet zdrojové kódy

chore: 更新版本

lanjianrong před 4 roky
rodič
revize
5bf58a47cc
100 změnil soubory, kde provedl 4764 přidání a 3219 odebrání
  1. 33 36
      build/config/themeConfig.ts
  2. 37 44
      build/vite/plugin/index.ts
  3. 10 10
      build/vite/plugin/styleImport.ts
  4. 24 27
      build/vite/plugin/theme.ts
  5. 43 47
      package.json
  6. 9 1
      src/api/sys/user.ts
  7. 14 7
      src/components/Application/index.ts
  8. 31 51
      src/components/Application/src/AppDarkModeToggle.vue
  9. 40 33
      src/components/Application/src/AppLocalePicker.vue
  10. 51 45
      src/components/Application/src/AppLogo.vue
  11. 43 40
      src/components/Application/src/AppProvider.vue
  12. 13 22
      src/components/Application/src/search/AppSearch.vue
  13. 15 23
      src/components/Application/src/search/AppSearchFooter.vue
  14. 4 6
      src/components/Application/src/search/AppSearchKeyItem.vue
  15. 55 50
      src/components/Application/src/search/AppSearchModal.vue
  16. 105 92
      src/components/Application/src/search/useMenuSearch.ts
  17. 7 8
      src/components/Application/src/useAppContext.ts
  18. 3 2
      src/components/Authority/index.ts
  19. 45 0
      src/components/Authority/src/Authority.vue
  20. 7 4
      src/components/Basic/index.ts
  21. 37 28
      src/components/Basic/src/BasicArrow.vue
  22. 61 77
      src/components/Basic/src/BasicHelp.vue
  23. 34 23
      src/components/Basic/src/BasicTitle.vue
  24. 5 3
      src/components/Button/index.ts
  25. 42 30
      src/components/Button/src/BasicButton.vue
  26. 34 32
      src/components/Button/src/PopConfirmButton.vue
  27. 3 2
      src/components/ClickOutSide/index.ts
  28. 26 0
      src/components/ClickOutSide/src/ClickOutSide.vue
  29. 5 14
      src/components/CodeEditor/index.ts
  30. 23 30
      src/components/CodeEditor/src/CodeEditor.vue
  31. 56 73
      src/components/CodeEditor/src/codemirror/CodeMirror.vue
  32. 14 46
      src/components/CodeEditor/src/codemirror/codemirror.css
  33. 6 10
      src/components/CodeEditor/src/json-preview/JsonPreview.vue
  34. 9 5
      src/components/Container/index.ts
  35. 77 76
      src/components/Container/src/LazyContainer.vue
  36. 34 31
      src/components/Container/src/ScrollContainer.vue
  37. 45 51
      src/components/Container/src/collapse/CollapseContainer.vue
  38. 17 14
      src/components/Container/src/collapse/CollapseHeader.vue
  39. 17 0
      src/components/Container/src/typing.ts
  40. 2 2
      src/components/ContextMenu/index.ts
  41. 196 0
      src/components/ContextMenu/src/ContextMenu.vue
  42. 44 42
      src/components/ContextMenu/src/createContextMenu.ts
  43. 35 0
      src/components/ContextMenu/src/typing.ts
  44. 5 3
      src/components/CountDown/index.ts
  45. 36 34
      src/components/CountDown/src/CountButton.vue
  46. 21 22
      src/components/CountDown/src/CountdownInput.vue
  47. 3 3
      src/components/CountTo/index.ts
  48. 110 0
      src/components/CountTo/src/CountTo.vue
  49. 3 3
      src/components/Cropper/index.ts
  50. 120 0
      src/components/Cropper/src/Cropper.vue
  51. 4 4
      src/components/Description/index.ts
  52. 160 0
      src/components/Description/src/Description.vue
  53. 90 115
      src/components/Drawer/src/BasicDrawer.vue
  54. 18 20
      src/components/Drawer/src/components/DrawerFooter.vue
  55. 3 7
      src/components/FlowChart/index.ts
  56. 155 0
      src/components/FlowChart/src/FlowChart.vue
  57. 122 114
      src/components/Form/src/BasicForm.vue
  58. 42 38
      src/components/Form/src/componentMap.ts
  59. 56 57
      src/components/Form/src/components/ApiSelect.vue
  60. 43 64
      src/components/Form/src/components/FormAction.vue
  61. 164 158
      src/components/Form/src/components/FormItem.vue
  62. 23 23
      src/components/Form/src/components/RadioButtonGroup.vue
  63. 67 74
      src/components/Form/src/hooks/useAdvanced.ts
  64. 20 25
      src/components/Form/src/hooks/useAutoFocus.ts
  65. 110 114
      src/components/Form/src/hooks/useFormEvents.ts
  66. 40 45
      src/components/Form/src/hooks/useFormValues.ts
  67. 23 19
      src/components/Form/src/props.ts
  68. 108 105
      src/components/Form/src/types/form.ts
  69. 17 15
      src/components/Form/src/types/index.ts
  70. 5 5
      src/components/Icon/index.ts
  71. 107 0
      src/components/Icon/src/Icon.vue
  72. 64 67
      src/components/Icon/src/IconPicker.vue
  73. 22 28
      src/components/Icon/src/SvgIcon.vue
  74. 4 4
      src/components/Loading/index.ts
  75. 69 0
      src/components/Loading/src/Loading.vue
  76. 24 24
      src/components/Loading/src/createLoading.ts
  77. 3 3
      src/components/Markdown/index.ts
  78. 121 0
      src/components/Markdown/src/Markdown.vue
  79. 88 106
      src/components/Modal/src/BasicModal.vue
  80. 68 85
      src/components/Modal/src/components/ModalWrapper.vue
  81. 78 84
      src/components/Modal/src/hooks/useModal.ts
  82. 61 59
      src/components/Modal/src/types.ts
  83. 76 75
      src/components/Page/src/PageWrapper.vue
  84. 1 1
      src/components/Preview/index.ts
  85. 94 0
      src/components/Preview/src/Preview.vue
  86. 2 2
      src/components/Qrcode/index.ts
  87. 105 0
      src/components/Qrcode/src/Qrcode.vue
  88. 27 22
      src/components/Qrcode/src/types.ts
  89. 3 3
      src/components/Scrollbar/index.ts
  90. 198 0
      src/components/Scrollbar/src/Scrollbar.vue
  91. 47 63
      src/components/Scrollbar/src/bar.ts
  92. 33 33
      src/components/SimpleMenu/src/SimpleMenuTag.vue
  93. 30 35
      src/components/SimpleMenu/src/SimpleSubMenu.vue
  94. 39 41
      src/components/SimpleMenu/src/components/MenuItem.vue
  95. 122 133
      src/components/SimpleMenu/src/components/SubMenuItem.vue
  96. 41 43
      src/components/SimpleMenu/src/components/useMenu.ts
  97. 1 1
      src/components/StrengthMeter/index.ts
  98. 145 0
      src/components/StrengthMeter/src/StrengthMeter.vue
  99. 112 103
      src/components/Table/src/BasicTable.vue
  100. 0 0
      src/components/Table/src/componentMap.ts

+ 33 - 36
build/config/themeConfig.ts

@@ -1,77 +1,74 @@
-import { generate } from '@ant-design/colors';
+import { generate } from '@ant-design/colors'
 
-export const primaryColor = '#0960bd';
+export const primaryColor = '#0960bd'
 
-export const darkMode = 'light';
+export const darkMode = 'light'
 
-type Fn = (...arg: any) => any;
+type Fn = (...arg: any) => any
+
+type GenerateTheme = 'default' | 'dark'
 
 export interface GenerateColorsParams {
-  mixLighten: Fn;
-  mixDarken: Fn;
-  tinycolor: any;
-  color?: string;
+  mixLighten: Fn
+  mixDarken: Fn
+  tinycolor: any
+  color?: string
 }
 
-export function generateAntColors(color: string) {
+export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
   return generate(color, {
-    theme: 'default',
-  });
+    theme
+  })
 }
 
 export function getThemeColors(color?: string) {
-  const tc = color || primaryColor;
-  const colors = generateAntColors(tc);
-  const primary = colors[5];
-  const modeColors = generateAntColors(primary);
+  const tc = color || primaryColor
+  const lightColors = generateAntColors(tc)
+  const primary = lightColors[5]
+  const modeColors = generateAntColors(primary, 'dark')
 
-  return [...colors, ...modeColors];
+  return [...lightColors, ...modeColors]
 }
 
-export function generateColors({
-  color = primaryColor,
-  mixLighten,
-  mixDarken,
-  tinycolor,
-}: GenerateColorsParams) {
-  const arr = new Array(19).fill(0);
+export function generateColors({ color = primaryColor, mixLighten, mixDarken, tinycolor }: GenerateColorsParams) {
+  const arr = new Array(19).fill(0)
   const lightens = arr.map((_t, i) => {
-    return mixLighten(color, i / 5);
-  });
+    return mixLighten(color, i / 5)
+  })
 
   const darkens = arr.map((_t, i) => {
-    return mixDarken(color, i / 5);
-  });
+    return mixDarken(color, i / 5)
+  })
 
   const alphaColors = arr.map((_t, i) => {
     return tinycolor(color)
       .setAlpha(i / 20)
-      .toRgbString();
-  });
+      .toRgbString()
+  })
 
-  const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.'));
+  const shortAlphaColors = alphaColors.map(item => item.replace(/\s/g, '').replace(/0\./g, '.'))
 
   const tinycolorLightens = arr
     .map((_t, i) => {
       return tinycolor(color)
         .lighten(i * 5)
-        .toHexString();
+        .toHexString()
     })
-    .filter((item) => item !== '#ffffff');
+    .filter(item => item !== '#ffffff')
 
   const tinycolorDarkens = arr
     .map((_t, i) => {
       return tinycolor(color)
         .darken(i * 5)
-        .toHexString();
+        .toHexString()
     })
-    .filter((item) => item !== '#000000');
+    .filter(item => item !== '#000000')
   return [
     ...lightens,
     ...darkens,
     ...alphaColors,
     ...shortAlphaColors,
     ...tinycolorDarkens,
-    ...tinycolorLightens,
-  ].filter((item) => !item.includes('-'));
+    ...tinycolorLightens
+  ].filter(item => !item.includes('-'))
 }

+ 37 - 44
build/vite/plugin/index.ts

@@ -1,82 +1,75 @@
-import type { Plugin } from 'vite';
-
-import vue from '@vitejs/plugin-vue';
-import vueJsx from '@vitejs/plugin-vue-jsx';
-import legacy from '@vitejs/plugin-legacy';
-
-import PurgeIcons from 'vite-plugin-purge-icons';
-
-import { configHtmlPlugin } from './html';
-import { configPwaConfig } from './pwa';
-import { configMockPlugin } from './mock';
-import { configCompressPlugin } from './compress';
-import { configStyleImportPlugin } from './styleImport';
-import { configVisualizerConfig } from './visualizer';
-import { configThemePlugin } from './theme';
-import { configImageminPlugin } from './imagemin';
-import { configWindiCssPlugin } from './windicss';
-import { configSvgIconsPlugin } from './svgSprite';
-import { configHmrPlugin } from './hmr';
+import type { Plugin } from 'vite'
+
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import legacy from '@vitejs/plugin-legacy'
+
+import purgeIcons from 'vite-plugin-purge-icons'
+import windiCSS from 'vite-plugin-windicss'
+
+import { configHtmlPlugin } from './html'
+import { configPwaConfig } from './pwa'
+import { configMockPlugin } from './mock'
+import { configCompressPlugin } from './compress'
+import { configStyleImportPlugin } from './styleImport'
+import { configVisualizerConfig } from './visualizer'
+import { configThemePlugin } from './theme'
+import { configImageminPlugin } from './imagemin'
+import { configSvgIconsPlugin } from './svgSprite'
+import { configHmrPlugin } from './hmr'
 
 export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
-  const {
-    VITE_USE_IMAGEMIN,
-    VITE_USE_MOCK,
-    VITE_LEGACY,
-    VITE_BUILD_COMPRESS,
-    VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
-  } = viteEnv;
+  const { VITE_USE_IMAGEMIN, VITE_USE_MOCK, VITE_LEGACY, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } =
+    viteEnv
 
   const vitePlugins: (Plugin | Plugin[])[] = [
     // have to
     vue(),
     // have to
-    vueJsx(),
-  ];
+    vueJsx()
+  ]
 
   // TODO
-  !isBuild && vitePlugins.push(configHmrPlugin());
+  !isBuild && vitePlugins.push(configHmrPlugin())
 
   // @vitejs/plugin-legacy
-  VITE_LEGACY && isBuild && vitePlugins.push(legacy());
+  VITE_LEGACY && isBuild && vitePlugins.push(legacy())
 
   // vite-plugin-html
-  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
+  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
 
   // vite-plugin-svg-icons
-  vitePlugins.push(configSvgIconsPlugin(isBuild));
+  vitePlugins.push(configSvgIconsPlugin(isBuild))
 
   // vite-plugin-windicss
-  vitePlugins.push(configWindiCssPlugin());
+  vitePlugins.push(windiCSS())
 
   // vite-plugin-mock
-  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
+  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild))
 
   // vite-plugin-purge-icons
-  vitePlugins.push(PurgeIcons());
+  vitePlugins.push(purgeIcons())
 
   // vite-plugin-style-import
-  vitePlugins.push(configStyleImportPlugin(isBuild));
+  vitePlugins.push(configStyleImportPlugin(isBuild))
 
   // rollup-plugin-visualizer
-  vitePlugins.push(configVisualizerConfig());
+  vitePlugins.push(configVisualizerConfig())
 
   //vite-plugin-theme
-  vitePlugins.push(configThemePlugin(isBuild));
+  vitePlugins.push(configThemePlugin(isBuild))
 
   // The following plugins only work in the production environment
   if (isBuild) {
     //vite-plugin-imagemin
-    VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin());
+    VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin())
 
     // rollup-plugin-gzip
-    vitePlugins.push(
-      configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE)
-    );
+    vitePlugins.push(configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE))
 
     // vite-plugin-pwa
-    vitePlugins.push(configPwaConfig(viteEnv));
+    vitePlugins.push(configPwaConfig(viteEnv))
   }
 
-  return vitePlugins;
+  return vitePlugins
 }

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

@@ -3,20 +3,20 @@
  * https://github.com/anncwb/vite-plugin-style-import
  */
 
-import styleImport from 'vite-plugin-style-import';
+import styleImport from 'vite-plugin-style-import'
 
 export function configStyleImportPlugin(isBuild: boolean) {
-  if (!isBuild) return [];
-  const pwaPlugin = styleImport({
+  if (!isBuild) return []
+  const styleImportPlugin = styleImport({
     libs: [
       {
         libraryName: 'ant-design-vue',
         esModule: true,
-        resolveStyle: (name) => {
-          return `ant-design-vue/es/${name}/style/index`;
-        },
-      },
-    ],
-  });
-  return pwaPlugin;
+        resolveStyle: name => {
+          return `ant-design-vue/es/${name}/style/index`
+        }
+      }
+    ]
+  })
+  return styleImportPlugin
 }

+ 24 - 27
build/vite/plugin/theme.ts

@@ -2,45 +2,42 @@
  * Vite plugin for website theme color switching
  * https://github.com/anncwb/vite-plugin-theme
  */
-import type { Plugin } from 'vite';
-import path from 'path';
-import {
-  viteThemePlugin,
-  antdDarkThemePlugin,
-  mixLighten,
-  mixDarken,
-  tinycolor,
-} from 'vite-plugin-theme';
-import { getThemeColors, generateColors } from '../../config/themeConfig';
-import { generateModifyVars } from '../../generate/generateModifyVars';
+import type { Plugin } from 'vite'
+import path from 'path'
+import { viteThemePlugin, antdDarkThemePlugin, mixLighten, mixDarken, tinycolor } from 'vite-plugin-theme'
+import { getThemeColors, generateColors } from '../../config/themeConfig'
+import { generateModifyVars } from '../../generate/generateModifyVars'
 
 export function configThemePlugin(isBuild: boolean): Plugin[] {
   const colors = generateColors({
     mixDarken,
     mixLighten,
-    tinycolor,
-  });
+    tinycolor
+  })
   const plugin = [
     viteThemePlugin({
-      resolveSelector: (s) => {
-        s = s.trim();
+      resolveSelector: s => {
+        s = s.trim()
         switch (s) {
           case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
-            return '.ant-steps-item-icon > .ant-steps-icon';
+            return '.ant-steps-item-icon > .ant-steps-icon'
+          case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
+          case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
+          case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
+            return s
           case '.ant-steps-item-icon > .ant-steps-icon':
-            return s;
+            return s
         }
-
-        return `[data-theme] ${s}`;
+        return `[data-theme] ${s}`
       },
-      colorVariables: [...getThemeColors(), ...colors],
+      colorVariables: [...getThemeColors(), ...colors]
     }),
     antdDarkThemePlugin({
       preloadFiles: [
         path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.less'),
-        path.resolve(process.cwd(), 'src/design/index.less'),
+        path.resolve(process.cwd(), 'src/design/index.less')
       ],
-      filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
+      filter: id => (isBuild ? !id.endsWith('antd.less') : true),
       // extractCss: false,
       darkModifyVars: {
         ...generateModifyVars(true),
@@ -53,10 +50,10 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
         'border-color-base': '#303030',
         // 'border-color-split': '#30363d',
         'item-active-bg': '#111b26',
-        'app-content-background': 'rgb(255 255 255 / 4%)',
-      },
-    }),
-  ];
+        'app-content-background': 'rgb(255 255 255 / 4%)'
+      }
+    })
+  ]
 
-  return (plugin as unknown) as Plugin[];
+  return plugin as unknown as Plugin[]
 }

+ 43 - 47
package.json

@@ -34,13 +34,13 @@
     "postinstall": "npm run install:husky"
   },
   "dependencies": {
-    "@iconify/iconify": "^2.0.0-rc.6",
-    "@vueuse/core": "^4.9.0",
+    "@iconify/iconify": "^2.0.1",
+    "@vueuse/core": "^5.0.2",
     "@zxcvbn-ts/core": "^0.3.0",
-    "ant-design-vue": "^2.1.2",
+    "ant-design-vue": "2.1.2",
     "axios": "^0.21.1",
     "crypto-js": "^4.0.0",
-    "echarts": "^5.1.0",
+    "echarts": "^5.1.2",
     "lodash-es": "^4.17.21",
     "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
@@ -49,78 +49,74 @@
     "qrcode": "^1.4.4",
     "sortablejs": "^1.13.0",
     "vue": "3.0.11",
-    "vue-i18n": "9.0.0",
-    "vue-router": "^4.0.6",
+    "vue-i18n": "9.1.6",
+    "vue-router": "^4.0.8",
     "vue-types": "^3.0.2"
   },
   "devDependencies": {
-    "@commitlint/cli": "^12.1.1",
-    "@commitlint/config-conventional": "^12.1.1",
-    "@iconify/json": "^1.1.331",
+    "@commitlint/cli": "^12.1.4",
+    "@commitlint/config-conventional": "^12.1.4",
+    "@iconify/json": "^1.1.354",
     "@purge-icons/generated": "^0.7.0",
-    "@types/codemirror": "^0.0.109",
+    "@types/codemirror": "^5.60.0",
     "@types/crypto-js": "^4.0.1",
     "@types/fs-extra": "^9.0.11",
     "@types/inquirer": "^7.3.1",
     "@types/lodash-es": "^4.17.4",
     "@types/mockjs": "^1.0.3",
+    "@types/node": "^15.12.2",
     "@types/nprogress": "^0.2.0",
     "@types/qrcode": "^1.4.0",
     "@types/qs": "^6.9.6",
     "@types/sortablejs": "^1.10.6",
-    "@typescript-eslint/eslint-plugin": "^4.22.0",
-    "@typescript-eslint/parser": "^4.22.0",
-    "@vitejs/plugin-legacy": "^1.3.2",
-    "@vitejs/plugin-vue": "^1.2.1",
-    "@vitejs/plugin-vue-jsx": "^1.1.3",
+    "@typescript-eslint/eslint-plugin": "^4.26.1",
+    "@typescript-eslint/parser": "^4.26.1",
+    "@vitejs/plugin-legacy": "^1.4.1",
+    "@vitejs/plugin-vue": "^1.2.3",
+    "@vitejs/plugin-vue-jsx": "^1.1.5",
     "@vue/compiler-sfc": "3.0.11",
-    "autoprefixer": "^10.2.5",
-    "body-parser": "^1.19.0",
-    "commitizen": "^4.2.3",
+    "autoprefixer": "^10.2.6",
+    "commitizen": "^4.2.4",
     "conventional-changelog-cli": "^2.1.1",
     "cross-env": "^7.0.3",
-    "dotenv": "^8.2.0",
-    "eslint": "^7.25.0",
-    "eslint-config-prettier": "^8.2.0",
+    "dotenv": "^10.0.0",
+    "eslint": "^7.28.0",
+    "eslint-config-prettier": "^8.3.0",
     "eslint-define-config": "^1.0.8",
-    "eslint-plugin-html": "^6.1.2",
-    "eslint-plugin-javascript": "^1.3.4",
-    "eslint-plugin-jsx": "^0.1.0",
     "eslint-plugin-prettier": "^3.4.0",
-    "eslint-plugin-typescript": "^0.14.0",
-    "eslint-plugin-vue": "^7.9.0",
-    "esno": "^0.5.0",
-    "fs-extra": "^9.1.0",
+    "eslint-plugin-vue": "^7.10.0",
+    "esno": "^0.7.1",
+    "fs-extra": "^10.0.0",
     "http-server": "^0.12.3",
     "husky": "^6.0.0",
-    "inquirer": "^8.0.0",
+    "inquirer": "^8.1.0",
     "is-ci": "^3.0.0",
     "less": "^4.1.1",
-    "lint-staged": "^10.5.4",
-    "postcss": "^8.2.12",
-    "prettier": "^2.2.1",
+    "lint-staged": "^11.0.0",
+    "postcss": "^8.3.0",
+    "prettier": "^2.3.1",
     "pretty-quick": "^3.1.0",
     "rimraf": "^3.0.2",
-    "rollup-plugin-visualizer": "5.3.4",
-    "stylelint": "^13.12.0",
+    "rollup-plugin-visualizer": "5.5.0",
+    "stylelint": "^13.13.1",
     "stylelint-config-prettier": "^8.0.2",
-    "stylelint-config-standard": "^21.0.0",
+    "stylelint-config-standard": "^22.0.0",
     "stylelint-order": "^4.1.0",
-    "ts-node": "^9.1.1",
-    "typescript": "4.2.4",
-    "vite": "2.1.5",
-    "vite-plugin-compression": "^0.2.4",
+    "ts-node": "^10.0.0",
+    "typescript": "4.3.2",
+    "vite": "2.3.3",
+    "vite-plugin-compression": "^0.2.5",
     "vite-plugin-html": "^2.0.7",
-    "vite-plugin-imagemin": "^0.3.0",
-    "vite-plugin-mock": "^2.5.0",
+    "vite-plugin-imagemin": "^0.3.2",
+    "vite-plugin-mock": "^2.7.1",
     "vite-plugin-purge-icons": "^0.7.0",
-    "vite-plugin-pwa": "^0.7.2",
-    "vite-plugin-style-import": "^0.10.0",
-    "vite-plugin-svg-icons": "^0.4.3",
-    "vite-plugin-theme": "^0.7.1",
-    "vite-plugin-windicss": "0.14.6",
+    "vite-plugin-pwa": "^0.7.3",
+    "vite-plugin-style-import": "^0.10.1",
+    "vite-plugin-svg-icons": "^0.7.0",
+    "vite-plugin-theme": "^0.8.1",
+    "vite-plugin-windicss": "^1.0.3",
     "vue-eslint-parser": "^7.6.0",
-    "vue-tsc": "^0.0.25"
+    "vue-tsc": "^0.1.7"
   },
   "resolutions": {
     "//": "Used to install imagemin dependencies, because imagemin may not be installed in China.If it is abroad, you can delete it",

+ 9 - 1
src/api/sys/user.ts

@@ -13,7 +13,8 @@ enum Api {
   Logout = '/backstage/login/out',
   GetUserInfoById = '/login/project/name',
   GetPermCodeByUserId = '/getPermCodeByUserId',
-  GetToken = '/login/project/name'
+  GetToken = '/login/project/name',
+  GetAccountGroupList = '/projectAccount/group'
 }
 
 /**
@@ -60,3 +61,10 @@ export function logoutApi() {
     url: Api.Logout
   })
 }
+
+/** 获取账号组 */
+export function getAccountGroupList() {
+  return defHttp.get({
+    url: Api.GetAccountGroupList
+  })
+}

+ 14 - 7
src/components/Application/index.ts

@@ -1,8 +1,15 @@
-import AppLogo from './src/AppLogo.vue';
-import AppProvider from './src/AppProvider.vue';
-import AppSearch from './src/search/AppSearch.vue';
-import AppLocalePicker from './src/AppLocalePicker.vue';
-import AppDarkModeToggle from './src/AppDarkModeToggle.vue';
+import { withInstall } from '/@/utils'
 
-export { useAppProviderContext } from './src/useAppContext';
-export { AppLogo, AppProvider, AppSearch, AppLocalePicker, AppDarkModeToggle };
+import appLogo from './src/AppLogo.vue'
+import appProvider from './src/AppProvider.vue'
+import appSearch from './src/search/AppSearch.vue'
+import appLocalePicker from './src/AppLocalePicker.vue'
+import appDarkModeToggle from './src/AppDarkModeToggle.vue'
+
+export { useAppProviderContext } from './src/useAppContext'
+
+export const AppLogo = withInstall(appLogo)
+export const AppProvider = withInstall(appProvider)
+export const AppSearch = withInstall(appSearch)
+export const AppLocalePicker = withInstall(appLocalePicker)
+export const AppDarkModeToggle = withInstall(appDarkModeToggle)

+ 31 - 51
src/components/Application/src/AppDarkModeToggle.vue

@@ -1,64 +1,55 @@
 <template>
-  <div
-    v-if="getShowDarkModeToggle"
-    :class="[
-      prefixCls,
-      {
-        [`${prefixCls}--dark`]: isDark,
-      },
-    ]"
-    @click="toggleDarkMode"
-  >
+  <div v-if="getShowDarkModeToggle" :class="getClass" @click="toggleDarkMode">
     <div :class="`${prefixCls}-inner`"> </div>
     <SvgIcon size="14" name="sun" />
     <SvgIcon size="14" name="moon" />
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent, computed } from 'vue';
-
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { SvgIcon } from '/@/components/Icon';
-  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-  import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
-  import { updateDarkTheme } from '/@/logics/theme/dark';
-
-  import { ThemeEnum } from '/@/enums/appEnum';
+  import { defineComponent, computed, unref } from 'vue'
+  import { SvgIcon } from '/@/components/Icon'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting'
+  import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'
+  import { updateDarkTheme } from '/@/logics/theme/dark'
+  import { ThemeEnum } from '/@/enums/appEnum'
 
   export default defineComponent({
     name: 'DarkModeToggle',
     components: { SvgIcon },
-    // props: {
-    //   size: {
-    //     type: String,
-    //     default: 'default',
-    //     validate: (val) => ['default', 'large'].includes(val),
-    //   },
-    // },
     setup() {
-      const { prefixCls } = useDesign('dark-mode-toggle');
-      const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
-      const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
+      const { prefixCls } = useDesign('dark-switch')
+      const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting()
+
+      const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK)
+
+      const getClass = computed(() => [
+        prefixCls,
+        {
+          [`${prefixCls}--dark`]: unref(isDark)
+        }
+      ])
+
       function toggleDarkMode() {
-        const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
-        setDarkMode(darkMode);
-        updateDarkTheme(darkMode);
-        updateHeaderBgColor();
-        updateSidebarBgColor();
+        const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK
+        setDarkMode(darkMode)
+        updateDarkTheme(darkMode)
+        updateHeaderBgColor()
+        updateSidebarBgColor()
       }
 
       return {
+        getClass,
         isDark,
         prefixCls,
         toggleDarkMode,
-        getShowDarkModeToggle,
-      };
-    },
-  });
+        getShowDarkModeToggle
+      }
+    }
+  })
 </script>
 <style lang="less" scoped>
-  @prefix-cls: ~'@{namespace}-dark-mode-toggle';
+  @prefix-cls: ~'@{namespace}-dark-switch';
 
   html[data-theme='dark'] {
     .@{prefix-cls} {
@@ -95,16 +86,5 @@
         transform: translateX(calc(100% + 2px));
       }
     }
-
-    // &--large {
-    //   width: 70px;
-    //   height: 34px;
-    //   padding: 0 10px;
-
-    //   .@{prefix-cls}-inner {
-    //     width: 26px;
-    //     height: 26px;
-    //   }
-    // }
   }
 </style>

+ 40 - 33
src/components/Application/src/AppLocalePicker.vue

@@ -13,60 +13,67 @@
   >
     <span class="cursor-pointer flex items-center">
       <Icon icon="ion:language" />
-      <span v-if="showText" class="ml-1">{{ getLangText }}</span>
+      <span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
     </span>
   </Dropdown>
 </template>
 <script lang="ts">
-  import type { LocaleType } from '/#/config';
-  import type { DropMenu } from '/@/components/Dropdown';
+  import type { LocaleType } from '/#/config'
+  import type { DropMenu } from '/@/components/Dropdown'
+  import { defineComponent, ref, watchEffect, unref, computed } from 'vue'
+  import { Dropdown } from '/@/components/Dropdown'
+  import { Icon } from '/@/components/Icon'
+  import { useLocale } from '/@/locales/useLocale'
+  import { localeList } from '/@/settings/localeSetting'
 
-  import { defineComponent, ref, watchEffect, unref, computed } from 'vue';
-  import { Dropdown } from '/@/components/Dropdown';
-  import Icon from '/@/components/Icon';
-
-  import { useLocale } from '/@/locales/useLocale';
-  import { localeList } from '/@/settings/localeSetting';
-  import { propTypes } from '/@/utils/propTypes';
+  const props = {
+    /**
+     * Whether to display text
+     */
+    showText: { type: Boolean, default: true },
+    /**
+     * Whether to refresh the interface when changing
+     */
+    reload: { type: Boolean }
+  }
 
   export default defineComponent({
     name: 'AppLocalPicker',
     components: { Dropdown, Icon },
-    props: {
-      // Whether to display text
-      showText: propTypes.bool.def(true),
-      // Whether to refresh the interface when changing
-      reload: propTypes.bool,
-    },
+    props,
     setup(props) {
-      const selectedKeys = ref<string[]>([]);
+      const selectedKeys = ref<string[]>([])
 
-      const { changeLocale, getLocale } = useLocale();
+      const { changeLocale, getLocale } = useLocale()
 
-      const getLangText = computed(() => {
-        const key = selectedKeys.value[0];
-        if (!key) return '';
-        return localeList.find((item) => item.event === key)?.text;
-      });
+      const getLocaleText = computed(() => {
+        const key = selectedKeys.value[0]
+        if (!key) {
+          return ''
+        }
+        return localeList.find(item => item.event === key)?.text
+      })
 
       watchEffect(() => {
-        selectedKeys.value = [unref(getLocale)];
-      });
+        selectedKeys.value = [unref(getLocale)]
+      })
 
       async function toggleLocale(lang: LocaleType | string) {
-        await changeLocale(lang as LocaleType);
-        selectedKeys.value = [lang as string];
-        props.reload && location.reload();
+        await changeLocale(lang as LocaleType)
+        selectedKeys.value = [lang as string]
+        props.reload && location.reload()
       }
 
       function handleMenuEvent(menu: DropMenu) {
-        if (unref(getLocale) === menu.event) return;
-        toggleLocale(menu.event as string);
+        if (unref(getLocale) === menu.event) {
+          return
+        }
+        toggleLocale(menu.event as string)
       }
 
-      return { localeList, handleMenuEvent, selectedKeys, getLangText };
-    },
-  });
+      return { localeList, handleMenuEvent, selectedKeys, getLocaleText }
+    }
+  })
 </script>
 
 <style lang="less">

+ 51 - 45
src/components/Application/src/AppLogo.vue

@@ -3,66 +3,72 @@
  * @Description: logo component
 -->
 <template>
-  <div
-    class="anticon"
-    :class="[prefixCls, theme, { 'collapsed-show-title': getCollapsedShowTitle }]"
-    @click="handleGoHome"
-  >
+  <div class="anticon" :class="getAppLogoClass" @click="goHome">
     <img src="../../../assets/images/logo.png" />
-    <div
-      class="ml-2 truncate md:opacity-100"
-      :class="[
-        `${prefixCls}__title`,
-        {
-          'xs:opacity-0': !alwaysShowTitle,
-        },
-      ]"
-      v-show="showTitle"
-    >
+    <div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
       {{ title }}
     </div>
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent } from 'vue';
+  import { defineComponent, computed, unref } from 'vue'
+  import { useGlobSetting } from '/@/hooks/setting'
+  import { useGo } from '/@/hooks/web/usePage'
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { PageEnum } from '/@/enums/pageEnum'
 
-  import { useGlobSetting } from '/@/hooks/setting';
-  import { useGo } from '/@/hooks/web/usePage';
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { PageEnum } from '/@/enums/pageEnum';
-  import { propTypes } from '/@/utils/propTypes';
+  const props = {
+    /**
+     * The theme of the current parent component
+     */
+    theme: { type: String, validator: v => ['light', 'dark'].includes(v) },
+    /**
+     * Whether to show title
+     */
+    showTitle: { type: Boolean, default: true },
+    /**
+     * The title is also displayed when the menu is collapsed
+     */
+    alwaysShowTitle: { type: Boolean }
+  }
 
   export default defineComponent({
     name: 'AppLogo',
-    props: {
-      /**
-       * The theme of the current parent component
-       */
-      theme: propTypes.oneOf(['light', 'dark']),
-      // Whether to show title
-      showTitle: propTypes.bool.def(true),
-      alwaysShowTitle: propTypes.bool.def(false),
-    },
-    setup() {
-      const { prefixCls } = useDesign('app-logo');
-      const { getCollapsedShowTitle } = useMenuSetting();
-      const { title } = useGlobSetting();
-      const go = useGo();
+    props: props,
+    setup(props) {
+      const { prefixCls } = useDesign('app-logo')
+      const { getCollapsedShowTitle } = useMenuSetting()
+      const { title } = useGlobSetting()
+      const go = useGo()
+
+      const getAppLogoClass = computed(() => [
+        prefixCls,
+        props.theme,
+        { 'collapsed-show-title': unref(getCollapsedShowTitle) }
+      ])
+
+      const getTitleClass = computed(() => [
+        `${prefixCls}__title`,
+        {
+          'xs:opacity-0': !props.alwaysShowTitle
+        }
+      ])
 
-      function handleGoHome(): void {
-        go(PageEnum.BASE_HOME);
+      function goHome() {
+        go(PageEnum.BASE_HOME)
       }
 
       return {
-        handleGoHome,
-        title,
-        prefixCls,
+        getAppLogoClass,
+        getTitleClass,
         getCollapsedShowTitle,
-      };
-    },
-  });
+        goHome,
+        title,
+        prefixCls
+      }
+    }
+  })
 </script>
 <style lang="less" scoped>
   @prefix-cls: ~'@{namespace}-app-logo';

+ 43 - 40
src/components/Application/src/AppProvider.vue

@@ -1,74 +1,77 @@
 <script lang="ts">
-  import { defineComponent, toRefs, ref, unref } from 'vue';
+  import { defineComponent, toRefs, ref, unref } from 'vue'
+  import { createAppProviderContext } from './useAppContext'
+  import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'
+  import { prefixCls } from '/@/settings/designSetting'
+  import { useAppStore } from '/@/store/modules/app'
+  import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'
 
-  import { createAppProviderContext } from './useAppContext';
-
-  import { prefixCls } from '/@/settings/designSetting';
-  import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
-  import { propTypes } from '/@/utils/propTypes';
-  import { useAppStore } from '/@/store/modules/app';
-  import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
+  const props = {
+    /**
+     * class style prefix
+     */
+    prefixCls: { type: String, default: prefixCls }
+  }
 
   export default defineComponent({
     name: 'AppProvider',
     inheritAttrs: false,
-    props: {
-      prefixCls: propTypes.string.def(prefixCls),
-    },
+    props,
     setup(props, { slots }) {
-      const isMobile = ref(false);
-      const isSetState = ref(false);
+      const isMobile = ref(false)
+      const isSetState = ref(false)
 
-      const appStore = useAppStore();
+      const appStore = useAppStore()
 
+      // Monitor screen breakpoint information changes
       createBreakpointListen(({ screenMap, sizeEnum, width }) => {
-        const lgWidth = screenMap.get(sizeEnum.LG);
+        const lgWidth = screenMap.get(sizeEnum.LG)
         if (lgWidth) {
-          isMobile.value = width.value - 1 < lgWidth;
+          isMobile.value = width.value - 1 < lgWidth
         }
-        handleRestoreState();
-      });
+        handleRestoreState()
+      })
+
+      const { prefixCls } = toRefs(props)
 
-      const { prefixCls } = toRefs(props);
-      createAppProviderContext({ prefixCls, isMobile });
+      // Inject variables into the global
+      createAppProviderContext({ prefixCls, isMobile })
 
+      /**
+       * Used to maintain the state before the window changes
+       */
       function handleRestoreState() {
         if (unref(isMobile)) {
           if (!unref(isSetState)) {
-            isSetState.value = true;
+            isSetState.value = true
             const {
-              menuSetting: {
-                type: menuType,
-                mode: menuMode,
-                collapsed: menuCollapsed,
-                split: menuSplit,
-              },
-            } = appStore.getProjectConfig;
+              menuSetting: { type: menuType, mode: menuMode, collapsed: menuCollapsed, split: menuSplit }
+            } = appStore.getProjectConfig
             appStore.setProjectConfig({
               menuSetting: {
                 type: MenuTypeEnum.SIDEBAR,
                 mode: MenuModeEnum.INLINE,
-                split: false,
-              },
-            });
-            appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit });
+                split: false
+              }
+            })
+            appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit })
           }
         } else {
           if (unref(isSetState)) {
-            isSetState.value = false;
-            const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo;
+            isSetState.value = false
+            const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo
             appStore.setProjectConfig({
               menuSetting: {
                 type: menuType,
                 mode: menuMode,
                 collapsed: menuCollapsed,
-                split: menuSplit,
-              },
-            });
+                split: menuSplit
+              }
+            })
           }
         }
       }
-      return () => slots.default?.();
-    },
-  });
+      return () => slots.default?.()
+    }
+  })
 </script>

+ 13 - 22
src/components/Application/src/search/AppSearch.vue

@@ -1,42 +1,33 @@
 <script lang="tsx">
-  import { defineComponent, ref, unref } from 'vue';
-
-  import { Tooltip } from 'ant-design-vue';
-  import { SearchOutlined } from '@ant-design/icons-vue';
-  import AppSearchModal from './AppSearchModal.vue';
-
-  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-  import { useI18n } from '/@/hooks/web/useI18n';
+  import { defineComponent, ref, unref } from 'vue'
+  import { Tooltip } from 'ant-design-vue'
+  import { SearchOutlined } from '@ant-design/icons-vue'
+  import AppSearchModal from './AppSearchModal.vue'
+  import { useI18n } from '/@/hooks/web/useI18n'
 
   export default defineComponent({
     name: 'AppSearch',
-    components: { AppSearchModal, Tooltip },
     setup() {
-      const showModal = ref(false);
-
-      const { getShowSearch } = useHeaderSetting();
-      const { t } = useI18n();
+      const showModal = ref(false)
+      const { t } = useI18n()
 
       function changeModal(show: boolean) {
-        showModal.value = show;
+        showModal.value = show
       }
 
       return () => {
-        if (!unref(getShowSearch)) {
-          return null;
-        }
         return (
           <div class="p-1" onClick={changeModal.bind(null, true)}>
             <Tooltip>
               {{
                 title: () => t('common.searchText'),
-                default: () => <SearchOutlined />,
+                default: () => <SearchOutlined />
               }}
             </Tooltip>
             <AppSearchModal onClose={changeModal.bind(null, false)} visible={unref(showModal)} />
           </div>
-        );
-      };
-    },
-  });
+        )
+      }
+    }
+  })
 </script>

+ 15 - 23
src/components/Application/src/search/AppSearchFooter.vue

@@ -1,36 +1,29 @@
 <template>
   <div :class="`${prefixCls}`">
-    <AppSearchKeyItem :class="`${prefixCls}__item`" icon="ant-design:enter-outlined" />
+    <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ant-design:enter-outlined" />
     <span>{{ t('component.app.toSearch') }}</span>
-
-    <AppSearchKeyItem :class="`${prefixCls}__item`" icon="ion:arrow-up-outline" />
-    <AppSearchKeyItem :class="`${prefixCls}__item`" icon="ion:arrow-down-outline" />
+    <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-up-outline" />
+    <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-down-outline" />
     <span>{{ t('component.app.toNavigate') }}</span>
-    <AppSearchKeyItem :class="`${prefixCls}__item`" icon="mdi:keyboard-esc" />
-
+    <AppSearchKeyItem :class="`${prefixCls}-item`" icon="mdi:keyboard-esc" />
     <span>{{ t('common.closeText') }}</span>
   </div>
 </template>
 
 <script lang="ts">
-  import { defineComponent } from 'vue';
-
-  import AppSearchKeyItem from './AppSearchKeyItem.vue';
-
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { useI18n } from '/@/hooks/web/useI18n';
+  import { defineComponent } from 'vue'
+  import AppSearchKeyItem from './AppSearchKeyItem.vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { useI18n } from '/@/hooks/web/useI18n'
   export default defineComponent({
     name: 'AppSearchFooter',
     components: { AppSearchKeyItem },
     setup() {
-      const { prefixCls } = useDesign('app-search-footer');
-      const { t } = useI18n();
-      return {
-        prefixCls,
-        t,
-      };
-    },
-  });
+      const { prefixCls } = useDesign('app-search-footer')
+      const { t } = useI18n()
+      return { prefixCls, t }
+    }
+  })
 </script>
 <style lang="less" scoped>
   @prefix-cls: ~'@{namespace}-app-search-footer';
@@ -48,7 +41,7 @@
     align-items: center;
     flex-shrink: 0;
 
-    &__item {
+    &-item {
       display: flex;
       width: 20px;
       height: 18px;
@@ -56,8 +49,7 @@
       margin-right: 0.4em;
       background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
       border-radius: 2px;
-      box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
-        0 1px 2px 1px rgba(30, 35, 90, 0.4);
+      box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4);
       align-items: center;
       justify-content: center;
 

+ 4 - 6
src/components/Application/src/search/AppSearchKeyItem.vue

@@ -4,12 +4,10 @@
   </span>
 </template>
 <script lang="ts">
-  import { defineComponent } from 'vue';
-  import { Icon } from '/@/components/Icon';
+  import { defineComponent } from 'vue'
+  import { Icon } from '/@/components/Icon'
   export default defineComponent({
     components: { Icon },
-    props: {
-      icon: String,
-    },
-  });
+    props: { icon: String }
+  })
 </script>

+ 55 - 50
src/components/Application/src/search/AppSearchModal.vue

@@ -7,11 +7,11 @@
             <Input
               :class="`${prefixCls}-input`"
               :placeholder="t('common.searchText')"
+              ref="inputRef"
               allow-clear
               @change="handleSearch"
             >
               <template #prefix>
-                <!-- <Icon icon="ion:search"/> -->
                 <SearchOutlined />
               </template>
             </Input>
@@ -35,8 +35,8 @@
               :class="[
                 `${prefixCls}-list__item`,
                 {
-                  [`${prefixCls}-list__item--active`]: activeIndex === index,
-                },
+                  [`${prefixCls}-list__item--active`]: activeIndex === index
+                }
               ]"
             >
               <div :class="`${prefixCls}-list__item-icon`">
@@ -57,65 +57,69 @@
   </Teleport>
 </template>
 <script lang="ts">
-  import { defineComponent, computed, unref, ref } from 'vue';
-
-  import { SearchOutlined } from '@ant-design/icons-vue';
-  import { Input } from 'ant-design-vue';
-  import AppSearchFooter from './AppSearchFooter.vue';
-  import Icon from '/@/components/Icon';
-
-  import clickOutside from '/@/directives/clickOutside';
-
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { useRefs } from '/@/hooks/core/useRefs';
-  import { useMenuSearch } from './useMenuSearch';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useAppInject } from '/@/hooks/web/useAppInject';
-
-  import { propTypes } from '/@/utils/propTypes';
+  import { defineComponent, computed, unref, ref, watch, nextTick } from 'vue'
+  import { SearchOutlined } from '@ant-design/icons-vue'
+  import { Input } from 'ant-design-vue'
+  import AppSearchFooter from './AppSearchFooter.vue'
+  import Icon from '/@/components/Icon'
+  import clickOutside from '/@/directives/clickOutside'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { useRefs } from '/@/hooks/core/useRefs'
+  import { useMenuSearch } from './useMenuSearch'
+  import { useI18n } from '/@/hooks/web/useI18n'
+  import { useAppInject } from '/@/hooks/web/useAppInject'
+
+  const props = {
+    visible: { type: Boolean }
+  }
 
   export default defineComponent({
     name: 'AppSearchModal',
     components: { Icon, SearchOutlined, AppSearchFooter, Input },
     directives: {
-      clickOutside,
-    },
-    props: {
-      visible: propTypes.bool,
+      clickOutside
     },
+    props,
     emits: ['close'],
-    setup(_, { emit }) {
-      const scrollWrap = ref<ElRef>(null);
-      const { prefixCls } = useDesign('app-search-modal');
-      const { t } = useI18n();
-      const [refs, setRefs] = useRefs();
-      const { getIsMobile } = useAppInject();
-
-      const {
-        handleSearch,
-        searchResult,
-        keyword,
-        activeIndex,
-        handleEnter,
-        handleMouseenter,
-      } = useMenuSearch(refs, scrollWrap, emit);
+    setup(props, { emit }) {
+      const scrollWrap = ref<ElRef>(null)
+      const inputRef = ref<Nullable<HTMLElement>>(null)
+
+      const { t } = useI18n()
+      const { prefixCls } = useDesign('app-search-modal')
+      const [refs, setRefs] = useRefs()
+      const { getIsMobile } = useAppInject()
 
-      const getIsNotData = computed(() => {
-        return !keyword || unref(searchResult).length === 0;
-      });
+      const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = useMenuSearch(
+        refs,
+        scrollWrap,
+        emit
+      )
+
+      const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0)
 
       const getClass = computed(() => {
         return [
           prefixCls,
           {
-            [`${prefixCls}--mobile`]: unref(getIsMobile),
-          },
-        ];
-      });
+            [`${prefixCls}--mobile`]: unref(getIsMobile)
+          }
+        ]
+      })
+
+      watch(
+        () => props.visible,
+        (visible: boolean) => {
+          visible &&
+            nextTick(() => {
+              unref(inputRef)?.focus()
+            })
+        }
+      )
 
       function handleClose() {
-        searchResult.value = [];
-        emit('close');
+        searchResult.value = []
+        emit('close')
       }
 
       return {
@@ -131,9 +135,10 @@
         scrollWrap,
         handleMouseenter,
         handleClose,
-      };
-    },
-  });
+        inputRef
+      }
+    }
+  })
 </script>
 <style lang="less" scoped>
   @prefix-cls: ~'@{namespace}-app-search-modal';

+ 105 - 92
src/components/Application/src/search/useMenuSearch.ts

@@ -1,153 +1,166 @@
-import type { Menu } from '/@/router/types';
-
-import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
-
-import { getMenus } from '/@/router/menus';
-
-import { cloneDeep } from 'lodash-es';
-import { filter, forEach } from '/@/utils/helper/treeHelper';
-
-import { useGo } from '/@/hooks/web/usePage';
-import { useScrollTo } from '/@/hooks/event/useScrollTo';
-import { onKeyStroke, useDebounceFn } from '@vueuse/core';
-import { useI18n } from '/@/hooks/web/useI18n';
+import type { Menu } from '/@/router/types'
+import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'
+import { getMenus } from '/@/router/menus'
+import { cloneDeep } from 'lodash-es'
+import { filter, forEach } from '/@/utils/helper/treeHelper'
+import { useGo } from '/@/hooks/web/usePage'
+import { useScrollTo } from '/@/hooks/event/useScrollTo'
+import { onKeyStroke, useDebounceFn } from '@vueuse/core'
+import { useI18n } from '/@/hooks/web/useI18n'
 
 export interface SearchResult {
-  name: string;
-  path: string;
-  icon?: string;
+  name: string
+  path: string
+  icon?: string
 }
 
 // Translate special characters
 function transform(c: string) {
-  const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'];
-  return code.includes(c) ? `\\${c}` : c;
+  const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|']
+  return code.includes(c) ? `\\${c}` : c
 }
 
 function createSearchReg(key: string) {
-  const keys = [...key].map((item) => transform(item));
-  const str = ['', ...keys, ''].join('.*');
-  return new RegExp(str);
+  const keys = [...key].map(item => transform(item))
+  const str = ['', ...keys, ''].join('.*')
+  return new RegExp(str)
 }
 
 export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
-  const searchResult = ref<SearchResult[]>([]);
-  const keyword = ref('');
-  const activeIndex = ref(-1);
+  const searchResult = ref<SearchResult[]>([])
+  const keyword = ref('')
+  const activeIndex = ref(-1)
 
-  let menuList: Menu[] = [];
+  let menuList: Menu[] = []
 
-  const { t } = useI18n();
-  const go = useGo();
-  const handleSearch = useDebounceFn(search, 200);
+  const { t } = useI18n()
+  const go = useGo()
+  const handleSearch = useDebounceFn(search, 200)
 
   onBeforeMount(async () => {
-    const list = await getMenus();
-    menuList = cloneDeep(list);
-    forEach(menuList, (item) => {
-      item.name = t(item.name);
-    });
-  });
+    const list = await getMenus()
+    menuList = cloneDeep(list)
+    forEach(menuList, item => {
+      item.name = t(item.name)
+    })
+  })
 
   function search(e: ChangeEvent) {
-    e?.stopPropagation();
-    const key = e.target.value;
-    keyword.value = key.trim();
+    e?.stopPropagation()
+    const key = e.target.value
+    keyword.value = key.trim()
     if (!key) {
-      searchResult.value = [];
-      return;
+      searchResult.value = []
+      return
     }
-    const reg = createSearchReg(unref(keyword));
-    const filterMenu = filter(menuList, (item) => {
-      return reg.test(item.name);
-    });
-    searchResult.value = handlerSearchResult(filterMenu, reg);
-    activeIndex.value = 0;
+    const reg = createSearchReg(unref(keyword))
+    const filterMenu = filter(menuList, item => {
+      return reg.test(item.name)
+    })
+    searchResult.value = handlerSearchResult(filterMenu, reg)
+    activeIndex.value = 0
   }
 
   function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
-    const ret: SearchResult[] = [];
-
-    filterMenu.forEach((item) => {
-      const { name, path, icon, children } = item;
+    const ret: SearchResult[] = []
+    filterMenu.forEach(item => {
+      const { name, path, icon, children } = item
       if (reg.test(name) && !children?.length) {
         ret.push({
           name: parent?.name ? `${parent.name} > ${name}` : name,
           path,
-          icon,
-        });
+          icon
+        })
       }
       if (Array.isArray(children) && children.length) {
-        ret.push(...handlerSearchResult(children, reg, item));
+        ret.push(...handlerSearchResult(children, reg, item))
       }
-    });
-    return ret;
+    })
+    return ret
   }
 
+  // Activate when the mouse moves to a certain line
   function handleMouseenter(e: any) {
-    const index = e.target.dataset.index;
-    activeIndex.value = Number(index);
+    const index = e.target.dataset.index
+    activeIndex.value = Number(index)
   }
 
+  // Arrow key up
   function handleUp() {
-    if (!searchResult.value.length) return;
-    activeIndex.value--;
+    if (!searchResult.value.length) return
+    activeIndex.value--
     if (activeIndex.value < 0) {
-      activeIndex.value = searchResult.value.length - 1;
+      activeIndex.value = searchResult.value.length - 1
     }
-    handleScroll();
+    handleScroll()
   }
 
+  // Arrow key down
   function handleDown() {
-    if (!searchResult.value.length) return;
-    activeIndex.value++;
+    if (!searchResult.value.length) return
+    activeIndex.value++
     if (activeIndex.value > searchResult.value.length - 1) {
-      activeIndex.value = 0;
+      activeIndex.value = 0
     }
-    handleScroll();
+    handleScroll()
   }
 
+  // When the keyboard up and down keys move to an invisible place
+  // the scroll bar needs to scroll automatically
   function handleScroll() {
-    const refList = unref(refs);
-    if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) return;
-
-    const index = unref(activeIndex);
-    const currentRef = refList[index];
-    if (!currentRef) return;
-    const wrapEl = unref(scrollWrap);
-    if (!wrapEl) return;
-    const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight;
-    const wrapHeight = wrapEl.offsetHeight;
+    const refList = unref(refs)
+    if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) {
+      return
+    }
+
+    const index = unref(activeIndex)
+    const currentRef = refList[index]
+    if (!currentRef) {
+      return
+    }
+    const wrapEl = unref(scrollWrap)
+    if (!wrapEl) {
+      return
+    }
+    const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight
+    const wrapHeight = wrapEl.offsetHeight
     const { start } = useScrollTo({
       el: wrapEl,
       duration: 100,
-      to: scrollHeight - wrapHeight,
-    });
-    start();
+      to: scrollHeight - wrapHeight
+    })
+    start()
   }
 
+  // enter keyboard event
   async function handleEnter() {
-    if (!searchResult.value.length) return;
-    const result = unref(searchResult);
-    const index = unref(activeIndex);
+    if (!searchResult.value.length) {
+      return
+    }
+    const result = unref(searchResult)
+    const index = unref(activeIndex)
     if (result.length === 0 || index < 0) {
-      return;
+      return
     }
-    const to = result[index];
-    handleClose();
-    await nextTick();
-    go(to.path);
+    const to = result[index]
+    handleClose()
+    await nextTick()
+    go(to.path)
   }
 
+  // close search modal
   function handleClose() {
-    searchResult.value = [];
-    emit('close');
+    searchResult.value = []
+    emit('close')
   }
 
-  onKeyStroke('Enter', handleEnter);
-  onKeyStroke('ArrowUp', handleUp);
-  onKeyStroke('ArrowDown', handleDown);
-  onKeyStroke('Escape', handleClose);
+  // enter search
+  onKeyStroke('Enter', handleEnter)
+  // Monitor keyboard arrow keys
+  onKeyStroke('ArrowUp', handleUp)
+  onKeyStroke('ArrowDown', handleDown)
+  // esc close
+  onKeyStroke('Escape', handleClose)
 
-  return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter };
+  return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter }
 }

+ 7 - 8
src/components/Application/src/useAppContext.ts

@@ -1,18 +1,17 @@
-import { InjectionKey, Ref } from 'vue';
-import { createContext, useContext } from '/@/hooks/core/useContext';
+import { InjectionKey, Ref } from 'vue'
+import { createContext, useContext } from '/@/hooks/core/useContext'
 
 export interface AppProviderContextProps {
-  prefixCls: Ref<string>;
-
-  isMobile: Ref<boolean>;
+  prefixCls: Ref<string>
+  isMobile: Ref<boolean>
 }
 
-const key: InjectionKey<AppProviderContextProps> = Symbol();
+const key: InjectionKey<AppProviderContextProps> = Symbol()
 
 export function createAppProviderContext(context: AppProviderContextProps) {
-  return createContext<AppProviderContextProps>(context, key);
+  return createContext<AppProviderContextProps>(context, key)
 }
 
 export function useAppProviderContext() {
-  return useContext<AppProviderContextProps>(key);
+  return useContext<AppProviderContextProps>(key)
 }

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

@@ -1,3 +1,4 @@
-import Authority from './src/index.vue';
+import { withInstall } from '/@/utils'
+import authority from './src/Authority.vue'
 
-export { Authority };
+export const Authority = withInstall(authority)

+ 45 - 0
src/components/Authority/src/Authority.vue

@@ -0,0 +1,45 @@
+<!--
+ Access control component for fine-grained access control.
+-->
+<script lang="ts">
+  import type { PropType } from 'vue'
+  import { defineComponent } from 'vue'
+  import { RoleEnum } from '/@/enums/roleEnum'
+  import { usePermission } from '/@/hooks/web/usePermission'
+  import { getSlot } from '/@/utils/helper/tsxHelper'
+
+  export default defineComponent({
+    name: 'Authority',
+    props: {
+      /**
+       * Specified role is visible
+       * When the permission mode is the role mode, the value value can pass the role value.
+       * When the permission mode is background, the value value can pass the code permission value
+       * @default ''
+       */
+      value: {
+        type: [Number, Array, String] as PropType<RoleEnum | RoleEnum[] | string | string[]>,
+        default: ''
+      }
+    },
+    setup(props, { slots }) {
+      const { hasPermission } = usePermission()
+
+      /**
+       * Render role button
+       */
+      function renderAuth() {
+        const { value } = props
+        if (!value) {
+          return getSlot(slots)
+        }
+        return hasPermission(value) ? getSlot(slots) : null
+      }
+
+      return () => {
+        // Role-based value control
+        return renderAuth()
+      }
+    }
+  })
+</script>

+ 7 - 4
src/components/Basic/index.ts

@@ -1,5 +1,8 @@
-import BasicArrow from './src/BasicArrow.vue';
-import BasicTitle from './src/BasicTitle.vue';
-import BasicHelp from './src/BasicHelp.vue';
+import { withInstall } from '/@/utils'
+import basicArrow from './src/BasicArrow.vue'
+import basicTitle from './src/BasicTitle.vue'
+import basicHelp from './src/BasicHelp.vue'
 
-export { BasicArrow, BasicTitle, BasicHelp };
+export const BasicArrow = withInstall(basicArrow)
+export const BasicTitle = withInstall(basicTitle)
+export const BasicHelp = withInstall(basicHelp)

+ 37 - 28
src/components/Basic/src/BasicArrow.vue

@@ -8,44 +8,53 @@
   </span>
 </template>
 <script lang="ts">
-  import { defineComponent, computed } from 'vue';
+  import { defineComponent, computed } from 'vue'
+  import { Icon } from '/@/components/Icon'
+  import { useDesign } from '/@/hooks/web/useDesign'
 
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { propTypes } from '/@/utils/propTypes';
-
-  import { Icon } from '/@/components/Icon';
+  const props = {
+    /**
+     * Arrow expand state
+     */
+    expand: { type: Boolean },
+    /**
+     * Arrow up by default
+     */
+    up: { type: Boolean },
+    /**
+     * Arrow down by default
+     */
+    down: { type: Boolean },
+    /**
+     * Cancel padding/margin for inline
+     */
+    inset: { type: Boolean }
+  }
 
   export default defineComponent({
     name: 'BasicArrow',
     components: { Icon },
-    props: {
-      expand: propTypes.bool,
-      top: propTypes.bool,
-      bottom: propTypes.bool,
-      inset: propTypes.bool,
-    },
+    props,
     setup(props) {
-      const { prefixCls } = useDesign('basic-arrow');
+      const { prefixCls } = useDesign('basic-arrow')
 
+      // get component class
       const getClass = computed(() => {
-        const { expand, top, bottom, inset } = props;
+        const { expand, up, down, inset } = props
         return [
           prefixCls,
           {
             [`${prefixCls}--active`]: expand,
-            top,
+            up,
             inset,
-            bottom,
-          },
-        ];
-      });
+            down
+          }
+        ]
+      })
 
-      return {
-        getClass,
-      };
-    },
-  });
+      return { getClass }
+    }
+  })
 </script>
 <style lang="less" scoped>
   @prefix-cls: ~'@{namespace}-basic-arrow';
@@ -65,19 +74,19 @@
       line-height: 0px;
     }
 
-    &.top {
+    &.up {
       transform: rotate(-90deg);
     }
 
-    &.bottom {
+    &.down {
       transform: rotate(90deg);
     }
 
-    &.top.@{prefix-cls}--active {
+    &.up.@{prefix-cls}--active {
       transform: rotate(90deg);
     }
 
-    &.bottom.@{prefix-cls}--active {
+    &.down.@{prefix-cls}--active {
       transform: rotate(-90deg);
     }
   }

+ 61 - 77
src/components/Basic/src/BasicHelp.vue

@@ -1,109 +1,93 @@
 <script lang="tsx">
-  import type { CSSProperties, PropType } from 'vue';
-  import { defineComponent, computed, unref } from 'vue';
+  import type { CSSProperties, PropType } from 'vue'
+  import { defineComponent, computed, unref } from 'vue'
+  import { Tooltip } from 'ant-design-vue'
+  import { InfoCircleOutlined } from '@ant-design/icons-vue'
+  import { getPopupContainer } from '/@/utils'
+  import { isString, isArray } from '/@/utils/is'
+  import { getSlot } from '/@/utils/helper/tsxHelper'
+  import { useDesign } from '/@/hooks/web/useDesign'
 
-  import { Tooltip } from 'ant-design-vue';
-  import { InfoCircleOutlined } from '@ant-design/icons-vue';
-
-  import { getPopupContainer } from '/@/utils';
-  import { isString, isArray } from '/@/utils/is';
-  import { getSlot } from '/@/utils/helper/tsxHelper';
-  import { propTypes } from '/@/utils/propTypes';
-
-  import { useDesign } from '/@/hooks/web/useDesign';
+  const props = {
+    /**
+     * Help text max-width
+     * @default: 600px
+     */
+    maxWidth: { type: String, default: '600px' },
+    /**
+     * Whether to display the serial number
+     * @default: false
+     */
+    showIndex: { type: Boolean },
+    /**
+     * Help text font color
+     * @default: #ffffff
+     */
+    color: { type: String, default: '#ffffff' },
+    /**
+     * Help text font size
+     * @default: 14px
+     */
+    fontSize: { type: String, default: '14px' },
+    /**
+     * Help text list
+     */
+    placement: { type: String, default: 'right' },
+    /**
+     * Help text list
+     */
+    text: { type: [Array, String] as PropType<string[] | string> }
+  }
 
   export default defineComponent({
     name: 'BasicHelp',
     components: { Tooltip },
-    props: {
-      // max-width
-      maxWidth: propTypes.string.def('600px'),
-      // Whether to display the serial number
-      showIndex: propTypes.bool,
-      // color
-      color: propTypes.string.def('#ffffff'),
-      fontSize: propTypes.string.def('14px'),
-      placement: propTypes.string.def('right'),
-      absolute: propTypes.bool,
-      // Text list
-      text: {
-        type: [Array, String] as PropType<string[] | string>,
-      },
-      // 定位
-      position: {
-        type: [Object] as PropType<any>,
-        default: () => ({
-          position: 'absolute',
-          left: 0,
-          bottom: 0,
-        }),
-      },
-    },
+    props,
     setup(props, { slots }) {
-      const { prefixCls } = useDesign('basic-help');
-
-      const getOverlayStyle = computed(
-        (): CSSProperties => {
-          return {
-            maxWidth: props.maxWidth,
-          };
-        }
-      );
+      const { prefixCls } = useDesign('basic-help')
 
-      const getWrapStyle = computed(
-        (): CSSProperties => {
-          return {
-            color: props.color,
-            fontSize: props.fontSize,
-          };
-        }
-      );
+      const getTooltipStyle = computed((): CSSProperties => ({ color: props.color, fontSize: props.fontSize }))
 
-      const getMainStyleRef = computed(() => {
-        return props.absolute ? props.position : {};
-      });
+      const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth }))
 
-      const renderTitle = () => {
-        const list = props.text;
+      function renderTitle() {
+        const textList = props.text
 
-        if (isString(list)) {
-          return <p>{list}</p>;
+        if (isString(textList)) {
+          return <p>{textList}</p>
         }
 
-        if (isArray(list)) {
-          return list.map((item, index) => {
+        if (isArray(textList)) {
+          return textList.map((text, index) => {
             return (
-              <p key={item}>
+              <p key={text}>
                 <>
                   {props.showIndex ? `${index + 1}. ` : ''}
-                  {item}
+                  {text}
                 </>
               </p>
-            );
-          });
+            )
+          })
         }
-
-        return null;
-      };
+        return null
+      }
 
       return () => {
         return (
           <Tooltip
-            title={<div style={unref(getWrapStyle)}>{renderTitle()}</div>}
             overlayClassName={`${prefixCls}__wrap`}
+            title={<div style={unref(getTooltipStyle)}>{renderTitle()}</div>}
             autoAdjustOverflow={true}
             overlayStyle={unref(getOverlayStyle)}
-            placement={props.placement as 'left'}
+            placement={props.placement as 'right'}
             getPopupContainer={() => getPopupContainer()}
           >
-            <span class={prefixCls} style={unref(getMainStyleRef)}>
-              {getSlot(slots) || <InfoCircleOutlined />}
-            </span>
+            <span class={prefixCls}>{getSlot(slots) || <InfoCircleOutlined />}</span>
           </Tooltip>
-        );
-      };
-    },
-  });
+        )
+      }
+    }
+  })
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-basic-help';

+ 34 - 23
src/components/Basic/src/BasicTitle.vue

@@ -1,41 +1,52 @@
 <template>
   <span :class="getClass">
     <slot></slot>
-    <BasicHelp :class="`${prefixCls}__help`" v-if="helpMessage" :text="helpMessage" />
+    <BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
   </span>
 </template>
 <script lang="ts">
-  import type { PropType } from 'vue';
+  import type { PropType } from 'vue'
+  import { defineComponent, computed } from 'vue'
+  import BasicHelp from './BasicHelp.vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
 
-  import { defineComponent, computed } from 'vue';
-  import BasicHelp from './BasicHelp.vue';
-
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { propTypes } from '/@/utils/propTypes';
+  const props = {
+    /**
+     * Help text list or string
+     * @default: ''
+     */
+    helpMessage: {
+      type: [String, Array] as PropType<string | string[]>,
+      default: ''
+    },
+    /**
+     * Whether the color block on the left side of the title
+     * @default: false
+     */
+    span: { type: Boolean },
+    /**
+     * Whether to default the text, that is, not bold
+     * @default: false
+     */
+    normal: { type: Boolean }
+  }
 
   export default defineComponent({
     name: 'BasicTitle',
     components: { BasicHelp },
-    props: {
-      helpMessage: {
-        type: [String, Array] as PropType<string | string[]>,
-        default: '',
-      },
-      span: propTypes.bool,
-      normal: propTypes.bool.def(false),
-    },
+    props,
     setup(props, { slots }) {
-      const { prefixCls } = useDesign('basic-title');
+      const { prefixCls } = useDesign('basic-title')
 
       const getClass = computed(() => [
         prefixCls,
         { [`${prefixCls}-show-span`]: props.span && slots.default },
-        { [`${prefixCls}-normal`]: props.normal },
-      ]);
-      return { prefixCls, getClass };
-    },
-  });
+        { [`${prefixCls}-normal`]: props.normal }
+      ])
+
+      return { prefixCls, getClass }
+    }
+  })
 </script>
 <style lang="less" scoped>
   @prefix-cls: ~'@{namespace}-basic-title';
@@ -67,7 +78,7 @@
       content: '';
     }
 
-    &__help {
+    &-help {
       margin-left: 10px;
     }
   }

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

@@ -1,4 +1,6 @@
-import Button from './src/BasicButton.vue';
-import PopConfirmButton from './src/PopConfirmButton.vue';
+import { withInstall } from '/@/utils'
+import button from './src/BasicButton.vue'
+import popConfirmButton from './src/PopConfirmButton.vue'
 
-export { Button, PopConfirmButton };
+export const Button = withInstall(button)
+export const PopConfirmButton = withInstall(popConfirmButton)

+ 42 - 30
src/components/Button/src/BasicButton.vue

@@ -1,47 +1,59 @@
 <template>
-  <Button v-bind="getBindValue" :class="[getColor, $attrs.class]" @click="onClick">
+  <Button v-bind="getBindValue" :class="getButtonClass" @click="onClick">
     <template #default="data">
-      <Icon :icon="preIcon" v-if="preIcon" :size="14" />
+      <Icon :icon="preIcon" v-if="preIcon" :size="iconSize" />
       <slot v-bind="data"></slot>
-      <Icon :icon="postIcon" v-if="postIcon" :size="14" />
+      <Icon :icon="postIcon" v-if="postIcon" :size="iconSize" />
     </template>
   </Button>
 </template>
 <script lang="ts">
-  import { defineComponent, computed } from 'vue';
+  import { defineComponent, computed } from 'vue'
+  import { Button } from 'ant-design-vue'
+  import { Icon } from '/@/components/Icon'
 
-  import { Button } from 'ant-design-vue';
-  import Icon from '/@/components/Icon';
-
-  import { propTypes } from '/@/utils/propTypes';
+  const props = {
+    color: { type: String, validator: v => ['error', 'warning', 'success', ''].includes(v) },
+    loading: { type: Boolean },
+    disabled: { type: Boolean },
+    /**
+     * Text before icon.
+     */
+    preIcon: { type: String },
+    /**
+     * Text after icon.
+     */
+    postIcon: { type: String },
+    /**
+     * preIcon and postIcon icon size.
+     * @default: 14
+     */
+    iconSize: { type: Number, default: 14 },
+    onClick: { type: Function as PropType<(...args) => any>, default: null }
+  }
 
   export default defineComponent({
     name: 'AButton',
     components: { Button, Icon },
     inheritAttrs: false,
-    props: {
-      type: propTypes.oneOf(['primary', 'default', 'danger', 'dashed', 'link']).def('default'),
-      color: propTypes.oneOf(['error', 'warning', 'success', '']),
-      loading: propTypes.bool,
-      disabled: propTypes.bool,
-      preIcon: propTypes.string,
-      postIcon: propTypes.string,
-      onClick: propTypes.func,
-    },
+    props,
     setup(props, { attrs }) {
-      const getColor = computed(() => {
-        const { color, disabled } = props;
-        return {
-          [`ant-btn-${color}`]: !!color,
-          [`is-disabled`]: disabled,
-        };
-      });
+      // get component class
+      const getButtonClass = computed(() => {
+        const { color, disabled } = props
+        return [
+          {
+            [`ant-btn-${color}`]: !!color,
+            [`is-disabled`]: disabled
+          },
+          attrs.class
+        ]
+      })
 
-      const getBindValue = computed((): any => {
-        return { ...attrs, ...props };
-      });
+      // get inherit binding value
+      const getBindValue = computed(() => ({ ...attrs, ...props }))
 
-      return { getBindValue, getColor };
-    },
-  });
+      return { getBindValue, getButtonClass }
+    }
+  })
 </script>

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

@@ -1,52 +1,54 @@
 <script lang="ts">
-  import { defineComponent, h, unref, computed } from 'vue';
-
-  import { Popconfirm } from 'ant-design-vue';
-
-  import BasicButton from './BasicButton.vue';
-
-  import { propTypes } from '/@/utils/propTypes';
-  import { extendSlots } from '/@/utils/helper/tsxHelper';
-  import { omit } from 'lodash-es';
-
-  import { useAttrs } from '/@/hooks/core/useAttrs';
-  import { useI18n } from '/@/hooks/web/useI18n';
+  import { defineComponent, h, unref, computed } from 'vue'
+  import BasicButton from './BasicButton.vue'
+  import { Popconfirm } from 'ant-design-vue'
+  import { extendSlots } from '/@/utils/helper/tsxHelper'
+  import { omit } from 'lodash-es'
+  import { useAttrs } from '/@/hooks/core/useAttrs'
+  import { useI18n } from '/@/hooks/web/useI18n'
+
+  const props = {
+    /**
+     * Whether to enable the drop-down menu
+     * @default: true
+     */
+    enable: {
+      type: Boolean,
+      default: true
+    }
+  }
 
   export default defineComponent({
     name: 'PopButton',
     components: { Popconfirm, BasicButton },
     inheritAttrs: false,
-    props: {
-      size: propTypes.oneOf(['large', 'default', 'small']).def(),
-      enable: propTypes.bool.def(true),
-      okText: propTypes.string,
-      cancelText: propTypes.string,
-    },
+    props,
     setup(props, { slots }) {
-      const { t } = useI18n();
-      const attrs = useAttrs();
+      const { t } = useI18n()
+      const attrs = useAttrs()
 
+      // get inherit binding value
       const getBindValues = computed(() => {
         const popValues = Object.assign(
           {
             okText: t('common.okText'),
-            cancelText: t('common.cancelText'),
+            cancelText: t('common.cancelText')
           },
           { ...props, ...unref(attrs) }
-        );
-        return popValues;
-      });
+        )
+        return popValues
+      })
 
       return () => {
-        const values = omit(unref(getBindValues), 'icon');
-        const Button = h(BasicButton, values, extendSlots(slots));
+        const bindValues = omit(unref(getBindValues), 'icon')
+        const Button = h(BasicButton, bindValues, extendSlots(slots))
 
+        // If it is not enabled, it is a normal button
         if (!props.enable) {
-          return Button;
+          return Button
         }
-
-        return h(Popconfirm, values, { default: () => Button });
-      };
-    },
-  });
+        return h(Popconfirm, bindValues, { default: () => Button })
+      }
+    }
+  })
 </script>

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

@@ -1,3 +1,4 @@
-import ClickOutSide from './src/index.vue';
+import { withInstall } from '/@/utils'
+import clickOutSide from './src/ClickOutSide.vue'
 
-export { ClickOutSide };
+export const ClickOutSide = withInstall(clickOutSide)

+ 26 - 0
src/components/ClickOutSide/src/ClickOutSide.vue

@@ -0,0 +1,26 @@
+<template>
+  <div ref="wrap">
+    <slot></slot>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, onMounted } from 'vue'
+  import { onClickOutside } from '@vueuse/core'
+  export default defineComponent({
+    name: 'ClickOutSide',
+    emits: ['mounted', 'clickOutside'],
+    setup(_, { emit }) {
+      const wrap = ref<ElRef>(null)
+
+      onClickOutside(wrap, () => {
+        emit('clickOutside')
+      })
+
+      onMounted(() => {
+        emit('mounted')
+      })
+
+      return { wrap }
+    }
+  })
+</script>

+ 5 - 14
src/components/CodeEditor/index.ts

@@ -1,15 +1,6 @@
-import type { App } from 'vue';
-import codeEditor from './src/CodeEditor.vue';
-import jsonPreview from './src/json-preview/JsonPreview.vue';
+import { withInstall } from '/@/utils'
+import codeEditor from './src/CodeEditor.vue'
+import jsonPreview from './src/json-preview/JsonPreview.vue'
 
-export const CodeEditor = Object.assign(codeEditor, {
-  install(app: App) {
-    app.component(codeEditor.name, codeEditor);
-  },
-});
-
-export const JsonPreview = Object.assign(jsonPreview, {
-  install(app: App) {
-    app.component(jsonPreview.name, jsonPreview);
-  },
-});
+export const CodeEditor = withInstall(codeEditor)
+export const JsonPreview = withInstall(jsonPreview)

+ 23 - 30
src/components/CodeEditor/src/CodeEditor.vue

@@ -1,51 +1,44 @@
 <template>
   <div class="h-full">
-    <CodeMirrorEditor :value="getValue" @change="handleValueChange" :mode="mode" />
+    <CodeMirrorEditor :value="getValue" @change="handleValueChange" :mode="mode" :readonly="readonly" />
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent, computed } from 'vue';
-  import CodeMirrorEditor from './codemirror/CodeMirror.vue';
-  import { isString } from '/@/utils/is';
+  import { defineComponent, computed } from 'vue'
+  import CodeMirrorEditor from './codemirror/CodeMirror.vue'
+  import { isString } from '/@/utils/is'
 
   const MODE = {
     JSON: 'application/json',
     html: 'htmlmixed',
-    js: 'javascript',
-  };
+    js: 'javascript'
+  }
+
+  const props = {
+    value: { type: [Object, String] as PropType<Record<string, any> | string> },
+    mode: { type: String, default: MODE.JSON },
+    readonly: { type: Boolean }
+  }
+
   export default defineComponent({
     name: 'CodeEditor',
     components: { CodeMirrorEditor },
-    props: {
-      value: {
-        type: [Object, String],
-      },
-      mode: {
-        type: String,
-        default: MODE.JSON,
-      },
-    },
+    props,
     emits: ['change'],
     setup(props, { emit }) {
       const getValue = computed(() => {
-        const { value, mode } = props;
-
-        if (mode === MODE.JSON) {
-          return isString(value)
-            ? JSON.stringify(JSON.parse(value), null, 2)
-            : JSON.stringify(value, null, 2);
+        const { value, mode } = props
+        if (mode !== MODE.JSON) {
+          return value as string
         }
-        return value;
-      });
+        return isString(value) ? JSON.stringify(JSON.parse(value), null, 2) : JSON.stringify(value, null, 2)
+      })
 
       function handleValueChange(v) {
-        emit('change', v);
+        emit('change', v)
       }
 
-      return {
-        handleValueChange,
-        getValue,
-      };
-    },
-  });
+      return { handleValueChange, getValue }
+    }
+  })
 </script>

+ 56 - 73
src/components/CodeEditor/src/codemirror/CodeMirror.vue

@@ -1,86 +1,70 @@
 <template>
-  <div class="relative h-100 !h-full w-full overflow-hidden" ref="el"> </div>
+  <div class="relative !h-full w-full overflow-hidden" ref="el"> </div>
 </template>
 
 <script lang="ts">
-  import {
-    ref,
-    onMounted,
-    onUnmounted,
-    watchEffect,
-    watch,
-    defineComponent,
-    unref,
-    nextTick,
-  } from 'vue';
-  import { useDebounceFn } from '@vueuse/core';
-  import { useAppStore } from '/@/store/modules/app';
+  import { ref, onMounted, onUnmounted, watchEffect, watch, defineComponent, unref, nextTick } from 'vue'
+  import { useDebounceFn } from '@vueuse/core'
+  import { useAppStore } from '/@/store/modules/app'
+  import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'
+  import CodeMirror from 'codemirror'
+  // css
+  import './codemirror.css'
+  import 'codemirror/theme/idea.css'
+  import 'codemirror/theme/material-palenight.css'
+  // modes
+  import 'codemirror/mode/javascript/javascript'
+  import 'codemirror/mode/css/css'
+  import 'codemirror/mode/htmlmixed/htmlmixed'
 
-  import CodeMirror from 'codemirror';
-  import './codemirror.css';
-  import 'codemirror/theme/idea.css';
-  import 'codemirror/theme/material-palenight.css';
+  const props = {
+    mode: { type: String, default: 'application/json' },
+    value: { type: String, default: '' },
+    readonly: { type: Boolean, default: false }
+  }
 
-  // modes
-  import 'codemirror/mode/javascript/javascript';
-  import 'codemirror/mode/css/css';
-  import 'codemirror/mode/htmlmixed/htmlmixed';
   export default defineComponent({
-    props: {
-      mode: {
-        type: String,
-        default: 'application/json',
-      },
-      value: {
-        type: String,
-        default: '',
-      },
-      readonly: {
-        type: Boolean,
-        default: false,
-      },
-    },
+    props,
     emits: ['change'],
     setup(props, { emit }) {
-      const el = ref();
-      let editor: Nullable<CodeMirror.Editor>;
+      const el = ref()
+      let editor: Nullable<CodeMirror.Editor>
 
-      const debounceRefresh = useDebounceFn(refresh, 100);
-      const appStore = useAppStore();
+      const debounceRefresh = useDebounceFn(refresh, 100)
+      const appStore = useAppStore()
 
       watch(
         () => props.value,
-        async (v) => {
-          await nextTick();
-          const oldValue = editor?.getValue();
-          v && v !== oldValue && editor?.setValue(v);
+        async value => {
+          await nextTick()
+          const oldValue = editor?.getValue()
+          if (value !== oldValue) {
+            editor?.setValue(value ? value : '')
+          }
         },
         { flush: 'post' }
-      );
+      )
 
       watchEffect(() => {
-        editor?.setOption('mode', props.mode);
-      });
+        editor?.setOption('mode', props.mode)
+      })
 
       watch(
         () => appStore.getDarkMode,
         async () => {
-          setTheme();
+          setTheme()
         },
         {
-          immediate: true,
+          immediate: true
         }
-      );
+      )
 
       function setTheme() {
-        unref(editor)?.setOption(
-          'theme',
-          appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight'
-        );
+        unref(editor)?.setOption('theme', appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight')
       }
 
       function refresh() {
-        editor?.refresh();
+        editor?.refresh()
       }
 
       async function init() {
@@ -88,8 +72,8 @@
           autoCloseBrackets: true,
           autoCloseTags: true,
           foldGutter: true,
-          gutters: ['CodeMirror-linenumbers'],
-        };
+          gutters: ['CodeMirror-linenumbers']
+        }
 
         editor = CodeMirror(el.value!, {
           value: '',
@@ -99,27 +83,26 @@
           theme: 'material-palenight',
           lineWrapping: true,
           lineNumbers: true,
-          ...addonOptions,
-        });
-        editor?.setValue(props.value);
-        setTheme();
+          ...addonOptions
+        })
+        editor?.setValue(props.value)
+        setTheme()
         editor?.on('change', () => {
-          emit('change', editor?.getValue());
-        });
+          emit('change', editor?.getValue())
+        })
       }
 
       onMounted(async () => {
-        await nextTick();
-        init();
-        window.addEventListener('resize', debounceRefresh);
-        setTimeout(refresh, 50);
-      });
+        await nextTick()
+        init()
+        useWindowSizeFn(debounceRefresh)
+      })
 
       onUnmounted(() => {
-        window.removeEventListener('resize', debounceRefresh);
-        editor = null;
-      });
-      return { el };
-    },
-  });
+        editor = null
+      })
+
+      return { el }
+    }
+  })
 </script>

+ 14 - 46
src/components/CodeEditor/src/codemirror/codemirror.css

@@ -12,20 +12,21 @@
   --qualifier: #ff6032;
   --important: var(--string);
 
+  position: relative;
   height: auto;
   height: 100%;
+  overflow: hidden;
   font-family: var(--font-code);
+  background: white;
   direction: ltr;
 }
 
 /* PADDING */
 
 .CodeMirror-lines {
+  min-height: 1px; /* prevents collapsing before first draw */
   padding: 4px 0; /* Vertical padding around content */
-}
-
-.CodeMirror pre {
-  padding: 0 4px; /* Horizontal padding of content */
+  cursor: text;
 }
 
 .CodeMirror-scrollbar-filler,
@@ -36,6 +37,11 @@
 /* GUTTER */
 
 .CodeMirror-gutters {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 3;
+  min-height: 100%;
   white-space: nowrap;
   background-color: transparent;
   border-right: 1px solid #ddd;
@@ -96,7 +102,9 @@
 /* CURSOR */
 
 .CodeMirror-cursor {
+  position: absolute;
   width: 0;
+  pointer-events: none;
   border-right: none;
   border-left: 1px solid black;
 }
@@ -132,37 +140,19 @@
   animation: blink 1.06s steps(1) infinite;
 }
 @-moz-keyframes blink {
-  0% {
-  }
-
   50% {
     background-color: transparent;
   }
-
-  100% {
-  }
 }
 @-webkit-keyframes blink {
-  0% {
-  }
-
   50% {
     background-color: transparent;
   }
-
-  100% {
-  }
 }
 @keyframes blink {
-  0% {
-  }
-
   50% {
     background-color: transparent;
   }
-
-  100% {
-  }
 }
 
 .cm-tab {
@@ -316,12 +306,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
 /* The rest of this file contains styles related to the mechanics of
    the editor. You probably shouldn't touch them. */
 
-.CodeMirror {
-  position: relative;
-  overflow: hidden;
-  background: white;
-}
-
 .CodeMirror-scroll {
   position: relative;
   height: 100%;
@@ -338,6 +322,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
 
 .CodeMirror-sizer {
   position: relative;
+  margin-bottom: 20px !important;
   border-right: 30px solid transparent;
 }
 
@@ -377,14 +362,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
   left: 0;
 }
 
-.CodeMirror-gutters {
-  position: absolute;
-  top: 0;
-  left: 0;
-  z-index: 3;
-  min-height: 100%;
-}
-
 .CodeMirror-gutter {
   display: inline-block;
   height: 100%;
@@ -421,14 +398,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
   background-color: transparent;
 }
 
-.CodeMirror-lines {
-  min-height: 1px; /* prevents collapsing before first draw */
-  cursor: text;
-}
-
 .CodeMirror pre {
   position: relative;
   z-index: 2;
+  padding: 0 4px; /* Horizontal padding of content */
   margin: 0;
   overflow: visible;
   font-family: inherit;
@@ -496,11 +469,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
   visibility: hidden;
 }
 
-.CodeMirror-cursor {
-  position: absolute;
-  pointer-events: none;
-}
-
 .CodeMirror-measure pre {
   position: static;
 }

+ 6 - 10
src/components/CodeEditor/src/json-preview/JsonPreview.vue

@@ -3,16 +3,12 @@
 </template>
 
 <script lang="ts">
-  import VueJsonPretty from 'vue-json-pretty';
-  import 'vue-json-pretty/lib/styles.css';
-  import { defineComponent } from 'vue';
+  import VueJsonPretty from 'vue-json-pretty'
+  import 'vue-json-pretty/lib/styles.css'
+  import { defineComponent } from 'vue'
   export default defineComponent({
     name: 'JsonPreview',
-    components: {
-      VueJsonPretty,
-    },
-    props: {
-      data: Object,
-    },
-  });
+    components: { VueJsonPretty },
+    props: { data: Object }
+  })
 </script>

+ 9 - 5
src/components/Container/index.ts

@@ -1,6 +1,10 @@
-import CollapseContainer from './src/collapse/CollapseContainer.vue';
-import ScrollContainer from './src/ScrollContainer.vue';
-import LazyContainer from './src/LazyContainer.vue';
+import { withInstall } from '/@/utils'
+import collapseContainer from './src/collapse/CollapseContainer.vue'
+import scrollContainer from './src/ScrollContainer.vue'
+import lazyContainer from './src/LazyContainer.vue'
 
-export { CollapseContainer, ScrollContainer, LazyContainer };
-export * from './src/types';
+export const CollapseContainer = withInstall(collapseContainer)
+export const ScrollContainer = withInstall(scrollContainer)
+export const LazyContainer = withInstall(lazyContainer)
+
+export * from './src/typing'

+ 77 - 76
src/components/Container/src/LazyContainer.vue

@@ -1,12 +1,5 @@
 <template>
-  <transition-group
-    class="h-full w-full"
-    v-bind="$attrs"
-    ref="elRef"
-    :name="transitionName"
-    :tag="tag"
-    mode="out-in"
-  >
+  <transition-group class="h-full w-full" v-bind="$attrs" ref="elRef" :name="transitionName" :tag="tag" mode="out-in">
     <div key="component" v-if="isInit">
       <slot :loading="loading"></slot>
     </div>
@@ -17,96 +10,104 @@
   </transition-group>
 </template>
 <script lang="ts">
-  import type { PropType } from 'vue';
-
-  import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
-
-  import { Skeleton } from 'ant-design-vue';
-  import { useTimeoutFn } from '/@/hooks/core/useTimeout';
-  import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
-  import { propTypes } from '/@/utils/propTypes';
+  import type { PropType } from 'vue'
+  import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'
+  import { Skeleton } from 'ant-design-vue'
+  import { useTimeoutFn } from '/@/hooks/core/useTimeout'
+  import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'
 
   interface State {
-    isInit: boolean;
-    loading: boolean;
-    intersectionObserverInstance: IntersectionObserver | null;
+    isInit: boolean
+    loading: boolean
+    intersectionObserverInstance: IntersectionObserver | null
+  }
+
+  const props = {
+    /**
+     * Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
+     */
+    timeout: { type: Number },
+    /**
+     * The viewport where the component is located.
+     * If the component is scrolling in the page container, the viewport is the container
+     */
+    viewport: {
+      type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>,
+      default: () => null
+    },
+    /**
+     * Preload threshold, css unit
+     */
+    threshold: { type: String, default: '0px' },
+    /**
+     * The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
+     */
+    direction: {
+      type: String,
+      default: 'vertical',
+      validator: v => ['vertical', 'horizontal'].includes(v)
+    },
+    /**
+     * The label name of the outer container that wraps the component
+     */
+    tag: { type: String, default: 'div' },
+    maxWaitingTime: { type: Number, default: 80 },
+    /**
+     * transition name
+     */
+    transitionName: { type: String, default: 'lazy-container' }
   }
 
   export default defineComponent({
     name: 'LazyContainer',
     components: { Skeleton },
     inheritAttrs: false,
-    props: {
-      // Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
-      timeout: propTypes.number,
-
-      // The viewport where the component is located. If the component is scrolling in the page container, the viewport is the container
-      viewport: {
-        type: (typeof window !== 'undefined'
-          ? window.HTMLElement
-          : Object) as PropType<HTMLElement>,
-        default: () => null,
-      },
-
-      // Preload threshold, css unit
-      threshold: propTypes.string.def('0px'),
-
-      // The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
-      direction: propTypes.oneOf(['vertical', 'horizontal']).def('vertical'),
-
-      // The label name of the outer container that wraps the component
-      tag: propTypes.string.def('div'),
-
-      maxWaitingTime: propTypes.number.def(80),
-
-      // transition name
-      transitionName: propTypes.string.def('lazy-container'),
-    },
+    props,
     emits: ['init'],
     setup(props, { emit }) {
-      const elRef = ref();
+      const elRef = ref()
       const state = reactive<State>({
         isInit: false,
         loading: false,
-        intersectionObserverInstance: null,
-      });
+        intersectionObserverInstance: null
+      })
 
       onMounted(() => {
-        immediateInit();
-        initIntersectionObserver();
-      });
+        immediateInit()
+        initIntersectionObserver()
+      })
 
       // If there is a set delay time, it will be executed immediately
       function immediateInit() {
-        const { timeout } = props;
+        const { timeout } = props
         timeout &&
           useTimeoutFn(() => {
-            init();
-          }, timeout);
+            init()
+          }, timeout)
       }
 
       function init() {
-        state.loading = true;
+        state.loading = true
 
         useTimeoutFn(() => {
-          if (state.isInit) return;
-          state.isInit = true;
-          emit('init');
-        }, props.maxWaitingTime || 80);
+          if (state.isInit) return
+          state.isInit = true
+          emit('init')
+        }, props.maxWaitingTime || 80)
       }
 
       function initIntersectionObserver() {
-        const { timeout, direction, threshold } = props;
-        if (timeout) return;
+        const { timeout, direction, threshold } = props
+        if (timeout) return
         // According to the scrolling direction to construct the viewport margin, used to load in advance
-        let rootMargin = '0px';
+        let rootMargin = '0px'
         switch (direction) {
           case 'vertical':
-            rootMargin = `${threshold} 0px`;
-            break;
+            rootMargin = `${threshold} 0px`
+            break
           case 'horizontal':
-            rootMargin = `0px ${threshold}`;
-            break;
+            rootMargin = `0px ${threshold}`
+            break
         }
 
         try {
@@ -114,24 +115,24 @@
             rootMargin,
             target: toRef(elRef.value, '$el'),
             onIntersect: (entries: any[]) => {
-              const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio;
+              const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio
               if (isIntersecting) {
-                init();
+                init()
                 if (observer) {
-                  stop();
+                  stop()
                 }
               }
             },
-            root: toRef(props, 'viewport'),
-          });
+            root: toRef(props, 'viewport')
+          })
         } catch (e) {
-          init();
+          init()
         }
       }
       return {
         elRef,
-        ...toRefs(state),
-      };
-    },
-  });
+        ...toRefs(state)
+      }
+    }
+  })
 </script>

+ 34 - 31
src/components/Container/src/ScrollContainer.vue

@@ -5,73 +5,76 @@
 </template>
 
 <script lang="ts">
-  import { defineComponent, ref, unref, nextTick } from 'vue';
-  import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar';
-
-  import { useScrollTo } from '/@/hooks/event/useScrollTo';
+  import { defineComponent, ref, unref, nextTick } from 'vue'
+  import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar'
+  import { useScrollTo } from '/@/hooks/event/useScrollTo'
 
   export default defineComponent({
     name: 'ScrollContainer',
     components: { Scrollbar },
     setup() {
-      const scrollbarRef = ref<Nullable<ScrollbarType>>(null);
+      const scrollbarRef = ref<Nullable<ScrollbarType>>(null)
 
+      /**
+       * Scroll to the specified position
+       */
       function scrollTo(to: number, duration = 500) {
-        const scrollbar = unref(scrollbarRef);
+        const scrollbar = unref(scrollbarRef)
         if (!scrollbar) {
-          return;
+          return
         }
-
         nextTick(() => {
-          const wrap = unref(scrollbar.wrap);
+          const wrap = unref(scrollbar.wrap)
           if (!wrap) {
-            return;
+            return
           }
           const { start } = useScrollTo({
             el: wrap,
             to,
-            duration,
-          });
-          start();
-        });
+            duration
+          })
+          start()
+        })
       }
 
       function getScrollWrap() {
-        const scrollbar = unref(scrollbarRef);
+        const scrollbar = unref(scrollbarRef)
         if (!scrollbar) {
-          return null;
+          return null
         }
-        return scrollbar.wrap;
+        return scrollbar.wrap
       }
 
+      /**
+       * Scroll to the bottom
+       */
       function scrollBottom() {
-        const scrollbar = unref(scrollbarRef);
+        const scrollbar = unref(scrollbarRef)
         if (!scrollbar) {
-          return;
+          return
         }
-
         nextTick(() => {
-          const wrap = unref(scrollbar.wrap);
+          const wrap = unref(scrollbar.wrap)
           if (!wrap) {
-            return;
+            return
           }
-          const scrollHeight = wrap.scrollHeight as number;
+          const scrollHeight = wrap.scrollHeight as number
           const { start } = useScrollTo({
             el: wrap,
-            to: scrollHeight,
-          });
-          start();
-        });
+            to: scrollHeight
+          })
+          start()
+        })
       }
 
       return {
         scrollbarRef,
         scrollTo,
         scrollBottom,
-        getScrollWrap,
-      };
-    },
-  });
+        getScrollWrap
+      }
+    }
+  })
 </script>
 <style lang="less">
   .scroll-container {

+ 45 - 51
src/components/Container/src/collapse/CollapseContainer.vue

@@ -1,11 +1,6 @@
 <template>
   <div :class="prefixCls">
-    <CollapseHeader
-      v-bind="getBindValues"
-      :prefixCls="prefixCls"
-      :show="show"
-      @expand="handleExpand"
-    >
+    <CollapseHeader v-bind="$props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
       <template #title>
         <slot name="title"></slot>
       </template>
@@ -16,86 +11,85 @@
 
     <div class="p-2">
       <CollapseTransition :enable="canExpan">
-        <Skeleton v-if="loading" :active="active" />
+        <Skeleton v-if="loading" :active="loading" />
         <div :class="`${prefixCls}__body`" v-else v-show="show">
           <slot></slot>
         </div>
       </CollapseTransition>
     </div>
-
     <div :class="`${prefixCls}__footer`" v-if="$slots.footer">
       <slot name="footer"></slot>
     </div>
   </div>
 </template>
 <script lang="ts">
-  import type { PropType } from 'vue';
-
-  import { defineComponent, ref, computed } from 'vue';
-
+  import type { PropType } from 'vue'
+  import { defineComponent, ref } from 'vue'
   // component
-  import { Skeleton } from 'ant-design-vue';
-  import { CollapseTransition } from '/@/components/Transition';
-  import CollapseHeader from './CollapseHeader.vue';
-
-  import { triggerWindowResize } from '/@/utils/event';
+  import { Skeleton } from 'ant-design-vue'
+  import { CollapseTransition } from '/@/components/Transition'
+  import CollapseHeader from './CollapseHeader.vue'
+  import { triggerWindowResize } from '/@/utils/event'
   // hook
-  import { useTimeoutFn } from '/@/hooks/core/useTimeout';
-  import { propTypes } from '/@/utils/propTypes';
-  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useTimeoutFn } from '/@/hooks/core/useTimeout'
+  import { useDesign } from '/@/hooks/web/useDesign'
+
+  const props = {
+    title: { type: String, default: '' },
+    loading: { type: Boolean },
+    /**
+     *  Can it be expanded
+     */
+    canExpan: { type: Boolean, default: true },
+    /**
+     * Warm reminder on the right side of the title
+     */
+    helpMessage: {
+      type: [Array, String] as PropType<string[] | string>,
+      default: ''
+    },
+    /**
+     * Whether to trigger window.resize when expanding and contracting,
+     * Can adapt to tables and forms, when the form shrinks, the form triggers resize to adapt to the height
+     */
+    triggerWindowResize: { type: Boolean },
+    /**
+     * Delayed loading time
+     */
+    lazyTime: { type: Number, default: 0 }
+  }
 
   export default defineComponent({
     name: 'CollapseContainer',
     components: {
       Skeleton,
       CollapseHeader,
-      CollapseTransition,
-    },
-    props: {
-      title: propTypes.string.def(''),
-      // Can it be expanded
-      canExpan: propTypes.bool.def(true),
-      // Warm reminder on the right side of the title
-      helpMessage: {
-        type: [Array, String] as PropType<string[] | string>,
-        default: '',
-      },
-      // Whether to trigger window.resize when expanding and contracting,
-      // Can adapt to tables and forms, when the form shrinks, the form triggers resize to adapt to the height
-      triggerWindowResize: propTypes.bool,
-      loading: propTypes.bool.def(false),
-      active: propTypes.bool.def(true),
-      // Delayed loading time
-      lazyTime: propTypes.number.def(0),
+      CollapseTransition
     },
+    props,
     setup(props) {
-      const show = ref(true);
+      const show = ref(true)
 
-      const { prefixCls } = useDesign('collapse-container');
+      const { prefixCls } = useDesign('collapse-container')
 
       /**
        * @description: Handling development events
        */
       function handleExpand() {
-        show.value = !show.value;
+        show.value = !show.value
         if (props.triggerWindowResize) {
           // 200 milliseconds here is because the expansion has animation,
-          useTimeoutFn(triggerWindowResize, 200);
+          useTimeoutFn(triggerWindowResize, 200)
         }
       }
 
-      const getBindValues = computed((): any => {
-        return props;
-      });
-
       return {
         show,
         handleExpand,
-        prefixCls,
-        getBindValues,
-      };
-    },
-  });
+        prefixCls
+      }
+    }
+  })
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-collapse-container';

+ 17 - 14
src/components/Container/src/collapse/CollapseHeader.vue

@@ -8,28 +8,31 @@
         <slot name="title"></slot>
       </template>
     </BasicTitle>
-
     <div :class="`${prefixCls}__action`">
       <slot name="action"></slot>
-      <BasicArrow v-if="canExpan" top :expand="show" @click="$emit('expand')" />
+      <BasicArrow v-if="canExpan" up :expand="show" @click="$emit('expand')" />
     </div>
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent } from 'vue';
-  import { BasicArrow, BasicTitle } from '/@/components/Basic';
-  import { propTypes } from '/@/utils/propTypes';
+  import { defineComponent } from 'vue'
+  import { BasicArrow, BasicTitle } from '/@/components/Basic'
+
+  const props = {
+    prefixCls: { type: String },
+    helpMessage: {
+      type: [Array, String] as PropType<string[] | string>,
+      default: ''
+    },
+    title: { type: String },
+    show: { type: Boolean },
+    canExpan: { type: Boolean }
+  }
 
   export default defineComponent({
     components: { BasicArrow, BasicTitle },
     inheritAttrs: false,
-    props: {
-      prefixCls: propTypes.string,
-      helpMessage: propTypes.string,
-      title: propTypes.string,
-      show: propTypes.bool,
-      canExpan: propTypes.bool,
-    },
-    emits: ['expand'],
-  });
+    props,
+    emits: ['expand']
+  })
 </script>

+ 17 - 0
src/components/Container/src/typing.ts

@@ -0,0 +1,17 @@
+export type ScrollType = 'default' | 'main'
+
+export interface CollapseContainerOptions {
+  canExpand?: boolean
+  title?: string
+  helpMessage?: Array<any> | string
+}
+export interface ScrollContainerOptions {
+  enableScroll?: boolean
+  type?: ScrollType
+}
+
+export type ScrollActionType = RefType<{
+  scrollBottom: () => void
+  getScrollWrap: () => Nullable<HTMLElement>
+  scrollTo: (top: number) => void
+}>

+ 2 - 2
src/components/ContextMenu/index.ts

@@ -1,3 +1,3 @@
-export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
+export { createContextMenu, destroyContextMenu } from './src/createContextMenu'
 
-export * from './src/types';
+export * from './src/typing'

+ 196 - 0
src/components/ContextMenu/src/ContextMenu.vue

@@ -0,0 +1,196 @@
+<script lang="tsx">
+  import type { ContextMenuItem, ItemContentProps, Axis } from './typing'
+  import type { FunctionalComponent, CSSProperties } from 'vue'
+  import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'
+  import Icon from '/@/components/Icon'
+  import { Menu, Divider } from 'ant-design-vue'
+
+  const prefixCls = 'context-menu'
+
+  const props = {
+    width: { type: Number, default: 156 },
+    customEvent: { type: Object as PropType<Event>, default: null },
+    styles: { type: Object as PropType<CSSProperties> },
+    showIcon: { type: Boolean, default: true },
+    axis: {
+      // The position of the right mouse button click
+      type: Object as PropType<Axis>,
+      default() {
+        return { x: 0, y: 0 }
+      }
+    },
+    items: {
+      // The most important list, if not, will not be displayed
+      type: Array as PropType<ContextMenuItem[]>,
+      default() {
+        return []
+      }
+    }
+  }
+
+  const ItemContent: FunctionalComponent<ItemContentProps> = props => {
+    const { item } = props
+    return (
+      <span style="display: inline-block; width: 100%; " class="px-4" onClick={props.handler.bind(null, item)}>
+        {props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
+        <span>{item.label}</span>
+      </span>
+    )
+  }
+
+  export default defineComponent({
+    name: 'ContextMenu',
+    props,
+    setup(props) {
+      const wrapRef = ref<ElRef>(null)
+      const showRef = ref(false)
+
+      const getStyle = computed((): CSSProperties => {
+        const { axis, items, styles, width } = props
+        const { x, y } = axis || { x: 0, y: 0 }
+        const menuHeight = (items || []).length * 40
+        const menuWidth = width
+        const body = document.body
+
+        const left = body.clientWidth < x + menuWidth ? x - menuWidth : x
+        const top = body.clientHeight < y + menuHeight ? y - menuHeight : y
+        return {
+          ...styles,
+          width: `${width}px`,
+          left: `${left + 1}px`,
+          top: `${top + 1}px`
+        }
+      })
+
+      onMounted(() => {
+        nextTick(() => (showRef.value = true))
+      })
+
+      onUnmounted(() => {
+        const el = unref(wrapRef)
+        el && document.body.removeChild(el)
+      })
+
+      function handleAction(item: ContextMenuItem, e: MouseEvent) {
+        const { handler, disabled } = item
+        if (disabled) {
+          return
+        }
+        showRef.value = false
+        e?.stopPropagation()
+        e?.preventDefault()
+        handler?.()
+      }
+
+      function renderMenuItem(items: ContextMenuItem[]) {
+        return items.map(item => {
+          const { disabled, label, children, divider = false } = item
+
+          const contentProps = {
+            item,
+            handler: handleAction,
+            showIcon: props.showIcon
+          }
+
+          if (!children || children.length === 0) {
+            return (
+              <>
+                <Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
+                  <ItemContent {...contentProps} />
+                </Menu.Item>
+                {divider ? <Divider key={`d-${label}`} /> : null}
+              </>
+            )
+          }
+          if (!unref(showRef)) return null
+
+          return (
+            <Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
+              {{
+                title: () => <ItemContent {...contentProps} />,
+                default: () => renderMenuItem(children)
+              }}
+            </Menu.SubMenu>
+          )
+        })
+      }
+      return () => {
+        if (!unref(showRef)) {
+          return null
+        }
+        const { items } = props
+        return (
+          <Menu inlineIndent={12} mode="vertical" class={prefixCls} ref={wrapRef} style={unref(getStyle)}>
+            {renderMenuItem(items)}
+          </Menu>
+        )
+      }
+    }
+  })
+</script>
+<style lang="less">
+  @default-height: 42px !important;
+
+  @small-height: 36px !important;
+
+  @large-height: 36px !important;
+
+  .item-style() {
+    li {
+      display: inline-block;
+      width: 100%;
+      height: @default-height;
+      margin: 0 !important;
+      line-height: @default-height;
+
+      span {
+        line-height: @default-height;
+      }
+
+      > div {
+        margin: 0 !important;
+      }
+
+      &:not(.ant-menu-item-disabled):hover {
+        color: @text-color-base;
+        background-color: @item-hover-bg;
+      }
+    }
+  }
+
+  .context-menu {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 200;
+    display: block;
+    width: 156px;
+    margin: 0;
+    list-style: none;
+    background-color: @component-background;
+    border: 1px solid rgba(0, 0, 0, 0.08);
+    border-radius: 0.25rem;
+    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.1), 0 1px 5px 0 rgba(0, 0, 0, 0.06);
+    background-clip: padding-box;
+    user-select: none;
+
+    .item-style();
+
+    .ant-divider {
+      margin: 0 0;
+    }
+
+    &__popup {
+      .ant-divider {
+        margin: 0 0;
+      }
+
+      .item-style();
+    }
+
+    .ant-menu-submenu-title,
+    .ant-menu-item {
+      padding: 0 !important;
+    }
+  }
+</style>

+ 44 - 42
src/components/ContextMenu/src/createContextMenu.ts

@@ -1,73 +1,75 @@
-import contextMenuVue from './index';
-import { isClient } from '/@/utils/is';
-import { CreateContextOptions, ContextMenuProps } from './types';
-import { createVNode, render } from 'vue';
+import contextMenuVue from './ContextMenu.vue'
+import { isClient } from '/@/utils/is'
+import { CreateContextOptions, ContextMenuProps } from './typing'
+import { createVNode, render } from 'vue'
 
 const menuManager: {
-  domList: Element[];
-  resolve: Fn;
+  domList: Element[]
+  resolve: Fn
 } = {
   domList: [],
-  resolve: () => {},
-};
+  resolve: () => {}
+}
 
 export const createContextMenu = function (options: CreateContextOptions) {
-  const { event } = options || {};
+  const { event } = options || {}
 
-  event && event?.preventDefault();
+  event && event?.preventDefault()
 
-  if (!isClient) return;
-  return new Promise((resolve) => {
-    const body = document.body;
+  if (!isClient) {
+    return
+  }
+  return new Promise(resolve => {
+    const body = document.body
 
-    const container = document.createElement('div');
-    const propsData: Partial<ContextMenuProps> = {};
+    const container = document.createElement('div')
+    const propsData: Partial<ContextMenuProps> = {}
     if (options.styles) {
-      propsData.styles = options.styles;
+      propsData.styles = options.styles
     }
 
     if (options.items) {
-      propsData.items = options.items;
+      propsData.items = options.items
     }
 
     if (options.event) {
-      propsData.customEvent = event;
-      propsData.axis = { x: event.clientX, y: event.clientY };
+      propsData.customEvent = event
+      propsData.axis = { x: event.clientX, y: event.clientY }
     }
 
-    const vm = createVNode(contextMenuVue, propsData);
-    render(vm, container);
+    const vm = createVNode(contextMenuVue, propsData)
+    render(vm, container)
 
     const handleClick = function () {
-      menuManager.resolve('');
-    };
+      menuManager.resolve('')
+    }
 
-    menuManager.domList.push(container);
+    menuManager.domList.push(container)
 
     const remove = function () {
       menuManager.domList.forEach((dom: Element) => {
         try {
-          dom && body.removeChild(dom);
+          dom && body.removeChild(dom)
         } catch (error) {}
-      });
-      body.removeEventListener('click', handleClick);
-      body.removeEventListener('scroll', handleClick);
-    };
+      })
+      body.removeEventListener('click', handleClick)
+      body.removeEventListener('scroll', handleClick)
+    }
 
-    menuManager.resolve = function (...arg: any) {
-      remove();
-      resolve(arg[0]);
-    };
-    remove();
-    body.appendChild(container);
-    body.addEventListener('click', handleClick);
-    body.addEventListener('scroll', handleClick);
-  });
-};
+    menuManager.resolve = function (arg) {
+      remove()
+      resolve(arg)
+    }
+    remove()
+    body.appendChild(container)
+    body.addEventListener('click', handleClick)
+    body.addEventListener('scroll', handleClick)
+  })
+}
 
 export const destroyContextMenu = function () {
   if (menuManager) {
-    menuManager.resolve('');
-    menuManager.domList = [];
+    menuManager.resolve('')
+    menuManager.domList = []
   }
-};
+}

+ 35 - 0
src/components/ContextMenu/src/typing.ts

@@ -0,0 +1,35 @@
+export interface Axis {
+  x: number
+  y: number
+}
+
+export interface ContextMenuItem {
+  label: string
+  icon?: string
+  disabled?: boolean
+  handler?: Fn
+  divider?: boolean
+  children?: ContextMenuItem[]
+}
+export interface CreateContextOptions {
+  event: MouseEvent
+  icon?: string
+  styles?: any
+  items?: ContextMenuItem[]
+}
+
+export interface ContextMenuProps {
+  event?: MouseEvent
+  styles?: any
+  items: ContextMenuItem[]
+  customEvent?: MouseEvent
+  axis?: Axis
+  width?: number
+  showIcon?: boolean
+}
+
+export interface ItemContentProps {
+  showIcon: boolean | undefined
+  item: ContextMenuItem
+  handler: Fn
+}

+ 5 - 3
src/components/CountDown/index.ts

@@ -1,4 +1,6 @@
-import CountButton from './src/CountButton.vue';
-import CountdownInput from './src/CountdownInput.vue';
+import { withInstall } from '/@/utils'
+import countButton from './src/CountButton.vue'
+import countdownInput from './src/CountdownInput.vue'
 
-export { CountdownInput, CountButton };
+export const CountdownInput = withInstall(countdownInput)
+export const CountButton = withInstall(countButton)

+ 36 - 34
src/components/CountDown/src/CountButton.vue

@@ -1,60 +1,62 @@
 <template>
   <Button v-bind="$attrs" :disabled="isStart" @click="handleStart" :loading="loading">
-    {{
-      !isStart
-        ? t('component.countdown.normalText')
-        : t('component.countdown.sendText', [currentCount])
-    }}
+    {{ getButtonText }}
   </Button>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, PropType, watchEffect } from 'vue';
+  import { defineComponent, ref, watchEffect, computed, unref } from 'vue'
+  import { Button } from 'ant-design-vue'
+  import { useCountdown } from './useCountdown'
+  import { isFunction } from '/@/utils/is'
+  import { useI18n } from '/@/hooks/web/useI18n'
 
-  import { Button } from 'ant-design-vue';
-
-  import { useCountdown } from './useCountdown';
-  import { isFunction } from '/@/utils/is';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { propTypes } from '/@/utils/propTypes';
+  const props = {
+    value: { type: [Object, Number, String, Array] },
+    count: { type: Number, default: 60 },
+    beforeStartFunc: {
+      type: Function as PropType<() => Promise<boolean>>,
+      default: null
+    }
+  }
 
   export default defineComponent({
     name: 'CountButton',
     components: { Button },
-    props: {
-      value: propTypes.any,
-      count: propTypes.number.def(60),
-      beforeStartFunc: {
-        type: Function as PropType<() => boolean>,
-        default: null,
-      },
-    },
+    props,
     setup(props) {
-      const loading = ref(false);
+      const loading = ref(false)
+
+      const { currentCount, isStart, start, reset } = useCountdown(props.count)
+      const { t } = useI18n()
 
-      const { currentCount, isStart, start, reset } = useCountdown(props.count);
-      const { t } = useI18n();
+      const getButtonText = computed(() => {
+        return !unref(isStart)
+          ? t('component.countdown.normalText')
+          : t('component.countdown.sendText', [unref(currentCount)])
+      })
 
       watchEffect(() => {
-        props.value === undefined && reset();
-      });
+        props.value === undefined && reset()
+      })
+
       /**
        * @description: Judge whether there is an external function before execution, and decide whether to start after execution
        */
       async function handleStart() {
-        const { beforeStartFunc } = props;
+        const { beforeStartFunc } = props
         if (beforeStartFunc && isFunction(beforeStartFunc)) {
-          loading.value = true;
+          loading.value = true
           try {
-            const canStart = await beforeStartFunc();
-            canStart && start();
+            const canStart = await beforeStartFunc()
+            canStart && start()
           } finally {
-            loading.value = false;
+            loading.value = false
           }
         } else {
-          start();
+          start()
         }
       }
-      return { handleStart, isStart, currentCount, loading, t };
-    },
-  });
+      return { handleStart, currentCount, loading, getButtonText, isStart }
+    }
+  })
 </script>

+ 21 - 22
src/components/CountDown/src/CountdownInput.vue

@@ -6,35 +6,34 @@
   </AInput>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType } from 'vue';
+  import { defineComponent, PropType } from 'vue'
+  import { Input } from 'ant-design-vue'
+  import CountButton from './CountButton.vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { useRuleFormItem } from '/@/hooks/component/useFormItem'
 
-  import { Input } from 'ant-design-vue';
-  import CountButton from './CountButton.vue';
-
-  import { propTypes } from '/@/utils/propTypes';
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
+  const props = {
+    value: { type: String },
+    size: { type: String, validator: v => ['default', 'large', 'small'].includes(v) },
+    count: { type: Number, default: 60 },
+    sendCodeApi: {
+      type: Function as PropType<() => Promise<boolean>>,
+      default: null
+    }
+  }
 
   export default defineComponent({
     name: 'CountDownInput',
     components: { [Input.name]: Input, CountButton },
     inheritAttrs: false,
-    props: {
-      value: propTypes.string,
-      size: propTypes.oneOf(['default', 'large', 'small']),
-      count: propTypes.number.def(60),
-      sendCodeApi: {
-        type: Function as PropType<() => boolean>,
-        default: null,
-      },
-    },
+    props,
     setup(props) {
-      const { prefixCls } = useDesign('countdown-input');
-      const [state] = useRuleFormItem(props);
-      return { prefixCls, state };
-    },
-  });
+      const { prefixCls } = useDesign('countdown-input')
+      const [state] = useRuleFormItem(props)
+
+      return { prefixCls, state }
+    }
+  })
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-countdown-input';

+ 3 - 3
src/components/CountTo/index.ts

@@ -1,4 +1,4 @@
-// Transform vue-count-to to support vue3 version
+import { withInstall } from '/@/utils'
+import countTo from './src/CountTo.vue'
 
-import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
-export const CountTo = createAsyncComponent(() => import('./src/index.vue'));
+export const CountTo = withInstall(countTo)

+ 110 - 0
src/components/CountTo/src/CountTo.vue

@@ -0,0 +1,110 @@
+<template>
+  <span :style="{ color }">
+    {{ value }}
+  </span>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue'
+  import { useTransition, TransitionPresets } from '@vueuse/core'
+  import { isNumber } from '/@/utils/is'
+
+  const props = {
+    startVal: { type: Number, default: 0 },
+    endVal: { type: Number, default: 2021 },
+    duration: { type: Number, default: 1500 },
+    autoplay: { type: Boolean, default: true },
+    decimals: {
+      type: Number,
+      default: 0,
+      validator(value: number) {
+        return value >= 0
+      }
+    },
+    prefix: { type: String, default: '' },
+    suffix: { type: String, default: '' },
+    separator: { type: String, default: ',' },
+    decimal: { type: String, default: '.' },
+    /**
+     * font color
+     */
+    color: { type: String },
+    /**
+     * Turn on digital animation
+     */
+    useEasing: { type: Boolean, default: true },
+    /**
+     * Digital animation
+     */
+    transition: { type: String, default: 'linear' }
+  }
+
+  export default defineComponent({
+    name: 'CountTo',
+    props,
+    emits: ['onStarted', 'onFinished'],
+    setup(props, { emit }) {
+      const source = ref(props.startVal)
+      const disabled = ref(false)
+      let outputValue = useTransition(source)
+
+      const value = computed(() => formatNumber(unref(outputValue)))
+
+      watchEffect(() => {
+        source.value = props.startVal
+      })
+
+      watch([() => props.startVal, () => props.endVal], () => {
+        if (props.autoplay) {
+          start()
+        }
+      })
+
+      onMounted(() => {
+        props.autoplay && start()
+      })
+
+      function start() {
+        run()
+        source.value = props.endVal
+      }
+
+      function reset() {
+        source.value = props.startVal
+        run()
+      }
+
+      function run() {
+        outputValue = useTransition(source, {
+          disabled,
+          duration: props.duration,
+          onFinished: () => emit('onFinished'),
+          onStarted: () => emit('onStarted'),
+          ...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {})
+        })
+      }
+
+      function formatNumber(num: number | string) {
+        if (!num) {
+          return ''
+        }
+        const { decimals, decimal, separator, suffix, prefix } = props
+        num = Number(num).toFixed(decimals)
+        num += ''
+
+        const x = num.split('.')
+        let x1 = x[0]
+        const x2 = x.length > 1 ? decimal + x[1] : ''
+
+        const rgx = /(\d+)(\d{3})/
+        if (separator && !isNumber(separator)) {
+          while (rgx.test(x1)) {
+            x1 = x1.replace(rgx, '$1' + separator + '$2')
+          }
+        }
+        return prefix + x1 + x2 + suffix
+      }
+
+      return { value, start, reset }
+    }
+  })
+</script>

+ 3 - 3
src/components/Cropper/index.ts

@@ -1,4 +1,4 @@
-import type Cropper from 'cropperjs';
+import type Cropper from 'cropperjs'
 
-export type { Cropper };
-export { default as CropperImage } from './src/index.vue';
+export type { Cropper }
+export { default as CropperImage } from './src/Cropper.vue'

+ 120 - 0
src/components/Cropper/src/Cropper.vue

@@ -0,0 +1,120 @@
+<template>
+  <div :class="$attrs.class" :style="getWrapperStyle">
+    <img v-show="isReady" ref="imgElRef" :src="src" :alt="alt" :crossorigin="crossorigin" :style="getImageStyle" />
+  </div>
+</template>
+<script lang="ts">
+  import type { CSSProperties } from 'vue'
+
+  import { defineComponent, onMounted, ref, unref, computed } from 'vue'
+
+  import Cropper from 'cropperjs'
+  import 'cropperjs/dist/cropper.css'
+
+  type Options = Cropper.Options
+
+  const defaultOptions: Options = {
+    aspectRatio: 16 / 9,
+    zoomable: true,
+    zoomOnTouch: true,
+    zoomOnWheel: true,
+    cropBoxMovable: true,
+    cropBoxResizable: true,
+    toggleDragModeOnDblclick: true,
+    autoCrop: true,
+    background: true,
+    highlight: true,
+    center: true,
+    responsive: true,
+    restore: true,
+    checkCrossOrigin: true,
+    checkOrientation: true,
+    scalable: true,
+    modal: true,
+    guides: true,
+    movable: true,
+    rotatable: true
+  }
+
+  export default defineComponent({
+    name: 'CropperImage',
+    props: {
+      src: {
+        type: String,
+        required: true
+      },
+      alt: {
+        type: String
+      },
+      height: {
+        type: [String, Number],
+        default: '360px'
+      },
+      crossorigin: {
+        type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
+        default: undefined
+      },
+      imageStyle: {
+        type: Object as PropType<CSSProperties>,
+        default: () => ({})
+      },
+      options: {
+        type: Object as PropType<Options>,
+        default: () => ({})
+      }
+    },
+    emits: ['cropperedInfo'],
+    setup(props, ctx) {
+      const imgElRef = ref<ElRef<HTMLImageElement>>(null)
+      const cropper: any = ref<Nullable<Cropper>>(null)
+
+      const isReady = ref(false)
+
+      const getImageStyle = computed((): CSSProperties => {
+        return {
+          height: props.height,
+          maxWidth: '100%',
+          ...props.imageStyle
+        }
+      })
+
+      const getWrapperStyle = computed((): CSSProperties => {
+        const { height } = props
+        return { height: `${height}`.replace(/px/, '') + 'px' }
+      })
+
+      async function init() {
+        const imgEl = unref(imgElRef)
+        if (!imgEl) {
+          return
+        }
+        cropper.value = new Cropper(imgEl, {
+          ...defaultOptions,
+          ready: () => {
+            isReady.value = true
+          },
+          ...props.options
+        })
+      }
+
+      // event: return base64 and width and height information after cropping
+      const croppered = (): void => {
+        let imgInfo = cropper.value.getData()
+        cropper.value.getCroppedCanvas().toBlob(blob => {
+          let fileReader: FileReader = new FileReader()
+          fileReader.onloadend = e => {
+            ctx.emit('cropperedInfo', {
+              imgBase64: e.target?.result ?? '',
+              imgInfo
+            })
+          }
+          fileReader.readAsDataURL(blob)
+        }, 'image/jpeg')
+      }
+
+      onMounted(init)
+
+      return { imgElRef, getWrapperStyle, getImageStyle, isReady, croppered }
+    }
+  })
+</script>

+ 4 - 4
src/components/Description/index.ts

@@ -1,5 +1,5 @@
-import Description from './src/index.vue';
+import Description from './src/Description.vue'
 
-export { Description };
-export * from './src/types';
-export { useDescription } from './src/useDescription';
+export { Description }
+export * from './src/types'
+export { useDescription } from './src/useDescription'

+ 160 - 0
src/components/Description/src/Description.vue

@@ -0,0 +1,160 @@
+<script lang="tsx">
+  import type { DescOptions, DescInstance, DescItem } from './types'
+  import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
+  import type { CSSProperties } from 'vue'
+  import type { CollapseContainerOptions } from '/@/components/Container/index'
+
+  import { defineComponent, computed, ref, unref } from 'vue'
+  import { get } from 'lodash-es'
+  import { Descriptions } from 'ant-design-vue'
+  import { CollapseContainer } from '/@/components/Container/index'
+
+  import { useDesign } from '/@/hooks/web/useDesign'
+
+  import { isFunction } from '/@/utils/is'
+  import { getSlot } from '/@/utils/helper/tsxHelper'
+
+  import descProps from './props'
+  import { useAttrs } from '/@/hooks/core/useAttrs'
+
+  export default defineComponent({
+    name: 'Description',
+    props: descProps,
+    emits: ['register'],
+    setup(props, { slots, emit }) {
+      const propsRef = ref<Partial<DescOptions> | null>(null)
+
+      const { prefixCls } = useDesign('description')
+      const attrs = useAttrs()
+
+      // Custom title component: get title
+      const getMergeProps = computed(() => {
+        return {
+          ...props,
+          ...(unref(propsRef) as Recordable)
+        } as DescOptions
+      })
+
+      const getProps = computed(() => {
+        const opt = {
+          ...unref(getMergeProps),
+          title: undefined
+        }
+        return opt as DescOptions
+      })
+
+      /**
+       * @description: Whether to setting title
+       */
+      const useWrapper = computed(() => !!unref(getMergeProps).title)
+
+      /**
+       * @description: Get configuration Collapse
+       */
+      const getCollapseOptions = computed((): CollapseContainerOptions => {
+        return {
+          // Cannot be expanded by default
+          canExpand: false,
+          ...unref(getProps).collapseOptions
+        }
+      })
+
+      const getDescriptionsProps = computed(() => {
+        return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps
+      })
+
+      /**
+       * @description:设置desc
+       */
+      function setDescProps(descProps: Partial<DescOptions>): void {
+        // Keep the last setDrawerProps
+        propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable
+      }
+
+      // Prevent line breaks
+      function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
+        if (!labelStyle && !labelMinWidth) {
+          return label
+        }
+
+        const labelStyles: CSSProperties = {
+          ...labelStyle,
+
+          minWidth: `${labelMinWidth}px `
+        }
+        return <div style={labelStyles}>{label}</div>
+      }
+
+      function renderItem() {
+        const { schema, data } = unref(getProps)
+        return unref(schema)
+          .map(item => {
+            const { render, field, span, show, contentMinWidth } = item
+
+            if (show && isFunction(show) && !show(data)) {
+              return null
+            }
+
+            const getContent = () => {
+              const _data = unref(getProps)?.data
+              if (!_data) return null
+              const getField = get(_data, field)
+              return isFunction(render) ? render(getField, _data) : getField ?? ''
+            }
+
+            const width = contentMinWidth
+            return (
+              <Descriptions.Item label={renderLabel(item)} key={field} span={span}>
+                {() => {
+                  if (!contentMinWidth) {
+                    return getContent()
+                  }
+                  const style: CSSProperties = {
+                    minWidth: `${width}px`
+                  }
+                  return <div style={style}>{getContent()}</div>
+                }}
+              </Descriptions.Item>
+            )
+          })
+          .filter(item => !!item)
+      }
+
+      const renderDesc = () => {
+        return (
+          <Descriptions class={`${prefixCls}`} {...(unref(getDescriptionsProps) as any)}>
+            {renderItem()}
+          </Descriptions>
+        )
+      }
+
+      const renderContainer = () => {
+        const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>
+        // Reduce the dom level
+
+        if (!props.useCollapse) {
+          return content
+        }
+
+        const { canExpand, helpMessage } = unref(getCollapseOptions)
+        const { title } = unref(getMergeProps)
+
+        return (
+          <CollapseContainer title={title} canExpan={canExpand} helpMessage={helpMessage}>
+            {{
+              default: () => content,
+              action: () => getSlot(slots, 'action')
+            }}
+          </CollapseContainer>
+        )
+      }
+
+      const methods: DescInstance = {
+        setDescProps
+      }
+
+      emit('register', methods)
+      return () => (unref(useWrapper) ? renderContainer() : renderDesc())
+    }
+  })
+</script>

+ 90 - 115
src/components/Drawer/src/BasicDrawer.vue

@@ -1,12 +1,7 @@
 <template>
   <Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
     <template #title v-if="!$slots.title">
-      <DrawerHeader
-        :title="getMergeProps.title"
-        :isDetail="isDetail"
-        :showDetailBack="showDetailBack"
-        @close="onClose"
-      >
+      <DrawerHeader :title="getMergeProps.title" :isDetail="isDetail" :showDetailBack="showDetailBack" @close="onClose">
         <template #titleToolbar>
           <slot name="titleToolbar"></slot>
         </template>
@@ -31,33 +26,23 @@
   </Drawer>
 </template>
 <script lang="ts">
-  import type { DrawerInstance, DrawerProps } from './types';
-  import type { CSSProperties } from 'vue';
-
-  import {
-    defineComponent,
-    ref,
-    computed,
-    watchEffect,
-    watch,
-    unref,
-    nextTick,
-    toRaw,
-    getCurrentInstance,
-  } from 'vue';
-  import { Drawer } from 'ant-design-vue';
-
-  import { useI18n } from '/@/hooks/web/useI18n';
-
-  import { isFunction, isNumber } from '/@/utils/is';
-  import { deepMerge } from '/@/utils';
-  import DrawerFooter from './components/DrawerFooter.vue';
-  import DrawerHeader from './components/DrawerHeader.vue';
-  import { ScrollContainer } from '/@/components/Container';
-
-  import { basicProps } from './props';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { useAttrs } from '/@/hooks/core/useAttrs';
+  import type { DrawerInstance, DrawerProps } from './types'
+  import type { CSSProperties } from 'vue'
+
+  import { defineComponent, ref, computed, watchEffect, watch, unref, nextTick, toRaw, getCurrentInstance } from 'vue'
+  import { Drawer } from 'ant-design-vue'
+
+  import { useI18n } from '/@/hooks/web/useI18n'
+
+  import { isFunction, isNumber } from '/@/utils/is'
+  import { deepMerge } from '/@/utils'
+  import DrawerFooter from './components/DrawerFooter.vue'
+  import DrawerHeader from './components/DrawerHeader.vue'
+  import { ScrollContainer } from '/@/components/Container'
+
+  import { basicProps } from './props'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { useAttrs } from '/@/hooks/core/useAttrs'
 
   export default defineComponent({
     components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
@@ -65,125 +50,115 @@
     props: basicProps,
     emits: ['visible-change', 'ok', 'close', 'register'],
     setup(props, { emit }) {
-      const visibleRef = ref(false);
-      const attrs = useAttrs();
-      const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
+      const visibleRef = ref(false)
+      const attrs = useAttrs()
+      const propsRef = ref<Partial<Nullable<DrawerProps>>>(null)
 
-      const { t } = useI18n();
-      const { prefixVar, prefixCls } = useDesign('basic-drawer');
+      const { t } = useI18n()
+      const { prefixVar, prefixCls } = useDesign('basic-drawer')
 
       const drawerInstance: DrawerInstance = {
         setDrawerProps: setDrawerProps,
-        emitVisible: undefined,
-      };
+        emitVisible: undefined
+      }
+
+      const instance = getCurrentInstance()
 
-      const instance = getCurrentInstance();
+      instance && emit('register', drawerInstance, instance.uid)
 
-      instance && emit('register', drawerInstance, instance.uid);
+      const getMergeProps = computed((): DrawerProps => {
+        return deepMerge(toRaw(props), unref(propsRef))
+      })
 
-      const getMergeProps = computed(
-        (): DrawerProps => {
-          return deepMerge(toRaw(props), unref(propsRef));
+      const getProps = computed((): DrawerProps => {
+        const opt = {
+          placement: 'right',
+          ...unref(attrs),
+          ...unref(getMergeProps),
+          visible: unref(visibleRef)
         }
-      );
-
-      const getProps = computed(
-        (): DrawerProps => {
-          const opt = {
-            placement: 'right',
-            ...unref(attrs),
-            ...unref(getMergeProps),
-            visible: unref(visibleRef),
-          };
-          opt.title = undefined;
-          const { isDetail, width, wrapClassName, getContainer } = opt;
-          if (isDetail) {
-            if (!width) {
-              opt.width = '100%';
-            }
-            const detailCls = `${prefixCls}__detail`;
-            opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
-
-            if (!getContainer) {
-              // TODO type error?
-              opt.getContainer = `.${prefixVar}-layout-content` as any;
-            }
+        opt.title = undefined
+        const { isDetail, width, wrapClassName, getContainer } = opt
+        if (isDetail) {
+          if (!width) {
+            opt.width = '100%'
+          }
+          const detailCls = `${prefixCls}__detail`
+          opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls
+
+          if (!getContainer) {
+            // TODO type error?
+            opt.getContainer = `.${prefixVar}-layout-content` as any
           }
-          return opt as DrawerProps;
         }
-      );
-
-      const getBindValues = computed(
-        (): DrawerProps => {
-          return {
-            ...attrs,
-            ...unref(getProps),
-          };
+        return opt as DrawerProps
+      })
+
+      const getBindValues = computed((): DrawerProps => {
+        return {
+          ...attrs,
+          ...unref(getProps)
         }
-      );
+      })
 
       // Custom implementation of the bottom button,
       const getFooterHeight = computed(() => {
-        const { footerHeight, showFooter } = unref(getProps);
+        const { footerHeight, showFooter } = unref(getProps)
         if (showFooter && footerHeight) {
-          return isNumber(footerHeight)
-            ? `${footerHeight}px`
-            : `${footerHeight.replace('px', '')}px`;
+          return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`
         }
-        return `0px`;
-      });
-
-      const getScrollContentStyle = computed(
-        (): CSSProperties => {
-          const footerHeight = unref(getFooterHeight);
-          return {
-            position: 'relative',
-            height: `calc(100% - ${footerHeight})`,
-          };
+        return `0px`
+      })
+
+      const getScrollContentStyle = computed((): CSSProperties => {
+        const footerHeight = unref(getFooterHeight)
+        return {
+          position: 'relative',
+          height: `calc(100% - ${footerHeight})`
         }
-      );
+      })
 
       const getLoading = computed(() => {
-        return !!unref(getProps)?.loading;
-      });
+        return !!unref(getProps)?.loading
+      })
 
       watchEffect(() => {
-        visibleRef.value = props.visible;
-      });
+        visibleRef.value = props.visible
+      })
 
       watch(
         () => visibleRef.value,
-        (visible) => {
+        visible => {
           nextTick(() => {
-            emit('visible-change', visible);
-            instance && drawerInstance.emitVisible?.(visible, instance.uid);
-          });
+            emit('visible-change', visible)
+            instance && drawerInstance.emitVisible?.(visible, instance.uid)
+          })
         }
-      );
+      )
 
       // Cancel event
       async function onClose(e: Recordable) {
-        const { closeFunc } = unref(getProps);
-        emit('close', e);
+        const { closeFunc } = unref(getProps)
+        emit('close', e)
         if (closeFunc && isFunction(closeFunc)) {
-          const res = await closeFunc();
-          visibleRef.value = !res;
-          return;
+          const res = await closeFunc()
+          visibleRef.value = !res
+          return
         }
-        visibleRef.value = false;
+        visibleRef.value = false
       }
 
       function setDrawerProps(props: Partial<DrawerProps>): void {
         // Keep the last setDrawerProps
-        propsRef.value = deepMerge(unref(propsRef) || {}, props);
+        propsRef.value = deepMerge(unref(propsRef) || {}, props)
 
         if (Reflect.has(props, 'visible')) {
-          visibleRef.value = !!props.visible;
+          visibleRef.value = !!props.visible
         }
       }
 
       function handleOk() {
-        emit('ok');
+        emit('ok')
       }
 
       return {
@@ -196,10 +171,10 @@
         getLoading,
         getBindValues,
         getFooterHeight,
-        handleOk,
-      };
-    },
-  });
+        handleOk
+      }
+    }
+  })
 </script>
 <style lang="less">
   @header-height: 60px;

+ 18 - 20
src/components/Drawer/src/components/DrawerFooter.vue

@@ -25,44 +25,42 @@
   </div>
 </template>
 <script lang="ts">
-  import type { CSSProperties } from 'vue';
-  import { defineComponent, computed } from 'vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
+  import type { CSSProperties } from 'vue'
+  import { defineComponent, computed } from 'vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
 
-  import { footerProps } from '../props';
+  import { footerProps } from '../props'
   export default defineComponent({
     name: 'BasicDrawerFooter',
     props: {
       ...footerProps,
       height: {
         type: String,
-        default: '60px',
-      },
+        default: '60px'
+      }
     },
     emits: ['ok', 'close'],
     setup(props, { emit }) {
-      const { prefixCls } = useDesign('basic-drawer-footer');
+      const { prefixCls } = useDesign('basic-drawer-footer')
 
-      const getStyle = computed(
-        (): CSSProperties => {
-          const heightStr = `${props.height}`;
-          return {
-            height: heightStr,
-            lineHeight: heightStr,
-          };
+      const getStyle = computed((): CSSProperties => {
+        const heightStr = `${props.height}`
+        return {
+          height: heightStr,
+          lineHeight: heightStr
         }
-      );
+      })
 
       function handleOk() {
-        emit('ok');
+        emit('ok')
       }
 
       function handleClose() {
-        emit('close');
+        emit('close')
       }
-      return { handleOk, prefixCls, handleClose, getStyle };
-    },
-  });
+      return { handleOk, prefixCls, handleClose, getStyle }
+    }
+  })
 </script>
 
 <style lang="less">

+ 3 - 7
src/components/FlowChart/index.ts

@@ -1,8 +1,4 @@
-import type { App } from 'vue';
-import flowChart from './src/index.vue';
+import { withInstall } from '/@/utils'
+import flowChart from './src/FlowChart.vue'
 
-export const FlowChart = Object.assign(flowChart, {
-  install(app: App) {
-    app.component(flowChart.name, flowChart);
-  },
-});
+export const FlowChart = withInstall(flowChart)

+ 155 - 0
src/components/FlowChart/src/FlowChart.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="h-full" :class="prefixCls">
+    <FlowChartToolbar :prefixCls="prefixCls" v-if="toolbar" @view-data="handlePreview" />
+    <div ref="lfElRef" class="h-full"></div>
+    <BasicModal @register="register" title="流程数据" width="50%">
+      <JsonPreview :data="graphData" />
+    </BasicModal>
+  </div>
+</template>
+<script lang="ts">
+  import type { Definition } from '@logicflow/core'
+
+  import { defineComponent, ref, onMounted, unref, nextTick, computed, watch } from 'vue'
+
+  import FlowChartToolbar from './FlowChartToolbar.vue'
+  import LogicFlow from '@logicflow/core'
+  import { Snapshot, BpmnElement, Menu, DndPanel } from '@logicflow/extension'
+
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { useAppStore } from '/@/store/modules/app'
+  import { createFlowChartContext } from './useFlowContext'
+
+  import { toLogicFlowData } from './adpterForTurbo'
+  import { useModal, BasicModal } from '/@/components/Modal'
+  import { JsonPreview } from '/@/components/CodeEditor'
+
+  import '@logicflow/core/dist/style/index.css'
+  import '@logicflow/extension/lib/style/index.css'
+  export default defineComponent({
+    name: 'FlowChart',
+    components: { BasicModal, FlowChartToolbar, JsonPreview },
+    props: {
+      flowOptions: {
+        type: Object as PropType<Definition>,
+        default: () => ({})
+      },
+
+      data: {
+        type: Object as PropType<any>,
+        default: () => ({})
+      },
+
+      toolbar: {
+        type: Boolean,
+        default: true
+      }
+    },
+    setup(props) {
+      const lfElRef = ref<ElRef>(null)
+      const graphData = ref<Recordable>({})
+
+      const lfInstance = ref<Nullable<LogicFlow>>(null)
+
+      const { prefixCls } = useDesign('flow-chart')
+      const appStore = useAppStore()
+      const [register, { openModal }] = useModal()
+      createFlowChartContext({
+        logicFlow: lfInstance as unknown as LogicFlow
+      })
+
+      const getFlowOptions = computed(() => {
+        const { flowOptions } = props
+
+        const defaultOptions: Partial<Definition> = {
+          grid: true,
+          background: {
+            color: appStore.getDarkMode === 'light' ? '#f7f9ff' : '#151515'
+          },
+          keyboard: {
+            enabled: true
+          },
+          ...flowOptions
+        }
+        return defaultOptions as Definition
+      })
+
+      watch(
+        () => props.data,
+        () => {
+          onRender()
+        }
+      )
+
+      // TODO
+      // watch(
+      //   () => appStore.getDarkMode,
+      //   () => {
+      //     init();
+      //   }
+      // );
+
+      watch(
+        () => unref(getFlowOptions),
+        options => {
+          unref(lfInstance)?.updateEditConfig(options)
+        }
+      )
+
+      // init logicFlow
+      async function init() {
+        await nextTick()
+
+        const lfEl = unref(lfElRef)
+        if (!lfEl) {
+          return
+        }
+
+        // Canvas configuration
+        LogicFlow.use(Snapshot)
+        // Use the bpmn plug-in to introduce bpmn elements, which can be used after conversion in turbo
+        LogicFlow.use(BpmnElement)
+        // Start the right-click menu
+        LogicFlow.use(Menu)
+        LogicFlow.use(DndPanel)
+
+        lfInstance.value = new LogicFlow({
+          ...unref(getFlowOptions),
+          container: lfEl
+        })
+        unref(lfInstance)?.setDefaultEdgeType('line')
+        onRender()
+      }
+
+      async function onRender() {
+        await nextTick()
+        const lf = unref(lfInstance)
+        if (!lf) {
+          return
+        }
+        const lFData = toLogicFlowData(props.data)
+        lf.render(lFData)
+      }
+
+      function handlePreview() {
+        const lf = unref(lfInstance)
+        if (!lf) {
+          return
+        }
+        graphData.value = unref(lf).getGraphData()
+
+        openModal()
+      }
+
+      onMounted(init)
+
+      return {
+        register,
+        prefixCls,
+        lfElRef,
+        handlePreview,
+        graphData
+      }
+    }
+  })
+</script>

+ 122 - 114
src/components/Form/src/BasicForm.vue

@@ -1,6 +1,12 @@
 <template>
-  <Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
-    <Row :style="getRowWrapStyle">
+  <Form
+    v-bind="{ ...$attrs, ...$props, ...getProps }"
+    :class="getFormClass"
+    ref="formElRef"
+    :model="formModel"
+    @keypress.enter="handleEnterPress"
+  >
+    <Row v-bind="{ ...getRow }">
       <slot name="formHeader"></slot>
       <template v-for="schema in getSchema" :key="schema.field">
         <FormItem
@@ -19,10 +25,7 @@
       </template>
 
       <FormAction v-bind="{ ...getProps, ...advanceState }" @toggle-advanced="handleToggleAdvanced">
-        <template
-          #[item]="data"
-          v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
-        >
+        <template #[item]="data" v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']">
           <slot :name="item" v-bind="data"></slot>
         </template>
       </FormAction>
@@ -31,40 +34,32 @@
   </Form>
 </template>
 <script lang="ts">
-  import type { FormActionType, FormProps, FormSchema } from './types/form';
-  import type { AdvanceState } from './types/hooks';
-  import type { CSSProperties, Ref } from 'vue';
-
-  import {
-    defineComponent,
-    reactive,
-    ref,
-    computed,
-    unref,
-    onMounted,
-    watch,
-    toRefs,
-    nextTick,
-  } from 'vue';
-  import { Form, Row } from 'ant-design-vue';
-  import FormItem from './components/FormItem.vue';
-  import FormAction from './components/FormAction.vue';
-
-  import { dateItemType } from './helper';
-  import { dateUtil } from '/@/utils/dateUtil';
+  import type { FormActionType, FormProps, FormSchema } from './types/form'
+  import type { AdvanceState } from './types/hooks'
+  import type { CSSProperties, Ref } from 'vue'
+
+  import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue'
+  import { Form, Row } from 'ant-design-vue'
+  import FormItem from './components/FormItem.vue'
+  import FormAction from './components/FormAction.vue'
+
+  import { dateItemType } from './helper'
+  import { dateUtil } from '/@/utils/dateUtil'
 
   // import { cloneDeep } from 'lodash-es';
-  import { deepMerge } from '/@/utils';
+  import { deepMerge } from '/@/utils'
 
-  import { useFormValues } from './hooks/useFormValues';
-  import useAdvanced from './hooks/useAdvanced';
-  import { useFormEvents } from './hooks/useFormEvents';
-  import { createFormContext } from './hooks/useFormContext';
-  import { useAutoFocus } from './hooks/useAutoFocus';
-  import { useModalContext } from '/@/components/Modal';
+  import { useFormValues } from './hooks/useFormValues'
+  import useAdvanced from './hooks/useAdvanced'
+  import { useFormEvents } from './hooks/useFormEvents'
+  import { createFormContext } from './hooks/useFormContext'
+  import { useAutoFocus } from './hooks/useAutoFocus'
+  import { useModalContext } from '/@/components/Modal'
 
-  import { basicProps } from './props';
-  import { useDesign } from '/@/hooks/web/useDesign';
+  import { basicProps } from './props'
+  import { useDesign } from '/@/hooks/web/useDesign'
+
+  import type { RowProps } from 'ant-design-vue/lib/grid/Row'
 
   export default defineComponent({
     name: 'BasicForm',
@@ -72,67 +67,66 @@
     props: basicProps,
     emits: ['advanced-change', 'reset', 'submit', 'register'],
     setup(props, { emit }) {
-      const formModel = reactive<Recordable>({});
-      const modalFn = useModalContext();
+      const formModel = reactive<Recordable>({})
+      const modalFn = useModalContext()
 
       const advanceState = reactive<AdvanceState>({
         isAdvanced: true,
         hideAdvanceBtn: false,
         isLoad: false,
-        actionSpan: 6,
-      });
+        actionSpan: 6
+      })
 
-      const defaultValueRef = ref<Recordable>({});
-      const isInitedDefaultRef = ref(false);
-      const propsRef = ref<Partial<FormProps>>({});
-      const schemaRef = ref<Nullable<FormSchema[]>>(null);
-      const formElRef = ref<Nullable<FormActionType>>(null);
+      const defaultValueRef = ref<Recordable>({})
+      const isInitedDefaultRef = ref(false)
+      const propsRef = ref<Partial<FormProps>>({})
+      const schemaRef = ref<Nullable<FormSchema[]>>(null)
+      const formElRef = ref<Nullable<FormActionType>>(null)
 
-      const { prefixCls } = useDesign('basic-form');
+      const { prefixCls } = useDesign('basic-form')
 
       // Get the basic configuration of the form
-      const getProps = computed(
-        (): FormProps => {
-          return { ...props, ...unref(propsRef) } as FormProps;
-        }
-      );
+      const getProps = computed((): FormProps => {
+        return { ...props, ...unref(propsRef) } as FormProps
+      })
 
       const getFormClass = computed(() => {
         return [
           prefixCls,
           {
-            [`${prefixCls}--compact`]: unref(getProps).compact,
-          },
-        ];
-      });
-
-      // Get uniform row style
-      const getRowWrapStyle = computed(
-        (): CSSProperties => {
-          const { baseRowStyle = {} } = unref(getProps);
-          return baseRowStyle;
+            [`${prefixCls}--compact`]: unref(getProps).compact
+          }
+        ]
+      })
+
+      // Get uniform row style and Row configuration for the entire form
+      const getRow = computed((): CSSProperties | RowProps => {
+        const { baseRowStyle = {}, rowProps } = unref(getProps)
+        return {
+          style: baseRowStyle,
+          ...rowProps
         }
-      );
+      })
 
       const getSchema = computed((): FormSchema[] => {
-        const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
+        const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
         for (const schema of schemas) {
-          const { defaultValue, component } = schema;
+          const { defaultValue, component } = schema
           // handle date type
           if (defaultValue && dateItemType.includes(component)) {
             if (!Array.isArray(defaultValue)) {
-              schema.defaultValue = dateUtil(defaultValue);
+              schema.defaultValue = dateUtil(defaultValue)
             } else {
-              const def: moment.Moment[] = [];
-              defaultValue.forEach((item) => {
-                def.push(dateUtil(item));
-              });
-              schema.defaultValue = def;
+              const def: moment.Moment[] = []
+              defaultValue.forEach(item => {
+                def.push(dateUtil(item))
+              })
+              schema.defaultValue = def
             }
           }
         }
-        return schemas as FormSchema[];
-      });
+        return schemas as FormSchema[]
+      })
 
       const { handleToggleAdvanced } = useAdvanced({
         advanceState,
@@ -140,27 +134,22 @@
         getProps,
         getSchema,
         formModel,
-        defaultValueRef,
-      });
-
-      const { transformDateFunc, fieldMapToTime, autoFocusFirstItem } = toRefs(
-        unref(getProps)
-      ) as any;
+        defaultValueRef
+      })
 
       const { handleFormValues, initDefault } = useFormValues({
-        transformDateFuncRef: transformDateFunc,
-        fieldMapToTimeRef: fieldMapToTime,
+        getProps,
         defaultValueRef,
         getSchema,
-        formModel,
-      });
+        formModel
+      })
 
       useAutoFocus({
         getSchema,
-        autoFocusFirstItem,
+        getProps,
         isInitedDefault: isInitedDefaultRef,
-        formElRef: formElRef as Ref<FormActionType>,
-      });
+        formElRef: formElRef as Ref<FormActionType>
+      })
 
       const {
         handleSubmit,
@@ -174,7 +163,7 @@
         appendSchemaByField,
         removeSchemaByFiled,
         resetFields,
-        scrollToField,
+        scrollToField
       } = useFormEvents({
         emit,
         getProps,
@@ -183,49 +172,67 @@
         defaultValueRef,
         formElRef: formElRef as Ref<FormActionType>,
         schemaRef: schemaRef as Ref<FormSchema[]>,
-        handleFormValues,
-      });
+        handleFormValues
+      })
 
       createFormContext({
         resetAction: resetFields,
-        submitAction: handleSubmit,
-      });
+        submitAction: handleSubmit
+      })
 
       watch(
         () => unref(getProps).model,
         () => {
-          const { model } = unref(getProps);
-          if (!model) return;
-          setFieldsValue(model);
+          const { model } = unref(getProps)
+          if (!model) return
+          setFieldsValue(model)
         },
         {
-          immediate: true,
+          immediate: true
+        }
+      )
+
+      watch(
+        () => unref(getProps).schemas,
+        schemas => {
+          resetSchema(schemas ?? [])
         }
-      );
+      )
 
       watch(
         () => getSchema.value,
-        (schema) => {
+        schema => {
           nextTick(() => {
             //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
-            modalFn?.redoModalHeight?.();
-          });
+            modalFn?.redoModalHeight?.()
+          })
           if (unref(isInitedDefaultRef)) {
-            return;
+            return
           }
           if (schema?.length) {
-            initDefault();
-            isInitedDefaultRef.value = true;
+            initDefault()
+            isInitedDefaultRef.value = true
           }
         }
-      );
+      )
 
       async function setProps(formProps: Partial<FormProps>): Promise<void> {
-        propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
+        propsRef.value = deepMerge(unref(propsRef) || {}, formProps)
       }
 
       function setFormModel(key: string, value: any) {
-        formModel[key] = value;
+        formModel[key] = value
+      }
+
+      function handleEnterPress(e: KeyboardEvent) {
+        const { autoSubmitOnEnter } = unref(getProps)
+        if (!autoSubmitOnEnter) return
+        if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
+          const target: HTMLElement = e.target as HTMLElement
+          if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
+            handleSubmit()
+          }
+        }
       }
 
       const formActionType: Partial<FormActionType> = {
@@ -241,20 +248,21 @@
         validateFields,
         validate,
         submit: handleSubmit,
-        scrollToField: scrollToField,
-      };
+        scrollToField: scrollToField
+      }
 
       onMounted(() => {
-        initDefault();
-        emit('register', formActionType);
-      });
+        initDefault()
+        emit('register', formActionType)
+      })
 
       return {
         handleToggleAdvanced,
+        handleEnterPress,
         formModel,
         defaultValueRef,
         advanceState,
-        getRowWrapStyle,
+        getRow,
         getProps,
         formElRef,
         getSchema,
@@ -262,10 +270,10 @@
         setFormModel,
         prefixCls,
         getFormClass,
-        ...formActionType,
-      };
-    },
-  });
+        ...formActionType
+      }
+    }
+  })
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-basic-form';

+ 42 - 38
src/components/Form/src/componentMap.ts

@@ -1,5 +1,5 @@
-import type { Component } from 'vue';
-import type { ComponentType } from './types/index';
+import type { Component } from 'vue'
+import type { ComponentType } from './types/index'
 
 /**
  * Component list, register here to setting it in the form
@@ -16,52 +16,56 @@ import {
   Switch,
   TimePicker,
   TreeSelect,
-} from 'ant-design-vue';
+  Slider,
+  Rate
+} from 'ant-design-vue'
 
-import RadioButtonGroup from './components/RadioButtonGroup.vue';
-import ApiSelect from './components/ApiSelect.vue';
-import { BasicUpload } from '/@/components/Upload';
-import { StrengthMeter } from '/@/components/StrengthMeter';
-import { IconPicker } from '/@/components/Icon';
-import { CountdownInput } from '/@/components/CountDown';
+import RadioButtonGroup from './components/RadioButtonGroup.vue'
+import ApiSelect from './components/ApiSelect.vue'
+import { BasicUpload } from '/@/components/Upload'
+import { StrengthMeter } from '/@/components/StrengthMeter'
+import { IconPicker } from '/@/components/Icon'
+import { CountdownInput } from '/@/components/CountDown'
 
-const componentMap = new Map<ComponentType, Component>();
+const componentMap = new Map<ComponentType, Component>()
 
-componentMap.set('Input', Input);
-componentMap.set('InputGroup', Input.Group);
-componentMap.set('InputPassword', Input.Password);
-componentMap.set('InputSearch', Input.Search);
-componentMap.set('InputTextArea', Input.TextArea);
-componentMap.set('InputNumber', InputNumber);
-componentMap.set('AutoComplete', AutoComplete);
+componentMap.set('Input', Input)
+componentMap.set('InputGroup', Input.Group)
+componentMap.set('InputPassword', Input.Password)
+componentMap.set('InputSearch', Input.Search)
+componentMap.set('InputTextArea', Input.TextArea)
+componentMap.set('InputNumber', InputNumber)
+componentMap.set('AutoComplete', AutoComplete)
 
-componentMap.set('Select', Select);
-componentMap.set('ApiSelect', ApiSelect);
-componentMap.set('TreeSelect', TreeSelect);
-componentMap.set('Switch', Switch);
-componentMap.set('RadioButtonGroup', RadioButtonGroup);
-componentMap.set('RadioGroup', Radio.Group);
-componentMap.set('Checkbox', Checkbox);
-componentMap.set('CheckboxGroup', Checkbox.Group);
-componentMap.set('Cascader', Cascader);
+componentMap.set('Select', Select)
+componentMap.set('ApiSelect', ApiSelect)
+componentMap.set('TreeSelect', TreeSelect)
+componentMap.set('Switch', Switch)
+componentMap.set('RadioButtonGroup', RadioButtonGroup)
+componentMap.set('RadioGroup', Radio.Group)
+componentMap.set('Checkbox', Checkbox)
+componentMap.set('CheckboxGroup', Checkbox.Group)
+componentMap.set('Cascader', Cascader)
+componentMap.set('Slider', Slider)
+componentMap.set('Rate', Rate)
 
-componentMap.set('DatePicker', DatePicker);
-componentMap.set('MonthPicker', DatePicker.MonthPicker);
-componentMap.set('RangePicker', DatePicker.RangePicker);
-componentMap.set('WeekPicker', DatePicker.WeekPicker);
-componentMap.set('TimePicker', TimePicker);
-componentMap.set('StrengthMeter', StrengthMeter);
-componentMap.set('IconPicker', IconPicker);
-componentMap.set('InputCountDown', CountdownInput);
+componentMap.set('DatePicker', DatePicker)
+componentMap.set('MonthPicker', DatePicker.MonthPicker)
+componentMap.set('RangePicker', DatePicker.RangePicker)
+componentMap.set('WeekPicker', DatePicker.WeekPicker)
+componentMap.set('TimePicker', TimePicker)
+componentMap.set('StrengthMeter', StrengthMeter)
+componentMap.set('IconPicker', IconPicker)
+componentMap.set('InputCountDown', CountdownInput)
 
-componentMap.set('Upload', BasicUpload);
+componentMap.set('Upload', BasicUpload)
 
 export function add(compName: ComponentType, component: Component) {
-  componentMap.set(compName, component);
+  componentMap.set(compName, component)
 }
 
 export function del(compName: ComponentType) {
-  componentMap.delete(compName);
+  componentMap.delete(compName)
 }
 
-export { componentMap };
+export { componentMap }

+ 56 - 57
src/components/Form/src/components/ApiSelect.vue

@@ -1,10 +1,5 @@
 <template>
-  <Select
-    @dropdownVisibleChange="handleFetch"
-    v-bind="attrs"
-    :options="getOptions"
-    v-model:value="state"
-  >
+  <Select @dropdownVisibleChange="handleFetch" v-bind="attrs" :options="getOptions" v-model:value="state">
     <template #[item]="data" v-for="item in Object.keys($slots)">
       <slot :name="item" v-bind="data"></slot>
     </template>
@@ -20,114 +15,118 @@
   </Select>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue';
-  import { Select } from 'ant-design-vue';
-  import { isFunction } from '/@/utils/is';
-  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
-  import { useAttrs } from '/@/hooks/core/useAttrs';
-  import { get } from 'lodash-es';
+  import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue'
+  import { Select } from 'ant-design-vue'
+  import { isFunction } from '/@/utils/is'
+  import { useRuleFormItem } from '/@/hooks/component/useFormItem'
+  import { useAttrs } from '/@/hooks/core/useAttrs'
+  import { get, omit } from 'lodash-es'
 
-  import { LoadingOutlined } from '@ant-design/icons-vue';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { propTypes } from '/@/utils/propTypes';
+  import { LoadingOutlined } from '@ant-design/icons-vue'
+  import { useI18n } from '/@/hooks/web/useI18n'
+  import { propTypes } from '/@/utils/propTypes'
 
-  type OptionsItem = { label: string; value: string; disabled?: boolean };
+  type OptionsItem = { label: string; value: string; disabled?: boolean }
 
   export default defineComponent({
     name: 'ApiSelect',
     components: {
       Select,
-      LoadingOutlined,
+      LoadingOutlined
     },
     inheritAttrs: false,
     props: {
-      value: propTypes.oneOfType([
-        propTypes.object,
-        propTypes.number,
-        propTypes.string,
-        propTypes.array,
-      ]),
+      value: propTypes.oneOfType([propTypes.object, propTypes.number, propTypes.string, propTypes.array]),
       numberToString: propTypes.bool,
       api: {
         type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
-        default: null,
+        default: null
       },
       // api params
       params: {
         type: Object as PropType<Recordable>,
-        default: () => {},
+        default: () => ({})
       },
       // support xxx.xxx.xx
       resultField: propTypes.string.def(''),
       labelField: propTypes.string.def('label'),
       valueField: propTypes.string.def('value'),
-      immediate: propTypes.bool.def(true),
+      immediate: propTypes.bool.def(true)
     },
     emits: ['options-change', 'change'],
     setup(props, { emit }) {
-      const options = ref<OptionsItem[]>([]);
-      const loading = ref(false);
-      const isFirstLoad = ref(true);
-      const attrs = useAttrs();
-      const { t } = useI18n();
+      const options = ref<OptionsItem[]>([])
+      const loading = ref(false)
+      const isFirstLoad = ref(true)
+      const attrs = useAttrs()
+      const { t } = useI18n()
 
       // Embedded in the form, just use the hook binding to perform form verification
-      const [state] = useRuleFormItem(props);
+      const [state] = useRuleFormItem(props)
 
       const getOptions = computed(() => {
-        const { labelField, valueField, numberToString } = props;
+        const { labelField, valueField, numberToString } = props
 
         return unref(options).reduce((prev, next: Recordable) => {
           if (next) {
-            const value = next[valueField];
+            const value = next[valueField]
             prev.push({
               label: next[labelField],
               value: numberToString ? `${value}` : value,
-            });
+              ...omit(next, [labelField, valueField])
+            })
           }
-          return prev;
-        }, [] as OptionsItem[]);
-      });
+          return prev
+        }, [] as OptionsItem[])
+      })
 
       watchEffect(() => {
-        props.immediate && fetch();
-      });
+        props.immediate && fetch()
+      })
+
+      watch(
+        () => props.params,
+        () => {
+          !unref(isFirstLoad) && fetch()
+        },
+        { deep: true }
+      )
 
       async function fetch() {
-        const api = props.api;
-        if (!api || !isFunction(api)) return;
+        const api = props.api
+        if (!api || !isFunction(api)) return
 
         try {
-          loading.value = true;
-          const res = await api(props.params);
+          loading.value = true
+          const res = await api(props.params)
           if (Array.isArray(res)) {
-            options.value = res;
-            emitChange();
-            return;
+            options.value = res
+            emitChange()
+            return
           }
           if (props.resultField) {
-            options.value = get(res, props.resultField) || [];
+            options.value = get(res, props.resultField) || []
           }
-          emitChange();
+          emitChange()
         } catch (error) {
-          console.warn(error);
+          console.warn(error)
         } finally {
-          loading.value = false;
+          loading.value = false
         }
       }
 
       async function handleFetch() {
         if (!props.immediate && unref(isFirstLoad)) {
-          await fetch();
-          isFirstLoad.value = false;
+          await fetch()
+          isFirstLoad.value = false
         }
       }
 
       function emitChange() {
-        emit('options-change', unref(options));
+        emit('options-change', unref(options))
       }
 
-      return { state, attrs, getOptions, loading, t, handleFetch };
-    },
-  });
+      return { state, attrs, getOptions, loading, t, handleFetch }
+    }
+  })
 </script>

+ 43 - 64
src/components/Form/src/components/FormAction.vue

@@ -2,55 +2,38 @@
   <a-col v-bind="actionColOpt" :style="{ textAlign: 'right' }" v-if="showActionButtonGroup">
     <FormItem>
       <slot name="resetBefore"></slot>
-      <Button
-        type="default"
-        class="mr-2"
-        v-bind="getResetBtnOptions"
-        @click="resetAction"
-        v-if="showResetButton"
-      >
+      <Button type="default" class="mr-2" v-bind="getResetBtnOptions" @click="resetAction" v-if="showResetButton">
         {{ getResetBtnOptions.text }}
       </Button>
       <slot name="submitBefore"></slot>
 
-      <Button
-        type="primary"
-        class="mr-2"
-        v-bind="getSubmitBtnOptions"
-        @click="submitAction"
-        v-if="showSubmitButton"
-      >
+      <Button type="primary" class="mr-2" v-bind="getSubmitBtnOptions" @click="submitAction" v-if="showSubmitButton">
         {{ getSubmitBtnOptions.text }}
       </Button>
 
       <slot name="advanceBefore"></slot>
-      <Button
-        type="link"
-        size="small"
-        @click="toggleAdvanced"
-        v-if="showAdvancedButton && !hideAdvanceBtn"
-      >
+      <Button type="link" size="small" @click="toggleAdvanced" v-if="showAdvancedButton && !hideAdvanceBtn">
         {{ isAdvanced ? t('component.form.putAway') : t('component.form.unfold') }}
-        <BasicArrow class="ml-1" :expand="!isAdvanced" top />
+        <BasicArrow class="ml-1" :expand="!isAdvanced" up />
       </Button>
       <slot name="advanceAfter"></slot>
     </FormItem>
   </a-col>
 </template>
 <script lang="ts">
-  import type { ColEx } from '../types/index';
-  import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
+  import type { ColEx } from '../types/index'
+  import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
 
-  import { defineComponent, computed, PropType } from 'vue';
-  import { Form, Col } from 'ant-design-vue';
-  import { Button } from '/@/components/Button';
-  import { BasicArrow } from '/@/components/Basic/index';
-  import { useFormContext } from '../hooks/useFormContext';
+  import { defineComponent, computed, PropType } from 'vue'
+  import { Form, Col } from 'ant-design-vue'
+  import { Button } from '/@/components/Button'
+  import { BasicArrow } from '/@/components/Basic/index'
+  import { useFormContext } from '../hooks/useFormContext'
 
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { propTypes } from '/@/utils/propTypes';
+  import { useI18n } from '/@/hooks/web/useI18n'
+  import { propTypes } from '/@/utils/propTypes'
 
-  type ButtonOptions = Partial<ButtonProps> & { text: string };
+  type ButtonOptions = Partial<ButtonProps> & { text: string }
 
   export default defineComponent({
     name: 'BasicFormAction',
@@ -58,7 +41,7 @@
       FormItem: Form.Item,
       Button,
       BasicArrow,
-      [Col.name]: Col,
+      [Col.name]: Col
     },
     props: {
       showActionButtonGroup: propTypes.bool.def(true),
@@ -67,60 +50,56 @@
       showAdvancedButton: propTypes.bool.def(true),
       resetButtonOptions: {
         type: Object as PropType<ButtonOptions>,
-        default: () => {},
+        default: () => ({})
       },
       submitButtonOptions: {
         type: Object as PropType<ButtonOptions>,
-        default: () => {},
+        default: () => ({})
       },
       actionColOptions: {
         type: Object as PropType<Partial<ColEx>>,
-        default: () => {},
+        default: () => ({})
       },
       actionSpan: propTypes.number.def(6),
       isAdvanced: propTypes.bool,
-      hideAdvanceBtn: propTypes.bool,
+      hideAdvanceBtn: propTypes.bool
     },
     emits: ['toggle-advanced'],
     setup(props, { emit }) {
-      const { t } = useI18n();
+      const { t } = useI18n()
 
       const actionColOpt = computed(() => {
-        const { showAdvancedButton, actionSpan: span, actionColOptions } = props;
-        const actionSpan = 24 - span;
-        const advancedSpanObj = showAdvancedButton
-          ? { span: actionSpan < 6 ? 24 : actionSpan }
-          : {};
+        const { showAdvancedButton, actionSpan: span, actionColOptions } = props
+        const actionSpan = 24 - span
+        const advancedSpanObj = showAdvancedButton ? { span: actionSpan < 6 ? 24 : actionSpan } : {}
         const actionColOpt: Partial<ColEx> = {
           span: showAdvancedButton ? 6 : 4,
           ...advancedSpanObj,
-          ...actionColOptions,
-        };
-        return actionColOpt;
-      });
-
-      const getResetBtnOptions = computed(
-        (): ButtonOptions => {
-          return Object.assign(
-            {
-              text: t('common.resetText'),
-            },
-            props.resetButtonOptions
-          );
+          ...actionColOptions
         }
-      );
+        return actionColOpt
+      })
+
+      const getResetBtnOptions = computed((): ButtonOptions => {
+        return Object.assign(
+          {
+            text: t('common.resetText')
+          },
+          props.resetButtonOptions
+        )
+      })
 
       const getSubmitBtnOptions = computed(() => {
         return Object.assign(
           {
-            text: t('common.queryText'),
+            text: t('common.queryText')
           },
           props.submitButtonOptions
-        );
-      });
+        )
+      })
 
       function toggleAdvanced() {
-        emit('toggle-advanced');
+        emit('toggle-advanced')
       }
 
       return {
@@ -129,8 +108,8 @@
         getResetBtnOptions,
         getSubmitBtnOptions,
         toggleAdvanced,
-        ...useFormContext(),
-      };
-    },
-  });
+        ...useFormContext()
+      }
+    }
+  })
 </script>

+ 164 - 158
src/components/Form/src/components/FormItem.vue

@@ -1,22 +1,22 @@
 <script lang="tsx">
-  import type { PropType, Ref } from 'vue';
-  import type { FormActionType, FormProps } from '../types/form';
-  import type { FormSchema } from '../types/form';
-  import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
-  import type { TableActionType } from '/@/components/Table';
+  import type { PropType, Ref } from 'vue'
+  import type { FormActionType, FormProps } from '../types/form'
+  import type { FormSchema } from '../types/form'
+  import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
+  import type { TableActionType } from '/@/components/Table'
 
-  import { defineComponent, computed, unref, toRefs } from 'vue';
-  import { Form, Col } from 'ant-design-vue';
-  import { componentMap } from '../componentMap';
-  import { BasicHelp } from '/@/components/Basic';
+  import { defineComponent, computed, unref, toRefs } from 'vue'
+  import { Form, Col } from 'ant-design-vue'
+  import { componentMap } from '../componentMap'
+  import { BasicHelp } from '/@/components/Basic'
 
-  import { isBoolean, isFunction } from '/@/utils/is';
-  import { getSlot } from '/@/utils/helper/tsxHelper';
-  import { createPlaceholderMessage, setComponentRuleType } from '../helper';
-  import { upperFirst, cloneDeep } from 'lodash-es';
+  import { isBoolean, isFunction, isNull } from '/@/utils/is'
+  import { getSlot } from '/@/utils/helper/tsxHelper'
+  import { createPlaceholderMessage, setComponentRuleType } from '../helper'
+  import { upperFirst, cloneDeep } from 'lodash-es'
 
-  import { useItemLabelWidth } from '../hooks/useLabelWidth';
-  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useItemLabelWidth } from '../hooks/useLabelWidth'
+  import { useI18n } from '/@/hooks/web/useI18n'
 
   export default defineComponent({
     name: 'BasicFormItem',
@@ -24,270 +24,280 @@
     props: {
       schema: {
         type: Object as PropType<FormSchema>,
-        default: () => {},
+        default: () => ({})
       },
       formProps: {
         type: Object as PropType<FormProps>,
-        default: () => {},
+        default: () => ({})
       },
       allDefaultValues: {
         type: Object as PropType<Recordable>,
-        default: () => {},
+        default: () => ({})
       },
       formModel: {
         type: Object as PropType<Recordable>,
-        default: () => {},
+        default: () => ({})
       },
       setFormModel: {
         type: Function as PropType<(key: string, value: any) => void>,
-        default: null,
+        default: null
       },
       tableAction: {
-        type: Object as PropType<TableActionType>,
+        type: Object as PropType<TableActionType>
       },
       formActionType: {
-        type: Object as PropType<FormActionType>,
-      },
+        type: Object as PropType<FormActionType>
+      }
     },
     setup(props, { slots }) {
-      const { t } = useI18n();
+      const { t } = useI18n()
 
       const { schema, formProps } = toRefs(props) as {
-        schema: Ref<FormSchema>;
-        formProps: Ref<FormProps>;
-      };
+        schema: Ref<FormSchema>
+        formProps: Ref<FormProps>
+      }
 
-      const itemLabelWidthProp = useItemLabelWidth(schema, formProps);
+      const itemLabelWidthProp = useItemLabelWidth(schema, formProps)
 
       const getValues = computed(() => {
-        const { allDefaultValues, formModel, schema } = props;
-        const { mergeDynamicData } = props.formProps;
+        const { allDefaultValues, formModel, schema } = props
+        const { mergeDynamicData } = props.formProps
         return {
           field: schema.field,
           model: formModel,
           values: {
             ...mergeDynamicData,
             ...allDefaultValues,
-            ...formModel,
+            ...formModel
           } as Recordable,
-          schema: schema,
-        };
-      });
+          schema: schema
+        }
+      })
 
       const getComponentsProps = computed(() => {
-        const { schema, tableAction, formModel, formActionType } = props;
-        const { componentProps = {} } = schema;
+        const { schema, tableAction, formModel, formActionType } = props
+        const { componentProps = {} } = schema
         if (!isFunction(componentProps)) {
-          return componentProps;
+          return componentProps
         }
-        return componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
-      });
+        return componentProps({ schema, tableAction, formModel, formActionType }) ?? {}
+      })
 
       const getDisable = computed(() => {
-        const { disabled: globDisabled } = props.formProps;
-        const { dynamicDisabled } = props.schema;
-        const { disabled: itemDisabled = false } = unref(getComponentsProps);
-        let disabled = !!globDisabled || itemDisabled;
+        const { disabled: globDisabled } = props.formProps
+        const { dynamicDisabled } = props.schema
+        const { disabled: itemDisabled = false } = unref(getComponentsProps)
+        let disabled = !!globDisabled || itemDisabled
         if (isBoolean(dynamicDisabled)) {
-          disabled = dynamicDisabled;
+          disabled = dynamicDisabled
         }
 
         if (isFunction(dynamicDisabled)) {
-          disabled = dynamicDisabled(unref(getValues));
+          disabled = dynamicDisabled(unref(getValues))
         }
-        return disabled;
-      });
+        return disabled
+      })
 
       function getShow(): { isShow: boolean; isIfShow: boolean } {
-        const { show, ifShow } = props.schema;
-        const { showAdvancedButton } = props.formProps;
+        const { show, ifShow } = props.schema
+        const { showAdvancedButton } = props.formProps
         const itemIsAdvanced = showAdvancedButton
           ? isBoolean(props.schema.isAdvanced)
             ? props.schema.isAdvanced
             : true
-          : true;
+          : true
 
-        let isShow = true;
-        let isIfShow = true;
+        let isShow = true
+        let isIfShow = true
 
         if (isBoolean(show)) {
-          isShow = show;
+          isShow = show
         }
         if (isBoolean(ifShow)) {
-          isIfShow = ifShow;
+          isIfShow = ifShow
         }
         if (isFunction(show)) {
-          isShow = show(unref(getValues));
+          isShow = show(unref(getValues))
         }
         if (isFunction(ifShow)) {
-          isIfShow = ifShow(unref(getValues));
+          isIfShow = ifShow(unref(getValues))
         }
-        isShow = isShow && itemIsAdvanced;
-        return { isShow, isIfShow };
+        isShow = isShow && itemIsAdvanced
+        return { isShow, isIfShow }
       }
 
       function handleRules(): ValidationRule[] {
-        const {
-          rules: defRules = [],
-          component,
-          rulesMessageJoinLabel,
-          label,
-          dynamicRules,
-          required,
-        } = props.schema;
+        const { rules: defRules = [], component, rulesMessageJoinLabel, label, dynamicRules, required } = props.schema
 
         if (isFunction(dynamicRules)) {
-          return dynamicRules(unref(getValues)) as ValidationRule[];
+          return dynamicRules(unref(getValues)) as ValidationRule[]
+        }
+
+        let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]
+        const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps
+
+        const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
+          ? rulesMessageJoinLabel
+          : globalRulesMessageJoinLabel
+        const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`
+
+        function validator(rule: any, value: any) {
+          const msg = rule.message || defaultMsg
+          if (value === undefined || isNull(value)) {
+            // 空值
+            return Promise.reject(msg)
+          } else if (Array.isArray(value) && value.length === 0) {
+            // 数组类型
+            return Promise.reject(msg)
+          } else if (typeof value === 'string' && value.trim() === '') {
+            // 空字符串
+            return Promise.reject(msg)
+          } else if (
+            typeof value === 'object' &&
+            Reflect.has(value, 'checked') &&
+            Reflect.has(value, 'halfChecked') &&
+            Array.isArray(value.checked) &&
+            Array.isArray(value.halfChecked) &&
+            value.checked.length === 0 &&
+            value.halfChecked.length === 0
+          ) {
+            // 非关联选择的tree组件
+            return Promise.reject(msg)
+          }
+          return Promise.resolve()
         }
 
-        let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
+        const getRequired = isFunction(required) ? required(unref(getValues)) : required
 
-        if ((!rules || rules.length === 0) && required) {
-          rules = [{ required, type: 'string' }];
+        if ((!rules || rules.length === 0) && getRequired) {
+          rules = [{ required: getRequired, validator }]
         }
 
         const requiredRuleIndex: number = rules.findIndex(
-          (rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator')
-        );
-        const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
+          rule => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator')
+        )
+
         if (requiredRuleIndex !== -1) {
-          const rule = rules[requiredRuleIndex];
-          const { isShow } = getShow();
+          const rule = rules[requiredRuleIndex]
+          const { isShow } = getShow()
           if (!isShow) {
-            rule.required = false;
+            rule.required = false
           }
           if (component) {
             if (!Reflect.has(rule, 'type')) {
-              rule.type = component === 'InputNumber' ? 'number' : 'string';
+              rule.type = component === 'InputNumber' ? 'number' : 'string'
             }
-            const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
-              ? rulesMessageJoinLabel
-              : globalRulesMessageJoinLabel;
 
-            rule.message =
-              rule.message || createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
+            rule.message = rule.message || defaultMsg
 
             if (component.includes('Input') || component.includes('Textarea')) {
-              rule.whitespace = true;
+              rule.whitespace = true
             }
-            const valueFormat = unref(getComponentsProps)?.valueFormat;
-            setComponentRuleType(rule, component, valueFormat);
+            const valueFormat = unref(getComponentsProps)?.valueFormat
+            setComponentRuleType(rule, component, valueFormat)
           }
         }
 
         // Maximum input length rule check
-        const characterInx = rules.findIndex((val) => val.max);
+        const characterInx = rules.findIndex(val => val.max)
         if (characterInx !== -1 && !rules[characterInx].validator) {
           rules[characterInx].message =
-            rules[characterInx].message ||
-            t('component.form.maxTip', [rules[characterInx].max] as Recordable);
+            rules[characterInx].message || t('component.form.maxTip', [rules[characterInx].max] as Recordable)
         }
-        return rules;
+        return rules
       }
 
       function renderComponent() {
-        const {
-          renderComponentContent,
-          component,
-          field,
-          changeEvent = 'change',
-          valueField,
-        } = props.schema;
+        const { renderComponentContent, component, field, changeEvent = 'change', valueField } = props.schema
 
-        const isCheck = component && ['Switch', 'Checkbox'].includes(component);
+        const isCheck = component && ['Switch', 'Checkbox'].includes(component)
 
-        const eventKey = `on${upperFirst(changeEvent)}`;
+        const eventKey = `on${upperFirst(changeEvent)}`
 
         const on = {
           [eventKey]: (e: Nullable<Recordable>) => {
             if (propsData[eventKey]) {
-              propsData[eventKey](e);
+              propsData[eventKey](e)
             }
-            const target = e ? e.target : null;
-            const value = target ? (isCheck ? target.checked : target.value) : e;
-            props.setFormModel(field, value);
-          },
-        };
-        const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;
-
-        const { autoSetPlaceHolder, size } = props.formProps;
+            const target = e ? e.target : null
+            const value = target ? (isCheck ? target.checked : target.value) : e
+            props.setFormModel(field, value)
+          }
+        }
+        const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>
+
+        const { autoSetPlaceHolder, size } = props.formProps
         const propsData: Recordable = {
           allowClear: true,
           getPopupContainer: (trigger: Element) => trigger.parentNode,
           size,
           ...unref(getComponentsProps),
-          disabled: unref(getDisable),
-        };
+          disabled: unref(getDisable)
+        }
 
-        const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
-        let placeholder;
+        const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder
         // RangePicker place is an array
         if (isCreatePlaceholder && component !== 'RangePicker' && component) {
-          placeholder =
-            unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
+          propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component)
         }
-        propsData.placeholder = placeholder;
-        propsData.codeField = field;
-        propsData.formValues = unref(getValues);
+        propsData.codeField = field
+        propsData.formValues = unref(getValues)
 
         const bindValue: Recordable = {
-          [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
-        };
+          [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field]
+        }
 
         const compAttr: Recordable = {
           ...propsData,
           ...on,
-          ...bindValue,
-        };
+          ...bindValue
+        }
 
         if (!renderComponentContent) {
-          return <Comp {...compAttr} />;
+          return <Comp {...compAttr} />
         }
         const compSlot = isFunction(renderComponentContent)
           ? { ...renderComponentContent(unref(getValues)) }
           : {
-              default: () => renderComponentContent,
-            };
+              default: () => renderComponentContent
+            }
 
-        return <Comp {...compAttr}>{compSlot}</Comp>;
+        return <Comp {...compAttr}>{compSlot}</Comp>
       }
 
       function renderLabelHelpMessage() {
-        const { label, helpMessage, helpComponentProps, subLabel } = props.schema;
+        const { label, helpMessage, helpComponentProps, subLabel } = props.schema
         const renderLabel = subLabel ? (
           <span>
             {label} <span class="text-secondary">{subLabel}</span>
           </span>
         ) : (
           label
-        );
-        if (!helpMessage || (Array.isArray(helpMessage) && helpMessage.length === 0)) {
-          return renderLabel;
+        )
+        const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage
+        if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
+          return renderLabel
         }
         return (
           <span>
             {renderLabel}
-            <BasicHelp placement="top" class="mx-1" text={helpMessage} {...helpComponentProps} />
+            <BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} />
           </span>
-        );
+        )
       }
 
       function renderItem() {
-        const { itemProps, slot, render, field, suffix } = props.schema;
-        const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
-        const { colon } = props.formProps;
+        const { itemProps, slot, render, field, suffix } = props.schema
+        const { labelCol, wrapperCol } = unref(itemLabelWidthProp)
+        const { colon } = props.formProps
 
         const getContent = () => {
-          return slot
-            ? getSlot(slots, slot, unref(getValues))
-            : render
-            ? render(unref(getValues))
-            : renderComponent();
-        };
+          return slot ? getSlot(slots, slot, unref(getValues)) : render ? render(unref(getValues)) : renderComponent()
+        }
 
-        const showSuffix = !!suffix;
+        const showSuffix = !!suffix
 
-        const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
+        const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix
 
         return (
           <Form.Item
@@ -305,25 +315,21 @@
               {showSuffix && <span class="suffix">{getSuffix}</span>}
             </>
           </Form.Item>
-        );
+        )
       }
       return () => {
-        const { colProps = {}, colSlot, renderColContent, component } = props.schema;
-        if (!componentMap.has(component)) return null;
+        const { colProps = {}, colSlot, renderColContent, component } = props.schema
+        if (!componentMap.has(component)) return null
 
-        const { baseColProps = {} } = props.formProps;
+        const { baseColProps = {} } = props.formProps
 
-        const realColProps = { ...baseColProps, ...colProps };
-        const { isIfShow, isShow } = getShow();
+        const realColProps = { ...baseColProps, ...colProps }
+        const { isIfShow, isShow } = getShow()
 
-        const values = unref(getValues);
+        const values = unref(getValues)
         const getContent = () => {
-          return colSlot
-            ? getSlot(slots, colSlot, values)
-            : renderColContent
-            ? renderColContent(values)
-            : renderItem();
-        };
+          return colSlot ? getSlot(slots, colSlot, values) : renderColContent ? renderColContent(values) : renderItem()
+        }
 
         return (
           isIfShow && (
@@ -331,8 +337,8 @@
               {getContent()}
             </Col>
           )
-        );
-      };
-    },
-  });
+        )
+      }
+    }
+  })
 </script>

+ 23 - 23
src/components/Form/src/components/RadioButtonGroup.vue

@@ -5,52 +5,52 @@
 <template>
   <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
     <template v-for="item in getOptions" :key="`${item.value}`">
-      <RadioButton :value="item.value">
+      <RadioButton :value="item.value" :disabled="item.disabled">
         {{ item.label }}
       </RadioButton>
     </template>
   </RadioGroup>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType, computed } from 'vue';
-  import { Radio } from 'ant-design-vue';
-  import { isString } from '/@/utils/is';
-  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
-  import { useAttrs } from '/@/hooks/core/useAttrs';
-  type OptionsItem = { label: string; value: string; disabled?: boolean };
-  type RadioItem = string | OptionsItem;
+  import { defineComponent, PropType, computed } from 'vue'
+  import { Radio } from 'ant-design-vue'
+  import { isString } from '/@/utils/is'
+  import { useRuleFormItem } from '/@/hooks/component/useFormItem'
+  import { useAttrs } from '/@/hooks/core/useAttrs'
+  type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean }
+  type RadioItem = string | OptionsItem
 
   export default defineComponent({
     name: 'RadioButtonGroup',
     components: {
       RadioGroup: Radio.Group,
-      RadioButton: Radio.Button,
+      RadioButton: Radio.Button
     },
     props: {
       value: {
-        type: String as PropType<string>,
+        type: [String, Number, Boolean] as PropType<string | number | boolean>
       },
       options: {
         type: Array as PropType<RadioItem[]>,
-        default: () => [],
-      },
+        default: () => []
+      }
     },
     setup(props) {
-      const attrs = useAttrs();
+      const attrs = useAttrs()
       // Embedded in the form, just use the hook binding to perform form verification
-      const [state] = useRuleFormItem(props);
+      const [state] = useRuleFormItem(props)
       // Processing options value
       const getOptions = computed((): OptionsItem[] => {
-        const { options } = props;
-        if (!options || options?.length === 0) return [];
+        const { options } = props
+        if (!options || options?.length === 0) return []
 
-        const isStringArr = options.some((item) => isString(item));
-        if (!isStringArr) return options as OptionsItem[];
+        const isStringArr = options.some(item => isString(item))
+        if (!isStringArr) return options as OptionsItem[]
 
-        return options.map((item) => ({ label: item, value: item })) as OptionsItem[];
-      });
+        return options.map(item => ({ label: item, value: item })) as OptionsItem[]
+      })
 
-      return { state, getOptions, attrs };
-    },
-  });
+      return { state, getOptions, attrs }
+    }
+  })
 </script>

+ 67 - 74
src/components/Form/src/hooks/useAdvanced.ts

@@ -1,126 +1,122 @@
-import type { ColEx } from '../types';
-import type { AdvanceState } from '../types/hooks';
-import type { ComputedRef, Ref } from 'vue';
-import type { FormProps, FormSchema } from '../types/form';
+import type { ColEx } from '../types'
+import type { AdvanceState } from '../types/hooks'
+import type { ComputedRef, Ref } from 'vue'
+import type { FormProps, FormSchema } from '../types/form'
 
-import { computed, unref, watch } from 'vue';
-import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
+import { computed, unref, watch } from 'vue'
+import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'
 
-import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
+import { useBreakpoint } from '/@/hooks/event/useBreakpoint'
+import { useDebounceFn } from '@vueuse/core'
 
-const BASIC_COL_LEN = 24;
+const BASIC_COL_LEN = 24
 
 interface UseAdvancedContext {
-  advanceState: AdvanceState;
-  emit: EmitType;
-  getProps: ComputedRef<FormProps>;
-  getSchema: ComputedRef<FormSchema[]>;
-  formModel: Recordable;
-  defaultValueRef: Ref<Recordable>;
+  advanceState: AdvanceState
+  emit: EmitType
+  getProps: ComputedRef<FormProps>
+  getSchema: ComputedRef<FormSchema[]>
+  formModel: Recordable
+  defaultValueRef: Ref<Recordable>
 }
 
-export default function ({
-  advanceState,
-  emit,
-  getProps,
-  getSchema,
-  formModel,
-  defaultValueRef,
-}: UseAdvancedContext) {
-  const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
+export default function ({ advanceState, emit, getProps, getSchema, formModel, defaultValueRef }: UseAdvancedContext) {
+  const { realWidthRef, screenEnum, screenRef } = useBreakpoint()
 
   const getEmptySpan = computed((): number => {
     if (!advanceState.isAdvanced) {
-      return 0;
+      return 0
     }
     // For some special cases, you need to manually specify additional blank lines
-    const emptySpan = unref(getProps).emptySpan || 0;
+    const emptySpan = unref(getProps).emptySpan || 0
 
     if (isNumber(emptySpan)) {
-      return emptySpan;
+      return emptySpan
     }
     if (isObject(emptySpan)) {
-      const { span = 0 } = emptySpan;
-      const screen = unref(screenRef) as string;
+      const { span = 0 } = emptySpan
+      const screen = unref(screenRef) as string
 
-      const screenSpan = (emptySpan as any)[screen.toLowerCase()];
-      return screenSpan || span || 0;
+      const screenSpan = (emptySpan as any)[screen.toLowerCase()]
+      return screenSpan || span || 0
     }
-    return 0;
-  });
+    return 0
+  })
+
+  const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30)
 
   watch(
     [() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
     () => {
-      const { showAdvancedButton } = unref(getProps);
+      const { showAdvancedButton } = unref(getProps)
       if (showAdvancedButton) {
-        updateAdvanced();
+        debounceUpdateAdvanced()
       }
     },
     { immediate: true }
-  );
+  )
 
   function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
-    const width = unref(realWidthRef);
+    const width = unref(realWidthRef)
 
     const mdWidth =
       parseInt(itemCol.md as string) ||
       parseInt(itemCol.xs as string) ||
       parseInt(itemCol.sm as string) ||
       (itemCol.span as number) ||
-      BASIC_COL_LEN;
+      BASIC_COL_LEN
 
-    const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
-    const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
-    const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
+    const lgWidth = parseInt(itemCol.lg as string) || mdWidth
+    const xlWidth = parseInt(itemCol.xl as string) || lgWidth
+    const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth
     if (width <= screenEnum.LG) {
-      itemColSum += mdWidth;
+      itemColSum += mdWidth
     } else if (width < screenEnum.XL) {
-      itemColSum += lgWidth;
+      itemColSum += lgWidth
     } else if (width < screenEnum.XXL) {
-      itemColSum += xlWidth;
+      itemColSum += xlWidth
     } else {
-      itemColSum += xxlWidth;
+      itemColSum += xxlWidth
     }
 
     if (isLastAction) {
-      advanceState.hideAdvanceBtn = false;
+      advanceState.hideAdvanceBtn = false
       if (itemColSum <= BASIC_COL_LEN * 2) {
         // When less than or equal to 2 lines, the collapse and expand buttons are not displayed
-        advanceState.hideAdvanceBtn = true;
-        advanceState.isAdvanced = true;
+        advanceState.hideAdvanceBtn = true
+        advanceState.isAdvanced = true
       } else if (
         itemColSum > BASIC_COL_LEN * 2 &&
         itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
       ) {
-        advanceState.hideAdvanceBtn = false;
+        advanceState.hideAdvanceBtn = false
 
         // More than 3 lines collapsed by default
       } else if (!advanceState.isLoad) {
-        advanceState.isLoad = true;
-        advanceState.isAdvanced = !advanceState.isAdvanced;
+        advanceState.isLoad = true
+        advanceState.isAdvanced = !advanceState.isAdvanced
       }
-      return { isAdvanced: advanceState.isAdvanced, itemColSum };
+      return { isAdvanced: advanceState.isAdvanced, itemColSum }
     }
     if (itemColSum > BASIC_COL_LEN) {
-      return { isAdvanced: advanceState.isAdvanced, itemColSum };
+      return { isAdvanced: advanceState.isAdvanced, itemColSum }
     } else {
       // The first line is always displayed
-      return { isAdvanced: true, itemColSum };
+      return { isAdvanced: true, itemColSum }
     }
   }
 
   function updateAdvanced() {
-    let itemColSum = 0;
-    let realItemColSum = 0;
-    const { baseColProps = {} } = unref(getProps);
+    let itemColSum = 0
+    let realItemColSum = 0
+    const { baseColProps = {} } = unref(getProps)
 
     for (const schema of unref(getSchema)) {
-      const { show, colProps } = schema;
-      let isShow = true;
+      const { show, colProps } = schema
+      let isShow = true
 
       if (isBoolean(show)) {
-        isShow = show;
+        isShow = show
       }
 
       if (isFunction(show)) {
@@ -130,35 +126,32 @@ export default function ({
           field: schema.field,
           values: {
             ...unref(defaultValueRef),
-            ...formModel,
-          },
-        });
+            ...formModel
+          }
+        })
       }
 
       if (isShow && (colProps || baseColProps)) {
-        const { itemColSum: sum, isAdvanced } = getAdvanced(
-          { ...baseColProps, ...colProps },
-          itemColSum
-        );
+        const { itemColSum: sum, isAdvanced } = getAdvanced({ ...baseColProps, ...colProps }, itemColSum)
 
-        itemColSum = sum || 0;
+        itemColSum = sum || 0
         if (isAdvanced) {
-          realItemColSum = itemColSum;
+          realItemColSum = itemColSum
         }
-        schema.isAdvanced = isAdvanced;
+        schema.isAdvanced = isAdvanced
       }
     }
 
-    advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
+    advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan)
 
-    getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);
+    getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true)
 
-    emit('advanced-change');
+    emit('advanced-change')
   }
 
   function handleToggleAdvanced() {
-    advanceState.isAdvanced = !advanceState.isAdvanced;
+    advanceState.isAdvanced = !advanceState.isAdvanced
   }
 
-  return { handleToggleAdvanced };
+  return { handleToggleAdvanced }
 }

+ 20 - 25
src/components/Form/src/hooks/useAutoFocus.ts

@@ -1,34 +1,29 @@
-import type { ComputedRef, Ref } from 'vue';
-import type { FormSchema, FormActionType } from '../types/form';
+import type { ComputedRef, Ref } from 'vue'
+import type { FormSchema, FormActionType, FormProps } from '../types/form'
 
-import { unref, nextTick, watchEffect } from 'vue';
+import { unref, nextTick, watchEffect } from 'vue'
 
 interface UseAutoFocusContext {
-  getSchema: ComputedRef<FormSchema[]>;
-  autoFocusFirstItem: Ref<boolean>;
-  isInitedDefault: Ref<boolean>;
-  formElRef: Ref<FormActionType>;
+  getSchema: ComputedRef<FormSchema[]>
+  getProps: ComputedRef<FormProps>
+  isInitedDefault: Ref<boolean>
+  formElRef: Ref<FormActionType>
 }
-export async function useAutoFocus({
-  getSchema,
-  autoFocusFirstItem,
-  formElRef,
-  isInitedDefault,
-}: UseAutoFocusContext) {
+export async function useAutoFocus({ getSchema, getProps, formElRef, isInitedDefault }: UseAutoFocusContext) {
   watchEffect(async () => {
-    if (unref(isInitedDefault) || !unref(autoFocusFirstItem)) return;
-    await nextTick();
-    const schemas = unref(getSchema);
-    const formEl = unref(formElRef);
-    const el = (formEl as any)?.$el as HTMLElement;
-    if (!formEl || !el || !schemas || schemas.length === 0) return;
+    if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) return
+    await nextTick()
+    const schemas = unref(getSchema)
+    const formEl = unref(formElRef)
+    const el = (formEl as any)?.$el as HTMLElement
+    if (!formEl || !el || !schemas || schemas.length === 0) return
 
-    const firstItem = schemas[0];
+    const firstItem = schemas[0]
     // Only open when the first form item is input type
-    if (!firstItem.component.includes('Input')) return;
+    if (!firstItem.component.includes('Input')) return
 
-    const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
-    if (!inputEl) return;
-    inputEl?.focus();
-  });
+    const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>
+    if (!inputEl) return
+    inputEl?.focus()
+  })
 }

+ 110 - 114
src/components/Form/src/hooks/useFormEvents.ts

@@ -1,25 +1,25 @@
-import type { ComputedRef, Ref } from 'vue';
-import type { FormProps, FormSchema, FormActionType } from '../types/form';
-import type { NamePath } from 'ant-design-vue/lib/form/interface';
+import type { ComputedRef, Ref } from 'vue'
+import type { FormProps, FormSchema, FormActionType } from '../types/form'
+import type { NamePath } from 'ant-design-vue/lib/form/interface'
 
-import { unref, toRaw } from 'vue';
+import { unref, toRaw } from 'vue'
 
-import { isArray, isFunction, isObject, isString } from '/@/utils/is';
-import { deepMerge } from '/@/utils';
-import { dateItemType, handleInputNumberValue } from '../helper';
-import { dateUtil } from '/@/utils/dateUtil';
-import { cloneDeep, uniqBy } from 'lodash-es';
-import { error } from '/@/utils/log';
+import { isArray, isFunction, isObject, isString } from '/@/utils/is'
+import { deepMerge } from '/@/utils'
+import { dateItemType, handleInputNumberValue } from '../helper'
+import { dateUtil } from '/@/utils/dateUtil'
+import { cloneDeep, uniqBy } from 'lodash-es'
+import { error } from '/@/utils/log'
 
 interface UseFormActionContext {
-  emit: EmitType;
-  getProps: ComputedRef<FormProps>;
-  getSchema: ComputedRef<FormSchema[]>;
-  formModel: Recordable;
-  defaultValueRef: Ref<Recordable>;
-  formElRef: Ref<FormActionType>;
-  schemaRef: Ref<FormSchema[]>;
-  handleFormValues: Fn;
+  emit: EmitType
+  getProps: ComputedRef<FormProps>
+  getSchema: ComputedRef<FormSchema[]>
+  formModel: Recordable
+  defaultValueRef: Ref<Recordable>
+  formElRef: Ref<FormActionType>
+  schemaRef: Ref<FormSchema[]>
+  handleFormValues: Fn
 }
 export function useFormEvents({
   emit,
@@ -29,21 +29,21 @@ export function useFormEvents({
   defaultValueRef,
   formElRef,
   schemaRef,
-  handleFormValues,
+  handleFormValues
 }: UseFormActionContext) {
   async function resetFields(): Promise<void> {
-    const { resetFunc, submitOnReset } = unref(getProps);
-    resetFunc && isFunction(resetFunc) && (await resetFunc());
+    const { resetFunc, submitOnReset } = unref(getProps)
+    resetFunc && isFunction(resetFunc) && (await resetFunc())
 
-    const formEl = unref(formElRef);
-    if (!formEl) return;
+    const formEl = unref(formElRef)
+    if (!formEl) return
 
-    Object.keys(formModel).forEach((key) => {
-      formModel[key] = defaultValueRef.value[key];
-    });
-    clearValidate();
-    emit('reset', toRaw(formModel));
-    submitOnReset && handleSubmit();
+    Object.keys(formModel).forEach(key => {
+      formModel[key] = defaultValueRef.value[key]
+    })
+    clearValidate()
+    emit('reset', toRaw(formModel))
+    submitOnReset && handleSubmit()
   }
 
   /**
@@ -51,60 +51,60 @@ export function useFormEvents({
    */
   async function setFieldsValue(values: Recordable): Promise<void> {
     const fields = unref(getSchema)
-      .map((item) => item.field)
-      .filter(Boolean);
+      .map(item => item.field)
+      .filter(Boolean)
 
-    const validKeys: string[] = [];
-    Object.keys(values).forEach((key) => {
-      const schema = unref(getSchema).find((item) => item.field === key);
-      let value = values[key];
+    const validKeys: string[] = []
+    Object.keys(values).forEach(key => {
+      const schema = unref(getSchema).find(item => item.field === key)
+      let value = values[key]
 
-      const hasKey = Reflect.has(values, key);
+      const hasKey = Reflect.has(values, key)
 
-      value = handleInputNumberValue(schema?.component, value);
+      value = handleInputNumberValue(schema?.component, value)
       // 0| '' is allow
       if (hasKey && fields.includes(key)) {
         // time type
         if (itemIsDateType(key)) {
           if (Array.isArray(value)) {
-            const arr: any[] = [];
+            const arr: any[] = []
             for (const ele of value) {
-              arr.push(ele ? dateUtil(ele) : null);
+              arr.push(ele ? dateUtil(ele) : null)
             }
-            formModel[key] = arr;
+            formModel[key] = arr
           } else {
-            const { componentProps } = schema || {};
-            let _props = componentProps as any;
+            const { componentProps } = schema || {}
+            let _props = componentProps as any
             if (typeof componentProps === 'function') {
-              _props = _props();
+              _props = _props({ formModel })
             }
-            formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null;
+            formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null
           }
         } else {
-          formModel[key] = value;
+          formModel[key] = value
         }
-        validKeys.push(key);
+        validKeys.push(key)
       }
-    });
-    validateFields(validKeys);
+    })
+    validateFields(validKeys)
   }
   /**
    * @description: Delete based on field name
    */
   async function removeSchemaByFiled(fields: string | string[]): Promise<void> {
-    const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
+    const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
     if (!fields) {
-      return;
+      return
     }
 
-    let fieldList: string[] = isString(fields) ? [fields] : fields;
+    let fieldList: string[] = isString(fields) ? [fields] : fields
     if (isString(fields)) {
-      fieldList = [fields];
+      fieldList = [fields]
     }
     for (const field of fieldList) {
-      _removeSchemaByFiled(field, schemaList);
+      _removeSchemaByFiled(field, schemaList)
     }
-    schemaRef.value = schemaList;
+    schemaRef.value = schemaList
   }
 
   /**
@@ -112,10 +112,10 @@ export function useFormEvents({
    */
   function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void {
     if (isString(field)) {
-      const index = schemaList.findIndex((schema) => schema.field === field);
+      const index = schemaList.findIndex(schema => schema.field === field)
       if (index !== -1) {
-        delete formModel[field];
-        schemaList.splice(index, 1);
+        delete formModel[field]
+        schemaList.splice(index, 1)
       }
     }
   }
@@ -124,124 +124,120 @@ export function useFormEvents({
    * @description: Insert after a certain field, if not insert the last
    */
   async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) {
-    const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
+    const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
 
-    const index = schemaList.findIndex((schema) => schema.field === prefixField);
-    const hasInList = schemaList.some((item) => item.field === prefixField || schema.field);
+    const index = schemaList.findIndex(schema => schema.field === prefixField)
+    const hasInList = schemaList.some(item => item.field === prefixField || schema.field)
 
-    if (!hasInList) return;
+    if (!hasInList) return
 
     if (!prefixField || index === -1 || first) {
-      first ? schemaList.unshift(schema) : schemaList.push(schema);
-      schemaRef.value = schemaList;
-      return;
+      first ? schemaList.unshift(schema) : schemaList.push(schema)
+      schemaRef.value = schemaList
+      return
     }
     if (index !== -1) {
-      schemaList.splice(index + 1, 0, schema);
+      schemaList.splice(index + 1, 0, schema)
     }
-    schemaRef.value = schemaList;
+    schemaRef.value = schemaList
   }
 
   async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
-    let updateData: Partial<FormSchema>[] = [];
+    let updateData: Partial<FormSchema>[] = []
     if (isObject(data)) {
-      updateData.push(data as FormSchema);
+      updateData.push(data as FormSchema)
     }
     if (isArray(data)) {
-      updateData = [...data];
+      updateData = [...data]
     }
 
-    const hasField = updateData.every((item) => Reflect.has(item, 'field') && item.field);
+    const hasField = updateData.every(item => Reflect.has(item, 'field') && item.field)
 
     if (!hasField) {
-      error(
-        'All children of the form Schema array that need to be updated must contain the `field` field'
-      );
-      return;
+      error('All children of the form Schema array that need to be updated must contain the `field` field')
+      return
     }
-    schemaRef.value = updateData as FormSchema[];
+    schemaRef.value = updateData as FormSchema[]
   }
 
   async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
-    let updateData: Partial<FormSchema>[] = [];
+    let updateData: Partial<FormSchema>[] = []
     if (isObject(data)) {
-      updateData.push(data as FormSchema);
+      updateData.push(data as FormSchema)
     }
     if (isArray(data)) {
-      updateData = [...data];
+      updateData = [...data]
     }
 
-    const hasField = updateData.every((item) => Reflect.has(item, 'field') && item.field);
+    const hasField = updateData.every(item => Reflect.has(item, 'field') && item.field)
 
     if (!hasField) {
-      error(
-        'All children of the form Schema array that need to be updated must contain the `field` field'
-      );
-      return;
+      error('All children of the form Schema array that need to be updated must contain the `field` field')
+      return
     }
-    const schema: FormSchema[] = [];
-    updateData.forEach((item) => {
-      unref(getSchema).forEach((val) => {
+    const schema: FormSchema[] = []
+    updateData.forEach(item => {
+      unref(getSchema).forEach(val => {
         if (val.field === item.field) {
-          const newSchema = deepMerge(val, item);
-          schema.push(newSchema as FormSchema);
+          const newSchema = deepMerge(val, item)
+          schema.push(newSchema as FormSchema)
         } else {
-          schema.push(val);
+          schema.push(val)
         }
-      });
-    });
-    schemaRef.value = uniqBy(schema, 'field');
+      })
+    })
+    schemaRef.value = uniqBy(schema, 'field')
   }
 
   function getFieldsValue(): Recordable {
-    const formEl = unref(formElRef);
-    if (!formEl) return {};
-    return handleFormValues(toRaw(unref(formModel)));
+    const formEl = unref(formElRef)
+    if (!formEl) return {}
+    return handleFormValues(toRaw(unref(formModel)))
   }
 
   /**
    * @description: Is it time
    */
   function itemIsDateType(key: string) {
-    return unref(getSchema).some((item) => {
-      return item.field === key ? dateItemType.includes(item.component) : false;
-    });
+    return unref(getSchema).some(item => {
+      return item.field === key ? dateItemType.includes(item.component) : false
+    })
   }
 
   async function validateFields(nameList?: NamePath[] | undefined) {
-    return unref(formElRef)?.validateFields(nameList);
+    return unref(formElRef)?.validateFields(nameList)
   }
 
   async function validate(nameList?: NamePath[] | undefined) {
-    return await unref(formElRef)?.validate(nameList);
+    return await unref(formElRef)?.validate(nameList)
   }
 
   async function clearValidate(name?: string | string[]) {
-    await unref(formElRef)?.clearValidate(name);
+    await unref(formElRef)?.clearValidate(name)
   }
 
   async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
-    await unref(formElRef)?.scrollToField(name, options);
+    await unref(formElRef)?.scrollToField(name, options)
   }
 
   /**
    * @description: Form submission
    */
   async function handleSubmit(e?: Event): Promise<void> {
-    e && e.preventDefault();
-    const { submitFunc } = unref(getProps);
+    e && e.preventDefault()
+    const { submitFunc } = unref(getProps)
     if (submitFunc && isFunction(submitFunc)) {
-      await submitFunc();
-      return;
+      await submitFunc()
+      return
     }
-    const formEl = unref(formElRef);
-    if (!formEl) return;
+    const formEl = unref(formElRef)
+    if (!formEl) return
     try {
-      const values = await validate();
-      const res = handleFormValues(values);
-      emit('submit', res);
+      const values = await validate()
+      const res = handleFormValues(values)
+      emit('submit', res)
     } catch (error) {
-      throw new Error(error);
+      throw new Error(error)
     }
   }
 
@@ -257,6 +253,6 @@ export function useFormEvents({
     removeSchemaByFiled,
     resetFields,
     setFieldsValue,
-    scrollToField,
-  };
+    scrollToField
+  }
 }

+ 40 - 45
src/components/Form/src/hooks/useFormValues.ts

@@ -1,89 +1,84 @@
-import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
-import { dateUtil } from '/@/utils/dateUtil';
+import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'
+import { dateUtil } from '/@/utils/dateUtil'
 
-import { unref } from 'vue';
-import type { Ref, ComputedRef } from 'vue';
-import type { FieldMapToTime, FormSchema } from '../types/form';
+import { unref } from 'vue'
+import type { Ref, ComputedRef } from 'vue'
+import type { FormProps, FormSchema } from '../types/form'
+
+import { set } from 'lodash-es'
 
 interface UseFormValuesContext {
-  transformDateFuncRef: Ref<Fn>;
-  fieldMapToTimeRef: Ref<FieldMapToTime>;
-  defaultValueRef: Ref<any>;
-  getSchema: ComputedRef<FormSchema[]>;
-  formModel: Recordable;
+  defaultValueRef: Ref<any>
+  getSchema: ComputedRef<FormSchema[]>
+  getProps: ComputedRef<FormProps>
+  formModel: Recordable
 }
-export function useFormValues({
-  transformDateFuncRef,
-  fieldMapToTimeRef,
-  defaultValueRef,
-  getSchema,
-  formModel,
-}: UseFormValuesContext) {
+export function useFormValues({ defaultValueRef, getSchema, formModel, getProps }: UseFormValuesContext) {
   // Processing form values
   function handleFormValues(values: Recordable) {
     if (!isObject(values)) {
-      return {};
+      return {}
     }
-    const res: Recordable = {};
+    const res: Recordable = {}
     for (const item of Object.entries(values)) {
-      let [, value] = item;
-      const [key] = item;
+      let [, value] = item
+      const [key] = item
       if ((isArray(value) && value.length === 0) || isFunction(value)) {
-        continue;
+        continue
       }
-      const transformDateFunc = unref(transformDateFuncRef);
+      const transformDateFunc = unref(getProps).transformDateFunc
       if (isObject(value)) {
-        value = transformDateFunc(value);
+        value = transformDateFunc?.(value)
       }
       if (isArray(value) && value[0]?._isAMomentObject && value[1]?._isAMomentObject) {
-        value = value.map((item) => transformDateFunc(item));
+        value = value.map(item => transformDateFunc?.(item))
       }
       // Remove spaces
       if (isString(value)) {
-        value = value.trim();
+        value = value.trim()
       }
-      res[key] = value;
+      set(res, key, value)
     }
-    return handleRangeTimeValue(res);
+    return handleRangeTimeValue(res)
   }
 
   /**
    * @description: Processing time interval parameters
    */
   function handleRangeTimeValue(values: Recordable) {
-    const fieldMapToTime = unref(fieldMapToTimeRef);
+    const fieldMapToTime = unref(getProps).fieldMapToTime
 
     if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
-      return values;
+      return values
     }
 
     for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) {
       if (!field || !startTimeKey || !endTimeKey || !values[field]) {
-        continue;
+        continue
       }
 
-      const [startTime, endTime]: string[] = values[field];
+      const [startTime, endTime]: string[] = values[field]
 
-      values[startTimeKey] = dateUtil(startTime).format(format);
-      values[endTimeKey] = dateUtil(endTime).format(format);
-      Reflect.deleteProperty(values, field);
+      values[startTimeKey] = dateUtil(startTime).format(format)
+      values[endTimeKey] = dateUtil(endTime).format(format)
+      Reflect.deleteProperty(values, field)
     }
 
-    return values;
+    return values
   }
 
   function initDefault() {
-    const schemas = unref(getSchema);
-    const obj: Recordable = {};
-    schemas.forEach((item) => {
-      const { defaultValue } = item;
+    const schemas = unref(getSchema)
+    const obj: Recordable = {}
+    schemas.forEach(item => {
+      const { defaultValue } = item
       if (!isNullOrUnDef(defaultValue)) {
-        obj[item.field] = defaultValue;
-        formModel[item.field] = defaultValue;
+        obj[item.field] = defaultValue
+        formModel[item.field] = defaultValue
       }
-    });
-    defaultValueRef.value = obj;
+    })
+    defaultValueRef.value = obj
   }
 
-  return { handleFormValues, initDefault };
+  return { handleFormValues, initDefault }
 }

+ 23 - 19
src/components/Form/src/props.ts

@@ -1,49 +1,51 @@
-import type { FieldMapToTime, FormSchema } from './types/form';
-import type { CSSProperties, PropType } from 'vue';
-import type { ColEx } from './types';
-import type { TableActionType } from '/@/components/Table';
-import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
-
-import { propTypes } from '/@/utils/propTypes';
+import type { FieldMapToTime, FormSchema } from './types/form'
+import type { CSSProperties, PropType } from 'vue'
+import type { ColEx } from './types'
+import type { TableActionType } from '/@/components/Table'
+import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
+import type { RowProps } from 'ant-design-vue/lib/grid/Row'
+import { propTypes } from '/@/utils/propTypes'
 
 export const basicProps = {
   model: {
     type: Object as PropType<Recordable>,
-    default: {},
+    default: {}
   },
   // 标签宽度  固定宽度
   labelWidth: {
     type: [Number, String] as PropType<number | string>,
-    default: 0,
+    default: 0
   },
   fieldMapToTime: {
     type: Array as PropType<FieldMapToTime>,
-    default: () => [],
+    default: () => []
   },
   compact: propTypes.bool,
   // 表单配置规则
   schemas: {
     type: [Array] as PropType<FormSchema[]>,
-    default: () => [],
+    default: () => []
   },
   mergeDynamicData: {
     type: Object as PropType<Recordable>,
-    default: null,
+    default: null
   },
   baseRowStyle: {
-    type: Object as PropType<CSSProperties>,
+    type: Object as PropType<CSSProperties>
   },
   baseColProps: {
-    type: Object as PropType<Partial<ColEx>>,
+    type: Object as PropType<Partial<ColEx>>
   },
   autoSetPlaceHolder: propTypes.bool.def(true),
+  // 在INPUT组件上单击回车时,是否自动提交
+  autoSubmitOnEnter: propTypes.bool.def(false),
   submitOnReset: propTypes.bool,
   size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
   // 禁用表单
   disabled: propTypes.bool,
   emptySpan: {
     type: [Number, Object] as PropType<number>,
-    default: 0,
+    default: 0
   },
   // 是否显示收起展开按钮
   showAdvancedButton: propTypes.bool,
@@ -51,8 +53,8 @@ export const basicProps = {
   transformDateFunc: {
     type: Function as PropType<Fn>,
     default: (date: any) => {
-      return date._isAMomentObject ? date?.format('YYYY-MM-DD HH:mm:ss') : date;
-    },
+      return date._isAMomentObject ? date?.format('YYYY-MM-DD HH:mm:ss') : date
+    }
   },
   rulesMessageJoinLabel: propTypes.bool.def(true),
   // 超过3行自动折叠
@@ -85,7 +87,7 @@ export const basicProps = {
 
   layout: propTypes.oneOf(['horizontal', 'vertical', 'inline']).def('horizontal'),
   tableAction: {
-    type: Object as PropType<TableActionType>,
+    type: Object as PropType<TableActionType>
   },
 
   wrapperCol: Object as PropType<Partial<ColEx>>,
@@ -93,4 +95,6 @@ export const basicProps = {
   colon: propTypes.bool,
 
   labelAlign: propTypes.string,
-};
+
+  rowProps: Object as PropType<RowProps>
+}

+ 108 - 105
src/components/Form/src/types/form.ts

@@ -1,209 +1,212 @@
-import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
-import type { VNode } from 'vue';
-import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/buttonTypes';
+import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface'
+import type { VNode } from 'vue'
+import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/buttonTypes'
 
-import type { FormItem } from './formItem';
-import type { ColEx, ComponentType } from './index';
-import type { TableActionType } from '/@/components/Table/src/types/table';
-import type { CSSProperties } from 'vue';
+import type { FormItem } from './formItem'
+import type { ColEx, ComponentType } from './index'
+import type { TableActionType } from '/@/components/Table/src/types/table'
+import type { CSSProperties } from 'vue'
+import type { RowProps } from 'ant-design-vue/lib/grid/Row'
 
-export type FieldMapToTime = [string, [string, string], string?][];
+export type FieldMapToTime = [string, [string, string], string?][]
 
 export type Rule = RuleObject & {
-  trigger?: 'blur' | 'change' | ['change', 'blur'];
-};
+  trigger?: 'blur' | 'change' | ['change', 'blur']
+}
 
 export interface RenderCallbackParams {
-  schema: FormSchema;
-  values: Recordable;
-  model: Recordable;
-  field: string;
+  schema: FormSchema
+  values: Recordable
+  model: Recordable
+  field: string
 }
 
 export interface ButtonProps extends AntdButtonProps {
-  text?: string;
+  text?: string
 }
 
 export interface FormActionType {
-  submit: () => Promise<void>;
-  setFieldsValue: <T>(values: T) => Promise<void>;
-  resetFields: () => Promise<void>;
-  getFieldsValue: () => Recordable;
-  clearValidate: (name?: string | string[]) => Promise<void>;
-  updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
-  resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
-  setProps: (formProps: Partial<FormProps>) => Promise<void>;
-  removeSchemaByFiled: (field: string | string[]) => Promise<void>;
+  submit: () => Promise<void>
+  setFieldsValue: <T>(values: T) => Promise<void>
+  resetFields: () => Promise<void>
+  getFieldsValue: () => Recordable
+  clearValidate: (name?: string | string[]) => Promise<void>
+  updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>
+  resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>
+  setProps: (formProps: Partial<FormProps>) => Promise<void>
+  removeSchemaByFiled: (field: string | string[]) => Promise<void>
   appendSchemaByField: (
     schema: FormSchema,
     prefixField: string | undefined,
     first?: boolean | undefined
-  ) => Promise<void>;
-  validateFields: (nameList?: NamePath[]) => Promise<any>;
-  validate: (nameList?: NamePath[]) => Promise<any>;
-  scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
+  ) => Promise<void>
+  validateFields: (nameList?: NamePath[]) => Promise<any>
+  validate: (nameList?: NamePath[]) => Promise<any>
+  scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>
 }
 
-export type RegisterFn = (formInstance: FormActionType) => void;
+export type RegisterFn = (formInstance: FormActionType) => void
 
-export type UseFormReturnType = [RegisterFn, FormActionType];
+export type UseFormReturnType = [RegisterFn, FormActionType]
 
 export interface FormProps {
-  // layout?: 'vertical' | 'inline' | 'horizontal';
+  layout?: 'vertical' | 'inline' | 'horizontal'
   // Form value
-  model?: Recordable;
+  model?: Recordable
   // The width of all items in the entire form
-  labelWidth?: number | string;
+  labelWidth?: number | string
+  //alignment
+  labelAlign?: 'left' | 'right'
+  //Row configuration for the entire form
+  rowProps?: RowProps
   // Submit form on reset
-  submitOnReset?: boolean;
+  submitOnReset?: boolean
   // Col configuration for the entire form
-  labelCol?: Partial<ColEx>;
+  labelCol?: Partial<ColEx>
   // Col configuration for the entire form
-  wrapperCol?: Partial<ColEx>;
+  wrapperCol?: Partial<ColEx>
 
   // General row style
-  baseRowStyle?: CSSProperties;
+  baseRowStyle?: CSSProperties
 
   // General col configuration
-  baseColProps?: Partial<ColEx>;
+  baseColProps?: Partial<ColEx>
 
   // Form configuration rules
-  schemas?: FormSchema[];
+  schemas?: FormSchema[]
   // Function values used to merge into dynamic control form items
-  mergeDynamicData?: Recordable;
+  mergeDynamicData?: Recordable
   // Compact mode for search forms
-  compact?: boolean;
+  compact?: boolean
   // Blank line span
-  emptySpan?: number | Partial<ColEx>;
+  emptySpan?: number | Partial<ColEx>
   // Internal component size of the form
-  size?: 'default' | 'small' | 'large';
+  size?: 'default' | 'small' | 'large'
   // Whether to disable
-  disabled?: boolean;
+  disabled?: boolean
   // Time interval fields are mapped into multiple
-  fieldMapToTime?: FieldMapToTime;
+  fieldMapToTime?: FieldMapToTime
   // Placeholder is set automatically
-  autoSetPlaceHolder?: boolean;
+  autoSetPlaceHolder?: boolean
+  // Auto submit on press enter on input
+  autoSubmitOnEnter?: boolean
   // Check whether the information is added to the label
-  rulesMessageJoinLabel?: boolean;
+  rulesMessageJoinLabel?: boolean
   // Whether to show collapse and expand buttons
-  showAdvancedButton?: boolean;
+  showAdvancedButton?: boolean
   // Whether to focus on the first input box, only works when the first form item is input
-  autoFocusFirstItem?: boolean;
+  autoFocusFirstItem?: boolean
   // Automatically collapse over the specified number of rows
-  autoAdvancedLine?: number;
+  autoAdvancedLine?: number
   // Whether to show the operation button
-  showActionButtonGroup?: boolean;
+  showActionButtonGroup?: boolean
 
   // Reset button configuration
-  resetButtonOptions?: Partial<ButtonProps>;
+  resetButtonOptions?: Partial<ButtonProps>
 
   // Confirm button configuration
-  submitButtonOptions?: Partial<ButtonProps>;
+  submitButtonOptions?: Partial<ButtonProps>
 
   // Operation column configuration
-  actionColOptions?: Partial<ColEx>;
+  actionColOptions?: Partial<ColEx>
 
   // Show reset button
-  showResetButton?: boolean;
+  showResetButton?: boolean
   // Show confirmation button
-  showSubmitButton?: boolean;
+  showSubmitButton?: boolean
 
-  resetFunc?: () => Promise<void>;
-  submitFunc?: () => Promise<void>;
-  transformDateFunc?: (date: any) => string;
-  colon?: boolean;
+  resetFunc?: () => Promise<void>
+  submitFunc?: () => Promise<void>
+  transformDateFunc?: (date: any) => string
+  colon?: boolean
 }
 export interface FormSchema {
   // Field name
-  field: string;
+  field: string
   // Event name triggered by internal value change, default change
-  changeEvent?: string;
+  changeEvent?: string
   // Variable name bound to v-model Default value
-  valueField?: string;
+  valueField?: string
   // Label name
-  label: string;
+  label: string
   // Auxiliary text
-  subLabel?: string;
+  subLabel?: string
   // Help text on the right side of the text
-  helpMessage?: string | string[];
+  helpMessage?: string | string[] | ((renderCallbackParams: RenderCallbackParams) => string | string[])
   // BaseHelp component props
-  helpComponentProps?: Partial<HelpComponentProps>;
+  helpComponentProps?: Partial<HelpComponentProps>
   // Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
-  labelWidth?: string | number;
+  labelWidth?: string | number
   // Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
-  disabledLabelWidth?: boolean;
+  disabledLabelWidth?: boolean
   // render component
-  component: ComponentType;
+  component: ComponentType
   // Component parameters
   componentProps?:
     | ((opt: {
-        schema: FormSchema;
-        tableAction: TableActionType;
-        formActionType: FormActionType;
-        formModel: Recordable;
+        schema: FormSchema
+        tableAction: TableActionType
+        formActionType: FormActionType
+        formModel: Recordable
       }) => Recordable)
-    | object;
+    | object
   // Required
-  required?: boolean;
+  required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
 
-  suffix?: string | number | ((values: RenderCallbackParams) => string | number);
+  suffix?: string | number | ((values: RenderCallbackParams) => string | number)
 
   // Validation rules
-  rules?: Rule[];
+  rules?: Rule[]
   // Check whether the information is added to the label
-  rulesMessageJoinLabel?: boolean;
+  rulesMessageJoinLabel?: boolean
 
   // Reference formModelItem
-  itemProps?: Partial<FormItem>;
+  itemProps?: Partial<FormItem>
 
   // col configuration outside formModelItem
-  colProps?: Partial<ColEx>;
+  colProps?: Partial<ColEx>
 
   // 默认值
-  defaultValue?: any;
-  isAdvanced?: boolean;
+  defaultValue?: any
+  isAdvanced?: boolean
 
   // Matching details components
-  span?: number;
+  span?: number
 
-  ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
+  ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
 
-  show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
+  show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
 
   // Render the content in the form-item tag
-  render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
+  render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string
 
   // Rendering col content requires outer wrapper form-item
-  renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
+  renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string
 
-  renderComponentContent?:
-    | ((renderCallbackParams: RenderCallbackParams) => any)
-    | VNode
-    | VNode[]
-    | string;
+  renderComponentContent?: ((renderCallbackParams: RenderCallbackParams) => any) | VNode | VNode[] | string
 
   // Custom slot, in from-item
-  slot?: string;
+  slot?: string
 
   // Custom slot, similar to renderColContent
-  colSlot?: string;
+  colSlot?: string
 
-  dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
+  dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean)
 
-  dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
+  dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]
 }
 export interface HelpComponentProps {
-  maxWidth: string;
+  maxWidth: string
   // Whether to display the serial number
-  showIndex: boolean;
+  showIndex: boolean
   // Text list
-  text: any;
+  text: any
   // colour
-  color: string;
+  color: string
   // font size
-  fontSize: string;
-  icon: string;
-  absolute: boolean;
+  fontSize: string
+  icon: string
+  absolute: boolean
   // Positioning
-  position: any;
+  position: any
 }

+ 17 - 15
src/components/Form/src/types/index.ts

@@ -1,83 +1,83 @@
-type ColSpanType = number | string;
+type ColSpanType = number | string
 export interface ColEx {
-  style?: any;
+  style?: any
   /**
    * raster number of cells to occupy, 0 corresponds to display: none
    * @default none (0)
    * @type ColSpanType
    */
-  span?: ColSpanType;
+  span?: ColSpanType
 
   /**
    * raster order, used in flex layout mode
    * @default 0
    * @type ColSpanType
    */
-  order?: ColSpanType;
+  order?: ColSpanType
 
   /**
    * the layout fill of flex
    * @default none
    * @type ColSpanType
    */
-  flex?: ColSpanType;
+  flex?: ColSpanType
 
   /**
    * the number of cells to offset Col from the left
    * @default 0
    * @type ColSpanType
    */
-  offset?: ColSpanType;
+  offset?: ColSpanType
 
   /**
    * the number of cells that raster is moved to the right
    * @default 0
    * @type ColSpanType
    */
-  push?: ColSpanType;
+  push?: ColSpanType
 
   /**
    * the number of cells that raster is moved to the left
    * @default 0
    * @type ColSpanType
    */
-  pull?: ColSpanType;
+  pull?: ColSpanType
 
   /**
    * <576px and also default setting, could be a span value or an object containing above props
    * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
    */
-  xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
+  xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
 
   /**
    * ≥576px, could be a span value or an object containing above props
    * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
    */
-  sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
+  sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
 
   /**
    * ≥768px, could be a span value or an object containing above props
    * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
    */
-  md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
+  md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
 
   /**
    * ≥992px, could be a span value or an object containing above props
    * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
    */
-  lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
+  lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
 
   /**
    * ≥1200px, could be a span value or an object containing above props
    * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
    */
-  xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
+  xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
 
   /**
    * ≥1600px, could be a span value or an object containing above props
    * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
    */
-  xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
+  xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
 }
 
 export type ComponentType =
@@ -108,4 +108,6 @@ export type ComponentType =
   | 'StrengthMeter'
   | 'Upload'
   | 'IconPicker'
-  | 'Render';
+  | 'Render'
+  | 'Slider'
+  | 'Rate'

+ 5 - 5
src/components/Icon/index.ts

@@ -1,7 +1,7 @@
-import Icon from './src/index.vue';
-import SvgIcon from './src/SvgIcon.vue';
-import IconPicker from './src/IconPicker.vue';
+import Icon from './src/Icon.vue'
+import SvgIcon from './src/SvgIcon.vue'
+import IconPicker from './src/IconPicker.vue'
 
-export { Icon, IconPicker, SvgIcon };
+export { Icon, IconPicker, SvgIcon }
 
-export default Icon;
+export default Icon

+ 107 - 0
src/components/Icon/src/Icon.vue

@@ -0,0 +1,107 @@
+<template>
+  <SvgIcon :size="size" :name="getSvgIcon" v-if="isSvgIcon" :class="[$attrs.class]" :spin="spin" />
+  <span
+    v-else
+    ref="elRef"
+    :class="[$attrs.class, 'app-iconify anticon', spin && 'app-iconify-spin']"
+    :style="getWrapStyle"
+  ></span>
+</template>
+<script lang="ts">
+  import type { PropType } from 'vue'
+  import { defineComponent, ref, watch, onMounted, nextTick, unref, computed, CSSProperties } from 'vue'
+
+  import SvgIcon from './SvgIcon.vue'
+  import Iconify from '@purge-icons/generated'
+  import { isString } from '/@/utils/is'
+  import { propTypes } from '/@/utils/propTypes'
+
+  const SVG_END_WITH_FLAG = '|svg'
+  export default defineComponent({
+    name: 'GIcon',
+    components: { SvgIcon },
+    props: {
+      // icon name
+      icon: propTypes.string,
+      // icon color
+      color: propTypes.string,
+      // icon size
+      size: {
+        type: [String, Number] as PropType<string | number>,
+        default: 16
+      },
+      spin: propTypes.bool.def(false),
+      prefix: propTypes.string.def('')
+    },
+    setup(props) {
+      const elRef = ref<ElRef>(null)
+
+      const isSvgIcon = computed(() => props.icon?.endsWith(SVG_END_WITH_FLAG))
+      const getSvgIcon = computed(() => props.icon.replace(SVG_END_WITH_FLAG, ''))
+      const getIconRef = computed(() => `${props.prefix ? props.prefix + ':' : ''}${props.icon}`)
+
+      const update = async () => {
+        if (unref(isSvgIcon)) return
+
+        const el = unref(elRef)
+        if (!el) return
+
+        await nextTick()
+        const icon = unref(getIconRef)
+        if (!icon) return
+
+        const svg = Iconify.renderSVG(icon, {})
+        if (svg) {
+          el.textContent = ''
+          el.appendChild(svg)
+        } else {
+          const span = document.createElement('span')
+          span.className = 'iconify'
+          span.dataset.icon = icon
+          el.textContent = ''
+          el.appendChild(span)
+        }
+      }
+
+      const getWrapStyle = computed((): CSSProperties => {
+        const { size, color } = props
+        let fs = size
+        if (isString(size)) {
+          fs = parseInt(size, 10)
+        }
+
+        return {
+          fontSize: `${fs}px`,
+          color: color,
+          display: 'inline-flex'
+        }
+      })
+
+      watch(() => props.icon, update, { flush: 'post' })
+
+      onMounted(update)
+
+      return { elRef, getWrapStyle, isSvgIcon, getSvgIcon }
+    }
+  })
+</script>
+<style lang="less">
+  .app-iconify {
+    display: inline-block;
+    // vertical-align: middle;
+
+    &-spin {
+      svg {
+        animation: loadingCircle 1s infinite linear;
+      }
+    }
+  }
+
+  span.iconify {
+    display: block;
+    min-width: 1em;
+    min-height: 1em;
+    background-color: @iconify-bg-color;
+    border-radius: 100%;
+  }
+</style>

+ 64 - 67
src/components/Icon/src/IconPicker.vue

@@ -7,19 +7,10 @@
     v-model:value="currentSelect"
   >
     <template #addonAfter>
-      <Popover
-        placement="bottomLeft"
-        trigger="click"
-        v-model="visible"
-        :overlayClassName="`${prefixCls}-popover`"
-      >
+      <Popover placement="bottomLeft" trigger="click" v-model="visible" :overlayClassName="`${prefixCls}-popover`">
         <template #title>
           <div class="flex justify-between">
-            <a-input
-              :placeholder="t('component.icon.search')"
-              @change="handleSearchChange"
-              allowClear
-            />
+            <a-input :placeholder="t('component.icon.search')" @change="handleSearchChange" allowClear />
           </div>
         </template>
 
@@ -31,7 +22,18 @@
                   v-for="icon in getPaginationList"
                   :key="icon"
                   :class="currentSelect === icon ? 'border border-primary' : ''"
-                  class="p-2 w-1/8 cursor-pointer mr-1 mt-1 flex justify-center items-center border border-solid hover:border-primary"
+                  class="
+                    p-2
+                    w-1/8
+                    cursor-pointer
+                    mr-1
+                    mt-1
+                    flex
+                    justify-center
+                    items-center
+                    border border-solid
+                    hover:border-primary
+                  "
                   @click="handleClick(icon)"
                   :title="icon"
                 >
@@ -65,38 +67,38 @@
   </a-input>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, watchEffect, watch, unref } from 'vue';
+  import { defineComponent, ref, watchEffect, watch, unref } from 'vue'
 
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { ScrollContainer } from '/@/components/Container';
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { ScrollContainer } from '/@/components/Container'
 
-  import { Input, Popover, Pagination, Empty } from 'ant-design-vue';
-  import Icon from './index.vue';
-  import SvgIcon from './SvgIcon.vue';
+  import { Input, Popover, Pagination, Empty } from 'ant-design-vue'
+  import Icon from './Icon.vue'
+  import SvgIcon from './SvgIcon.vue'
 
-  import iconsData from '../data/icons.data';
-  import { propTypes } from '/@/utils/propTypes';
-  import { usePagination } from '/@/hooks/web/usePagination';
-  import { useDebounceFn } from '@vueuse/core';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import svgIcons from 'virtual:svg-icons-names';
+  import iconsData from '../data/icons.data'
+  import { propTypes } from '/@/utils/propTypes'
+  import { usePagination } from '/@/hooks/web/usePagination'
+  import { useDebounceFn } from '@vueuse/core'
+  import { useI18n } from '/@/hooks/web/useI18n'
+  import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'
+  import { useMessage } from '/@/hooks/web/useMessage'
+  import svgIcons from 'virtual:svg-icons-names'
 
   function getIcons() {
-    const data = iconsData as any;
-    const prefix: string = data?.prefix ?? '';
-    let result: string[] = [];
+    const data = iconsData as any
+    const prefix: string = data?.prefix ?? ''
+    let result: string[] = []
     if (prefix) {
-      result = (data?.icons ?? []).map((item) => `${prefix}:${item}`);
+      result = (data?.icons ?? []).map(item => `${prefix}:${item}`)
     } else if (Array.isArray(iconsData)) {
-      result = iconsData as string[];
+      result = iconsData as string[]
     }
-    return result;
+    return result
   }
 
   function getSvgIcons() {
-    return svgIcons.map((icon) => icon.replace('icon-', ''));
+    return svgIcons.map(icon => icon.replace('icon-', ''))
   }
 
   export default defineComponent({
@@ -108,62 +110,57 @@
       width: propTypes.string.def('100%'),
       pageSize: propTypes.number.def(140),
       copy: propTypes.bool.def(false),
-      mode: propTypes
-        .oneOf<('svg' | 'iconify')[]>(['svg', 'iconify'])
-        .def('iconify'),
+      mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify')
     },
     emits: ['change'],
     setup(props, { emit }) {
-      const isSvgMode = props.mode === 'svg';
-      const icons = isSvgMode ? getSvgIcons() : getIcons();
+      const isSvgMode = props.mode === 'svg'
+      const icons = isSvgMode ? getSvgIcons() : getIcons()
 
-      const currentSelect = ref('');
-      const visible = ref(false);
-      const currentList = ref(icons);
+      const currentSelect = ref('')
+      const visible = ref(false)
+      const currentList = ref(icons)
 
-      const { t } = useI18n();
-      const { prefixCls } = useDesign('icon-picker');
+      const { t } = useI18n()
+      const { prefixCls } = useDesign('icon-picker')
 
-      const debounceHandleSearchChange = useDebounceFn(handleSearchChange, 100);
-      const { clipboardRef, isSuccessRef } = useCopyToClipboard(props.value);
-      const { createMessage } = useMessage();
+      const debounceHandleSearchChange = useDebounceFn(handleSearchChange, 100)
+      const { clipboardRef, isSuccessRef } = useCopyToClipboard(props.value)
+      const { createMessage } = useMessage()
 
-      const { getPaginationList, getTotal, setCurrentPage } = usePagination(
-        currentList,
-        props.pageSize
-      );
+      const { getPaginationList, getTotal, setCurrentPage } = usePagination(currentList, props.pageSize)
 
       watchEffect(() => {
-        currentSelect.value = props.value;
-      });
+        currentSelect.value = props.value
+      })
 
       watch(
         () => currentSelect.value,
-        (v) => emit('change', v)
-      );
+        v => emit('change', v)
+      )
 
       function handlePageChange(page: number) {
-        setCurrentPage(page);
+        setCurrentPage(page)
       }
 
       function handleClick(icon: string) {
-        currentSelect.value = icon;
+        currentSelect.value = icon
         if (props.copy) {
-          clipboardRef.value = icon;
+          clipboardRef.value = icon
           if (unref(isSuccessRef)) {
-            createMessage.success(t('component.icon.copy'));
+            createMessage.success(t('component.icon.copy'))
           }
         }
       }
 
       function handleSearchChange(e: ChangeEvent) {
-        const value = e.target.value;
+        const value = e.target.value
         if (!value) {
-          setCurrentPage(1);
-          currentList.value = icons;
-          return;
+          setCurrentPage(1)
+          currentList.value = icons
+          return
         }
-        currentList.value = icons.filter((item) => item.includes(value));
+        currentList.value = icons.filter(item => item.includes(value))
       }
 
       return {
@@ -176,10 +173,10 @@
         handlePageChange,
         handleClick,
         currentSelect,
-        handleSearchChange: debounceHandleSearchChange,
-      };
-    },
-  });
+        handleSearchChange: debounceHandleSearchChange
+      }
+    }
+  })
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-icon-picker';

+ 22 - 28
src/components/Icon/src/SvgIcon.vue

@@ -1,55 +1,49 @@
 <template>
-  <svg
-    :class="[prefixCls, $attrs.class, spin && 'svg-icon-spin']"
-    :style="getStyle"
-    aria-hidden="true"
-  >
+  <svg :class="[prefixCls, $attrs.class, spin && 'svg-icon-spin']" :style="getStyle" aria-hidden="true">
     <use :xlink:href="symbolId" />
   </svg>
 </template>
 <script lang="ts">
-  import type { CSSProperties } from 'vue';
-  import { defineComponent, computed } from 'vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
+  import type { CSSProperties } from 'vue'
+  import { defineComponent, computed } from 'vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
 
   export default defineComponent({
     name: 'SvgIcon',
     props: {
       prefix: {
         type: String,
-        default: 'icon',
+        default: 'icon'
       },
       name: {
         type: String,
-        required: true,
+        required: true
       },
       size: {
         type: [Number, String],
-        default: 16,
+        default: 16
       },
       spin: {
         type: Boolean,
-        default: false,
-      },
+        default: false
+      }
     },
     setup(props) {
-      const { prefixCls } = useDesign('svg-icon');
-      const symbolId = computed(() => `#${props.prefix}-${props.name}`);
+      const { prefixCls } = useDesign('svg-icon')
+      const symbolId = computed(() => `#${props.prefix}-${props.name}`)
 
-      const getStyle = computed(
-        (): CSSProperties => {
-          const { size } = props;
-          let s = `${size}`;
-          s = `${s.replace('px', '')}px`;
-          return {
-            width: s,
-            height: s,
-          };
+      const getStyle = computed((): CSSProperties => {
+        const { size } = props
+        let s = `${size}`
+        s = `${s.replace('px', '')}px`
+        return {
+          width: s,
+          height: s
         }
-      );
-      return { symbolId, prefixCls, getStyle };
-    },
-  });
+      })
+      return { symbolId, prefixCls, getStyle }
+    }
+  })
 </script>
 <style lang="less" scoped>
   @prefix-cls: ~'@{namespace}-svg-icon';

+ 4 - 4
src/components/Loading/index.ts

@@ -1,5 +1,5 @@
-import Loading from './src/index.vue';
+import Loading from './src/Loading.vue'
 
-export { Loading };
-export { useLoading } from './src/useLoading';
-export { createLoading } from './src/createLoading';
+export { Loading }
+export { useLoading } from './src/useLoading'
+export { createLoading } from './src/createLoading'

+ 69 - 0
src/components/Loading/src/Loading.vue

@@ -0,0 +1,69 @@
+<template>
+  <section class="full-loading" :class="{ absolute }" v-show="loading">
+    <Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
+  </section>
+</template>
+<script lang="ts">
+  import { PropType } from 'vue'
+
+  import { defineComponent } from 'vue'
+  import { Spin } from 'ant-design-vue'
+
+  import { SizeEnum } from '/@/enums/sizeEnum'
+
+  export default defineComponent({
+    name: 'Loading',
+    components: { Spin },
+    props: {
+      tip: {
+        type: String as PropType<string>,
+        default: ''
+      },
+      size: {
+        type: String as PropType<SizeEnum>,
+        default: SizeEnum.LARGE,
+        validator: (v: SizeEnum): boolean => {
+          return [SizeEnum.DEFAULT, SizeEnum.SMALL, SizeEnum.LARGE].includes(v)
+        }
+      },
+      absolute: {
+        type: Boolean as PropType<boolean>,
+        default: false
+      },
+      loading: {
+        type: Boolean as PropType<boolean>,
+        default: false
+      },
+      background: {
+        type: String as PropType<string>
+      }
+    }
+  })
+</script>
+<style lang="less" scoped>
+  .full-loading {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 200;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(240, 242, 245, 0.4);
+
+    &.absolute {
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 300;
+    }
+  }
+
+  html[data-theme='dark'] {
+    .full-loading {
+      background-color: @modal-mask-bg;
+    }
+  }
+</style>

+ 24 - 24
src/components/Loading/src/createLoading.ts

@@ -1,65 +1,65 @@
-import { VNode, defineComponent } from 'vue';
-import type { LoadingProps } from './types';
+import { VNode, defineComponent } from 'vue'
+import type { LoadingProps } from './types'
 
-import { createVNode, render, reactive, h } from 'vue';
-import Loading from './index.vue';
+import { createVNode, render, reactive, h } from 'vue'
+import Loading from './Loading.vue'
 
 export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElement, wait = false) {
-  let vm: Nullable<VNode> = null;
+  let vm: Nullable<VNode> = null
   const data = reactive({
     tip: '',
     loading: true,
-    ...props,
-  });
+    ...props
+  })
 
   const LoadingWrap = defineComponent({
     render() {
-      return h(Loading, { ...data });
-    },
-  });
+      return h(Loading, { ...data })
+    }
+  })
 
-  vm = createVNode(LoadingWrap);
+  vm = createVNode(LoadingWrap)
 
   // TODO fix https://github.com/anncwb/vue-vben-admin/issues/438
   if (wait) {
     setTimeout(() => {
-      render(vm, document.createElement('div'));
-    }, 0);
+      render(vm, document.createElement('div'))
+    }, 0)
   } else {
-    render(vm, document.createElement('div'));
+    render(vm, document.createElement('div'))
   }
 
   function close() {
     if (vm?.el && vm.el.parentNode) {
-      vm.el.parentNode.removeChild(vm.el);
+      vm.el.parentNode.removeChild(vm.el)
     }
   }
 
   function open(target: HTMLElement = document.body) {
     if (!vm || !vm.el) {
-      return;
+      return
     }
-    target.appendChild(vm.el as HTMLElement);
+    target.appendChild(vm.el as HTMLElement)
   }
 
   if (target) {
-    open(target);
+    open(target)
   }
   return {
     vm,
     close,
     open,
     setTip: (tip: string) => {
-      data.tip = tip;
+      data.tip = tip
     },
     setLoading: (loading: boolean) => {
-      data.loading = loading;
+      data.loading = loading
     },
     get loading() {
-      return data.loading;
+      return data.loading
     },
     get $el() {
-      return vm?.el as HTMLElement;
-    },
-  };
+      return vm?.el as HTMLElement
+    }
+  }
 }

+ 3 - 3
src/components/Markdown/index.ts

@@ -1,4 +1,4 @@
-import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
-export const MarkDown = createAsyncComponent(() => import('./src/index.vue'));
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'
+export const MarkDown = createAsyncComponent(() => import('./src/Markdown.vue'))
 
-export * from './src/types';
+export * from './src/types'

+ 121 - 0
src/components/Markdown/src/Markdown.vue

@@ -0,0 +1,121 @@
+<template>
+  <div ref="wrapRef"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, onMounted, unref, onUnmounted, nextTick, computed, watch } from 'vue'
+  import Vditor from 'vditor'
+  import 'vditor/dist/index.css'
+
+  import { propTypes } from '/@/utils/propTypes'
+  import { useLocale } from '/@/locales/useLocale'
+  import { useModalContext } from '../../Modal'
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting'
+
+  type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined
+  export default defineComponent({
+    inheritAttrs: false,
+    props: {
+      height: propTypes.number.def(360),
+      value: propTypes.string.def('')
+    },
+    emits: ['change', 'get'],
+    setup(props, { attrs, emit }) {
+      const wrapRef = ref<ElRef>(null)
+      const vditorRef = ref<Nullable<Vditor>>(null)
+      const initedRef = ref(false)
+
+      const modalFn = useModalContext()
+
+      const { getLocale } = useLocale()
+      const { getDarkMode } = useRootSetting()
+
+      watch(
+        [() => getDarkMode.value, () => initedRef.value],
+        ([val]) => {
+          const vditor = unref(vditorRef)
+
+          if (!vditor) {
+            return
+          }
+          const theme = val === 'dark' ? 'dark' : undefined
+          vditor.setTheme(theme as 'dark')
+        },
+        {
+          immediate: true,
+          flush: 'post'
+        }
+      )
+
+      const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
+        let lang: Lang
+        switch (unref(getLocale)) {
+          case 'en':
+            lang = 'en_US'
+            break
+          case 'ja':
+            lang = 'ja_JP'
+            break
+          case 'ko':
+            lang = 'ko_KR'
+            break
+          default:
+            lang = 'zh_CN'
+        }
+        return lang
+      })
+      function init() {
+        const wrapEl = unref(wrapRef)
+        if (!wrapEl) return
+        const bindValue = { ...attrs, ...props }
+        vditorRef.value = new Vditor(wrapEl, {
+          theme: 'classic',
+          lang: unref(getCurrentLang),
+          mode: 'sv',
+          preview: {
+            actions: []
+          },
+          input: v => {
+            // emit('update:value', v);
+            emit('change', v)
+          },
+          blur: () => {
+            unref(vditorRef)?.setValue(props.value)
+          },
+          ...bindValue,
+          cache: {
+            enable: false
+          }
+        })
+        initedRef.value = true
+      }
+
+      const instance = {
+        getVditor: (): Vditor => vditorRef.value!
+      }
+
+      onMounted(() => {
+        nextTick(() => {
+          init()
+          setTimeout(() => {
+            modalFn?.redoModalHeight?.()
+          }, 200)
+        })
+
+        emit('get', instance)
+      })
+
+      onUnmounted(() => {
+        const vditorInstance = unref(vditorRef)
+        if (!vditorInstance) return
+        try {
+          vditorInstance?.destroy?.()
+        } catch (error) {}
+      })
+
+      return {
+        wrapRef,
+        ...instance
+      }
+    }
+  })
+</script>

+ 88 - 106
src/components/Modal/src/BasicModal.vue

@@ -10,11 +10,7 @@
     </template>
 
     <template #title v-if="!$slots.title">
-      <ModalHeader
-        :helpMessage="getProps.helpMessage"
-        :title="getMergeProps.title"
-        @dblclick="handleTitleDbClick"
-      />
+      <ModalHeader :helpMessage="getProps.helpMessage" :title="getMergeProps.title" @dblclick="handleTitleDbClick" />
     </template>
 
     <template #footer v-if="!$slots.footer">
@@ -49,33 +45,23 @@
   </Modal>
 </template>
 <script lang="ts">
-  import type { ModalProps, ModalMethods } from './types';
-
-  import {
-    defineComponent,
-    computed,
-    ref,
-    watch,
-    unref,
-    watchEffect,
-    toRef,
-    getCurrentInstance,
-    nextTick,
-  } from 'vue';
-
-  import Modal from './components/Modal';
-  import ModalWrapper from './components/ModalWrapper.vue';
-  import ModalClose from './components/ModalClose.vue';
-  import ModalFooter from './components/ModalFooter.vue';
-  import ModalHeader from './components/ModalHeader.vue';
-
-  import { isFunction } from '/@/utils/is';
-  import { deepMerge } from '/@/utils';
-
-  import { basicProps } from './props';
-  import { useFullScreen } from './hooks/useModalFullScreen';
-
-  import { omit } from 'lodash-es';
+  import type { ModalProps, ModalMethods } from './types'
+
+  import { defineComponent, computed, ref, watch, unref, watchEffect, toRef, getCurrentInstance, nextTick } from 'vue'
+
+  import Modal from './components/Modal'
+  import ModalWrapper from './components/ModalWrapper.vue'
+  import ModalClose from './components/ModalClose.vue'
+  import ModalFooter from './components/ModalFooter.vue'
+  import ModalHeader from './components/ModalHeader.vue'
+
+  import { isFunction } from '/@/utils/is'
+  import { deepMerge } from '/@/utils'
+
+  import { basicProps } from './props'
+  import { useFullScreen } from './hooks/useModalFullScreen'
+
+  import { omit } from 'lodash-es'
   export default defineComponent({
     name: 'BasicModal',
     components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
@@ -83,108 +69,104 @@
     props: basicProps,
     emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'],
     setup(props, { emit, attrs }) {
-      const visibleRef = ref(false);
-      const propsRef = ref<Partial<ModalProps> | null>(null);
-      const modalWrapperRef = ref<ComponentRef>(null);
+      const visibleRef = ref(false)
+      const propsRef = ref<Partial<ModalProps> | null>(null)
+      const modalWrapperRef = ref<ComponentRef>(null)
 
       // modal   Bottom and top height
-      const extHeightRef = ref(0);
+      const extHeightRef = ref(0)
       const modalMethods: ModalMethods = {
         setModalProps,
         emitVisible: undefined,
         redoModalHeight: () => {
           nextTick(() => {
             if (unref(modalWrapperRef)) {
-              (unref(modalWrapperRef) as any).setModalHeight();
+              ;(unref(modalWrapperRef) as any).setModalHeight()
             }
-          });
-        },
-      };
+          })
+        }
+      }
 
-      const instance = getCurrentInstance();
+      const instance = getCurrentInstance()
       if (instance) {
-        emit('register', modalMethods, instance.uid);
+        emit('register', modalMethods, instance.uid)
       }
 
       // Custom title component: get title
-      const getMergeProps = computed(
-        (): ModalProps => {
-          return {
-            ...props,
-            ...(unref(propsRef) as any),
-          };
+      const getMergeProps = computed((): ModalProps => {
+        return {
+          ...props,
+          ...(unref(propsRef) as any)
         }
-      );
+      })
 
       const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
         modalWrapperRef,
         extHeightRef,
-        wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
-      });
-
-      // modal component does not need title
-      const getProps = computed(
-        (): ModalProps => {
-          const opt = {
-            ...unref(getMergeProps),
-            visible: unref(visibleRef),
-            title: undefined,
-          };
-          return {
-            ...opt,
-            wrapClassName: unref(getWrapClassName),
-          };
+        wrapClassName: toRef(getMergeProps.value, 'wrapClassName')
+      })
+
+      // modal component does not need title and origin buttons
+      const getProps = computed((): ModalProps => {
+        const opt = {
+          ...unref(getMergeProps),
+          visible: unref(visibleRef),
+          okButtonProps: undefined,
+          cancelButtonProps: undefined,
+          title: undefined
         }
-      );
-
-      const getBindValue = computed(
-        (): Recordable => {
-          const attr = { ...attrs, ...unref(getProps) };
-          if (unref(fullScreenRef)) {
-            return omit(attr, 'height');
-          }
-          return attr;
+        return {
+          ...opt,
+          wrapClassName: unref(getWrapClassName)
         }
-      );
+      })
+
+      const getBindValue = computed((): Recordable => {
+        const attr = { ...attrs, ...unref(getProps) }
+        if (unref(fullScreenRef)) {
+          return omit(attr, 'height')
+        }
+        return attr
+      })
 
       const getWrapperHeight = computed(() => {
-        if (unref(fullScreenRef)) return undefined;
-        return unref(getProps).height;
-      });
+        if (unref(fullScreenRef)) return undefined
+        return unref(getProps).height
+      })
 
       watchEffect(() => {
-        visibleRef.value = !!props.visible;
-        fullScreenRef.value = !!props.defaultFullscreen;
-      });
+        visibleRef.value = !!props.visible
+        fullScreenRef.value = !!props.defaultFullscreen
+      })
 
       watch(
         () => unref(visibleRef),
-        (v) => {
-          emit('visible-change', v);
-          instance && modalMethods.emitVisible?.(v, instance.uid);
+        v => {
+          emit('visible-change', v)
+          instance && modalMethods.emitVisible?.(v, instance.uid)
           nextTick(() => {
             if (props.scrollTop && v && unref(modalWrapperRef)) {
-              (unref(modalWrapperRef) as any).scrollTop();
+              ;(unref(modalWrapperRef) as any).scrollTop()
             }
-          });
+          })
         },
         {
-          immediate: false,
+          immediate: false
         }
-      );
+      )
 
       // 取消事件
       async function handleCancel(e: Event) {
-        e?.stopPropagation();
+        e?.stopPropagation()
 
         if (props.closeFunc && isFunction(props.closeFunc)) {
-          const isClose: boolean = await props.closeFunc();
-          visibleRef.value = !isClose;
-          return;
+          const isClose: boolean = await props.closeFunc()
+          visibleRef.value = !isClose
+          return
         }
 
-        visibleRef.value = false;
-        emit('cancel');
+        visibleRef.value = false
+        emit('cancel')
       }
 
       /**
@@ -192,27 +174,27 @@
        */
       function setModalProps(props: Partial<ModalProps>): void {
         // Keep the last setModalProps
-        propsRef.value = deepMerge(unref(propsRef) || {}, props);
-        if (!Reflect.has(props, 'visible')) return;
-        visibleRef.value = !!props.visible;
+        propsRef.value = deepMerge(unref(propsRef) || {}, props)
+        if (!Reflect.has(props, 'visible')) return
+        visibleRef.value = !!props.visible
       }
 
       function handleOk() {
-        emit('ok');
+        emit('ok')
       }
 
       function handleHeightChange(height: string) {
-        emit('height-change', height);
+        emit('height-change', height)
       }
 
       function handleExtHeight(height: number) {
-        extHeightRef.value = height;
+        extHeightRef.value = height
       }
 
       function handleTitleDbClick(e: ChangeEvent) {
-        if (!props.canFullscreen) return;
-        e.stopPropagation();
-        handleFullScreen(e);
+        if (!props.canFullscreen) return
+        e.stopPropagation()
+        handleFullScreen(e)
       }
 
       return {
@@ -229,8 +211,8 @@
         handleExtHeight,
         handleHeightChange,
         handleTitleDbClick,
-        getWrapperHeight,
-      };
-    },
-  });
+        getWrapperHeight
+      }
+    }
+  })
 </script>

+ 68 - 85
src/components/Modal/src/components/ModalWrapper.vue

@@ -6,26 +6,16 @@
   </ScrollContainer>
 </template>
 <script lang="ts">
-  import type { ModalWrapperProps } from '../types';
-  import type { CSSProperties } from 'vue';
-
-  import {
-    defineComponent,
-    computed,
-    ref,
-    watchEffect,
-    unref,
-    watch,
-    onMounted,
-    nextTick,
-    onUnmounted,
-  } from 'vue';
-
-  import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
-  import { ScrollContainer } from '/@/components/Container';
-
-  import { propTypes } from '/@/utils/propTypes';
-  import { createModalContext } from '../hooks/useModalContext';
+  import type { ModalWrapperProps } from '../types'
+  import type { CSSProperties } from 'vue'
+
+  import { defineComponent, computed, ref, watchEffect, unref, watch, onMounted, nextTick, onUnmounted } from 'vue'
+
+  import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'
+  import { ScrollContainer } from '/@/components/Container'
+
+  import { propTypes } from '/@/utils/propTypes'
+  import { createModalContext } from '../hooks/useModalContext'
 
   export default defineComponent({
     name: 'ModalWrapper',
@@ -41,124 +31,117 @@
       footerOffset: propTypes.number.def(0),
       visible: propTypes.bool,
       fullScreen: propTypes.bool,
-      loadingTip: propTypes.string,
+      loadingTip: propTypes.string
     },
     emits: ['height-change', 'ext-height'],
     setup(props: ModalWrapperProps, { emit }) {
-      const wrapperRef = ref<ComponentRef>(null);
-      const spinRef = ref<ElRef>(null);
-      const realHeightRef = ref(0);
-      const minRealHeightRef = ref(0);
+      const wrapperRef = ref<ComponentRef>(null)
+      const spinRef = ref<ElRef>(null)
+      const realHeightRef = ref(0)
+      const minRealHeightRef = ref(0)
 
-      let realHeight = 0;
+      let realHeight = 0
 
-      let stopElResizeFn: Fn = () => {};
+      let stopElResizeFn: Fn = () => {}
 
-      useWindowSizeFn(setModalHeight.bind(null, false));
+      useWindowSizeFn(setModalHeight.bind(null, false))
 
       createModalContext({
-        redoModalHeight: setModalHeight,
-      });
-
-      const spinStyle = computed(
-        (): CSSProperties => {
-          return {
-            minHeight: `${props.minHeight}px`,
-            // padding 28
-            height: `${unref(realHeightRef)}px`,
-          };
+        redoModalHeight: setModalHeight
+      })
+
+      const spinStyle = computed((): CSSProperties => {
+        return {
+          minHeight: `${props.minHeight}px`,
+          // padding 28
+          maxHeight: `${unref(realHeightRef)}px`
         }
-      );
+      })
 
       watchEffect(() => {
-        props.useWrapper && setModalHeight();
-      });
+        props.useWrapper && setModalHeight()
+      })
 
       watch(
         () => props.fullScreen,
-        (v) => {
-          setModalHeight();
+        v => {
+          setModalHeight()
           if (!v) {
-            realHeightRef.value = minRealHeightRef.value;
+            realHeightRef.value = minRealHeightRef.value
           } else {
-            minRealHeightRef.value = realHeightRef.value;
+            minRealHeightRef.value = realHeightRef.value
           }
         }
-      );
+      )
 
       onMounted(() => {
-        const { modalHeaderHeight, modalFooterHeight } = props;
-        emit('ext-height', modalHeaderHeight + modalFooterHeight);
+        const { modalHeaderHeight, modalFooterHeight } = props
+        emit('ext-height', modalHeaderHeight + modalFooterHeight)
         // listenElResize();
-      });
+      })
 
       onUnmounted(() => {
-        stopElResizeFn && stopElResizeFn();
-      });
+        stopElResizeFn && stopElResizeFn()
+      })
 
       async function scrollTop() {
         nextTick(() => {
-          const wrapperRefDom = unref(wrapperRef);
-          if (!wrapperRefDom) return;
-          (wrapperRefDom as any)?.scrollTo?.(0);
-        });
+          const wrapperRefDom = unref(wrapperRef)
+          if (!wrapperRefDom) return
+          ;(wrapperRefDom as any)?.scrollTo?.(0)
+        })
       }
 
       async function setModalHeight() {
         // 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
         // 加上这个,就必须在使用的时候传递父级的visible
-        if (!props.visible) return;
-        const wrapperRefDom = unref(wrapperRef);
-        if (!wrapperRefDom) return;
+        if (!props.visible) return
+        const wrapperRefDom = unref(wrapperRef)
+        if (!wrapperRefDom) return
 
-        const bodyDom = wrapperRefDom.$el.parentElement;
-        if (!bodyDom) return;
-        bodyDom.style.padding = '0';
-        await nextTick();
+        const bodyDom = wrapperRefDom.$el.parentElement
+        if (!bodyDom) return
+        bodyDom.style.padding = '0'
+        await nextTick()
 
         try {
-          const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement;
-          if (!modalDom) return;
+          const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement
+          if (!modalDom) return
 
-          const modalRect = getComputedStyle(modalDom).top;
-          const modalTop = Number.parseInt(modalRect);
+          const modalRect = getComputedStyle(modalDom).top
+          const modalTop = Number.parseInt(modalRect)
           let maxHeight =
             window.innerHeight -
             modalTop * 2 +
             (props.footerOffset! || 0) -
             props.modalFooterHeight -
-            props.modalHeaderHeight;
+            props.modalHeaderHeight
 
           // 距离顶部过进会出现滚动条
           if (modalTop < 40) {
-            maxHeight -= 26;
+            maxHeight -= 26
           }
-          await nextTick();
-          const spinEl = unref(spinRef);
+          await nextTick()
+          const spinEl = unref(spinRef)
 
-          if (!spinEl) return;
-          await nextTick();
+          if (!spinEl) return
+          await nextTick()
           // if (!realHeight) {
-          realHeight = spinEl.scrollHeight;
+          realHeight = spinEl.scrollHeight
           // }
 
           if (props.fullScreen) {
-            realHeightRef.value =
-              window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 28;
+            realHeightRef.value = window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 28
           } else {
-            realHeightRef.value = props.height
-              ? props.height
-              : realHeight > maxHeight
-              ? maxHeight
-              : realHeight;
+            realHeightRef.value = props.height ? props.height : realHeight > maxHeight ? maxHeight : realHeight
           }
-          emit('height-change', unref(realHeightRef));
+          emit('height-change', unref(realHeightRef))
         } catch (error) {
-          console.log(error);
+          console.log(error)
         }
       }
 
-      return { wrapperRef, spinRef, spinStyle, scrollTop, setModalHeight };
-    },
-  });
+      return { wrapperRef, spinRef, spinStyle, scrollTop, setModalHeight }
+    }
+  })
 </script>

+ 78 - 84
src/components/Modal/src/hooks/useModal.ts

@@ -1,155 +1,149 @@
-import type {
-  UseModalReturnType,
-  ModalMethods,
-  ModalProps,
-  ReturnMethods,
-  UseModalInnerReturnType,
-} from '../types';
-
-import {
-  ref,
-  onUnmounted,
-  unref,
-  getCurrentInstance,
-  reactive,
-  watchEffect,
-  nextTick,
-  toRaw,
-} from 'vue';
-import { isProdMode } from '/@/utils/env';
-import { isFunction } from '/@/utils/is';
-import { isEqual } from 'lodash-es';
-import { tryOnUnmounted } from '@vueuse/core';
-
-import { error } from '/@/utils/log';
-import { computed } from 'vue';
-const dataTransferRef = reactive<any>({});
-
-const visibleData = reactive<{ [key: number]: boolean }>({});
+import type { UseModalReturnType, ModalMethods, ModalProps, ReturnMethods, UseModalInnerReturnType } from '../types'
+
+import { ref, onUnmounted, unref, getCurrentInstance, reactive, watchEffect, nextTick, toRaw } from 'vue'
+import { isProdMode } from '/@/utils/env'
+import { isFunction } from '/@/utils/is'
+import { isEqual } from 'lodash-es'
+import { tryOnUnmounted } from '@vueuse/core'
+
+import { error } from '/@/utils/log'
+import { computed } from 'vue'
+const dataTransferRef = reactive<any>({})
+
+const visibleData = reactive<{ [key: number]: boolean }>({})
 
 /**
  * @description: Applicable to independent modal and call outside
  */
 export function useModal(): UseModalReturnType {
-  const modalRef = ref<Nullable<ModalMethods>>(null);
-  const loadedRef = ref<Nullable<boolean>>(false);
-  const uidRef = ref<string>('');
+  const modalRef = ref<Nullable<ModalMethods>>(null)
+  const loadedRef = ref<Nullable<boolean>>(false)
+  const uidRef = ref<string>('')
 
   function register(modalMethod: ModalMethods, uuid: string) {
-    uidRef.value = uuid;
+    uidRef.value = uuid
 
     isProdMode() &&
       onUnmounted(() => {
-        modalRef.value = null;
-        loadedRef.value = false;
-        dataTransferRef[unref(uidRef)] = null;
-      });
-    if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return;
+        modalRef.value = null
+        loadedRef.value = false
+        dataTransferRef[unref(uidRef)] = null
+      })
+    if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return
 
-    modalRef.value = modalMethod;
+    modalRef.value = modalMethod
     modalMethod.emitVisible = (visible: boolean, uid: number) => {
-      visibleData[uid] = visible;
-    };
+      visibleData[uid] = visible
+    }
   }
 
   const getInstance = () => {
-    const instance = unref(modalRef);
+    const instance = unref(modalRef)
     if (!instance) {
-      error('useModal instance is undefined!');
+      error('useModal instance is undefined!')
     }
-    return instance;
-  };
+    return instance
+  }
 
   const methods: ReturnMethods = {
     setModalProps: (props: Partial<ModalProps>): void => {
-      getInstance()?.setModalProps(props);
+      getInstance()?.setModalProps(props)
     },
 
     getVisible: computed((): boolean => {
-      return visibleData[~~unref(uidRef)];
+      return visibleData[~~unref(uidRef)]
     }),
 
     redoModalHeight: () => {
-      getInstance()?.redoModalHeight?.();
+      getInstance()?.redoModalHeight?.()
     },
 
     openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => {
       getInstance()?.setModalProps({
-        visible: visible,
-      });
+        visible: visible
+      })
 
-      if (!data) return;
+      if (!data) return
 
       if (openOnSet) {
-        dataTransferRef[unref(uidRef)] = null;
-        dataTransferRef[unref(uidRef)] = toRaw(data);
-        return;
+        dataTransferRef[unref(uidRef)] = null
+        dataTransferRef[unref(uidRef)] = toRaw(data)
+        return
       }
-      const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), toRaw(data));
+      const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), toRaw(data))
       if (!equal) {
-        dataTransferRef[unref(uidRef)] = toRaw(data);
+        dataTransferRef[unref(uidRef)] = toRaw(data)
       }
     },
-  };
-  return [register, methods];
+
+    closeModal: () => {
+      getInstance()?.setModalProps({ visible: false })
+    }
+  }
+  return [register, methods]
 }
 
 export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
-  const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
-  const currentInstance = getCurrentInstance();
-  const uidRef = ref<string>('');
+  const modalInstanceRef = ref<Nullable<ModalMethods>>(null)
+  const currentInstance = getCurrentInstance()
+  const uidRef = ref<string>('')
 
   // currentInstall.type.emits = [...currentInstall.type.emits, 'register'];
   // Object.assign(currentInstall.type.emits, ['register']);
 
   const getInstance = () => {
-    const instance = unref(modalInstanceRef);
+    const instance = unref(modalInstanceRef)
     if (!instance) {
-      error('useModalInner instance is undefined!');
+      error('useModalInner instance is undefined!')
     }
-    return instance;
-  };
+    return instance
+  }
 
   const register = (modalInstance: ModalMethods, uuid: string) => {
     isProdMode() &&
       tryOnUnmounted(() => {
-        modalInstanceRef.value = null;
-      });
-    uidRef.value = uuid;
-    modalInstanceRef.value = modalInstance;
-    currentInstance?.emit('register', modalInstance, uuid);
-  };
+        modalInstanceRef.value = null
+      })
+    uidRef.value = uuid
+    modalInstanceRef.value = modalInstance
+    currentInstance?.emit('register', modalInstance, uuid)
+  }
 
   watchEffect(() => {
-    const data = dataTransferRef[unref(uidRef)];
-    if (!data) return;
-    if (!callbackFn || !isFunction(callbackFn)) return;
+    const data = dataTransferRef[unref(uidRef)]
+    if (!data) return
+    if (!callbackFn || !isFunction(callbackFn)) return
     nextTick(() => {
-      callbackFn(data);
-    });
-  });
+      callbackFn(data)
+    })
+  })
 
   return [
     register,
     {
       changeLoading: (loading = true) => {
-        getInstance()?.setModalProps({ loading });
+        getInstance()?.setModalProps({ loading })
       },
       getVisible: computed((): boolean => {
-        return visibleData[~~unref(uidRef)];
+        return visibleData[~~unref(uidRef)]
       }),
 
       changeOkLoading: (loading = true) => {
-        getInstance()?.setModalProps({ confirmLoading: loading });
+        getInstance()?.setModalProps({ confirmLoading: loading })
       },
 
       closeModal: () => {
-        getInstance()?.setModalProps({ visible: false });
+        getInstance()?.setModalProps({ visible: false })
       },
 
       setModalProps: (props: Partial<ModalProps>) => {
-        getInstance()?.setModalProps(props);
+        getInstance()?.setModalProps(props)
       },
-    },
-  ];
-};
+
+      redoModalHeight: () => {
+        const callRedo = getInstance()?.redoModalHeight
+        callRedo && callRedo()
+      }
+    }
+  ]
+}

+ 61 - 59
src/components/Modal/src/types.ts

@@ -1,206 +1,208 @@
-import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
-import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
+import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'
+import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'
 /**
  * @description: 弹窗对外暴露的方法
  */
 export interface ModalMethods {
-  setModalProps: (props: Partial<ModalProps>) => void;
-  emitVisible?: (visible: boolean, uid: number) => void;
-  redoModalHeight?: () => void;
+  setModalProps: (props: Partial<ModalProps>) => void
+  emitVisible?: (visible: boolean, uid: number) => void
+  redoModalHeight?: () => void
 }
 
-export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
+export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void
 
 export interface ReturnMethods extends ModalMethods {
-  openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
-  getVisible?: ComputedRef<boolean>;
+  openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void
+  closeModal: () => void
+  getVisible?: ComputedRef<boolean>
 }
 
-export type UseModalReturnType = [RegisterFn, ReturnMethods];
+export type UseModalReturnType = [RegisterFn, ReturnMethods]
 
 export interface ReturnInnerMethods extends ModalMethods {
-  closeModal: () => void;
-  changeLoading: (loading: boolean) => void;
-  changeOkLoading: (loading: boolean) => void;
-  getVisible?: ComputedRef<boolean>;
+  closeModal: () => void
+  changeLoading: (loading: boolean) => void
+  changeOkLoading: (loading: boolean) => void
+  getVisible?: ComputedRef<boolean>
+  redoModalHeight: () => void
 }
 
-export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods];
+export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]
 
 export interface ModalProps {
-  minHeight?: number;
-  height?: number;
+  minHeight?: number
+  height?: number
   // 启用wrapper后 底部可以适当增加高度
-  wrapperFooterOffset?: number;
-  draggable?: boolean;
-  scrollTop?: boolean;
+  wrapperFooterOffset?: number
+  draggable?: boolean
+  scrollTop?: boolean
 
   // 是否可以进行全屏
-  canFullscreen?: boolean;
-  visible?: boolean;
+  canFullscreen?: boolean
+  visible?: boolean
   // 温馨提醒信息
-  helpMessage: string | string[];
+  helpMessage: string | string[]
 
   // 是否使用modalWrapper
-  useWrapper: boolean;
+  useWrapper: boolean
 
-  loading: boolean;
-  loadingTip?: string;
+  loading: boolean
+  loadingTip?: string
 
-  wrapperProps: Omit<ModalWrapperProps, 'loading'>;
+  wrapperProps: Omit<ModalWrapperProps, 'loading'>
 
-  showOkBtn: boolean;
-  showCancelBtn: boolean;
-  closeFunc: () => Promise<any>;
+  showOkBtn: boolean
+  showCancelBtn: boolean
+  closeFunc: () => Promise<any>
 
   /**
    * Specify a function that will be called when modal is closed completely.
    * @type Function
    */
-  afterClose?: () => any;
+  afterClose?: () => any
 
   /**
    * Body style for modal body element. Such as height, padding etc.
    * @default {}
    * @type object
    */
-  bodyStyle?: CSSProperties;
+  bodyStyle?: CSSProperties
 
   /**
    * Text of the Cancel button
    * @default 'cancel'
    * @type string
    */
-  cancelText?: string;
+  cancelText?: string
 
   /**
    * Centered Modal
    * @default false
    * @type boolean
    */
-  centered?: boolean;
+  centered?: boolean
 
   /**
    * Whether a close (x) button is visible on top right of the modal dialog or not
    * @default true
    * @type boolean
    */
-  closable?: boolean;
+  closable?: boolean
   /**
    * Whether a close (x) button is visible on top right of the modal dialog or not
    */
-  closeIcon?: VNodeChild | JSX.Element;
+  closeIcon?: VNodeChild | JSX.Element
 
   /**
    * Whether to apply loading visual effect for OK button or not
    * @default false
    * @type boolean
    */
-  confirmLoading?: boolean;
+  confirmLoading?: boolean
 
   /**
    * Whether to unmount child components on onClose
    * @default false
    * @type boolean
    */
-  destroyOnClose?: boolean;
+  destroyOnClose?: boolean
 
   /**
    * Footer content, set as :footer="null" when you don't need default buttons
    * @default OK and Cancel buttons
    * @type any (string | slot)
    */
-  footer?: VNodeChild | JSX.Element;
+  footer?: VNodeChild | JSX.Element
 
   /**
    * Return the mount node for Modal
    * @default () => document.body
    * @type Function
    */
-  getContainer?: (instance: any) => HTMLElement;
+  getContainer?: (instance: any) => HTMLElement
 
   /**
    * Whether show mask or not.
    * @default true
    * @type boolean
    */
-  mask?: boolean;
+  mask?: boolean
 
   /**
    * Whether to close the modal dialog when the mask (area outside the modal) is clicked
    * @default true
    * @type boolean
    */
-  maskClosable?: boolean;
+  maskClosable?: boolean
 
   /**
    * Style for modal's mask element.
    * @default {}
    * @type object
    */
-  maskStyle?: CSSProperties;
+  maskStyle?: CSSProperties
 
   /**
    * Text of the OK button
    * @default 'OK'
    * @type string
    */
-  okText?: string;
+  okText?: string
 
   /**
    * Button type of the OK button
    * @default 'primary'
    * @type string
    */
-  okType?: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default';
+  okType?: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'
 
   /**
    * The ok button props, follow jsx rules
    * @type object
    */
-  okButtonProps?: ButtonProps;
+  okButtonProps?: ButtonProps
 
   /**
    * The cancel button props, follow jsx rules
    * @type object
    */
-  cancelButtonProps?: ButtonProps;
+  cancelButtonProps?: ButtonProps
 
   /**
    * The modal dialog's title
    * @type any (string | slot)
    */
-  title?: VNodeChild | JSX.Element;
+  title?: VNodeChild | JSX.Element
 
   /**
    * Width of the modal dialog
    * @default 520
    * @type string | number
    */
-  width?: string | number;
+  width?: string | number
 
   /**
    * The class name of the container of the modal dialog
    * @type string
    */
-  wrapClassName?: string;
+  wrapClassName?: string
 
   /**
    * The z-index of the Modal
    * @default 1000
    * @type number
    */
-  zIndex?: number;
+  zIndex?: number
 }
 
 export interface ModalWrapperProps {
-  footerOffset?: number;
-  loading: boolean;
-  modalHeaderHeight: number;
-  modalFooterHeight: number;
-  minHeight: number;
-  height: number;
-  visible: boolean;
-  fullScreen: boolean;
-  useWrapper: boolean;
+  footerOffset?: number
+  loading: boolean
+  modalHeaderHeight: number
+  modalFooterHeight: number
+  minHeight: number
+  height: number
+  visible: boolean
+  fullScreen: boolean
+  useWrapper: boolean
 }

+ 76 - 75
src/components/Page/src/PageWrapper.vue

@@ -17,9 +17,11 @@
         <slot :name="item" v-bind="data"></slot>
       </template>
     </PageHeader>
+
     <div class="overflow-hidden" :class="getContentClass" :style="getContentStyle">
       <slot></slot>
     </div>
+
     <PageFooter v-if="getShowFooter" ref="footerRef">
       <template #left>
         <slot name="leftFooter"></slot>
@@ -31,17 +33,17 @@
   </div>
 </template>
 <script lang="ts">
-  import type { CSSProperties, PropType } from 'vue';
+  import type { CSSProperties, PropType } from 'vue'
 
-  import { defineComponent, computed, watch, nextTick, ref, unref } from 'vue';
-  import PageFooter from './PageFooter.vue';
-  import { usePageContext } from '/@/hooks/component/usePageContext';
+  import { defineComponent, computed, watch, nextTick, ref, unref } from 'vue'
+  import PageFooter from './PageFooter.vue'
+  import { usePageContext } from '/@/hooks/component/usePageContext'
 
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { propTypes } from '/@/utils/propTypes';
-  import { omit } from 'lodash-es';
-  import { PageHeader } from 'ant-design-vue';
-  import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { propTypes } from '/@/utils/propTypes'
+  import { omit } from 'lodash-es'
+  import { PageHeader } from 'ant-design-vue'
+  import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'
   export default defineComponent({
     name: 'PageWrapper',
     components: { PageFooter, PageHeader },
@@ -52,117 +54,116 @@
       ghost: propTypes.bool,
       content: propTypes.string,
       contentStyle: {
-        type: Object as PropType<CSSProperties>,
+        type: Object as PropType<CSSProperties>
       },
       contentBackground: propTypes.bool,
       contentFullHeight: propTypes.bool,
       contentClass: propTypes.string,
-      fixedHeight: propTypes.bool,
+      fixedHeight: propTypes.bool
     },
     setup(props, { slots }) {
-      const headerRef = ref<ComponentRef>(null);
-      const footerRef = ref<ComponentRef>(null);
-      const footerHeight = ref(0);
-      const { prefixCls, prefixVar } = useDesign('page-wrapper');
-      const { contentHeight, setPageHeight, pageHeight } = usePageContext();
+      const headerRef = ref<ComponentRef>(null)
+      const footerRef = ref<ComponentRef>(null)
+      const footerHeight = ref(0)
+      const { prefixCls, prefixVar } = useDesign('page-wrapper')
+      const { contentHeight, setPageHeight, pageHeight } = usePageContext()
 
       const getClass = computed(() => {
         return [
           prefixCls,
           {
-            [`${prefixCls}--dense`]: props.dense,
-          },
-        ];
-      });
+            [`${prefixCls}--dense`]: props.dense
+          }
+        ]
+      })
 
-      const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
+      const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter)
 
       const getHeaderSlots = computed(() => {
-        return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
-      });
-
-      const getContentStyle = computed(
-        (): CSSProperties => {
-          const { contentFullHeight, contentStyle, fixedHeight } = props;
-          if (!contentFullHeight) {
-            return { ...contentStyle };
-          }
-          const height = `${unref(pageHeight)}px`;
-          return {
-            ...contentStyle,
-            minHeight: height,
-            ...(fixedHeight ? { height } : {}),
-            paddingBottom: `${unref(footerHeight)}px`,
-          };
+        return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'))
+      })
+
+      const getContentStyle = computed((): CSSProperties => {
+        const { contentFullHeight, contentStyle, fixedHeight } = props
+        if (!contentFullHeight) {
+          return { ...contentStyle }
         }
-      );
+        const height = `${unref(pageHeight)}px`
+        return {
+          ...contentStyle,
+          minHeight: height,
+          ...(fixedHeight ? { height } : {}),
+          paddingBottom: `${unref(footerHeight)}px`
+        }
+      })
 
       const getContentClass = computed(() => {
-        const { contentBackground, contentClass } = props;
+        const { contentBackground, contentClass } = props
         return [
           `${prefixCls}-content`,
           contentClass,
           {
-            [`${prefixCls}-content-bg`]: contentBackground,
-          },
-        ];
-      });
+            [`${prefixCls}-content-bg`]: contentBackground
+          }
+        ]
+      })
 
       watch(
         () => [contentHeight?.value, getShowFooter.value],
         () => {
-          calcContentHeight();
+          calcContentHeight()
         },
         {
           flush: 'post',
-          immediate: true,
+          immediate: true
         }
-      );
+      )
 
       onMountedOrActivated(() => {
         nextTick(() => {
-          calcContentHeight();
-        });
-      });
+          calcContentHeight()
+        })
+      })
 
       function calcContentHeight() {
         if (!props.contentFullHeight) {
-          return;
+          return
         }
         //fix:in contentHeight mode: delay getting footer and header dom element to get the correct height
-        const footer = unref(footerRef);
-        const header = unref(headerRef);
-        footerHeight.value = 0;
-        const footerEl = footer?.$el;
+        const footer = unref(footerRef)
+        const header = unref(headerRef)
+        footerHeight.value = 0
+        const footerEl = footer?.$el
 
         if (footerEl) {
-          footerHeight.value += footerEl?.offsetHeight ?? 0;
+          footerHeight.value += footerEl?.offsetHeight ?? 0
         }
-        let headerHeight = 0;
-        const headerEl = header?.$el;
+        let headerHeight = 0
+        const headerEl = header?.$el
         if (headerEl) {
-          headerHeight += headerEl?.offsetHeight ?? 0;
+          headerHeight += headerEl?.offsetHeight ?? 0
         }
         // fix:subtract content's marginTop and marginBottom value
-        let subtractHeight = 0;
-        let marginBottom = '0px';
-        let marginTop = '0px';
-        const classElments = document.querySelectorAll(`.${prefixVar}-page-wrapper-content`);
+        let subtractHeight = 0
+        const ZERO_PX = '0px'
+        let marginBottom = ZERO_PX
+        let marginTop = ZERO_PX
+        const classElments = document.querySelectorAll(`.${prefixVar}-page-wrapper-content`)
         if (classElments && classElments.length > 0) {
-          const contentEl = classElments[0];
-          const cssStyle = getComputedStyle(contentEl);
-          marginBottom = cssStyle?.marginBottom;
-          marginTop = cssStyle?.marginTop;
+          const contentEl = classElments[0]
+          const cssStyle = getComputedStyle(contentEl)
+          marginBottom = cssStyle?.marginBottom ?? ZERO_PX
+          marginTop = cssStyle?.marginTop ?? ZERO_PX
         }
         if (marginBottom) {
-          const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
-          subtractHeight += contentMarginBottom;
+          const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''))
+          subtractHeight += contentMarginBottom
         }
         if (marginTop) {
-          const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
-          subtractHeight += contentMarginTop;
+          const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''))
+          subtractHeight += contentMarginTop
         }
-        setPageHeight?.(unref(contentHeight) - unref(footerHeight) - headerHeight - subtractHeight);
+        setPageHeight?.(unref(contentHeight) - unref(footerHeight) - headerHeight - subtractHeight)
       }
 
       return {
@@ -175,10 +176,10 @@
         getShowFooter,
         pageHeight,
         omit,
-        getContentClass,
-      };
-    },
-  });
+        getContentClass
+      }
+    }
+  })
 </script>
 <style lang="less">
   @prefix-cls: ~'@{namespace}-page-wrapper';

+ 1 - 1
src/components/Preview/index.ts

@@ -1 +1 @@
-export { default as ImagePreview } from './src/index.vue';
+export { default as ImagePreview } from './src/Preview.vue'

+ 94 - 0
src/components/Preview/src/Preview.vue

@@ -0,0 +1,94 @@
+<template>
+  <div :class="prefixCls">
+    <PreviewGroup>
+      <slot v-if="!imageList || $slots.default"></slot>
+      <template v-else>
+        <template v-for="item in getImageList" :key="item.src">
+          <Image v-bind="item">
+            <template #placeholder v-if="item.placeholder">
+              <Image v-bind="item" :src="item.placeholder" :preview="false" />
+            </template>
+          </Image>
+        </template>
+      </template>
+    </PreviewGroup>
+  </div>
+</template>
+<script lang="ts">
+  import type { PropType } from 'vue'
+  import { defineComponent, computed } from 'vue'
+
+  import { Image } from 'ant-design-vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { propTypes } from '/@/utils/propTypes'
+  import { isString } from '/@/utils/is'
+
+  interface ImageProps {
+    alt?: string
+    fallback?: string
+    src: string
+    width: string | number
+    height?: string | number
+    placeholder?: string | boolean
+    preview?:
+      | boolean
+      | {
+          visible?: boolean
+          onVisibleChange?: (visible: boolean, prevVisible: boolean) => void
+          getContainer: string | HTMLElement | (() => HTMLElement)
+        }
+  }
+
+  type ImageItem = string | ImageProps
+
+  export default defineComponent({
+    name: 'ImagePreview',
+    components: {
+      Image,
+      PreviewGroup: Image.PreviewGroup
+    },
+    props: {
+      functional: propTypes.bool,
+      imageList: {
+        type: Array as PropType<ImageItem[]>
+      }
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('image-preview')
+
+      const getImageList = computed(() => {
+        const { imageList } = props
+        if (!imageList) {
+          return []
+        }
+        return imageList.map(item => {
+          if (isString(item)) {
+            return {
+              src: item,
+              placeholder: false
+            }
+          }
+          return item
+        })
+      })
+
+      return {
+        prefixCls,
+        getImageList
+      }
+    }
+  })
+</script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-image-preview';
+
+  .@{prefix-cls} {
+    .ant-image {
+      margin-right: 10px;
+    }
+
+    .ant-image-preview-operations {
+      background-color: rgba(0, 0, 0, 0.4);
+    }
+  }
+</style>

+ 2 - 2
src/components/Qrcode/index.ts

@@ -1,3 +1,3 @@
-export { default as QrCode } from './src/index.vue';
+export { default as QrCode } from './src/Qrcode.vue'
 
-export * from './src/types';
+export * from './src/types'

+ 105 - 0
src/components/Qrcode/src/Qrcode.vue

@@ -0,0 +1,105 @@
+<template>
+  <div>
+    <component :is="tag" ref="wrapRef" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, watchEffect, PropType, ref, unref } from 'vue'
+  import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus'
+  import { toDataURL } from 'qrcode'
+  import { downloadByUrl } from '/@/utils/file/download'
+  import { QrcodeDoneEventParams } from './types'
+
+  export default defineComponent({
+    name: 'QrCode',
+    props: {
+      value: {
+        type: [String, Array] as PropType<string | any[]>,
+        default: null
+      },
+      // 参数
+      options: {
+        type: Object as PropType<QRCodeRenderersOptions>,
+        default: null
+      },
+      // 宽度
+      width: {
+        type: Number as PropType<number>,
+        default: 200
+      },
+      // 中间logo图标
+      logo: {
+        type: [String, Object] as PropType<Partial<LogoType> | string>,
+        default: ''
+      },
+      // img 不支持内嵌logo
+      tag: {
+        type: String as PropType<'canvas' | 'img'>,
+        default: 'canvas',
+        validator: (v: string) => ['canvas', 'img'].includes(v)
+      }
+    },
+    emits: { done: (data: QrcodeDoneEventParams) => !!data, error: (error: any) => !!error },
+    setup(props, { emit }) {
+      const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null)
+      async function createQrcode() {
+        try {
+          const { tag, value, options = {}, width, logo } = props
+          const renderValue = String(value)
+          const wrapEl = unref(wrapRef)
+
+          if (!wrapEl) return
+
+          if (tag === 'canvas') {
+            const url: string = await toCanvas({
+              canvas: wrapEl,
+              width,
+              logo: logo as any,
+              content: renderValue,
+              options: options || {}
+            })
+            emit('done', { url, ctx: (wrapEl as HTMLCanvasElement).getContext('2d') })
+            return
+          }
+
+          if (tag === 'img') {
+            const url = await toDataURL(renderValue, {
+              errorCorrectionLevel: 'H',
+              width,
+              ...options
+            })
+            ;(unref(wrapRef) as HTMLImageElement).src = url
+            emit('done', { url })
+          }
+        } catch (error) {
+          emit('error', error)
+        }
+      }
+      /**
+       * file download
+       */
+      function download(fileName?: string) {
+        let url = ''
+        const wrapEl = unref(wrapRef)
+        if (wrapEl instanceof HTMLCanvasElement) {
+          url = wrapEl.toDataURL()
+        } else if (wrapEl instanceof HTMLImageElement) {
+          url = wrapEl.src
+        }
+        if (!url) return
+        downloadByUrl({
+          url,
+          fileName
+        })
+      }
+
+      watchEffect(() => {
+        setTimeout(() => {
+          createQrcode()
+        }, 30)
+      })
+
+      return { wrapRef, download }
+    }
+  })
+</script>

+ 27 - 22
src/components/Qrcode/src/types.ts

@@ -1,33 +1,38 @@
-import type { QRCodeSegment, QRCodeRenderersOptions } from 'qrcode';
+import type { QRCodeSegment, QRCodeRenderersOptions } from 'qrcode'
 
-export type ContentType = string | QRCodeSegment[];
+export type ContentType = string | QRCodeSegment[]
 
-export type { QRCodeRenderersOptions };
+export type { QRCodeRenderersOptions }
 
 export type LogoType = {
-  src: string;
-  logoSize: number;
-  borderColor: string;
-  bgColor: string;
-  borderSize: number;
-  crossOrigin: string;
-  borderRadius: number;
-  logoRadius: number;
-};
+  src: string
+  logoSize: number
+  borderColor: string
+  bgColor: string
+  borderSize: number
+  crossOrigin: string
+  borderRadius: number
+  logoRadius: number
+}
 
 export interface RenderQrCodeParams {
-  canvas: any;
-  content: ContentType;
-  width?: number;
-  options?: QRCodeRenderersOptions;
-  logo?: LogoType | string;
-  image?: HTMLImageElement;
-  downloadName?: string;
-  download?: boolean | Fn;
+  canvas: any
+  content: ContentType
+  width?: number
+  options?: QRCodeRenderersOptions
+  logo?: LogoType | string
+  image?: HTMLImageElement
+  downloadName?: string
+  download?: boolean | Fn
 }
 
-export type ToCanvasFn = (options: RenderQrCodeParams) => Promise<unknown>;
+export type ToCanvasFn = (options: RenderQrCodeParams) => Promise<unknown>
 
 export interface QrCodeActionType {
-  download: (fileName?: string) => void;
+  download: (fileName?: string) => void
+}
+
+export interface QrcodeDoneEventParams {
+  url: string
+  ctx?: CanvasRenderingContext2D | null
 }

+ 3 - 3
src/components/Scrollbar/index.ts

@@ -2,7 +2,7 @@
  * copy from element-ui
  */
 
-import Scrollbar from './src/index.vue';
+import Scrollbar from './src/Scrollbar.vue'
 
-export { Scrollbar };
-export type { ScrollbarType } from './src/types';
+export { Scrollbar }
+export type { ScrollbarType } from './src/types'

+ 198 - 0
src/components/Scrollbar/src/Scrollbar.vue

@@ -0,0 +1,198 @@
+<template>
+  <div class="scrollbar">
+    <div
+      ref="wrap"
+      :class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']"
+      :style="style"
+      @scroll="handleScroll"
+    >
+      <component :is="tag" ref="resize" :class="['scrollbar__view', viewClass]" :style="viewStyle">
+        <slot></slot>
+      </component>
+    </div>
+    <template v-if="!native">
+      <bar :move="moveX" :size="sizeWidth" />
+      <bar vertical :move="moveY" :size="sizeHeight" />
+    </template>
+  </div>
+</template>
+<script lang="ts">
+  import { addResizeListener, removeResizeListener } from '/@/utils/event'
+  import componentSetting from '/@/settings/componentSetting'
+  const { scrollbar } = componentSetting
+  import { toObject } from './util'
+  import { defineComponent, ref, onMounted, onBeforeUnmount, nextTick, provide, computed, unref } from 'vue'
+  import Bar from './bar'
+
+  export default defineComponent({
+    name: 'Scrollbar',
+    // inheritAttrs: false,
+    components: { Bar },
+    props: {
+      native: {
+        type: Boolean,
+        default: scrollbar?.native ?? false
+      },
+      wrapStyle: {
+        type: [String, Array],
+        default: ''
+      },
+      wrapClass: {
+        type: [String, Array],
+        default: ''
+      },
+      viewClass: {
+        type: [String, Array],
+        default: ''
+      },
+      viewStyle: {
+        type: [String, Array],
+        default: ''
+      },
+      noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
+      tag: {
+        type: String,
+        default: 'div'
+      }
+    },
+    setup(props) {
+      const sizeWidth = ref('0')
+      const sizeHeight = ref('0')
+      const moveX = ref(0)
+      const moveY = ref(0)
+      const wrap = ref()
+      const resize = ref()
+
+      provide('scroll-bar-wrap', wrap)
+
+      const style = computed(() => {
+        if (Array.isArray(props.wrapStyle)) {
+          return toObject(props.wrapStyle)
+        }
+        return props.wrapStyle
+      })
+
+      const handleScroll = () => {
+        if (!props.native) {
+          moveY.value = (unref(wrap).scrollTop * 100) / unref(wrap).clientHeight
+          moveX.value = (unref(wrap).scrollLeft * 100) / unref(wrap).clientWidth
+        }
+      }
+
+      const update = () => {
+        if (!unref(wrap)) return
+
+        const heightPercentage = (unref(wrap).clientHeight * 100) / unref(wrap).scrollHeight
+        const widthPercentage = (unref(wrap).clientWidth * 100) / unref(wrap).scrollWidth
+
+        sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : ''
+        sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : ''
+      }
+
+      onMounted(() => {
+        if (props.native) return
+        nextTick(update)
+        if (!props.noresize) {
+          addResizeListener(unref(resize), update)
+          addResizeListener(unref(wrap), update)
+          addEventListener('resize', update)
+        }
+      })
+
+      onBeforeUnmount(() => {
+        if (props.native) return
+        if (!props.noresize) {
+          removeResizeListener(unref(resize), update)
+          removeResizeListener(unref(wrap), update)
+          removeEventListener('resize', update)
+        }
+      })
+
+      return {
+        moveX,
+        moveY,
+        sizeWidth,
+        sizeHeight,
+        style,
+        wrap,
+        resize,
+        update,
+        handleScroll
+      }
+    }
+  })
+</script>
+<style lang="less">
+  .scrollbar {
+    position: relative;
+    height: 100%;
+    overflow: hidden;
+
+    &__wrap {
+      height: 100%;
+      overflow: auto;
+
+      &--hidden-default {
+        scrollbar-width: none;
+
+        &::-webkit-scrollbar {
+          display: none;
+          width: 0;
+          height: 0;
+          opacity: 0;
+        }
+      }
+    }
+
+    &__thumb {
+      position: relative;
+      display: block;
+      width: 0;
+      height: 0;
+      cursor: pointer;
+      background-color: rgba(144, 147, 153, 0.3);
+      border-radius: inherit;
+      transition: 0.3s background-color;
+
+      &:hover {
+        background-color: rgba(144, 147, 153, 0.5);
+      }
+    }
+
+    &__bar {
+      position: absolute;
+      right: 2px;
+      bottom: 2px;
+      z-index: 1;
+      border-radius: 4px;
+      opacity: 0;
+      -webkit-transition: opacity 80ms ease;
+      transition: opacity 80ms ease;
+
+      &.is-vertical {
+        top: 2px;
+        width: 6px;
+
+        & > div {
+          width: 100%;
+        }
+      }
+
+      &.is-horizontal {
+        left: 2px;
+        height: 6px;
+
+        & > div {
+          height: 100%;
+        }
+      }
+    }
+  }
+
+  .scrollbar:active > .scrollbar__bar,
+  .scrollbar:focus > .scrollbar__bar,
+  .scrollbar:hover > .scrollbar__bar {
+    opacity: 1;
+    transition: opacity 340ms ease-out;
+  }
+</style>

+ 47 - 63
src/components/Scrollbar/src/bar.ts

@@ -1,16 +1,7 @@
-import {
-  defineComponent,
-  h,
-  computed,
-  ref,
-  getCurrentInstance,
-  onUnmounted,
-  inject,
-  Ref,
-} from 'vue';
-import { on, off } from '/@/utils/domUtils';
+import { defineComponent, h, computed, ref, getCurrentInstance, onUnmounted, inject, Ref } from 'vue'
+import { on, off } from '/@/utils/domUtils'
 
-import { renderThumbStyle, BAR_MAP } from './util';
+import { renderThumbStyle, BAR_MAP } from './util'
 
 export default defineComponent({
   name: 'Bar',
@@ -18,81 +9,74 @@ export default defineComponent({
   props: {
     vertical: Boolean,
     size: String,
-    move: Number,
+    move: Number
   },
 
   setup(props) {
-    const instance = getCurrentInstance();
-    const thumb = ref();
-    const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any;
+    const instance = getCurrentInstance()
+    const thumb = ref()
+    const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any
     const bar = computed(() => {
-      return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
-    });
-    const barStore = ref<Recordable>({});
-    const cursorDown = ref();
+      return BAR_MAP[props.vertical ? 'vertical' : 'horizontal']
+    })
+    const barStore = ref<Recordable>({})
+    const cursorDown = ref()
     const clickThumbHandler = (e: any) => {
       // prevent click event of right button
       if (e.ctrlKey || e.button === 2) {
-        return;
+        return
       }
-      startDrag(e);
+      window.getSelection()?.removeAllRanges()
+      startDrag(e)
       barStore.value[bar.value.axis] =
         e.currentTarget[bar.value.offset] -
-        (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
-    };
+        (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction])
+    }
 
     const clickTrackHandler = (e: any) => {
-      const offset = Math.abs(
-        e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]
-      );
-      const thumbHalf = thumb.value[bar.value.offset] / 2;
-      const thumbPositionPercentage =
-        ((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset];
+      const offset = Math.abs(e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client])
+      const thumbHalf = thumb.value[bar.value.offset] / 2
+      const thumbPositionPercentage = ((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset]
 
-      wrap.value[bar.value.scroll] =
-        (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
-    };
+      wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100
+    }
     const startDrag = (e: any) => {
-      e.stopImmediatePropagation();
-      cursorDown.value = true;
-      on(document, 'mousemove', mouseMoveDocumentHandler);
-      on(document, 'mouseup', mouseUpDocumentHandler);
-      document.onselectstart = () => false;
-    };
+      e.stopImmediatePropagation()
+      cursorDown.value = true
+      on(document, 'mousemove', mouseMoveDocumentHandler)
+      on(document, 'mouseup', mouseUpDocumentHandler)
+      document.onselectstart = () => false
+    }
 
     const mouseMoveDocumentHandler = (e: any) => {
-      if (cursorDown.value === false) return;
-      const prevPage = barStore.value[bar.value.axis];
+      if (cursorDown.value === false) return
+      const prevPage = barStore.value[bar.value.axis]
 
-      if (!prevPage) return;
+      if (!prevPage) return
 
-      const offset =
-        (instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) *
-        -1;
-      const thumbClickPosition = thumb.value[bar.value.offset] - prevPage;
-      const thumbPositionPercentage =
-        ((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset];
-      wrap.value[bar.value.scroll] =
-        (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
-    };
+      const offset = (instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1
+      const thumbClickPosition = thumb.value[bar.value.offset] - prevPage
+      const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset]
+      wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100
+    }
 
     function mouseUpDocumentHandler() {
-      cursorDown.value = false;
-      barStore.value[bar.value.axis] = 0;
-      off(document, 'mousemove', mouseMoveDocumentHandler);
-      document.onselectstart = null;
+      cursorDown.value = false
+      barStore.value[bar.value.axis] = 0
+      off(document, 'mousemove', mouseMoveDocumentHandler)
+      document.onselectstart = null
     }
 
     onUnmounted(() => {
-      off(document, 'mouseup', mouseUpDocumentHandler);
-    });
+      off(document, 'mouseup', mouseUpDocumentHandler)
+    })
 
     return () =>
       h(
         'div',
         {
           class: ['scrollbar__bar', 'is-' + bar.value.key],
-          onMousedown: clickTrackHandler,
+          onMousedown: clickTrackHandler
         },
         h('div', {
           ref: thumb,
@@ -101,9 +85,9 @@ export default defineComponent({
           style: renderThumbStyle({
             size: props.size,
             move: props.move,
-            bar: bar.value,
-          }),
+            bar: bar.value
+          })
         })
-      );
-  },
-});
+      )
+  }
+})

+ 33 - 33
src/components/SimpleMenu/src/SimpleMenuTag.vue

@@ -2,67 +2,67 @@
   <span :class="getTagClass" v-if="getShowTag">{{ getContent }}</span>
 </template>
 <script lang="ts">
-  import type { Menu } from '/@/router/types';
+  import type { Menu } from '/@/router/types'
 
-  import { defineComponent, computed } from 'vue';
+  import { defineComponent, computed } from 'vue'
 
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { propTypes } from '/@/utils/propTypes';
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { propTypes } from '/@/utils/propTypes'
 
   export default defineComponent({
     name: 'SimpleMenuTag',
     props: {
       item: {
         type: Object as PropType<Menu>,
-        default: () => {},
+        default: () => ({})
       },
       dot: propTypes.bool,
-      collapseParent: propTypes.bool,
+      collapseParent: propTypes.bool
     },
     setup(props) {
-      const { prefixCls } = useDesign('simple-menu');
+      const { prefixCls } = useDesign('simple-menu')
 
       const getShowTag = computed(() => {
-        const { item } = props;
+        const { item } = props
 
-        if (!item) return false;
+        if (!item) return false
 
-        const { tag } = item;
-        if (!tag) return false;
+        const { tag } = item
+        if (!tag) return false
 
-        const { dot, content } = tag;
-        if (!dot && !content) return false;
-        return true;
-      });
+        const { dot, content } = tag
+        if (!dot && !content) return false
+        return true
+      })
 
       const getContent = computed(() => {
-        if (!getShowTag.value) return '';
-        const { item, collapseParent } = props;
-        const { tag } = item;
-        const { dot, content } = tag!;
-        return dot || collapseParent ? '' : content;
-      });
+        if (!getShowTag.value) return ''
+        const { item, collapseParent } = props
+        const { tag } = item
+        const { dot, content } = tag!
+        return dot || collapseParent ? '' : content
+      })
 
       const getTagClass = computed(() => {
-        const { item, collapseParent } = props;
-        const { tag = {} } = item || {};
-        const { dot, type = 'error' } = tag;
-        const tagCls = `${prefixCls}-tag`;
+        const { item, collapseParent } = props
+        const { tag = {} } = item || {}
+        const { dot, type = 'error' } = tag
+        const tagCls = `${prefixCls}-tag`
         return [
           tagCls,
 
           [`${tagCls}--${type}`],
           {
             [`${tagCls}--collapse`]: collapseParent,
-            [`${tagCls}--dot`]: dot || props.dot,
-          },
-        ];
-      });
+            [`${tagCls}--dot`]: dot || props.dot
+          }
+        ]
+      })
       return {
         getTagClass,
         getShowTag,
-        getContent,
-      };
-    },
-  });
+        getContent
+      }
+    }
+  })
 </script>

+ 30 - 35
src/components/SimpleMenu/src/SimpleSubMenu.vue

@@ -1,10 +1,5 @@
 <template>
-  <MenuItem
-    :name="item.path"
-    v-if="!menuHasChildren(item) && getShowMenu"
-    v-bind="$props"
-    :class="getLevelClass"
-  >
+  <MenuItem :name="item.path" v-if="!menuHasChildren(item) && getShowMenu" v-bind="$props" :class="getLevelClass">
     <Icon v-if="getIcon" :icon="getIcon" :size="16" />
     <div v-if="collapsedShowTitle && getIsCollapseParent" class="mt-1 collapse-title">
       {{ getI18nName }}
@@ -40,18 +35,18 @@
   </SubMenu>
 </template>
 <script lang="ts">
-  import type { PropType } from 'vue';
-  import type { Menu } from '/@/router/types';
+  import type { PropType } from 'vue'
+  import type { Menu } from '/@/router/types'
 
-  import { defineComponent, computed } from 'vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import Icon from '/@/components/Icon/index';
+  import { defineComponent, computed } from 'vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import Icon from '/@/components/Icon/index'
 
-  import MenuItem from './components/MenuItem.vue';
-  import SubMenu from './components/SubMenuItem.vue';
-  import { propTypes } from '/@/utils/propTypes';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+  import MenuItem from './components/MenuItem.vue'
+  import SubMenu from './components/SubMenuItem.vue'
+  import { propTypes } from '/@/utils/propTypes'
+  import { useI18n } from '/@/hooks/web/useI18n'
+  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'
 
   export default defineComponent({
     name: 'SimpleSubMenu',
@@ -59,35 +54,35 @@
       SubMenu,
       MenuItem,
       SimpleMenuTag: createAsyncComponent(() => import('./SimpleMenuTag.vue')),
-      Icon,
+      Icon
     },
     props: {
       item: {
         type: Object as PropType<Menu>,
-        default: () => {},
+        default: () => ({})
       },
       parent: propTypes.bool,
       collapsedShowTitle: propTypes.bool,
       collapse: propTypes.bool,
-      theme: propTypes.oneOf(['dark', 'light']),
+      theme: propTypes.oneOf(['dark', 'light'])
     },
     setup(props) {
-      const { t } = useI18n();
-      const { prefixCls } = useDesign('simple-menu');
+      const { t } = useI18n()
+      const { prefixCls } = useDesign('simple-menu')
 
-      const getShowMenu = computed(() => !props.item?.meta?.hideMenu);
-      const getIcon = computed(() => props.item?.icon);
-      const getI18nName = computed(() => t(props.item?.name));
-      const getShowSubTitle = computed(() => !props.collapse || !props.parent);
-      const getIsCollapseParent = computed(() => !!props.collapse && !!props.parent);
+      const getShowMenu = computed(() => !props.item?.meta?.hideMenu)
+      const getIcon = computed(() => props.item?.icon)
+      const getI18nName = computed(() => t(props.item?.name))
+      const getShowSubTitle = computed(() => !props.collapse || !props.parent)
+      const getIsCollapseParent = computed(() => !!props.collapse && !!props.parent)
       const getLevelClass = computed(() => {
         return [
           {
             [`${prefixCls}__parent`]: props.parent,
-            [`${prefixCls}__children`]: !props.parent,
-          },
-        ];
-      });
+            [`${prefixCls}__children`]: !props.parent
+          }
+        ]
+      })
 
       function menuHasChildren(menuTreeItem: Menu): boolean {
         return (
@@ -95,7 +90,7 @@
           Reflect.has(menuTreeItem, 'children') &&
           !!menuTreeItem.children &&
           menuTreeItem.children.length > 0
-        );
+        )
       }
 
       return {
@@ -106,8 +101,8 @@
         getI18nName,
         getShowSubTitle,
         getLevelClass,
-        getIsCollapseParent,
-      };
-    },
-  });
+        getIsCollapseParent
+      }
+    }
+  })
 </script>

+ 39 - 41
src/components/SimpleMenu/src/components/MenuItem.vue

@@ -17,35 +17,33 @@
 </template>
 
 <script lang="ts">
-  import { PropType } from 'vue';
-  import { defineComponent, ref, computed, unref, getCurrentInstance, watch } from 'vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { propTypes } from '/@/utils/propTypes';
-  import { useMenuItem } from './useMenu';
-  import { Tooltip } from 'ant-design-vue';
-  import { useSimpleRootMenuContext } from './useSimpleMenuContext';
+  import { PropType } from 'vue'
+  import { defineComponent, ref, computed, unref, getCurrentInstance, watch } from 'vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { propTypes } from '/@/utils/propTypes'
+  import { useMenuItem } from './useMenu'
+  import { Tooltip } from 'ant-design-vue'
+  import { useSimpleRootMenuContext } from './useSimpleMenuContext'
   export default defineComponent({
     name: 'MenuItem',
     components: { Tooltip },
     props: {
       name: {
         type: [String, Number] as PropType<string | number>,
-        required: true,
+        required: true
       },
-      disabled: propTypes.bool,
+      disabled: propTypes.bool
     },
     setup(props, { slots }) {
-      const instance = getCurrentInstance();
+      const instance = getCurrentInstance()
 
-      const active = ref(false);
+      const active = ref(false)
 
-      const { getItemStyle, getParentList, getParentMenu, getParentRootMenu } = useMenuItem(
-        instance
-      );
+      const { getItemStyle, getParentList, getParentMenu, getParentRootMenu } = useMenuItem(instance)
 
-      const { prefixCls } = useDesign('menu');
+      const { prefixCls } = useDesign('menu')
 
-      const { rootMenuEmitter, activeName } = useSimpleRootMenuContext();
+      const { rootMenuEmitter, activeName } = useSimpleRootMenuContext()
 
       const getClass = computed(() => {
         return [
@@ -53,56 +51,56 @@
           {
             [`${prefixCls}-item-active`]: unref(active),
             [`${prefixCls}-item-selected`]: unref(active),
-            [`${prefixCls}-item-disabled`]: !!props.disabled,
-          },
-        ];
-      });
+            [`${prefixCls}-item-disabled`]: !!props.disabled
+          }
+        ]
+      })
 
-      const getCollapse = computed(() => unref(getParentRootMenu)?.props.collapse);
+      const getCollapse = computed(() => unref(getParentRootMenu)?.props.collapse)
 
       const showTooptip = computed(() => {
-        return unref(getParentMenu)?.type.name === 'Menu' && unref(getCollapse) && slots.title;
-      });
+        return unref(getParentMenu)?.type.name === 'Menu' && unref(getCollapse) && slots.title
+      })
 
       function handleClickItem() {
-        const { disabled } = props;
+        const { disabled } = props
         if (disabled) {
-          return;
+          return
         }
 
-        rootMenuEmitter.emit('on-menu-item-select', props.name);
+        rootMenuEmitter.emit('on-menu-item-select', props.name)
         if (unref(getCollapse)) {
-          return;
+          return
         }
-        const { uidList } = getParentList();
+        const { uidList } = getParentList()
 
         rootMenuEmitter.emit('on-update-opened', {
           opend: false,
           parent: instance?.parent,
-          uidList: uidList,
-        });
+          uidList: uidList
+        })
       }
       watch(
         () => activeName.value,
         (name: string) => {
           if (name === props.name) {
-            const { list, uidList } = getParentList();
-            active.value = true;
-            list.forEach((item) => {
+            const { list, uidList } = getParentList()
+            active.value = true
+            list.forEach(item => {
               if (item.proxy) {
-                (item.proxy as any).active = true;
+                ;(item.proxy as any).active = true
               }
-            });
+            })
 
-            rootMenuEmitter.emit('on-update-active-name:submenu', uidList);
+            rootMenuEmitter.emit('on-update-active-name:submenu', uidList)
           } else {
-            active.value = false;
+            active.value = false
           }
         },
         { immediate: true }
-      );
+      )
 
-      return { getClass, prefixCls, getItemStyle, getCollapse, handleClickItem, showTooptip };
-    },
-  });
+      return { getClass, prefixCls, getItemStyle, getCollapse, handleClickItem, showTooptip }
+    }
+  })
 </script>

+ 122 - 133
src/components/SimpleMenu/src/components/SubMenuItem.vue

@@ -3,11 +3,7 @@
     <template v-if="!getCollapse">
       <div :class="`${prefixCls}-submenu-title`" @click.stop="handleClick" :style="getItemStyle">
         <slot name="title"></slot>
-        <Icon
-          icon="eva:arrow-ios-downward-outline"
-          :size="14"
-          :class="`${prefixCls}-submenu-title-icon`"
-        />
+        <Icon icon="eva:arrow-ios-downward-outline" :size="14" :class="`${prefixCls}-submenu-title-icon`" />
       </div>
       <CollapseTransition>
         <ul :class="prefixCls" v-show="opened">
@@ -30,8 +26,8 @@
           :class="[
             {
               [`${prefixCls}-submenu-popup`]: !getParentSubMenu,
-              [`${prefixCls}-submenu-collapsed-show-tit`]: collapsedShowTitle,
-            },
+              [`${prefixCls}-submenu-collapsed-show-tit`]: collapsedShowTitle
+            }
           ]"
         >
           <slot name="title"></slot>
@@ -56,8 +52,8 @@
 </template>
 
 <script lang="ts">
-  import type { CSSProperties, PropType } from 'vue';
-  import type { SubMenuProvider } from './types';
+  import type { CSSProperties, PropType } from 'vue'
+  import type { SubMenuProvider } from './types'
   import {
     defineComponent,
     computed,
@@ -67,57 +63,55 @@
     reactive,
     provide,
     onBeforeMount,
-    inject,
-  } from 'vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { propTypes } from '/@/utils/propTypes';
-  import { useMenuItem } from './useMenu';
-  import { useSimpleRootMenuContext } from './useSimpleMenuContext';
-  import { CollapseTransition } from '/@/components/Transition';
-  import Icon from '/@/components/Icon';
-  import { Popover } from 'ant-design-vue';
-  import { isBoolean, isObject } from '/@/utils/is';
-  import Mitt from '/@/utils/mitt';
-
-  const DELAY = 200;
+    inject
+  } from 'vue'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { propTypes } from '/@/utils/propTypes'
+  import { useMenuItem } from './useMenu'
+  import { useSimpleRootMenuContext } from './useSimpleMenuContext'
+  import { CollapseTransition } from '/@/components/Transition'
+  import Icon from '/@/components/Icon'
+  import { Popover } from 'ant-design-vue'
+  import { isBoolean, isObject } from '/@/utils/is'
+  import Mitt from '/@/utils/mitt'
+
+  const DELAY = 200
   export default defineComponent({
     name: 'SubMenu',
     components: {
       Icon,
       CollapseTransition,
-      Popover,
+      Popover
     },
     props: {
       name: {
         type: [String, Number] as PropType<string | number>,
-        required: true,
+        required: true
       },
       disabled: propTypes.bool,
-      collapsedShowTitle: propTypes.bool,
+      collapsedShowTitle: propTypes.bool
     },
     setup(props) {
-      const instance = getCurrentInstance();
+      const instance = getCurrentInstance()
 
       const state = reactive({
         active: false,
-        opened: false,
-      });
+        opened: false
+      })
 
       const data = reactive({
         timeout: null as TimeoutHandle | null,
         mouseInChild: false,
-        isChild: false,
-      });
+        isChild: false
+      })
 
-      const { getParentSubMenu, getItemStyle, getParentMenu, getParentList } = useMenuItem(
-        instance
-      );
+      const { getParentSubMenu, getItemStyle, getParentMenu, getParentList } = useMenuItem(instance)
 
-      const { prefixCls } = useDesign('menu');
+      const { prefixCls } = useDesign('menu')
 
-      const subMenuEmitter = new Mitt();
+      const subMenuEmitter = new Mitt()
 
-      const { rootMenuEmitter } = useSimpleRootMenuContext();
+      const { rootMenuEmitter } = useSimpleRootMenuContext()
 
       const {
         addSubMenu: parentAddSubmenu,
@@ -128,8 +122,8 @@
         sliceIndex,
         level,
         props: rootProps,
-        handleMouseleave: parentHandleMouseleave,
-      } = inject<SubMenuProvider>(`subMenu:${getParentMenu.value?.uid}`)!;
+        handleMouseleave: parentHandleMouseleave
+      } = inject<SubMenuProvider>(`subMenu:${getParentMenu.value?.uid}`)!
 
       const getClass = computed(() => {
         return [
@@ -139,169 +133,164 @@
             [`${prefixCls}-opened`]: state.opened,
             [`${prefixCls}-submenu-disabled`]: props.disabled,
             [`${prefixCls}-submenu-has-parent-submenu`]: unref(getParentSubMenu),
-            [`${prefixCls}-child-item-active`]: state.active,
-          },
-        ];
-      });
-
-      const getAccordion = computed(() => rootProps.accordion);
-      const getCollapse = computed(() => rootProps.collapse);
-      const getTheme = computed(() => rootProps.theme);
-
-      const getOverlayStyle = computed(
-        (): CSSProperties => {
-          return {
-            minWidth: '200px',
-          };
+            [`${prefixCls}-child-item-active`]: state.active
+          }
+        ]
+      })
+
+      const getAccordion = computed(() => rootProps.accordion)
+      const getCollapse = computed(() => rootProps.collapse)
+      const getTheme = computed(() => rootProps.theme)
+
+      const getOverlayStyle = computed((): CSSProperties => {
+        return {
+          minWidth: '200px'
         }
-      );
+      })
 
       const getIsOpend = computed(() => {
-        const name = props.name;
+        const name = props.name
         if (unref(getCollapse)) {
-          return parentGetOpenNames().includes(name);
+          return parentGetOpenNames().includes(name)
         }
-        return state.opened;
-      });
+        return state.opened
+      })
 
       const getSubClass = computed(() => {
-        const isActive = rootProps.activeSubMenuNames.includes(props.name);
+        const isActive = rootProps.activeSubMenuNames.includes(props.name)
         return [
           `${prefixCls}-submenu-title`,
           {
             [`${prefixCls}-submenu-active`]: isActive,
             [`${prefixCls}-submenu-active-border`]: isActive && level === 0,
-            [`${prefixCls}-submenu-collapse`]: unref(getCollapse) && level === 0,
-          },
-        ];
-      });
+            [`${prefixCls}-submenu-collapse`]: unref(getCollapse) && level === 0
+          }
+        ]
+      })
 
       function getEvents(deep: boolean) {
         if (!unref(getCollapse)) {
-          return {};
+          return {}
         }
         return {
           onMouseenter: handleMouseenter,
-          onMouseleave: () => handleMouseleave(deep),
-        };
+          onMouseleave: () => handleMouseleave(deep)
+        }
       }
 
       function handleClick() {
-        const { disabled } = props;
-        if (disabled || unref(getCollapse)) return;
-        const opened = state.opened;
+        const { disabled } = props
+        if (disabled || unref(getCollapse)) return
+        const opened = state.opened
 
         if (unref(getAccordion)) {
-          const { uidList } = getParentList();
+          const { uidList } = getParentList()
           rootMenuEmitter.emit('on-update-opened', {
             opend: false,
             parent: instance?.parent,
-            uidList: uidList,
-          });
+            uidList: uidList
+          })
         } else {
           rootMenuEmitter.emit('open-name-change', {
             name: props.name,
-            opened: !opened,
-          });
+            opened: !opened
+          })
         }
-        state.opened = !opened;
+        state.opened = !opened
       }
 
       function handleMouseenter() {
-        const disabled = props.disabled;
-        if (disabled) return;
+        const disabled = props.disabled
+        if (disabled) return
 
-        subMenuEmitter.emit('submenu:mouse-enter-child');
+        subMenuEmitter.emit('submenu:mouse-enter-child')
 
-        const index = parentGetOpenNames().findIndex((item) => item === props.name);
+        const index = parentGetOpenNames().findIndex(item => item === props.name)
 
-        sliceIndex(index);
+        sliceIndex(index)
 
-        const isRoot = level === 0 && parentGetOpenNames().length === 2;
+        const isRoot = level === 0 && parentGetOpenNames().length === 2
         if (isRoot) {
-          parentRemoveAll();
+          parentRemoveAll()
         }
-        data.isChild = parentGetOpenNames().includes(props.name);
-        clearTimeout(data.timeout!);
+        data.isChild = parentGetOpenNames().includes(props.name)
+        clearTimeout(data.timeout!)
         data.timeout = setTimeout(() => {
-          parentAddSubmenu(props.name);
-        }, DELAY);
+          parentAddSubmenu(props.name)
+        }, DELAY)
       }
 
       function handleMouseleave(deepDispatch = false) {
-        const parentName = getParentMenu.value?.props.name;
+        const parentName = getParentMenu.value?.props.name
         if (!parentName) {
-          isRemoveAllPopup.value = true;
+          isRemoveAllPopup.value = true
         }
 
         if (parentGetOpenNames().slice(-1)[0] === props.name) {
-          data.isChild = false;
+          data.isChild = false
         }
 
-        subMenuEmitter.emit('submenu:mouse-leave-child');
+        subMenuEmitter.emit('submenu:mouse-leave-child')
         if (data.timeout) {
-          clearTimeout(data.timeout!);
+          clearTimeout(data.timeout!)
           data.timeout = setTimeout(() => {
             if (isRemoveAllPopup.value) {
-              parentRemoveAll();
+              parentRemoveAll()
             } else if (!data.mouseInChild) {
-              parentRemoveSubmenu(props.name);
+              parentRemoveSubmenu(props.name)
             }
-          }, DELAY);
+          }, DELAY)
         }
         if (deepDispatch) {
           if (getParentSubMenu.value) {
-            parentHandleMouseleave?.(true);
+            parentHandleMouseleave?.(true)
           }
         }
       }
 
       onBeforeMount(() => {
         subMenuEmitter.on('submenu:mouse-enter-child', () => {
-          data.mouseInChild = true;
-          isRemoveAllPopup.value = false;
-          clearTimeout(data.timeout!);
-        });
+          data.mouseInChild = true
+          isRemoveAllPopup.value = false
+          clearTimeout(data.timeout!)
+        })
         subMenuEmitter.on('submenu:mouse-leave-child', () => {
-          if (data.isChild) return;
-          data.mouseInChild = false;
-          clearTimeout(data.timeout!);
-        });
-
-        rootMenuEmitter.on(
-          'on-update-opened',
-          (data: boolean | (string | number)[] | Recordable) => {
-            if (unref(getCollapse)) return;
-            if (isBoolean(data)) {
-              state.opened = data;
-              return;
-            }
+          if (data.isChild) return
+          data.mouseInChild = false
+          clearTimeout(data.timeout!)
+        })
+
+        rootMenuEmitter.on('on-update-opened', (data: boolean | (string | number)[] | Recordable) => {
+          if (unref(getCollapse)) return
+          if (isBoolean(data)) {
+            state.opened = data
+            return
+          }
 
-            if (isObject(data)) {
-              const { opend, parent, uidList } = data as Recordable;
-              if (parent === instance?.parent) {
-                state.opened = opend;
-              } else if (!uidList.includes(instance?.uid)) {
-                state.opened = false;
-              }
-              return;
+          if (isObject(data)) {
+            const { opend, parent, uidList } = data as Recordable
+            if (parent === instance?.parent) {
+              state.opened = opend
+            } else if (!uidList.includes(instance?.uid)) {
+              state.opened = false
             }
+            return
+          }
 
-            if (props.name && Array.isArray(data)) {
-              state.opened = (data as (string | number)[]).includes(props.name);
-            }
+          if (props.name && Array.isArray(data)) {
+            state.opened = (data as (string | number)[]).includes(props.name)
           }
-        );
+        })
 
         rootMenuEmitter.on('on-update-active-name:submenu', (data: number[]) => {
           if (instance?.uid) {
-            state.active = data.includes(instance?.uid);
+            state.active = data.includes(instance?.uid)
           }
-        });
-      });
+        })
+      })
 
       function handleVisibleChange(visible: boolean) {
-        state.opened = visible;
+        state.opened = visible
       }
 
       // provide
@@ -314,8 +303,8 @@
         sliceIndex,
         level: level + 1,
         handleMouseleave,
-        props: rootProps,
-      });
+        props: rootProps
+      })
 
       return {
         getClass,
@@ -331,8 +320,8 @@
         getEvents,
         getSubClass,
         ...toRefs(state),
-        ...toRefs(data),
-      };
-    },
-  });
+        ...toRefs(data)
+      }
+    }
+  })
 </script>

+ 41 - 43
src/components/SimpleMenu/src/components/useMenu.ts

@@ -1,78 +1,76 @@
-import { computed, ComponentInternalInstance, unref } from 'vue';
-import type { CSSProperties } from 'vue';
+import { computed, ComponentInternalInstance, unref } from 'vue'
+import type { CSSProperties } from 'vue'
 
 export function useMenuItem(instance: ComponentInternalInstance | null) {
   const getParentMenu = computed(() => {
-    return findParentMenu(['Menu', 'SubMenu']);
-  });
+    return findParentMenu(['Menu', 'SubMenu'])
+  })
 
   const getParentRootMenu = computed(() => {
-    return findParentMenu(['Menu']);
-  });
+    return findParentMenu(['Menu'])
+  })
 
   const getParentSubMenu = computed(() => {
-    return findParentMenu(['SubMenu']);
-  });
+    return findParentMenu(['SubMenu'])
+  })
 
-  const getItemStyle = computed(
-    (): CSSProperties => {
-      let parent = instance?.parent;
-      if (!parent) return {};
-      const indentSize = (unref(getParentRootMenu)?.props.indentSize as number) ?? 20;
-      let padding = indentSize;
+  const getItemStyle = computed((): CSSProperties => {
+    let parent = instance?.parent
+    if (!parent) return {}
+    const indentSize = (unref(getParentRootMenu)?.props.indentSize as number) ?? 20
+    let padding = indentSize
 
-      if (unref(getParentRootMenu)?.props.collapse) {
-        padding = indentSize;
-      } else {
-        while (parent && parent.type.name !== 'Menu') {
-          if (parent.type.name === 'SubMenu') {
-            padding += indentSize;
-          }
-          parent = parent.parent;
+    if (unref(getParentRootMenu)?.props.collapse) {
+      padding = indentSize
+    } else {
+      while (parent && parent.type.name !== 'Menu') {
+        if (parent.type.name === 'SubMenu') {
+          padding += indentSize
         }
+        parent = parent.parent
       }
-      return { paddingLeft: padding + 'px' };
     }
-  );
+    return { paddingLeft: padding + 'px' }
+  })
 
   function findParentMenu(name: string[]) {
-    let parent = instance?.parent;
-    if (!parent) return null;
+    let parent = instance?.parent
+    if (!parent) return null
     while (parent && name.indexOf(parent.type.name!) === -1) {
-      parent = parent.parent;
+      parent = parent.parent
     }
-    return parent;
+    return parent
   }
 
   function getParentList() {
-    let parent = instance;
+    let parent = instance
     if (!parent)
       return {
         uidList: [],
-        list: [],
-      };
-    const ret: any[] = [];
+        list: []
+      }
+    const ret: any[] = []
     while (parent && parent.type.name !== 'Menu') {
       if (parent.type.name === 'SubMenu') {
-        ret.push(parent);
+        ret.push(parent)
       }
-      parent = parent.parent;
+      parent = parent.parent
     }
     return {
-      uidList: ret.map((item) => item.uid),
-      list: ret,
-    };
+      uidList: ret.map(item => item.uid),
+      list: ret
+    }
   }
 
   function getParentInstance(instance: ComponentInternalInstance, name = 'SubMenu') {
-    let parent = instance.parent;
+    let parent = instance.parent
     while (parent) {
       if (parent.type.name !== name) {
-        return parent;
+        return parent
       }
-      parent = parent.parent;
+      parent = parent.parent
     }
-    return parent;
+    return parent
   }
 
   return {
@@ -81,6 +79,6 @@ export function useMenuItem(instance: ComponentInternalInstance | null) {
     getParentRootMenu,
     getParentList,
     getParentSubMenu,
-    getItemStyle,
-  };
+    getItemStyle
+  }
 }

+ 1 - 1
src/components/StrengthMeter/index.ts

@@ -1 +1 @@
-export { default as StrengthMeter } from './src/index.vue';
+export { default as StrengthMeter } from './src/StrengthMeter.vue'

+ 145 - 0
src/components/StrengthMeter/src/StrengthMeter.vue

@@ -0,0 +1,145 @@
+<template>
+  <div :class="prefixCls" class="relative">
+    <InputPassword
+      v-if="showInput"
+      v-bind="$attrs"
+      allowClear
+      :value="innerValueRef"
+      @change="handleChange"
+      :disabled="disabled"
+    >
+      <template #[item]="data" v-for="item in Object.keys($slots)">
+        <slot :name="item" v-bind="data"></slot>
+      </template>
+    </InputPassword>
+    <div :class="`${prefixCls}-bar`">
+      <div :class="`${prefixCls}-bar--fill`" :data-score="getPasswordStrength"></div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue'
+
+  import { Input } from 'ant-design-vue'
+
+  // @ts-ignore
+  import { zxcvbn } from '@zxcvbn-ts/core'
+  import { useDesign } from '/@/hooks/web/useDesign'
+  import { propTypes } from '/@/utils/propTypes'
+
+  export default defineComponent({
+    name: 'StrengthMeter',
+    components: { InputPassword: Input.Password },
+    props: {
+      value: propTypes.string,
+      showInput: propTypes.bool.def(true),
+      disabled: propTypes.bool
+    },
+    emits: ['score-change', 'change'],
+    setup(props, { emit }) {
+      const innerValueRef = ref('')
+      const { prefixCls } = useDesign('strength-meter')
+
+      const getPasswordStrength = computed(() => {
+        const { disabled } = props
+        if (disabled) return -1
+        const innerValue = unref(innerValueRef)
+        const score = innerValue ? zxcvbn(unref(innerValueRef)).score : -1
+        emit('score-change', score)
+        return score
+      })
+
+      function handleChange(e: ChangeEvent) {
+        innerValueRef.value = e.target.value
+      }
+
+      watchEffect(() => {
+        innerValueRef.value = props.value || ''
+      })
+
+      watch(
+        () => unref(innerValueRef),
+        val => {
+          emit('change', val)
+        }
+      )
+
+      return {
+        getPasswordStrength,
+        handleChange,
+        prefixCls,
+        innerValueRef
+      }
+    }
+  })
+</script>
+<style lang="less" scoped>
+  @prefix-cls: ~'@{namespace}-strength-meter';
+
+  .@{prefix-cls} {
+    &-bar {
+      position: relative;
+      height: 6px;
+      margin: 10px auto 6px;
+      background-color: @disabled-color;
+      border-radius: 6px;
+
+      &::before,
+      &::after {
+        position: absolute;
+        z-index: 10;
+        display: block;
+        width: 20%;
+        height: inherit;
+        background-color: transparent;
+        border-color: @white;
+        border-style: solid;
+        border-width: 0 5px 0 5px;
+        content: '';
+      }
+
+      &::before {
+        left: 20%;
+      }
+
+      &::after {
+        right: 20%;
+      }
+
+      &--fill {
+        position: absolute;
+        width: 0;
+        height: inherit;
+        background-color: transparent;
+        border-radius: inherit;
+        transition: width 0.5s ease-in-out, background 0.25s;
+
+        &[data-score='0'] {
+          width: 20%;
+          background-color: darken(@error-color, 10%);
+        }
+
+        &[data-score='1'] {
+          width: 40%;
+          background-color: @error-color;
+        }
+
+        &[data-score='2'] {
+          width: 60%;
+          background-color: @warning-color;
+        }
+
+        &[data-score='3'] {
+          width: 80%;
+          background-color: fade(@success-color, 50%);
+        }
+
+        &[data-score='4'] {
+          width: 100%;
+          background-color: @success-color;
+        }
+      }
+    }
+  }
+</style>

+ 112 - 103
src/components/Table/src/BasicTable.vue

@@ -32,38 +32,40 @@
   </div>
 </template>
 <script lang="ts">
-  import type { BasicTableProps, TableActionType, SizeType } from './types/table';
-
-  import { defineComponent, ref, computed, unref, toRaw } from 'vue';
-  import { Table } from 'ant-design-vue';
-  import { BasicForm, useForm } from '/@/components/Form/index';
-  import expandIcon from './components/ExpandIcon';
-  import HeaderCell from './components/HeaderCell.vue';
-
-  import { usePagination } from './hooks/usePagination';
-  import { useColumns } from './hooks/useColumns';
-  import { useDataSource } from './hooks/useDataSource';
-  import { useLoading } from './hooks/useLoading';
-  import { useRowSelection } from './hooks/useRowSelection';
-  import { useTableScroll } from './hooks/useTableScroll';
-  import { useCustomRow } from './hooks/useCustomRow';
-  import { useTableStyle } from './hooks/useTableStyle';
-  import { useTableHeader } from './hooks/useTableHeader';
-  import { useTableExpand } from './hooks/useTableExpand';
-  import { createTableContext } from './hooks/useTableContext';
-  import { useTableFooter } from './hooks/useTableFooter';
-  import { useTableForm } from './hooks/useTableForm';
-  import { useExpose } from '/@/hooks/core/useExpose';
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  import { omit } from 'lodash-es';
-  import { basicProps } from './props';
+  import type { BasicTableProps, TableActionType, SizeType, ColumnChangeParam } from './types/table'
+
+  import { defineComponent, ref, computed, unref, toRaw } from 'vue'
+  import { Table } from 'ant-design-vue'
+  import { BasicForm, useForm } from '/@/components/Form/index'
+  import expandIcon from './components/ExpandIcon'
+  import HeaderCell from './components/HeaderCell.vue'
+  import { InnerHandlers } from './types/table'
+
+  import { usePagination } from './hooks/usePagination'
+  import { useColumns } from './hooks/useColumns'
+  import { useDataSource } from './hooks/useDataSource'
+  import { useLoading } from './hooks/useLoading'
+  import { useRowSelection } from './hooks/useRowSelection'
+  import { useTableScroll } from './hooks/useTableScroll'
+  import { useCustomRow } from './hooks/useCustomRow'
+  import { useTableStyle } from './hooks/useTableStyle'
+  import { useTableHeader } from './hooks/useTableHeader'
+  import { useTableExpand } from './hooks/useTableExpand'
+  import { createTableContext } from './hooks/useTableContext'
+  import { useTableFooter } from './hooks/useTableFooter'
+  import { useTableForm } from './hooks/useTableForm'
+  import { useExpose } from '/@/hooks/core/useExpose'
+  import { useDesign } from '/@/hooks/web/useDesign'
+
+  import { omit } from 'lodash-es'
+  import { basicProps } from './props'
+  import { isFunction } from '/@/utils/is'
 
   export default defineComponent({
     components: {
       Table,
       BasicForm,
-      HeaderCell,
+      HeaderCell
     },
     props: basicProps,
     emits: [
@@ -81,29 +83,26 @@
       'edit-row-end',
       'edit-change',
       'expanded-rows-change',
+      'change',
+      'columns-change'
     ],
     setup(props, { attrs, emit, slots }) {
-      const tableElRef = ref<ComponentRef>(null);
-      const tableData = ref<Recordable[]>([]);
+      const tableElRef = ref<ComponentRef>(null)
+      const tableData = ref<Recordable[]>([])
 
-      const wrapRef = ref<Nullable<HTMLDivElement>>(null);
-      const innerPropsRef = ref<Partial<BasicTableProps>>();
+      const wrapRef = ref<Nullable<HTMLDivElement>>(null)
+      const innerPropsRef = ref<Partial<BasicTableProps>>()
 
-      const { prefixCls } = useDesign('basic-table');
-      const [registerForm, formActions] = useForm();
+      const { prefixCls } = useDesign('basic-table')
+      const [registerForm, formActions] = useForm()
 
       const getProps = computed(() => {
-        return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
-      });
+        return { ...props, ...unref(innerPropsRef) } as BasicTableProps
+      })
 
-      const { getLoading, setLoading } = useLoading(getProps);
-      const {
-        getPaginationInfo,
-        getPagination,
-        setPagination,
-        setShowPagination,
-        getShowPagination,
-      } = usePagination(getProps);
+      const { getLoading, setLoading } = useLoading(getProps)
+      const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } =
+        usePagination(getProps)
 
       const {
         getRowSelection,
@@ -112,19 +111,20 @@
         clearSelectedRowKeys,
         getSelectRowKeys,
         deleteSelectRowByKey,
-        setSelectedRowKeys,
-      } = useRowSelection(getProps, tableData, emit);
+        setSelectedRowKeys
+      } = useRowSelection(getProps, tableData, emit)
 
       const {
-        handleTableChange,
+        handleTableChange: onTableChange,
         getDataSourceRef,
         getDataSource,
         setTableData,
+        updateTableDataRecord,
         fetch,
         getRowKey,
         reload,
         getAutoCreateKey,
-        updateTableData,
+        updateTableData
       } = useDataSource(
         getProps,
         {
@@ -133,19 +133,21 @@
           setLoading,
           setPagination,
           getFieldsValue: formActions.getFieldsValue,
-          clearSelectedRowKeys,
+          clearSelectedRowKeys
         },
         emit
-      );
+      )
+
+      function handleTableChange(...args) {
+        onTableChange.call(undefined, ...args)
+        emit('change', ...args)
+        // 解决通过useTable注册onChange时不起作用的问题
+        const { onChange } = unref(getProps)
+        onChange && isFunction(onChange) && onChange.call(undefined, ...args)
+      }
 
-      const {
-        getViewColumns,
-        getColumns,
-        setCacheColumnsByField,
-        setColumns,
-        getColumnsRef,
-        getCacheColumns,
-      } = useColumns(getProps, getPaginationInfo);
+      const { getViewColumns, getColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } =
+        useColumns(getProps, getPaginationInfo)
 
       const { getScrollRef, redoHeight } = useTableScroll(
         getProps,
@@ -153,38 +155,41 @@
         getColumnsRef,
         getRowSelectionRef,
         getDataSourceRef
-      );
+      )
 
       const { customRow } = useCustomRow(getProps, {
         setSelectedRowKeys,
         getSelectRowKeys,
         clearSelectedRowKeys,
         getAutoCreateKey,
-        emit,
-      });
+        emit
+      })
 
-      const { getRowClassName } = useTableStyle(getProps, prefixCls);
+      const { getRowClassName } = useTableStyle(getProps, prefixCls)
 
-      const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit);
+      const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit)
 
-      const { getHeaderProps } = useTableHeader(getProps, slots);
+      const handlers: InnerHandlers = {
+        onColumnsChange: (data: ColumnChangeParam[]) => {
+          emit('columns-change', data)
+          // support useTable
+          unref(getProps).onColumnsChange?.(data)
+        }
+      }
 
-      const { getFooterProps } = useTableFooter(
-        getProps,
-        getScrollRef,
-        tableElRef,
-        getDataSourceRef
-      );
+      const { getHeaderProps } = useTableHeader(getProps, slots, handlers)
 
-      const {
-        getFormProps,
-        replaceFormSlotKey,
-        getFormSlotKeys,
-        handleSearchInfoChange,
-      } = useTableForm(getProps, slots, fetch, getLoading);
+      const { getFooterProps } = useTableFooter(getProps, getScrollRef, tableElRef, getDataSourceRef)
+
+      const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = useTableForm(
+        getProps,
+        slots,
+        fetch,
+        getLoading
+      )
 
       const getBindValues = computed(() => {
-        const dataSource = unref(getDataSourceRef);
+        const dataSource = unref(getDataSourceRef)
         let propsData: Recordable = {
           size: 'middle',
           // ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
@@ -202,38 +207,38 @@
           pagination: toRaw(unref(getPaginationInfo)),
           dataSource,
           footer: unref(getFooterProps),
-          ...unref(getExpandOption),
-        };
+          ...unref(getExpandOption)
+        }
         if (slots.expandedRowRender) {
-          propsData = omit(propsData, 'scroll');
+          propsData = omit(propsData, 'scroll')
         }
 
-        propsData = omit(propsData, 'class');
-        return propsData;
-      });
+        propsData = omit(propsData, ['class', 'onChange'])
+        return propsData
+      })
 
       const getWrapperClass = computed(() => {
-        const values = unref(getBindValues);
+        const values = unref(getBindValues)
         return [
           prefixCls,
           attrs.class,
           {
             [`${prefixCls}-form-container`]: values.useSearchForm,
-            [`${prefixCls}--inset`]: values.inset,
-          },
-        ];
-      });
+            [`${prefixCls}--inset`]: values.inset
+          }
+        ]
+      })
 
       const getEmptyDataIsShowTable = computed(() => {
-        const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
+        const { emptyDataIsShowTable, useSearchForm } = unref(getProps)
         if (emptyDataIsShowTable || !useSearchForm) {
-          return true;
+          return true
         }
-        return !!unref(getDataSourceRef).length;
-      });
+        return !!unref(getDataSourceRef).length
+      })
 
       function setProps(props: Partial<BasicTableProps>) {
-        innerPropsRef.value = { ...unref(innerPropsRef), ...props };
+        innerPropsRef.value = { ...unref(innerPropsRef), ...props }
       }
 
       const tableAction: TableActionType = {
@@ -244,6 +249,7 @@
         deleteSelectRowByKey,
         setPagination,
         setTableData,
+        updateTableDataRecord,
         redoHeight,
         setSelectedRowKeys,
         setColumns,
@@ -262,14 +268,14 @@
         expandAll,
         collapseAll,
         getSize: () => {
-          return unref(getBindValues).size as SizeType;
-        },
-      };
-      createTableContext({ ...tableAction, wrapRef, getBindValues });
+          return unref(getBindValues).size as SizeType
+        }
+      }
+      createTableContext({ ...tableAction, wrapRef, getBindValues })
 
-      useExpose<TableActionType>(tableAction);
+      useExpose<TableActionType>(tableAction)
 
-      emit('register', tableAction, formActions);
+      emit('register', tableAction, formActions)
 
       return {
         tableElRef,
@@ -287,10 +293,10 @@
         replaceFormSlotKey,
         getFormSlotKeys,
         getWrapperClass,
-        columns: getViewColumns,
-      };
-    },
-  });
+        columns: getViewColumns
+      }
+    }
+  })
 </script>
 <style lang="less">
   @border-color: #cecece4d;
@@ -298,9 +304,11 @@
   @prefix-cls: ~'@{namespace}-basic-table';
 
   .@{prefix-cls} {
+    max-width: 100%;
+
     &-row__striped {
       td {
-        background-color: content-background;
+        background-color: @app-content-background;
       }
     }
 
@@ -331,6 +339,7 @@
       border-radius: 2px;
 
       .ant-table-title {
+        min-height: 40px;
         padding: 0 0 8px 0 !important;
       }
 

+ 0 - 0
src/components/Table/src/componentMap.ts


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů