37 Commits 39624e6632 ... 6037335934

Author SHA1 Message Date
  kailong321200875 6037335934 fix: 修复IconPicker BUG 1 year ago
  kailong321200875 296685eced feat: IconPicker 1 year ago
  kailong321200875 8e340053b8 wip: IconPicker 开发中 1 year ago
  kailong321200875 7019511c77 fix: 修复useCrudSchemas无法自定义label 1 year ago
  kailong321200875 4bd0a6e925 mod: 删除无用代码 1 year ago
  kailong321200875 10b5acfbd2 mod: 去除无用代码 1 year ago
  Archer 0f3739ac68 Merge pull request #341 from genffy/master 1 year ago
  genffy a10a283683 fix: default interceptor response return 1 year ago
  kailong321200875 2ad0a4b4d1 docs: 更新README 1 year ago
  kailong321200875 2a56d891b3 docs: 更新README 1 year ago
  kailong321200875 cb2f276bf9 style: 修复样式层级问题 1 year ago
  Archer 7270e1d03b Merge pull request #338 from kailong321200875/revert-334-master 1 year ago
  Archer 40d68032ff Revert "feat: 表格工具栏新增列设置功能" 1 year ago
  Archer 38c5192455 Merge pull request #334 from vvandk/master 1 year ago
  ktianc 5fb9df16ed feat: 表格工具栏新增列设置功能 1 year ago
  kailong321200875 1d1640a957 docs: 更新群二维码 1 year ago
  kailong321200875 93a0bc9bba fix: #326 1 year ago
  kailong321200875 9c45923d40 style: 修改登录样式 1 year ago
  Archer 1ade4b3514 Merge pull request #324 from kailong321200875/release-please--branches--release--components--standard-version 1 year ago
  Archer 52df745f2c chore(release): release 2.2.0 1 year ago
  kailong321200875 4561758cf7 Merge branch 'master' into release 1 year ago
  kailong321200875 1c071b6d7f docs: 更新群二维码 1 year ago
  kailong321200875 d8666b3c7e perf: 优化动态路由 1 year ago
  kailong321200875 bb3ebbf3f2 fix: 修复动态路由无效 1 year ago
  kailong321200875 a67fb33dbe feat: 新增 useCrudSchemas demo 1 year ago
  kailong321200875 f0c4cc1258 feat: JsonEditor 1 year ago
  kailong321200875 59a3f5eb07 perf: 更新demo 1 year ago
  kailong321200875 7e4e1725c0 fix: #319 1 year ago
  kailong321200875 2481b80e2b fix: #317 1 year ago
  kailong321200875 aace14d0eb fix: #318 1 year ago
  kailong321200875 bd0c08307f docs: 更新群二维码 1 year ago
  kailong321200875 81cdd60541 fix: #316 1 year ago
  kailong321200875 fdc7c48c2b style: 修改Descriptions样式 1 year ago
  kailong321200875 a538933a85 fix: 修复useValidator报错 1 year ago
  kailong321200875 1e5cdcaf58 mod: 删除无用代码 1 year ago
  kailong321200875 f9cfa7cc73 refactor: 重构useValidator 1 year ago
  kailong321200875 cc49593ded chore: 删除无用依赖 1 year ago
64 changed files with 4624 additions and 1191 deletions
  1. 75 0
      CHANGELOG.md
  2. 4 6
      README.md
  3. 4 6
      README.zh-CN.md
  4. 38 46
      mock/analysis/index.ts
  5. 18 32
      mock/department/index.ts
  6. 17 21
      mock/dict/index.ts
  7. 214 229
      mock/menu/index.ts
  8. 432 87
      mock/role/index.ts
  9. 15 27
      mock/table/index.ts
  10. 7 13
      mock/user/index.ts
  11. 111 121
      mock/workplace/index.ts
  12. 11 7
      package.json
  13. 71 0
      scripts/icon.ts
  14. 1 1
      src/api/login/index.ts
  15. 4 4
      src/components/Breadcrumb/src/Breadcrumb.vue
  16. 1 2
      src/components/Breadcrumb/src/helper.ts
  17. 1 1
      src/components/Descriptions/src/Descriptions.vue
  18. 3 1
      src/components/Form/src/helper/componentMap.ts
  19. 4 1
      src/components/Form/src/types/index.ts
  20. 3 0
      src/components/IconPicker/index.ts
  21. 168 0
      src/components/IconPicker/src/IconPicker.vue
  22. 795 0
      src/components/IconPicker/src/data/icons.ant-design.ts
  23. 299 0
      src/components/IconPicker/src/data/icons.ep.ts
  24. 1209 0
      src/components/IconPicker/src/data/icons.tdesign.ts
  25. 4 0
      src/components/JsonEditor/index.ts
  26. 98 0
      src/components/JsonEditor/src/JsonEditor.vue
  27. 23 0
      src/components/JsonEditor/src/types/index.ts
  28. 1 2
      src/components/Menu/src/components/useRenderMenuItem.tsx
  29. 1 2
      src/components/Menu/src/helper.ts
  30. 14 1
      src/components/Setting/src/components/InterfaceDisplay.vue
  31. 14 9
      src/components/TabMenu/src/TabMenu.vue
  32. 2 3
      src/components/TabMenu/src/helper.ts
  33. 1 47
      src/components/Table/src/Table.vue
  34. 3 70
      src/components/Table/src/components/TableActions.vue
  35. 2 2
      src/components/TagsView/src/helper.ts
  36. 1 0
      src/config/axios/service.ts
  37. 4 4
      src/hooks/web/useCrudSchemas.ts
  38. 49 0
      src/hooks/web/useGuide.ts
  39. 0 47
      src/hooks/web/useIntro.ts
  40. 22 7
      src/hooks/web/useStorage.ts
  41. 48 44
      src/hooks/web/useValidator.ts
  42. 16 5
      src/locales/en.ts
  43. 16 5
      src/locales/zh-CN.ts
  44. 4 17
      src/permission.ts
  45. 354 2
      src/router/index.ts
  46. 12 4
      src/store/modules/app.ts
  47. 0 34
      src/store/modules/dict.ts
  48. 10 6
      src/store/modules/permission.ts
  49. 10 2
      src/store/modules/tagsView.ts
  50. 11 8
      src/utils/routerHelper.ts
  51. 36 0
      src/views/Components/Editor/JsonEditor.vue
  52. 26 2
      src/views/Components/Form/DefaultForm.vue
  53. 16 0
      src/views/Components/IconPicker.vue
  54. 19 0
      src/views/Function/MultipleTabsDemo2.vue
  55. 3 3
      src/views/Guide/Guide.vue
  56. 1 1
      src/views/Login/Login.vue
  57. 7 15
      src/views/Login/components/LoginForm.vue
  58. 177 214
      src/views/hooks/useCrudSchemas.vue
  59. 3 3
      src/views/hooks/useTagsView.vue
  60. 80 0
      src/views/hooks/useValidator.vue
  61. 1 3
      tsconfig.json
  62. 2 2
      types/components.d.ts
  63. 18 16
      types/router.d.ts
  64. 10 6
      vite.config.ts

+ 75 - 0
CHANGELOG.md

@@ -2,6 +2,81 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+## [2.2.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v2.1.0...v2.2.0) (2023-08-27)
+
+
+### Features
+
+* JsonEditor ([c0f4517](https://github.com/kailong321200875/vue-element-plus-admin/commit/c0f4517b87de5a0172a057fb9da141f758cca1fa))
+* 新增 useCrudSchemas demo ([ae0628e](https://github.com/kailong321200875/vue-element-plus-admin/commit/ae0628e3af3466c7c9d7b593b825f776843de5ec))
+* 新增useTagsView ([a869a45](https://github.com/kailong321200875/vue-element-plus-admin/commit/a869a457e6a8052531ce3040ae0d332d7afbb478))
+
+
+### Bug Fixes
+
+* [#316](https://github.com/kailong321200875/vue-element-plus-admin/issues/316) ([7582e4d](https://github.com/kailong321200875/vue-element-plus-admin/commit/7582e4d12f18ae86f5ef4ff36211c364afca5763))
+* [#317](https://github.com/kailong321200875/vue-element-plus-admin/issues/317) ([2095caa](https://github.com/kailong321200875/vue-element-plus-admin/commit/2095caaa854b686b57f47ee183419f42563a5a95))
+* [#318](https://github.com/kailong321200875/vue-element-plus-admin/issues/318) ([4169e52](https://github.com/kailong321200875/vue-element-plus-admin/commit/4169e52baaaa43765848c29c5ce222d019e81c35))
+* [#319](https://github.com/kailong321200875/vue-element-plus-admin/issues/319) ([b6ee4e5](https://github.com/kailong321200875/vue-element-plus-admin/commit/b6ee4e5d48deb3a07f289366ed3700baa3674cd6))
+* 修复useValidator报错 ([4912f6c](https://github.com/kailong321200875/vue-element-plus-admin/commit/4912f6c0586249b3de7ac7d365c8ea98af7923c7))
+* 修复动态路由无效 ([1452a1a](https://github.com/kailong321200875/vue-element-plus-admin/commit/1452a1afc77eb3f64cd3de91a05ddc15e40f4a06))
+
+
+### Docs
+
+* 更新README ([5b4defa](https://github.com/kailong321200875/vue-element-plus-admin/commit/5b4defa8c4be2de894b2cb50ae9ea739a10cf7d9))
+* 更新群二维码 ([13aa71c](https://github.com/kailong321200875/vue-element-plus-admin/commit/13aa71c5bd5b5076599501961a24a171a9133c57))
+* 更新群二维码 ([ae29e97](https://github.com/kailong321200875/vue-element-plus-admin/commit/ae29e974bfed2214d1beda703b976cdfa63070ab))
+
+
+### Styling
+
+* 修改Descriptions样式 ([cd0e05a](https://github.com/kailong321200875/vue-element-plus-admin/commit/cd0e05a6b9146af7ae64be62613724cd58e6c2a3))
+
+
+### Code Refactoring
+
+* 重构useValidator ([b8849da](https://github.com/kailong321200875/vue-element-plus-admin/commit/b8849dabe2b306831f69e84db167a367570d992a))
+
+
+### Performance Improvements
+
+* 优化动态路由 ([8793588](https://github.com/kailong321200875/vue-element-plus-admin/commit/879358821d02d5e4575dfee0d189b9fee7f2e217))
+* 完善useTagsView ([e0c55f4](https://github.com/kailong321200875/vue-element-plus-admin/commit/e0c55f40d4c1c47e29de6c4c7e9433efa978bf7f))
+* 完善useTagsView ([175abd0](https://github.com/kailong321200875/vue-element-plus-admin/commit/175abd0aa3388e8473f6ecbf63e28133fce55bd3))
+* 更新demo ([2c99cd2](https://github.com/kailong321200875/vue-element-plus-admin/commit/2c99cd20f0c25a740ac7a3a8319f7a112e69c0d3))
+
+## [2.1.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v2.0.0...v2.1.0) (2023-08-12)
+
+
+### Features
+
+* 新增多开标签页Demo ([5c253ce](https://github.com/kailong321200875/vue-element-plus-admin/commit/5c253ce803a9ef7ce03534ddd5f0865db4602378))
+
+
+### Bug Fixes
+
+* [#307](https://github.com/kailong321200875/vue-element-plus-admin/issues/307) ([4ce07e1](https://github.com/kailong321200875/vue-element-plus-admin/commit/4ce07e150c0bd3903cc5f43fcd88c2cb292d7690))
+* [#311](https://github.com/kailong321200875/vue-element-plus-admin/issues/311) ([bdde4cc](https://github.com/kailong321200875/vue-element-plus-admin/commit/bdde4ccd39d5d698d68b299c6e80546d4a8be89f))
+* 修复eslint错误 ([b5e47e0](https://github.com/kailong321200875/vue-element-plus-admin/commit/b5e47e04d8f5f889e0c46a2dced108d058ded94e))
+* 修复Table插槽传参错误 ([97344e6](https://github.com/kailong321200875/vue-element-plus-admin/commit/97344e68f5abb144d9e5d4ad273108858dbcfba2))
+* 修复Table组件插槽传参错误 ([c83a026](https://github.com/kailong321200875/vue-element-plus-admin/commit/c83a026d559e2854fead17d2e28fbebcf25490de))
+
+
+### Docs
+
+* 修改Readme ([ee059b7](https://github.com/kailong321200875/vue-element-plus-admin/commit/ee059b7619ad01ded9d3be20287086ddbcce3253))
+* 修改Readme ([e05f5a7](https://github.com/kailong321200875/vue-element-plus-admin/commit/e05f5a77edc175daa267e4fc6abbcfc8fec2e291))
+* 修改Readme ([b0e561d](https://github.com/kailong321200875/vue-element-plus-admin/commit/b0e561d8acd36e8780087e317cc34257956981fd))
+* 修改Readme ([fced2e0](https://github.com/kailong321200875/vue-element-plus-admin/commit/fced2e0087694445a89cf360e5e3e3013d8ca604))
+* 修改README ([dce76f0](https://github.com/kailong321200875/vue-element-plus-admin/commit/dce76f042d5243039540828a3fd982af25f37531))
+* 更新群二维码 ([607ef58](https://github.com/kailong321200875/vue-element-plus-admin/commit/607ef585d010c9ade6f54d96c2a12b36099ece74))
+
+
+### Styling
+
+* 修改TabMenu样式 ([e8cd6f9](https://github.com/kailong321200875/vue-element-plus-admin/commit/e8cd6f9e1c4387c582e461cde4d59796bf17c1bd))
+
 ## [2.0.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.10.0...v2.0.0) (2023-08-06)
 
 

+ 4 - 6
README.md

@@ -31,11 +31,7 @@ If you need a basic template, please switch to the `mini` branch. `mini` simply
 - [vue-element-plus-admin](https://element-plus-admin.cn/) - Full version of the github site
 - [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - Full version of the gitee site
 
-account: **admin/admin test/test**
-
-`admin` account is used to simulate the control permission of the server, and render whatever the server returns
-
-`test` account is used to simulate the front-end control authority. The server only returns the menu key to be displayed, and the front-end performs matching rendering
+account: **admin/admin**
 
 Online examples do not apply to menu filtering by default, but directly use Static routing
 
@@ -139,7 +135,9 @@ If you find this project helpful, welcome sponsorship to show your support~
 
 ## Group
 
-<img src="https://github.com/kailong321200875/my-image/raw/master/chat-0820.jpg" />
+If you want to join the technical communication group for discussion, please add me as a friend
+
+<img src="https://github.com/kailong321200875/my-image/raw/master/me.jpg" />
 
 ## License
 

+ 4 - 6
README.zh-CN.md

@@ -31,11 +31,7 @@ vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模
 - [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点
 - [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点
 
-帐号:**admin/admin test/test**
-
-`admin` 帐号用于模拟服务端控制权限,服务端返回什么就渲染什么
-
-`test` 帐号用于模拟前端控制权限,服务端只返回需要显示的菜单 key,前端进行匹配渲染
+帐号:**admin/admin**
 
 在线例子默认不适用菜单过滤,而是直接使用静态路由表
 
@@ -139,7 +135,9 @@ pnpm run build:pro
 
 ## 交流群
 
-<img src="https://gitee.com/kailong110120130/my-image/raw/master/chat-0820.jpg" />
+如果你想进入技术交流群讨论,请添加我为好友
+
+<img src="https://gitee.com/kailong110120130/my-image/raw/master/me.jpg" />
 
 ## 许可证
 

+ 38 - 46
mock/analysis/index.ts

@@ -13,14 +13,12 @@ export default [
     timeout,
     response: () => {
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            users: 102400,
-            messages: 81212,
-            moneys: 9280,
-            shoppings: 13600
-          }
+          users: 102400,
+          messages: 81212,
+          moneys: 9280,
+          shoppings: 13600
         }
       }
     }
@@ -32,16 +30,14 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            { value: 1000, name: 'analysis.directAccess' },
-            { value: 310, name: 'analysis.mailMarketing' },
-            { value: 234, name: 'analysis.allianceAdvertising' },
-            { value: 135, name: 'analysis.videoAdvertising' },
-            { value: 1548, name: 'analysis.searchEngines' }
-          ]
-        }
+        code: code,
+        data: [
+          { value: 1000, name: 'analysis.directAccess' },
+          { value: 310, name: 'analysis.mailMarketing' },
+          { value: 234, name: 'analysis.allianceAdvertising' },
+          { value: 135, name: 'analysis.videoAdvertising' },
+          { value: 1548, name: 'analysis.searchEngines' }
+        ]
       }
     }
   },
@@ -52,18 +48,16 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            { value: 13253, name: 'analysis.monday' },
-            { value: 34235, name: 'analysis.tuesday' },
-            { value: 26321, name: 'analysis.wednesday' },
-            { value: 12340, name: 'analysis.thursday' },
-            { value: 24643, name: 'analysis.friday' },
-            { value: 1322, name: 'analysis.saturday' },
-            { value: 1324, name: 'analysis.sunday' }
-          ]
-        }
+        code: code,
+        data: [
+          { value: 13253, name: 'analysis.monday' },
+          { value: 34235, name: 'analysis.tuesday' },
+          { value: 26321, name: 'analysis.wednesday' },
+          { value: 12340, name: 'analysis.thursday' },
+          { value: 24643, name: 'analysis.friday' },
+          { value: 1322, name: 'analysis.saturday' },
+          { value: 1324, name: 'analysis.sunday' }
+        ]
       }
     }
   },
@@ -74,23 +68,21 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            { estimate: 100, actual: 120, name: 'analysis.january' },
-            { estimate: 120, actual: 82, name: 'analysis.february' },
-            { estimate: 161, actual: 91, name: 'analysis.march' },
-            { estimate: 134, actual: 154, name: 'analysis.april' },
-            { estimate: 105, actual: 162, name: 'analysis.may' },
-            { estimate: 160, actual: 140, name: 'analysis.june' },
-            { estimate: 165, actual: 145, name: 'analysis.july' },
-            { estimate: 114, actual: 250, name: 'analysis.august' },
-            { estimate: 163, actual: 134, name: 'analysis.september' },
-            { estimate: 185, actual: 56, name: 'analysis.october' },
-            { estimate: 118, actual: 99, name: 'analysis.november' },
-            { estimate: 123, actual: 123, name: 'analysis.december' }
-          ]
-        }
+        code: code,
+        data: [
+          { estimate: 100, actual: 120, name: 'analysis.january' },
+          { estimate: 120, actual: 82, name: 'analysis.february' },
+          { estimate: 161, actual: 91, name: 'analysis.march' },
+          { estimate: 134, actual: 154, name: 'analysis.april' },
+          { estimate: 105, actual: 162, name: 'analysis.may' },
+          { estimate: 160, actual: 140, name: 'analysis.june' },
+          { estimate: 165, actual: 145, name: 'analysis.july' },
+          { estimate: 114, actual: 250, name: 'analysis.august' },
+          { estimate: 163, actual: 134, name: 'analysis.september' },
+          { estimate: 185, actual: 56, name: 'analysis.october' },
+          { estimate: 118, actual: 99, name: 'analysis.november' },
+          { estimate: 123, actual: 123, name: 'analysis.december' }
+        ]
       }
     }
   }

+ 18 - 32
mock/department/index.ts

@@ -99,11 +99,9 @@ export default [
     method: 'get',
     response: () => {
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            list: departmentList
-          }
+          list: departmentList
         }
       }
     }
@@ -113,12 +111,10 @@ export default [
     method: 'get',
     response: () => {
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            list: departmentList,
-            total: 5
-          }
+          list: departmentList,
+          total: 5
         }
       }
     }
@@ -150,12 +146,10 @@ export default [
         )
       }
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            total: 100,
-            list: mockList
-          }
+          total: 100,
+          list: mockList
         }
       }
     }
@@ -167,10 +161,8 @@ export default [
     timeout: 1000,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: 'success'
-        }
+        code: code,
+        data: 'success'
       }
     }
   },
@@ -182,15 +174,13 @@ export default [
       const ids = body.ids
       if (!ids) {
         return {
-          code: '500',
+          code: 500,
           message: '请选择需要删除的数据'
         }
       } else {
         return {
-          data: {
-            code: code,
-            data: 'success'
-          }
+          code: code,
+          data: 'success'
         }
       }
     }
@@ -202,10 +192,8 @@ export default [
     timeout: 1000,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: 'success'
-        }
+        code: code,
+        data: 'success'
       }
     }
   },
@@ -217,15 +205,13 @@ export default [
       const ids = body.ids
       if (!ids) {
         return {
-          code: '500',
+          code: 500,
           message: '请选择需要删除的数据'
         }
       } else {
         return {
-          data: {
-            code: code,
-            data: 'success'
-          }
+          code: code,
+          data: 'success'
         }
       }
     }

+ 17 - 21
mock/dict/index.ts

@@ -30,10 +30,8 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: dictObj
-        }
+        code: code,
+        data: dictObj
       }
     }
   },
@@ -44,23 +42,21 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            {
-              label: 'test1',
-              value: 0
-            },
-            {
-              label: 'test2',
-              value: 1
-            },
-            {
-              label: 'test3',
-              value: 2
-            }
-          ]
-        }
+        code: code,
+        data: [
+          {
+            label: 'test1',
+            value: 0
+          },
+          {
+            label: 'test2',
+            value: 1
+          },
+          {
+            label: 'test3',
+            value: 2
+          }
+        ]
       }
     }
   }

+ 214 - 229
mock/menu/index.ts

@@ -4,7 +4,7 @@ import Mock from 'mockjs'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 export default [
   // 列表接口
@@ -14,248 +14,233 @@ export default [
     timeout,
     response: () => {
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            list: [
-              {
-                path: '/dashboard',
-                component: '#',
-                redirect: '/dashboard/analysis',
-                name: 'Dashboard',
-                status: Mock.Random.integer(0, 1),
-                id: 1,
-                preciousMenu: '',
-                meta: {
-                  title: '首页',
-                  icon: 'ant-design:dashboard-filled',
-                  alwaysShow: true
+          list: [
+            {
+              path: '/dashboard',
+              component: '#',
+              redirect: '/dashboard/analysis',
+              name: 'Dashboard',
+              status: Mock.Random.integer(0, 1),
+              id: 1,
+              meta: {
+                title: '首页',
+                icon: 'ant-design:dashboard-filled',
+                alwaysShow: true
+              },
+              children: [
+                {
+                  path: 'analysis',
+                  component: 'views/Dashboard/Analysis',
+                  name: 'Analysis',
+                  status: Mock.Random.integer(0, 1),
+                  id: 2,
+                  meta: {
+                    title: '分析页',
+                    noCache: true
+                  }
                 },
-                children: [
-                  {
-                    path: 'analysis',
-                    component: 'views/Dashboard/Analysis',
-                    name: 'Analysis',
-                    preciousMenu: 'Dashboard',
-                    status: Mock.Random.integer(0, 1),
-                    id: 2,
-                    meta: {
-                      title: '分析页',
-                      noCache: true
-                    }
-                  },
-                  {
-                    path: 'workplace',
-                    component: 'views/Dashboard/Workplace',
-                    name: 'Workplace',
-                    preciousMenu: 'Dashboard',
-                    status: Mock.Random.integer(0, 1),
-                    id: 3,
-                    meta: {
-                      title: '工作台',
-                      noCache: true
-                    }
+                {
+                  path: 'workplace',
+                  component: 'views/Dashboard/Workplace',
+                  name: 'Workplace',
+                  status: Mock.Random.integer(0, 1),
+                  id: 3,
+                  meta: {
+                    title: '工作台',
+                    noCache: true
                   }
-                ]
+                }
+              ]
+            },
+            {
+              path: '/external-link',
+              component: '#',
+              meta: {
+                title: '文档',
+                icon: 'clarity:document-solid'
               },
-              {
-                path: '/external-link',
-                component: '#',
-                meta: {
-                  title: '文档',
-                  icon: 'clarity:document-solid'
-                },
-                name: 'ExternalLink',
-                status: Mock.Random.integer(0, 1),
-                id: 4,
-                children: [
-                  {
-                    path: 'https://element-plus-admin-doc.cn/',
-                    name: 'DocumentLink',
-                    status: Mock.Random.integer(0, 1),
-                    id: 5,
-                    meta: {
-                      title: '文档'
-                    }
+              name: 'ExternalLink',
+              status: Mock.Random.integer(0, 1),
+              id: 4,
+              children: [
+                {
+                  path: 'https://element-plus-admin-doc.cn/',
+                  name: 'DocumentLink',
+                  status: Mock.Random.integer(0, 1),
+                  id: 5,
+                  meta: {
+                    title: '文档'
                   }
-                ]
+                }
+              ]
+            },
+            {
+              path: '/level',
+              component: '#',
+              redirect: '/level/menu1/menu1-1/menu1-1-1',
+              name: 'Level',
+              status: Mock.Random.integer(0, 1),
+              id: 6,
+              meta: {
+                title: '菜单',
+                icon: 'carbon:skill-level-advanced'
               },
-              {
-                path: '/level',
-                component: '#',
-                redirect: '/level/menu1/menu1-1/menu1-1-1',
-                name: 'Level',
-                status: Mock.Random.integer(0, 1),
-                id: 6,
-                meta: {
-                  title: '菜单',
-                  icon: 'carbon:skill-level-advanced'
-                },
-                children: [
-                  {
-                    path: 'menu1',
-                    name: 'Menu1',
-                    component: '##',
-                    preciousMenu: 'Level',
-                    status: Mock.Random.integer(0, 1),
-                    id: 7,
-                    redirect: '/level/menu1/menu1-1/menu1-1-1',
-                    meta: {
-                      title: '菜单1'
-                    },
-                    children: [
-                      {
-                        path: 'menu1-1',
-                        name: 'Menu11',
-                        preciousMenu: 'Menu1',
-                        component: '##',
-                        status: Mock.Random.integer(0, 1),
-                        id: 8,
-                        redirect: '/level/menu1/menu1-1/menu1-1-1',
-                        meta: {
-                          title: '菜单1-1',
-                          alwaysShow: true
-                        },
-                        children: [
-                          {
-                            path: 'menu1-1-1',
-                            name: 'Menu111',
-                            preciousMenu: 'Menu11',
-                            component: 'views/Level/Menu111',
-                            status: Mock.Random.integer(0, 1),
-                            id: 9,
-                            permission: ['edit', 'add', 'delete'],
-                            meta: {
-                              title: '菜单1-1-1'
-                            }
-                          }
-                        ]
+              children: [
+                {
+                  path: 'menu1',
+                  name: 'Menu1',
+                  component: '##',
+                  status: Mock.Random.integer(0, 1),
+                  id: 7,
+                  redirect: '/level/menu1/menu1-1/menu1-1-1',
+                  meta: {
+                    title: '菜单1'
+                  },
+                  children: [
+                    {
+                      path: 'menu1-1',
+                      name: 'Menu11',
+                      component: '##',
+                      status: Mock.Random.integer(0, 1),
+                      id: 8,
+                      redirect: '/level/menu1/menu1-1/menu1-1-1',
+                      meta: {
+                        title: '菜单1-1',
+                        alwaysShow: true
                       },
-                      {
-                        path: 'menu1-2',
-                        name: 'Menu12',
-                        preciousMenu: 'Menu1',
-                        component: 'views/Level/Menu12',
-                        status: Mock.Random.integer(0, 1),
-                        id: 10,
-                        permission: ['edit', 'add', 'delete'],
-                        meta: {
-                          title: '菜单1-2'
+                      children: [
+                        {
+                          path: 'menu1-1-1',
+                          name: 'Menu111',
+                          component: 'views/Level/Menu111',
+                          status: Mock.Random.integer(0, 1),
+                          id: 9,
+                          permission: ['edit', 'add', 'delete'],
+                          meta: {
+                            title: '菜单1-1-1'
+                          }
                         }
+                      ]
+                    },
+                    {
+                      path: 'menu1-2',
+                      name: 'Menu12',
+                      component: 'views/Level/Menu12',
+                      status: Mock.Random.integer(0, 1),
+                      id: 10,
+                      permission: ['edit', 'add', 'delete'],
+                      meta: {
+                        title: '菜单1-2'
                       }
-                    ]
-                  },
-                  {
-                    path: 'menu2',
-                    name: 'Menu2Demo',
-                    preciousMenu: 'Level',
-                    component: 'views/Level/Menu2',
-                    status: Mock.Random.integer(0, 1),
-                    id: 11,
-                    permission: ['edit', 'add', 'delete'],
-                    meta: {
-                      title: '菜单2'
                     }
+                  ]
+                },
+                {
+                  path: 'menu2',
+                  name: 'Menu2Demo',
+                  component: 'views/Level/Menu2',
+                  status: Mock.Random.integer(0, 1),
+                  id: 11,
+                  permission: ['edit', 'add', 'delete'],
+                  meta: {
+                    title: '菜单2'
                   }
-                ]
+                }
+              ]
+            },
+            {
+              path: '/example',
+              component: '#',
+              redirect: '/example/example-dialog',
+              name: 'Example',
+              status: Mock.Random.integer(0, 1),
+              id: 12,
+              meta: {
+                title: '综合示例',
+                icon: 'ep:management',
+                alwaysShow: true
               },
-              {
-                path: '/example',
-                component: '#',
-                redirect: '/example/example-dialog',
-                name: 'Example',
-                status: Mock.Random.integer(0, 1),
-                id: 12,
-                meta: {
-                  title: '综合示例',
-                  icon: 'ep:management',
-                  alwaysShow: true
+              children: [
+                {
+                  path: 'example-dialog',
+                  component: 'views/Example/Dialog/ExampleDialog',
+                  name: 'ExampleDialog',
+                  status: Mock.Random.integer(0, 1),
+                  id: 13,
+                  permission: ['edit', 'add', 'delete'],
+                  meta: {
+                    title: '综合示例-弹窗',
+                    permission: ['edit', 'add']
+                  }
                 },
-                children: [
-                  {
-                    path: 'example-dialog',
-                    component: 'views/Example/Dialog/ExampleDialog',
-                    name: 'ExampleDialog',
-                    preciousMenu: 'Example',
-                    status: Mock.Random.integer(0, 1),
-                    id: 13,
-                    permission: ['edit', 'add', 'delete'],
-                    meta: {
-                      title: '综合示例-弹窗',
-                      permission: ['edit', 'add']
-                    }
-                  },
-                  {
-                    path: 'example-page',
-                    component: 'views/Example/Page/ExamplePage',
-                    name: 'ExamplePage',
-                    preciousMenu: 'Example',
-                    status: Mock.Random.integer(0, 1),
-                    id: 14,
-                    permission: ['edit', 'add', 'delete'],
-                    meta: {
-                      title: '综合示例-页面',
-                      permission: ['edit', 'add']
-                    }
-                  },
-                  {
-                    path: 'example-add',
-                    component: 'views/Example/Page/ExampleAdd',
-                    name: 'ExampleAdd',
-                    preciousMenu: 'Example',
-                    status: Mock.Random.integer(0, 1),
-                    id: 15,
-                    permission: ['edit', 'add', 'delete'],
-                    meta: {
-                      title: '综合示例-新增',
-                      noTagsView: true,
-                      noCache: true,
-                      hidden: true,
-                      canTo: true,
-                      activeMenu: '/example/example-page',
-                      permission: ['delete', 'add']
-                    }
-                  },
-                  {
-                    path: 'example-edit',
-                    component: 'views/Example/Page/ExampleEdit',
-                    name: 'ExampleEdit',
-                    preciousMenu: 'Example',
-                    status: Mock.Random.integer(0, 1),
-                    id: 16,
-                    permission: ['edit', 'add', 'delete'],
-                    meta: {
-                      title: '综合示例-编辑',
-                      noTagsView: true,
-                      noCache: true,
-                      hidden: true,
-                      canTo: true,
-                      activeMenu: '/example/example-page',
-                      permission: ['delete', 'add']
-                    }
-                  },
-                  {
-                    path: 'example-detail',
-                    component: 'views/Example/Page/ExampleDetail',
-                    name: 'ExampleDetail',
-                    preciousMenu: 'Example',
-                    status: Mock.Random.integer(0, 1),
-                    id: 17,
-                    permission: ['edit', 'add', 'delete'],
-                    meta: {
-                      title: '综合示例-详情',
-                      noTagsView: true,
-                      noCache: true,
-                      hidden: true,
-                      canTo: true,
-                      activeMenu: '/example/example-page',
-                      permission: ['delete', 'edit']
-                    }
+                {
+                  path: 'example-page',
+                  component: 'views/Example/Page/ExamplePage',
+                  name: 'ExamplePage',
+                  status: Mock.Random.integer(0, 1),
+                  id: 14,
+                  permission: ['edit', 'add', 'delete'],
+                  meta: {
+                    title: '综合示例-页面',
+                    permission: ['edit', 'add']
+                  }
+                },
+                {
+                  path: 'example-add',
+                  component: 'views/Example/Page/ExampleAdd',
+                  name: 'ExampleAdd',
+                  status: Mock.Random.integer(0, 1),
+                  id: 15,
+                  permission: ['edit', 'add', 'delete'],
+                  meta: {
+                    title: '综合示例-新增',
+                    noTagsView: true,
+                    noCache: true,
+                    hidden: true,
+                    showMainRoute: true,
+                    activeMenu: '/example/example-page',
+                    permission: ['delete', 'add']
+                  }
+                },
+                {
+                  path: 'example-edit',
+                  component: 'views/Example/Page/ExampleEdit',
+                  name: 'ExampleEdit',
+                  status: Mock.Random.integer(0, 1),
+                  id: 16,
+                  permission: ['edit', 'add', 'delete'],
+                  meta: {
+                    title: '综合示例-编辑',
+                    noTagsView: true,
+                    noCache: true,
+                    hidden: true,
+                    showMainRoute: true,
+                    activeMenu: '/example/example-page',
+                    permission: ['delete', 'add']
+                  }
+                },
+                {
+                  path: 'example-detail',
+                  component: 'views/Example/Page/ExampleDetail',
+                  name: 'ExampleDetail',
+                  status: Mock.Random.integer(0, 1),
+                  id: 17,
+                  permission: ['edit', 'add', 'delete'],
+                  meta: {
+                    title: '综合示例-详情',
+                    noTagsView: true,
+                    noCache: true,
+                    hidden: true,
+                    showMainRoute: true,
+                    activeMenu: '/example/example-page',
+                    permission: ['delete', 'edit']
                   }
-                ]
-              }
-            ]
-          }
+                }
+              ]
+            }
+          ]
         }
       }
     }

+ 432 - 87
mock/role/index.ts

@@ -5,7 +5,7 @@ import { toAnyString } from '@/utils'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 const adminList = [
   {
@@ -40,108 +40,385 @@ const adminList = [
     ]
   },
   {
-    path: '/manage',
+    path: '/external-link',
     component: '#',
-    redirect: '/manage/news-page',
-    name: 'Manage',
+    meta: {},
+    name: 'ExternalLink',
+    children: [
+      {
+        path: 'https://element-plus-admin-doc.cn/',
+        name: 'DocumentLink',
+        meta: {
+          title: 'router.document',
+          icon: 'clarity:document-solid'
+        }
+      }
+    ]
+  },
+  {
+    path: '/guide',
+    component: '#',
+    name: 'Guide',
+    meta: {},
+    children: [
+      {
+        path: 'index',
+        component: 'views/Guide/Guide',
+        name: 'GuideDemo',
+        meta: {
+          title: 'router.guide',
+          icon: 'cib:telegram-plane'
+        }
+      }
+    ]
+  },
+  {
+    path: '/components',
+    component: '#',
+    redirect: '/components/form/default-form',
+    name: 'ComponentsDemo',
     meta: {
-      title: '模块管理',
-      icon: 'ep:menu',
+      title: 'router.component',
+      icon: 'bx:bxs-component',
       alwaysShow: true
     },
     children: [
       {
-        path: 'news-page',
-        component: 'views/Manage/News/NewsPage',
-        name: 'NewsPage',
+        path: 'form',
+        component: '##',
+        name: 'Form',
+        meta: {
+          title: 'router.form',
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'default-form',
+            component: 'views/Components/Form/DefaultForm',
+            name: 'DefaultForm',
+            meta: {
+              title: 'router.defaultForm'
+            }
+          },
+          {
+            path: 'use-form',
+            component: 'views/Components/Form/UseFormDemo',
+            name: 'UseForm',
+            meta: {
+              title: 'UseForm'
+            }
+          }
+        ]
+      },
+      {
+        path: 'table',
+        component: '##',
+        redirect: '/components/table/default-table',
+        name: 'TableDemo',
+        meta: {
+          title: 'router.table',
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'default-table',
+            component: 'views/Components/Table/DefaultTable',
+            name: 'DefaultTable',
+            meta: {
+              title: 'router.defaultTable'
+            }
+          },
+          {
+            path: 'use-table',
+            component: 'views/Components/Table/UseTableDemo',
+            name: 'UseTable',
+            meta: {
+              title: 'UseTable'
+            }
+          },
+          {
+            path: 'tree-table',
+            component: 'views/Components/Table/TreeTable',
+            name: 'TreeTable',
+            meta: {
+              title: 'TreeTable'
+            }
+          },
+          {
+            path: 'table-image-preview',
+            component: 'views/Components/Table/TableImagePreview',
+            name: 'TableImagePreview',
+            meta: {
+              title: 'router.PicturePreview'
+            }
+          }
+          // {
+          //   path: 'ref-table',
+          //   component: 'views/Components/Table/RefTable',
+          //   name: 'RefTable',
+          //   meta: {
+          //     title: 'RefTable'
+          //   }
+          // }
+        ]
+      },
+      {
+        path: 'editor-demo',
+        component: '##',
+        redirect: '/components/editor-demo/editor',
+        name: 'EditorDemo',
+        meta: {
+          title: 'router.editor',
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'editor',
+            component: 'views/Components/Editor/Editor',
+            name: 'Editor',
+            meta: {
+              title: 'router.richText'
+            }
+          },
+          {
+            path: 'json-editor',
+            component: 'views/Components/Editor/JsonEditor',
+            name: 'JsonEditor',
+            meta: {
+              title: 'router.jsonEditor'
+            }
+          }
+        ]
+      },
+      {
+        path: 'search',
+        component: 'views/Components/Search',
+        name: 'Search',
         meta: {
-          title: '新闻管理'
+          title: 'router.search'
         }
       },
       {
-        path: 'news-add',
-        component: 'views/Manage/News/NewsAdd',
-        name: 'NewsAdd',
+        path: 'descriptions',
+        component: 'views/Components/Descriptions',
+        name: 'Descriptions',
         meta: {
-          title: '新增新闻',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/news-page'
+          title: 'router.descriptions'
         }
       },
       {
-        path: 'news-edit',
-        component: 'views/Manage/News/NewsEdit',
-        name: 'NewsEdit',
+        path: 'image-viewer',
+        component: 'views/Components/ImageViewer',
+        name: 'ImageViewer',
         meta: {
-          title: '编辑新闻',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/news-page'
+          title: 'router.imageViewer'
         }
       },
       {
-        path: 'news-detail',
-        component: 'views/Manage/News/NewsDetail',
-        name: 'NewsDetail',
+        path: 'dialog',
+        component: 'views/Components/Dialog',
+        name: 'Dialog',
         meta: {
-          title: '新闻详情',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/news-page'
+          title: 'router.dialog'
         }
       },
       {
-        path: 'product-page',
-        component: 'views/Manage/Product/ProductPage',
-        name: 'ProductPage',
+        path: 'icon',
+        component: 'views/Components/Icon',
+        name: 'Icon',
         meta: {
-          title: '产品管理'
+          title: 'router.icon'
         }
       },
       {
-        path: 'product-add',
-        component: 'views/Manage/Product/ProductAdd',
-        name: 'ProductAdd',
+        path: 'icon-picker',
+        component: 'views/Components/IconPicker',
+        name: 'IconPicker',
         meta: {
-          title: '新增产品',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/product-page'
+          title: 'router.iconPicker'
         }
       },
       {
-        path: 'product-edit',
-        component: 'views/Manage/Product/ProductEdit',
-        name: 'ProductEdit',
+        path: 'echart',
+        component: 'views/Components/Echart',
+        name: 'Echart',
         meta: {
-          title: '编辑产品',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/product-page'
+          title: 'router.echart'
         }
       },
       {
-        path: 'product-detail',
-        component: 'views/Manage/Product/ProductDetail',
-        name: 'ProductDetail',
+        path: 'count-to',
+        component: 'views/Components/CountTo',
+        name: 'CountTo',
+        meta: {
+          title: 'router.countTo'
+        }
+      },
+      {
+        path: 'qrcode',
+        component: 'views/Components/Qrcode',
+        name: 'Qrcode',
+        meta: {
+          title: 'router.qrcode'
+        }
+      },
+      {
+        path: 'highlight',
+        component: 'views/Components/Highlight',
+        name: 'Highlight',
+        meta: {
+          title: 'router.highlight'
+        }
+      },
+      {
+        path: 'infotip',
+        component: 'views/Components/Infotip',
+        name: 'Infotip',
+        meta: {
+          title: 'router.infotip'
+        }
+      },
+      {
+        path: 'input-password',
+        component: 'views/Components/InputPassword',
+        name: 'InputPassword',
+        meta: {
+          title: 'router.inputPassword'
+        }
+      }
+    ]
+  },
+  {
+    path: '/function',
+    component: '#',
+    redirect: '/function/multipleTabs',
+    name: 'Function',
+    meta: {
+      title: 'router.function',
+      icon: 'ri:function-fill',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'multipleTabs',
+        component: 'views/Function/MultipleTabs',
+        name: 'MultipleTabs',
+        meta: {
+          title: 'router.multipleTabs'
+        }
+      },
+      {
+        path: 'multipleTabs-demo/:id',
+        component: 'views/Function/MultipleTabsDemo',
+        name: 'MultipleTabsDemo',
         meta: {
-          title: '产品详情',
-          noTagsView: true,
-          noCache: true,
           hidden: true,
-          canTo: true,
-          activeMenu: '/manage/product-page'
+          title: 'router.details',
+          canTo: true
+        }
+      }
+    ]
+  },
+  {
+    path: '/hooks',
+    component: '#',
+    redirect: '/hooks/useWatermark',
+    name: 'Hooks',
+    meta: {
+      title: 'hooks',
+      icon: 'ic:outline-webhook',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'useWatermark',
+        component: 'views/hooks/useWatermark',
+        name: 'UseWatermark',
+        meta: {
+          title: 'useWatermark'
+        }
+      },
+      {
+        path: 'useTagsView',
+        component: 'views/hooks/useTagsView',
+        name: 'UseTagsView',
+        meta: {
+          title: 'useTagsView'
+        }
+      },
+      {
+        path: 'useValidator',
+        component: 'views/hooks/useValidator',
+        name: 'UseValidator',
+        meta: {
+          title: 'useValidator'
+        }
+      },
+      {
+        path: 'useCrudSchemas',
+        component: 'views/hooks/useCrudSchemas',
+        name: 'UseCrudSchemas',
+        meta: {
+          title: 'useCrudSchemas'
+        }
+      }
+    ]
+  },
+  {
+    path: '/level',
+    component: '#',
+    redirect: '/level/menu1/menu1-1/menu1-1-1',
+    name: 'Level',
+    meta: {
+      title: 'router.level',
+      icon: 'carbon:skill-level-advanced'
+    },
+    children: [
+      {
+        path: 'menu1',
+        name: 'Menu1',
+        component: '##',
+        redirect: '/level/menu1/menu1-1/menu1-1-1',
+        meta: {
+          title: 'router.menu1'
+        },
+        children: [
+          {
+            path: 'menu1-1',
+            name: 'Menu11',
+            component: '##',
+            redirect: '/level/menu1/menu1-1/menu1-1-1',
+            meta: {
+              title: 'router.menu11',
+              alwaysShow: true
+            },
+            children: [
+              {
+                path: 'menu1-1-1',
+                name: 'Menu111',
+                component: 'views/Level/Menu111',
+                meta: {
+                  title: 'router.menu111'
+                }
+              }
+            ]
+          },
+          {
+            path: 'menu1-2',
+            name: 'Menu12',
+            component: 'views/Level/Menu12',
+            meta: {
+              title: 'router.menu12'
+            }
+          }
+        ]
+      },
+      {
+        path: 'menu2',
+        name: 'Menu2Demo',
+        component: 'views/Level/Menu2',
+        meta: {
+          title: 'router.menu2'
         }
       }
     ]
@@ -149,7 +426,7 @@ const adminList = [
   {
     path: '/example',
     component: '#',
-    redirect: '/example/example-page',
+    redirect: '/example/example-dialog',
     name: 'Example',
     meta: {
       title: 'router.example',
@@ -157,6 +434,14 @@ const adminList = [
       alwaysShow: true
     },
     children: [
+      {
+        path: 'example-dialog',
+        component: 'views/Example/Dialog/ExampleDialog',
+        name: 'ExampleDialog',
+        meta: {
+          title: 'router.exampleDialog'
+        }
+      },
       {
         path: 'example-page',
         component: 'views/Example/Page/ExamplePage',
@@ -174,7 +459,7 @@ const adminList = [
           noTagsView: true,
           noCache: true,
           hidden: true,
-          canTo: true,
+          showMainRoute: true,
           activeMenu: '/example/example-page'
         }
       },
@@ -187,7 +472,7 @@ const adminList = [
           noTagsView: true,
           noCache: true,
           hidden: true,
-          canTo: true,
+          showMainRoute: true,
           activeMenu: '/example/example-page'
         }
       },
@@ -200,12 +485,49 @@ const adminList = [
           noTagsView: true,
           noCache: true,
           hidden: true,
-          canTo: true,
+          showMainRoute: true,
           activeMenu: '/example/example-page'
         }
       }
     ]
   },
+  {
+    path: '/error',
+    component: '#',
+    redirect: '/error/404',
+    name: 'Error',
+    meta: {
+      title: 'router.errorPage',
+      icon: 'ci:error',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: '404-demo',
+        component: 'views/Error/404',
+        name: '404Demo',
+        meta: {
+          title: '404'
+        }
+      },
+      {
+        path: '403-demo',
+        component: 'views/Error/403',
+        name: '403Demo',
+        meta: {
+          title: '403'
+        }
+      },
+      {
+        path: '500-demo',
+        component: 'views/Error/500',
+        name: '500Demo',
+        meta: {
+          title: '500'
+        }
+      }
+    ]
+  },
   {
     path: '/authorization',
     component: '#',
@@ -254,8 +576,7 @@ const adminList = [
         component: 'views/Authorization/Test/Test',
         name: 'Test',
         meta: {
-          title: 'router.permission',
-          permission: ['add', 'edit', 'delete']
+          title: 'router.permission'
         }
       }
     ]
@@ -283,11 +604,13 @@ const testList: string[] = [
   '/components/table/ref-table',
   '/components/editor-demo',
   '/components/editor-demo/editor',
+  '/components/editor-demo/json-editor',
   '/components/search',
   '/components/descriptions',
   '/components/image-viewer',
   '/components/dialog',
   '/components/icon',
+  '/components/iconPicker',
   '/components/echart',
   '/components/count-to',
   '/components/qrcode',
@@ -301,7 +624,8 @@ const testList: string[] = [
   '/hooks',
   '/hooks/useWatermark',
   '/hooks/useTagsView',
-  // '/hooks/useCrudSchemas',
+  '/hooks/useValidator',
+  '/hooks/useCrudSchemas',
   '/level',
   '/level/menu1',
   '/level/menu1/menu1-1',
@@ -516,7 +840,7 @@ const menus = [
             noTagsView: true,
             noCache: true,
             hidden: true,
-            canTo: true,
+            showMainRoute: true,
             activeMenu: '/example/example-page',
             permission: ['edit', 'add', 'delete']
           }
@@ -533,7 +857,7 @@ const menus = [
             noTagsView: true,
             noCache: true,
             hidden: true,
-            canTo: true,
+            showMainRoute: true,
             activeMenu: '/example/example-page',
             permission: ['edit', 'add', 'delete']
           }
@@ -550,7 +874,7 @@ const menus = [
             noTagsView: true,
             noCache: true,
             hidden: true,
-            canTo: true,
+            showMainRoute: true,
             activeMenu: '/example/example-page',
             permission: ['edit', 'add', 'delete']
           }
@@ -724,7 +1048,7 @@ const menus = [
             noTagsView: true,
             noCache: true,
             hidden: true,
-            canTo: true,
+            showMainRoute: true,
             activeMenu: '/example/example-page',
             permission: ['edit', 'add', 'delete']
           }
@@ -755,28 +1079,49 @@ export default [
     url: '/role/list',
     method: 'get',
     timeout,
-    response: ({ query }) => {
-      const { roleName } = query
+    response: () => {
       return {
+        code: code,
+        data: adminList
+      }
+    }
+  },
+  {
+    url: '/role/table',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
         data: {
-          code: code,
-          data: roleName === 'admin' ? adminList : testList
+          list: List,
+          total: 4
         }
       }
     }
   },
+  // 列表接口
+  {
+    url: '/role/list2',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
+        data: testList
+      }
+    }
+  },
   {
     url: '/role/table',
     method: 'get',
     timeout,
     response: () => {
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            list: List,
-            total: 4
-          }
+          list: List,
+          total: 4
         }
       }
     }

+ 15 - 27
mock/table/index.ts

@@ -150,12 +150,10 @@ export default [
         (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
       )
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            total: mockList.length,
-            list: pageList
-          }
+          total: mockList.length,
+          list: pageList
         }
       }
     }
@@ -175,12 +173,10 @@ export default [
         (_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
       )
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            total: mockList.length,
-            list: pageList
-          }
+          total: mockList.length,
+          list: pageList
         }
       }
     }
@@ -198,10 +194,8 @@ export default [
           })
         ].concat(List)
         return {
-          data: {
-            code: code,
-            data: 'success'
-          }
+          code: code,
+          data: 'success'
         }
       } else {
         List.map((item) => {
@@ -212,10 +206,8 @@ export default [
           }
         })
         return {
-          data: {
-            code: code,
-            data: 'success'
-          }
+          code: code,
+          data: 'success'
         }
       }
     }
@@ -229,10 +221,8 @@ export default [
       for (const example of List) {
         if (example.id === id) {
           return {
-            data: {
-              code: code,
-              data: example
-            }
+            code: code,
+            data: example
           }
         }
       }
@@ -246,7 +236,7 @@ export default [
       const ids = body.ids
       if (!ids) {
         return {
-          code: '500',
+          code: 500,
           message: '请选择需要删除的数据'
         }
       } else {
@@ -257,10 +247,8 @@ export default [
           }
         }
         return {
-          data: {
-            code: code,
-            data: 'success'
-          }
+          code: code,
+          data: 'success'
         }
       }
     }

+ 7 - 13
mock/user/index.ts

@@ -45,12 +45,10 @@ export default [
       )
 
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            total: mockList.length,
-            list: pageList
-          }
+          total: mockList.length,
+          list: pageList
         }
       }
     }
@@ -67,10 +65,8 @@ export default [
         if (user.username === data.username && user.password === data.password) {
           hasUser = true
           return {
-            data: {
-              code: code,
-              data: user
-            }
+            code: code,
+            data: user
           }
         }
       }
@@ -89,10 +85,8 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: null
-        }
+        code: code,
+        data: null
       }
     }
   }

+ 111 - 121
mock/workplace/index.ts

@@ -13,13 +13,11 @@ export default [
     timeout,
     response: () => {
       return {
+        code: code,
         data: {
-          code: code,
-          data: {
-            project: 40,
-            access: 2340,
-            todo: 10
-          }
+          project: 40,
+          access: 2340,
+          todo: 10
         }
       }
     }
@@ -31,53 +29,51 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            {
-              name: 'Github',
-              icon: 'akar-icons:github-fill',
-              message: 'workplace.introduction',
-              personal: 'Archer',
-              time: new Date()
-            },
-            {
-              name: 'Vue',
-              icon: 'logos:vue',
-              message: 'workplace.introduction',
-              personal: 'Archer',
-              time: new Date()
-            },
-            {
-              name: 'Angular',
-              icon: 'logos:angular-icon',
-              message: 'workplace.introduction',
-              personal: 'Archer',
-              time: new Date()
-            },
-            {
-              name: 'React',
-              icon: 'logos:react',
-              message: 'workplace.introduction',
-              personal: 'Archer',
-              time: new Date()
-            },
-            {
-              name: 'Webpack',
-              icon: 'logos:webpack',
-              message: 'workplace.introduction',
-              personal: 'Archer',
-              time: new Date()
-            },
-            {
-              name: 'Vite',
-              icon: 'vscode-icons:file-type-vite',
-              message: 'workplace.introduction',
-              personal: 'Archer',
-              time: new Date()
-            }
-          ]
-        }
+        code: code,
+        data: [
+          {
+            name: 'Github',
+            icon: 'akar-icons:github-fill',
+            message: 'workplace.introduction',
+            personal: 'Archer',
+            time: new Date()
+          },
+          {
+            name: 'Vue',
+            icon: 'logos:vue',
+            message: 'workplace.introduction',
+            personal: 'Archer',
+            time: new Date()
+          },
+          {
+            name: 'Angular',
+            icon: 'logos:angular-icon',
+            message: 'workplace.introduction',
+            personal: 'Archer',
+            time: new Date()
+          },
+          {
+            name: 'React',
+            icon: 'logos:react',
+            message: 'workplace.introduction',
+            personal: 'Archer',
+            time: new Date()
+          },
+          {
+            name: 'Webpack',
+            icon: 'logos:webpack',
+            message: 'workplace.introduction',
+            personal: 'Archer',
+            time: new Date()
+          },
+          {
+            name: 'Vite',
+            icon: 'vscode-icons:file-type-vite',
+            message: 'workplace.introduction',
+            personal: 'Archer',
+            time: new Date()
+          }
+        ]
       }
     }
   },
@@ -88,35 +84,33 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            {
-              keys: ['workplace.push', 'Github'],
-              time: new Date()
-            },
-            {
-              keys: ['workplace.push', 'Github'],
-              time: new Date()
-            },
-            {
-              keys: ['workplace.push', 'Github'],
-              time: new Date()
-            },
-            {
-              keys: ['workplace.push', 'Github'],
-              time: new Date()
-            },
-            {
-              keys: ['workplace.push', 'Github'],
-              time: new Date()
-            },
-            {
-              keys: ['workplace.push', 'Github'],
-              time: new Date()
-            }
-          ]
-        }
+        code: code,
+        data: [
+          {
+            keys: ['workplace.push', 'Github'],
+            time: new Date()
+          },
+          {
+            keys: ['workplace.push', 'Github'],
+            time: new Date()
+          },
+          {
+            keys: ['workplace.push', 'Github'],
+            time: new Date()
+          },
+          {
+            keys: ['workplace.push', 'Github'],
+            time: new Date()
+          },
+          {
+            keys: ['workplace.push', 'Github'],
+            time: new Date()
+          },
+          {
+            keys: ['workplace.push', 'Github'],
+            time: new Date()
+          }
+        ]
       }
     }
   },
@@ -127,35 +121,33 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            {
-              name: 'Github',
-              icon: 'akar-icons:github-fill'
-            },
-            {
-              name: 'Vue',
-              icon: 'logos:vue'
-            },
-            {
-              name: 'Angular',
-              icon: 'logos:angular-icon'
-            },
-            {
-              name: 'React',
-              icon: 'logos:react'
-            },
-            {
-              name: 'Webpack',
-              icon: 'logos:webpack'
-            },
-            {
-              name: 'Vite',
-              icon: 'vscode-icons:file-type-vite'
-            }
-          ]
-        }
+        code: code,
+        data: [
+          {
+            name: 'Github',
+            icon: 'akar-icons:github-fill'
+          },
+          {
+            name: 'Vue',
+            icon: 'logos:vue'
+          },
+          {
+            name: 'Angular',
+            icon: 'logos:angular-icon'
+          },
+          {
+            name: 'React',
+            icon: 'logos:react'
+          },
+          {
+            name: 'Webpack',
+            icon: 'logos:webpack'
+          },
+          {
+            name: 'Vite',
+            icon: 'vscode-icons:file-type-vite'
+          }
+        ]
       }
     }
   },
@@ -166,16 +158,14 @@ export default [
     timeout,
     response: () => {
       return {
-        data: {
-          code: code,
-          data: [
-            { name: 'workplace.quote', max: 65, personal: 42, team: 50 },
-            { name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
-            { name: 'workplace.hot', max: 300, personal: 20, team: 28 },
-            { name: 'workplace.yield', max: 130, personal: 35, team: 35 },
-            { name: 'workplace.follow', max: 100, personal: 80, team: 90 }
-          ]
-        }
+        code: code,
+        data: [
+          { name: 'workplace.quote', max: 65, personal: 42, team: 50 },
+          { name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
+          { name: 'workplace.hot', max: 300, personal: 20, team: 28 },
+          { name: 'workplace.yield', max: 130, personal: 35, team: 35 },
+          { name: 'workplace.follow', max: 100, personal: 80, team: 90 }
+        ]
       }
     }
   }

+ 11 - 7
package.json

@@ -1,6 +1,6 @@
 {
   "name": "vue-element-plus-admin",
-  "version": "2.0.0",
+  "version": "2.2.0",
   "description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
   "author": "Archer <502431556@qq.com>",
   "private": false,
@@ -23,7 +23,8 @@
     "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
     "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
     "prepare": "husky install",
-    "p": "plop"
+    "p": "plop",
+    "icon": "esno ./scripts/icon.ts"
   },
   "dependencies": {
     "@iconify/iconify": "^3.1.1",
@@ -35,10 +36,10 @@
     "animate.css": "^4.1.1",
     "axios": "^1.4.0",
     "dayjs": "^1.11.9",
+    "driver.js": "^1.2.1",
     "echarts": "^5.4.3",
     "echarts-wordcloud": "^2.1.0",
     "element-plus": "^2.3.9",
-    "intro.js": "^7.0.1",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
     "mockjs": "^1.1.0",
@@ -47,10 +48,10 @@
     "pinia-plugin-persist": "^1.0.0",
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
-    "sortablejs": "^1.15.0",
     "url": "^0.11.1",
     "vue": "3.3.4",
     "vue-i18n": "9.2.2",
+    "vue-json-pretty": "^2.2.4",
     "vue-router": "^4.2.4",
     "vue-types": "^5.1.1"
   },
@@ -60,7 +61,8 @@
     "@iconify/json": "^2.2.101",
     "@intlify/unplugin-vue-i18n": "^0.12.2",
     "@purge-icons/generated": "^0.9.0",
-    "@types/intro.js": "^5.1.1",
+    "@types/fs-extra": "^11.0.2",
+    "@types/inquirer": "^9.0.3",
     "@types/lodash-es": "^4.17.8",
     "@types/node": "^20.4.10",
     "@types/nprogress": "^0.2.0",
@@ -73,15 +75,18 @@
     "@vitejs/plugin-legacy": "^4.1.1",
     "@vitejs/plugin-vue": "^4.2.3",
     "@vitejs/plugin-vue-jsx": "^3.0.1",
-    "@vue-macros/volar": "^0.13.3",
     "autoprefixer": "^10.4.14",
+    "chalk": "^5.3.0",
     "consola": "^3.2.3",
     "eslint": "^8.47.0",
     "eslint-config-prettier": "^9.0.0",
     "eslint-define-config": "^1.23.0",
     "eslint-plugin-prettier": "^5.0.0",
     "eslint-plugin-vue": "^9.17.0",
+    "esno": "^0.17.0",
+    "fs-extra": "^11.1.1",
     "husky": "^8.0.3",
+    "inquirer": "^9.2.11",
     "less": "^4.2.0",
     "lint-staged": "^13.2.3",
     "plop": "^3.1.2",
@@ -99,7 +104,6 @@
     "terser": "^5.19.2",
     "typescript": "5.1.6",
     "unocss": "^0.55.0",
-    "unplugin-vue-define-options": "^1.3.15",
     "vite": "4.4.9",
     "vite-plugin-ejs": "^1.6.4",
     "vite-plugin-eslint": "^1.8.1",

+ 71 - 0
scripts/icon.ts

@@ -0,0 +1,71 @@
+import path from 'path'
+import fs from 'fs-extra'
+import inquirer from 'inquirer'
+import chalk from 'chalk'
+import pkg from '../package.json'
+
+interface Icon {
+  name: string
+  prefix: string
+  icons: string[]
+}
+
+async function generateIcon() {
+  const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json')
+
+  const raw = await fs.readJSON(path.join(dir, 'collections.json'))
+
+  const collections = Object.entries(raw).map(([id, v]) => ({
+    ...(v as any),
+    id
+  }))
+
+  const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }))
+
+  inquirer
+    .prompt([
+      // {
+      //   type: 'list',
+      //   name: 'useType',
+      //   choices: [
+      //     { key: 'local', value: 'local', name: 'Local' },
+      //     { key: 'onLine', value: 'onLine', name: 'OnLine' }
+      //   ],
+      //   message: 'How to use icons?'
+      // },
+      {
+        type: 'list',
+        name: 'iconSet',
+        choices: choices,
+        message: 'Select the icon set that needs to be generated?'
+      }
+    ])
+    // ↓命令行问答的答案
+    .then(async (answers) => {
+      const { iconSet } = answers
+      // const isOnLine = useType === 'onLine'
+      const outputDir = path.resolve(process.cwd(), 'src/components/IconPicker/src/data')
+      fs.ensureDir(outputDir)
+      const genCollections = collections.filter((item) => [iconSet].includes(item.id))
+      const prefixSet: string[] = []
+      for (const info of genCollections) {
+        const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`))
+        if (data) {
+          const { prefix } = data
+          const icons = Object.keys(data.icons).map((item) => `${prefix}:${item}`)
+
+          await fs.writeFileSync(
+            path.join('src/components/IconPicker/src/data', `icons.${prefix}.ts`),
+            `export default ${JSON.stringify({ name: info.name, prefix, icons })}`
+          )
+          // ↓分类处理完成,push类型名称
+          prefixSet.push(prefix)
+        }
+      }
+      console.log(
+        `✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`
+      )
+    })
+}
+
+generateIcon()

+ 1 - 1
src/api/login/index.ts

@@ -30,5 +30,5 @@ export const getAdminRoleApi = (
 }
 
 export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
-  return request.get({ url: '/role/list', params })
+  return request.get({ url: '/role/list2', params })
 }

+ 4 - 4
src/components/Breadcrumb/src/Breadcrumb.vue

@@ -5,7 +5,7 @@ import { useRouter } from 'vue-router'
 import { usePermissionStore } from '@/store/modules/permission'
 import { filterBreadcrumb } from './helper'
 import { filter, treeToList } from '@/utils/tree'
-import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Icon } from '@/components/Icon'
 import { useAppStore } from '@/store/modules/app'
@@ -47,15 +47,15 @@ export default defineComponent({
       const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
       return breadcrumbList.map((v) => {
         const disabled = !v.redirect || v.redirect === 'noredirect'
-        const meta = v.meta as RouteMeta
+        const meta = v.meta
         return (
           <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
             {meta?.icon && breadcrumbIcon.value ? (
               <>
-                <Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title)}
+                <Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
               </>
             ) : (
-              t(v?.meta?.title)
+              t(v?.meta?.title || '')
             )}
           </ElBreadcrumbItem>
         )

+ 1 - 2
src/components/Breadcrumb/src/helper.ts

@@ -1,5 +1,4 @@
 import { pathResolve } from '@/utils/routerHelper'
-import type { RouteMeta } from 'vue-router'
 
 export const filterBreadcrumb = (
   routes: AppRouteRecordRaw[],
@@ -8,7 +7,7 @@ export const filterBreadcrumb = (
   const res: AppRouteRecordRaw[] = []
 
   for (const route of routes) {
-    const meta = route?.meta as RouteMeta
+    const meta = route?.meta
     if (meta.hidden && !meta.canTo) {
       continue
     }

+ 1 - 1
src/components/Descriptions/src/Descriptions.vue

@@ -102,7 +102,7 @@ export default defineComponent({
           ) : null}
 
           <ElCollapseTransition>
-            <div v-show={unref(show)} class={[`${prefixCls}-content`]}>
+            <div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
               <ElDescriptions {...unref(getBindValue)}>
                 {{
                   extra: () => (slots['extra'] ? slots['extra']() : props.extra),

+ 3 - 1
src/components/Form/src/helper/componentMap.ts

@@ -22,6 +22,7 @@ import {
 } from 'element-plus'
 import { InputPassword } from '@/components/InputPassword'
 import { Editor } from '@/components/Editor'
+import { JsonEditor } from '@/components/JsonEditor'
 import { ComponentName } from '../types'
 
 const componentMap: Recordable<Component, ComponentName> = {
@@ -47,7 +48,8 @@ const componentMap: Recordable<Component, ComponentName> = {
   InputPassword: InputPassword,
   Editor: Editor,
   TreeSelect: ElTreeSelect,
-  Upload: ElUpload
+  Upload: ElUpload,
+  JsonEditor: JsonEditor
 }
 
 export { componentMap }

+ 4 - 1
src/components/Form/src/types/index.ts

@@ -21,6 +21,7 @@ import {
   UploadProps
 } from 'element-plus'
 import { IEditorConfig } from '@wangeditor/editor'
+import { JsonEditorProps } from '@/components/JsonEditor'
 import { CSSProperties } from 'vue'
 
 export interface PlaceholderModel {
@@ -53,7 +54,8 @@ export enum ComponentNameEnum {
   INPUT_PASSWORD = 'InputPassword',
   EDITOR = 'Editor',
   TREE_SELECT = 'TreeSelect',
-  UPLOAD = 'Upload'
+  UPLOAD = 'Upload',
+  JSON_EDITOR = 'JsonEditor'
 }
 
 type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
@@ -620,6 +622,7 @@ export interface FormSchema {
     | InputPasswordComponentProps
     | TreeSelectComponentProps
     | UploadComponentProps
+    | JsonEditorProps
     | any
 
   /**

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

@@ -0,0 +1,3 @@
+import IconPicker from './src/IconPicker.vue'
+
+export { IconPicker }

+ 168 - 0
src/components/IconPicker/src/IconPicker.vue

@@ -0,0 +1,168 @@
+<script setup lang="ts">
+import epIcons from './data/icons.ep'
+import antIcons from './data/icons.ant-design'
+import tIcons from './data/icons.tdesign'
+import { useDesign } from '@/hooks/web/useDesign'
+import { ElInput, ElPopover, ElScrollbar, ElTabs, ElTabPane, ElPagination } from 'element-plus'
+import { useAppStore } from '@/store/modules/app'
+import { computed, CSSProperties, ref, unref, watch } from 'vue'
+import { nextTick } from 'vue'
+
+const modelValue = defineModel<string>()
+
+const appStore = useAppStore()
+
+const size = computed(() => appStore.getCurrentSize)
+
+const iconSize = computed(() => {
+  return size.value === 'small'
+    ? 'var(--el-component-size-small)'
+    : size.value === 'large'
+    ? 'var(--el-component-size-large)'
+    : 'var(--el-component-size)'
+})
+
+const iconWrapStyle = computed((): CSSProperties => {
+  return {
+    width: iconSize.value,
+    height: iconSize.value,
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    boxShadow: '0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset',
+    position: 'relative',
+    left: '-1px',
+    cursor: 'pointer'
+  }
+})
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('icon-picker')
+
+const icons = [epIcons, antIcons, tIcons]
+
+const iconName = ref(icons[0].prefix)
+
+const currentIconNameIndex = computed(() => {
+  return icons.findIndex((item) => item.prefix === iconName.value)
+})
+
+const tabChange = () => {
+  currentPage.value = 1
+}
+
+const pageSize = ref(63)
+
+const currentPage = ref(1)
+
+const filterIcons = (icons: string[]) => {
+  const start = (currentPage.value - 1) * pageSize.value
+  const end = currentPage.value * pageSize.value
+  return icons.slice(start, end)
+}
+
+watch(
+  () => modelValue.value,
+  (val) => {
+    init(val)
+  },
+  {
+    immediate: true
+  }
+)
+
+async function init(icon?: string) {
+  if (!icon) return
+  const iconInfo = icon.split(':')
+  iconName.value = iconInfo[0]
+  const wrapIndex = icons.findIndex((item) => item.prefix === iconInfo[0])
+  // 查询当前icon的索引
+  const index = icons[wrapIndex].icons.findIndex((item) => item === icon)
+  // 计算当前icon的页码
+  await nextTick()
+  currentPage.value = Math.ceil((index + 1) / pageSize.value)
+}
+
+const popoverShow = () => {
+  init(unref(modelValue))
+}
+
+const iconSelect = (icon: string) => {
+  modelValue.value = icon
+}
+</script>
+
+<template>
+  <div :class="prefixCls" class="flex justify-center items-center box">
+    <ElInput disabled v-model="modelValue" />
+    <ElPopover
+      placement="bottom"
+      trigger="click"
+      :width="450"
+      popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; height: 400px;"
+      @show="popoverShow"
+    >
+      <template #reference>
+        <div :style="iconWrapStyle">
+          <Icon v-if="modelValue" :icon="modelValue" />
+        </div>
+      </template>
+      <ElScrollbar class="h-[calc(100%-50px)]!">
+        <ElTabs tab-position="left" v-model="iconName" @tab-change="tabChange">
+          <ElTabPane v-for="item in icons" :key="item.name" :label="item.name" :name="item.prefix">
+            <div class="flex flex-wrap box-border">
+              <div
+                v-for="icon in filterIcons(item.icons)"
+                :key="icon"
+                :style="{
+                  width: iconSize,
+                  height: iconSize,
+                  display: 'flex',
+                  alignItems: 'center',
+                  justifyContent: 'center',
+                  cursor: 'pointer',
+                  border: `1px solid ${
+                    icon === modelValue ? 'var(--el-color-primary)' : 'var(--el-border-color)'
+                  }`,
+                  boxSizing: 'border-box',
+                  margin: '2px'
+                }"
+                @click="iconSelect(icon)"
+              >
+                <Icon
+                  :icon="icon"
+                  :color="icon === modelValue ? 'var(--el-color-primary)' : 'inherit'"
+                />
+              </div>
+            </div>
+          </ElTabPane>
+        </ElTabs>
+      </ElScrollbar>
+      <div
+        class="h-50px absolute bottom-0 left-0 flex items-center pl-[var(--el-popover-padding)] pr-[var(--el-popover-padding)]"
+      >
+        <ElPagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :pager-count="5"
+          small
+          :page-sizes="[100, 200, 300, 400]"
+          layout="total, prev, pager, next, jumper"
+          :total="icons[currentIconNameIndex].icons.length"
+        />
+      </div>
+    </ElPopover>
+  </div>
+</template>
+
+<style lang="less" scoped>
+@prefix-cls: ~'@{namespace}-icon-picker';
+
+.@{prefix-cls} {
+  :deep(.@{elNamespace}-input__wrapper) {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+}
+</style>

+ 795 - 0
src/components/IconPicker/src/data/icons.ant-design.ts

@@ -0,0 +1,795 @@
+export default {
+  name: 'Ant Design Icons',
+  prefix: 'ant-design',
+  icons: [
+    'ant-design:account-book-filled',
+    'ant-design:account-book-outlined',
+    'ant-design:account-book-twotone',
+    'ant-design:aim-outlined',
+    'ant-design:alert-filled',
+    'ant-design:alert-outlined',
+    'ant-design:alert-twotone',
+    'ant-design:alibaba-outlined',
+    'ant-design:align-center-outlined',
+    'ant-design:align-left-outlined',
+    'ant-design:align-right-outlined',
+    'ant-design:alipay-circle-filled',
+    'ant-design:alipay-circle-outlined',
+    'ant-design:alipay-outlined',
+    'ant-design:alipay-square-filled',
+    'ant-design:aliwangwang-filled',
+    'ant-design:aliwangwang-outlined',
+    'ant-design:aliyun-outlined',
+    'ant-design:amazon-circle-filled',
+    'ant-design:amazon-outlined',
+    'ant-design:amazon-square-filled',
+    'ant-design:android-filled',
+    'ant-design:android-outlined',
+    'ant-design:ant-cloud-outlined',
+    'ant-design:ant-design-outlined',
+    'ant-design:apartment-outlined',
+    'ant-design:api-filled',
+    'ant-design:api-outlined',
+    'ant-design:api-twotone',
+    'ant-design:apple-filled',
+    'ant-design:apple-outlined',
+    'ant-design:appstore-add-outlined',
+    'ant-design:appstore-filled',
+    'ant-design:appstore-outlined',
+    'ant-design:appstore-twotone',
+    'ant-design:area-chart-outlined',
+    'ant-design:arrow-down-outlined',
+    'ant-design:arrow-left-outlined',
+    'ant-design:arrow-right-outlined',
+    'ant-design:arrow-up-outlined',
+    'ant-design:arrows-alt-outlined',
+    'ant-design:audio-filled',
+    'ant-design:audio-muted-outlined',
+    'ant-design:audio-outlined',
+    'ant-design:audio-twotone',
+    'ant-design:audit-outlined',
+    'ant-design:backward-filled',
+    'ant-design:backward-outlined',
+    'ant-design:bank-filled',
+    'ant-design:bank-outlined',
+    'ant-design:bank-twotone',
+    'ant-design:bar-chart-outlined',
+    'ant-design:barcode-outlined',
+    'ant-design:bars-outlined',
+    'ant-design:behance-circle-filled',
+    'ant-design:behance-outlined',
+    'ant-design:behance-square-filled',
+    'ant-design:behance-square-outlined',
+    'ant-design:bell-filled',
+    'ant-design:bell-outlined',
+    'ant-design:bell-twotone',
+    'ant-design:bg-colors-outlined',
+    'ant-design:block-outlined',
+    'ant-design:bold-outlined',
+    'ant-design:book-filled',
+    'ant-design:book-outlined',
+    'ant-design:book-twotone',
+    'ant-design:border-bottom-outlined',
+    'ant-design:border-horizontal-outlined',
+    'ant-design:border-inner-outlined',
+    'ant-design:border-left-outlined',
+    'ant-design:border-outer-outlined',
+    'ant-design:border-outlined',
+    'ant-design:border-right-outlined',
+    'ant-design:border-top-outlined',
+    'ant-design:border-verticle-outlined',
+    'ant-design:borderless-table-outlined',
+    'ant-design:box-plot-filled',
+    'ant-design:box-plot-outlined',
+    'ant-design:box-plot-twotone',
+    'ant-design:branches-outlined',
+    'ant-design:bug-filled',
+    'ant-design:bug-outlined',
+    'ant-design:bug-twotone',
+    'ant-design:build-filled',
+    'ant-design:build-outlined',
+    'ant-design:build-twotone',
+    'ant-design:bulb-filled',
+    'ant-design:bulb-outlined',
+    'ant-design:bulb-twotone',
+    'ant-design:calculator-filled',
+    'ant-design:calculator-outlined',
+    'ant-design:calculator-twotone',
+    'ant-design:calendar-filled',
+    'ant-design:calendar-outlined',
+    'ant-design:calendar-twotone',
+    'ant-design:camera-filled',
+    'ant-design:camera-outlined',
+    'ant-design:camera-twotone',
+    'ant-design:car-filled',
+    'ant-design:car-outlined',
+    'ant-design:car-twotone',
+    'ant-design:caret-down-filled',
+    'ant-design:caret-down-outlined',
+    'ant-design:caret-left-filled',
+    'ant-design:caret-left-outlined',
+    'ant-design:caret-right-filled',
+    'ant-design:caret-right-outlined',
+    'ant-design:caret-up-filled',
+    'ant-design:caret-up-outlined',
+    'ant-design:carry-out-filled',
+    'ant-design:carry-out-outlined',
+    'ant-design:carry-out-twotone',
+    'ant-design:check-circle-filled',
+    'ant-design:check-circle-outlined',
+    'ant-design:check-circle-twotone',
+    'ant-design:check-outlined',
+    'ant-design:check-square-filled',
+    'ant-design:check-square-outlined',
+    'ant-design:check-square-twotone',
+    'ant-design:chrome-filled',
+    'ant-design:chrome-outlined',
+    'ant-design:ci-circle-filled',
+    'ant-design:ci-circle-outlined',
+    'ant-design:ci-circle-twotone',
+    'ant-design:ci-outlined',
+    'ant-design:ci-twotone',
+    'ant-design:clear-outlined',
+    'ant-design:clock-circle-filled',
+    'ant-design:clock-circle-outlined',
+    'ant-design:clock-circle-twotone',
+    'ant-design:close-circle-filled',
+    'ant-design:close-circle-outlined',
+    'ant-design:close-circle-twotone',
+    'ant-design:close-outlined',
+    'ant-design:close-square-filled',
+    'ant-design:close-square-outlined',
+    'ant-design:close-square-twotone',
+    'ant-design:cloud-download-outlined',
+    'ant-design:cloud-filled',
+    'ant-design:cloud-outlined',
+    'ant-design:cloud-server-outlined',
+    'ant-design:cloud-sync-outlined',
+    'ant-design:cloud-twotone',
+    'ant-design:cloud-upload-outlined',
+    'ant-design:cluster-outlined',
+    'ant-design:code-filled',
+    'ant-design:code-outlined',
+    'ant-design:code-sandbox-circle-filled',
+    'ant-design:code-sandbox-outlined',
+    'ant-design:code-sandbox-square-filled',
+    'ant-design:code-twotone',
+    'ant-design:codepen-circle-filled',
+    'ant-design:codepen-circle-outlined',
+    'ant-design:codepen-outlined',
+    'ant-design:codepen-square-filled',
+    'ant-design:coffee-outlined',
+    'ant-design:column-height-outlined',
+    'ant-design:column-width-outlined',
+    'ant-design:comment-outlined',
+    'ant-design:compass-filled',
+    'ant-design:compass-outlined',
+    'ant-design:compass-twotone',
+    'ant-design:compress-outlined',
+    'ant-design:console-sql-outlined',
+    'ant-design:contacts-filled',
+    'ant-design:contacts-outlined',
+    'ant-design:contacts-twotone',
+    'ant-design:container-filled',
+    'ant-design:container-outlined',
+    'ant-design:container-twotone',
+    'ant-design:control-filled',
+    'ant-design:control-outlined',
+    'ant-design:control-twotone',
+    'ant-design:copy-filled',
+    'ant-design:copy-outlined',
+    'ant-design:copy-twotone',
+    'ant-design:copyright-circle-filled',
+    'ant-design:copyright-circle-outlined',
+    'ant-design:copyright-circle-twotone',
+    'ant-design:copyright-outlined',
+    'ant-design:copyright-twotone',
+    'ant-design:credit-card-filled',
+    'ant-design:credit-card-outlined',
+    'ant-design:credit-card-twotone',
+    'ant-design:crown-filled',
+    'ant-design:crown-outlined',
+    'ant-design:crown-twotone',
+    'ant-design:customer-service-filled',
+    'ant-design:customer-service-outlined',
+    'ant-design:customer-service-twotone',
+    'ant-design:dash-outlined',
+    'ant-design:dashboard-filled',
+    'ant-design:dashboard-outlined',
+    'ant-design:dashboard-twotone',
+    'ant-design:database-filled',
+    'ant-design:database-outlined',
+    'ant-design:database-twotone',
+    'ant-design:delete-column-outlined',
+    'ant-design:delete-filled',
+    'ant-design:delete-outlined',
+    'ant-design:delete-row-outlined',
+    'ant-design:delete-twotone',
+    'ant-design:delivered-procedure-outlined',
+    'ant-design:deployment-unit-outlined',
+    'ant-design:desktop-outlined',
+    'ant-design:diff-filled',
+    'ant-design:diff-outlined',
+    'ant-design:diff-twotone',
+    'ant-design:dingding-outlined',
+    'ant-design:dingtalk-circle-filled',
+    'ant-design:dingtalk-outlined',
+    'ant-design:dingtalk-square-filled',
+    'ant-design:disconnect-outlined',
+    'ant-design:dislike-filled',
+    'ant-design:dislike-outlined',
+    'ant-design:dislike-twotone',
+    'ant-design:dollar-circle-filled',
+    'ant-design:dollar-circle-outlined',
+    'ant-design:dollar-circle-twotone',
+    'ant-design:dollar-outlined',
+    'ant-design:dollar-twotone',
+    'ant-design:dot-chart-outlined',
+    'ant-design:double-left-outlined',
+    'ant-design:double-right-outlined',
+    'ant-design:down-circle-filled',
+    'ant-design:down-circle-outlined',
+    'ant-design:down-circle-twotone',
+    'ant-design:down-outlined',
+    'ant-design:down-square-filled',
+    'ant-design:down-square-outlined',
+    'ant-design:down-square-twotone',
+    'ant-design:download-outlined',
+    'ant-design:drag-outlined',
+    'ant-design:dribbble-circle-filled',
+    'ant-design:dribbble-outlined',
+    'ant-design:dribbble-square-filled',
+    'ant-design:dribbble-square-outlined',
+    'ant-design:dropbox-circle-filled',
+    'ant-design:dropbox-outlined',
+    'ant-design:dropbox-square-filled',
+    'ant-design:edit-filled',
+    'ant-design:edit-outlined',
+    'ant-design:edit-twotone',
+    'ant-design:ellipsis-outlined',
+    'ant-design:enter-outlined',
+    'ant-design:environment-filled',
+    'ant-design:environment-outlined',
+    'ant-design:environment-twotone',
+    'ant-design:euro-circle-filled',
+    'ant-design:euro-circle-outlined',
+    'ant-design:euro-circle-twotone',
+    'ant-design:euro-outlined',
+    'ant-design:euro-twotone',
+    'ant-design:exception-outlined',
+    'ant-design:exclamation-circle-filled',
+    'ant-design:exclamation-circle-outlined',
+    'ant-design:exclamation-circle-twotone',
+    'ant-design:exclamation-outlined',
+    'ant-design:expand-alt-outlined',
+    'ant-design:expand-outlined',
+    'ant-design:experiment-filled',
+    'ant-design:experiment-outlined',
+    'ant-design:experiment-twotone',
+    'ant-design:export-outlined',
+    'ant-design:eye-filled',
+    'ant-design:eye-invisible-filled',
+    'ant-design:eye-invisible-outlined',
+    'ant-design:eye-invisible-twotone',
+    'ant-design:eye-outlined',
+    'ant-design:eye-twotone',
+    'ant-design:facebook-filled',
+    'ant-design:facebook-outlined',
+    'ant-design:fall-outlined',
+    'ant-design:fast-backward-filled',
+    'ant-design:fast-backward-outlined',
+    'ant-design:fast-forward-filled',
+    'ant-design:fast-forward-outlined',
+    'ant-design:field-binary-outlined',
+    'ant-design:field-number-outlined',
+    'ant-design:field-string-outlined',
+    'ant-design:field-time-outlined',
+    'ant-design:file-add-filled',
+    'ant-design:file-add-outlined',
+    'ant-design:file-add-twotone',
+    'ant-design:file-done-outlined',
+    'ant-design:file-excel-filled',
+    'ant-design:file-excel-outlined',
+    'ant-design:file-excel-twotone',
+    'ant-design:file-exclamation-filled',
+    'ant-design:file-exclamation-outlined',
+    'ant-design:file-exclamation-twotone',
+    'ant-design:file-filled',
+    'ant-design:file-gif-outlined',
+    'ant-design:file-image-filled',
+    'ant-design:file-image-outlined',
+    'ant-design:file-image-twotone',
+    'ant-design:file-jpg-outlined',
+    'ant-design:file-markdown-filled',
+    'ant-design:file-markdown-outlined',
+    'ant-design:file-markdown-twotone',
+    'ant-design:file-outlined',
+    'ant-design:file-pdf-filled',
+    'ant-design:file-pdf-outlined',
+    'ant-design:file-pdf-twotone',
+    'ant-design:file-ppt-filled',
+    'ant-design:file-ppt-outlined',
+    'ant-design:file-ppt-twotone',
+    'ant-design:file-protect-outlined',
+    'ant-design:file-search-outlined',
+    'ant-design:file-sync-outlined',
+    'ant-design:file-text-filled',
+    'ant-design:file-text-outlined',
+    'ant-design:file-text-twotone',
+    'ant-design:file-twotone',
+    'ant-design:file-unknown-filled',
+    'ant-design:file-unknown-outlined',
+    'ant-design:file-unknown-twotone',
+    'ant-design:file-word-filled',
+    'ant-design:file-word-outlined',
+    'ant-design:file-word-twotone',
+    'ant-design:file-zip-filled',
+    'ant-design:file-zip-outlined',
+    'ant-design:file-zip-twotone',
+    'ant-design:filter-filled',
+    'ant-design:filter-outlined',
+    'ant-design:filter-twotone',
+    'ant-design:fire-filled',
+    'ant-design:fire-outlined',
+    'ant-design:fire-twotone',
+    'ant-design:flag-filled',
+    'ant-design:flag-outlined',
+    'ant-design:flag-twotone',
+    'ant-design:folder-add-filled',
+    'ant-design:folder-add-outlined',
+    'ant-design:folder-add-twotone',
+    'ant-design:folder-filled',
+    'ant-design:folder-open-filled',
+    'ant-design:folder-open-outlined',
+    'ant-design:folder-open-twotone',
+    'ant-design:folder-outlined',
+    'ant-design:folder-twotone',
+    'ant-design:folder-view-outlined',
+    'ant-design:font-colors-outlined',
+    'ant-design:font-size-outlined',
+    'ant-design:fork-outlined',
+    'ant-design:form-outlined',
+    'ant-design:format-painter-filled',
+    'ant-design:format-painter-outlined',
+    'ant-design:forward-filled',
+    'ant-design:forward-outlined',
+    'ant-design:frown-filled',
+    'ant-design:frown-outlined',
+    'ant-design:frown-twotone',
+    'ant-design:fullscreen-exit-outlined',
+    'ant-design:fullscreen-outlined',
+    'ant-design:function-outlined',
+    'ant-design:fund-filled',
+    'ant-design:fund-outlined',
+    'ant-design:fund-projection-screen-outlined',
+    'ant-design:fund-twotone',
+    'ant-design:fund-view-outlined',
+    'ant-design:funnel-plot-filled',
+    'ant-design:funnel-plot-outlined',
+    'ant-design:funnel-plot-twotone',
+    'ant-design:gateway-outlined',
+    'ant-design:gif-outlined',
+    'ant-design:gift-filled',
+    'ant-design:gift-outlined',
+    'ant-design:gift-twotone',
+    'ant-design:github-filled',
+    'ant-design:github-outlined',
+    'ant-design:gitlab-filled',
+    'ant-design:gitlab-outlined',
+    'ant-design:global-outlined',
+    'ant-design:gold-filled',
+    'ant-design:gold-outlined',
+    'ant-design:gold-twotone',
+    'ant-design:golden-filled',
+    'ant-design:google-circle-filled',
+    'ant-design:google-outlined',
+    'ant-design:google-plus-circle-filled',
+    'ant-design:google-plus-outlined',
+    'ant-design:google-plus-square-filled',
+    'ant-design:google-square-filled',
+    'ant-design:group-outlined',
+    'ant-design:hdd-filled',
+    'ant-design:hdd-outlined',
+    'ant-design:hdd-twotone',
+    'ant-design:heart-filled',
+    'ant-design:heart-outlined',
+    'ant-design:heart-twotone',
+    'ant-design:heat-map-outlined',
+    'ant-design:highlight-filled',
+    'ant-design:highlight-outlined',
+    'ant-design:highlight-twotone',
+    'ant-design:history-outlined',
+    'ant-design:holder-outlined',
+    'ant-design:home-filled',
+    'ant-design:home-outlined',
+    'ant-design:home-twotone',
+    'ant-design:hourglass-filled',
+    'ant-design:hourglass-outlined',
+    'ant-design:hourglass-twotone',
+    'ant-design:html5-filled',
+    'ant-design:html5-outlined',
+    'ant-design:html5-twotone',
+    'ant-design:idcard-filled',
+    'ant-design:idcard-outlined',
+    'ant-design:idcard-twotone',
+    'ant-design:ie-circle-filled',
+    'ant-design:ie-outlined',
+    'ant-design:ie-square-filled',
+    'ant-design:import-outlined',
+    'ant-design:inbox-outlined',
+    'ant-design:info-circle-filled',
+    'ant-design:info-circle-outlined',
+    'ant-design:info-circle-twotone',
+    'ant-design:info-outlined',
+    'ant-design:insert-row-above-outlined',
+    'ant-design:insert-row-below-outlined',
+    'ant-design:insert-row-left-outlined',
+    'ant-design:insert-row-right-outlined',
+    'ant-design:instagram-filled',
+    'ant-design:instagram-outlined',
+    'ant-design:insurance-filled',
+    'ant-design:insurance-outlined',
+    'ant-design:insurance-twotone',
+    'ant-design:interaction-filled',
+    'ant-design:interaction-outlined',
+    'ant-design:interaction-twotone',
+    'ant-design:issues-close-outlined',
+    'ant-design:italic-outlined',
+    'ant-design:key-outlined',
+    'ant-design:laptop-outlined',
+    'ant-design:layout-filled',
+    'ant-design:layout-outlined',
+    'ant-design:layout-twotone',
+    'ant-design:left-circle-filled',
+    'ant-design:left-circle-outlined',
+    'ant-design:left-circle-twotone',
+    'ant-design:left-outlined',
+    'ant-design:left-square-filled',
+    'ant-design:left-square-outlined',
+    'ant-design:left-square-twotone',
+    'ant-design:like-filled',
+    'ant-design:like-outlined',
+    'ant-design:like-twotone',
+    'ant-design:line-chart-outlined',
+    'ant-design:line-height-outlined',
+    'ant-design:line-outlined',
+    'ant-design:link-outlined',
+    'ant-design:linkedin-filled',
+    'ant-design:linkedin-outlined',
+    'ant-design:loading-3-quarters-outlined',
+    'ant-design:loading-outlined',
+    'ant-design:lock-filled',
+    'ant-design:lock-outlined',
+    'ant-design:lock-twotone',
+    'ant-design:login-outlined',
+    'ant-design:logout-outlined',
+    'ant-design:mac-command-filled',
+    'ant-design:mac-command-outlined',
+    'ant-design:mail-filled',
+    'ant-design:mail-outlined',
+    'ant-design:mail-twotone',
+    'ant-design:man-outlined',
+    'ant-design:medicine-box-filled',
+    'ant-design:medicine-box-outlined',
+    'ant-design:medicine-box-twotone',
+    'ant-design:medium-circle-filled',
+    'ant-design:medium-outlined',
+    'ant-design:medium-square-filled',
+    'ant-design:medium-workmark-outlined',
+    'ant-design:meh-filled',
+    'ant-design:meh-outlined',
+    'ant-design:meh-twotone',
+    'ant-design:menu-fold-outlined',
+    'ant-design:menu-outlined',
+    'ant-design:menu-unfold-outlined',
+    'ant-design:merge-cells-outlined',
+    'ant-design:message-filled',
+    'ant-design:message-outlined',
+    'ant-design:message-twotone',
+    'ant-design:minus-circle-filled',
+    'ant-design:minus-circle-outlined',
+    'ant-design:minus-circle-twotone',
+    'ant-design:minus-outlined',
+    'ant-design:minus-square-filled',
+    'ant-design:minus-square-outlined',
+    'ant-design:minus-square-twotone',
+    'ant-design:mobile-filled',
+    'ant-design:mobile-outlined',
+    'ant-design:mobile-twotone',
+    'ant-design:money-collect-filled',
+    'ant-design:money-collect-outlined',
+    'ant-design:money-collect-twotone',
+    'ant-design:monitor-outlined',
+    'ant-design:more-outlined',
+    'ant-design:node-collapse-outlined',
+    'ant-design:node-expand-outlined',
+    'ant-design:node-index-outlined',
+    'ant-design:notification-filled',
+    'ant-design:notification-outlined',
+    'ant-design:notification-twotone',
+    'ant-design:number-outlined',
+    'ant-design:one-to-one-outlined',
+    'ant-design:ordered-list-outlined',
+    'ant-design:paper-clip-outlined',
+    'ant-design:partition-outlined',
+    'ant-design:pause-circle-filled',
+    'ant-design:pause-circle-outlined',
+    'ant-design:pause-circle-twotone',
+    'ant-design:pause-outlined',
+    'ant-design:pay-circle-filled',
+    'ant-design:pay-circle-outlined',
+    'ant-design:percentage-outlined',
+    'ant-design:phone-filled',
+    'ant-design:phone-outlined',
+    'ant-design:phone-twotone',
+    'ant-design:pic-center-outlined',
+    'ant-design:pic-left-outlined',
+    'ant-design:pic-right-outlined',
+    'ant-design:picture-filled',
+    'ant-design:picture-outlined',
+    'ant-design:picture-twotone',
+    'ant-design:pie-chart-filled',
+    'ant-design:pie-chart-outlined',
+    'ant-design:pie-chart-twotone',
+    'ant-design:play-circle-filled',
+    'ant-design:play-circle-outlined',
+    'ant-design:play-circle-twotone',
+    'ant-design:play-square-filled',
+    'ant-design:play-square-outlined',
+    'ant-design:play-square-twotone',
+    'ant-design:plus-circle-filled',
+    'ant-design:plus-circle-outlined',
+    'ant-design:plus-circle-twotone',
+    'ant-design:plus-outlined',
+    'ant-design:plus-square-filled',
+    'ant-design:plus-square-outlined',
+    'ant-design:plus-square-twotone',
+    'ant-design:pound-circle-filled',
+    'ant-design:pound-circle-outlined',
+    'ant-design:pound-circle-twotone',
+    'ant-design:pound-outlined',
+    'ant-design:poweroff-outlined',
+    'ant-design:printer-filled',
+    'ant-design:printer-outlined',
+    'ant-design:printer-twotone',
+    'ant-design:profile-filled',
+    'ant-design:profile-outlined',
+    'ant-design:profile-twotone',
+    'ant-design:project-filled',
+    'ant-design:project-outlined',
+    'ant-design:project-twotone',
+    'ant-design:property-safety-filled',
+    'ant-design:property-safety-outlined',
+    'ant-design:property-safety-twotone',
+    'ant-design:pull-request-outlined',
+    'ant-design:pushpin-filled',
+    'ant-design:pushpin-outlined',
+    'ant-design:pushpin-twotone',
+    'ant-design:qq-circle-filled',
+    'ant-design:qq-outlined',
+    'ant-design:qq-square-filled',
+    'ant-design:qrcode-outlined',
+    'ant-design:question-circle-filled',
+    'ant-design:question-circle-outlined',
+    'ant-design:question-circle-twotone',
+    'ant-design:question-outlined',
+    'ant-design:radar-chart-outlined',
+    'ant-design:radius-bottomleft-outlined',
+    'ant-design:radius-bottomright-outlined',
+    'ant-design:radius-setting-outlined',
+    'ant-design:radius-upleft-outlined',
+    'ant-design:radius-upright-outlined',
+    'ant-design:read-filled',
+    'ant-design:read-outlined',
+    'ant-design:reconciliation-filled',
+    'ant-design:reconciliation-outlined',
+    'ant-design:reconciliation-twotone',
+    'ant-design:red-envelope-filled',
+    'ant-design:red-envelope-outlined',
+    'ant-design:red-envelope-twotone',
+    'ant-design:reddit-circle-filled',
+    'ant-design:reddit-outlined',
+    'ant-design:reddit-square-filled',
+    'ant-design:redo-outlined',
+    'ant-design:reload-outlined',
+    'ant-design:rest-filled',
+    'ant-design:rest-outlined',
+    'ant-design:rest-twotone',
+    'ant-design:retweet-outlined',
+    'ant-design:right-circle-filled',
+    'ant-design:right-circle-outlined',
+    'ant-design:right-circle-twotone',
+    'ant-design:right-outlined',
+    'ant-design:right-square-filled',
+    'ant-design:right-square-outlined',
+    'ant-design:right-square-twotone',
+    'ant-design:rise-outlined',
+    'ant-design:robot-filled',
+    'ant-design:robot-outlined',
+    'ant-design:rocket-filled',
+    'ant-design:rocket-outlined',
+    'ant-design:rocket-twotone',
+    'ant-design:rollback-outlined',
+    'ant-design:rotate-left-outlined',
+    'ant-design:rotate-right-outlined',
+    'ant-design:safety-certificate-filled',
+    'ant-design:safety-certificate-outlined',
+    'ant-design:safety-certificate-twotone',
+    'ant-design:safety-outlined',
+    'ant-design:save-filled',
+    'ant-design:save-outlined',
+    'ant-design:save-twotone',
+    'ant-design:scan-outlined',
+    'ant-design:schedule-filled',
+    'ant-design:schedule-outlined',
+    'ant-design:schedule-twotone',
+    'ant-design:scissor-outlined',
+    'ant-design:search-outlined',
+    'ant-design:security-scan-filled',
+    'ant-design:security-scan-outlined',
+    'ant-design:security-scan-twotone',
+    'ant-design:select-outlined',
+    'ant-design:send-outlined',
+    'ant-design:setting-filled',
+    'ant-design:setting-outlined',
+    'ant-design:setting-twotone',
+    'ant-design:shake-outlined',
+    'ant-design:share-alt-outlined',
+    'ant-design:shop-filled',
+    'ant-design:shop-outlined',
+    'ant-design:shop-twotone',
+    'ant-design:shopping-cart-outlined',
+    'ant-design:shopping-filled',
+    'ant-design:shopping-outlined',
+    'ant-design:shopping-twotone',
+    'ant-design:shrink-outlined',
+    'ant-design:signal-filled',
+    'ant-design:sisternode-outlined',
+    'ant-design:sketch-circle-filled',
+    'ant-design:sketch-outlined',
+    'ant-design:sketch-square-filled',
+    'ant-design:skin-filled',
+    'ant-design:skin-outlined',
+    'ant-design:skin-twotone',
+    'ant-design:skype-filled',
+    'ant-design:skype-outlined',
+    'ant-design:slack-circle-filled',
+    'ant-design:slack-outlined',
+    'ant-design:slack-square-filled',
+    'ant-design:slack-square-outlined',
+    'ant-design:sliders-filled',
+    'ant-design:sliders-outlined',
+    'ant-design:sliders-twotone',
+    'ant-design:small-dash-outlined',
+    'ant-design:smile-filled',
+    'ant-design:smile-outlined',
+    'ant-design:smile-twotone',
+    'ant-design:snippets-filled',
+    'ant-design:snippets-outlined',
+    'ant-design:snippets-twotone',
+    'ant-design:solution-outlined',
+    'ant-design:sort-ascending-outlined',
+    'ant-design:sort-descending-outlined',
+    'ant-design:sound-filled',
+    'ant-design:sound-outlined',
+    'ant-design:sound-twotone',
+    'ant-design:split-cells-outlined',
+    'ant-design:star-filled',
+    'ant-design:star-outlined',
+    'ant-design:star-twotone',
+    'ant-design:step-backward-filled',
+    'ant-design:step-backward-outlined',
+    'ant-design:step-forward-filled',
+    'ant-design:step-forward-outlined',
+    'ant-design:stock-outlined',
+    'ant-design:stop-filled',
+    'ant-design:stop-outlined',
+    'ant-design:stop-twotone',
+    'ant-design:strikethrough-outlined',
+    'ant-design:subnode-outlined',
+    'ant-design:swap-left-outlined',
+    'ant-design:swap-outlined',
+    'ant-design:swap-right-outlined',
+    'ant-design:switcher-filled',
+    'ant-design:switcher-outlined',
+    'ant-design:switcher-twotone',
+    'ant-design:sync-outlined',
+    'ant-design:table-outlined',
+    'ant-design:tablet-filled',
+    'ant-design:tablet-outlined',
+    'ant-design:tablet-twotone',
+    'ant-design:tag-filled',
+    'ant-design:tag-outlined',
+    'ant-design:tag-twotone',
+    'ant-design:tags-filled',
+    'ant-design:tags-outlined',
+    'ant-design:tags-twotone',
+    'ant-design:taobao-circle-filled',
+    'ant-design:taobao-circle-outlined',
+    'ant-design:taobao-outlined',
+    'ant-design:taobao-square-filled',
+    'ant-design:team-outlined',
+    'ant-design:thunderbolt-filled',
+    'ant-design:thunderbolt-outlined',
+    'ant-design:thunderbolt-twotone',
+    'ant-design:to-top-outlined',
+    'ant-design:tool-filled',
+    'ant-design:tool-outlined',
+    'ant-design:tool-twotone',
+    'ant-design:trademark-circle-filled',
+    'ant-design:trademark-circle-outlined',
+    'ant-design:trademark-circle-twotone',
+    'ant-design:trademark-outlined',
+    'ant-design:transaction-outlined',
+    'ant-design:translation-outlined',
+    'ant-design:trophy-filled',
+    'ant-design:trophy-outlined',
+    'ant-design:trophy-twotone',
+    'ant-design:twitter-circle-filled',
+    'ant-design:twitter-outlined',
+    'ant-design:twitter-square-filled',
+    'ant-design:underline-outlined',
+    'ant-design:undo-outlined',
+    'ant-design:ungroup-outlined',
+    'ant-design:unlock-filled',
+    'ant-design:unlock-outlined',
+    'ant-design:unlock-twotone',
+    'ant-design:unordered-list-outlined',
+    'ant-design:up-circle-filled',
+    'ant-design:up-circle-outlined',
+    'ant-design:up-circle-twotone',
+    'ant-design:up-outlined',
+    'ant-design:up-square-filled',
+    'ant-design:up-square-outlined',
+    'ant-design:up-square-twotone',
+    'ant-design:upload-outlined',
+    'ant-design:usb-filled',
+    'ant-design:usb-outlined',
+    'ant-design:usb-twotone',
+    'ant-design:user-add-outlined',
+    'ant-design:user-delete-outlined',
+    'ant-design:user-outlined',
+    'ant-design:user-switch-outlined',
+    'ant-design:usergroup-add-outlined',
+    'ant-design:usergroup-delete-outlined',
+    'ant-design:verified-outlined',
+    'ant-design:vertical-align-bottom-outlined',
+    'ant-design:vertical-align-middle-outlined',
+    'ant-design:vertical-align-top-outlined',
+    'ant-design:vertical-left-outlined',
+    'ant-design:vertical-right-outlined',
+    'ant-design:video-camera-add-outlined',
+    'ant-design:video-camera-filled',
+    'ant-design:video-camera-outlined',
+    'ant-design:video-camera-twotone',
+    'ant-design:wallet-filled',
+    'ant-design:wallet-outlined',
+    'ant-design:wallet-twotone',
+    'ant-design:warning-filled',
+    'ant-design:warning-outlined',
+    'ant-design:warning-twotone',
+    'ant-design:wechat-filled',
+    'ant-design:wechat-outlined',
+    'ant-design:weibo-circle-filled',
+    'ant-design:weibo-circle-outlined',
+    'ant-design:weibo-outlined',
+    'ant-design:weibo-square-filled',
+    'ant-design:weibo-square-outlined',
+    'ant-design:whats-app-outlined',
+    'ant-design:wifi-outlined',
+    'ant-design:windows-filled',
+    'ant-design:windows-outlined',
+    'ant-design:woman-outlined',
+    'ant-design:yahoo-filled',
+    'ant-design:yahoo-outlined',
+    'ant-design:youtube-filled',
+    'ant-design:youtube-outlined',
+    'ant-design:yuque-filled',
+    'ant-design:yuque-outlined',
+    'ant-design:zhihu-circle-filled',
+    'ant-design:zhihu-outlined',
+    'ant-design:zhihu-square-filled',
+    'ant-design:zoom-in-outlined',
+    'ant-design:zoom-out-outlined'
+  ]
+}

+ 299 - 0
src/components/IconPicker/src/data/icons.ep.ts

@@ -0,0 +1,299 @@
+export default {
+  name: 'Element Plus',
+  prefix: 'ep',
+  icons: [
+    'ep:add-location',
+    'ep:aim',
+    'ep:alarm-clock',
+    'ep:apple',
+    'ep:arrow-down',
+    'ep:arrow-down-bold',
+    'ep:arrow-left',
+    'ep:arrow-left-bold',
+    'ep:arrow-right',
+    'ep:arrow-right-bold',
+    'ep:arrow-up',
+    'ep:arrow-up-bold',
+    'ep:avatar',
+    'ep:back',
+    'ep:baseball',
+    'ep:basketball',
+    'ep:bell',
+    'ep:bell-filled',
+    'ep:bicycle',
+    'ep:bottom',
+    'ep:bottom-left',
+    'ep:bottom-right',
+    'ep:bowl',
+    'ep:box',
+    'ep:briefcase',
+    'ep:brush',
+    'ep:brush-filled',
+    'ep:burger',
+    'ep:calendar',
+    'ep:camera',
+    'ep:camera-filled',
+    'ep:caret-bottom',
+    'ep:caret-left',
+    'ep:caret-right',
+    'ep:caret-top',
+    'ep:cellphone',
+    'ep:chat-dot-round',
+    'ep:chat-dot-square',
+    'ep:chat-line-round',
+    'ep:chat-line-square',
+    'ep:chat-round',
+    'ep:chat-square',
+    'ep:check',
+    'ep:checked',
+    'ep:cherry',
+    'ep:chicken',
+    'ep:chrome-filled',
+    'ep:circle-check',
+    'ep:circle-check-filled',
+    'ep:circle-close',
+    'ep:circle-close-filled',
+    'ep:circle-plus',
+    'ep:circle-plus-filled',
+    'ep:clock',
+    'ep:close',
+    'ep:close-bold',
+    'ep:cloudy',
+    'ep:coffee',
+    'ep:coffee-cup',
+    'ep:coin',
+    'ep:cold-drink',
+    'ep:collection',
+    'ep:collection-tag',
+    'ep:comment',
+    'ep:compass',
+    'ep:connection',
+    'ep:coordinate',
+    'ep:copy-document',
+    'ep:cpu',
+    'ep:credit-card',
+    'ep:crop',
+    'ep:d-arrow-left',
+    'ep:d-arrow-right',
+    'ep:d-caret',
+    'ep:data-analysis',
+    'ep:data-board',
+    'ep:data-line',
+    'ep:delete',
+    'ep:delete-filled',
+    'ep:delete-location',
+    'ep:dessert',
+    'ep:discount',
+    'ep:dish',
+    'ep:dish-dot',
+    'ep:document',
+    'ep:document-add',
+    'ep:document-checked',
+    'ep:document-copy',
+    'ep:document-delete',
+    'ep:document-remove',
+    'ep:download',
+    'ep:drizzling',
+    'ep:edit',
+    'ep:edit-pen',
+    'ep:eleme',
+    'ep:eleme-filled',
+    'ep:element-plus',
+    'ep:expand',
+    'ep:failed',
+    'ep:female',
+    'ep:files',
+    'ep:film',
+    'ep:filter',
+    'ep:finished',
+    'ep:first-aid-kit',
+    'ep:flag',
+    'ep:fold',
+    'ep:folder',
+    'ep:folder-add',
+    'ep:folder-checked',
+    'ep:folder-delete',
+    'ep:folder-opened',
+    'ep:folder-remove',
+    'ep:food',
+    'ep:football',
+    'ep:fork-spoon',
+    'ep:fries',
+    'ep:full-screen',
+    'ep:goblet',
+    'ep:goblet-full',
+    'ep:goblet-square',
+    'ep:goblet-square-full',
+    'ep:gold-medal',
+    'ep:goods',
+    'ep:goods-filled',
+    'ep:grape',
+    'ep:grid',
+    'ep:guide',
+    'ep:handbag',
+    'ep:headset',
+    'ep:help',
+    'ep:help-filled',
+    'ep:hide',
+    'ep:histogram',
+    'ep:home-filled',
+    'ep:hot-water',
+    'ep:house',
+    'ep:ice-cream',
+    'ep:ice-cream-round',
+    'ep:ice-cream-square',
+    'ep:ice-drink',
+    'ep:ice-tea',
+    'ep:info-filled',
+    'ep:iphone',
+    'ep:key',
+    'ep:knife-fork',
+    'ep:lightning',
+    'ep:link',
+    'ep:list',
+    'ep:loading',
+    'ep:location',
+    'ep:location-filled',
+    'ep:location-information',
+    'ep:lock',
+    'ep:lollipop',
+    'ep:magic-stick',
+    'ep:magnet',
+    'ep:male',
+    'ep:management',
+    'ep:map-location',
+    'ep:medal',
+    'ep:memo',
+    'ep:menu',
+    'ep:message',
+    'ep:message-box',
+    'ep:mic',
+    'ep:microphone',
+    'ep:milk-tea',
+    'ep:minus',
+    'ep:money',
+    'ep:monitor',
+    'ep:moon',
+    'ep:moon-night',
+    'ep:more',
+    'ep:more-filled',
+    'ep:mostly-cloudy',
+    'ep:mouse',
+    'ep:mug',
+    'ep:mute',
+    'ep:mute-notification',
+    'ep:no-smoking',
+    'ep:notebook',
+    'ep:notification',
+    'ep:odometer',
+    'ep:office-building',
+    'ep:open',
+    'ep:operation',
+    'ep:opportunity',
+    'ep:orange',
+    'ep:paperclip',
+    'ep:partly-cloudy',
+    'ep:pear',
+    'ep:phone',
+    'ep:phone-filled',
+    'ep:picture',
+    'ep:picture-filled',
+    'ep:picture-rounded',
+    'ep:pie-chart',
+    'ep:place',
+    'ep:platform',
+    'ep:plus',
+    'ep:pointer',
+    'ep:position',
+    'ep:postcard',
+    'ep:pouring',
+    'ep:present',
+    'ep:price-tag',
+    'ep:printer',
+    'ep:promotion',
+    'ep:quartz-watch',
+    'ep:question-filled',
+    'ep:rank',
+    'ep:reading',
+    'ep:reading-lamp',
+    'ep:refresh',
+    'ep:refresh-left',
+    'ep:refresh-right',
+    'ep:refrigerator',
+    'ep:remove',
+    'ep:remove-filled',
+    'ep:right',
+    'ep:scale-to-original',
+    'ep:school',
+    'ep:scissor',
+    'ep:search',
+    'ep:select',
+    'ep:sell',
+    'ep:semi-select',
+    'ep:service',
+    'ep:set-up',
+    'ep:setting',
+    'ep:share',
+    'ep:ship',
+    'ep:shop',
+    'ep:shopping-bag',
+    'ep:shopping-cart',
+    'ep:shopping-cart-full',
+    'ep:shopping-trolley',
+    'ep:smoking',
+    'ep:soccer',
+    'ep:sold-out',
+    'ep:sort',
+    'ep:sort-down',
+    'ep:sort-up',
+    'ep:stamp',
+    'ep:star',
+    'ep:star-filled',
+    'ep:stopwatch',
+    'ep:success-filled',
+    'ep:sugar',
+    'ep:suitcase',
+    'ep:suitcase-line',
+    'ep:sunny',
+    'ep:sunrise',
+    'ep:sunset',
+    'ep:switch',
+    'ep:switch-button',
+    'ep:switch-filled',
+    'ep:takeaway-box',
+    'ep:ticket',
+    'ep:tickets',
+    'ep:timer',
+    'ep:toilet-paper',
+    'ep:tools',
+    'ep:top',
+    'ep:top-left',
+    'ep:top-right',
+    'ep:trend-charts',
+    'ep:trophy',
+    'ep:trophy-base',
+    'ep:turn-off',
+    'ep:umbrella',
+    'ep:unlock',
+    'ep:upload',
+    'ep:upload-filled',
+    'ep:user',
+    'ep:user-filled',
+    'ep:van',
+    'ep:video-camera',
+    'ep:video-camera-filled',
+    'ep:video-pause',
+    'ep:video-play',
+    'ep:view',
+    'ep:wallet',
+    'ep:wallet-filled',
+    'ep:warn-triangle-filled',
+    'ep:warning',
+    'ep:warning-filled',
+    'ep:watch',
+    'ep:watermelon',
+    'ep:wind-power',
+    'ep:zoom-in',
+    'ep:zoom-out'
+  ]
+}

+ 1209 - 0
src/components/IconPicker/src/data/icons.tdesign.ts

@@ -0,0 +1,1209 @@
+export default {
+  name: 'TDesign Icons',
+  prefix: 'tdesign',
+  icons: [
+    'tdesign:activity',
+    'tdesign:add',
+    'tdesign:add-and-subtract',
+    'tdesign:add-circle',
+    'tdesign:add-rectangle',
+    'tdesign:address-book',
+    'tdesign:adjustment',
+    'tdesign:airplay-wave',
+    'tdesign:alarm',
+    'tdesign:alarm-add',
+    'tdesign:alarm-off',
+    'tdesign:align-top',
+    'tdesign:align-vertical',
+    'tdesign:alpha',
+    'tdesign:analytics',
+    'tdesign:anchor',
+    'tdesign:angry',
+    'tdesign:animation',
+    'tdesign:animation-1',
+    'tdesign:anticlockwise',
+    'tdesign:api',
+    'tdesign:app',
+    'tdesign:apple',
+    'tdesign:application',
+    'tdesign:architecture-hui-style',
+    'tdesign:archway',
+    'tdesign:archway-1',
+    'tdesign:arrow-down',
+    'tdesign:arrow-down-circle',
+    'tdesign:arrow-down-rectangle',
+    'tdesign:arrow-left',
+    'tdesign:arrow-left-circle',
+    'tdesign:arrow-left-down',
+    'tdesign:arrow-left-down-circle',
+    'tdesign:arrow-left-right-1',
+    'tdesign:arrow-left-right-2',
+    'tdesign:arrow-left-right-3',
+    'tdesign:arrow-left-right-circle',
+    'tdesign:arrow-left-up',
+    'tdesign:arrow-left-up-circle',
+    'tdesign:arrow-right',
+    'tdesign:arrow-right-circle',
+    'tdesign:arrow-right-down',
+    'tdesign:arrow-right-down-circle',
+    'tdesign:arrow-right-up',
+    'tdesign:arrow-right-up-circle',
+    'tdesign:arrow-triangle-down',
+    'tdesign:arrow-triangle-down-filled',
+    'tdesign:arrow-triangle-up',
+    'tdesign:arrow-triangle-up-filled',
+    'tdesign:arrow-up',
+    'tdesign:arrow-up-circle',
+    'tdesign:arrow-up-down-1',
+    'tdesign:arrow-up-down-2',
+    'tdesign:arrow-up-down-3',
+    'tdesign:arrow-up-down-circle',
+    'tdesign:artboard',
+    'tdesign:article',
+    'tdesign:assignment',
+    'tdesign:assignment-checked',
+    'tdesign:assignment-code',
+    'tdesign:assignment-error',
+    'tdesign:assignment-user',
+    'tdesign:attach',
+    'tdesign:attic',
+    'tdesign:attic-1',
+    'tdesign:audio',
+    'tdesign:awkward',
+    'tdesign:backtop',
+    'tdesign:backtop-rectangle',
+    'tdesign:backup',
+    'tdesign:backward',
+    'tdesign:bad-laugh',
+    'tdesign:bamboo-shoot',
+    'tdesign:banana',
+    'tdesign:barbecue',
+    'tdesign:barcode',
+    'tdesign:barcode-1',
+    'tdesign:base-station',
+    'tdesign:battery',
+    'tdesign:battery-add',
+    'tdesign:battery-charging',
+    'tdesign:battery-low',
+    'tdesign:bean',
+    'tdesign:beer',
+    'tdesign:beta',
+    'tdesign:bifurcate',
+    'tdesign:bill',
+    'tdesign:blockchain',
+    'tdesign:bluetooth',
+    'tdesign:bone',
+    'tdesign:book',
+    'tdesign:book-open',
+    'tdesign:bookmark',
+    'tdesign:bookmark-add',
+    'tdesign:bookmark-checked',
+    'tdesign:bookmark-double',
+    'tdesign:bookmark-minus',
+    'tdesign:braces',
+    'tdesign:brackets',
+    'tdesign:bread',
+    'tdesign:bridge',
+    'tdesign:bridge-1',
+    'tdesign:bridge-2',
+    'tdesign:bridge-3',
+    'tdesign:bridge-4',
+    'tdesign:bridge-5',
+    'tdesign:bridge-6',
+    'tdesign:brightness',
+    'tdesign:brightness-1',
+    'tdesign:broccoli',
+    'tdesign:browse',
+    'tdesign:browse-gallery',
+    'tdesign:browse-off',
+    'tdesign:brush',
+    'tdesign:bug',
+    'tdesign:bug-report',
+    'tdesign:building',
+    'tdesign:building-1',
+    'tdesign:building-2',
+    'tdesign:building-3',
+    'tdesign:building-4',
+    'tdesign:building-5',
+    'tdesign:bulletpoint',
+    'tdesign:button',
+    'tdesign:cabbage',
+    'tdesign:cake',
+    'tdesign:calculation',
+    'tdesign:calculation-1',
+    'tdesign:calculator',
+    'tdesign:calculator-1',
+    'tdesign:calendar',
+    'tdesign:calendar-1',
+    'tdesign:calendar-2',
+    'tdesign:calendar-edit',
+    'tdesign:calendar-event',
+    'tdesign:call',
+    'tdesign:call-1',
+    'tdesign:call-cancel',
+    'tdesign:call-forwarded',
+    'tdesign:call-incoming',
+    'tdesign:call-off',
+    'tdesign:calm',
+    'tdesign:calm-1',
+    'tdesign:camera',
+    'tdesign:camera-1',
+    'tdesign:camera-2',
+    'tdesign:camera-off',
+    'tdesign:candy',
+    'tdesign:card',
+    'tdesign:cardmembership',
+    'tdesign:caret-down',
+    'tdesign:caret-down-small',
+    'tdesign:caret-left',
+    'tdesign:caret-left-small',
+    'tdesign:caret-right',
+    'tdesign:caret-right-small',
+    'tdesign:caret-up',
+    'tdesign:caret-up-small',
+    'tdesign:cart',
+    'tdesign:cart-add',
+    'tdesign:cast',
+    'tdesign:castle',
+    'tdesign:castle-1',
+    'tdesign:castle-2',
+    'tdesign:castle-3',
+    'tdesign:castle-4',
+    'tdesign:castle-5',
+    'tdesign:castle-6',
+    'tdesign:castle-7',
+    'tdesign:cat',
+    'tdesign:catalog',
+    'tdesign:cd',
+    'tdesign:celsius',
+    'tdesign:center-focus-strong',
+    'tdesign:centimeter',
+    'tdesign:certificate',
+    'tdesign:certificate-1',
+    'tdesign:chart',
+    'tdesign:chart-3d',
+    'tdesign:chart-add',
+    'tdesign:chart-analytics',
+    'tdesign:chart-area',
+    'tdesign:chart-area-multi',
+    'tdesign:chart-bar',
+    'tdesign:chart-bubble',
+    'tdesign:chart-colum',
+    'tdesign:chart-combo',
+    'tdesign:chart-line',
+    'tdesign:chart-line-data',
+    'tdesign:chart-line-data-1',
+    'tdesign:chart-line-multi',
+    'tdesign:chart-maximum',
+    'tdesign:chart-median',
+    'tdesign:chart-minimum',
+    'tdesign:chart-pie',
+    'tdesign:chart-radar',
+    'tdesign:chart-radial',
+    'tdesign:chart-ring',
+    'tdesign:chart-ring-1',
+    'tdesign:chart-scatter',
+    'tdesign:chart-stacked',
+    'tdesign:chat',
+    'tdesign:chat-add',
+    'tdesign:chat-bubble',
+    'tdesign:chat-bubble-1',
+    'tdesign:chat-bubble-add',
+    'tdesign:chat-bubble-error',
+    'tdesign:chat-bubble-help',
+    'tdesign:chat-bubble-history',
+    'tdesign:chat-bubble-locked',
+    'tdesign:chat-bubble-smile',
+    'tdesign:chat-checked',
+    'tdesign:chat-clear',
+    'tdesign:chat-double',
+    'tdesign:chat-error',
+    'tdesign:chat-heart',
+    'tdesign:chat-message',
+    'tdesign:chat-off',
+    'tdesign:chat-poll',
+    'tdesign:chat-setting',
+    'tdesign:check',
+    'tdesign:check-circle',
+    'tdesign:check-circle-filled',
+    'tdesign:check-double',
+    'tdesign:check-rectangle',
+    'tdesign:check-rectangle-filled',
+    'tdesign:cheese',
+    'tdesign:cherry',
+    'tdesign:chevron-down',
+    'tdesign:chevron-down-circle',
+    'tdesign:chevron-down-double',
+    'tdesign:chevron-down-double-s',
+    'tdesign:chevron-down-rectangle',
+    'tdesign:chevron-down-s',
+    'tdesign:chevron-left',
+    'tdesign:chevron-left-circle',
+    'tdesign:chevron-left-double',
+    'tdesign:chevron-left-double-s',
+    'tdesign:chevron-left-rectangle',
+    'tdesign:chevron-left-s',
+    'tdesign:chevron-right',
+    'tdesign:chevron-right-circle',
+    'tdesign:chevron-right-double',
+    'tdesign:chevron-right-double-s',
+    'tdesign:chevron-right-rectangle',
+    'tdesign:chevron-right-s',
+    'tdesign:chevron-up',
+    'tdesign:chevron-up-circle',
+    'tdesign:chevron-up-double',
+    'tdesign:chevron-up-double-s',
+    'tdesign:chevron-up-rectangle',
+    'tdesign:chevron-up-s',
+    'tdesign:chicken',
+    'tdesign:chili',
+    'tdesign:chimney',
+    'tdesign:chimney-1',
+    'tdesign:chimney-2',
+    'tdesign:chinese-cabbage',
+    'tdesign:church',
+    'tdesign:circle',
+    'tdesign:city',
+    'tdesign:city-1',
+    'tdesign:city-10',
+    'tdesign:city-11',
+    'tdesign:city-12',
+    'tdesign:city-13',
+    'tdesign:city-14',
+    'tdesign:city-15',
+    'tdesign:city-2',
+    'tdesign:city-3',
+    'tdesign:city-4',
+    'tdesign:city-5',
+    'tdesign:city-6',
+    'tdesign:city-7',
+    'tdesign:city-8',
+    'tdesign:city-9',
+    'tdesign:city-ancient',
+    'tdesign:city-ancient-1',
+    'tdesign:city-ancient-2',
+    'tdesign:clear',
+    'tdesign:clear-formatting',
+    'tdesign:clear-formatting-1',
+    'tdesign:close',
+    'tdesign:close-circle',
+    'tdesign:close-circle-filled',
+    'tdesign:close-octagon',
+    'tdesign:close-rectangle',
+    'tdesign:cloud',
+    'tdesign:cloud-download',
+    'tdesign:cloud-upload',
+    'tdesign:cloudy-day',
+    'tdesign:cloudy-night',
+    'tdesign:cloudy-night-rain',
+    'tdesign:cloudy-rain',
+    'tdesign:cloudy-sunny',
+    'tdesign:code',
+    'tdesign:code-1',
+    'tdesign:code-off',
+    'tdesign:cola',
+    'tdesign:collage',
+    'tdesign:collection',
+    'tdesign:color-invert',
+    'tdesign:combination',
+    'tdesign:command',
+    'tdesign:compass',
+    'tdesign:compass-1',
+    'tdesign:component-breadcrumb',
+    'tdesign:component-checkbox',
+    'tdesign:component-divider-horizontal',
+    'tdesign:component-divider-vertical',
+    'tdesign:component-dropdown',
+    'tdesign:component-grid',
+    'tdesign:component-input',
+    'tdesign:component-layout',
+    'tdesign:component-radio',
+    'tdesign:component-space',
+    'tdesign:component-steps',
+    'tdesign:component-switch',
+    'tdesign:constraint',
+    'tdesign:contrast',
+    'tdesign:contrast-1',
+    'tdesign:control-platform',
+    'tdesign:cooperate',
+    'tdesign:coordinate-system',
+    'tdesign:copy',
+    'tdesign:copyright',
+    'tdesign:corn',
+    'tdesign:coupon',
+    'tdesign:course',
+    'tdesign:cpu',
+    'tdesign:crack',
+    'tdesign:creditcard',
+    'tdesign:creditcard-add',
+    'tdesign:creditcard-off',
+    'tdesign:crooked-smile',
+    'tdesign:cry-and-laugh',
+    'tdesign:cry-loudly',
+    'tdesign:css3',
+    'tdesign:cucumber',
+    'tdesign:currency-exchange',
+    'tdesign:cursor',
+    'tdesign:curtain',
+    'tdesign:curve',
+    'tdesign:cut',
+    'tdesign:cut-1',
+    'tdesign:dam',
+    'tdesign:dam-1',
+    'tdesign:dam-2',
+    'tdesign:dam-3',
+    'tdesign:dam-4',
+    'tdesign:dam-5',
+    'tdesign:dam-6',
+    'tdesign:dam-7',
+    'tdesign:dart-board',
+    'tdesign:dashboard',
+    'tdesign:dashboard-1',
+    'tdesign:data',
+    'tdesign:data-base',
+    'tdesign:data-checked',
+    'tdesign:data-display',
+    'tdesign:data-error',
+    'tdesign:data-search',
+    'tdesign:delete',
+    'tdesign:delete-1',
+    'tdesign:delete-time',
+    'tdesign:delta',
+    'tdesign:depressed',
+    'tdesign:desktop',
+    'tdesign:desktop-1',
+    'tdesign:despise',
+    'tdesign:device',
+    'tdesign:discount',
+    'tdesign:discount-filled',
+    'tdesign:dissatisfaction',
+    'tdesign:divide',
+    'tdesign:dividers',
+    'tdesign:dividers-1',
+    'tdesign:doge',
+    'tdesign:double-storey',
+    'tdesign:download',
+    'tdesign:download-1',
+    'tdesign:downscale',
+    'tdesign:drag-drop',
+    'tdesign:drag-move',
+    'tdesign:drink',
+    'tdesign:drumstick',
+    'tdesign:dv',
+    'tdesign:dvd',
+    'tdesign:earphone',
+    'tdesign:earth',
+    'tdesign:edit',
+    'tdesign:edit-1',
+    'tdesign:edit-2',
+    'tdesign:edit-off',
+    'tdesign:education',
+    'tdesign:eggplant',
+    'tdesign:ellipsis',
+    'tdesign:emo-emotional',
+    'tdesign:enter',
+    'tdesign:equal',
+    'tdesign:error',
+    'tdesign:error-circle',
+    'tdesign:error-circle-filled',
+    'tdesign:error-triangle',
+    'tdesign:excited',
+    'tdesign:excited-1',
+    'tdesign:expand-horizontal',
+    'tdesign:expand-vertical',
+    'tdesign:explore',
+    'tdesign:explore-off',
+    'tdesign:exposure',
+    'tdesign:extension',
+    'tdesign:extension-off',
+    'tdesign:face-retouching',
+    'tdesign:fact-check',
+    'tdesign:fahrenheit-scale',
+    'tdesign:feel-at-ease',
+    'tdesign:ferocious',
+    'tdesign:ferris-wheel',
+    'tdesign:file',
+    'tdesign:file-1',
+    'tdesign:file-add',
+    'tdesign:file-add-1',
+    'tdesign:file-attachment',
+    'tdesign:file-blocked',
+    'tdesign:file-code',
+    'tdesign:file-code-1',
+    'tdesign:file-copy',
+    'tdesign:file-download',
+    'tdesign:file-excel',
+    'tdesign:file-export',
+    'tdesign:file-icon',
+    'tdesign:file-image',
+    'tdesign:file-import',
+    'tdesign:file-locked',
+    'tdesign:file-minus',
+    'tdesign:file-music',
+    'tdesign:file-onenote',
+    'tdesign:file-outlook',
+    'tdesign:file-paste',
+    'tdesign:file-pdf',
+    'tdesign:file-powerpoint',
+    'tdesign:file-restore',
+    'tdesign:file-safety',
+    'tdesign:file-search',
+    'tdesign:file-setting',
+    'tdesign:file-teams',
+    'tdesign:file-unknown',
+    'tdesign:file-unlocked',
+    'tdesign:file-word',
+    'tdesign:file-zip',
+    'tdesign:fill-color',
+    'tdesign:fill-color-1',
+    'tdesign:film',
+    'tdesign:film-1',
+    'tdesign:filter',
+    'tdesign:filter-1',
+    'tdesign:filter-2',
+    'tdesign:filter-3',
+    'tdesign:filter-clear',
+    'tdesign:filter-off',
+    'tdesign:fingerprint',
+    'tdesign:fingerprint-1',
+    'tdesign:fingerprint-2',
+    'tdesign:fingerprint-3',
+    'tdesign:fish',
+    'tdesign:flag',
+    'tdesign:flag-1',
+    'tdesign:flag-2',
+    'tdesign:flag-3',
+    'tdesign:flag-4',
+    'tdesign:flashlight',
+    'tdesign:flight-landing',
+    'tdesign:flight-takeoff',
+    'tdesign:flip-smiling-face',
+    'tdesign:flip-to-back',
+    'tdesign:flip-to-front',
+    'tdesign:focus',
+    'tdesign:fog',
+    'tdesign:fog-night',
+    'tdesign:fog-sunny',
+    'tdesign:folder',
+    'tdesign:folder-1',
+    'tdesign:folder-add',
+    'tdesign:folder-add-1',
+    'tdesign:folder-blocked',
+    'tdesign:folder-details',
+    'tdesign:folder-export',
+    'tdesign:folder-import',
+    'tdesign:folder-locked',
+    'tdesign:folder-minus',
+    'tdesign:folder-move',
+    'tdesign:folder-off',
+    'tdesign:folder-open',
+    'tdesign:folder-open-1',
+    'tdesign:folder-search',
+    'tdesign:folder-setting',
+    'tdesign:folder-shared',
+    'tdesign:folder-unlocked',
+    'tdesign:folder-zip',
+    'tdesign:forest',
+    'tdesign:fork',
+    'tdesign:form',
+    'tdesign:format-horizontal-align-bottom',
+    'tdesign:format-horizontal-align-center',
+    'tdesign:format-horizontal-align-top',
+    'tdesign:format-vertical-align-center',
+    'tdesign:format-vertical-align-left',
+    'tdesign:format-vertical-align-right',
+    'tdesign:forward',
+    'tdesign:frame',
+    'tdesign:frame-1',
+    'tdesign:fries',
+    'tdesign:fullscreen',
+    'tdesign:fullscreen-1',
+    'tdesign:fullscreen-2',
+    'tdesign:fullscreen-exit',
+    'tdesign:fullscreen-exit-1',
+    'tdesign:function-curve',
+    'tdesign:functions',
+    'tdesign:functions-1',
+    'tdesign:gamepad',
+    'tdesign:gamepad-1',
+    'tdesign:gamma',
+    'tdesign:garlic',
+    'tdesign:gender-female',
+    'tdesign:gender-male',
+    'tdesign:gesture-applause',
+    'tdesign:gesture-click',
+    'tdesign:gesture-down',
+    'tdesign:gesture-expansion',
+    'tdesign:gesture-left',
+    'tdesign:gesture-left-slip',
+    'tdesign:gesture-pray',
+    'tdesign:gesture-pray-1',
+    'tdesign:gesture-press',
+    'tdesign:gesture-ranslation',
+    'tdesign:gesture-ranslation-1',
+    'tdesign:gesture-right',
+    'tdesign:gesture-right-slip',
+    'tdesign:gesture-slide-up',
+    'tdesign:gesture-up',
+    'tdesign:gesture-up-1',
+    'tdesign:gesture-up-2',
+    'tdesign:gesture-up-and-down',
+    'tdesign:gesture-wipe-down',
+    'tdesign:gift',
+    'tdesign:giggle',
+    'tdesign:git-branch',
+    'tdesign:git-commit',
+    'tdesign:git-merge',
+    'tdesign:git-pull-request',
+    'tdesign:git-repository',
+    'tdesign:git-repository-commits',
+    'tdesign:git-repository-private',
+    'tdesign:gps',
+    'tdesign:grape',
+    'tdesign:greater-than',
+    'tdesign:greater-than-or-equal',
+    'tdesign:green-onion',
+    'tdesign:grid-add',
+    'tdesign:grid-view',
+    'tdesign:guitar',
+    'tdesign:hamburger',
+    'tdesign:happy',
+    'tdesign:hard-disk-storage',
+    'tdesign:hard-drive',
+    'tdesign:hashtag',
+    'tdesign:hd',
+    'tdesign:heart',
+    'tdesign:heart-filled',
+    'tdesign:help',
+    'tdesign:help-circle',
+    'tdesign:help-circle-filled',
+    'tdesign:help-rectangle',
+    'tdesign:highlight',
+    'tdesign:highlight-1',
+    'tdesign:history',
+    'tdesign:history-setting',
+    'tdesign:home',
+    'tdesign:hospital',
+    'tdesign:hospital-1',
+    'tdesign:hotspot-wave',
+    'tdesign:hourglass',
+    'tdesign:houses',
+    'tdesign:houses-1',
+    'tdesign:houses-2',
+    'tdesign:html5',
+    'tdesign:https',
+    'tdesign:ice-cream',
+    'tdesign:icon',
+    'tdesign:image',
+    'tdesign:image-1',
+    'tdesign:image-add',
+    'tdesign:image-edit',
+    'tdesign:image-error',
+    'tdesign:image-off',
+    'tdesign:image-search',
+    'tdesign:indent-left',
+    'tdesign:indent-right',
+    'tdesign:indicator',
+    'tdesign:info-circle',
+    'tdesign:info-circle-filled',
+    'tdesign:ink',
+    'tdesign:install',
+    'tdesign:install-desktop',
+    'tdesign:install-mobile',
+    'tdesign:institution',
+    'tdesign:institution-checked',
+    'tdesign:internet',
+    'tdesign:ipod',
+    'tdesign:joyful',
+    'tdesign:jump',
+    'tdesign:jump-off',
+    'tdesign:keyboard',
+    'tdesign:laptop',
+    'tdesign:layers',
+    'tdesign:layout',
+    'tdesign:leaderboard',
+    'tdesign:lemon',
+    'tdesign:lemon-slice',
+    'tdesign:less-than',
+    'tdesign:less-than-or-equal',
+    'tdesign:letters-a',
+    'tdesign:letters-b',
+    'tdesign:letters-c',
+    'tdesign:letters-d',
+    'tdesign:letters-e',
+    'tdesign:letters-f',
+    'tdesign:letters-g',
+    'tdesign:letters-h',
+    'tdesign:letters-i',
+    'tdesign:letters-j',
+    'tdesign:letters-k',
+    'tdesign:letters-l',
+    'tdesign:letters-m',
+    'tdesign:letters-n',
+    'tdesign:letters-o',
+    'tdesign:letters-p',
+    'tdesign:letters-q',
+    'tdesign:letters-r',
+    'tdesign:letters-s',
+    'tdesign:letters-t',
+    'tdesign:letters-u',
+    'tdesign:letters-v',
+    'tdesign:letters-w',
+    'tdesign:letters-x',
+    'tdesign:letters-y',
+    'tdesign:letters-z',
+    'tdesign:lightbulb',
+    'tdesign:lightbulb-circle',
+    'tdesign:lighthouse',
+    'tdesign:lighthouse-1',
+    'tdesign:lighthouse-2',
+    'tdesign:lighting-circle',
+    'tdesign:line-height',
+    'tdesign:link',
+    'tdesign:link-1',
+    'tdesign:link-unlink',
+    'tdesign:liquor',
+    'tdesign:list',
+    'tdesign:load',
+    'tdesign:loading',
+    'tdesign:location',
+    'tdesign:location-1',
+    'tdesign:location-enlargement',
+    'tdesign:location-error',
+    'tdesign:location-parking-place',
+    'tdesign:location-reduction',
+    'tdesign:location-setting',
+    'tdesign:lock-off',
+    'tdesign:lock-on',
+    'tdesign:lock-time',
+    'tdesign:login',
+    'tdesign:logo-adobe-illustrate',
+    'tdesign:logo-adobe-photoshop',
+    'tdesign:logo-adobe-photoshop-1',
+    'tdesign:logo-android',
+    'tdesign:logo-apple',
+    'tdesign:logo-apple-filled',
+    'tdesign:logo-behance',
+    'tdesign:logo-chrome',
+    'tdesign:logo-chrome-filled',
+    'tdesign:logo-cinema4d',
+    'tdesign:logo-codepen',
+    'tdesign:logo-codesandbox',
+    'tdesign:logo-dribbble',
+    'tdesign:logo-facebook',
+    'tdesign:logo-figma',
+    'tdesign:logo-framer',
+    'tdesign:logo-github',
+    'tdesign:logo-github-filled',
+    'tdesign:logo-gitlab',
+    'tdesign:logo-ie',
+    'tdesign:logo-ie-filled',
+    'tdesign:logo-instagram',
+    'tdesign:logo-qq',
+    'tdesign:logo-twitter',
+    'tdesign:logo-wechat',
+    'tdesign:logo-wechat-stroke',
+    'tdesign:logo-wecom',
+    'tdesign:logo-windows',
+    'tdesign:logo-windows-filled',
+    'tdesign:logo-youtube',
+    'tdesign:logout',
+    'tdesign:look-around',
+    'tdesign:loudspeaker',
+    'tdesign:mail',
+    'tdesign:map',
+    'tdesign:map-3d',
+    'tdesign:map-add',
+    'tdesign:map-aiming',
+    'tdesign:map-blocked',
+    'tdesign:map-bubble',
+    'tdesign:map-cancel',
+    'tdesign:map-chat',
+    'tdesign:map-checked',
+    'tdesign:map-collection',
+    'tdesign:map-connection',
+    'tdesign:map-distance',
+    'tdesign:map-double',
+    'tdesign:map-edit',
+    'tdesign:map-grid',
+    'tdesign:map-information',
+    'tdesign:map-information-1',
+    'tdesign:map-information-2',
+    'tdesign:map-location',
+    'tdesign:map-locked',
+    'tdesign:map-marked',
+    'tdesign:map-navigation',
+    'tdesign:map-outline',
+    'tdesign:map-route-planning',
+    'tdesign:map-ruler',
+    'tdesign:map-safety',
+    'tdesign:map-search',
+    'tdesign:map-search-1',
+    'tdesign:map-setting',
+    'tdesign:map-unlocked',
+    'tdesign:mark-as-unread',
+    'tdesign:markup',
+    'tdesign:mathematics',
+    'tdesign:measurement',
+    'tdesign:measurement-1',
+    'tdesign:measurement-2',
+    'tdesign:meat-pepper',
+    'tdesign:media-library',
+    'tdesign:member',
+    'tdesign:menu',
+    'tdesign:menu-application',
+    'tdesign:menu-fold',
+    'tdesign:menu-unfold',
+    'tdesign:merge-cells',
+    'tdesign:microphone',
+    'tdesign:microphone-1',
+    'tdesign:microphone-2',
+    'tdesign:milk',
+    'tdesign:minus',
+    'tdesign:minus-circle',
+    'tdesign:minus-circle-filled',
+    'tdesign:minus-rectangle',
+    'tdesign:minus-rectangle-filled',
+    'tdesign:mirror',
+    'tdesign:mobile',
+    'tdesign:mobile-blocked',
+    'tdesign:mobile-list',
+    'tdesign:mobile-navigation',
+    'tdesign:mobile-shortcut',
+    'tdesign:mobile-vibrate',
+    'tdesign:mode-dark',
+    'tdesign:mode-light',
+    'tdesign:module',
+    'tdesign:money',
+    'tdesign:monument',
+    'tdesign:moon',
+    'tdesign:moon-fall',
+    'tdesign:moon-rising',
+    'tdesign:more',
+    'tdesign:mosque',
+    'tdesign:mosque-1',
+    'tdesign:mouse',
+    'tdesign:move',
+    'tdesign:move-1',
+    'tdesign:movie-clapper',
+    'tdesign:multiply',
+    'tdesign:museum',
+    'tdesign:museum-1',
+    'tdesign:museum-2',
+    'tdesign:mushroom',
+    'tdesign:mushroom-1',
+    'tdesign:music',
+    'tdesign:music-1',
+    'tdesign:music-2',
+    'tdesign:music-rectangle-add',
+    'tdesign:navigation-arrow',
+    'tdesign:next',
+    'tdesign:no-expression',
+    'tdesign:noodle',
+    'tdesign:notification',
+    'tdesign:notification-add',
+    'tdesign:notification-circle',
+    'tdesign:notification-error',
+    'tdesign:notification-filled',
+    'tdesign:numbers-0',
+    'tdesign:numbers-0-1',
+    'tdesign:numbers-1',
+    'tdesign:numbers-1-1',
+    'tdesign:numbers-2',
+    'tdesign:numbers-2-1',
+    'tdesign:numbers-3',
+    'tdesign:numbers-3-1',
+    'tdesign:numbers-4',
+    'tdesign:numbers-4-1',
+    'tdesign:numbers-5',
+    'tdesign:numbers-5-1',
+    'tdesign:numbers-6',
+    'tdesign:numbers-6-1',
+    'tdesign:numbers-7',
+    'tdesign:numbers-7-1',
+    'tdesign:numbers-8',
+    'tdesign:numbers-8-1',
+    'tdesign:numbers-9',
+    'tdesign:numbers-9-1',
+    'tdesign:nut',
+    'tdesign:object-storage',
+    'tdesign:open-mouth',
+    'tdesign:opera',
+    'tdesign:order-adjustment-column',
+    'tdesign:order-ascending',
+    'tdesign:order-descending',
+    'tdesign:outbox',
+    'tdesign:page-first',
+    'tdesign:page-head',
+    'tdesign:page-last',
+    'tdesign:palace',
+    'tdesign:palace-1',
+    'tdesign:palace-2',
+    'tdesign:palace-3',
+    'tdesign:palace-4',
+    'tdesign:palette',
+    'tdesign:palette-1',
+    'tdesign:panorama-horizontal',
+    'tdesign:panorama-vertical',
+    'tdesign:pantone',
+    'tdesign:parabola',
+    'tdesign:parentheses',
+    'tdesign:paste',
+    'tdesign:patio',
+    'tdesign:pause',
+    'tdesign:pause-circle',
+    'tdesign:pause-circle-filled',
+    'tdesign:pause-circle-stroke',
+    'tdesign:pea',
+    'tdesign:peach',
+    'tdesign:pear',
+    'tdesign:pearl-of-the-orient',
+    'tdesign:pen',
+    'tdesign:pen-ball',
+    'tdesign:pen-brush',
+    'tdesign:pen-mark',
+    'tdesign:pen-quill',
+    'tdesign:pending',
+    'tdesign:percent',
+    'tdesign:personal-information',
+    'tdesign:phone-locked',
+    'tdesign:phone-search',
+    'tdesign:pi',
+    'tdesign:piano',
+    'tdesign:pin',
+    'tdesign:pin-filled',
+    'tdesign:play',
+    'tdesign:play-circle',
+    'tdesign:play-circle-filled',
+    'tdesign:play-circle-stroke',
+    'tdesign:play-circle-stroke-add',
+    'tdesign:play-demo',
+    'tdesign:play-rectangle',
+    'tdesign:plus',
+    'tdesign:popsicle',
+    'tdesign:portrait',
+    'tdesign:pout',
+    'tdesign:poweroff',
+    'tdesign:precise-monitor',
+    'tdesign:previous',
+    'tdesign:print',
+    'tdesign:pumpkin',
+    'tdesign:pyramid',
+    'tdesign:pyramid-maya',
+    'tdesign:qrcode',
+    'tdesign:quadratic',
+    'tdesign:questionnaire',
+    'tdesign:queue',
+    'tdesign:radar',
+    'tdesign:radio-1',
+    'tdesign:radio-2',
+    'tdesign:radish',
+    'tdesign:rain-heavy',
+    'tdesign:rain-light',
+    'tdesign:rain-medium',
+    'tdesign:rainbow',
+    'tdesign:rectangle',
+    'tdesign:refresh',
+    'tdesign:relation',
+    'tdesign:relativity',
+    'tdesign:remote-wave',
+    'tdesign:remove',
+    'tdesign:replay',
+    'tdesign:rice',
+    'tdesign:rice-ball',
+    'tdesign:roast',
+    'tdesign:rocket',
+    'tdesign:rollback',
+    'tdesign:rollfront',
+    'tdesign:root-list',
+    'tdesign:rotate',
+    'tdesign:rotate-locked',
+    'tdesign:rotation',
+    'tdesign:round',
+    'tdesign:router-wave',
+    'tdesign:rss',
+    'tdesign:ruler',
+    'tdesign:sailing-hotel',
+    'tdesign:sandwich',
+    'tdesign:saturation',
+    'tdesign:sausage',
+    'tdesign:save',
+    'tdesign:saving-pot',
+    'tdesign:scan',
+    'tdesign:screen-4k',
+    'tdesign:screencast',
+    'tdesign:screenshot',
+    'tdesign:scroll-bar',
+    'tdesign:sd-card',
+    'tdesign:sd-card-1',
+    'tdesign:search',
+    'tdesign:search-error',
+    'tdesign:secured',
+    'tdesign:send',
+    'tdesign:send-cancel',
+    'tdesign:sensors',
+    'tdesign:sensors-1',
+    'tdesign:sensors-2',
+    'tdesign:sensors-off',
+    'tdesign:serenity',
+    'tdesign:server',
+    'tdesign:service',
+    'tdesign:setting',
+    'tdesign:setting-1',
+    'tdesign:share',
+    'tdesign:share-1',
+    'tdesign:sharpness',
+    'tdesign:shield-error',
+    'tdesign:shimen',
+    'tdesign:shop',
+    'tdesign:shop-1',
+    'tdesign:shop-2',
+    'tdesign:shop-3',
+    'tdesign:shop-4',
+    'tdesign:shop-5',
+    'tdesign:shrimp',
+    'tdesign:shrink-horizontal',
+    'tdesign:shrink-vertical',
+    'tdesign:shutter',
+    'tdesign:shutup',
+    'tdesign:sim-card',
+    'tdesign:sim-card-1',
+    'tdesign:sim-card-2',
+    'tdesign:sinister-smile',
+    'tdesign:sip',
+    'tdesign:slash',
+    'tdesign:sleep',
+    'tdesign:slice',
+    'tdesign:slideshow',
+    'tdesign:smile',
+    'tdesign:sneer',
+    'tdesign:snowflake',
+    'tdesign:sonic',
+    'tdesign:sound',
+    'tdesign:sound-down',
+    'tdesign:sound-high',
+    'tdesign:sound-low',
+    'tdesign:sound-mute',
+    'tdesign:sound-mute-1',
+    'tdesign:sound-up',
+    'tdesign:space',
+    'tdesign:speechless-1',
+    'tdesign:star',
+    'tdesign:star-filled',
+    'tdesign:statue-of-jesus',
+    'tdesign:sticky-note',
+    'tdesign:stop',
+    'tdesign:stop-circle',
+    'tdesign:stop-circle-filled',
+    'tdesign:stop-circle-stroke',
+    'tdesign:store',
+    'tdesign:street-road',
+    'tdesign:street-road-1',
+    'tdesign:subtitle',
+    'tdesign:subway-line',
+    'tdesign:sum',
+    'tdesign:sun-fall',
+    'tdesign:sun-rising',
+    'tdesign:sunny',
+    'tdesign:support',
+    'tdesign:surprised',
+    'tdesign:surprised-1',
+    'tdesign:swap',
+    'tdesign:swap-left',
+    'tdesign:swap-right',
+    'tdesign:swear-1',
+    'tdesign:swear-2',
+    'tdesign:system-2',
+    'tdesign:system-3',
+    'tdesign:system-application',
+    'tdesign:system-blocked',
+    'tdesign:system-code',
+    'tdesign:system-components',
+    'tdesign:system-coordinate',
+    'tdesign:system-device',
+    'tdesign:system-interface',
+    'tdesign:system-location',
+    'tdesign:system-locked',
+    'tdesign:system-log',
+    'tdesign:system-marked',
+    'tdesign:system-messages',
+    'tdesign:system-regulation',
+    'tdesign:system-search',
+    'tdesign:system-setting',
+    'tdesign:system-storage',
+    'tdesign:system-sum',
+    'tdesign:system-unlocked',
+    'tdesign:tab',
+    'tdesign:table',
+    'tdesign:table-1',
+    'tdesign:table-2',
+    'tdesign:table-add',
+    'tdesign:table-split',
+    'tdesign:tag',
+    'tdesign:tangerinr',
+    'tdesign:tape',
+    'tdesign:task',
+    'tdesign:task-1',
+    'tdesign:task-add',
+    'tdesign:task-add-1',
+    'tdesign:task-checked',
+    'tdesign:task-error',
+    'tdesign:task-location',
+    'tdesign:task-marked',
+    'tdesign:task-setting',
+    'tdesign:task-visible',
+    'tdesign:tea',
+    'tdesign:teahouse',
+    'tdesign:template',
+    'tdesign:temple',
+    'tdesign:terminal',
+    'tdesign:terminal-rectangle',
+    'tdesign:terminal-rectangle-1',
+    'tdesign:terminal-window',
+    'tdesign:textbox',
+    'tdesign:textformat-bold',
+    'tdesign:textformat-color',
+    'tdesign:textformat-italic',
+    'tdesign:textformat-strikethrough',
+    'tdesign:textformat-underline',
+    'tdesign:textformat-wrap',
+    'tdesign:theaters',
+    'tdesign:thumb-down',
+    'tdesign:thumb-down-1',
+    'tdesign:thumb-down-2',
+    'tdesign:thumb-up',
+    'tdesign:thumb-up-1',
+    'tdesign:thumb-up-2',
+    'tdesign:thunder',
+    'tdesign:thunderstorm',
+    'tdesign:thunderstorm-night',
+    'tdesign:thunderstorm-sunny',
+    'tdesign:ticket',
+    'tdesign:time',
+    'tdesign:time-filled',
+    'tdesign:tips',
+    'tdesign:tips-double',
+    'tdesign:tomato',
+    'tdesign:tools',
+    'tdesign:tools-circle',
+    'tdesign:tornado',
+    'tdesign:tower',
+    'tdesign:tower-1',
+    'tdesign:tower-2',
+    'tdesign:tower-3',
+    'tdesign:tower-clock',
+    'tdesign:town',
+    'tdesign:traffic',
+    'tdesign:traffic-events',
+    'tdesign:transform',
+    'tdesign:transform-1',
+    'tdesign:transform-2',
+    'tdesign:transform-3',
+    'tdesign:translate',
+    'tdesign:translate-1',
+    'tdesign:tree-list',
+    'tdesign:tree-round-dot',
+    'tdesign:tree-round-dot-vertical',
+    'tdesign:tree-square-dot',
+    'tdesign:tree-square-dot-vertical',
+    'tdesign:trending-down',
+    'tdesign:trending-up',
+    'tdesign:tv',
+    'tdesign:tv-1',
+    'tdesign:tv-2',
+    'tdesign:typography',
+    'tdesign:uncomfortable',
+    'tdesign:uncomfortable-1',
+    'tdesign:uncomfortable-2',
+    'tdesign:undertake',
+    'tdesign:undertake-delivery',
+    'tdesign:undertake-environment-protection',
+    'tdesign:undertake-hold-up',
+    'tdesign:undertake-transaction',
+    'tdesign:unfold-less',
+    'tdesign:unfold-more',
+    'tdesign:unhappy',
+    'tdesign:unhappy-1',
+    'tdesign:uninstall',
+    'tdesign:upload',
+    'tdesign:upload-1',
+    'tdesign:upscale',
+    'tdesign:usb',
+    'tdesign:user',
+    'tdesign:user-1',
+    'tdesign:user-add',
+    'tdesign:user-arrow-down',
+    'tdesign:user-arrow-left',
+    'tdesign:user-arrow-right',
+    'tdesign:user-arrow-up',
+    'tdesign:user-avatar',
+    'tdesign:user-blocked',
+    'tdesign:user-business',
+    'tdesign:user-checked',
+    'tdesign:user-checked-1',
+    'tdesign:user-circle',
+    'tdesign:user-clear',
+    'tdesign:user-error-1',
+    'tdesign:user-invisible',
+    'tdesign:user-list',
+    'tdesign:user-locked',
+    'tdesign:user-marked',
+    'tdesign:user-password',
+    'tdesign:user-safety',
+    'tdesign:user-search',
+    'tdesign:user-setting',
+    'tdesign:user-talk',
+    'tdesign:user-talk-1',
+    'tdesign:user-talk-off-1',
+    'tdesign:user-time',
+    'tdesign:user-transmit',
+    'tdesign:user-unknown',
+    'tdesign:user-unlocked',
+    'tdesign:user-vip',
+    'tdesign:user-visible',
+    'tdesign:usergroup',
+    'tdesign:usergroup-add',
+    'tdesign:usergroup-clear',
+    'tdesign:vehicle',
+    'tdesign:verified',
+    'tdesign:verify',
+    'tdesign:video',
+    'tdesign:video-camera',
+    'tdesign:video-camera-1',
+    'tdesign:video-camera-2',
+    'tdesign:video-camera-dollar',
+    'tdesign:video-camera-minus',
+    'tdesign:video-camera-music',
+    'tdesign:video-camera-off',
+    'tdesign:video-library',
+    'tdesign:view-agenda',
+    'tdesign:view-column',
+    'tdesign:view-in-ar',
+    'tdesign:view-list',
+    'tdesign:view-module',
+    'tdesign:visual-recognition',
+    'tdesign:wallet',
+    'tdesign:watch',
+    'tdesign:watermelon',
+    'tdesign:wave-left',
+    'tdesign:wave-right',
+    'tdesign:wealth',
+    'tdesign:wealth-1',
+    'tdesign:widget',
+    'tdesign:wifi',
+    'tdesign:wifi-1',
+    'tdesign:wifi-off',
+    'tdesign:wifi-off-1',
+    'tdesign:window',
+    'tdesign:window-1',
+    'tdesign:windy',
+    'tdesign:windy-rain',
+    'tdesign:wink',
+    'tdesign:work',
+    'tdesign:work-history',
+    'tdesign:work-off',
+    'tdesign:wry-smile',
+    'tdesign:zoom-in',
+    'tdesign:zoom-out'
+  ]
+}

+ 4 - 0
src/components/JsonEditor/index.ts

@@ -0,0 +1,4 @@
+import JsonEditor from './src/JsonEditor.vue'
+export type { JsonEditorProps } from './src/types'
+
+export { JsonEditor }

+ 98 - 0
src/components/JsonEditor/src/JsonEditor.vue

@@ -0,0 +1,98 @@
+<script setup lang="ts">
+import VueJsonPretty from 'vue-json-pretty'
+import 'vue-json-pretty/lib/styles.css'
+import { propTypes } from '@/utils/propTypes'
+import { computed } from 'vue'
+
+const emits = defineEmits([
+  'update:modelValue',
+  'node-click',
+  'brackets-click',
+  'icon-click',
+  'selected-value'
+])
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    default: () => ({})
+  },
+  deep: propTypes.number.def(5),
+  showLength: propTypes.bool.def(true),
+  showLineNumbers: propTypes.bool.def(true),
+  showLineNumber: propTypes.bool.def(true),
+  showIcon: propTypes.bool.def(true),
+  showDoubleQuotes: propTypes.bool.def(true),
+  virtual: propTypes.bool.def(false),
+  height: propTypes.number.def(400),
+  itemHeight: propTypes.number.def(20),
+  rootPath: propTypes.string.def('root'),
+  nodeSelectable: propTypes.func.def(),
+  selectableType: propTypes.oneOf<'multiple' | 'single'>(['multiple', 'single']).def(),
+  showSelectController: propTypes.bool.def(false),
+  selectOnClickNode: propTypes.bool.def(true),
+  highlightSelectedNode: propTypes.bool.def(true),
+  collapsedOnClickBrackets: propTypes.bool.def(true),
+  renderNodeKey: propTypes.func.def(),
+  renderNodeValue: propTypes.func.def(),
+  editable: propTypes.bool.def(true),
+  editableTrigger: propTypes.oneOf<'click' | 'dblclick'>(['click', 'dblclick']).def('click')
+})
+
+const data = computed(() => props.modelValue)
+
+const localModelValue = computed({
+  get: () => data.value,
+  set: (val) => {
+    console.log(val)
+    emits('update:modelValue', val)
+  }
+})
+
+const nodeClick = (node: any) => {
+  emits('node-click', node)
+}
+
+const bracketsClick = (collapsed: boolean) => {
+  emits('brackets-click', collapsed)
+}
+
+const iconClick = (collapsed: boolean) => {
+  emits('icon-click', collapsed)
+}
+
+const selectedChange = (newVal: any, oldVal: any) => {
+  console.log(newVal, oldVal)
+  emits('selected-value', newVal, oldVal)
+}
+</script>
+
+<template>
+  <VueJsonPretty
+    v-model:data="localModelValue"
+    :deep="deep"
+    :show-length="showLength"
+    :show-line-numbers="showLineNumbers"
+    :show-line-number="showLineNumber"
+    :show-icon="showIcon"
+    :show-double-quotes="showDoubleQuotes"
+    :virtual="virtual"
+    :height="height"
+    :item-height="itemHeight"
+    :root-path="rootPath"
+    :node-selectable="nodeSelectable"
+    :selectable-type="selectableType"
+    :show-select-controller="showSelectController"
+    :select-on-click-node="selectOnClickNode"
+    :highlight-selected-node="highlightSelectedNode"
+    :collapsed-on-click-brackets="collapsedOnClickBrackets"
+    :render-node-key="renderNodeKey"
+    :render-node-value="renderNodeValue"
+    :editable="editable"
+    :editable-trigger="editableTrigger"
+    @node-click="nodeClick"
+    @brackets-click="bracketsClick"
+    @icon-click="iconClick"
+    @selected-change="selectedChange"
+  />
+</template>

+ 23 - 0
src/components/JsonEditor/src/types/index.ts

@@ -0,0 +1,23 @@
+export interface JsonEditorProps {
+  value: any
+  deep?: number
+  showLength?: boolean
+  showLineNumbers?: boolean
+  showLineNumber?: boolean
+  showIcon?: boolean
+  showDoubleQuotes?: boolean
+  virtual?: boolean
+  height?: number
+  itemHeight?: number
+  rootPath?: string
+  nodeSelectable?: (...args: any[]) => boolean
+  selectableType?: 'multiple' | 'single'
+  showSelectController?: boolean
+  selectOnClickNode?: boolean
+  highlightSelectedNode?: boolean
+  collapsedOnClickBrackets?: boolean
+  renderNodeKey?: (...args: any[]) => any
+  renderNodeValue?: (...args: any[]) => any
+  editable?: boolean
+  editableTrigger?: 'click' | 'dblclick'
+}

+ 1 - 2
src/components/Menu/src/components/useRenderMenuItem.tsx

@@ -1,5 +1,4 @@
 import { ElSubMenu, ElMenuItem } from 'element-plus'
-import type { RouteMeta } from 'vue-router'
 import { hasOneShowingChild } from '../helper'
 import { isUrl } from '@/utils/is'
 import { useRenderMenuTitle } from './useRenderMenuTitle'
@@ -14,7 +13,7 @@ export const useRenderMenuItem = (
 ) => {
   const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
     return routers.map((v) => {
-      const meta = (v.meta ?? {}) as RouteMeta
+      const meta = v.meta ?? {}
       if (!meta.hidden) {
         const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
         const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')

+ 1 - 2
src/components/Menu/src/helper.ts

@@ -1,4 +1,3 @@
-import type { RouteMeta } from 'vue-router'
 import { ref, unref } from 'vue'
 import { findPath } from '@/utils/tree'
 
@@ -21,7 +20,7 @@ export const hasOneShowingChild = (
   const onlyOneChild = ref<OnlyOneChildType>()
 
   const showingChildren = children.filter((v) => {
-    const meta = (v.meta ?? {}) as RouteMeta
+    const meta = v.meta ?? {}
     if (meta.hidden) {
       return false
     } else {

+ 14 - 1
src/components/Setting/src/components/InterfaceDisplay.vue

@@ -108,13 +108,21 @@ const greyModeChange = (show: boolean) => {
 }
 
 // 动态路由
-const dynamicRouter = ref(appStore.getDynamicRouter)
+const dynamicRouter = ref(!!appStore.getDynamicRouter)
 
 const dynamicRouterChange = (show: boolean) => {
   ElMessage.info(t('setting.reExperienced'))
   appStore.setDynamicRouter(show)
 }
 
+// 服务端动态路由
+const serverDynamicRouter = ref(appStore.getServerDynamicRouter)
+
+const serverDynamicRouterChange = (show: boolean) => {
+  ElMessage.info(t('setting.reExperienced'))
+  appStore.setServerDynamicRouter(show)
+}
+
 // 固定菜单
 const fixedMenu = ref(appStore.getFixedMenu)
 
@@ -206,6 +214,11 @@ watch(
       <ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
     </div>
 
+    <div class="flex justify-between items-center">
+      <span class="text-14px">{{ t('setting.serverDynamicRouter') }}</span>
+      <ElSwitch v-model="serverDynamicRouter" @change="serverDynamicRouterChange" />
+    </div>
+
     <div class="flex justify-between items-center">
       <span class="text-14px">{{ t('setting.fixedMenu') }}</span>
       <ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />

+ 14 - 9
src/components/TabMenu/src/TabMenu.vue

@@ -3,7 +3,7 @@ import { usePermissionStore } from '@/store/modules/permission'
 import { useAppStore } from '@/store/modules/app'
 import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
-import { ElScrollbar } from 'element-plus'
+import { ElScrollbar, ClickOutside } from 'element-plus'
 import { Icon } from '@/components/Icon'
 import { Menu } from '@/components/Menu'
 import { useRouter } from 'vue-router'
@@ -19,6 +19,9 @@ const prefixCls = getPrefixCls('tab-menu')
 
 export default defineComponent({
   name: 'TabMenu',
+  directives: {
+    ClickOutside
+  },
   setup() {
     const { push, currentRoute } = useRouter()
 
@@ -105,7 +108,8 @@ export default defineComponent({
       tabActive.value = item.children ? item.path : item.path.split('/')[0]
       if (item.children) {
         if (newPath === oldPath || !unref(showMenu)) {
-          showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
+          // showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
+          showMenu.value = !unref(showMenu)
         }
         if (unref(showMenu)) {
           permissionStore.setMenuTabRouters(
@@ -131,14 +135,16 @@ export default defineComponent({
       return false
     }
 
-    const mouseleave = () => {
-      if (!unref(showMenu) || unref(fixedMenu)) return
-      showMenu.value = false
+    const clickOut = () => {
+      if (!unref(fixedMenu)) {
+        showMenu.value = false
+      }
     }
 
     return () => (
       <div
         id={`${variables.namespace}-menu`}
+        v-click-outside={clickOut}
         class={[
           prefixCls,
           'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',
@@ -147,7 +153,6 @@ export default defineComponent({
             'w-[var(--tab-menu-min-width)]': unref(collapse)
           }
         ]}
-        onMouseleave={mouseleave}
       >
         <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
           <div>
@@ -178,7 +183,7 @@ export default defineComponent({
                       <Icon icon={item?.meta?.icon}></Icon>
                     </div>
                     {!unref(showTitle) ? undefined : (
-                      <p class="break-words mt-5px px-2px">{t(item.meta?.title)}</p>
+                      <p class="break-words mt-5px px-2px">{t(item.meta?.title || '')}</p>
                     )}
                   </div>
                 )
@@ -197,11 +202,11 @@ export default defineComponent({
         </div>
         <Menu
           class={[
-            '!absolute top-0',
+            '!absolute top-0 z-1000',
             {
               '!left-[var(--tab-menu-min-width)]': unref(collapse),
               '!left-[var(--tab-menu-max-width)]': !unref(collapse),
-              '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
+              '!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
               '!w-0': !unref(showMenu) && !unref(fixedMenu)
             }
           ]}

+ 2 - 3
src/components/TabMenu/src/helper.ts

@@ -1,5 +1,4 @@
 import { getAllParentPath } from '@/components/Menu/src/helper'
-import type { RouteMeta } from 'vue-router'
 import { isUrl } from '@/utils/is'
 import { cloneDeep } from 'lodash-es'
 import { reactive } from 'vue'
@@ -12,7 +11,7 @@ export const tabPathMap = reactive<TabMapTypes>({})
 
 export const initTabMap = (routes: AppRouteRecordRaw[]) => {
   for (const v of routes) {
-    const meta = (v.meta ?? {}) as RouteMeta
+    const meta = v.meta ?? {}
     if (!meta?.hidden) {
       tabPathMap[v.path] = []
     }
@@ -26,7 +25,7 @@ export const filterMenusPath = (
   const res: AppRouteRecordRaw[] = []
   for (const v of routes) {
     let data: Nullable<AppRouteRecordRaw> = null
-    const meta = (v.meta ?? {}) as RouteMeta
+    const meta = v.meta ?? {}
     if (!meta.hidden || meta.canTo) {
       const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
 

+ 1 - 47
src/components/Table/src/Table.vue

@@ -15,8 +15,6 @@ import { set, get } from 'lodash-es'
 import { CSSProperties } from 'vue'
 import { getSlot } from '@/utils/tsxHelper'
 import TableActions from './components/TableActions.vue'
-// import Sortable from 'sortablejs'
-// import { Icon } from '@/components/Icon'
 
 export default defineComponent({
   name: 'Table',
@@ -32,8 +30,6 @@ export default defineComponent({
       type: Array as PropType<TableColumn[]>,
       default: () => []
     },
-    // 展开行
-    // expand: propTypes.bool.def(false),
     // 是否展示分页
     pagination: {
       type: Object as PropType<Pagination>,
@@ -62,7 +58,6 @@ export default defineComponent({
       type: Array as PropType<string[]>,
       default: () => []
     },
-    // sortable: propTypes.bool.def(false),
     height: propTypes.oneOfType([Number, String]),
     maxHeight: propTypes.oneOfType([Number, String]),
     stripe: propTypes.bool.def(false),
@@ -188,7 +183,7 @@ export default defineComponent({
     scrollbarAlwaysOn: propTypes.bool.def(false),
     flexible: propTypes.bool.def(false)
   },
-  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
+  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
   setup(props, { attrs, emit, slots, expose }) {
     const elTableRef = ref<ComponentRef<typeof ElTable>>()
 
@@ -213,33 +208,6 @@ export default defineComponent({
       return propsObj
     })
 
-    // const sortableEl = ref()
-    // 初始化拖拽
-    // const initDropTable = () => {
-    //   const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
-    //   if (!el) return
-    //   if (unref(sortableEl)) unref(sortableEl).destroy()
-
-    //   sortableEl.value = Sortable.create(el, {
-    //     handle: '.table-move',
-    //     animation: 180,
-    //     onEnd(e: any) {
-    //       emit('sortable-change', e)
-    //     }
-    //   })
-    // }
-
-    // watch(
-    //   () => getProps.value.sortable,
-    //   async (v) => {
-    //     await nextTick()
-    //     v && initDropTable()
-    //   },
-    //   {
-    //     immediate: true
-    //   }
-    // )
-
     const setProps = (props: TableProps = {}) => {
       mergeProps.value = Object.assign(unref(mergeProps), props)
       outsideProps.value = { ...props } as any
@@ -495,20 +463,6 @@ export default defineComponent({
         tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
       }
 
-      // const { sortable } = unref(getProps)
-
-      // const sortableEl = sortable ? (
-      //   <ElTableColumn
-      //     className="table-move cursor-move"
-      //     type="sortable"
-      //     prop="sortable"
-      //     width="60px"
-      //     align="center"
-      //   >
-      //     <Icon icon="ant-design:drag-outlined" />
-      //   </ElTableColumn>
-      // ) : null
-
       return (
         <div v-loading={unref(getProps).loading}>
           {unref(getProps).showAction ? (

+ 3 - 70
src/components/Table/src/components/TableActions.vue

@@ -1,20 +1,10 @@
 <script lang="tsx">
-import { defineComponent, unref, computed, PropType, watch } from 'vue'
-import {
-  ElTooltip,
-  ElDropdown,
-  ElDropdownMenu,
-  ElDropdownItem,
-  ComponentSize
-  // ElPopover,
-  // ElTree
-} from 'element-plus'
+import { defineComponent, unref, computed, PropType } from 'vue'
+import { ElTooltip, ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
 import { Icon } from '@/components/Icon'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useAppStore } from '@/store/modules/app'
 import { TableColumn } from '../types'
-import { cloneDeep } from 'lodash-es'
-// import { eachTree } from '@/utils/tree'
 
 const appStore = useAppStore()
 const sizeMap = computed(() => appStore.sizeMap)
@@ -30,7 +20,7 @@ export default defineComponent({
     }
   },
   emits: ['refresh', 'changSize'],
-  setup(props, { emit }) {
+  setup(_, { emit }) {
     const refresh = () => {
       emit('refresh')
     }
@@ -39,25 +29,6 @@ export default defineComponent({
       emit('changSize', size)
     }
 
-    const columns = computed(() => {
-      return cloneDeep(props.columns).filter((v) => {
-        // 去掉type为selection的列和expand的列
-        if (v.type !== 'selection' && v.type !== 'expand') {
-          return v
-        }
-      })
-    })
-
-    watch(
-      () => columns.value,
-      (newColumns) => {
-        console.log('columns change:', newColumns)
-      },
-      {
-        deep: true
-      }
-    )
-
     return () => (
       <>
         <div class="text-right h-28px flex items-center justify-end">
@@ -105,44 +76,6 @@ export default defineComponent({
               }}
             </ElDropdown>
           </ElTooltip>
-
-          {/* <ElTooltip content={t('common.columnSetting')} placement="top"> */}
-          {/* <ElPopover trigger="click" placement="left">
-            {{
-              default: () => {
-                return (
-                  <div>
-                    <ElTree
-                      data={unref(columns)}
-                      show-checkbox
-                      default-checked-keys={unref(defaultCheckeds)}
-                      draggable
-                      node-key="field"
-                      allow-drop={(_draggingNode: any, _dropNode: any, type: string) => {
-                        if (type === 'inner') {
-                          return false
-                        } else {
-                          return true
-                        }
-                      }}
-                      onNode-drag-end={onNodeDragEnd}
-                      onCheck-change={onCheckChange}
-                    />
-                  </div>
-                )
-              },
-              reference: () => {
-                return (
-                  <Icon
-                    icon="ant-design:setting-outlined"
-                    class="cursor-pointer"
-                    hoverColor="var(--el-color-primary)"
-                  />
-                )
-              }
-            }}
-          </ElPopover> */}
-          {/* </ElTooltip> */}
         </div>
       </>
     )

+ 2 - 2
src/components/TagsView/src/helper.ts

@@ -1,10 +1,10 @@
-import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
 import { pathResolve } from '@/utils/routerHelper'
 
 export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => {
   let tags: RouteLocationNormalizedLoaded[] = []
   routes.forEach((route) => {
-    const meta = route.meta as RouteMeta
+    const meta = route.meta ?? {}
     const tagPath = pathResolve(parentPath, route.path)
     if (meta?.affix) {
       tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded)

+ 1 - 0
src/config/axios/service.ts

@@ -27,6 +27,7 @@ axiosInstance.interceptors.response.use(
   (res: AxiosResponse) => {
     const url = res.config.url || ''
     abortControllerMap.delete(url)
+    // 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
     return res
   },
   (err: any) => err

+ 4 - 4
src/hooks/web/useCrudSchemas.ts

@@ -84,7 +84,7 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
         component: schemaItem?.search?.component || 'Input',
         ...schemaItem.search,
         field: schemaItem.field,
-        label: schemaItem.label
+        label: schemaItem.search?.label || schemaItem.label
       }
 
       // 删除不必要的字段
@@ -103,8 +103,8 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
     conversion: (schema: CrudSchema) => {
       if (!schema?.table?.hidden) {
         return {
-          ...schema.table,
-          ...schema
+          ...schema,
+          ...schema.table
         }
       }
     }
@@ -132,7 +132,7 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
         component: formItem?.form?.component || 'Input',
         ...formItem.form,
         field: formItem.field,
-        label: formItem.label
+        label: formItem.form?.label || formItem.label
       }
 
       // 删除不必要的字段

+ 49 - 0
src/hooks/web/useGuide.ts

@@ -0,0 +1,49 @@
+import { Config, driver } from 'driver.js'
+import 'driver.js/dist/driver.css'
+import { useDesign } from '@/hooks/web/useDesign'
+import { useI18n } from '@/hooks/web/useI18n'
+
+const { t } = useI18n()
+
+const { variables } = useDesign()
+
+export const useGuide = (options?: Config) => {
+  const driverObj = driver(
+    options || {
+      showProgress: true,
+      nextBtnText: t('common.nextLabel'),
+      prevBtnText: t('common.prevLabel'),
+      doneBtnText: t('common.doneLabel'),
+      steps: [
+        {
+          element: `#${variables.namespace}-menu`,
+          popover: {
+            title: t('common.menu'),
+            description: t('common.menuDes'),
+            side: 'right'
+          }
+        },
+        {
+          element: `#${variables.namespace}-tool-header`,
+          popover: {
+            title: t('common.tool'),
+            description: t('common.toolDes'),
+            side: 'left'
+          }
+        },
+        {
+          element: `#${variables.namespace}-tags-view`,
+          popover: {
+            title: t('common.tagsView'),
+            description: t('common.tagsViewDes'),
+            side: 'bottom'
+          }
+        }
+      ]
+    }
+  )
+
+  return {
+    ...driverObj
+  }
+}

+ 0 - 47
src/hooks/web/useIntro.ts

@@ -1,47 +0,0 @@
-import introJs from 'intro.js'
-import { IntroJs, Step, Options } from 'intro.js'
-import 'intro.js/introjs.css'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useDesign } from '@/hooks/web/useDesign'
-
-export const useIntro = (setps?: Step[], options?: Options) => {
-  const { t } = useI18n()
-
-  const { variables } = useDesign()
-
-  const defaultSetps: Step[] = setps || [
-    {
-      element: `#${variables.namespace}-menu`,
-      title: t('common.menu'),
-      intro: t('common.menuDes'),
-      position: 'right'
-    },
-    {
-      element: `#${variables.namespace}-tool-header`,
-      title: t('common.tool'),
-      intro: t('common.toolDes'),
-      position: 'left'
-    },
-    {
-      element: `#${variables.namespace}-tags-view`,
-      title: t('common.tagsView'),
-      intro: t('common.tagsViewDes'),
-      position: 'bottom'
-    }
-  ]
-
-  const defaultOptions: Options = options || {
-    prevLabel: t('common.prevLabel'),
-    nextLabel: t('common.nextLabel'),
-    skipLabel: t('common.skipLabel'),
-    doneLabel: t('common.doneLabel')
-  }
-
-  const introRef: IntroJs = introJs()
-
-  introRef.addSteps(defaultSetps).setOptions(defaultOptions)
-
-  return {
-    introRef
-  }
-}

+ 22 - 7
src/hooks/web/useStorage.ts

@@ -1,15 +1,21 @@
-import { isArray, isObject } from '@/utils/is'
+// 获取传入的值的类型
+const getValueType = (value: any) => {
+  const type = Object.prototype.toString.call(value)
+  return type.slice(8, -1)
+}
 
 export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
   const setStorage = (key: string, value: any) => {
-    window[type].setItem(key, isArray(value) || isObject(value) ? JSON.stringify(value) : value)
+    const valueType = getValueType(value)
+    window[type].setItem(key, JSON.stringify({ type: valueType, value }))
   }
 
   const getStorage = (key: string) => {
     const value = window[type].getItem(key)
-    try {
-      return JSON.parse(value || '')
-    } catch (error) {
+    if (value) {
+      const { value: val } = JSON.parse(value)
+      return val
+    } else {
       return value
     }
   }
@@ -18,8 +24,17 @@ export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionSto
     window[type].removeItem(key)
   }
 
-  const clear = () => {
-    window[type].clear()
+  const clear = (excludes?: string[]) => {
+    // 获取排除项
+    const keys = Object.keys(window[type])
+    const defaultExcludes = ['dynamicRouter', 'serverDynamicRouter']
+    const excludesArr = excludes ? [...excludes, ...defaultExcludes] : defaultExcludes
+    const excludesKeys = excludesArr ? keys.filter((key) => !excludesArr.includes(key)) : keys
+    // 排除项不清除
+    excludesKeys.forEach((key) => {
+      window[type].removeItem(key)
+    })
+    // window[type].clear()
   }
 
   return {

+ 48 - 44
src/hooks/web/useValidator.ts

@@ -1,76 +1,81 @@
 import { useI18n } from '@/hooks/web/useI18n'
+import { FormItemRule } from 'element-plus'
 
 const { t } = useI18n()
 
-type Callback = (error?: string | Error | undefined) => void
-
 interface LengthRange {
   min: number
   max: number
-  message: string
+  message?: string
 }
 
 export const useValidator = () => {
-  const required = (message?: string) => {
+  const required = (message?: string): FormItemRule => {
     return {
       required: true,
       message: message || t('common.required')
     }
   }
 
-  const lengthRange = (val: any, callback: Callback, options: LengthRange) => {
+  const lengthRange = (options: LengthRange): FormItemRule => {
     const { min, max, message } = options
-    if (val.length < min || val.length > max) {
-      callback(new Error(message))
-    } else {
-      callback()
-    }
-  }
 
-  const notSpace = (val: any, callback: Callback, message: string) => {
-    // 用户名不能有空格
-    if (val.indexOf(' ') !== -1) {
-      callback(new Error(message))
-    } else {
-      callback()
+    return {
+      min,
+      max,
+      message: message || t('common.lengthRange', { min, max })
     }
   }
 
-  const notSpecialCharacters = (val: any, callback: Callback, message: string) => {
-    // 密码不能是特殊字符
-    if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
-      callback(new Error(message))
-    } else {
-      callback()
+  const notSpace = (message?: string): FormItemRule => {
+    return {
+      validator: (_, val, callback) => {
+        if (val?.indexOf(' ') !== -1) {
+          callback(new Error(message || t('common.notSpace')))
+        } else {
+          callback()
+        }
+      }
     }
   }
 
-  // 两个字符串是否想等
-  const isEqual = (val1: string, val2: string, callback: Callback, message: string) => {
-    if (val1 === val2) {
-      callback()
-    } else {
-      callback(new Error(message))
+  const notSpecialCharacters = (message?: string): FormItemRule => {
+    return {
+      validator: (_, val, callback) => {
+        if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
+          callback(new Error(message || t('common.notSpecialCharacters')))
+        } else {
+          callback()
+        }
+      }
     }
   }
 
-  const isEmail = (val: any, callback: Callback, message: string) => {
-    // 判断是否是邮箱
-    if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/g.test(val)) {
-      callback(new Error(message))
-    } else {
-      callback()
+  const isEmail = (message: string): FormItemRule => {
+    return {
+      validator: (_, val, callback) => {
+        // 判断是否是邮箱
+        if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/g.test(val)) {
+          callback(new Error(message))
+        } else {
+          callback()
+        }
+      }
     }
   }
 
-  const isMobilePhone = (val: any, callback: Callback, message: string) => {
-    // 判断是否是手机号
-    if (
-      !/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/g.test(val)
-    ) {
-      callback(new Error(message))
-    } else {
-      callback()
+  const isMobilePhone = (message: string): FormItemRule => {
+    return {
+      validator: (_, val, callback) => {
+        // 判断是否是手机号
+        if (
+          !/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/g.test(val)
+        ) {
+          callback(new Error(message))
+        } else {
+          callback()
+        }
+      }
     }
   }
 
@@ -79,7 +84,6 @@ export const useValidator = () => {
     lengthRange,
     notSpace,
     notSpecialCharacters,
-    isEqual,
     isEmail,
     isMobilePhone
   }

+ 16 - 5
src/locales/en.ts

@@ -44,7 +44,11 @@ export default {
     refresh: 'Refresh',
     fullscreen: 'Fullscreen',
     size: 'Size',
-    columnSetting: 'Column setting'
+    columnSetting: 'Column setting',
+    lengthRange: 'The length should be between {min} and {max}',
+    notSpace: 'Spaces are not allowed',
+    notSpecialCharacters: 'Special characters are not allowed',
+    isEqual: 'The two are not equal'
   },
   lock: {
     lockScreen: 'Lock screen',
@@ -89,7 +93,9 @@ export default {
     footer: 'Footer',
     uniqueOpened: 'Unique opened',
     tagsViewIcon: 'Tags view icon',
-    dynamicRouter: 'Dynamic router',
+    // 开启动态路由
+    dynamicRouter: 'Enable dynamic router',
+    serverDynamicRouter: 'Server dynamic router',
     reExperienced: 'Please exit the login experience again',
     fixedMenu: 'Fixed menu'
   },
@@ -143,6 +149,7 @@ export default {
     defaultTable: 'Basic example',
     editor: 'Editor',
     richText: 'Rich text',
+    jsonEditor: 'JSON Editor',
     dialog: 'Dialog',
     imageViewer: 'Image viewer',
     descriptions: 'Descriptions',
@@ -167,7 +174,8 @@ export default {
     permission: 'Permission test page',
     function: 'Function',
     multipleTabs: 'Multiple tabs',
-    details: 'Details'
+    details: 'Details',
+    iconPicker: 'Icon picker'
   },
   permission: {
     hasPermission: 'Please set the operation permission value'
@@ -296,6 +304,7 @@ export default {
     verifyReset: 'Verify reset',
     // 富文本编辑器
     richText: 'Rich text',
+    jsonEditor: 'JSON Editor',
     form: 'Form',
     // 远程加载
     remoteLoading: 'Remote loading',
@@ -318,7 +327,7 @@ export default {
     guide: 'Guide',
     start: 'Start',
     message:
-      'The guide page is very useful for some people who enter the project for the first time. You can briefly introduce the functions of the project. The boot page is based on intro js'
+      'The guide page is very useful for some people who enter the project for the first time. You can briefly introduce the functions of the project. The boot page is based on driver.js'
   },
   iconDemo: {
     icon: 'Icon',
@@ -444,7 +453,9 @@ export default {
   },
   richText: {
     richText: 'Rich text',
-    richTextDes: 'Secondary packaging based on wangeditor'
+    richTextDes: 'Secondary packaging based on wangeditor',
+    jsonEditor: 'JSON Editor',
+    jsonEditorDes: 'Secondary packaging based on vue-json-pretty'
   },
   dialogDemo: {
     dialog: 'Dialog',

+ 16 - 5
src/locales/zh-CN.ts

@@ -44,7 +44,11 @@ export default {
     refresh: '刷新',
     fullscreen: '全屏',
     size: '尺寸',
-    columnSetting: '列设置'
+    columnSetting: '列设置',
+    lengthRange: '长度在 {min} 到 {max} 个字符',
+    notSpace: '不能包含空格',
+    notSpecialCharacters: '不能包含特殊字符',
+    isEqual: '两次输入不一致'
   },
   lock: {
     lockScreen: '锁定屏幕',
@@ -89,7 +93,8 @@ export default {
     footer: '页脚',
     uniqueOpened: '菜单手风琴',
     tagsViewIcon: '标签页图标',
-    dynamicRouter: '动态路由',
+    dynamicRouter: '开启动态路由',
+    serverDynamicRouter: '服务端动态路由',
     reExperienced: '请重新退出登录体验',
     fixedMenu: '固定菜单'
   },
@@ -143,6 +148,7 @@ export default {
     defaultTable: '基础示例',
     editor: '编辑器',
     richText: '富文本',
+    jsonEditor: 'JSON编辑器',
     dialog: '弹窗',
     imageViewer: '图片预览',
     descriptions: '描述',
@@ -166,7 +172,8 @@ export default {
     permission: '权限测试页',
     function: '功能',
     multipleTabs: '多开标签页',
-    details: '详情页'
+    details: '详情页',
+    iconPicker: '图标选择器'
   },
   permission: {
     hasPermission: '请设置操作权限值'
@@ -294,6 +301,8 @@ export default {
     verifyReset: '验证重置',
     // 富文本编辑器
     richText: '富文本编辑器',
+    // JSON编辑器
+    jsonEditor: 'JSON编辑器',
     form: '表单',
     // 远程加载
     remoteLoading: '远程加载',
@@ -313,7 +322,7 @@ export default {
     guide: '引导页',
     start: '开始',
     message:
-      '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 intro.js'
+      '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 driver.js'
   },
   iconDemo: {
     icon: '图标',
@@ -437,7 +446,9 @@ export default {
   },
   richText: {
     richText: '富文本',
-    richTextDes: '基于 wangeditor 二次封装'
+    richTextDes: '基于 wangeditor 二次封装',
+    jsonEditor: 'JSON编辑器',
+    jsonEditorDes: '基于 vue-json-pretty 二次封装'
   },
   dialogDemo: {
     dialog: '弹窗',

+ 4 - 17
src/permission.ts

@@ -5,16 +5,12 @@ import type { RouteRecordRaw } from 'vue-router'
 import { useTitle } from '@/hooks/web/useTitle'
 import { useNProgress } from '@/hooks/web/useNProgress'
 import { usePermissionStoreWithOut } from '@/store/modules/permission'
-// import { useDictStoreWithOut } from '@/store/modules/dict'
 import { usePageLoading } from '@/hooks/web/usePageLoading'
-// import { getDictApi } from '@/api/common'
 
 const permissionStore = usePermissionStoreWithOut()
 
 const appStore = useAppStoreWithOut()
 
-// const dictStore = useDictStoreWithOut()
-
 const { getStorage } = useStorage()
 
 const { start, done } = useNProgress()
@@ -30,14 +26,6 @@ router.beforeEach(async (to, from, next) => {
     if (to.path === '/login') {
       next({ path: '/' })
     } else {
-      // if (!dictStore.getIsSetDict) {
-      //   // 获取所有字典
-      //   const res = await getDictApi()
-      //   if (res) {
-      //     dictStore.setDictObj(res.data)
-      //     dictStore.setIsSetDict(true)
-      //   }
-      // }
       if (permissionStore.getIsAddRouters) {
         next()
         return
@@ -45,15 +33,14 @@ router.beforeEach(async (to, from, next) => {
 
       // 开发者可根据实际情况进行修改
       const roleRouters = getStorage('roleRouters') || []
-      const userInfo = getStorage(appStore.getUserInfo)
 
       // 是否使用动态路由
       if (appStore.getDynamicRouter) {
-        userInfo.role === 'admin'
-          ? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
-          : await permissionStore.generateRoutes('test', roleRouters as string[])
+        appStore.serverDynamicRouter
+          ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
+          : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
       } else {
-        await permissionStore.generateRoutes('none')
+        await permissionStore.generateRoutes('static')
       }
 
       permissionStore.getAddRouters.forEach((route) => {

+ 354 - 2
src/router/index.ts

@@ -95,7 +95,106 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         component: () => import('@/views/Manage/News/NewsPage.vue'),
         name: 'NewsPage',
         meta: {
-          title: '文案管理'
+          title: t('router.form'),
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'default-form',
+            component: () => import('@/views/Components/Form/DefaultForm.vue'),
+            name: 'DefaultForm',
+            meta: {
+              title: t('router.defaultForm')
+            }
+          },
+          {
+            path: 'use-form',
+            component: () => import('@/views/Components/Form/UseFormDemo.vue'),
+            name: 'UseForm',
+            meta: {
+              title: 'UseForm'
+            }
+          }
+        ]
+      },
+      {
+        path: 'table',
+        component: getParentLayout(),
+        redirect: '/components/table/default-table',
+        name: 'TableDemo',
+        meta: {
+          title: t('router.table'),
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'default-table',
+            component: () => import('@/views/Components/Table/DefaultTable.vue'),
+            name: 'DefaultTable',
+            meta: {
+              title: t('router.defaultTable')
+            }
+          },
+          {
+            path: 'use-table',
+            component: () => import('@/views/Components/Table/UseTableDemo.vue'),
+            name: 'UseTable',
+            meta: {
+              title: 'UseTable'
+            }
+          },
+          {
+            path: 'tree-table',
+            component: () => import('@/views/Components/Table/TreeTable.vue'),
+            name: 'TreeTable',
+            meta: {
+              title: t('router.treeTable')
+            }
+          },
+          {
+            path: 'table-image-preview',
+            component: () => import('@/views/Components/Table/TableImagePreview.vue'),
+            name: 'TableImagePreview',
+            meta: {
+              title: t('router.PicturePreview')
+            }
+          }
+        ]
+      },
+      {
+        path: 'editor-demo',
+        component: getParentLayout(),
+        redirect: '/components/editor-demo/editor',
+        name: 'EditorDemo',
+        meta: {
+          title: t('router.editor'),
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'editor',
+            component: () => import('@/views/Components/Editor/Editor.vue'),
+            name: 'Editor',
+            meta: {
+              title: t('router.richText')
+            }
+          },
+          {
+            path: 'json-editor',
+            component: () => import('@/views/Components/Editor/JsonEditor.vue'),
+            name: 'JsonEditor',
+            meta: {
+              title: t('router.jsonEditor')
+            }
+          }
+        ]
+      },
+      {
+        path: 'search',
+        component: () => import('@/views/Components/Search.vue'),
+        name: 'Search',
+        meta: {
+          title: t('router.search')
         }
       },
       {
@@ -103,7 +202,260 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         component: () => import('@/views/Manage/News/NewsAdd.vue'),
         name: 'NewsAdd',
         meta: {
-          title: '新增文案',
+          title: t('router.descriptions')
+        }
+      },
+      {
+        path: 'image-viewer',
+        component: () => import('@/views/Components/ImageViewer.vue'),
+        name: 'ImageViewer',
+        meta: {
+          title: t('router.imageViewer')
+        }
+      },
+      {
+        path: 'dialog',
+        component: () => import('@/views/Components/Dialog.vue'),
+        name: 'Dialog',
+        meta: {
+          title: t('router.dialog')
+        }
+      },
+      {
+        path: 'icon',
+        component: () => import('@/views/Components/Icon.vue'),
+        name: 'Icon',
+        meta: {
+          title: t('router.icon')
+        }
+      },
+      {
+        path: 'icon-picker',
+        component: () => import('@/views/Components/IconPicker.vue'),
+        name: 'IconPicker',
+        meta: {
+          title: t('router.iconPicker')
+        }
+      },
+      {
+        path: 'echart',
+        component: () => import('@/views/Components/Echart.vue'),
+        name: 'Echart',
+        meta: {
+          title: t('router.echart')
+        }
+      },
+      {
+        path: 'count-to',
+        component: () => import('@/views/Components/CountTo.vue'),
+        name: 'CountTo',
+        meta: {
+          title: t('router.countTo')
+        }
+      },
+      {
+        path: 'qrcode',
+        component: () => import('@/views/Components/Qrcode.vue'),
+        name: 'Qrcode',
+        meta: {
+          title: t('router.qrcode')
+        }
+      },
+      {
+        path: 'highlight',
+        component: () => import('@/views/Components/Highlight.vue'),
+        name: 'Highlight',
+        meta: {
+          title: t('router.highlight')
+        }
+      },
+      {
+        path: 'infotip',
+        component: () => import('@/views/Components/Infotip.vue'),
+        name: 'Infotip',
+        meta: {
+          title: t('router.infotip')
+        }
+      },
+      {
+        path: 'input-password',
+        component: () => import('@/views/Components/InputPassword.vue'),
+        name: 'InputPassword',
+        meta: {
+          title: t('router.inputPassword')
+        }
+      }
+    ]
+  },
+  {
+    path: '/function',
+    component: Layout,
+    redirect: '/function/multipleTabs',
+    name: 'Function',
+    meta: {
+      title: t('router.function'),
+      icon: 'ri:function-fill',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'multiple-tabs',
+        component: () => import('@/views/Function/MultipleTabs.vue'),
+        name: 'MultipleTabs',
+        meta: {
+          title: t('router.multipleTabs')
+        }
+      },
+      {
+        path: 'multiple-tabs-demo/:id',
+        component: () => import('@/views/Function/MultipleTabsDemo.vue'),
+        name: 'MultipleTabsDemo',
+        meta: {
+          hidden: true,
+          title: t('router.details'),
+          canTo: true,
+          activeMenu: '/function/multiple-tabs'
+        }
+      }
+    ]
+  },
+  {
+    path: '/hooks',
+    component: Layout,
+    redirect: '/hooks/useWatermark',
+    name: 'Hooks',
+    meta: {
+      title: 'hooks',
+      icon: 'ic:outline-webhook',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'useWatermark',
+        component: () => import('@/views/hooks/useWatermark.vue'),
+        name: 'UseWatermark',
+        meta: {
+          title: 'useWatermark'
+        }
+      },
+      {
+        path: 'useTagsView',
+        component: () => import('@/views/hooks/useTagsView.vue'),
+        name: 'UseTagsView',
+        meta: {
+          title: 'useTagsView'
+        }
+      },
+      {
+        path: 'useValidator',
+        component: () => import('@/views/hooks/useValidator.vue'),
+        name: 'UseValidator',
+        meta: {
+          title: 'useValidator'
+        }
+      },
+      {
+        path: 'useCrudSchemas',
+        component: () => import('@/views/hooks/useCrudSchemas.vue'),
+        name: 'UseCrudSchemas',
+        meta: {
+          title: 'useCrudSchemas'
+        }
+      }
+    ]
+  },
+  {
+    path: '/level',
+    component: Layout,
+    redirect: '/level/menu1/menu1-1/menu1-1-1',
+    name: 'Level',
+    meta: {
+      title: t('router.level'),
+      icon: 'carbon:skill-level-advanced'
+    },
+    children: [
+      {
+        path: 'menu1',
+        name: 'Menu1',
+        component: getParentLayout(),
+        redirect: '/level/menu1/menu1-1/menu1-1-1',
+        meta: {
+          title: t('router.menu1')
+        },
+        children: [
+          {
+            path: 'menu1-1',
+            name: 'Menu11',
+            component: getParentLayout(),
+            redirect: '/level/menu1/menu1-1/menu1-1-1',
+            meta: {
+              title: t('router.menu11'),
+              alwaysShow: true
+            },
+            children: [
+              {
+                path: 'menu1-1-1',
+                name: 'Menu111',
+                component: () => import('@/views/Level/Menu111.vue'),
+                meta: {
+                  title: t('router.menu111')
+                }
+              }
+            ]
+          },
+          {
+            path: 'menu1-2',
+            name: 'Menu12',
+            component: () => import('@/views/Level/Menu12.vue'),
+            meta: {
+              title: t('router.menu12')
+            }
+          }
+        ]
+      },
+      {
+        path: 'menu2',
+        name: 'Menu2',
+        component: () => import('@/views/Level/Menu2.vue'),
+        meta: {
+          title: t('router.menu2')
+        }
+      }
+    ]
+  },
+  {
+    path: '/example',
+    component: Layout,
+    redirect: '/example/example-dialog',
+    name: 'Example',
+    meta: {
+      title: t('router.example'),
+      icon: 'ep:management',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'example-dialog',
+        component: () => import('@/views/Example/Dialog/ExampleDialog.vue'),
+        name: 'ExampleDialog',
+        meta: {
+          title: t('router.exampleDialog')
+        }
+      },
+      {
+        path: 'example-page',
+        component: () => import('@/views/Example/Page/ExamplePage.vue'),
+        name: 'ExamplePage',
+        meta: {
+          title: t('router.examplePage')
+        }
+      },
+      {
+        path: 'example-add',
+        component: () => import('@/views/Example/Page/ExampleAdd.vue'),
+        name: 'ExampleAdd',
+        meta: {
+          title: t('router.exampleAdd'),
           noTagsView: true,
           noCache: true,
           hidden: true,

+ 12 - 4
src/store/modules/app.ts

@@ -21,6 +21,7 @@ interface AppState {
   fixedHeader: boolean
   greyMode: boolean
   dynamicRouter: boolean
+  serverDynamicRouter: boolean
   pageLoading: boolean
   layout: LayoutType
   title: string
@@ -42,7 +43,6 @@ export const useAppStore = defineStore('app', {
       mobile: false, // 是否是移动端
       title: import.meta.env.VITE_APP_TITLE, // 标题
       pageLoading: false, // 路由跳转loading
-
       breadcrumb: true, // 面包屑
       breadcrumbIcon: false, // 面包屑图标
       collapse: false, // 折叠菜单
@@ -57,11 +57,12 @@ export const useAppStore = defineStore('app', {
       fixedHeader: true, // 固定toolheader
       footer: false, // 显示页脚
       greyMode: false, // 是否开始灰色模式,用于特殊悼念日
-      dynamicRouter: false, // 是否动态路由
-      fixedMenu: getStorage('fixedMenu') || true, // 是否固定菜单
+      dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
+      serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
+      fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
 
       layout: getStorage('layout') || 'classic', // layout布局
-      isDark: false, // 是否是暗黑模式
+      isDark: false, // 是否是暗黑模式 getStorage('isDark')
       currentSize: getStorage('default') || 'default', // 组件尺寸
       theme: getStorage('theme') || {
         // 主题色
@@ -138,6 +139,9 @@ export const useAppStore = defineStore('app', {
     getDynamicRouter(): boolean {
       return this.dynamicRouter
     },
+    getServerDynamicRouter(): boolean {
+      return this.serverDynamicRouter
+    },
     getFixedMenu(): boolean {
       return this.fixedMenu
     },
@@ -216,6 +220,10 @@ export const useAppStore = defineStore('app', {
       setStorage('dynamicRouter', dynamicRouter)
       this.dynamicRouter = dynamicRouter
     },
+    setServerDynamicRouter(serverDynamicRouter: boolean) {
+      setStorage('serverDynamicRouter', serverDynamicRouter)
+      this.serverDynamicRouter = serverDynamicRouter
+    },
     setFixedMenu(fixedMenu: boolean) {
       setStorage('fixedMenu', fixedMenu)
       this.fixedMenu = fixedMenu

+ 0 - 34
src/store/modules/dict.ts

@@ -1,34 +0,0 @@
-import { defineStore } from 'pinia'
-import { store } from '../index'
-
-export interface DictState {
-  isSetDict: boolean
-  dictObj: Recordable
-}
-
-export const useDictStore = defineStore('dict', {
-  state: (): DictState => ({
-    isSetDict: false,
-    dictObj: {}
-  }),
-  getters: {
-    getDictObj(): Recordable {
-      return this.dictObj
-    },
-    getIsSetDict(): boolean {
-      return this.isSetDict
-    }
-  },
-  actions: {
-    setDictObj(dictObj: Recordable) {
-      this.dictObj = dictObj
-    },
-    setIsSetDict(isSetDict: boolean) {
-      this.isSetDict = isSetDict
-    }
-  }
-})
-
-export const useDictStoreWithOut = () => {
-  return useDictStore(store)
-}

+ 10 - 6
src/store/modules/permission.ts

@@ -1,6 +1,10 @@
 import { defineStore } from 'pinia'
 import { asyncRouterMap, constantRouterMap } from '@/router'
-import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
+import {
+  generateRoutesByFrontEnd,
+  generateRoutesByServer,
+  flatMultiLevelRoutes
+} from '@/utils/routerHelper'
 import { store } from '../index'
 import { cloneDeep } from 'lodash-es'
 
@@ -34,17 +38,17 @@ export const usePermissionStore = defineStore('permission', {
   },
   actions: {
     generateRoutes(
-      type: 'admin' | 'test' | 'none',
+      type: 'server' | 'frontEnd' | 'static',
       routers?: AppCustomRouteRecordRaw[] | string[]
     ): Promise<unknown> {
       return new Promise<void>((resolve) => {
         let routerMap: AppRouteRecordRaw[] = []
-        if (type === 'admin') {
+        if (type === 'server') {
           // 模拟后端过滤菜单
-          routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
-        } else if (type === 'test') {
+          routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
+        } else if (type === 'frontEnd') {
           // 模拟前端过滤菜单
-          routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
+          routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
         } else {
           // 直接读取静态路由表
           routerMap = cloneDeep(asyncRouterMap)

+ 10 - 2
src/store/modules/tagsView.ts

@@ -4,6 +4,12 @@ import { getRawRoute } from '@/utils/routerHelper'
 import { defineStore } from 'pinia'
 import { store } from '../index'
 import { findIndex } from '@/utils'
+import { useStorage } from '@/hooks/web/useStorage'
+import { useAppStoreWithOut } from './app'
+
+const appStore = useAppStoreWithOut()
+
+const { getStorage } = useStorage()
 
 export interface TagsViewState {
   visitedViews: RouteLocationNormalizedLoaded[]
@@ -49,7 +55,7 @@ export const useTagsViewStore = defineStore('tagsView', {
       const cacheMap: Set<string> = new Set()
       for (const v of this.visitedViews) {
         const item = getRawRoute(v)
-        const needCache = !item.meta?.noCache
+        const needCache = !item?.meta?.noCache
         if (!needCache) {
           continue
         }
@@ -90,7 +96,9 @@ export const useTagsViewStore = defineStore('tagsView', {
     // 删除所有tag
     delAllVisitedViews() {
       // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
-      this.visitedViews = this.visitedViews.filter((tag) => tag.meta?.affix)
+      this.visitedViews = getStorage(appStore.getUserInfo)
+        ? this.visitedViews.filter((tag) => tag?.meta?.affix)
+        : []
     },
     // 删除其它
     delOthersViews(view: RouteLocationNormalizedLoaded) {

+ 11 - 8
src/utils/routerHelper.ts

@@ -3,7 +3,6 @@ import type {
   Router,
   RouteLocationNormalized,
   RouteRecordNormalized,
-  RouteMeta,
   RouteRecordRaw
 } from 'vue-router'
 import { isUrl } from '@/utils/is'
@@ -39,7 +38,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
 }
 
 // 前端控制路由生成
-export const generateRoutesFn1 = (
+export const generateRoutesByFrontEnd = (
   routes: AppRouteRecordRaw[],
   keys: string[],
   basePath = '/'
@@ -47,7 +46,7 @@ export const generateRoutesFn1 = (
   const res: AppRouteRecordRaw[] = []
 
   for (const route of routes) {
-    const meta = route.meta as RouteMeta
+    const meta = route.meta ?? {}
     // skip some route
     if (meta.hidden && !meta.canTo) {
       continue
@@ -70,7 +69,7 @@ export const generateRoutesFn1 = (
       if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
         data = Object.assign({}, route)
       } else {
-        const routePath = onlyOneChild ?? pathResolve(basePath, route.path)
+        const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
         if (routePath === item || meta.followRoute === item) {
           data = Object.assign({}, route)
         }
@@ -79,7 +78,11 @@ export const generateRoutesFn1 = (
 
     // recursive child routes
     if (route.children && data) {
-      data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
+      data.children = generateRoutesByFrontEnd(
+        route.children,
+        keys,
+        pathResolve(basePath, data.path)
+      )
     }
     if (data) {
       res.push(data as AppRouteRecordRaw)
@@ -89,7 +92,7 @@ export const generateRoutesFn1 = (
 }
 
 // 后端控制路由生成
-export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
+export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
   const res: AppRouteRecordRaw[] = []
 
   for (const route of routes) {
@@ -112,7 +115,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
     }
     // recursive child routes
     if (route.children) {
-      data.children = generateRoutesFn2(route.children)
+      data.children = generateRoutesByServer(route.children)
     }
     res.push(data as AppRouteRecordRaw)
   }
@@ -122,7 +125,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
 export const pathResolve = (parentPath: string, path: string) => {
   if (isUrl(path)) return path
   const childPath = path.startsWith('/') || !path ? path : `/${path}`
-  return `${parentPath}${childPath}`.replace(/\/\//g, '/')
+  return `${parentPath}${childPath}`.replace(/\/\//g, '/').trim()
 }
 
 // 路由降级

+ 36 - 0
src/views/Components/Editor/JsonEditor.vue

@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { JsonEditor } from '@/components/JsonEditor'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ref, watch } from 'vue'
+
+const { t } = useI18n()
+
+const defaultData = ref({
+  title: '标题',
+  content: '内容'
+})
+
+watch(
+  () => defaultData.value,
+  (val) => {
+    console.log(val)
+  },
+  {
+    deep: true
+  }
+)
+
+setTimeout(() => {
+  defaultData.value = {
+    title: '异步标题',
+    content: '异步内容'
+  }
+}, 4000)
+</script>
+
+<template>
+  <ContentWrap :title="t('richText.jsonEditor')" :message="t('richText.jsonEditorDes')">
+    <JsonEditor v-model="defaultData" />
+  </ContentWrap>
+</template>

+ 26 - 2
src/views/Components/Form/DefaultForm.vue

@@ -442,6 +442,15 @@ const treeSelectData = [
   }
 ]
 
+// 模拟远程加载
+const getTreeSelectData = () => {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(treeSelectData)
+    }, 3000)
+  })
+}
+
 let id = 0
 
 const imageUrl = ref('')
@@ -1533,8 +1542,9 @@ const schema = reactive<FormSchema[]>([
     label: `${t('formDemo.treeSelect')}`,
     component: 'TreeSelect',
     // 远程加载option
-    optionApi: () => {
-      return treeSelectData
+    optionApi: async () => {
+      const res = await getTreeSelectData()
+      return res
     }
   },
   {
@@ -1750,6 +1760,20 @@ const schema = reactive<FormSchema[]>([
         )
       }
     }
+  },
+  {
+    field: 'field85',
+    component: 'Divider',
+    label: t('formDemo.jsonEditor')
+  },
+  {
+    field: 'field86',
+    component: 'JsonEditor',
+    label: t('formDemo.default'),
+    value: {
+      a: 1,
+      b: 2
+    }
   }
 ])
 </script>

+ 16 - 0
src/views/Components/IconPicker.vue

@@ -0,0 +1,16 @@
+<script setup lang="ts">
+import { IconPicker } from '@/components/IconPicker'
+import { ref } from 'vue'
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+
+const { t } = useI18n()
+
+const currentIcon = ref('tdesign:book-open')
+</script>
+
+<template>
+  <ContentWrap :title="t('router.iconPicker')">
+    <IconPicker v-model="currentIcon" />
+  </ContentWrap>
+</template>

+ 19 - 0
src/views/Function/MultipleTabsDemo2.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { ElInput } from 'element-plus'
+import { ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { useTagsView } from '@/hooks/web/useTagsView'
+
+const { setTitle } = useTagsView()
+
+const { query } = useRoute()
+
+const val = ref(query.id as string)
+
+setTitle(`详情页query-${val.value}`)
+</script>
+
+<template>
+  <ContentWrap> 获取参数: <ElInput v-model="val" /> </ContentWrap>
+</template>

+ 3 - 3
src/views/Guide/Guide.vue

@@ -1,15 +1,15 @@
 <script setup lang="ts">
 import { ContentWrap } from '@/components/ContentWrap'
 import { useI18n } from '@/hooks/web/useI18n'
-import { useIntro } from '@/hooks/web/useIntro'
 import { ElButton } from 'element-plus'
+import { useGuide } from '@/hooks/web/useGuide'
 
 const { t } = useI18n()
 
-const { introRef } = useIntro()
+const { drive } = useGuide()
 
 const guideStart = () => {
-  introRef.start()
+  drive()
 }
 </script>
 

+ 1 - 1
src/views/Login/Login.vue

@@ -34,7 +34,7 @@ const toLogin = () => {
     class="h-[100%] relative lt-xl:bg-[var(--login-bg-color)] lt-sm:px-10px lt-xl:px-10px lt-md:px-10px"
   >
     <ElScrollbar class="h-full">
-      <div class="relative flex mx-auto h-100vh">
+      <div class="relative flex mx-auto min-h-100vh">
         <div
           :class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
         >

+ 7 - 15
src/views/Login/components/LoginForm.vue

@@ -200,13 +200,6 @@ watch(
 
 // 登录
 const signIn = async () => {
-  await permissionStore.generateRoutes('none').catch(() => {})
-  permissionStore.getAddRouters.forEach((route) => {
-    addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
-  })
-  permissionStore.setIsAddRouters(true)
-  push({ path: redirect.value || permissionStore.addRouters[0].path })
-
   const formRef = await getElFormExpose()
   await formRef?.validate(async (isValid) => {
     if (isValid) {
@@ -223,7 +216,7 @@ const signIn = async () => {
           if (appStore.getDynamicRouter) {
             getRole()
           } else {
-            await permissionStore.generateRoutes('none').catch(() => {})
+            await permissionStore.generateRoutes('static').catch(() => {})
             permissionStore.getAddRouters.forEach((route) => {
               addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
             })
@@ -244,17 +237,16 @@ const getRole = async () => {
   const params = {
     roleName: formData.account
   }
-  // admin - 模拟后端过滤菜单
-  // test - 模拟前端过滤菜单
   const res =
-    formData.account === 'admin' ? await getAdminRoleApi(params) : await getTestRoleApi(params)
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await getAdminRoleApi(params)
+      : await getTestRoleApi(params)
   if (res) {
     const routers = res.data || []
     setStorage('roleRouters', routers)
-
-    formData.account === 'admin'
-      ? await permissionStore.generateRoutes('admin', routers).catch(() => {})
-      : await permissionStore.generateRoutes('test', routers).catch(() => {})
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
 
     permissionStore.getAddRouters.forEach((route) => {
       addRoute(route as RouteRecordRaw) // 动态添加可访问路由表

+ 177 - 214
src/views/hooks/useCrudSchemas.vue

@@ -1,223 +1,186 @@
 <script setup lang="ts">
-// import { ContentWrap } from '@/components/ContentWrap'
-// import { Search } from '@/components/Search'
-// import { useI18n } from '@/hooks/web/useI18n'
-// import { ElButton, ElTag } from 'element-plus'
-// import { Table } from '@/components/Table'
-// import { getTableListApi, delTableListApi } from '@/api/table'
-// import { useTable } from '@/hooks/web/useTable'
-// import { TableData } from '@/api/table/types'
-// import { h, ref, reactive } from 'vue'
-// import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-// import { useDictStore } from '@/store/modules/dict'
-// import { getDictOneApi } from '@/api/common'
-// import { TableColumn } from '@/types/table'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { useI18n } from '@/hooks/web/useI18n'
+import { reactive } from 'vue'
+import { JsonEditor } from '@/components/JsonEditor'
+import { ContentWrap } from '@/components/ContentWrap'
+import { ElRow, ElCol } from 'element-plus'
 
-// const dictStore = useDictStore()
+const { t } = useI18n()
 
-// const { register, tableObject, methods } = useTable<TableData>({
-//   getListApi: getTableListApi,
-//   delListApi: delTableListApi,
-//   response: {
-//     list: 'list',
-//     total: 'total'
-//   }
-// })
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    field: 'selection',
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'selection'
+    }
+  },
+  {
+    field: 'index',
+    label: t('tableDemo.index'),
+    type: 'index',
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    }
+  },
+  {
+    field: 'title',
+    label: t('tableDemo.title'),
+    search: {
+      component: 'Input'
+    },
+    form: {
+      component: 'Input',
+      colProps: {
+        span: 24
+      }
+    },
+    detail: {
+      span: 24
+    }
+  },
+  {
+    field: 'author',
+    label: t('tableDemo.author'),
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'display_time',
+    label: t('tableDemo.displayTime'),
+    search: {
+      hidden: true
+    },
+    form: {
+      component: 'DatePicker',
+      componentProps: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss'
+      }
+    }
+  },
+  {
+    field: 'importance',
+    label: t('tableDemo.importance'),
+    search: {
+      hidden: true
+    },
+    form: {
+      component: 'Select',
+      componentProps: {
+        style: {
+          width: '100%'
+        },
+        options: [
+          {
+            label: '重要',
+            value: 3
+          },
+          {
+            label: '良好',
+            value: 2
+          },
+          {
+            label: '一般',
+            value: 1
+          }
+        ]
+      }
+    }
+  },
+  {
+    field: 'pageviews',
+    label: t('tableDemo.pageviews'),
+    search: {
+      hidden: true
+    },
+    form: {
+      component: 'InputNumber',
+      value: 0
+    }
+  },
+  {
+    field: 'content',
+    label: t('exampleDemo.content'),
+    search: {
+      hidden: true
+    },
+    table: {
+      show: false
+    },
+    form: {
+      component: 'Editor',
+      colProps: {
+        span: 24
+      }
+    },
+    detail: {
+      span: 24
+    }
+  },
+  {
+    field: 'action',
+    width: '260px',
+    label: t('tableDemo.action'),
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    }
+  }
+])
 
-// const { getList, setSearchParams } = methods
-
-// getList()
-
-// const { t } = useI18n()
-
-// const crudSchemas = reactive<CrudSchema[]>([
-//   {
-//     field: 'index',
-//     label: t('tableDemo.index'),
-//     type: 'index',
-//     form: {
-//       show: false
-//     },
-//     detail: {
-//       show: false
-//     }
-//   },
-//   {
-//     field: 'title',
-//     label: t('tableDemo.title'),
-//     search: {
-//       show: true
-//     },
-//     form: {
-//       colProps: {
-//         span: 24
-//       }
-//     },
-//     detail: {
-//       span: 24
-//     }
-//   },
-//   {
-//     field: 'author',
-//     label: t('tableDemo.author')
-//   },
-//   {
-//     field: 'display_time',
-//     label: t('tableDemo.displayTime'),
-//     form: {
-//       component: 'DatePicker',
-//       componentProps: {
-//         type: 'datetime',
-//         valueFormat: 'YYYY-MM-DD HH:mm:ss'
-//       }
-//     }
-//   },
-//   {
-//     field: 'importance',
-//     label: t('tableDemo.importance'),
-//     formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
-//       return h(
-//         ElTag,
-//         {
-//           type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
-//         },
-//         () =>
-//           cellValue === 1
-//             ? t('tableDemo.important')
-//             : cellValue === 2
-//             ? t('tableDemo.good')
-//             : t('tableDemo.commonly')
-//       )
-//     },
-//     search: {
-//       show: true,
-//       component: 'Select',
-//       componentProps: {
-//         options: dictStore.getDictObj.importance
-//       }
-//     },
-//     form: {
-//       component: 'Select',
-//       componentProps: {
-//         options: [
-//           {
-//             label: '重要',
-//             value: 3
-//           },
-//           {
-//             label: '良好',
-//             value: 2
-//           },
-//           {
-//             label: '一般',
-//             value: 1
-//           }
-//         ]
-//       }
-//     }
-//   },
-//   {
-//     field: 'importance2',
-//     label: `${t('tableDemo.importance')}2`,
-//     search: {
-//       show: true,
-//       component: 'Select',
-//       dictName: 'importance'
-//     }
-//   },
-//   {
-//     field: 'importance3',
-//     label: `${t('tableDemo.importance')}3`,
-//     search: {
-//       show: true,
-//       component: 'Select',
-//       api: async () => {
-//         const res = await getDictOneApi()
-//         return res.data
-//       }
-//     }
-//   },
-//   {
-//     field: 'pageviews',
-//     label: t('tableDemo.pageviews'),
-//     form: {
-//       component: 'InputNumber',
-//       value: 0
-//     }
-//   },
-//   {
-//     field: 'content',
-//     label: t('exampleDemo.content'),
-//     table: {
-//       show: false
-//     },
-//     form: {
-//       component: 'Editor',
-//       colProps: {
-//         span: 24
-//       }
-//     },
-//     detail: {
-//       span: 24
-//     }
-//   },
-//   {
-//     field: 'action',
-//     width: '260px',
-//     label: t('tableDemo.action'),
-//     form: {
-//       show: false
-//     },
-//     detail: {
-//       show: false
-//     }
-//   }
-// ])
-
-// const { allSchemas } = useCrudSchemas(crudSchemas)
-
-// const delLoading = ref(false)
-
-// const delData = async (row: TableData | null, multiple: boolean) => {
-//   tableObject.currentRow = row
-//   const { delList, getSelections } = methods
-//   const selections = await getSelections()
-//   delLoading.value = true
-//   await delList(
-//     multiple ? selections.map((v) => v.id) : [tableObject.currentRow?.id as string],
-//     multiple
-//   ).finally(() => {
-//     delLoading.value = false
-//   })
-// }
+const { allSchemas } = useCrudSchemas(crudSchemas)
 </script>
 
 <template>
-  <ContentWrap>
-    <!-- <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <ElButton :loading="delLoading" type="danger" @click="delData(null, true)">
-        {{ t('exampleDemo.del') }}
-      </ElButton>
-    </div>
-
-    <Table
-      v-model:pageSize="tableObject.pageSize"
-      v-model:currentPage="tableObject.currentPage"
-      :columns="allSchemas.tableColumns"
-      :data="tableObject.tableList"
-      :loading="tableObject.loading"
-      :pagination="{
-        total: tableObject.total
-      }"
-      @register="register"
-    >
-      <template #action="{ row }">
-        <ElButton type="danger" @click="delData(row, false)">
-          {{ t('exampleDemo.del') }}
-        </ElButton>
-      </template>
-    </Table> -->
+  <ContentWrap title="useCrudSchemas">
+    <ElRow :gutter="20">
+      <ElCol :span="24">
+        <ContentWrap title="原始数据数据" class="mt-20px">
+          <JsonEditor v-model="crudSchemas" />
+        </ContentWrap>
+      </ElCol>
+      <ElCol :span="24">
+        <ContentWrap title="查询组件数据结构" class="mt-20px">
+          <JsonEditor v-model="allSchemas.searchSchema" />
+        </ContentWrap>
+      </ElCol>
+      <ElCol :span="24">
+        <ContentWrap title="表单组件数据结构" class="mt-20px">
+          <JsonEditor v-model="allSchemas.formSchema" />
+        </ContentWrap>
+      </ElCol>
+      <ElCol :span="24">
+        <ContentWrap title="表格组件数据结构" class="mt-20px">
+          <JsonEditor v-model="allSchemas.tableColumns" />
+        </ContentWrap>
+      </ElCol>
+      <ElCol :span="24">
+        <ContentWrap title="表格组件数据结构" class="mt-20px">
+          <JsonEditor v-model="allSchemas.detailSchema" />
+        </ContentWrap>
+      </ElCol>
+    </ElRow>
   </ContentWrap>
 </template>

+ 3 - 3
src/views/hooks/useTagsView.vue

@@ -11,7 +11,7 @@ const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage,
 
 const closeAllTabs = () => {
   closeAll(() => {
-    push('/dashboard/workplace')
+    push('/dashboard/analysis')
   })
 }
 
@@ -33,7 +33,7 @@ const refresh = () => {
 
 const closeCurrentTab = () => {
   closeCurrent(undefined, () => {
-    push('/dashboard/workplace')
+    push('/dashboard/analysis')
   })
 }
 
@@ -42,7 +42,7 @@ const setTabTitle = () => {
 }
 
 const setAnalysisTitle = () => {
-  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/workplace')
+  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')
 }
 </script>
 

+ 80 - 0
src/views/hooks/useValidator.vue

@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { Form, FormSchema } from '@/components/Form'
+import { useValidator } from '@/hooks/web/useValidator'
+import { useForm } from '@/hooks/web/useForm'
+import { reactive } from 'vue'
+import { FormItemRule } from 'element-plus'
+
+const { formRegister, formMethods } = useForm()
+
+const { getFormData } = formMethods
+
+const { required, lengthRange, notSpace, notSpecialCharacters } = useValidator()
+
+const formSchema = reactive<FormSchema[]>([
+  {
+    field: 'field1',
+    label: '必填',
+    component: 'Input'
+  },
+  {
+    field: 'field2',
+    label: '长度范围',
+    component: 'Input'
+  },
+  {
+    field: 'field3',
+    label: '不能有空格',
+    component: 'Input'
+  },
+  {
+    field: 'field4',
+    label: '不能有特殊字符',
+    component: 'Input'
+  },
+  {
+    field: 'field5',
+    label: '是否相等-值1',
+    component: 'Input'
+  },
+  {
+    field: 'field6',
+    label: '是否相等-值2',
+    component: 'Input'
+  }
+])
+
+const rules = reactive<{
+  [key: string]: FormItemRule[]
+}>({
+  field1: [required()],
+  field2: [
+    lengthRange({
+      min: 2,
+      max: 5
+    })
+  ],
+  field3: [notSpace()],
+  field4: [notSpecialCharacters()],
+  field5: [
+    {
+      asyncValidator: async (_, val, callback) => {
+        const formData = await getFormData()
+        const { field6 } = formData
+        if (val !== field6) {
+          callback(new Error('两个值不相等'))
+        } else {
+          callback()
+        }
+      }
+    }
+  ]
+})
+</script>
+
+<template>
+  <ContentWrap title="useValidator">
+    <Form :schema="formSchema" :rules="rules" @register="formRegister" />
+  </ContentWrap>
+</template>

+ 1 - 3
tsconfig.json

@@ -27,10 +27,8 @@
       "@intlify/unplugin-vue-i18n/types",
       "vite/client",
       "element-plus/global",
-      "@types/intro.js",
       "@types/qrcode",
-      "vite-plugin-svg-icons/client",
-      "unplugin-vue-define-options/macros-global"
+      "vite-plugin-svg-icons/client"
     ]
   },
   "include": ["src", "types/**/*.d.ts", "mock/**/*.ts"]

+ 2 - 2
types/components.d.ts

@@ -1,7 +1,7 @@
 declare module 'vue' {
   export interface GlobalComponents {
-    Icon: typeof import('../components/Icon/src/Icon.vue')['default']
-    Permission: typeof import('../components/Permission/src/Permission.vue')['default']
+    Icon: (typeof import('../components/Icon/src/Icon.vue'))['default']
+    Permission: (typeof import('../components/Permission/src/Permission.vue'))['default']
   }
 }
 

+ 18 - 16
types/router.d.ts

@@ -32,21 +32,23 @@ import { defineComponent } from 'vue'
     permission: ['edit','add', 'delete']    设置该路由的权限
   }
 **/
+
+interface RouteMetaCustom extends Record<string | number | symbol, unknown> {
+  hidden?: boolean
+  alwaysShow?: boolean
+  title?: string
+  icon?: string
+  noCache?: boolean
+  breadcrumb?: boolean
+  affix?: boolean
+  activeMenu?: string
+  noTagsView?: boolean
+  canTo?: boolean
+  permission?: string[]
+}
+
 declare module 'vue-router' {
-  interface RouteMeta extends Record<string | number | symbol, unknown> {
-    hidden?: boolean
-    alwaysShow?: boolean
-    title?: string
-    icon?: string
-    noCache?: boolean
-    breadcrumb?: boolean
-    affix?: boolean
-    activeMenu?: string
-    noTagsView?: boolean
-    followAuth?: string
-    canTo?: boolean
-    permission?: string[]
-  }
+  interface RouteMeta extends RouteMetaCustom {}
 }
 
 type Component<T = any> =
@@ -57,7 +59,7 @@ type Component<T = any> =
 declare global {
   declare interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
     name: string
-    meta: RouteMeta
+    meta: RouteMetaCustom
     component?: Component | string
     children?: AppRouteRecordRaw[]
     props?: Recordable
@@ -66,7 +68,7 @@ declare global {
 
   declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
     name: string
-    meta: RouteMeta
+    meta: RouteMetaCustom
     component: string
     path: string
     redirect: string

+ 10 - 6
vite.config.ts

@@ -10,7 +10,6 @@ import { ViteEjsPlugin } from "vite-plugin-ejs"
 import PurgeIcons from 'vite-plugin-purge-icons'
 import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
-import DefineOptions from "unplugin-vue-define-options/vite"
 import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
 import UnoCSS from 'unocss/vite'
 
@@ -32,9 +31,13 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
   return {
     base: env.VITE_BASE_PATH,
     plugins: [
-      Vue(),
+      Vue({
+        script: {
+          // 开启defineModel
+          defineModel: true
+        }
+      }),
       VueJsx(),
-      // WindiCSS(),
       progress(),
       createStyleImportPlugin({
         resolves: [ElementPlusResolve()],
@@ -75,7 +78,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       //     setupProdMockServer()
       //     `
       // }),
-      DefineOptions(),
       ViteEjsPlugin({
         title: env.VITE_APP_TITLE
       }),
@@ -144,10 +146,12 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         'qs',
         'echarts',
         'echarts-wordcloud',
-        'intro.js',
         'qrcode',
         '@wangeditor/editor',
-        '@wangeditor/editor-for-vue'
+        '@wangeditor/editor-for-vue',
+        'vue-json-pretty',
+        '@zxcvbn-ts/core',
+        'dayjs'
       ]
     }
   }