Browse Source

git reset gfast-ui

yanglzh 3 years ago
parent
commit
f4fee64931
100 changed files with 12107 additions and 64 deletions
  1. 8 0
      .env
  2. 5 0
      .env.development
  3. 10 0
      .env.production
  4. 18 0
      .eslintignore
  5. 63 0
      .eslintrc.js
  6. 23 64
      .gitignore
  7. 39 0
      .prettierrc.js
  8. 307 0
      CHANGELOG.md
  9. 21 0
      LICENSE
  10. 32 0
      index.html
  11. 2171 0
      package-lock.json
  12. 80 0
      package.json
  13. 3 0
      plugins.d.ts
  14. BIN
      public/favicon.ico
  15. 13 0
      shim.d.ts
  16. 6 0
      source.d.ts
  17. 92 0
      src/App.vue
  18. 34 0
      src/api/login/index.ts
  19. 42 0
      src/api/system/config/index.ts
  20. 27 0
      src/api/system/dbInit/index.ts
  21. 36 0
      src/api/system/dept/index.ts
  22. 70 0
      src/api/system/dict/data.ts
  23. 43 0
      src/api/system/dict/type.ts
  24. 57 0
      src/api/system/menu/index.ts
  25. 28 0
      src/api/system/monitor/loginLog/index.ts
  26. 9 0
      src/api/system/monitor/server/index.ts
  27. 35 0
      src/api/system/post/index.ts
  28. 50 0
      src/api/system/role/index.ts
  29. 75 0
      src/api/system/user/index.ts
  30. BIN
      src/assets/401.png
  31. BIN
      src/assets/404.png
  32. BIN
      src/assets/bg.jpg
  33. 8 0
      src/assets/login-icon-two.svg
  34. 2 0
      src/assets/logo-mini.svg
  35. 27 0
      src/components/auth/auth.vue
  36. 28 0
      src/components/auth/authAll.vue
  37. 33 0
      src/components/auth/auths.vue
  38. 148 0
      src/components/cropper/index.vue
  39. 79 0
      src/components/editor/index.vue
  40. 242 0
      src/components/iconSelector/index.vue
  41. 194 0
      src/components/noticeBar/index.vue
  42. 101 0
      src/components/pagination/index.vue
  43. 42 0
      src/components/svgIcon/index.vue
  44. 56 0
      src/i18n/index.ts
  45. 181 0
      src/i18n/lang/en.ts
  46. 181 0
      src/i18n/lang/zh-cn.ts
  47. 181 0
      src/i18n/lang/zh-tw.ts
  48. 13 0
      src/i18n/pages/formI18n/en.ts
  49. 13 0
      src/i18n/pages/formI18n/zh-cn.ts
  50. 13 0
      src/i18n/pages/formI18n/zh-tw.ts
  51. 29 0
      src/i18n/pages/login/en.ts
  52. 28 0
      src/i18n/pages/login/zh-cn.ts
  53. 28 0
      src/i18n/pages/login/zh-tw.ts
  54. 157 0
      src/layout/component/aside.vue
  55. 297 0
      src/layout/component/columnsAside.vue
  56. 32 0
      src/layout/component/header.vue
  57. 84 0
      src/layout/component/main.vue
  58. 46 0
      src/layout/footer/index.vue
  59. 55 0
      src/layout/index.vue
  60. 370 0
      src/layout/lockScreen/index.vue
  61. 82 0
      src/layout/logo/index.vue
  62. 36 0
      src/layout/main/classic.vue
  63. 38 0
      src/layout/main/columns.vue
  64. 44 0
      src/layout/main/defaults.vue
  65. 16 0
      src/layout/main/transverse.vue
  66. 152 0
      src/layout/navBars/breadcrumb/breadcrumb.vue
  67. 63 0
      src/layout/navBars/breadcrumb/closeFull.vue
  68. 115 0
      src/layout/navBars/breadcrumb/index.vue
  69. 134 0
      src/layout/navBars/breadcrumb/search.vue
  70. 795 0
      src/layout/navBars/breadcrumb/setings.vue
  71. 302 0
      src/layout/navBars/breadcrumb/user.vue
  72. 114 0
      src/layout/navBars/breadcrumb/userNews.vue
  73. 37 0
      src/layout/navBars/index.vue
  74. 137 0
      src/layout/navBars/tagsView/contextmenu.vue
  75. 681 0
      src/layout/navBars/tagsView/tagsView.vue
  76. 151 0
      src/layout/navMenu/horizontal.vue
  77. 47 0
      src/layout/navMenu/subItem.vue
  78. 98 0
      src/layout/navMenu/vertical.vue
  79. 60 0
      src/layout/routerView/iframes.vue
  80. 59 0
      src/layout/routerView/link.vue
  81. 83 0
      src/layout/routerView/parent.vue
  82. 39 0
      src/main.ts
  83. 108 0
      src/router/backEnd.ts
  84. 25 0
      src/router/frontEnd.ts
  85. 254 0
      src/router/index.ts
  86. 1028 0
      src/router/route.ts
  87. 27 0
      src/store/index.ts
  88. 98 0
      src/store/interface/index.ts
  89. 23 0
      src/store/modules/keepAliveNames.ts
  90. 23 0
      src/store/modules/requestOldRoutes.ts
  91. 41 0
      src/store/modules/routesList.ts
  92. 34 0
      src/store/modules/tagsViewRoutes.ts
  93. 153 0
      src/store/modules/themeConfig.ts
  94. 47 0
      src/store/modules/userInfos.ts
  95. 283 0
      src/theme/app.scss
  96. 94 0
      src/theme/common/transition.scss
  97. 219 0
      src/theme/dark.scss
  98. 224 0
      src/theme/element.scss
  99. 70 0
      src/theme/iconSelector.scss
  100. 8 0
      src/theme/index.scss

+ 8 - 0
.env

@@ -0,0 +1,8 @@
+# port 端口号
+VITE_PORT = 8888
+
+# open 运行 npm run dev 时自动打开浏览器
+VITE_OPEN = false
+
+# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
+VITE_PUBLIC_PATH = /vue-next-admin-preview/

+ 5 - 0
.env.development

@@ -0,0 +1,5 @@
+# 本地环境
+ENV = 'development'
+
+# 本地环境接口地址
+VITE_API_URL = 'http://localhost:8201/'

+ 10 - 0
.env.production

@@ -0,0 +1,10 @@
+# 线上环境
+ENV = 'production'
+
+# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
+VITE_PUBLIC_PATH = /sys/
+
+# 线上环境接口地址
+VITE_API_URL = '/'
+
+

+ 18 - 0
.eslintignore

@@ -0,0 +1,18 @@
+
+*.sh
+node_modules
+lib
+*.md
+*.scss
+*.woff
+*.ttf
+.vscode
+.idea
+dist
+mock
+public
+bin
+build
+config
+index.html
+src/assets

+ 63 - 0
.eslintrc.js

@@ -0,0 +1,63 @@
+module.exports = {
+	root: true,
+	env: {
+		browser: true,
+		es2021: true,
+		node: true,
+	},
+	parser: 'vue-eslint-parser',
+	parserOptions: {
+		ecmaVersion: 12,
+		parser: '@typescript-eslint/parser',
+		sourceType: 'module',
+	},
+	extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
+	plugins: ['vue', '@typescript-eslint'],
+	rules: {
+		// http://eslint.cn/docs/rules/
+		// https://eslint.vuejs.org/rules/
+		'@type-eslint/ban-ts-ignore': 'off',
+		'@type-eslint/explicit-function-return-type': 'off',
+		'@type-eslint/no-explicit-any': 'off',
+		'@type-eslint/no-var-requires': 'off',
+		'@type-eslint/no-empty-function': 'off',
+		'@type-eslint/no-use-before-define': 'off',
+		'@type-eslint/ban-ts-comment': 'off',
+		'@type-eslint/ban-types': 'off',
+		'@type-eslint/no-non-null-assertion': 'off',
+		'@type-eslint/explicit-module-boundary-types': 'off',
+		'vue/custom-event-name-casing': 'off',
+		'vue/attributes-order': 'off',
+		'vue/one-component-per-file': 'off',
+		'vue/html-closing-bracket-newline': 'off',
+		'vue/max-attributes-per-line': 'off',
+		'vue/multiline-html-element-content-newline': 'off',
+		'vue/singleline-html-element-content-newline': 'off',
+		'vue/attribute-hyphenation': 'off',
+		'vue/html-self-closing': 'off',
+		'vue/no-multiple-template-root': 'off',
+		'vue/require-default-prop': 'off',
+		'vue/no-v-model-argument': 'off',
+		'vue/no-arrow-functions-in-watch': 'off',
+		'vue/no-template-key': 'off',
+		'vue/no-v-html': 'off',
+		'vue/comment-directive': 'off',
+		'vue/no-parsing-error': 'off',
+		'vue/no-deprecated-v-on-native-modifier': 'off',
+		'vue/multi-word-component-names': 'off',
+		'no-useless-escape': 'off',
+		'no-sparse-arrays': 'off',
+		'no-prototype-builtins': 'off',
+		'no-constant-condition': 'off',
+		'no-use-before-define': 'off',
+		'no-restricted-globals': 'off',
+		'no-restricted-syntax': 'off',
+		'generator-star-spacing': 'off',
+		'no-unreachable': 'off',
+		'no-multiple-template-root': 'off',
+		'no-unused-vars': 'error',
+		'no-v-model-argument': 'off',
+		'no-case-declarations': 'off',
+		'no-console': 'error',
+	},
+};

+ 23 - 64
.gitignore

@@ -1,64 +1,23 @@
-# ---> WebStorm
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff:
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/dictionaries
-
-# Sensitive or high-churn files:
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.xml
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-
-# Gradle:
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# CMake
-cmake-build-debug/
-
-# Mongo Explorer plugin:
-.idea/**/mongoSettings.xml
-
-## File-based project format:
-*.iws
-
-## Plugin-specific files:
-
-# IntelliJ
-/out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Ruby plugin and RubyMine
-/.rakeTasks
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-### WebStorm Patch ###
-# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
-
-# *.iml
-# modules.xml
-# .idea/misc.xml
-# *.ipr
-
-# Sonarlint plugin
-.idea/sonarlint
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 39 - 0
.prettierrc.js

@@ -0,0 +1,39 @@
+module.exports = {
+	// 一行最多多少个字符
+	printWidth: 150,
+	// 指定每个缩进级别的空格数
+	tabWidth: 2,
+	// 使用制表符而不是空格缩进行
+	useTabs: true,
+	// 在语句末尾打印分号
+	semi: true,
+	// 使用单引号而不是双引号
+	singleQuote: true,
+	// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
+	quoteProps: 'as-needed',
+	// 在JSX中使用单引号而不是双引号
+	jsxSingleQuote: false,
+	// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
+	trailingComma: 'es5',
+	// 在对象文字中的括号之间打印空格
+	bracketSpacing: true,
+	// jsx 标签的反尖括号需要换行
+	jsxBracketSameLine: false,
+	// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
+	arrowParens: 'always',
+	// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
+	rangeStart: 0,
+	rangeEnd: Infinity,
+	// 指定要使用的解析器,不需要写文件开头的 @prettier
+	requirePragma: false,
+	// 不需要自动在文件开头插入 @prettier
+	insertPragma: false,
+	// 使用默认的折行标准 always\never\preserve
+	proseWrap: 'preserve',
+	// 指定HTML文件的全局空格敏感度 css\strict\ignore
+	htmlWhitespaceSensitivity: 'css',
+	// Vue文件脚本和样式标签缩进
+	vueIndentScriptAndStyle: false,
+	// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
+	endOfLine: 'lf',
+};

+ 307 - 0
CHANGELOG.md

@@ -0,0 +1,307 @@
+# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
+
+🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
+
+## 2.0.2
+
+`2022.03.04`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 Alert 提示添加边框
+- 🎯 优化 功能 / 数字滚动 演示界面
+- 🐞 修复 全局主题按钮颜色 :active 问题
+- 🐞 修复 Dropdown 下拉菜单样式问题
+- 🐞 修复 SvgIcon 图标组件动态切换时报警告问题,[SvgIcon 改变 name 时可能导致图像不显示](https://gitee.com/lyt-top/vue-next-admin/issues/I4VGE0),感谢@axcc1234
+
+## 2.0.1
+
+`2022.02.25`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 svgIcon 图标组件
+- 🎯 优化 vite.config.ts 打包,感谢群友@YourObjec
+- 🐞 修复 tagViews 开启图标不显示问题(风格 5),感谢群友@坏人
+- 🐞 修复 [Element Plus 1.2.0-beta.6 以后的版本 el-table 在移动端无法左右滑动](https://gitee.com/lyt-top/vue-next-admin/issues/I4UPTP),感谢@YGDada
+
+## 2.0.0
+
+`2022.02.21`
+
+⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。演示界面建议直接覆盖文件。如需使用之前版本,请前往[gitee 发行版](https://gitee.com/lyt-top/vue-next-admin/releases) 进行对应版本下载。基础版会基于 `master` 分支进行修改
+
+- 🌟 更新 依赖更新最新版本
+- 🌟 更新 登录页、首页
+- 💔 移除 vue-web-screen-shot
+- 💔 移除 城市多级联动,完整 json 数据请去 [vue-next-admin-images/menu](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 仓库查看
+- 💔 移除 功能/echartsTree 树图
+- 💔 移除 其它设置/Tagsview 风格 2、Tagsview 风格 3
+- 💔 移除 功能/验证器
+- 🚧 调整 src/api 编写方式
+- 🚧 调整 自定义封装公用组件演示,更好的维护
+- 🎉 新增 Volar 支持,vs code 配置参考 [Vue Language Features (Volar)](https://lyt-top.gitee.io/vue-next-admin-doc-preview/home/vscode/)
+- 🎉 新增 `SvgIcon` 支持本地 svg 图标使用
+- 🎉 新增 表单表格验证演示
+- 🎯 优化 全局主题(移除 success、info、warning、danger)
+- 🎯 优化 工作流(开源)
+- 🎯 优化 element plus svg 图标,`elementXXX` 改成 `ele-XXX`
+- 🌈 重构 深色模式
+- 🌹 合并 [处理 parent 的 h100 由于外层有 min-height 导致失效的问题](https://gitee.com/lyt-top/vue-next-admin/pulls/20),感谢@MaxNull、@21030442-mao
+- 🐞 修复 element plus 升级 `^1.3.0-beta.5` 后 组件 size 大小问题(大改:涉及布局、演示界面)
+- 🐞 修复 vs code 使用 Vue Language Features (Volar) 插件 代码报红问题(可以把公用的 ts 类型定义封装起来公用)
+
+## 1.2.2
+
+`2021.12.21`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 iframes 滚动条问题
+- 🎯 优化 部署后每次都要强制刷新清浏览器缓存问题
+- 🎉 新增 工具类百分比验证演示
+- 🐞 修复 [tag-view 标签右键会超出浏览器 #I4KN78](https://gitee.com/lyt-top/vue-next-admin/issues/I4KN78)
+
+## 1.2.1
+
+`2021.12.12`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 cropper 裁剪时卡顿问题 [#I4M2VQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4M2VQ)
+- 🎯 优化 Wangeditor 富文本编辑器的问题 [#I4LPC1](https://gitee.com/lyt-top/vue-next-admin/issues/I4LPC1)、[#I4LM7I](https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I)
+- 🐞 修复 浏览器标题问题
+- 🐞 修复 element plus svg 图标引入
+- 🐞 修复 工作流不可以拖线连接问题
+
+## 1.2.0
+
+`2021.11.28`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 深色模式
+- 🎯 优化 `/@/utils` 文件夹,合并删除单一内容
+- 🎯 优化 系统设置:菜单管理(新增、修改)、角色管理(新增菜单权限)、用户管理、部门管理、字典管理
+- 🎯 优化 登录界面逻辑、权限管理逻辑
+- 🎯 优化 同步 [vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 后端控制菜单模拟数据
+- 🎉 新增 适配 Font Icon 向 SVG Icon 迁移(改动大,"element-plus": "^1.2.0-beta.4" 谨慎更新)
+- 🐞 修复 热更新问题,感谢@甜蜜蜜
+- 🐞 修复 页面/element 字体图标演示
+- 🐞 修复 功能/图标选择器演示,新增高级功能 [issues #I4GJXQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4GJXQ)
+
+## 1.1.2
+
+`2021.10.17`
+
+- 🌟 更新 依赖更新最新版本
+- 🐞 修复 开启全屏时,刷新界面被还原成未全屏的状态
+- 🎯 优化 tagsView 右键菜单关闭逻辑
+- 🎯 优化 wangeditor 富文本编辑器(增加双向绑定)
+- 🎉 新增 工作流(暂不开源)
+- 🎉 新增 基础版 ts(不带国际化),切换 `vue-next-admin-template` 分支
+
+## 1.1.1
+
+`2021.09.25`
+
+- 🌟 更新 依赖更新最新版本(`"element-plus": "^1.1.0-beta.13"` 版本运行错误,`^1.1.0-beta.16`修复横向菜单卡死问题)
+- 🐞 修复 Dialog 弹窗位置错误、Drawer 抽屉内边距、el-menu 菜单收起时背景色问题
+- 🎯 优化 锁屏界面自动锁屏(s/秒)必须设置至少 1 秒
+- 🎉 新增 分栏布局,鼠标移入当前项时,显示当前项菜单内容
+- 🎉 新增 工作流(未完成)
+
+## 1.1.0
+
+`2021.09.10`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 小屏模式下登录页二维码遮挡标题问题
+- 🎉 新增 图片验证器
+- 🎉 新增 动态复杂表单
+- 🎉 新增 工作流(未完成)
+- 🎉 新增 深色主题(伪深色,样式变动大,谨慎更新)
+
+## 1.0.18
+
+`2021.08.29`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 权限组件去掉顶级 div(`/src/components/auth`)
+- 🎉 新增 布局配置添加恢复默认按钮
+- 🐞 修复 升级 <a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">element plus 1.1.0-beta.7</a>后项目无法启动、el-menu 菜单
+- 🐞 修复 表格固定列时的层级、设置了相对定位时,遮挡左侧导航菜单问题
+
+## 1.0.17
+
+`2021.08.22`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 去除设置布局切换,重置主题样式(initSetLayoutChange),切换布局需手动设置样式,设置的样式自动同步各布局
+- 🎯 优化 Dropdown 下拉菜单用户账号靠边时换行问题
+- 🎯 优化 左侧导航菜单,共用菜单树,防止 `布局配置` 设置 `菜单 / 顶栏` 时,样式丢失等问题
+- 🐞 修复 固定 header 后没有回到顶部的 bug,拉取项目后运行不起来的 bug。<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/14" target="_blank">!14</a>,感谢<a href="https://gitee.com/wjs0509" target="_blank">@wjs0509</a>
+- 🐞 修复 tagView 右键全屏后,浏览器窗口大小发生任何变化都会导致左边菜单显示出来,并且可点击打开对应页面。<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I46E6T" target="_blank">I46E6T</a>
+- 🐞 修复 默认设置 `菜单 / 顶栏` 样式不生效问题(/@/src/store/modules/themeConfig.ts)
+
+## 1.0.16
+
+`2021.08.14`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 菜单高亮(详情且详情设置了 meta.isHide 时,顶级菜单高亮),感谢群友@YourObject
+- 🎯 优化 详情路径写法:如父级(/pages/filtering),那么详情为(/pages/filtering/details?id=1)。这样写可实现(详情时,父级菜单高亮),否则写成(/pages/filteringDetails?id=1)顶级菜单将不会高亮。可参考:`页面/过滤筛选组件`,点击当前图片进行测试
+- 🎯 优化 tagsView 右键菜单全屏时,打开的界面高度问题
+- 🎯 优化 图表批量 resize 问题
+- 🐞 修复 菜单收起时(设置全局主题:primary 且有二级菜单时),文字高亮颜色不对
+- 🐞 修复 国际化 <a href="https://gitee.com/lyt-top/vue-next-admin/issues/I43NPE" target="_blank">#I43NPE</a>。可参考:`页面/过滤筛选组件`,点击顶部语言切换,进行底部分页国际化查看
+
+## 1.0.15
+
+`2021.08.06`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 tagsView 右键菜单点击时的字段名(id 已修改成 contextMenuClickId)与路由中返回的 id 名冲突问题,感谢群友@伯牙已遇钟子期
+- 🎉 新增 多个 form 表单验证界面演示
+
+## 1.0.14
+
+`2021.07.29`
+
+- 🌟 更新 依赖更新最新版本(vue、vuex、vue-router),出现问题,请手动降级。版本查看:<a href="https://www.npmjs.com/" target="_blank">vnpm</a>
+- 🎯 优化 数据可视化图表演示加载卡顿问题、优化有图表的演示界面
+- 🎯 优化 路由参数演示界面
+- 🎯 优化 tagsView 操作演示界面,由于存在相同路由多标签,必须要传全部参数值(query 或者 params)
+- 🎉 新增 开启 TagsView 共用,开启时:(多个路由菜单共用一个详情组件(参数为后点击的覆盖前面点击的),tagsView 中只会出现一个(不支持同时出现多个 tagsView 标签))。关闭时:(多个路由菜单共用一个详情组件,参数不同,会同时出现多个 tagsView 标签)
+- 🐞 修复 tagsView 共用(单标签)时,右键菜单功能点击,参数不对的问题(第 2n+个参数未覆盖第一个参数值)
+- 🐞 修复 多 tagsView 标签(参数不同)、单个 tagsView 标签公用(参数不同)所带来的刷新功能、横向自动滚动等问题
+- 🐞 修复 处理全屏若干问题,<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/12" target="_blank">pr!12</a>,感谢群友@另一个前端
+
+## 1.0.13
+
+`2021.07.25`
+
+- 🌟 更新 依赖更新最新版本
+- 🎉 新增 数据可视化演示界面(/visualizingDemo1、/visualizingDemo2)
+- 🎉 新增 登录页扫码登录
+
+## 1.0.12
+
+`2021.07.16`
+
+- 🌟 更新 依赖更新最新版本
+- 🎉 新增 数据可视化演示空界面(待完善)
+- 🎯 优化 tagsView 动态路由(xxx/:id/:name)时的右键菜单刷新、关闭其它时参数丢失问题(2021.07.15 优化)
+- 🐞 修复 路由带参数时,复制路径到登录页,跳转后参数消失的问题
+- 🐞 修复 设置多个外链,点击后,页面内容停留在上一个内容(内容未改变)、国际化处理、打开新窗口 sessionStorage 共享等
+
+## 1.0.11
+
+`2021.07.14`
+
+- 🌟 更新 依赖更新最新版本
+- 🎉 新增 路由参数、图片懒加载界面演示
+- ⚠️ 警告 Form 表单 `binding value must be a string or number`,解决:加上 `label-position="top"` 不报警告(等待官方修复)
+- 🎯 优化 锁屏界面动画效果、首页图表显示
+- 🎯 优化 tagsView 右键菜单 `关闭` 功能逻辑
+- 🐞 修复 开启 TagsView 拖拽报错及小于 `1000px` 时自动设置禁止拖拽(<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI" target="_blank">#I3ZRRI</a>)
+- 🐞 修复 `iframe 内嵌、外链` 高度问题,使用 computed 进行计算
+- 🐞 修复 默认布局开启 `侧边栏 Logo` 与关闭 `菜单水平折叠`,切换到横向布局时,菜单看不见的问题
+- 🐞 修复 切换不同布局时,再去开启 `经典布局分割菜单` 功能不生效问题
+- 🐞 修复 浏览器窗口标题中/英文切换不实时生效的问题
+- 🐞 修复 切换布局时,某些功能不可以使用。部分界面不需要取消事件监听(proxy.mittBus.off('xxx'))
+- 🐞 修复 动态路由带参数,router-link 跳转问题(<a href="hhttps://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G" target="_blank">#I3YX6G</a>)
+- 🐞 修复 横向菜单有二级菜单时,点击子级菜单不高亮问题
+- 🐞 修复 功能 tagsView 操作演示不生效
+
+## 1.0.10
+
+`2021.07.07`
+
+- 🌟 更新 依赖更新最新版本(字体图标无问题)
+- 🎯 优化 内嵌 iframe、外链,解决 tagsView 刷新问题
+
+## 1.0.9
+
+`2021.07.02`
+
+- 🌟 更新 依赖更新最新版本
+- 🎯 优化 图标选择器设置宽度、v-model 等问题
+- 🎯 优化 滚动通知栏在手机上的体验
+- 🎯 优化 系统管理/新增菜单(编辑菜单),使用 `图标选择器` 进行模拟
+- 🎯 优化 字体图标(自动载入) 逻辑
+- 🐞 修复 screenfull 全屏时,按键盘 esc 键图标不改变问题,感谢群友@伯牙已遇钟子期
+
+## 1.0.8
+
+`2021.06.29`
+
+- 🌟 更新 依赖更新最新版本
+- 🎉 新增 表单中英文切换演示
+- 🎯 优化 登录页查看密码 icon 图标
+- 🎯 优化 图标选择器
+- 🎯 优化 拖动指令
+- 🐞 修复 form 表单在页面小于 576px 时的排版问题
+
+## 1.0.7
+
+`2021.06.24`
+
+- 🌟 更新 依赖更新最新版本
+- 🎉 新增 拖动指令及其演示界面
+- 🎯 优化 锁屏界面,解锁提示
+- 🎯 优化 登录页在手机上显示的效果
+
+## 1.0.6
+
+`2021.06.23`
+
+- 🎯 优化 去掉内嵌 iframe 内边距(padding)
+- 🎯 优化 城市多级联动组件
+- 🎯 优化 Tree 树形控件改成表格组件
+- 🐞 修复 Cascader 级联选择器高度问题
+
+## 1.0.5
+
+`2021.06.22`
+
+- 🌟 更新 vite 降级为@vite2.3.7,降级方法 `cnpm install vite@2.3.7`,防止 element plus 字体图标消失
+- 🐞 修复 开启后端控制路由(isRequestRoutes = true)时,内嵌 iframe、外链不可使用的问题
+
+## 1.0.4
+
+`2021.06.19`
+
+- 🌟 更新 依赖更新最新版本("vite": "^2.3.7")热更新无问题
+- 🎉 新增 深克隆工具,方便开发,感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/6" target="_blank">#6</a>)
+- 🎯 优化 vuex 模块自动导入。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/4" target="_blank">#4</a>),感谢群友@web 小学生-第五君
+- 🎯 优化 类型定义提高编码体验,修复不能将类型“string | undefined”分配给类型“string”的问题。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/5" target="_blank">#5</a>)
+- 🎯 优化 `layout` 文件夹移动到与 `views` 文件夹同级(改动较大,`/@/views/layout` 变成 `/@/layout`)
+- 🎯 优化 页面有 `console.log` 时 `eslint` 不生效问题
+- 🎯 优化 页面、ts 中 `any` 类型问题(改动较大)
+- 🎯 优化 登录页在手机上显示的效果
+- 🎯 优化 多行注释信息,鼠标放到方法名即可查看,更加直观的知道方法参数等。引入方法时需去掉以 `.ts` 结尾的后缀(改动较大)
+- 🎯 优化 移除 `utils/storage.ts` 下的旧写法(改动较大)
+- 🎯 优化 拆分 `router` 下内容,路由、前端、后端控制分开写,方便理解
+- 🐞 修复 鼠标移入顶部用户信息栏 `开/关全屏` 文字反向问题
+- 🐞 修复 热更新时,NextLoading(界面 loading) 不消失问题 `window.nextLoading === undefined`
+- 🐞 修复 vuex 中不可以使用 `/@/api/xxx` 下的接口调用问题
+
+## 1.0.3
+
+`2021.06.02`
+
+- ❄️ 删除 G6 思维导图界面
+- 🌟 更新 手动更新 vue、vue-router、vuex 到最近最多人使用的版本,出现不可预测的问题请降低版本。版本查看:<a href="https://www.npmjs.com/package/vue" target="_blank">vue 版本查看</a>
+- 🐞 修复 开启后端控制路由 `isRequestRoutes` 在非首页刷新页面后,回到首页的问题,感谢群友@伯牙已遇钟子期
+
+## 1.0.2
+
+`2021.06.01`
+
+- 🌟 更新 依赖更新最新版本
+- 🐞 修复 菜单搜索中文不可以搜索的问题,感谢群友@逍遥天意
+
+## 1.0.1
+
+`2021.05.31`
+
+- 🎉 新增 更新日志文件 `CHANGELOG.md`,以后每次更新都会在这里显示对应内容
+- 🌟 更新 依赖更新最新版本
+- 🐞 修复 分栏、经典布局路由设置 `meta.isHide` 为 `true` 时报错问题,感谢群友@29、@芭芭拉
+- 🐞 修复 经典布局点击 `tagsView` 左侧菜单数据不变问题

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 lyt-Top
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 32 - 0
index.html

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+	<head>
+		<meta charset="utf-8" />
+		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<meta
+			name="keywords"
+			content="vue-next-admin,vue-prev-admin,vue-admin-wonderful,后台管理系统一站式平台模板,希望可以帮你完成快速开发。vue2.x,vue2.0,vue2,vue3,vue3.x,vue3.0,CompositionAPI,typescript,element plus,element,plus,admin,wonderful,wonderful-next,vue-next-admin,vite,vite-admin,快速,高效,后台模板,后台系统,管理系统"
+		/>
+		<meta
+			name="description"
+			content="vue-next-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus,适配手机、平板、pc 的后台开源免费管理系统模板!vue-prev-admin,基于 vue2 +  element ui,适配手机、平板、pc 的后台开源免费管理系统模板!"
+		/>
+		<link rel="icon" href="/favicon.ico" />
+		<title>gfast</title>
+	</head>
+	<body>
+		<div id="app"></div>
+		<script type="text/javascript">
+			var _hmt = _hmt || [];
+			(function () {
+				var hm = document.createElement('script');
+				hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
+				var s = document.getElementsByTagName('script')[0];
+				s.parentNode.insertBefore(hm, s);
+			})();
+		</script>
+		<script type="module" src="/src/main.ts"></script>
+		<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
+	</body>
+</html>

+ 2171 - 0
package-lock.json

@@ -0,0 +1,2171 @@
+{
+	"name": "vue-next-admin",
+	"version": "2.0.2",
+	"lockfileVersion": 1,
+	"requires": true,
+	"dependencies": {
+		"@babel/parser": {
+			"version": "7.17.3",
+			"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.17.3.tgz",
+			"integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA=="
+		},
+		"@babel/runtime": {
+			"version": "7.17.2",
+			"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.2.tgz",
+			"integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
+			"requires": {
+				"regenerator-runtime": "^0.13.4"
+			}
+		},
+		"@babel/runtime-corejs3": {
+			"version": "7.17.2",
+			"resolved": "https://registry.npmmirror.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz",
+			"integrity": "sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg==",
+			"requires": {
+				"core-js-pure": "^3.20.2",
+				"regenerator-runtime": "^0.13.4"
+			}
+		},
+		"@ctrl/tinycolor": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
+			"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
+		},
+		"@element-plus/icons-vue": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-1.1.1.tgz",
+			"integrity": "sha512-kRL/cWwaynmwi/6ounJxwnj316EHqAqKrl2WTCmcwiDPVqT+Wt1pSK6nAI0zUeLfe/y8NhYMB97+NDEWgNBCqA=="
+		},
+		"@eslint/eslintrc": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
+			"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
+			"dev": true,
+			"requires": {
+				"ajv": "^6.12.4",
+				"debug": "^4.3.2",
+				"espree": "^9.3.1",
+				"globals": "^13.9.0",
+				"ignore": "^5.2.0",
+				"import-fresh": "^3.2.1",
+				"js-yaml": "^4.1.0",
+				"minimatch": "^3.0.4",
+				"strip-json-comments": "^3.1.1"
+			}
+		},
+		"@humanwhocodes/config-array": {
+			"version": "0.9.5",
+			"resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
+			"integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
+			"dev": true,
+			"requires": {
+				"@humanwhocodes/object-schema": "^1.2.1",
+				"debug": "^4.1.1",
+				"minimatch": "^3.0.4"
+			}
+		},
+		"@humanwhocodes/object-schema": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+			"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+			"dev": true
+		},
+		"@interactjs/actions": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/actions/-/actions-1.10.11.tgz",
+			"integrity": "sha512-P39zeefr4hkmKx+5nZ+mrH1s0l2YJ3gIHrthXmE81n6MlMa42m0WtHcTms4C5JTTNBP2EEDY+KGgGxSnmJKvUw==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/auto-scroll": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/auto-scroll/-/auto-scroll-1.10.11.tgz",
+			"integrity": "sha512-feHNjhi0EMNLV2nQcEgjYPz2mI54aeSW2RiaoNtFLyBvtXKp0b4DmluwDv6DvuXmUpDwD5g/Hk1gGM2rgl7iqQ==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/auto-start": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/auto-start/-/auto-start-1.10.11.tgz",
+			"integrity": "sha512-cIg5CcalCPtC6AiGq6j/0hKUtL2MweEpvw12FuB19sz2Q9Dye0J4GliHKhOYvtumNinnvfVAZ4FZMqZEuX7YZA==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/core": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/core/-/core-1.10.11.tgz",
+			"integrity": "sha512-aJ50ccVeszpJt7wPH7Yfqm7f1aG1SA94qd90P0NaESh5/QUXn4CESO6igobo4DFHQ5z+1Rfdl8aphP4JxlH4gw=="
+		},
+		"@interactjs/dev-tools": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/dev-tools/-/dev-tools-1.10.11.tgz",
+			"integrity": "sha512-BP2FNfMbF7zLuOAUGMkDhCo1e1B0fnqyb9ih/Y8yAIJuoLrZxP/9htbsS1vZOIVZ4UgtrId4cYOwfcAZBMQtmw==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/inertia": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/inertia/-/inertia-1.10.11.tgz",
+			"integrity": "sha512-h+sknCzRqBSyHy4ctPNsq56mxkAMMdwHWD6en7rDEw899gdGKYaXVDVdv1jMfiwNRw0eRFBNoCiol8r3a/a3Jw==",
+			"requires": {
+				"@interactjs/interact": "1.10.11",
+				"@interactjs/offset": "1.10.11"
+			}
+		},
+		"@interactjs/interact": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/interact/-/interact-1.10.11.tgz",
+			"integrity": "sha512-0iZJ9l547JuBA/lKxK4ARGYVmMqRSsAdA8gXL1zWe51qEIQq8PyWmMipoi8JbDaL7exC2THKwkXu5uq5ndT+iA==",
+			"requires": {
+				"@interactjs/core": "1.10.11",
+				"@interactjs/types": "1.10.11",
+				"@interactjs/utils": "1.10.11"
+			}
+		},
+		"@interactjs/interactjs": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/interactjs/-/interactjs-1.10.11.tgz",
+			"integrity": "sha512-cGOxf6rp3Y8/sk88LhIT0XDn4gCiCzAnUG5Kkj9SAqiUO6BK/9+Wbp1IBkNaPgl/8uG8gNHh/dXBrlBBNcqJAg==",
+			"requires": {
+				"@interactjs/actions": "1.10.11",
+				"@interactjs/auto-scroll": "1.10.11",
+				"@interactjs/auto-start": "1.10.11",
+				"@interactjs/core": "1.10.11",
+				"@interactjs/dev-tools": "1.10.11",
+				"@interactjs/inertia": "1.10.11",
+				"@interactjs/interact": "1.10.11",
+				"@interactjs/modifiers": "1.10.11",
+				"@interactjs/offset": "1.10.11",
+				"@interactjs/pointer-events": "1.10.11",
+				"@interactjs/reflow": "1.10.11",
+				"@interactjs/utils": "1.10.11"
+			}
+		},
+		"@interactjs/modifiers": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/modifiers/-/modifiers-1.10.11.tgz",
+			"integrity": "sha512-ltqX1RSqeAIikixlQBlyEUdclT5+rbfIGi3sIdLLYaIZQnltYkWqL9MHKx/w5b+hV+Mc0p5MLUFWJbTdkSCZ9g==",
+			"requires": {
+				"@interactjs/interact": "1.10.11",
+				"@interactjs/snappers": "1.10.11"
+			}
+		},
+		"@interactjs/offset": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/offset/-/offset-1.10.11.tgz",
+			"integrity": "sha512-mBT7eIfy5ivofECiv+VwtEwwIMLV54fT9ujSMWJPduxdSYIHepUWgEf/3zjJknFh6jQc7pqz9dtjvVvyzRCLlQ==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/pointer-events": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/pointer-events/-/pointer-events-1.10.11.tgz",
+			"integrity": "sha512-yBT8JJVMZ+MgBay5l1WAHnL8ch/mZsRfaFahti+QFYeQyRloDtsWmEMDSYI/Onyy9+hS3gN/ge77ArGciZZ0Ow==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/reflow": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/reflow/-/reflow-1.10.11.tgz",
+			"integrity": "sha512-NSCtcCkjImOYSbxzzv2kFqR9t49J8KlhEr9UoePc7GyLbNXsiv3WQ3n0ehZd7CgZXQDiVXnP2UnmIOv5Zd4HQg==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/snappers": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/snappers/-/snappers-1.10.11.tgz",
+			"integrity": "sha512-yYtOMUZ7aFUZ1IYheq9Tj5hZ4J1r5dnaXhLF44WsI/awQ5L0DjZf07GPWof0B+7rZHEVudxyQNbPfFmb+1K94Q==",
+			"requires": {
+				"@interactjs/interact": "1.10.11"
+			}
+		},
+		"@interactjs/types": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/types/-/types-1.10.11.tgz",
+			"integrity": "sha512-YRsVFWjL8Gkkvlx3qnjeaxW4fnibSJ9791g8BA7Pv5ANByI64WmtR1vU7A2rXcrOn8XvyCEfY0ss1s8NhZP+MA=="
+		},
+		"@interactjs/utils": {
+			"version": "1.10.11",
+			"resolved": "https://registry.npmmirror.com/@interactjs/utils/-/utils-1.10.11.tgz",
+			"integrity": "sha512-410ZoxKF+r1roeSelL+WHXfdryUMg5iykC1XwQ3l6XqNw43IMACzyvTH6k6Pwxj7w7x42nce0Qdn1GQ3Y8xyCw=="
+		},
+		"@intlify/core-base": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.1.9.tgz",
+			"integrity": "sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==",
+			"requires": {
+				"@intlify/devtools-if": "9.1.9",
+				"@intlify/message-compiler": "9.1.9",
+				"@intlify/message-resolver": "9.1.9",
+				"@intlify/runtime": "9.1.9",
+				"@intlify/shared": "9.1.9",
+				"@intlify/vue-devtools": "9.1.9"
+			}
+		},
+		"@intlify/devtools-if": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.1.9.tgz",
+			"integrity": "sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==",
+			"requires": {
+				"@intlify/shared": "9.1.9"
+			}
+		},
+		"@intlify/message-compiler": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.1.9.tgz",
+			"integrity": "sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==",
+			"requires": {
+				"@intlify/message-resolver": "9.1.9",
+				"@intlify/shared": "9.1.9",
+				"source-map": "0.6.1"
+			}
+		},
+		"@intlify/message-resolver": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/message-resolver/-/message-resolver-9.1.9.tgz",
+			"integrity": "sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA=="
+		},
+		"@intlify/runtime": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/runtime/-/runtime-9.1.9.tgz",
+			"integrity": "sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==",
+			"requires": {
+				"@intlify/message-compiler": "9.1.9",
+				"@intlify/message-resolver": "9.1.9",
+				"@intlify/shared": "9.1.9"
+			}
+		},
+		"@intlify/shared": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.1.9.tgz",
+			"integrity": "sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw=="
+		},
+		"@intlify/vue-devtools": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.1.9.tgz",
+			"integrity": "sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==",
+			"requires": {
+				"@intlify/message-resolver": "9.1.9",
+				"@intlify/runtime": "9.1.9",
+				"@intlify/shared": "9.1.9"
+			}
+		},
+		"@nodelib/fs.scandir": {
+			"version": "2.1.5",
+			"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+			"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.stat": "2.0.5",
+				"run-parallel": "^1.1.9"
+			}
+		},
+		"@nodelib/fs.stat": {
+			"version": "2.0.5",
+			"resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+			"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+			"dev": true
+		},
+		"@nodelib/fs.walk": {
+			"version": "1.2.8",
+			"resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+			"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.scandir": "2.1.5",
+				"fastq": "^1.6.0"
+			}
+		},
+		"@popperjs/core": {
+			"version": "2.11.3",
+			"resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.3.tgz",
+			"integrity": "sha512-8U7hIl7+30XbIrJ0deQMXpXESM1L4yrt6BHok5hzcR0LivivuNkk+tHU1iRVScOwCmQcrOr6kvtIr29MNbQHqQ=="
+		},
+		"@types/json-schema": {
+			"version": "7.0.9",
+			"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.9.tgz",
+			"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
+			"dev": true
+		},
+		"@types/node": {
+			"version": "17.0.21",
+			"resolved": "https://registry.npmmirror.com/@types/node/-/node-17.0.21.tgz",
+			"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
+			"dev": true
+		},
+		"@types/nprogress": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmmirror.com/@types/nprogress/-/nprogress-0.2.0.tgz",
+			"integrity": "sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==",
+			"dev": true
+		},
+		"@types/sortablejs": {
+			"version": "1.10.7",
+			"resolved": "https://registry.npmmirror.com/@types/sortablejs/-/sortablejs-1.10.7.tgz",
+			"integrity": "sha512-lGCwwgpj8zW/ZmaueoPVSP7nnc9t8VqVWXS+ASX3eoUUENmiazv0rlXyTRludXzuX9ALjPsMqBu85TgJNWbTOg==",
+			"dev": true
+		},
+		"@typescript-eslint/eslint-plugin": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz",
+			"integrity": "sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/scope-manager": "5.14.0",
+				"@typescript-eslint/type-utils": "5.14.0",
+				"@typescript-eslint/utils": "5.14.0",
+				"debug": "^4.3.2",
+				"functional-red-black-tree": "^1.0.1",
+				"ignore": "^5.1.8",
+				"regexpp": "^3.2.0",
+				"semver": "^7.3.5",
+				"tsutils": "^3.21.0"
+			}
+		},
+		"@typescript-eslint/parser": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.14.0.tgz",
+			"integrity": "sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/scope-manager": "5.14.0",
+				"@typescript-eslint/types": "5.14.0",
+				"@typescript-eslint/typescript-estree": "5.14.0",
+				"debug": "^4.3.2"
+			}
+		},
+		"@typescript-eslint/scope-manager": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz",
+			"integrity": "sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/types": "5.14.0",
+				"@typescript-eslint/visitor-keys": "5.14.0"
+			}
+		},
+		"@typescript-eslint/type-utils": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz",
+			"integrity": "sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/utils": "5.14.0",
+				"debug": "^4.3.2",
+				"tsutils": "^3.21.0"
+			}
+		},
+		"@typescript-eslint/types": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.14.0.tgz",
+			"integrity": "sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw==",
+			"dev": true
+		},
+		"@typescript-eslint/typescript-estree": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz",
+			"integrity": "sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/types": "5.14.0",
+				"@typescript-eslint/visitor-keys": "5.14.0",
+				"debug": "^4.3.2",
+				"globby": "^11.0.4",
+				"is-glob": "^4.0.3",
+				"semver": "^7.3.5",
+				"tsutils": "^3.21.0"
+			}
+		},
+		"@typescript-eslint/utils": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.14.0.tgz",
+			"integrity": "sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w==",
+			"dev": true,
+			"requires": {
+				"@types/json-schema": "^7.0.9",
+				"@typescript-eslint/scope-manager": "5.14.0",
+				"@typescript-eslint/types": "5.14.0",
+				"@typescript-eslint/typescript-estree": "5.14.0",
+				"eslint-scope": "^5.1.1",
+				"eslint-utils": "^3.0.0"
+			}
+		},
+		"@typescript-eslint/visitor-keys": {
+			"version": "5.14.0",
+			"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz",
+			"integrity": "sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/types": "5.14.0",
+				"eslint-visitor-keys": "^3.0.0"
+			}
+		},
+		"@vitejs/plugin-vue": {
+			"version": "2.2.4",
+			"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.2.4.tgz",
+			"integrity": "sha512-ev9AOlp0ljCaDkFZF3JwC/pD2N4Hh+r5srl5JHM6BKg5+99jiiK0rE/XaRs3pVm1wzyKkjUy/StBSoXX5fFzcw==",
+			"dev": true
+		},
+		"@vue/compiler-core": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz",
+			"integrity": "sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==",
+			"requires": {
+				"@babel/parser": "^7.16.4",
+				"@vue/shared": "3.2.31",
+				"estree-walker": "^2.0.2",
+				"source-map": "^0.6.1"
+			}
+		},
+		"@vue/compiler-dom": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz",
+			"integrity": "sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==",
+			"requires": {
+				"@vue/compiler-core": "3.2.31",
+				"@vue/shared": "3.2.31"
+			}
+		},
+		"@vue/compiler-sfc": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz",
+			"integrity": "sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==",
+			"requires": {
+				"@babel/parser": "^7.16.4",
+				"@vue/compiler-core": "3.2.31",
+				"@vue/compiler-dom": "3.2.31",
+				"@vue/compiler-ssr": "3.2.31",
+				"@vue/reactivity-transform": "3.2.31",
+				"@vue/shared": "3.2.31",
+				"estree-walker": "^2.0.2",
+				"magic-string": "^0.25.7",
+				"postcss": "^8.1.10",
+				"source-map": "^0.6.1"
+			}
+		},
+		"@vue/compiler-ssr": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz",
+			"integrity": "sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==",
+			"requires": {
+				"@vue/compiler-dom": "3.2.31",
+				"@vue/shared": "3.2.31"
+			}
+		},
+		"@vue/devtools-api": {
+			"version": "6.0.5",
+			"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.0.5.tgz",
+			"integrity": "sha512-2nM84dzo3B63pKgxwoArlT1d/yqSL0y2lG2GiyyGhwpyPTwkfIuJHlCNbputCoSCNnT6MMfenK1g7nv7Mea19A=="
+		},
+		"@vue/reactivity": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.31.tgz",
+			"integrity": "sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==",
+			"requires": {
+				"@vue/shared": "3.2.31"
+			}
+		},
+		"@vue/reactivity-transform": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz",
+			"integrity": "sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==",
+			"requires": {
+				"@babel/parser": "^7.16.4",
+				"@vue/compiler-core": "3.2.31",
+				"@vue/shared": "3.2.31",
+				"estree-walker": "^2.0.2",
+				"magic-string": "^0.25.7"
+			}
+		},
+		"@vue/runtime-core": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz",
+			"integrity": "sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==",
+			"requires": {
+				"@vue/reactivity": "3.2.31",
+				"@vue/shared": "3.2.31"
+			}
+		},
+		"@vue/runtime-dom": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz",
+			"integrity": "sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==",
+			"requires": {
+				"@vue/runtime-core": "3.2.31",
+				"@vue/shared": "3.2.31",
+				"csstype": "^2.6.8"
+			}
+		},
+		"@vue/server-renderer": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz",
+			"integrity": "sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==",
+			"requires": {
+				"@vue/compiler-ssr": "3.2.31",
+				"@vue/shared": "3.2.31"
+			}
+		},
+		"@vue/shared": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.31.tgz",
+			"integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ=="
+		},
+		"@vueuse/core": {
+			"version": "7.7.1",
+			"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-7.7.1.tgz",
+			"integrity": "sha512-PRRgbATMpoeUmkCEBtUeJgOwtew8s+4UsEd+Pm7MhkjL2ihCNrSqxNVtM6NFE4uP2sWnkGcZpCjPuNSxowJ1Ow==",
+			"requires": {
+				"@vueuse/shared": "7.7.1",
+				"vue-demi": "*"
+			}
+		},
+		"@vueuse/shared": {
+			"version": "7.7.1",
+			"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-7.7.1.tgz",
+			"integrity": "sha512-rN2qd22AUl7VdBxihagWyhUNHCyVk9IpvBTTfHoLH9G7rGE552X1f+zeCfehuno0zXif13jPw+icW/wn2a0rnQ==",
+			"requires": {
+				"vue-demi": "*"
+			}
+		},
+		"acorn": {
+			"version": "8.7.0",
+			"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.7.0.tgz",
+			"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
+			"dev": true
+		},
+		"acorn-jsx": {
+			"version": "5.3.2",
+			"resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+			"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+			"dev": true
+		},
+		"ajv": {
+			"version": "6.12.6",
+			"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+			"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+			"dev": true,
+			"requires": {
+				"fast-deep-equal": "^3.1.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			}
+		},
+		"ansi-regex": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+			"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+			"dev": true
+		},
+		"ansi-styles": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+			"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+			"dev": true,
+			"requires": {
+				"color-convert": "^2.0.1"
+			}
+		},
+		"anymatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.2.tgz",
+			"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+			"dev": true,
+			"requires": {
+				"normalize-path": "^3.0.0",
+				"picomatch": "^2.0.4"
+			}
+		},
+		"argparse": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+			"dev": true
+		},
+		"array-union": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz",
+			"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+			"dev": true
+		},
+		"async-validator": {
+			"version": "4.0.7",
+			"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.0.7.tgz",
+			"integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ=="
+		},
+		"axios": {
+			"version": "0.26.1",
+			"resolved": "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz",
+			"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+			"requires": {
+				"follow-redirects": "^1.14.8"
+			}
+		},
+		"balanced-match": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+			"dev": true
+		},
+		"batch-processor": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmmirror.com/batch-processor/-/batch-processor-1.0.0.tgz",
+			"integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA=="
+		},
+		"binary-extensions": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz",
+			"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+			"dev": true
+		},
+		"brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"requires": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"braces": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz",
+			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"dev": true,
+			"requires": {
+				"fill-range": "^7.0.1"
+			}
+		},
+		"callsites": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+			"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+			"dev": true
+		},
+		"chalk": {
+			"version": "4.1.2",
+			"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+			"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+			"dev": true,
+			"requires": {
+				"ansi-styles": "^4.1.0",
+				"supports-color": "^7.1.0"
+			}
+		},
+		"chokidar": {
+			"version": "3.5.3",
+			"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz",
+			"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+			"dev": true,
+			"requires": {
+				"anymatch": "~3.1.2",
+				"braces": "~3.0.2",
+				"fsevents": "~2.3.2",
+				"glob-parent": "~5.1.2",
+				"is-binary-path": "~2.1.0",
+				"is-glob": "~4.0.1",
+				"normalize-path": "~3.0.0",
+				"readdirp": "~3.6.0"
+			}
+		},
+		"claygl": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmmirror.com/claygl/-/claygl-1.3.0.tgz",
+			"integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
+		},
+		"clipboard": {
+			"version": "2.0.8",
+			"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
+			"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
+			"requires": {
+				"good-listener": "^1.2.2",
+				"select": "^1.1.2",
+				"tiny-emitter": "^2.0.0"
+			}
+		},
+		"color-convert": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+			"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+			"dev": true,
+			"requires": {
+				"color-name": "~1.1.4"
+			}
+		},
+		"color-name": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+			"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+			"dev": true
+		},
+		"concat-map": {
+			"version": "0.0.1",
+			"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+			"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+			"dev": true
+		},
+		"core-js-pure": {
+			"version": "3.21.1",
+			"resolved": "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.21.1.tgz",
+			"integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ=="
+		},
+		"countup.js": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmmirror.com/countup.js/-/countup.js-2.1.0.tgz",
+			"integrity": "sha512-VanMzLEjkt3Hp/ty5BXikM8s4wE3OH4m1AnFro7THR86nYGRvGfGCoV+zrRJcqTbZi7X1egkLSIeUKDz7+4XLA=="
+		},
+		"cropperjs": {
+			"version": "1.5.12",
+			"resolved": "https://registry.npmmirror.com/cropperjs/-/cropperjs-1.5.12.tgz",
+			"integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
+		},
+		"cross-spawn": {
+			"version": "7.0.3",
+			"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
+			"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+			"dev": true,
+			"requires": {
+				"path-key": "^3.1.0",
+				"shebang-command": "^2.0.0",
+				"which": "^2.0.1"
+			}
+		},
+		"csstype": {
+			"version": "2.6.20",
+			"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz",
+			"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
+		},
+		"dayjs": {
+			"version": "1.10.8",
+			"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.10.8.tgz",
+			"integrity": "sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow=="
+		},
+		"debug": {
+			"version": "4.3.3",
+			"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.3.tgz",
+			"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+			"dev": true,
+			"requires": {
+				"ms": "2.1.2"
+			}
+		},
+		"deep-is": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+			"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+			"dev": true
+		},
+		"delegate": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+			"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+		},
+		"dir-glob": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
+			"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+			"dev": true,
+			"requires": {
+				"path-type": "^4.0.0"
+			}
+		},
+		"doctrine": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
+			"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+			"dev": true,
+			"requires": {
+				"esutils": "^2.0.2"
+			}
+		},
+		"dotenv": {
+			"version": "16.0.0",
+			"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.0.tgz",
+			"integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==",
+			"dev": true
+		},
+		"echarts": {
+			"version": "5.3.0",
+			"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.3.0.tgz",
+			"integrity": "sha512-zENufmwFE6WjM+24tW3xQq4ICqQtI0CGj4bDVDNd3BK3LtaA/5wBp+64ykIyKy3QElz0cieKqSYP4FX9Lv9MwQ==",
+			"requires": {
+				"tslib": "2.3.0",
+				"zrender": "5.3.0"
+			},
+			"dependencies": {
+				"tslib": {
+					"version": "2.3.0",
+					"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+					"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+				}
+			}
+		},
+		"echarts-gl": {
+			"version": "2.0.9",
+			"resolved": "https://registry.npmmirror.com/echarts-gl/-/echarts-gl-2.0.9.tgz",
+			"integrity": "sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==",
+			"requires": {
+				"claygl": "^1.2.1",
+				"zrender": "^5.1.1"
+			}
+		},
+		"echarts-wordcloud": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmmirror.com/echarts-wordcloud/-/echarts-wordcloud-2.0.0.tgz",
+			"integrity": "sha512-K7l6pTklqdW7ZWzT/1CS0KhBSINr/cd7c5N1fVMzZMwLQHEwT7x+nivK7g5hkVh7WNcAv4Dn6/ZS5zMKRozC1g=="
+		},
+		"element-plus": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.1.1.tgz",
+			"integrity": "sha512-XG6XBo9tLYzfW1MNTgGoBOoPGnfMAkFWJSsCaS5M7ekOCszC4HKCj4OqIkusGemLvzR8O96S06swzQwzNBe2uA==",
+			"requires": {
+				"@ctrl/tinycolor": "^3.4.0",
+				"@element-plus/icons-vue": "^1.0.1",
+				"@popperjs/core": "^2.11.2",
+				"@vueuse/core": "^7.7.1",
+				"async-validator": "^4.0.7",
+				"dayjs": "^1.10.8",
+				"escape-html": "^1.0.3",
+				"lodash": "^4.17.21",
+				"lodash-es": "^4.17.21",
+				"lodash-unified": "^1.0.2",
+				"memoize-one": "^6.0.0",
+				"normalize-wheel-es": "^1.1.1"
+			}
+		},
+		"element-resize-detector": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmmirror.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
+			"integrity": "sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==",
+			"requires": {
+				"batch-processor": "1.0.0"
+			}
+		},
+		"esbuild": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.25.tgz",
+			"integrity": "sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==",
+			"dev": true,
+			"requires": {
+				"esbuild-android-64": "0.14.25",
+				"esbuild-android-arm64": "0.14.25",
+				"esbuild-darwin-64": "0.14.25",
+				"esbuild-darwin-arm64": "0.14.25",
+				"esbuild-freebsd-64": "0.14.25",
+				"esbuild-freebsd-arm64": "0.14.25",
+				"esbuild-linux-32": "0.14.25",
+				"esbuild-linux-64": "0.14.25",
+				"esbuild-linux-arm": "0.14.25",
+				"esbuild-linux-arm64": "0.14.25",
+				"esbuild-linux-mips64le": "0.14.25",
+				"esbuild-linux-ppc64le": "0.14.25",
+				"esbuild-linux-riscv64": "0.14.25",
+				"esbuild-linux-s390x": "0.14.25",
+				"esbuild-netbsd-64": "0.14.25",
+				"esbuild-openbsd-64": "0.14.25",
+				"esbuild-sunos-64": "0.14.25",
+				"esbuild-windows-32": "0.14.25",
+				"esbuild-windows-64": "0.14.25",
+				"esbuild-windows-arm64": "0.14.25"
+			}
+		},
+		"esbuild-android-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.25.tgz",
+			"integrity": "sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-android-arm64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.25.tgz",
+			"integrity": "sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-darwin-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.25.tgz",
+			"integrity": "sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-darwin-arm64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.25.tgz",
+			"integrity": "sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-freebsd-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.25.tgz",
+			"integrity": "sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-freebsd-arm64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.25.tgz",
+			"integrity": "sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-32": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.25.tgz",
+			"integrity": "sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.25.tgz",
+			"integrity": "sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-arm": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.25.tgz",
+			"integrity": "sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-arm64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.25.tgz",
+			"integrity": "sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-mips64le": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.25.tgz",
+			"integrity": "sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-ppc64le": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.25.tgz",
+			"integrity": "sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-riscv64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.25.tgz",
+			"integrity": "sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-s390x": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.25.tgz",
+			"integrity": "sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-netbsd-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.25.tgz",
+			"integrity": "sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-openbsd-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz",
+			"integrity": "sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-sunos-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz",
+			"integrity": "sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-windows-32": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.25.tgz",
+			"integrity": "sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-windows-64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.25.tgz",
+			"integrity": "sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-windows-arm64": {
+			"version": "0.14.25",
+			"resolved": "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.25.tgz",
+			"integrity": "sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==",
+			"dev": true,
+			"optional": true
+		},
+		"escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+		},
+		"escape-string-regexp": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+			"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+			"dev": true
+		},
+		"eslint": {
+			"version": "8.11.0",
+			"resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.11.0.tgz",
+			"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
+			"dev": true,
+			"requires": {
+				"@eslint/eslintrc": "^1.2.1",
+				"@humanwhocodes/config-array": "^0.9.2",
+				"ajv": "^6.10.0",
+				"chalk": "^4.0.0",
+				"cross-spawn": "^7.0.2",
+				"debug": "^4.3.2",
+				"doctrine": "^3.0.0",
+				"escape-string-regexp": "^4.0.0",
+				"eslint-scope": "^7.1.1",
+				"eslint-utils": "^3.0.0",
+				"eslint-visitor-keys": "^3.3.0",
+				"espree": "^9.3.1",
+				"esquery": "^1.4.0",
+				"esutils": "^2.0.2",
+				"fast-deep-equal": "^3.1.3",
+				"file-entry-cache": "^6.0.1",
+				"functional-red-black-tree": "^1.0.1",
+				"glob-parent": "^6.0.1",
+				"globals": "^13.6.0",
+				"ignore": "^5.2.0",
+				"import-fresh": "^3.0.0",
+				"imurmurhash": "^0.1.4",
+				"is-glob": "^4.0.0",
+				"js-yaml": "^4.1.0",
+				"json-stable-stringify-without-jsonify": "^1.0.1",
+				"levn": "^0.4.1",
+				"lodash.merge": "^4.6.2",
+				"minimatch": "^3.0.4",
+				"natural-compare": "^1.4.0",
+				"optionator": "^0.9.1",
+				"regexpp": "^3.2.0",
+				"strip-ansi": "^6.0.1",
+				"strip-json-comments": "^3.1.0",
+				"text-table": "^0.2.0",
+				"v8-compile-cache": "^2.0.3"
+			},
+			"dependencies": {
+				"eslint-scope": {
+					"version": "7.1.1",
+					"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.1.1.tgz",
+					"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+					"dev": true,
+					"requires": {
+						"esrecurse": "^4.3.0",
+						"estraverse": "^5.2.0"
+					}
+				},
+				"estraverse": {
+					"version": "5.3.0",
+					"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+					"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+					"dev": true
+				},
+				"glob-parent": {
+					"version": "6.0.2",
+					"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+					"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+					"dev": true,
+					"requires": {
+						"is-glob": "^4.0.3"
+					}
+				}
+			}
+		},
+		"eslint-plugin-vue": {
+			"version": "8.5.0",
+			"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-8.5.0.tgz",
+			"integrity": "sha512-i1uHCTAKOoEj12RDvdtONWrGzjFm/djkzqfhmQ0d6M/W8KM81mhswd/z+iTZ0jCpdUedW3YRgcVfQ37/J4zoYQ==",
+			"dev": true,
+			"requires": {
+				"eslint-utils": "^3.0.0",
+				"natural-compare": "^1.4.0",
+				"semver": "^7.3.5",
+				"vue-eslint-parser": "^8.0.1"
+			}
+		},
+		"eslint-scope": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz",
+			"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+			"dev": true,
+			"requires": {
+				"esrecurse": "^4.3.0",
+				"estraverse": "^4.1.1"
+			}
+		},
+		"eslint-utils": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-3.0.0.tgz",
+			"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+			"dev": true,
+			"requires": {
+				"eslint-visitor-keys": "^2.0.0"
+			},
+			"dependencies": {
+				"eslint-visitor-keys": {
+					"version": "2.1.0",
+					"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+					"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+					"dev": true
+				}
+			}
+		},
+		"eslint-visitor-keys": {
+			"version": "3.3.0",
+			"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+			"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+			"dev": true
+		},
+		"espree": {
+			"version": "9.3.1",
+			"resolved": "https://registry.npmmirror.com/espree/-/espree-9.3.1.tgz",
+			"integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
+			"dev": true,
+			"requires": {
+				"acorn": "^8.7.0",
+				"acorn-jsx": "^5.3.1",
+				"eslint-visitor-keys": "^3.3.0"
+			}
+		},
+		"esquery": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.4.0.tgz",
+			"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+			"dev": true,
+			"requires": {
+				"estraverse": "^5.1.0"
+			},
+			"dependencies": {
+				"estraverse": {
+					"version": "5.3.0",
+					"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+					"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+					"dev": true
+				}
+			}
+		},
+		"esrecurse": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+			"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+			"dev": true,
+			"requires": {
+				"estraverse": "^5.2.0"
+			},
+			"dependencies": {
+				"estraverse": {
+					"version": "5.3.0",
+					"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+					"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+					"dev": true
+				}
+			}
+		},
+		"estraverse": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz",
+			"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+			"dev": true
+		},
+		"estree-walker": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+		},
+		"esutils": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+			"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+			"dev": true
+		},
+		"fast-deep-equal": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+			"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+			"dev": true
+		},
+		"fast-glob": {
+			"version": "3.2.11",
+			"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.11.tgz",
+			"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.stat": "^2.0.2",
+				"@nodelib/fs.walk": "^1.2.3",
+				"glob-parent": "^5.1.2",
+				"merge2": "^1.3.0",
+				"micromatch": "^4.0.4"
+			}
+		},
+		"fast-json-stable-stringify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+			"dev": true
+		},
+		"fast-levenshtein": {
+			"version": "2.0.6",
+			"resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+			"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+			"dev": true
+		},
+		"fastq": {
+			"version": "1.13.0",
+			"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.13.0.tgz",
+			"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+			"dev": true,
+			"requires": {
+				"reusify": "^1.0.4"
+			}
+		},
+		"file-entry-cache": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+			"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+			"dev": true,
+			"requires": {
+				"flat-cache": "^3.0.4"
+			}
+		},
+		"fill-range": {
+			"version": "7.0.1",
+			"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
+			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"dev": true,
+			"requires": {
+				"to-regex-range": "^5.0.1"
+			}
+		},
+		"flat-cache": {
+			"version": "3.0.4",
+			"resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.0.4.tgz",
+			"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+			"dev": true,
+			"requires": {
+				"flatted": "^3.1.0",
+				"rimraf": "^3.0.2"
+			}
+		},
+		"flatted": {
+			"version": "3.2.5",
+			"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.2.5.tgz",
+			"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
+			"dev": true
+		},
+		"follow-redirects": {
+			"version": "1.14.9",
+			"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.14.9.tgz",
+			"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
+		},
+		"fs.realpath": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+			"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+			"dev": true
+		},
+		"fsevents": {
+			"version": "2.3.2",
+			"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
+			"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+			"dev": true,
+			"optional": true
+		},
+		"function-bind": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
+			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+			"dev": true
+		},
+		"functional-red-black-tree": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+			"integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+			"dev": true
+		},
+		"glob": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.0.tgz",
+			"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+			"dev": true,
+			"requires": {
+				"fs.realpath": "^1.0.0",
+				"inflight": "^1.0.4",
+				"inherits": "2",
+				"minimatch": "^3.0.4",
+				"once": "^1.3.0",
+				"path-is-absolute": "^1.0.0"
+			}
+		},
+		"glob-parent": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+			"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+			"dev": true,
+			"requires": {
+				"is-glob": "^4.0.1"
+			}
+		},
+		"globals": {
+			"version": "13.12.1",
+			"resolved": "https://registry.npmmirror.com/globals/-/globals-13.12.1.tgz",
+			"integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==",
+			"dev": true,
+			"requires": {
+				"type-fest": "^0.20.2"
+			}
+		},
+		"globby": {
+			"version": "11.1.0",
+			"resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz",
+			"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+			"dev": true,
+			"requires": {
+				"array-union": "^2.1.0",
+				"dir-glob": "^3.0.1",
+				"fast-glob": "^3.2.9",
+				"ignore": "^5.2.0",
+				"merge2": "^1.4.1",
+				"slash": "^3.0.0"
+			}
+		},
+		"good-listener": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+			"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+			"requires": {
+				"delegate": "^3.1.2"
+			}
+		},
+		"has": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
+			"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+			"dev": true,
+			"requires": {
+				"function-bind": "^1.1.1"
+			}
+		},
+		"has-flag": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+			"dev": true
+		},
+		"ignore": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz",
+			"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+			"dev": true
+		},
+		"immutable": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.0.0.tgz",
+			"integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
+			"dev": true
+		},
+		"import-fresh": {
+			"version": "3.3.0",
+			"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
+			"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+			"dev": true,
+			"requires": {
+				"parent-module": "^1.0.0",
+				"resolve-from": "^4.0.0"
+			}
+		},
+		"imurmurhash": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+			"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+			"dev": true
+		},
+		"inflight": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+			"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+			"dev": true,
+			"requires": {
+				"once": "^1.3.0",
+				"wrappy": "1"
+			}
+		},
+		"inherits": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+			"dev": true
+		},
+		"is-binary-path": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+			"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+			"dev": true,
+			"requires": {
+				"binary-extensions": "^2.0.0"
+			}
+		},
+		"is-core-module": {
+			"version": "2.8.1",
+			"resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.8.1.tgz",
+			"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
+			"dev": true,
+			"requires": {
+				"has": "^1.0.3"
+			}
+		},
+		"is-extglob": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+			"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+			"dev": true
+		},
+		"is-glob": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+			"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+			"dev": true,
+			"requires": {
+				"is-extglob": "^2.1.1"
+			}
+		},
+		"is-number": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+			"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+			"dev": true
+		},
+		"isexe": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+			"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+			"dev": true
+		},
+		"js-yaml": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
+			"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+			"dev": true,
+			"requires": {
+				"argparse": "^2.0.1"
+			}
+		},
+		"json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+			"dev": true
+		},
+		"json-stable-stringify-without-jsonify": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+			"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+			"dev": true
+		},
+		"jsplumb": {
+			"version": "2.15.6",
+			"resolved": "https://registry.npmmirror.com/jsplumb/-/jsplumb-2.15.6.tgz",
+			"integrity": "sha512-sIpbpz5eMVM+vV+MQzFCidlaa1RsknrQs6LOTKYDjYUDdTAi2AN2bFi94TxB33TifcIsRNV1jebcaxg0tCoPzg=="
+		},
+		"klona": {
+			"version": "2.0.5",
+			"resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.5.tgz",
+			"integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==",
+			"dev": true
+		},
+		"levn": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
+			"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+			"dev": true,
+			"requires": {
+				"prelude-ls": "^1.2.1",
+				"type-check": "~0.4.0"
+			}
+		},
+		"lodash": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+		},
+		"lodash-es": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+			"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+		},
+		"lodash-unified": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.2.tgz",
+			"integrity": "sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g=="
+		},
+		"lodash.merge": {
+			"version": "4.6.2",
+			"resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
+			"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+			"dev": true
+		},
+		"lru-cache": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+			"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+			"dev": true,
+			"requires": {
+				"yallist": "^4.0.0"
+			}
+		},
+		"magic-string": {
+			"version": "0.25.9",
+			"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz",
+			"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+			"requires": {
+				"sourcemap-codec": "^1.4.8"
+			}
+		},
+		"memoize-one": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+			"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+		},
+		"merge2": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+			"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+			"dev": true
+		},
+		"micromatch": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.4.tgz",
+			"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+			"dev": true,
+			"requires": {
+				"braces": "^3.0.1",
+				"picomatch": "^2.2.3"
+			}
+		},
+		"minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"requires": {
+				"brace-expansion": "^1.1.7"
+			}
+		},
+		"mitt": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz",
+			"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
+		},
+		"ms": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+			"dev": true
+		},
+		"nanoid": {
+			"version": "3.3.1",
+			"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.1.tgz",
+			"integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw=="
+		},
+		"natural-compare": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+			"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+			"dev": true
+		},
+		"neo-async": {
+			"version": "2.6.2",
+			"resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
+			"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+			"dev": true
+		},
+		"normalize-path": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+			"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+			"dev": true
+		},
+		"normalize-wheel-es": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.1.1.tgz",
+			"integrity": "sha512-157VNH4CngrcsvF8xOVOe22cwniIR3nxSltdctvQeHZj8JttEeOXffK28jucWfWBXs0QNetAumjc1GiInnwX4w=="
+		},
+		"nprogress": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz",
+			"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
+		},
+		"once": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+			"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+			"dev": true,
+			"requires": {
+				"wrappy": "1"
+			}
+		},
+		"optionator": {
+			"version": "0.9.1",
+			"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.1.tgz",
+			"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+			"dev": true,
+			"requires": {
+				"deep-is": "^0.1.3",
+				"fast-levenshtein": "^2.0.6",
+				"levn": "^0.4.1",
+				"prelude-ls": "^1.2.1",
+				"type-check": "^0.4.0",
+				"word-wrap": "^1.2.3"
+			}
+		},
+		"parent-module": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+			"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+			"dev": true,
+			"requires": {
+				"callsites": "^3.0.0"
+			}
+		},
+		"path-is-absolute": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+			"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+			"dev": true
+		},
+		"path-key": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+			"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+			"dev": true
+		},
+		"path-parse": {
+			"version": "1.0.7",
+			"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+			"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+			"dev": true
+		},
+		"path-type": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz",
+			"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+			"dev": true
+		},
+		"picocolors": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz",
+			"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+		},
+		"picomatch": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+			"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+			"dev": true
+		},
+		"postcss": {
+			"version": "8.4.8",
+			"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.8.tgz",
+			"integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==",
+			"requires": {
+				"nanoid": "^3.3.1",
+				"picocolors": "^1.0.0",
+				"source-map-js": "^1.0.2"
+			}
+		},
+		"prelude-ls": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
+			"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+			"dev": true
+		},
+		"prettier": {
+			"version": "2.5.1",
+			"resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.5.1.tgz",
+			"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
+			"dev": true
+		},
+		"print-js": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmmirror.com/print-js/-/print-js-1.6.0.tgz",
+			"integrity": "sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg=="
+		},
+		"punycode": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz",
+			"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+			"dev": true
+		},
+		"qrcodejs2-fixes": {
+			"version": "0.0.2",
+			"resolved": "https://registry.npmmirror.com/qrcodejs2-fixes/-/qrcodejs2-fixes-0.0.2.tgz",
+			"integrity": "sha512-wMUXYMOixAEJlLnjk5MbLiFaz0gQObWYm/TIFWB5+j7sTY5gPyr09Cx1EpcLYbsgfFdN3wHjrKAhZofTuCBGhg=="
+		},
+		"queue-microtask": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+			"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+			"dev": true
+		},
+		"readdirp": {
+			"version": "3.6.0",
+			"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+			"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+			"dev": true,
+			"requires": {
+				"picomatch": "^2.2.1"
+			}
+		},
+		"regenerator-runtime": {
+			"version": "0.13.9",
+			"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+			"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+		},
+		"regexpp": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz",
+			"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+			"dev": true
+		},
+		"resolve": {
+			"version": "1.22.0",
+			"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz",
+			"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+			"dev": true,
+			"requires": {
+				"is-core-module": "^2.8.1",
+				"path-parse": "^1.0.7",
+				"supports-preserve-symlinks-flag": "^1.0.0"
+			}
+		},
+		"resolve-from": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+			"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+			"dev": true
+		},
+		"reusify": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
+			"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+			"dev": true
+		},
+		"rimraf": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
+			"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+			"dev": true,
+			"requires": {
+				"glob": "^7.1.3"
+			}
+		},
+		"rollup": {
+			"version": "2.70.0",
+			"resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.70.0.tgz",
+			"integrity": "sha512-iEzYw+syFxQ0X9RefVwhr8BA2TNJsTaX8L8dhyeyMECDbmiba+8UQzcu+xZdji0+JQ+s7kouQnw+9Oz5M19XKA==",
+			"dev": true,
+			"requires": {
+				"fsevents": "~2.3.2"
+			}
+		},
+		"run-parallel": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+			"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+			"dev": true,
+			"requires": {
+				"queue-microtask": "^1.2.2"
+			}
+		},
+		"sass": {
+			"version": "1.49.9",
+			"resolved": "https://registry.npmmirror.com/sass/-/sass-1.49.9.tgz",
+			"integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
+			"dev": true,
+			"requires": {
+				"chokidar": ">=3.0.0 <4.0.0",
+				"immutable": "^4.0.0",
+				"source-map-js": ">=0.6.2 <2.0.0"
+			}
+		},
+		"sass-loader": {
+			"version": "12.6.0",
+			"resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-12.6.0.tgz",
+			"integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==",
+			"dev": true,
+			"requires": {
+				"klona": "^2.0.4",
+				"neo-async": "^2.6.2"
+			}
+		},
+		"screenfull": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-6.0.1.tgz",
+			"integrity": "sha512-yzQW+j4zMUBQC51xxWaoDYjxOtl8Kn+xvue3p6v/fv2pIi1jH4AldgVLU8TBfFVgH2x3VXlf3+YiA/AYIPlaew=="
+		},
+		"select": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+			"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+		},
+		"semver": {
+			"version": "7.3.5",
+			"resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.5.tgz",
+			"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+			"dev": true,
+			"requires": {
+				"lru-cache": "^6.0.0"
+			}
+		},
+		"shebang-command": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+			"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+			"dev": true,
+			"requires": {
+				"shebang-regex": "^3.0.0"
+			}
+		},
+		"shebang-regex": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+			"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+			"dev": true
+		},
+		"slash": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz",
+			"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+			"dev": true
+		},
+		"sortablejs": {
+			"version": "1.14.0",
+			"resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz",
+			"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
+		},
+		"source-map": {
+			"version": "0.6.1",
+			"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+		},
+		"source-map-js": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
+			"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+		},
+		"sourcemap-codec": {
+			"version": "1.4.8",
+			"resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+			"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+		},
+		"splitpanes": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmmirror.com/splitpanes/-/splitpanes-3.1.1.tgz",
+			"integrity": "sha512-VUkxDJfIGSvTM/fm/+OSrx8ha9URwE/9B8FPvfzoBuAxVELIHBWpsfnJXIXv77zVwuex//QQL4kTU9SDBPeHjA=="
+		},
+		"strip-ansi": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+			"dev": true,
+			"requires": {
+				"ansi-regex": "^5.0.1"
+			}
+		},
+		"strip-json-comments": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+			"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+			"dev": true
+		},
+		"supports-color": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+			"dev": true,
+			"requires": {
+				"has-flag": "^4.0.0"
+			}
+		},
+		"supports-preserve-symlinks-flag": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+			"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+			"dev": true
+		},
+		"text-table": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
+			"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+			"dev": true
+		},
+		"tiny-emitter": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+			"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+		},
+		"to-regex-range": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+			"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+			"dev": true,
+			"requires": {
+				"is-number": "^7.0.0"
+			}
+		},
+		"tslib": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz",
+			"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+			"dev": true
+		},
+		"tsutils": {
+			"version": "3.21.0",
+			"resolved": "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz",
+			"integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+			"dev": true,
+			"requires": {
+				"tslib": "^1.8.1"
+			}
+		},
+		"type-check": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
+			"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+			"dev": true,
+			"requires": {
+				"prelude-ls": "^1.2.1"
+			}
+		},
+		"type-fest": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz",
+			"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+			"dev": true
+		},
+		"typescript": {
+			"version": "4.6.2",
+			"resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.6.2.tgz",
+			"integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
+			"dev": true
+		},
+		"uri-js": {
+			"version": "4.4.1",
+			"resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+			"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+			"dev": true,
+			"requires": {
+				"punycode": "^2.1.0"
+			}
+		},
+		"v8-compile-cache": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+			"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+			"dev": true
+		},
+		"vite": {
+			"version": "2.8.6",
+			"resolved": "https://registry.npmmirror.com/vite/-/vite-2.8.6.tgz",
+			"integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==",
+			"dev": true,
+			"requires": {
+				"esbuild": "^0.14.14",
+				"fsevents": "~2.3.2",
+				"postcss": "^8.4.6",
+				"resolve": "^1.22.0",
+				"rollup": "^2.59.0"
+			}
+		},
+		"vue": {
+			"version": "3.2.31",
+			"resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.31.tgz",
+			"integrity": "sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==",
+			"requires": {
+				"@vue/compiler-dom": "3.2.31",
+				"@vue/compiler-sfc": "3.2.31",
+				"@vue/runtime-dom": "3.2.31",
+				"@vue/server-renderer": "3.2.31",
+				"@vue/shared": "3.2.31"
+			}
+		},
+		"vue-clipboard3": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmmirror.com/vue-clipboard3/-/vue-clipboard3-1.0.1.tgz",
+			"integrity": "sha512-iJ2vrizowfA73W3pcxMAKhYSvfekJrQ3FhbveVe9esS1Vfu+xW3Fgc0UKE8N4Q6DyRtcAoNlef8txmD8tK8dIg==",
+			"requires": {
+				"clipboard": "^2.0.6"
+			}
+		},
+		"vue-demi": {
+			"version": "0.12.1",
+			"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.1.tgz",
+			"integrity": "sha512-QL3ny+wX8c6Xm1/EZylbgzdoDolye+VpCXRhI2hug9dJTP3OUJ3lmiKN3CsVV3mOJKwFi0nsstbgob0vG7aoIw=="
+		},
+		"vue-eslint-parser": {
+			"version": "8.3.0",
+			"resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
+			"integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==",
+			"dev": true,
+			"requires": {
+				"debug": "^4.3.2",
+				"eslint-scope": "^7.0.0",
+				"eslint-visitor-keys": "^3.1.0",
+				"espree": "^9.0.0",
+				"esquery": "^1.4.0",
+				"lodash": "^4.17.21",
+				"semver": "^7.3.5"
+			},
+			"dependencies": {
+				"eslint-scope": {
+					"version": "7.1.1",
+					"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.1.1.tgz",
+					"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+					"dev": true,
+					"requires": {
+						"esrecurse": "^4.3.0",
+						"estraverse": "^5.2.0"
+					}
+				},
+				"estraverse": {
+					"version": "5.3.0",
+					"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+					"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+					"dev": true
+				}
+			}
+		},
+		"vue-grid-layout": {
+			"version": "3.0.0-beta1",
+			"resolved": "https://registry.npmmirror.com/vue-grid-layout/-/vue-grid-layout-3.0.0-beta1.tgz",
+			"integrity": "sha512-MsW0yfYNtnAO/uDhfZvkP6effxSJxvhAFbIL37x6Rn3vW9xf0WHVefKaSbQMLpSq3mXnR6ut0pg2Cd5lqIIZzg==",
+			"requires": {
+				"@interactjs/actions": "^1.10.2",
+				"@interactjs/auto-start": "^1.10.2",
+				"@interactjs/dev-tools": "^1.10.2",
+				"@interactjs/interactjs": "^1.10.2",
+				"@interactjs/modifiers": "^1.10.2",
+				"element-resize-detector": "^1.2.1",
+				"mitt": "^2.1.0"
+			},
+			"dependencies": {
+				"mitt": {
+					"version": "2.1.0",
+					"resolved": "https://registry.npmmirror.com/mitt/-/mitt-2.1.0.tgz",
+					"integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg=="
+				}
+			}
+		},
+		"vue-i18n": {
+			"version": "9.1.9",
+			"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.1.9.tgz",
+			"integrity": "sha512-JeRdNVxS2OGp1E+pye5XB6+M6BBkHwAv9C80Q7+kzoMdUDGRna06tjC0vCB/jDX9aWrl5swxOMFcyAr7or8XTA==",
+			"requires": {
+				"@intlify/core-base": "9.1.9",
+				"@intlify/shared": "9.1.9",
+				"@intlify/vue-devtools": "9.1.9",
+				"@vue/devtools-api": "^6.0.0-beta.7"
+			}
+		},
+		"vue-router": {
+			"version": "4.0.14",
+			"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.0.14.tgz",
+			"integrity": "sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw==",
+			"requires": {
+				"@vue/devtools-api": "^6.0.0"
+			}
+		},
+		"vuex": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz",
+			"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
+			"requires": {
+				"@vue/devtools-api": "^6.0.0-beta.11"
+			}
+		},
+		"wangeditor": {
+			"version": "4.7.12",
+			"resolved": "https://registry.npmmirror.com/wangeditor/-/wangeditor-4.7.12.tgz",
+			"integrity": "sha512-5KOIpQ0+idKvDTkjZOp7RHYXA97FyJ9mjwy+zQUdMUCqZItlEXzvVOYtOlHkJr/HcbwgIz/7f/trGFggZK5X4g==",
+			"requires": {
+				"@babel/runtime": "^7.11.2",
+				"@babel/runtime-corejs3": "^7.11.2",
+				"tslib": "^2.1.0"
+			},
+			"dependencies": {
+				"tslib": {
+					"version": "2.3.1",
+					"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.1.tgz",
+					"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+				}
+			}
+		},
+		"which": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+			"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+			"dev": true,
+			"requires": {
+				"isexe": "^2.0.0"
+			}
+		},
+		"word-wrap": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz",
+			"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+			"dev": true
+		},
+		"wrappy": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+			"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+			"dev": true
+		},
+		"yallist": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+			"dev": true
+		},
+		"zrender": {
+			"version": "5.3.0",
+			"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.3.0.tgz",
+			"integrity": "sha512-Ln2QB5uqI1ftNYMtCRxd+XDq6MOttLgam2tmhKAVA+j0ko47UT+VNlDvKTkqe4K2sJhBvB0EhYNLebqlCTjatQ==",
+			"requires": {
+				"tslib": "2.3.0"
+			},
+			"dependencies": {
+				"tslib": {
+					"version": "2.3.0",
+					"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+					"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+				}
+			}
+		}
+	}
+}

+ 80 - 0
package.json

@@ -0,0 +1,80 @@
+{
+	"name": "vue-next-admin",
+	"version": "2.0.2",
+	"description": "vue3 vite next admin template",
+	"author": "lyt_20201208",
+	"license": "MIT",
+	"scripts": {
+		"dev": "vite --force",
+		"build": "vite build",
+		"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
+	},
+	"dependencies": {
+		"@element-plus/icons-vue": "^1.0.0",
+		"axios": "^0.26.0",
+		"countup.js": "^2.1.0",
+		"cropperjs": "^1.5.12",
+		"echarts": "^5.3.0",
+		"echarts-gl": "^2.0.9",
+		"echarts-wordcloud": "^2.0.0",
+		"element-plus": "^2.0.4",
+		"jsplumb": "^2.15.6",
+		"mitt": "^3.0.0",
+		"nprogress": "^0.2.0",
+		"print-js": "^1.6.0",
+		"qrcodejs2-fixes": "^0.0.2",
+		"screenfull": "^6.0.1",
+		"sortablejs": "^1.14.0",
+		"splitpanes": "^3.1.1",
+		"vue": "^3.2.31",
+		"vue-clipboard3": "^1.0.1",
+		"vue-grid-layout": "^3.0.0-beta1",
+		"vue-i18n": "^9.1.9",
+		"vue-router": "^4.0.13",
+		"vuex": "^4.0.2",
+		"wangeditor": "^4.7.12"
+	},
+	"devDependencies": {
+		"@types/node": "^17.0.21",
+		"@types/nprogress": "^0.2.0",
+		"@types/sortablejs": "^1.10.7",
+		"@typescript-eslint/eslint-plugin": "^5.13.0",
+		"@typescript-eslint/parser": "^5.13.0",
+		"@vitejs/plugin-vue": "^2.2.4",
+		"@vue/compiler-sfc": "^3.2.31",
+		"dotenv": "^16.0.0",
+		"eslint": "^8.10.0",
+		"eslint-plugin-vue": "^8.5.0",
+		"prettier": "^2.5.1",
+		"sass": "^1.49.9",
+		"sass-loader": "^12.6.0",
+		"typescript": "^4.6.2",
+		"vite": "^2.8.6",
+		"vue-eslint-parser": "^8.3.0"
+	},
+	"browserslist": [
+		"> 1%",
+		"last 2 versions",
+		"not dead"
+	],
+	"bugs": {
+		"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
+	},
+	"engines": {
+		"node": ">=12.0.0",
+		"npm": ">= 6.0.0"
+	},
+	"keywords": [
+		"vue",
+		"vue3",
+		"vuejs/vue-next",
+		"element-ui",
+		"element-plus",
+		"vue-next-admin",
+		"next-admin"
+	],
+	"repository": {
+		"type": "git",
+		"url": "https://gitee.com/lyt-top/vue-next-admin.git"
+	}
+}

+ 3 - 0
plugins.d.ts

@@ -0,0 +1,3 @@
+declare module 'vue-grid-layout';
+declare module 'qrcodejs2-fixes';
+declare module 'splitpanes';

BIN
public/favicon.ico


+ 13 - 0
shim.d.ts

@@ -0,0 +1,13 @@
+/* eslint-disable */
+
+// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
+declare module '*.vue' {
+	import type { DefineComponent } from 'vue';
+	const component: DefineComponent<{}, {}, any>;
+	export default component;
+}
+
+// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx,使用 getCurrentInstance() 来获取
+interface Window {
+	nextLoading: boolean;
+}

+ 6 - 0
source.d.ts

@@ -0,0 +1,6 @@
+declare module '*.json';
+declare module '*.png';
+declare module '*.jpg';
+declare module '*.scss';
+declare module '*.ts';
+declare module '*.js';

+ 92 - 0
src/App.vue

@@ -0,0 +1,92 @@
+<template>
+	<el-config-provider :size="getGlobalComponentSize" :locale="i18nLocale">
+		<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
+		<LockScreen v-if="getThemeConfig.isLockScreen" />
+		<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
+		<CloseFull />
+	</el-config-provider>
+</template>
+
+<script lang="ts">
+import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
+import { useRoute } from 'vue-router';
+import { useStore } from '/@/store/index';
+import other from '/@/utils/other';
+import { Local, Session } from '/@/utils/storage';
+import setIntroduction from '/@/utils/setIconfont';
+import LockScreen from '/@/layout/lockScreen/index.vue';
+import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
+import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
+export default defineComponent({
+	name: 'app',
+	components: { LockScreen, Setings, CloseFull },
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const setingsRef = ref();
+		const route = useRoute();
+		const store = useStore();
+		const state = reactive({
+			i18nLocale: null,
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 获取全局组件大小
+		const getGlobalComponentSize = computed(() => {
+			return other.globalComponentSize;
+		});
+		// 布局配置弹窗打开
+		const openSetingsDrawer = () => {
+			setingsRef.value.openDrawer();
+		};
+		// 设置初始化,防止刷新时恢复默认
+		onBeforeMount(() => {
+			// 设置批量第三方 icon 图标
+			setIntroduction.cssCdn();
+			// 设置批量第三方 js
+			setIntroduction.jsCdn();
+		});
+		// 页面加载时
+		onMounted(() => {
+			nextTick(() => {
+				// 监听布局配置弹窗点击打开
+				proxy.mittBus.on('openSetingsDrawer', () => {
+					openSetingsDrawer();
+				});
+				// 设置 i18n,App.vue 中的 el-config-provider
+				proxy.mittBus.on('getI18nConfig', (locale: string) => {
+					(state.i18nLocale as string | null) = locale;
+				});
+				// 获取缓存中的布局配置
+				if (Local.get('themeConfig')) {
+					store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
+					document.documentElement.style.cssText = Local.get('themeConfigStyle');
+				}
+				// 获取缓存中的全屏配置
+				if (Session.get('isTagsViewCurrenFull')) {
+					store.dispatch('tagsViewRoutes/setCurrenFullscreen', Session.get('isTagsViewCurrenFull'));
+				}
+			});
+		});
+		// 页面销毁时,关闭监听布局配置/i18n监听
+		onUnmounted(() => {
+			proxy.mittBus.off('openSetingsDrawer', () => {});
+			proxy.mittBus.off('getI18nConfig', () => {});
+		});
+		// 监听路由的变化,设置网站标题
+		watch(
+			() => route.path,
+			() => {
+				other.useTitle();
+			}
+		);
+		return {
+			setingsRef,
+			getThemeConfig,
+			getGlobalComponentSize,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 34 - 0
src/api/login/index.ts

@@ -0,0 +1,34 @@
+import request from '/@/utils/request';
+
+/**
+ * 登录api接口集合
+ * @method signIn 用户登录
+ */
+export function login(params: object){
+	return request({
+		url: '/api/v1/system/login',
+		method: 'post',
+		data: params,
+	});
+}
+
+/**
+ * 获取验证码
+ */
+export function captcha(){
+	return request({
+		url:"/api/v1/pub/captcha/get",
+		method:"get"
+	})
+}
+
+/**
+ * 退出登录
+ */
+export function signOut(params: object){
+	return request({
+		url: '/api/v1/user/signOut',
+		method: 'post',
+		data: params,
+	});
+}

+ 42 - 0
src/api/system/config/index.ts

@@ -0,0 +1,42 @@
+import request from '/@/utils/request';
+
+
+export function getConfigList(query:Object) {
+    return request({
+        url: '/api/v1/system/config/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function getConfig(id:number) {
+    return request({
+        url: '/api/v1/system/config/get',
+        method: 'get',
+        params:{id}
+    })
+}
+
+export function addConfig(data:any) {
+    return request({
+        url: '/api/v1/system/config/add',
+        method: 'post',
+        data:data
+    })
+}
+
+export function editConfig(data:any) {
+    return request({
+        url: '/api/v1/system/config/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+export function deleteConfig(ids:number[]) {
+    return request({
+        url: '/api/v1/system/config/delete',
+        method: 'delete',
+        data:{ids}
+    })
+}

+ 27 - 0
src/api/system/dbInit/index.ts

@@ -0,0 +1,27 @@
+import request from '/@/utils/request';
+
+// 是否已初始化
+export function isInit() {
+    return request({
+        url: '/api/v1/system/dbInit/isInit',
+        method: 'get'
+    })
+}
+
+
+// 获取环境信息
+export function getEnvInfo() {
+    return request({
+        url: '/api/v1/system/dbInit/getEnvInfo',
+        method: 'get'
+    })
+}
+
+// 创建数据库
+export function createDb (data:object) {
+    return request({
+        url: '/api/v1/system/dbInit/createDb',
+        method: 'post',
+        data: data
+    })
+}

+ 36 - 0
src/api/system/dept/index.ts

@@ -0,0 +1,36 @@
+import request from '/@/utils/request';
+
+export function getDeptList(query?:Object) {
+    return request({
+        url: '/api/v1/system/dept/list',
+        method: 'get',
+        params:query
+    })
+}
+
+
+export function addDept(data:object) {
+    return request({
+        url: '/api/v1/system/dept/add',
+        method: 'post',
+        data:data
+    })
+}
+
+
+export function editDept(data:object) {
+    return request({
+        url: '/api/v1/system/dept/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+
+export function deleteDept(id:number) {
+    return request({
+        url: '/api/v1/system/dept/delete',
+        method: 'delete',
+        data:{id}
+    })
+}

+ 70 - 0
src/api/system/dict/data.ts

@@ -0,0 +1,70 @@
+import request from '/@/utils/request';
+import {ref ,toRefs,ToRefs} from 'vue'
+// 根据字典类型查询字典数据信息
+export function getDicts(dictType :string,defaultValue?:string):Promise<any> {
+    let dv = defaultValue??''
+    let params ={
+        dictType:dictType,
+        defaultValue:dv
+    }
+    return request({
+        url: '/api/v1/system/dict/data/getDictData',
+        method: 'get',
+        params:params
+    })
+}
+
+/**
+ * 获取字典数据
+ */
+export function useDict(...args:string[]):ToRefs<any>{
+    const res:any = ref({});
+    args.forEach((d:string) => {
+        res.value[d] = [];
+        getDicts(d).then(resp => {
+            res.value[d] = resp.data.values.map((p:any) =>  ({ label: p.value, value: p.key, isDefault: p.isDefault }))
+        })
+    })
+    return toRefs(res.value);
+}
+
+
+export function getDataList(query:Object) {
+    return request({
+        url: '/api/v1/system/dict/data/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function getData(dictCode:number) {
+    return request({
+        url: '/api/v1/system/dict/data/get',
+        method: 'get',
+        params:{dictCode}
+    })
+}
+
+export function addData(data:any) {
+    return request({
+        url: '/api/v1/system/dict/data/add',
+        method: 'post',
+        data:data
+    })
+}
+
+export function editData(data:any) {
+    return request({
+        url: '/api/v1/system/dict/data/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+export function deleteData(ids:number[]) {
+    return request({
+        url: '/api/v1/system/dict/data/delete',
+        method: 'delete',
+        data:{ids}
+    })
+}

+ 43 - 0
src/api/system/dict/type.ts

@@ -0,0 +1,43 @@
+import request from '/@/utils/request';
+
+export function getTypeList(query:Object) {
+    return request({
+        url: '/api/v1/system/dict/type/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function getType(dictId:number) {
+    return request({
+        url: '/api/v1/system/dict/type/get',
+        method: 'get',
+        params:{dictId}
+    })
+}
+
+export function addType(data:any) {
+    return request({
+        url: '/api/v1/system/dict/type/add',
+        method: 'post',
+        data:data
+    })
+}
+
+export function editType(data:any) {
+    return request({
+        url: '/api/v1/system/dict/type/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+
+export function deleteType(dictIds:number[]) {
+    return request({
+        url: '/api/v1/system/dict/type/delete',
+        method: 'delete',
+        data:{dictIds}
+    })
+}
+

+ 57 - 0
src/api/system/menu/index.ts

@@ -0,0 +1,57 @@
+import request from '/@/utils/request';
+
+export function getMenuList(query:Object) {
+    return request({
+        url: '/api/v1/system/menu/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function getUserMenus() {
+    return request({
+        url: '/api/v1/system/user/getUserMenus',
+        method: 'get'
+    })
+}
+
+export function getMenuParams() {
+    return request({
+        url: '/api/v1/system/menu/getParams',
+        method: 'get'
+    })
+}
+
+export function addMenu(data:Object) {
+    return request({
+        url: '/api/v1/system/menu/add',
+        method: 'post',
+        data:data
+    })
+}
+
+export function getMenuInfo(id:number) {
+    return request({
+        url: '/api/v1/system/menu/get',
+        method: 'get',
+        params:{id}
+    })
+}
+
+export function updateMenu(data:Object) {
+    return request({
+        url: '/api/v1/system/menu/update',
+        method: 'put',
+        data:data
+    })
+}
+
+
+// 删除菜单
+export function delMenu(menuId:number) {
+    return request({
+        url: '/api/v1/system/menu/delete',
+        method: 'delete',
+        data:{ids:[menuId]}
+    })
+}

+ 28 - 0
src/api/system/monitor/loginLog/index.ts

@@ -0,0 +1,28 @@
+import request from '/@/utils/request';
+
+
+export function logList(query:object) {
+    return request({
+        url: '/api/v1/system/loginLog/list',
+        method: 'get',
+        params:query
+    })
+}
+
+
+export function deleteLog(ids:number[]) {
+    return request({
+        url: '/api/v1/system/loginLog/delete',
+        method: 'delete',
+        params:{ids}
+    })
+}
+
+
+
+export function clearLog() {
+    return request({
+        url: '/api/v1/system/loginLog/clear',
+        method: 'delete',
+    })
+}

+ 9 - 0
src/api/system/monitor/server/index.ts

@@ -0,0 +1,9 @@
+import request from '/@/utils/request';
+
+
+export function getSysInfo() {
+    return request({
+        url: '/api/v1/system/monitor/server',
+        method: 'get'
+    })
+}

+ 35 - 0
src/api/system/post/index.ts

@@ -0,0 +1,35 @@
+import request from '/@/utils/request';
+
+export function getPostList(query:Object) {
+    return request({
+        url: '/api/v1/system/post/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function addPost(data:object) {
+    return request({
+        url: '/api/v1/system/post/add',
+        method: 'post',
+        data:data
+    })
+}
+
+
+export function editPost(data:object) {
+    return request({
+        url: '/api/v1/system/post/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+
+export function deletePost(ids:number[]) {
+    return request({
+        url: '/api/v1/system/post/delete',
+        method: 'delete',
+        data:{ids}
+    })
+}

+ 50 - 0
src/api/system/role/index.ts

@@ -0,0 +1,50 @@
+import request from '/@/utils/request';
+
+export function getRoleList(query:Object) {
+    return request({
+        url: '/api/v1/system/role/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function getRoleParams() {
+    return request({
+        url: '/api/v1/system/role/getParams',
+        method: 'get'
+    })
+}
+
+export function addRole(data:object) {
+    return request({
+        url: '/api/v1/system/role/add',
+        method: 'post',
+        data:data
+    })
+}
+
+export function getRole(id:number) {
+    return request({
+        url: '/api/v1/system/role/get',
+        method: 'get',
+        params:{id}
+    })
+}
+
+
+export function editRole(data:object) {
+    return request({
+        url: '/api/v1/system/role/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+
+export function deleteRole(id:number) {
+    return request({
+        url: '/api/v1/system/role/delete',
+        method: 'delete',
+        data:{ids:[id]}
+    })
+}

+ 75 - 0
src/api/system/user/index.ts

@@ -0,0 +1,75 @@
+import request from '/@/utils/request';
+
+export function getUserList(query:Object) {
+    return request({
+        url: '/api/v1/system/user/list',
+        method: 'get',
+        params:query
+    })
+}
+
+export function getDeptTree() {
+    return request({
+        url: '/api/v1/system/dept/treeSelect',
+        method: 'get'
+    })
+}
+
+export function getParams() {
+    return request({
+        url: '/api/v1/system/user/params',
+        method: 'get'
+    })
+}
+
+export function getEditUser(id:number) {
+    return request({
+        url: '/api/v1/system/user/getEdit',
+        method: 'get',
+        params:{id}
+    })
+}
+
+export function addUser(data:object) {
+    return request({
+        url: '/api/v1/system/user/add',
+        method: 'post',
+        data:data
+    })
+}
+
+
+export function editUser(data:object) {
+    return request({
+        url: '/api/v1/system/user/edit',
+        method: 'put',
+        data:data
+    })
+}
+
+export function resetUserPwd(userId:number, password:string) {
+    return request({
+        url: '/api/v1/system/user/resetPwd',
+        method: 'put',
+        data:{userId,password}
+    })
+}
+
+export function changeUserStatus(userId:number, status:number) {
+    return request({
+        url: '/api/v1/system/user/setStatus',
+        method: 'put',
+        data:{userId,status}
+    })
+}
+
+
+export function deleteUser(ids:number[]) {
+    return request({
+        url: '/api/v1/system/user/delete',
+        method: 'delete',
+        data:{ids}
+    })
+}
+
+

BIN
src/assets/401.png


BIN
src/assets/404.png


BIN
src/assets/bg.jpg


File diff suppressed because it is too large
+ 8 - 0
src/assets/login-icon-two.svg


File diff suppressed because it is too large
+ 2 - 0
src/assets/logo-mini.svg


+ 27 - 0
src/components/auth/auth.vue

@@ -0,0 +1,27 @@
+<template>
+	<slot v-if="getUserAuthBtnList" />
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+export default defineComponent({
+	name: 'auth',
+	props: {
+		value: {
+			type: String,
+			default: () => '',
+		},
+	},
+	setup(props) {
+		const store = useStore();
+		// 获取 vuex 中的用户权限
+		const getUserAuthBtnList = computed(() => {
+			return store.state.userInfos.userInfos.authBtnList.some((v: string) => v === props.value);
+		});
+		return {
+			getUserAuthBtnList,
+		};
+	},
+});
+</script>

+ 28 - 0
src/components/auth/authAll.vue

@@ -0,0 +1,28 @@
+<template>
+	<slot v-if="getUserAuthBtnList" />
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import { judementSameArr } from '/@/utils/arrayOperation';
+export default defineComponent({
+	name: 'authAll',
+	props: {
+		value: {
+			type: Array,
+			default: () => [],
+		},
+	},
+	setup(props) {
+		const store = useStore();
+		// 获取 vuex 中的用户权限
+		const getUserAuthBtnList = computed(() => {
+			return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
+		});
+		return {
+			getUserAuthBtnList,
+		};
+	},
+});
+</script>

+ 33 - 0
src/components/auth/auths.vue

@@ -0,0 +1,33 @@
+<template>
+	<slot v-if="getUserAuthBtnList" />
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+export default defineComponent({
+	name: 'auths',
+	props: {
+		value: {
+			type: Array,
+			default: () => [],
+		},
+	},
+	setup(props) {
+		const store = useStore();
+		// 获取 vuex 中的用户权限
+		const getUserAuthBtnList = computed(() => {
+			let flag = false;
+			store.state.userInfos.userInfos.authBtnList.map((val: string) => {
+				props.value.map((v) => {
+					if (val === v) flag = true;
+				});
+			});
+			return flag;
+		});
+		return {
+			getUserAuthBtnList,
+		};
+	},
+});
+</script>

+ 148 - 0
src/components/cropper/index.vue

@@ -0,0 +1,148 @@
+<template>
+	<div>
+		<el-dialog title="更换头像" v-model="isShowDialog" width="769px">
+			<div class="cropper-warp">
+				<div class="cropper-warp-left">
+					<img :src="cropperImg" class="cropper-warp-left-img" />
+				</div>
+				<div class="cropper-warp-right">
+					<div class="cropper-warp-right-title">预览</div>
+					<div class="cropper-warp-right-item">
+						<div class="cropper-warp-right-value">
+							<img :src="cropperImgBase64" class="cropper-warp-right-value-img" />
+						</div>
+						<div class="cropper-warp-right-label">100 x 100</div>
+					</div>
+					<div class="cropper-warp-right-item">
+						<div class="cropper-warp-right-value">
+							<img :src="cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
+						</div>
+						<div class="cropper-warp-right-label">50 x 50</div>
+					</div>
+				</div>
+			</div>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="onCancel" size="default">取 消</el-button>
+					<el-button type="primary" @click="onSubmit" size="default">更 换</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, nextTick, defineComponent } from 'vue';
+import Cropper from 'cropperjs';
+import 'cropperjs/dist/cropper.css';
+export default defineComponent({
+	name: 'cropperIndex',
+	setup() {
+		const state = reactive({
+			isShowDialog: false,
+			cropperImg: '',
+			cropperImgBase64: '',
+			cropper: null,
+		});
+		// 打开弹窗
+		const openDialog = (imgs: any) => {
+			state.cropperImg = imgs;
+			state.isShowDialog = true;
+			nextTick(() => {
+				initCropper();
+			});
+		};
+		// 关闭弹窗
+		const closeDialog = () => {
+			state.isShowDialog = false;
+		};
+		// 取消
+		const onCancel = () => {
+			closeDialog();
+		};
+		// 更换
+		const onSubmit = () => {
+			// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
+		};
+		// 初始化cropperjs图片裁剪
+		const initCropper = () => {
+			const letImg: any = document.querySelector('.cropper-warp-left-img');
+			(<any>state.cropper) = new Cropper(letImg, {
+				viewMode: 1,
+				dragMode: 'none',
+				initialAspectRatio: 1,
+				aspectRatio: 1,
+				preview: '.before',
+				background: false,
+				autoCropArea: 0.6,
+				zoomOnWheel: false,
+				crop: () => {
+					state.cropperImgBase64 = (<any>state.cropper).getCroppedCanvas().toDataURL('image/jpeg');
+				},
+			});
+		};
+		return {
+			openDialog,
+			closeDialog,
+			onCancel,
+			onSubmit,
+			initCropper,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.cropper-warp {
+	display: flex;
+	.cropper-warp-left {
+		position: relative;
+		display: inline-block;
+		height: 350px;
+		flex: 1;
+		border: var(--el-border-base);
+		background: var(--el-color-white);
+		overflow: hidden;
+		background-repeat: no-repeat;
+		cursor: move;
+		border-radius: var(--el-border-radius-base);
+		.cropper-warp-left-img {
+			width: 100%;
+			height: 100%;
+		}
+	}
+	.cropper-warp-right {
+		width: 150px;
+		height: 350px;
+		.cropper-warp-right-title {
+			text-align: center;
+			height: 20px;
+			line-height: 20px;
+		}
+		.cropper-warp-right-item {
+			margin: 15px 0;
+			.cropper-warp-right-value {
+				display: flex;
+				.cropper-warp-right-value-img {
+					width: 100px;
+					height: 100px;
+					border-radius: var(--el-border-radius-circle);
+					margin: auto;
+				}
+				.cropper-size {
+					width: 50px;
+					height: 50px;
+				}
+			}
+			.cropper-warp-right-label {
+				text-align: center;
+				font-size: 12px;
+				color: var(--el-text-color-primary);
+				height: 30px;
+				line-height: 30px;
+			}
+		}
+	}
+}
+</style>

+ 79 - 0
src/components/editor/index.vue

@@ -0,0 +1,79 @@
+<template>
+	<div class="editor-container">
+		<div :id="id"></div>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, onMounted, watch, defineComponent } from 'vue';
+import wangeditor from 'wangeditor';
+
+// 定义接口来定义对象的类型
+interface WangeditorState {
+	editor: any;
+}
+
+export default defineComponent({
+	name: 'wngEditor',
+	props: {
+		// 节点 id
+		id: {
+			type: String,
+			default: () => 'wangeditor',
+		},
+		// 是否禁用
+		isDisable: {
+			type: Boolean,
+			default: () => false,
+		},
+		// 内容框默认 placeholder
+		placeholder: {
+			type: String,
+			default: () => '请输入内容',
+		},
+		// 双向绑定
+		// 双向绑定值,字段名为固定,改了之后将不生效
+		// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
+		modelValue: String,
+	},
+	setup(props, { emit }) {
+		const state = reactive<WangeditorState>({
+			editor: null,
+		});
+		// 初始化富文本
+		// https://doc.wangeditor.com/
+		const initWangeditor = () => {
+			state.editor = new wangeditor(`#${props.id}`);
+			state.editor.config.zIndex = 1;
+			state.editor.config.placeholder = props.placeholder;
+			state.editor.config.uploadImgShowBase64 = true;
+			state.editor.config.showLinkImg = false;
+			onWangeditorChange();
+			state.editor.create();
+			state.editor.txt.html(props.modelValue);
+			props.isDisable ? state.editor.disable() : state.editor.enable();
+		};
+		// 内容改变时
+		const onWangeditorChange = () => {
+			state.editor.config.onchange = (html: string) => {
+				emit('update:modelValue', html);
+			};
+		};
+		// 页面加载时
+		onMounted(() => {
+			initWangeditor();
+		});
+		// 监听双向绑定值的改变
+		// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
+		watch(
+			() => props.modelValue,
+			(value) => {
+				state.editor.txt.html(value);
+			}
+		);
+		return {
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 242 - 0
src/components/iconSelector/index.vue

@@ -0,0 +1,242 @@
+<template>
+	<div class="icon-selector">
+		<el-popover placement="bottom" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
+			<template #reference>
+				<el-input
+					v-model="fontIconSearch"
+					:placeholder="fontIconPlaceholder"
+					:clearable="clearable"
+					:disabled="disabled"
+					:size="size"
+					ref="inputWidthRef"
+					@clear="onClearFontIcon"
+					@focus="onIconFocus"
+					@blur="onIconBlur"
+				>
+					<template #prepend>
+						<SvgIcon
+							:name="fontIconPrefix === '' ? prepend : fontIconPrefix"
+							class="font14"
+							v-if="fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : fontIconPrefix?.indexOf('ele-') > -1"
+						/>
+						<i v-else :class="fontIconPrefix === '' ? prepend : fontIconPrefix" class="font14"></i>
+					</template>
+				</el-input>
+			</template>
+			<transition name="el-zoom-in-top">
+				<div class="icon-selector-warp" v-show="fontIconVisible">
+					<div class="icon-selector-warp-title flex">
+						<div class="flex-auto">{{ title }}</div>
+						<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
+							<span :class="{ 'span-active': fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标">ali</span>
+							<span :class="{ 'span-active': fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标">ele</span>
+							<span :class="{ 'span-active': fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标">awe</span>
+						</div>
+					</div>
+					<div class="icon-selector-warp-row">
+						<el-scrollbar ref="selectorScrollbarRef">
+							<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
+								<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
+									<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconPrefix === v }">
+										<div class="flex-margin">
+											<div class="icon-selector-warp-item-value">
+												<SvgIcon :name="v" />
+											</div>
+										</div>
+									</div>
+								</el-col>
+							</el-row>
+							<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
+						</el-scrollbar>
+					</div>
+				</div>
+			</transition>
+		</el-popover>
+	</div>
+</template>
+
+<script lang="ts">
+import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
+import initIconfont from '/@/utils/getStyleSheets';
+export default defineComponent({
+	name: 'iconSelector',
+	emits: ['update:modelValue', 'get', 'clear'],
+	props: {
+		// 输入框前置内容
+		prepend: {
+			type: String,
+			default: () => 'ele-Pointer',
+		},
+		// 输入框占位文本
+		placeholder: {
+			type: String,
+			default: () => '请输入内容搜索图标或者选择图标',
+		},
+		// 输入框占位文本
+		size: {
+			type: String,
+			default: () => 'default',
+		},
+		// 弹窗标题
+		title: {
+			type: String,
+			default: () => '请选择图标',
+		},
+		// icon 图标类型
+		type: {
+			type: String,
+			default: () => 'ele',
+		},
+		// 禁用
+		disabled: {
+			type: Boolean,
+			default: () => false,
+		},
+		// 是否可清空
+		clearable: {
+			type: Boolean,
+			default: () => true,
+		},
+		// 自定义空状态描述文字
+		emptyDescription: {
+			type: String,
+			default: () => '无相关图标',
+		},
+		// 双向绑定值,字段名为固定,改了之后将不生效
+		// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
+		modelValue: String,
+	},
+	setup(props, { emit }) {
+		const inputWidthRef = ref();
+		const selectorScrollbarRef = ref();
+		const state = reactive({
+			fontIconPrefix: '',
+			fontIconVisible: false,
+			fontIconWidth: 0,
+			fontIconSearch: '',
+			fontIconTabsIndex: 0,
+			fontIconSheetsList: [],
+			fontIconPlaceholder: '',
+			fontIconType: 'ali',
+			fontIconShow: true,
+		});
+		// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
+		const onIconFocus = () => {
+			state.fontIconVisible = true;
+			if (!props.modelValue) return false;
+			state.fontIconSearch = '';
+			state.fontIconPlaceholder = props.modelValue;
+		};
+		// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
+		const onIconBlur = () => {
+			setTimeout(() => {
+				const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch);
+				if (icon.length <= 0) state.fontIconSearch = '';
+			}, 300);
+		};
+		// 处理 icon 双向绑定数值回显
+		const initModeValueEcho = () => {
+			if (props.modelValue === '') return false;
+			(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
+			(<string | undefined>state.fontIconPrefix) = props.modelValue;
+		};
+		// 图标搜索及图标数据显示
+		const fontIconSheetsFilterList = computed(() => {
+			if (!state.fontIconSearch) return state.fontIconSheetsList;
+			let search = state.fontIconSearch.trim().toLowerCase();
+			return state.fontIconSheetsList.filter((item: any) => {
+				if (item.toLowerCase().indexOf(search) !== -1) return item;
+			});
+		});
+		// 获取 input 的宽度
+		const getInputWidth = () => {
+			nextTick(() => {
+				state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
+			});
+		};
+		// 监听页面宽度改变
+		const initResize = () => {
+			window.addEventListener('resize', () => {
+				getInputWidth();
+			});
+		};
+		// 初始化数据
+		const initFontIconData = async (type: string) => {
+			state.fontIconSheetsList = [];
+			if (type === 'ali') {
+				await initIconfont.ali().then((res: any) => {
+					// 阿里字体图标使用 `iconfont xxx`
+					state.fontIconSheetsList = res.map((i: string) => `iconfont ${i}`);
+				});
+			} else if (type === 'ele') {
+				await initIconfont.ele().then((res: any) => {
+					state.fontIconSheetsList = res;
+				});
+			} else if (type === 'awe') {
+				await initIconfont.awe().then((res: any) => {
+					// fontawesome字体图标使用 `fa xxx`
+					state.fontIconSheetsList = res.map((i: string) => `fa ${i}`);
+				});
+			}
+			// 初始化 input 的 placeholder
+			// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
+			state.fontIconPlaceholder = props.placeholder;
+			// 初始化双向绑定回显
+			initModeValueEcho();
+			// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
+			selectorScrollbarRef.value.wrap$.scrollTop = 0;
+		};
+		// 图标点击切换
+		const onIconChange = (type: string) => {
+			state.fontIconType = type;
+			initFontIconData(type);
+		};
+		// 获取当前点击的 icon 图标
+		const onColClick = (v: any) => {
+			state.fontIconPlaceholder = v;
+			state.fontIconVisible = false;
+			state.fontIconPrefix = v;
+			emit('get', state.fontIconPrefix);
+			emit('update:modelValue', state.fontIconPrefix);
+		};
+		// 清空当前点击的 icon 图标
+		const onClearFontIcon = () => {
+			state.fontIconPrefix = '';
+			emit('clear', state.fontIconPrefix);
+			emit('update:modelValue', state.fontIconPrefix);
+		};
+		// 页面加载时
+		onMounted(() => {
+			// 判断默认进来是什么类型图标,进行 tab 回显
+			if (props.type === 'all') {
+				if ((<any>props.modelValue)?.indexOf('iconfont') > -1) onIconChange('ali');
+				else if ((<any>props.modelValue)?.indexOf('ele-') > -1) onIconChange('ele');
+				else if ((<any>props.modelValue)?.indexOf('fa') > -1) onIconChange('awe');
+				else onIconChange('ali');
+			} else {
+				onIconChange(props.type);
+			}
+			initResize();
+			getInputWidth();
+		});
+		// 监听双向绑定 modelValue 的变化
+		watch(
+			() => props.modelValue,
+			() => {
+				initModeValueEcho();
+			}
+		);
+		return {
+			inputWidthRef,
+			selectorScrollbarRef,
+			fontIconSheetsFilterList,
+			onColClick,
+			onIconChange,
+			onClearFontIcon,
+			onIconFocus,
+			onIconBlur,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 194 - 0
src/components/noticeBar/index.vue

@@ -0,0 +1,194 @@
+<template>
+	<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!isMode">
+		<div class="notice-bar-warp" :style="{ color, fontSize: `${size}px` }">
+			<i v-if="leftIcon" class="notice-bar-warp-left-icon" :class="leftIcon"></i>
+			<div class="notice-bar-warp-text-box" ref="noticeBarWarpRef">
+				<div class="notice-bar-warp-text" ref="noticeBarTextRef" v-if="!scrollable">{{ text }}</div>
+				<div class="notice-bar-warp-slot" v-else><slot /></div>
+			</div>
+			<SvgIcon :name="rightIcon" v-if="rightIcon" class="notice-bar-warp-right-icon" @click="onRightIconClick" />
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
+export default defineComponent({
+	name: 'noticeBar',
+	props: {
+		// 通知栏模式,可选值为 closeable link
+		mode: {
+			type: String,
+			default: () => '',
+		},
+		// 通知文本内容
+		text: {
+			type: String,
+			default: () => '',
+		},
+		// 通知文本颜色
+		color: {
+			type: String,
+			default: () => 'var(--color-warning)',
+		},
+		// 通知背景色
+		background: {
+			type: String,
+			default: () => 'var(--color-warning-light-9)',
+		},
+		// 字体大小,单位px
+		size: {
+			type: [Number, String],
+			default: () => 14,
+		},
+		// 通知栏高度,单位px
+		height: {
+			type: Number,
+			default: () => 40,
+		},
+		// 动画延迟时间 (s)
+		delay: {
+			type: Number,
+			default: () => 1,
+		},
+		// 滚动速率 (px/s)
+		speed: {
+			type: Number,
+			default: () => 100,
+		},
+		// 是否开启垂直滚动
+		scrollable: {
+			type: Boolean,
+			default: () => false,
+		},
+		// 自定义左侧图标
+		leftIcon: {
+			type: String,
+			default: () => '',
+		},
+		// 自定义右侧图标
+		rightIcon: {
+			type: String,
+			default: () => '',
+		},
+	},
+	setup(props, { emit }) {
+		const noticeBarWarpRef = ref();
+		const noticeBarTextRef = ref();
+		const state = reactive({
+			order: 1,
+			oneTime: 0,
+			twoTime: 0,
+			warpOWidth: 0,
+			textOWidth: 0,
+			isMode: false,
+		});
+		// 初始化 animation 各项参数
+		const initAnimation = () => {
+			nextTick(() => {
+				state.warpOWidth = noticeBarWarpRef.value.offsetWidth;
+				state.textOWidth = noticeBarTextRef.value.offsetWidth;
+				document.styleSheets[0].insertRule(`@keyframes oneAnimation {0% {left: 0px;} 100% {left: -${state.textOWidth}px;}}`);
+				document.styleSheets[0].insertRule(`@keyframes twoAnimation {0% {left: ${state.warpOWidth}px;} 100% {left: -${state.textOWidth}px;}}`);
+				computeAnimationTime();
+				setTimeout(() => {
+					changeAnimation();
+				}, props.delay * 1000);
+			});
+		};
+		// 计算 animation 滚动时长
+		const computeAnimationTime = () => {
+			state.oneTime = state.textOWidth / props.speed;
+			state.twoTime = (state.textOWidth + state.warpOWidth) / props.speed;
+		};
+		// 改变 animation 动画调用
+		const changeAnimation = () => {
+			if (state.order === 1) {
+				noticeBarTextRef.value.style.cssText = `animation: oneAnimation ${state.oneTime}s linear; opactity: 1;}`;
+				state.order = 2;
+			} else {
+				noticeBarTextRef.value.style.cssText = `animation: twoAnimation ${state.twoTime}s linear infinite; opacity: 1;`;
+			}
+		};
+		// 监听 animation 动画的结束
+		const listenerAnimationend = () => {
+			noticeBarTextRef.value.addEventListener(
+				'animationend',
+				() => {
+					changeAnimation();
+				},
+				false
+			);
+		};
+		// 右侧 icon 图标点击
+		const onRightIconClick = () => {
+			if (!props.mode) return false;
+			if (props.mode === 'closeable') {
+				state.isMode = true;
+				emit('close');
+			} else if (props.mode === 'link') {
+				emit('link');
+			}
+		};
+		// 页面加载时
+		onMounted(() => {
+			if (props.scrollable) return false;
+			initAnimation();
+			listenerAnimationend();
+		});
+		return {
+			noticeBarWarpRef,
+			noticeBarTextRef,
+			onRightIconClick,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.notice-bar {
+	padding: 0 15px;
+	width: 100%;
+	border-radius: 4px;
+	.notice-bar-warp {
+		display: flex;
+		align-items: center;
+		width: 100%;
+		height: inherit;
+		.notice-bar-warp-text-box {
+			flex: 1;
+			height: inherit;
+			display: flex;
+			align-items: center;
+			overflow: hidden;
+			position: relative;
+			.notice-bar-warp-text {
+				white-space: nowrap;
+				position: absolute;
+				left: 0;
+			}
+			.notice-bar-warp-slot {
+				width: 100%;
+				white-space: nowrap;
+				::v-deep(.el-carousel__item) {
+					display: flex;
+					align-items: center;
+				}
+			}
+		}
+		.notice-bar-warp-left-icon {
+			width: 24px;
+			font-size: inherit !important;
+		}
+		.notice-bar-warp-right-icon {
+			width: 24px;
+			text-align: right;
+			font-size: inherit !important;
+			&:hover {
+				cursor: pointer;
+			}
+		}
+	}
+}
+</style>

+ 101 - 0
src/components/pagination/index.vue

@@ -0,0 +1,101 @@
+<template>
+  <div :class="{'hidden':hidden}" class="pagination-container">
+    <el-pagination
+        :background="background"
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :layout="layout"
+        :page-sizes="pageSizes"
+        :pager-count="pagerCount"
+        :total="total"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script lang="ts">
+import { toRefs, defineComponent,computed } from 'vue';
+const props = {
+  total: {
+    required: true,
+    type: Number
+  },
+  page: {
+    type: Number,
+    default: 1
+  },
+  limit: {
+    type: Number,
+    default: 20
+  },
+  pageSizes: {
+    type: Array,
+    default() {
+      return [10, 20, 30, 50]
+    }
+  },
+  // 移动端页码按钮的数量端默认值5
+  pagerCount: {
+    type: Number,
+    default: document.body.clientWidth < 992 ? 5 : 7
+  },
+  layout: {
+    type: String,
+    default: 'total, sizes, prev, pager, next, jumper'
+  },
+  background: {
+    type: Boolean,
+    default: true
+  },
+  hidden: {
+    type: Boolean,
+    default: false
+  }
+};
+export default defineComponent({
+  name: 'pagination',
+  props: props,
+  setup(props,{emit}){
+    const { page,limit,pageSizes } = toRefs(props);
+    const currentPage = computed({
+      get() {
+        return page.value;
+      },
+      set(val) {
+        emit('update:page', val)
+      }
+    });
+    const pageSize = computed({
+      get() {
+        return limit.value
+      },
+      set(val) {
+        emit('update:limit', val)
+      }
+    });
+    const handleSizeChange = (val:number) => {
+      emit('pagination', { page: currentPage.value, limit: val })
+    };
+    const handleCurrentChange=(val:number) => {
+      emit('pagination', { page: val, limit: pageSizes.value })
+    }
+    return {
+      currentPage,
+      pageSize,
+      handleSizeChange,
+      handleCurrentChange
+    }
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.pagination-container {
+  background: #fff;
+  padding: 32px 16px;
+}
+.pagination-container.hidden {
+  display: none;
+}
+</style>

+ 42 - 0
src/components/svgIcon/index.vue

@@ -0,0 +1,42 @@
+<script lang="ts">
+// 渲染函数:https://v3.cn.vuejs.org/guide/render-function.html
+import { h, resolveComponent } from 'vue';
+
+// 定义接口来定义对象的类型
+interface SvgIconProps {
+	name: string;
+	size: number;
+	color: string;
+}
+
+export default {
+	name: 'svgIcon',
+	props: {
+		// svg 图标组件名字
+		name: {
+			type: String,
+		},
+		// svg 大小
+		size: {
+			type: Number,
+			default: () => 14,
+		},
+		// svg 颜色
+		color: {
+			type: String,
+		},
+	},
+	setup(props: SvgIconProps) {
+		// 定义变量
+		const linesString: any[] = ['https', 'http', '/src', '/assets', import.meta.env.VITE_PUBLIC_PATH];
+		const onLineStyle: string = `font-size: ${props.size}px;color: ${props.color}`;
+		const localsStyle: string = `width: ${props.size}px;height: ${props.size}px`;
+		const eleSetStyle = { class: 'el-icon', style: onLineStyle };
+
+		// 逻辑判断
+		if (props.name?.startsWith('ele-')) return () => h('i', eleSetStyle, [props.name === 'ele-' ? '' : h(resolveComponent(props.name))]);
+		else if (linesString.find((str) => props.name?.startsWith(str))) return () => h('img', { src: props.name, style: localsStyle });
+		else return () => h('i', { class: props.name, style: onLineStyle });
+	},
+};
+</script>

+ 56 - 0
src/i18n/index.ts

@@ -0,0 +1,56 @@
+import { createI18n } from 'vue-i18n';
+import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
+import enLocale from 'element-plus/lib/locale/lang/en';
+import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
+import { store } from '/@/store/index';
+
+import nextZhcn from '/@/i18n/lang/zh-cn';
+import nextEn from '/@/i18n/lang/en';
+import nextZhtw from '/@/i18n/lang/zh-tw';
+
+import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn';
+import pagesLoginEn from '/@/i18n/pages/login/en';
+import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw';
+import pagesFormI18nZhcn from '/@/i18n/pages/formI18n/zh-cn';
+import pagesFormI18nEn from '/@/i18n/pages/formI18n/en';
+import pagesFormI18nZhtw from '/@/i18n/pages/formI18n/zh-tw';
+
+// 定义语言国际化内容
+/**
+ * 说明:
+ * /src/i18n/lang 下的 ts 为框架的国际化内容
+ * /src/i18n/pages 下的 ts 为各界面的国际化内容
+ */
+const messages = {
+	[zhcnLocale.name]: {
+		...zhcnLocale,
+		message: {
+			...nextZhcn,
+			...pagesLoginZhcn,
+			...pagesFormI18nZhcn,
+		},
+	},
+	[enLocale.name]: {
+		...enLocale,
+		message: {
+			...nextEn,
+			...pagesLoginEn,
+			...pagesFormI18nEn,
+		},
+	},
+	[zhtwLocale.name]: {
+		...zhtwLocale,
+		message: {
+			...nextZhtw,
+			...pagesLoginZhtw,
+			...pagesFormI18nZhtw,
+		},
+	},
+};
+
+// 导出语言国际化
+export const i18n = createI18n({
+	locale: store.state.themeConfig.themeConfig.globalI18n,
+	fallbackLocale: zhcnLocale.name,
+	messages,
+});

+ 181 - 0
src/i18n/lang/en.ts

@@ -0,0 +1,181 @@
+// 定义内容
+export default {
+	router: {
+		home: 'home',
+		system: 'system',
+		systemMenu: 'systemMenu',
+		systemRole: 'systemRole',
+		systemUser: 'systemUser',
+		systemDept: 'systemDept',
+		systemDic: 'systemDic',
+		limits: 'limits',
+		limitsFrontEnd: 'FrontEnd',
+		limitsFrontEndPage: 'FrontEndPage',
+		limitsFrontEndBtn: 'FrontEndBtn',
+		limitsBackEnd: 'BackEnd',
+		limitsBackEndEndPage: 'BackEndEndPage',
+		menu: 'menu',
+		menu1: 'menu1',
+		menu11: 'menu11',
+		menu12: 'menu12',
+		menu121: 'menu121',
+		menu122: 'menu122',
+		menu13: 'menu13',
+		menu2: 'menu2',
+		funIndex: 'function',
+		funTagsView: 'funTagsView',
+		funCountup: 'countup',
+		funWangEditor: 'wangEditor',
+		funCropper: 'cropper',
+		funQrcode: 'qrcode',
+		funEchartsMap: 'EchartsMap',
+		funPrintJs: 'PrintJs',
+		funClipboard: 'Copy cut',
+		funGridLayout: 'Drag layout',
+		funSplitpanes: 'Pane splitter',
+		funDragVerify: 'Validator',
+		pagesIndex: 'pages',
+		pagesFiltering: 'Filtering',
+		pagesFilteringDetails: 'FilteringDetails',
+		pagesFilteringDetails1: 'FilteringDetails1',
+		pagesIocnfont: 'iconfont icon',
+		pagesElement: 'element icon',
+		pagesAwesome: 'awesome icon',
+		pagesFormAdapt: 'FormAdapt',
+		pagesTableRules: 'pagesTableRules',
+		pagesFormI18n: 'FormI18n',
+		pagesFormRules: 'Multi form validation',
+		pagesDynamicForm: 'Dynamic complex form',
+		pagesWorkflow: 'Workflow',
+		pagesListAdapt: 'ListAdapt',
+		pagesWaterfall: 'Waterfall',
+		pagesSteps: 'Steps',
+		pagesPreview: 'Large preview',
+		pagesWaves: 'Wave effect',
+		pagesTree: 'tree alter table',
+		pagesDrag: 'Drag command',
+		pagesLazyImg: 'Image lazy loading',
+		makeIndex: 'makeIndex',
+		makeSelector: 'Icon selector',
+		makeNoticeBar: 'notification bar',
+		makeSvgDemo: 'Svgicon demo',
+		paramsIndex: 'Routing parameters',
+		paramsCommon: 'General routing',
+		paramsDynamic: 'Dynamic routing',
+		paramsCommonDetails: 'General routing details',
+		paramsDynamicDetails: 'Dynamic routing details',
+		chartIndex: 'chartIndex',
+		visualizingIndex: 'visualizingIndex',
+		visualizingLinkDemo1: 'visualizingLinkDemo1',
+		visualizingLinkDemo2: 'visualizingLinkDemo2',
+		personal: 'personal',
+		tools: 'tools',
+		layoutLinkView: 'LinkView',
+		layoutIfameView: 'IfameView',
+	},
+	staticRoutes: {
+		signIn: 'signIn',
+		notFound: 'notFound',
+		noPower: 'noPower',
+	},
+	user: {
+		title0: 'Component size',
+		title1: 'Language switching',
+		title2: 'Menu search',
+		title3: 'Layout configuration',
+		title4: 'news',
+		title5: 'Full screen on',
+		title6: 'Full screen off',
+		dropdownLarge: 'large',
+		dropdownDefault: 'default',
+		dropdownSmall: 'small',
+		dropdown1: 'home page',
+		dropdown2: 'Personal Center',
+		dropdown3: '404',
+		dropdown4: '401',
+		dropdown5: 'Log out',
+		dropdown6: 'Code warehouse',
+		searchPlaceholder: 'Menu search: support Chinese, routing path',
+		newTitle: 'notice',
+		newBtn: 'All read',
+		newGo: 'Go to the notification center',
+		newDesc: 'No notice',
+		logOutTitle: 'Tips',
+		logOutMessage: 'This operation will log out. Do you want to continue?',
+		logOutConfirm: 'determine',
+		logOutCancel: 'cancel',
+		logOutExit: 'Exiting',
+		logOutSuccess: 'Exit successfully!',
+	},
+	tagsView: {
+		refresh: 'refresh',
+		close: 'close',
+		closeOther: 'closeOther',
+		closeAll: 'closeAll',
+		fullscreen: 'fullscreen',
+		closeFullscreen: 'closeFullscreen',
+	},
+	notFound: {
+		foundTitle: 'Wrong address input, please re-enter the address~',
+		foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
+		foundBtn: 'Back to home page',
+	},
+	noAccess: {
+		accessTitle: 'You are not authorized to operate~',
+		accessMsg: 'Contact information: add QQ group discussion 665452019',
+		accessBtn: 'Reauthorization',
+	},
+	layout: {
+		configTitle: 'Layout configuration',
+		oneTitle: 'Global Themes',
+		twoTopTitle: 'top bar set up',
+		twoMenuTitle: 'Menu set up',
+		twoColumnsTitle: 'Columns set up',
+		twoTopBar: 'Top bar background',
+		twoTopBarColor: 'Top bar default font color',
+		twoIsTopBarColorGradual: 'Top bar gradient',
+		twoMenuBar: 'Menu background',
+		twoMenuBarColor: 'Menu default font color',
+		twoIsMenuBarColorGradual: 'Menu gradient',
+		twoColumnsMenuBar: 'Column menu background',
+		twoColumnsMenuBarColor: 'Default font color bar menu',
+		twoIsColumnsMenuBarColorGradual: 'Column gradient',
+		threeTitle: 'Interface settings',
+		threeIsCollapse: 'Menu horizontal collapse',
+		threeIsUniqueOpened: 'Menu accordion',
+		threeIsFixedHeader: 'Fixed header',
+		threeIsClassicSplitMenu: 'Classic layout split menu',
+		threeIsLockScreen: 'Open the lock screen',
+		threeLockScreenTime: 'screen locking(s/s)',
+		fourTitle: 'Interface display',
+		fourIsShowLogo: 'Sidebar logo',
+		fourIsBreadcrumb: 'Open breadcrumb',
+		fourIsBreadcrumbIcon: 'Open breadcrumb icon',
+		fourIsTagsview: 'Open tagsview',
+		fourIsTagsviewIcon: 'Open tagsview Icon',
+		fourIsCacheTagsView: 'Enable tagsview cache',
+		fourIsSortableTagsView: 'Enable tagsview drag',
+		fourIsShareTagsView: 'Enable tagsview sharing',
+		fourIsFooter: 'Open footer',
+		fourIsGrayscale: 'Grey model',
+		fourIsInvert: 'Color weak mode',
+		fourIsDark: 'Dark Mode',
+		fourIsWartermark: 'Turn on watermark',
+		fourWartermarkText: 'Watermark copy',
+		fiveTitle: 'Other settings',
+		fiveTagsStyle: 'Tagsview style',
+		fiveAnimation: 'page animation',
+		fiveColumnsAsideStyle: 'Column style',
+		fiveColumnsAsideLayout: 'Column layout',
+		sixTitle: 'Layout switch',
+		sixDefaults: 'One',
+		sixClassic: 'Two',
+		sixTransverse: 'Three',
+		sixColumns: 'Four',
+		tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.ts` It has been modified in.',
+		copyText: 'replication configuration',
+		resetText: 'restore default',
+		copyTextSuccess: 'Copy succeeded!',
+		copyTextError: 'Copy failed!',
+	},
+};

+ 181 - 0
src/i18n/lang/zh-cn.ts

@@ -0,0 +1,181 @@
+// 定义内容
+export default {
+	router: {
+		home: '首页',
+		system: '系统设置',
+		systemMenu: '菜单管理',
+		systemRole: '角色管理',
+		systemUser: '用户管理',
+		systemDept: '部门管理',
+		systemDic: '字典管理',
+		limits: '权限管理',
+		limitsFrontEnd: '前端控制',
+		limitsFrontEndPage: '页面权限',
+		limitsFrontEndBtn: '按钮权限',
+		limitsBackEnd: '后端控制',
+		limitsBackEndEndPage: '页面权限',
+		menu: '菜单嵌套',
+		menu1: '菜单1',
+		menu11: '菜单11',
+		menu12: '菜单12',
+		menu121: '菜单121',
+		menu122: '菜单122',
+		menu13: '菜单13',
+		menu2: '菜单2',
+		funIndex: '功能',
+		funTagsView: 'tagsView 操作',
+		funCountup: '数字滚动',
+		funWangEditor: 'Editor 编辑器',
+		funCropper: '图片裁剪',
+		funQrcode: '二维码生成',
+		funEchartsMap: '地理坐标/地图',
+		funPrintJs: '页面打印',
+		funClipboard: '复制剪切',
+		funGridLayout: '拖拽布局',
+		funSplitpanes: '窗格拆分器',
+		funDragVerify: '验证器',
+		pagesIndex: '页面',
+		pagesFiltering: '过滤筛选组件',
+		pagesFilteringDetails: '过滤筛选组件详情',
+		pagesFilteringDetails1: '过滤筛选组件详情111',
+		pagesIocnfont: 'ali 字体图标',
+		pagesElement: 'ele 字体图标',
+		pagesAwesome: 'awe 字体图标',
+		pagesFormAdapt: '表单自适应',
+		pagesTableRules: '表单表格验证',
+		pagesFormI18n: '表单国际化',
+		pagesFormRules: '多表单验证',
+		pagesDynamicForm: '动态复杂表单',
+		pagesWorkflow: '工作流',
+		pagesListAdapt: '列表自适应',
+		pagesWaterfall: '瀑布屏',
+		pagesSteps: '步骤条',
+		pagesPreview: '大图预览',
+		pagesWaves: '波浪效果',
+		pagesTree: '树形改表格',
+		pagesDrag: '拖动指令',
+		pagesLazyImg: '图片懒加载',
+		makeIndex: '组件封装',
+		makeSelector: '图标选择器',
+		makeNoticeBar: '滚动通知栏',
+		makeSvgDemo: 'svgIcon 演示',
+		paramsIndex: '路由参数',
+		paramsCommon: '普通路由',
+		paramsDynamic: '动态路由',
+		paramsCommonDetails: '普通路由详情',
+		paramsDynamicDetails: '动态路由详情',
+		chartIndex: '大数据图表',
+		visualizingIndex: '数据可视化',
+		visualizingLinkDemo1: '数据可视化演示1',
+		visualizingLinkDemo2: '数据可视化演示2',
+		personal: '个人中心',
+		tools: '工具类集合',
+		layoutLinkView: '外链',
+		layoutIfameView: '内嵌 iframe',
+	},
+	staticRoutes: {
+		signIn: '登录',
+		notFound: '找不到此页面',
+		noPower: '没有权限',
+	},
+	user: {
+		title0: '组件大小',
+		title1: '语言切换',
+		title2: '菜单搜索',
+		title3: '布局配置',
+		title4: '消息',
+		title5: '开全屏',
+		title6: '关全屏',
+		dropdownLarge: '大型',
+		dropdownDefault: '默认',
+		dropdownSmall: '小型',
+		dropdown1: '首页',
+		dropdown2: '个人中心',
+		dropdown3: '404',
+		dropdown4: '401',
+		dropdown5: '退出登录',
+		dropdown6: '代码仓库',
+		searchPlaceholder: '菜单搜索:支持中文、路由路径',
+		newTitle: '通知',
+		newBtn: '全部已读',
+		newGo: '前往通知中心',
+		newDesc: '暂无通知',
+		logOutTitle: '提示',
+		logOutMessage: '此操作将退出登录, 是否继续?',
+		logOutConfirm: '确定',
+		logOutCancel: '取消',
+		logOutExit: '退出中',
+		logOutSuccess: '安全退出成功!',
+	},
+	tagsView: {
+		refresh: '刷新',
+		close: '关闭',
+		closeOther: '关闭其它',
+		closeAll: '全部关闭',
+		fullscreen: '当前页全屏',
+		closeFullscreen: '关闭全屏',
+	},
+	notFound: {
+		foundTitle: '地址输入错误,请重新输入地址~',
+		foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
+		foundBtn: '返回首页',
+	},
+	noAccess: {
+		accessTitle: '您未被授权,没有操作权限~',
+		accessMsg: '联系方式:加QQ群探讨 665452019',
+		accessBtn: '重新授权',
+	},
+	layout: {
+		configTitle: '布局配置',
+		oneTitle: '全局主题',
+		twoTopTitle: '顶栏设置',
+		twoMenuTitle: '菜单设置',
+		twoColumnsTitle: '分栏设置',
+		twoTopBar: '顶栏背景',
+		twoTopBarColor: '顶栏默认字体颜色',
+		twoIsTopBarColorGradual: '顶栏背景渐变',
+		twoMenuBar: '菜单背景',
+		twoMenuBarColor: '菜单默认字体颜色',
+		twoIsMenuBarColorGradual: '菜单背景渐变',
+		twoColumnsMenuBar: '分栏菜单背景',
+		twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
+		twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
+		threeTitle: '界面设置',
+		threeIsCollapse: '菜单水平折叠',
+		threeIsUniqueOpened: '菜单手风琴',
+		threeIsFixedHeader: '固定 Header',
+		threeIsClassicSplitMenu: '经典布局分割菜单',
+		threeIsLockScreen: '开启锁屏',
+		threeLockScreenTime: '自动锁屏(s/秒)',
+		fourTitle: '界面显示',
+		fourIsShowLogo: '侧边栏 Logo',
+		fourIsBreadcrumb: '开启 Breadcrumb',
+		fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
+		fourIsTagsview: '开启 Tagsview',
+		fourIsTagsviewIcon: '开启 Tagsview 图标',
+		fourIsCacheTagsView: '开启 TagsView 缓存',
+		fourIsSortableTagsView: '开启 TagsView 拖拽',
+		fourIsShareTagsView: '开启 TagsView 共用',
+		fourIsFooter: '开启 Footer',
+		fourIsGrayscale: '灰色模式',
+		fourIsInvert: '色弱模式',
+		fourIsDark: '深色模式',
+		fourIsWartermark: '开启水印',
+		fourWartermarkText: '水印文案',
+		fiveTitle: '其它设置',
+		fiveTagsStyle: 'Tagsview 风格',
+		fiveAnimation: '主页面切换动画',
+		fiveColumnsAsideStyle: '分栏高亮风格',
+		fiveColumnsAsideLayout: '分栏布局风格',
+		sixTitle: '布局切换',
+		sixDefaults: '默认',
+		sixClassic: '经典',
+		sixTransverse: '横向',
+		sixColumns: '分栏',
+		tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.ts` 中修改。',
+		copyText: '一键复制配置',
+		resetText: '一键恢复默认',
+		copyTextSuccess: '复制成功!',
+		copyTextError: '复制失败!',
+	},
+};

+ 181 - 0
src/i18n/lang/zh-tw.ts

@@ -0,0 +1,181 @@
+// 定义内容
+export default {
+	router: {
+		home: '首頁',
+		system: '系統設置',
+		systemMenu: '選單管理',
+		systemRole: '角色管理',
+		systemUser: '用戶管理',
+		systemDept: '部門管理',
+		systemDic: '字典管理',
+		limits: '許可權管理',
+		limitsFrontEnd: '前端控制',
+		limitsFrontEndPage: '頁面許可權',
+		limitsFrontEndBtn: '按鈕許可權',
+		limitsBackEnd: '後端控制',
+		limitsBackEndEndPage: '頁面許可權',
+		menu: '選單嵌套',
+		menu1: '選單1',
+		menu11: '選單11',
+		menu12: '選單12',
+		menu121: '選單121',
+		menu122: '選單122',
+		menu13: '選單13',
+		menu2: '選單2',
+		funIndex: '功能',
+		funTagsView: 'tagsView 操作',
+		funCountup: '數位滾動',
+		funWangEditor: 'Editor 編輯器',
+		funCropper: '圖片裁剪',
+		funQrcode: '二維碼生成',
+		funEchartsMap: '地理座標/地圖',
+		funPrintJs: '頁面列印',
+		funClipboard: '複製剪切',
+		funGridLayout: '拖拽佈局',
+		funSplitpanes: '窗格折開器',
+		funDragVerify: '驗證器',
+		pagesIndex: '頁面',
+		pagesFiltering: '過濾篩選組件',
+		pagesFilteringDetails: '過濾篩選組件詳情',
+		pagesFilteringDetails1: '過濾篩選組件詳情111',
+		pagesIocnfont: 'ali 字體圖標',
+		pagesElement: 'ele 字體圖標',
+		pagesAwesome: 'awe 字體圖標',
+		pagesFormAdapt: '表單自我調整',
+		pagesTableRules: '表單表格驗證',
+		pagesFormI18n: '表單國際化',
+		pagesFormRules: '多表單驗證',
+		pagesDynamicForm: '動態複雜表單',
+		pagesWorkflow: '工作流',
+		pagesListAdapt: '清單自我調整',
+		pagesWaterfall: '瀑布屏',
+		pagesSteps: '步驟條',
+		pagesPreview: '大圖預覽',
+		pagesWaves: '波浪效果',
+		pagesTree: '樹形改表格',
+		pagesDrag: '拖動指令',
+		pagesLazyImg: '圖片懶加載',
+		makeIndex: '組件封裝',
+		makeSelector: '圖標選擇器',
+		makeNoticeBar: '滾動通知欄',
+		makeSvgDemo: 'svgIcon 演示',
+		paramsIndex: '路由參數',
+		paramsCommon: '普通路由',
+		paramsDynamic: '動態路由',
+		paramsCommonDetails: '普通路由詳情',
+		paramsDynamicDetails: '動態路由詳情',
+		chartIndex: '大資料圖表',
+		visualizingIndex: '數據視覺化',
+		visualizingLinkDemo1: '數據視覺化演示1',
+		visualizingLinkDemo2: '數據視覺化演示2',
+		personal: '個人中心',
+		tools: '工具類集合',
+		layoutLinkView: '外鏈',
+		layoutIfameView: '内嵌 iframe',
+	},
+	staticRoutes: {
+		signIn: '登入',
+		notFound: '找不到此頁面',
+		noPower: '沒有許可權',
+	},
+	user: {
+		title0: '組件大小',
+		title1: '語言切換',
+		title2: '選單蒐索',
+		title3: '佈局配寘',
+		title4: '消息',
+		title5: '開全屏',
+		title6: '關全屏',
+		dropdownLarge: '大型',
+		dropdownDefault: '默認',
+		dropdownSmall: '小型',
+		dropdown1: '首頁',
+		dropdown2: '個人中心',
+		dropdown3: '404',
+		dropdown4: '401',
+		dropdown5: '登出',
+		dropdown6: '程式碼倉庫',
+		searchPlaceholder: '選單蒐索:支援中文、路由路徑',
+		newTitle: '通知',
+		newBtn: '全部已讀',
+		newGo: '前往通知中心',
+		newDesc: '暫無通知',
+		logOutTitle: '提示',
+		logOutMessage: '此操作將登出,是否繼續?',
+		logOutConfirm: '確定',
+		logOutCancel: '取消',
+		logOutExit: '退出中',
+		logOutSuccess: '安全退出成功!',
+	},
+	tagsView: {
+		refresh: '重繪',
+		close: '關閉',
+		closeOther: '關閉其它',
+		closeAll: '全部關閉',
+		fullscreen: '當前頁全屏',
+		closeFullscreen: '關閉全屏',
+	},
+	notFound: {
+		foundTitle: '地址輸入錯誤,請重新輸入地址~',
+		foundMsg: '您可以先檢查網址,然後重新輸入或給我們迴響問題。',
+		foundBtn: '返回首頁',
+	},
+	noAccess: {
+		accessTitle: '您未被授權,沒有操作許可權~',
+		accessMsg: '聯繫方式:加QQ群探討665452019',
+		accessBtn: '重新授權',
+	},
+	layout: {
+		configTitle: '佈局配寘',
+		oneTitle: '全域主題',
+		twoTopTitle: '頂欄設定',
+		twoMenuTitle: '選單設定',
+		twoColumnsTitle: '分欄設定',
+		twoTopBar: '頂欄背景',
+		twoTopBarColor: '頂欄默認字體顏色',
+		twoIsTopBarColorGradual: '頂欄背景漸變',
+		twoMenuBar: '選單背景',
+		twoMenuBarColor: '選單默認字體顏色',
+		twoIsMenuBarColorGradual: '選單背景漸變',
+		twoColumnsMenuBar: '分欄選單背景',
+		twoColumnsMenuBarColor: '分欄選單默認字體顏色',
+		twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
+		threeTitle: '介面設定',
+		threeIsCollapse: '選單水准折疊',
+		threeIsUniqueOpened: '選單手風琴',
+		threeIsFixedHeader: '固定 Header',
+		threeIsClassicSplitMenu: '經典佈局分割選單',
+		threeIsLockScreen: '開啟鎖屏',
+		threeLockScreenTime: '自動鎖屏(s/秒)',
+		fourTitle: '介面顯示',
+		fourIsShowLogo: '側邊欄 Logo',
+		fourIsBreadcrumb: '開啟 Breadcrumb',
+		fourIsBreadcrumbIcon: '開啟 Breadcrumb 圖標',
+		fourIsTagsview: '開啟 Tagsview',
+		fourIsTagsviewIcon: '開啟 Tagsview 圖標',
+		fourIsCacheTagsView: '開啟 TagsView 緩存',
+		fourIsSortableTagsView: '開啟 TagsView 拖拽',
+		fourIsShareTagsView: '開啟 TagsView 共用',
+		fourIsFooter: '開啟 Footer',
+		fourIsGrayscale: '灰色模式',
+		fourIsInvert: '色弱模式',
+		fourIsDark: '深色模式',
+		fourIsWartermark: '開啟浮水印',
+		fourWartermarkText: '浮水印文案',
+		fiveTitle: '其它設定',
+		fiveTagsStyle: 'Tagsview 風格',
+		fiveAnimation: '主頁面切換動畫',
+		fiveColumnsAsideStyle: '分欄高亮風格',
+		fiveColumnsAsideLayout: '分欄佈局風格',
+		sixTitle: '佈局切換',
+		sixDefaults: '默認',
+		sixClassic: '經典',
+		sixTransverse: '橫向',
+		sixColumns: '分欄',
+		tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.ts`中修改。',
+		copyText: '一鍵複製配寘',
+		resetText: '一鍵恢復默認',
+		copyTextSuccess: '複製成功!',
+		copyTextError: '複製失敗!',
+	},
+};

+ 13 - 0
src/i18n/pages/formI18n/en.ts

@@ -0,0 +1,13 @@
+// 定义内容
+export default {
+	formI18nLabel: {
+		name: 'name',
+		email: 'email',
+		autograph: 'autograph',
+	},
+	formI18nPlaceholder: {
+		name: 'Please enter your name',
+		email: 'Please enter the users Department',
+		autograph: 'Please enter the login account name',
+	},
+};

+ 13 - 0
src/i18n/pages/formI18n/zh-cn.ts

@@ -0,0 +1,13 @@
+// 定义内容
+export default {
+	formI18nLabel: {
+		name: '姓名',
+		email: '用户归属部门',
+		autograph: '登陆账户名',
+	},
+	formI18nPlaceholder: {
+		name: '请输入姓名',
+		email: '请输入用户归属部门',
+		autograph: '请输入登陆账户名',
+	},
+};

+ 13 - 0
src/i18n/pages/formI18n/zh-tw.ts

@@ -0,0 +1,13 @@
+// 定义内容
+export default {
+	formI18nLabel: {
+		name: '姓名',
+		email: '用戶歸屬部門',
+		autograph: '登入帳戶名',
+	},
+	formI18nPlaceholder: {
+		name: '請輸入姓名',
+		email: '請輸入用戶歸屬部門',
+		autograph: '請輸入登入帳戶名',
+	},
+};

+ 29 - 0
src/i18n/pages/login/en.ts

@@ -0,0 +1,29 @@
+// 定义内容
+export default {
+	label: {
+		one1: 'User name login',
+		two2: 'Mobile number',
+	},
+	link: {
+		one3: 'Third party login',
+		two4: 'Links',
+	},
+	account: {
+		accountPlaceholder1: 'The user name admin or not is common',
+		accountPlaceholder2: 'Password: 123456',
+		accountPlaceholder3: 'Please enter the verification code',
+		accountBtnText: 'Sign in',
+	},
+	mobile: {
+		placeholder1: 'Please input mobile phone number',
+		placeholder2: 'Please enter the verification code',
+		codeText: 'Get code',
+		btnText: 'Sign in',
+		msgText:
+			'Warm tip: it is recommended to use Google, Microsoft edge, version 79.0.1072.62 and above browsers, and 360 browser, please use speed mode',
+	},
+	scan: {
+		text: 'Open the mobile phone to scan and quickly log in / register',
+	},
+	signInText: 'welcome back!',
+};

+ 28 - 0
src/i18n/pages/login/zh-cn.ts

@@ -0,0 +1,28 @@
+// 定义内容
+export default {
+	label: {
+		one1: '用户名登录',
+		two2: '手机号登录',
+	},
+	link: {
+		one3: '第三方登录',
+		two4: '友情链接',
+	},
+	account: {
+		accountPlaceholder1: '用户名 admin 或不输均为 common',
+		accountPlaceholder2: '密码:123456',
+		accountPlaceholder3: '请输入验证码',
+		accountBtnText: '登 录',
+	},
+	mobile: {
+		placeholder1: '请输入手机号',
+		placeholder2: '请输入验证码',
+		codeText: '获取验证码',
+		btnText: '登 录',
+		msgText: '* 温馨提示:建议使用谷歌、Microsoft Edge,版本 79.0.1072.62 及以上浏览器,360浏览器请使用极速模式',
+	},
+	scan: {
+		text: '打开手机扫一扫,快速登录/注册',
+	},
+	signInText: '欢迎回来!',
+};

+ 28 - 0
src/i18n/pages/login/zh-tw.ts

@@ -0,0 +1,28 @@
+// 定义内容
+export default {
+	label: {
+		one1: '用戶名登入',
+		two2: '手機號登入',
+	},
+	link: {
+		one3: '協力廠商登入',
+		two4: '友情連結',
+	},
+	account: {
+		accountPlaceholder1: '用戶名admin或不輸均為common',
+		accountPlaceholder2: '密碼:123456',
+		accountPlaceholder3: '請輸入驗證碼',
+		accountBtnText: '登入',
+	},
+	mobile: {
+		placeholder1: '請輸入手機號',
+		placeholder2: '請輸入驗證碼',
+		codeText: '獲取驗證碼',
+		btnText: '登入',
+		msgText: '* 溫馨提示:建議使用穀歌、Microsoft Edge,版本79.0.1072.62及以上瀏覽器,360瀏覽器請使用極速模式',
+	},
+	scan: {
+		text: '打開手機掃一掃,快速登錄/注册',
+	},
+	signInText: '歡迎回來!',
+};

+ 157 - 0
src/layout/component/aside.vue

@@ -0,0 +1,157 @@
+<template>
+	<div class="h100" v-show="!isTagsViewCurrenFull">
+		<el-aside class="layout-aside" :class="setCollapseStyle">
+			<Logo v-if="setShowLogo" />
+			<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
+				<Vertical :menuList="menuList" />
+			</el-scrollbar>
+		</el-aside>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import Logo from '/@/layout/logo/index.vue';
+import Vertical from '/@/layout/navMenu/vertical.vue';
+export default defineComponent({
+	name: 'layoutAside',
+	components: { Logo, Vertical },
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const store = useStore();
+		const state = reactive({
+			menuList: [],
+			clientWidth: 0,
+		});
+		// 获取卡片全屏信息
+		const isTagsViewCurrenFull = computed(() => {
+			return store.state.tagsViewRoutes.isTagsViewCurrenFull;
+		});
+		// 设置菜单展开/收起时的宽度
+		const setCollapseStyle = computed(() => {
+			const { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
+			const asideBrTheme = ['#FFFFFF', '#FFF', '#fff', '#ffffff'];
+			const asideBrColor = asideBrTheme.includes(menuBar) ? 'layout-el-aside-br-color' : '';
+			// 判断是否是手机端
+			if (state.clientWidth <= 1000) {
+				if (isCollapse) {
+					document.body.setAttribute('class', 'el-popup-parent--hidden');
+					const asideEle = document.querySelector('.layout-container') as HTMLElement;
+					const modeDivs = document.createElement('div');
+					modeDivs.setAttribute('class', 'layout-aside-mobile-mode');
+					asideEle.appendChild(modeDivs);
+					modeDivs.addEventListener('click', closeLayoutAsideMobileMode);
+					return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-open'];
+				} else {
+					// 关闭弹窗
+					closeLayoutAsideMobileMode();
+					return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
+				}
+			} else {
+				if (layout === 'columns') {
+					// 分栏布局,菜单收起时宽度给 1px
+					if (isCollapse) {
+						return [asideBrColor, 'layout-aside-pc-1'];
+					} else {
+						return [asideBrColor, 'layout-aside-pc-220'];
+					}
+				} else {
+					// 其它布局给 64px
+					if (isCollapse) {
+						return [asideBrColor, 'layout-aside-pc-64'];
+					} else {
+						return [asideBrColor, 'layout-aside-pc-220'];
+					}
+				}
+			}
+		});
+		// 关闭移动端蒙版
+		const closeLayoutAsideMobileMode = () => {
+			const el = document.querySelector('.layout-aside-mobile-mode');
+			el?.setAttribute('style', 'animation: error-img-two 0.3s');
+			setTimeout(() => {
+				el?.parentNode?.removeChild(el);
+			}, 300);
+			const clientWidth = document.body.clientWidth;
+			if (clientWidth < 1000) store.state.themeConfig.themeConfig.isCollapse = false;
+			document.body.setAttribute('class', '');
+		};
+		// 设置显示/隐藏 logo
+		const setShowLogo = computed(() => {
+			let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
+			return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
+		});
+		// 设置/过滤路由(非静态路由/是否显示在菜单中)
+		const setFilterRoutes = () => {
+			if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
+			(state.menuList as any) = filterRoutesFun(store.state.routesList.routesList);
+		};
+		// 路由过滤递归函数
+		const filterRoutesFun = (arr: Array<object>) => {
+			return arr
+				.filter((item: any) => !item.meta.isHide)
+				.map((item: any) => {
+					item = Object.assign({}, item);
+					if (item.children) item.children = filterRoutesFun(item.children);
+					return item;
+				});
+		};
+		// 设置菜单导航是否固定(移动端)
+		const initMenuFixed = (clientWidth: number) => {
+			state.clientWidth = clientWidth;
+		};
+		// 鼠标移入、移出
+		const onAsideEnterLeave = (bool: Boolean) => {
+			let { layout } = store.state.themeConfig.themeConfig;
+			if (layout !== 'columns') return false;
+			if (!bool) proxy.mittBus.emit('restoreDefault');
+			store.dispatch('routesList/setColumnsMenuHover', bool);
+		};
+		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
+		watch(store.state.themeConfig.themeConfig, (val) => {
+			if (val.isShowLogoChange !== val.isShowLogo) {
+				if (!proxy.$refs.layoutAsideScrollbarRef) return false;
+				proxy.$refs.layoutAsideScrollbarRef.update();
+			}
+		});
+		// 监听vuex值的变化,动态赋值给菜单中
+		watch(store.state, (val) => {
+			let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
+			if (layout === 'classic' && isClassicSplitMenu) return false;
+			setFilterRoutes();
+		});
+		// 页面加载前
+		onBeforeMount(() => {
+			initMenuFixed(document.body.clientWidth);
+			setFilterRoutes();
+			// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
+			// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
+			proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
+				state.menuList = res.children;
+			});
+			proxy.mittBus.on('setSendClassicChildren', (res: any) => {
+				let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
+				if (layout === 'classic' && isClassicSplitMenu) {
+					state.menuList = [];
+					state.menuList = res.children;
+				}
+			});
+			proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
+				setFilterRoutes();
+			});
+			proxy.mittBus.on('layoutMobileResize', (res: any) => {
+				initMenuFixed(res.clientWidth);
+				closeLayoutAsideMobileMode();
+			});
+		});
+		return {
+			setCollapseStyle,
+			setShowLogo,
+			isTagsViewCurrenFull,
+			onAsideEnterLeave,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 297 - 0
src/layout/component/columnsAside.vue

@@ -0,0 +1,297 @@
+<template>
+	<div class="layout-columns-aside">
+		<el-scrollbar>
+			<ul @mouseleave="onColumnsAsideMenuMouseleave()">
+				<li
+					v-for="(v, k) in columnsAsideList"
+					:key="k"
+					@click="onColumnsAsideMenuClick(v, k)"
+					@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
+					:ref="
+						(el) => {
+							if (el) columnsAsideOffsetTopRefs[k] = el;
+						}
+					"
+					:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
+					:title="v.meta.title.indexOf('.')>0?$t(v.meta.title):v.meta.title"
+				>
+					<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
+						<SvgIcon :name="v.meta.icon" />
+						<div class="columns-vertical-title font12">
+							{{tMenuTitle(v.meta.title)}}
+						</div>
+					</div>
+					<div :class="setColumnsAsidelayout" v-else>
+						<a :href="v.meta.isLink" target="_blank">
+							<SvgIcon :name="v.meta.icon" />
+							<div class="columns-vertical-title font12">
+								{{tMenuTitle(v.meta.title)}}
+							</div>
+						</a>
+					</div>
+				</li>
+				<div ref="columnsAsideActiveRef" :class="setColumnsAsideStyle"></div>
+			</ul>
+		</el-scrollbar>
+	</div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
+import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
+import { useStore } from '/@/store/index';
+import {useI18n} from "vue-i18n";
+
+// 定义接口来定义对象的类型
+interface ColumnsAsideState {
+	columnsAsideList: any[];
+	liIndex: number;
+	liOldIndex: null | number;
+	liHoverIndex: null | number;
+	liOldPath: null | string;
+	difference: number;
+	routeSplit: string[];
+	isNavHover: boolean;
+}
+
+export default defineComponent({
+	name: 'layoutColumnsAside',
+	setup() {
+		const columnsAsideOffsetTopRefs: any = ref([]);
+		const columnsAsideActiveRef = ref();
+    const { t } = useI18n();
+		const { proxy } = <any>getCurrentInstance();
+		const store = useStore();
+		const route = useRoute();
+		const router = useRouter();
+		const state = reactive<ColumnsAsideState>({
+			columnsAsideList: [],
+			liIndex: 0,
+			liOldIndex: null,
+			liHoverIndex: null,
+			liOldPath: null,
+			difference: 0,
+			routeSplit: [],
+			isNavHover: false,
+		});
+    // 设置菜单名称
+    const tMenuTitle = (title:string):string=>{
+      let rTitle = title.indexOf('.')>0?t(title):title;
+      rTitle && rTitle.length >= 4
+          ? rTitle.substring(0, store.state.themeConfig.themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
+          : rTitle
+      return rTitle
+    };
+		// 设置分栏高亮风格
+		const setColumnsAsideStyle = computed(() => {
+			return store.state.themeConfig.themeConfig.columnsAsideStyle;
+		});
+		// 设置分栏布局风格
+		const setColumnsAsidelayout = computed(() => {
+			return store.state.themeConfig.themeConfig.columnsAsideLayout;
+		});
+		// 设置菜单高亮位置移动
+		const setColumnsAsideMove = (k: number) => {
+			state.liIndex = k;
+			columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
+		};
+		// 菜单高亮点击事件
+		const onColumnsAsideMenuClick = (v: Object, k: number) => {
+			setColumnsAsideMove(k);
+			let { path, redirect } = v as any;
+			if (redirect) router.push(redirect);
+			else router.push(path);
+		};
+		// 鼠标移入时,显示当前的子级菜单
+		const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
+			let { path } = v;
+			state.liOldPath = path;
+			state.liOldIndex = k;
+			state.liHoverIndex = k;
+			proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
+			store.dispatch('routesList/setColumnsMenuHover', false);
+			store.dispatch('routesList/setColumnsNavHover', true);
+			state.isNavHover = true;
+		};
+		// 鼠标移走时,显示原来的子级菜单
+		const onColumnsAsideMenuMouseleave = async () => {
+			await store.dispatch('routesList/setColumnsNavHover', false);
+			// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
+			setTimeout(() => {
+				const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
+				if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
+			}, 100);
+			// state.isNavHover = false;
+		};
+		// 设置高亮动态位置
+		const onColumnsAsideDown = (k: number) => {
+			nextTick(() => {
+				setColumnsAsideMove(k);
+			});
+		};
+		// 设置/过滤路由(非静态路由/是否显示在菜单中)
+		const setFilterRoutes = () => {
+			state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
+			const resData: any = setSendChildren(route.path);
+			if (Object.keys(resData).length <= 0) return false;
+			onColumnsAsideDown(resData.item[0].k);
+			proxy.mittBus.emit('setSendColumnsChildren', resData);
+		};
+		// 传送当前子级数据到菜单中
+		const setSendChildren = (path: string) => {
+			const currentPathSplit = path.split('/');
+			let currentData: any = {};
+			state.columnsAsideList.map((v: any, k: number) => {
+				if (v.path === `/${currentPathSplit[1]}`) {
+					v['k'] = k;
+					currentData['item'] = [{ ...v }];
+					currentData['children'] = [{ ...v }];
+					if (v.children) currentData['children'] = v.children;
+				}
+			});
+			return currentData;
+		};
+		// 路由过滤递归函数
+		const filterRoutesFun = (arr: Array<object>) => {
+			return arr
+				.filter((item: any) => !item.meta.isHide)
+				.map((item: any) => {
+					item = Object.assign({}, item);
+					if (item.children) item.children = filterRoutesFun(item.children);
+					return item;
+				});
+		};
+		// tagsView 点击时,根据路由查找下标 columnsAsideList,实现左侧菜单高亮
+		const setColumnsMenuHighlight = (path: string) => {
+			state.routeSplit = path.split('/');
+			state.routeSplit.shift();
+			const routeFirst = `/${state.routeSplit[0]}`;
+			const currentSplitRoute = state.columnsAsideList.find((v: any) => v.path === routeFirst);
+			if (!currentSplitRoute) return false;
+			// 延迟拿值,防止取不到
+			setTimeout(() => {
+				onColumnsAsideDown((<any>currentSplitRoute).k);
+			}, 0);
+		};
+		// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
+		watch(store.state, (val) => {
+			val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
+			if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
+				state.liHoverIndex = null;
+				proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
+			} else {
+				state.liHoverIndex = state.liOldIndex;
+				if (!state.liOldPath) return false;
+				proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
+			}
+		});
+		// 页面加载时
+		onMounted(() => {
+			setFilterRoutes();
+			// 销毁变量,防止鼠标再次移入时,保留了上次的记录
+			proxy.mittBus.on('restoreDefault', () => {
+				state.liOldIndex = null;
+				state.liOldPath = null;
+			});
+		});
+		// 页面卸载时
+		onUnmounted(() => {
+			proxy.mittBus.off('restoreDefault', () => {});
+		});
+		// 路由更新时
+		onBeforeRouteUpdate((to) => {
+			setColumnsMenuHighlight(to.path);
+			proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
+		});
+		return {
+			columnsAsideOffsetTopRefs,
+			columnsAsideActiveRef,
+			onColumnsAsideDown,
+			setColumnsAsideStyle,
+			setColumnsAsidelayout,
+			onColumnsAsideMenuClick,
+			onColumnsAsideMenuMouseenter,
+			onColumnsAsideMenuMouseleave,
+      tMenuTitle,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-columns-aside {
+	width: 70px;
+	height: 100%;
+	background: var(--next-bg-columnsMenuBar);
+	ul {
+		position: relative;
+		li {
+			color: var(--next-bg-columnsMenuBarColor);
+			width: 100%;
+			height: 50px;
+			text-align: center;
+			display: flex;
+			cursor: pointer;
+			position: relative;
+			z-index: 1;
+			.columns-vertical {
+				margin: auto;
+				.columns-vertical-title {
+					padding-top: 1px;
+				}
+			}
+			.columns-horizontal {
+				display: flex;
+				height: 50px;
+				width: 100%;
+				align-items: center;
+				padding: 0 5px;
+				i {
+					margin-right: 3px;
+				}
+				a {
+					display: flex;
+					.columns-horizontal-title {
+						padding-top: 1px;
+					}
+				}
+			}
+			a {
+				text-decoration: none;
+				color: var(--next-bg-columnsMenuBarColor);
+			}
+		}
+		.layout-columns-active {
+			color: var(--el-color-white);
+			transition: 0.3s ease-in-out;
+		}
+		.layout-columns-hover {
+			color: var(--el-color-primary);
+			a {
+				color: var(--el-color-primary);
+			}
+		}
+		.columns-round {
+			background: var(--el-color-primary);
+			color: var(--el-color-white);
+			position: absolute;
+			left: 50%;
+			top: 2px;
+			height: 44px;
+			width: 65px;
+			transform: translateX(-50%);
+			z-index: 0;
+			transition: 0.3s ease-in-out;
+			border-radius: 5px;
+		}
+		.columns-card {
+			@extend .columns-round;
+			top: 0;
+			height: 50px;
+			width: 100%;
+			border-radius: 0;
+		}
+	}
+}
+</style>

+ 32 - 0
src/layout/component/header.vue

@@ -0,0 +1,32 @@
+<template>
+	<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
+		<NavBarsIndex />
+	</el-header>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import NavBarsIndex from '/@/layout/navBars/index.vue';
+export default defineComponent({
+	name: 'layoutHeader',
+	components: { NavBarsIndex },
+	setup() {
+		const store = useStore();
+		// 设置 header 的高度
+		const setHeaderHeight = computed(() => {
+			let { isTagsview, layout } = store.state.themeConfig.themeConfig;
+			if (isTagsview && layout !== 'classic') return '84px';
+			else return '50px';
+		});
+		// 获取卡片全屏信息
+		const isTagsViewCurrenFull = computed(() => {
+			return store.state.tagsViewRoutes.isTagsViewCurrenFull;
+		});
+		return {
+			setHeaderHeight,
+			isTagsViewCurrenFull,
+		};
+	},
+});
+</script>

+ 84 - 0
src/layout/component/main.vue

@@ -0,0 +1,84 @@
+<template>
+	<el-main class="layout-main">
+		<el-scrollbar
+			class="layout-scrollbar"
+			ref="layoutScrollbarRef"
+			:style="{ padding: currentRouteMeta.isLink && currentRouteMeta.isIframe ? 0 : '', transition: 'padding 0.3s ease-in-out' }"
+		>
+			<LayoutParentView :style="{ minHeight: `calc(100vh - ${headerHeight})` }" />
+			<Footer v-if="getThemeConfig.isFooter" />
+		</el-scrollbar>
+	</el-main>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted } from 'vue';
+import { useStore } from '/@/store/index';
+import { useRoute } from 'vue-router';
+import LayoutParentView from '/@/layout/routerView/parent.vue';
+import Footer from '/@/layout/footer/index.vue';
+
+// 定义接口来定义对象的类型
+interface MainState {
+	headerHeight: string | number;
+	currentRouteMeta: any;
+}
+
+export default defineComponent({
+	name: 'layoutMain',
+	components: { LayoutParentView, Footer },
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const route = useRoute();
+		const store = useStore();
+		const state = reactive<MainState>({
+			headerHeight: '',
+			currentRouteMeta: {},
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 设置 main 的高度
+		const initHeaderHeight = () => {
+			const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
+			let { isTagsview } = store.state.themeConfig.themeConfig;
+			if (isTagsview) return (state.headerHeight = bool ? `85px` : `114px`);
+			else return (state.headerHeight = `51px`);
+		};
+		// 初始化获取当前路由 meta,用于设置 iframes padding
+		const initGetMeta = () => {
+			state.currentRouteMeta = route.meta;
+		};
+		// 页面加载前
+		onMounted(async () => {
+			await initGetMeta();
+			initHeaderHeight();
+		});
+		// 监听路由变化
+		watch(
+			() => route.path,
+			() => {
+				state.currentRouteMeta = route.meta;
+				const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
+				state.headerHeight = bool ? `85px` : `114px`;
+				proxy.$refs.layoutScrollbarRef.update();
+			}
+		);
+		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
+		watch(store.state.themeConfig.themeConfig, (val) => {
+			state.currentRouteMeta = route.meta;
+			const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
+			state.headerHeight = val.isTagsview ? (bool ? `85px` : `114px`) : '51px';
+			if (val.isFixedHeaderChange !== val.isFixedHeader) {
+				if (!proxy.$refs.layoutScrollbarRef) return false;
+				proxy.$refs.layoutScrollbarRef.update();
+			}
+		});
+		return {
+			getThemeConfig,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 46 - 0
src/layout/footer/index.vue

@@ -0,0 +1,46 @@
+<template>
+	<div class="layout-footer mt15" v-show="isDelayFooter">
+		<div class="layout-footer-warp">
+			<div>Copyright © 2021-2023 g-fast.cn All Rights Reserved.</div>
+			<div class="mt5">云南奇讯科技有限公司版权所有</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, defineComponent } from 'vue';
+import { onBeforeRouteUpdate } from 'vue-router';
+export default defineComponent({
+	name: 'layoutFooter',
+	setup() {
+		const state = reactive({
+			isDelayFooter: true,
+		});
+		// 路由改变时,等主界面动画加载完毕再显示 footer
+		onBeforeRouteUpdate(() => {
+			setTimeout(() => {
+				state.isDelayFooter = false;
+				setTimeout(() => {
+					state.isDelayFooter = true;
+				}, 800);
+			}, 0);
+		});
+		return {
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-footer {
+	width: 100%;
+	display: flex;
+	&-warp {
+		margin: auto;
+		color: var(--el-text-color-secondary);
+		text-align: center;
+		animation: error-num 1s ease-in-out;
+	}
+}
+</style>

+ 55 - 0
src/layout/index.vue

@@ -0,0 +1,55 @@
+<template>
+	<component :is="getThemeConfig.layout" />
+</template>
+
+<script lang="ts">
+import { computed, onBeforeMount, onUnmounted, getCurrentInstance, defineComponent, defineAsyncComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import { Local } from '/@/utils/storage';
+export default defineComponent({
+	name: 'layout',
+	components: {
+		defaults: defineAsyncComponent(() => import('/@/layout/main/defaults.vue')),
+		classic: defineAsyncComponent(() => import('/@/layout/main/classic.vue')),
+		transverse: defineAsyncComponent(() => import('/@/layout/main/transverse.vue')),
+		columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
+	},
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const store = useStore();
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 窗口大小改变时(适配移动端)
+		const onLayoutResize = () => {
+			if (!Local.get('oldLayout')) Local.set('oldLayout', getThemeConfig.value.layout);
+			const clientWidth = document.body.clientWidth;
+			if (clientWidth < 1000) {
+				getThemeConfig.value.isCollapse = false;
+				proxy.mittBus.emit('layoutMobileResize', {
+					layout: 'defaults',
+					clientWidth,
+				});
+			} else {
+				proxy.mittBus.emit('layoutMobileResize', {
+					layout: Local.get('oldLayout') ? Local.get('oldLayout') : getThemeConfig.value.layout,
+					clientWidth,
+				});
+			}
+		};
+		// 页面加载前
+		onBeforeMount(() => {
+			onLayoutResize();
+			window.addEventListener('resize', onLayoutResize);
+		});
+		// 页面卸载时
+		onUnmounted(() => {
+			window.removeEventListener('resize', onLayoutResize);
+		});
+		return {
+			getThemeConfig,
+		};
+	},
+});
+</script>

+ 370 - 0
src/layout/lockScreen/index.vue

@@ -0,0 +1,370 @@
+<template>
+	<div v-show="isShowLockScreen">
+		<div class="layout-lock-screen-mask"></div>
+		<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': isShowLoockLogin }"></div>
+		<div class="layout-lock-screen">
+			<div
+				class="layout-lock-screen-date"
+				ref="layoutLockScreenDateRef"
+				@mousedown="onDown"
+				@mousemove="onMove"
+				@mouseup="onEnd"
+				@touchstart.stop="onDown"
+				@touchmove.stop="onMove"
+				@touchend.stop="onEnd"
+			>
+				<div class="layout-lock-screen-date-box">
+					<div class="layout-lock-screen-date-box-time">
+						{{ time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ time.s }}</span>
+					</div>
+					<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
+				</div>
+				<div class="layout-lock-screen-date-top">
+					<SvgIcon name="ele-Top" />
+					<div class="layout-lock-screen-date-top-text">上滑解锁</div>
+				</div>
+			</div>
+			<transition name="el-zoom-in-center">
+				<div v-show="isShowLoockLogin" class="layout-lock-screen-login">
+					<div class="layout-lock-screen-login-box">
+						<div class="layout-lock-screen-login-box-img">
+							<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg" />
+						</div>
+						<div class="layout-lock-screen-login-box-name">Administrator</div>
+						<div class="layout-lock-screen-login-box-value">
+							<el-input
+								placeholder="请输入密码"
+								ref="layoutLockScreenInputRef"
+								v-model="lockScreenPassword"
+								@keyup.enter.native.stop="onLockScreenSubmit()"
+							>
+								<template #append>
+									<el-button @click="onLockScreenSubmit">
+										<el-icon class="el-input__icon">
+											<ele-Right />
+										</el-icon>
+									</el-button>
+								</template>
+							</el-input>
+						</div>
+					</div>
+					<div class="layout-lock-screen-login-icon">
+						<SvgIcon name="ele-Microphone" />
+						<SvgIcon name="ele-AlarmClock" />
+						<SvgIcon name="ele-SwitchButton" />
+					</div>
+				</div>
+			</transition>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import { formatDate } from '/@/utils/formatTime';
+import { Local } from '/@/utils/storage';
+
+// 定义接口来定义对象的类型
+interface LockScreenState {
+	transparency: number;
+	downClientY: number;
+	moveDifference: number;
+	isShowLoockLogin: boolean;
+	isFlags: boolean;
+	querySelectorEl: HTMLElement | string;
+	time: {
+		hm: string;
+		s: string;
+		mdq: string;
+	};
+	setIntervalTime: number;
+	isShowLockScreen: boolean;
+	isShowLockScreenIntervalTime: number;
+	lockScreenPassword: string;
+}
+
+export default defineComponent({
+	name: 'layoutLockScreen',
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const layoutLockScreenInputRef = ref();
+		const store = useStore();
+		const state = reactive<LockScreenState>({
+			transparency: 1,
+			downClientY: 0,
+			moveDifference: 0,
+			isShowLoockLogin: false,
+			isFlags: false,
+			querySelectorEl: '',
+			time: {
+				hm: '',
+				s: '',
+				mdq: '',
+			},
+			setIntervalTime: 0,
+			isShowLockScreen: false,
+			isShowLockScreenIntervalTime: 0,
+			lockScreenPassword: '',
+		});
+		// 鼠标按下
+		const onDown = (down: any) => {
+			state.isFlags = true;
+			state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
+		};
+		// 鼠标移动
+		const onMove = (move: any) => {
+			if (state.isFlags) {
+				const el = <HTMLElement>state.querySelectorEl;
+				const opacitys = (state.transparency -= 1 / 200);
+				if (move.touches) {
+					state.moveDifference = move.touches[0].clientY - state.downClientY;
+				} else {
+					state.moveDifference = move.clientY - state.downClientY;
+				}
+				if (state.moveDifference >= 0) return false;
+				el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
+				if (state.moveDifference < -400) {
+					el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
+					state.moveDifference = -el.clientHeight;
+					setTimeout(() => {
+						el && el.parentNode?.removeChild(el);
+					}, 300);
+				}
+				if (state.moveDifference === -el.clientHeight) {
+					state.isShowLoockLogin = true;
+					layoutLockScreenInputRef.value.focus();
+				}
+			}
+		};
+		// 鼠标松开
+		const onEnd = () => {
+			state.isFlags = false;
+			state.transparency = 1;
+			if (state.moveDifference >= -400) {
+				(<HTMLElement>state.querySelectorEl).setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
+			}
+		};
+		// 获取要拖拽的初始元素
+		const initGetElement = () => {
+			nextTick(() => {
+				state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
+			});
+		};
+		// 时间初始化
+		const initTime = () => {
+			state.time.hm = formatDate(new Date(), 'HH:MM');
+			state.time.s = formatDate(new Date(), 'SS');
+			state.time.mdq = formatDate(new Date(), 'mm月dd日,WWW');
+		};
+		// 时间初始化定时器
+		const initSetTime = () => {
+			initTime();
+			state.setIntervalTime = window.setInterval(() => {
+				initTime();
+			}, 1000);
+		};
+		// 锁屏时间定时器
+		const initLockScreen = () => {
+			if (store.state.themeConfig.themeConfig.isLockScreen) {
+				state.isShowLockScreenIntervalTime = window.setInterval(() => {
+					if (store.state.themeConfig.themeConfig.lockScreenTime <= 1) {
+						state.isShowLockScreen = true;
+						setLocalThemeConfig();
+						return false;
+					}
+					store.state.themeConfig.themeConfig.lockScreenTime--;
+				}, 1000);
+			} else {
+				clearInterval(state.isShowLockScreenIntervalTime);
+			}
+		};
+		// 存储布局配置
+		const setLocalThemeConfig = () => {
+			store.state.themeConfig.themeConfig.isDrawer = false;
+			Local.set('themeConfig', store.state.themeConfig.themeConfig);
+		};
+		// 密码输入点击事件
+		const onLockScreenSubmit = () => {
+			store.state.themeConfig.themeConfig.isLockScreen = false;
+			store.state.themeConfig.themeConfig.lockScreenTime = 30;
+			setLocalThemeConfig();
+		};
+		// 页面加载时
+		onMounted(() => {
+			initGetElement();
+			initSetTime();
+			initLockScreen();
+		});
+		// 页面卸载时
+		onUnmounted(() => {
+			window.clearInterval(state.setIntervalTime);
+			window.clearInterval(state.isShowLockScreenIntervalTime);
+		});
+		return {
+			layoutLockScreenInputRef,
+			onDown,
+			onMove,
+			onEnd,
+			onLockScreenSubmit,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-lock-screen-fixed {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+}
+.layout-lock-screen-filter {
+	filter: blur(1px);
+}
+.layout-lock-screen-mask {
+	background: var(--el-color-white);
+	@extend .layout-lock-screen-fixed;
+	z-index: 9999990;
+}
+.layout-lock-screen-img {
+	@extend .layout-lock-screen-fixed;
+	background-image: url('https://gitee.com/lyt-top/vue-next-admin-images/raw/master/images/03.jpg');
+	background-size: 100% 100%;
+	z-index: 9999991;
+}
+.layout-lock-screen {
+	@extend .layout-lock-screen-fixed;
+	z-index: 9999992;
+	&-date {
+		position: absolute;
+		left: 0;
+		top: 0;
+		width: 100%;
+		height: 100%;
+		color: var(--el-color-white);
+		z-index: 9999993;
+		user-select: none;
+		&-box {
+			position: absolute;
+			left: 30px;
+			bottom: 50px;
+			&-time {
+				font-size: 100px;
+				color: var(--color-whites);
+			}
+			&-info {
+				font-size: 40px;
+				color: var(--color-whites);
+			}
+			&-minutes {
+				font-size: 16px;
+			}
+		}
+		&-top {
+			width: 40px;
+			height: 40px;
+			line-height: 40px;
+			border-radius: 100%;
+			border: 1px solid var(--el-border-color-light, #ebeef5);
+			background: rgba(255, 255, 255, 0.1);
+			color: var(--color-whites);
+			opacity: 0.8;
+			position: absolute;
+			right: 30px;
+			bottom: 50px;
+			text-align: center;
+			overflow: hidden;
+			transition: all 0.3s ease;
+			i {
+				transition: all 0.3s ease;
+			}
+			&-text {
+				opacity: 0;
+				position: absolute;
+				top: 150%;
+				font-size: 12px;
+				color: var(--color-whites);
+				left: 50%;
+				line-height: 1.2;
+				transform: translate(-50%, -50%);
+				transition: all 0.3s ease;
+				width: 35px;
+			}
+			&:hover {
+				border: 1px solid rgba(255, 255, 255, 0.5);
+				background: rgba(255, 255, 255, 0.2);
+				box-shadow: 0 0 12px 0 rgba(255, 255, 255, 0.5);
+				color: var(--color-whites);
+				opacity: 1;
+				transition: all 0.3s ease;
+				i {
+					transform: translateY(-40px);
+					transition: all 0.3s ease;
+				}
+				.layout-lock-screen-date-top-text {
+					opacity: 1;
+					top: 50%;
+					transition: all 0.3s ease;
+				}
+			}
+		}
+	}
+	&-login {
+		position: relative;
+		z-index: 9999994;
+		width: 100%;
+		height: 100%;
+		left: 0;
+		top: 0;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		color: var(--color-whites);
+		&-box {
+			text-align: center;
+			margin: auto;
+			&-img {
+				width: 180px;
+				height: 180px;
+				margin: auto;
+				img {
+					width: 100%;
+					height: 100%;
+					border-radius: 100%;
+				}
+			}
+			&-name {
+				font-size: 26px;
+				margin: 15px 0 30px;
+			}
+		}
+		&-icon {
+			position: absolute;
+			right: 30px;
+			bottom: 30px;
+			i {
+				font-size: 20px;
+				margin-left: 15px;
+				cursor: pointer;
+				opacity: 0.8;
+				&:hover {
+					opacity: 1;
+				}
+			}
+		}
+	}
+}
+::v-deep(.el-input-group__append) {
+	background: var(--el-color-white);
+	padding: 0px 15px;
+}
+::v-deep(.el-input__inner) {
+	border-right-color: var(--el-border-color-extra-light);
+	&:hover {
+		border-color: var(--el-border-color-extra-light);
+	}
+}
+</style>

+ 82 - 0
src/layout/logo/index.vue

@@ -0,0 +1,82 @@
+<template>
+	<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
+		<img :src="logoMini" class="layout-logo-medium-img" />
+		<span>{{ getThemeConfig.globalTitle }}</span>
+	</div>
+	<div class="layout-logo-size" v-else @click="onThemeConfigChange">
+		<img :src="logoMini" class="layout-logo-size-img" />
+	</div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+
+import logoMini from '/@/assets/logo-mini.svg';
+
+export default defineComponent({
+	name: 'layoutLogo',
+	setup() {
+		const store = useStore();
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 设置 logo 的显示。classic 经典布局默认显示 logo
+		const setShowLogo = computed(() => {
+			let { isCollapse, layout } = store.state.themeConfig.themeConfig;
+			return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
+		});
+		// logo 点击实现菜单展开/收起
+		const onThemeConfigChange = () => {
+			if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
+			store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
+		};
+		return {
+			logoMini,
+			setShowLogo,
+			getThemeConfig,
+			onThemeConfigChange,
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-logo {
+	width: 220px;
+	height: 50px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
+	color: var(--el-color-primary);
+	font-size: 16px;
+	cursor: pointer;
+	animation: logoAnimation 0.3s ease-in-out;
+	&:hover {
+		span {
+			color: var(--color-primary-light-2);
+		}
+	}
+	&-medium-img {
+		width: 45px;
+	}
+}
+.layout-logo-size {
+	width: 100%;
+	height: 50px;
+	display: flex;
+	cursor: pointer;
+	animation: logoAnimation 0.3s ease-in-out;
+	&-img {
+		width: 20px;
+		margin: auto;
+	}
+	&:hover {
+		img {
+			animation: logoAnimation 0.3s ease-in-out;
+		}
+	}
+}
+</style>

+ 36 - 0
src/layout/main/classic.vue

@@ -0,0 +1,36 @@
+<template>
+	<el-container class="layout-container flex-center">
+		<Header />
+		<el-container class="layout-mian-height-50">
+			<Aside />
+			<div class="flex-center layout-backtop">
+				<TagsView v-if="getThemeConfig.isTagsview" />
+				<Main />
+			</div>
+		</el-container>
+		<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import Aside from '/@/layout/component/aside.vue';
+import Header from '/@/layout/component/header.vue';
+import Main from '/@/layout/component/main.vue';
+import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
+export default defineComponent({
+	name: 'layoutClassic',
+	components: { Aside, Header, Main, TagsView },
+	setup() {
+		const store = useStore();
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		return {
+			getThemeConfig,
+		};
+	},
+});
+</script>

+ 38 - 0
src/layout/main/columns.vue

@@ -0,0 +1,38 @@
+<template>
+	<el-container class="layout-container">
+		<ColumnsAside />
+		<div class="layout-columns-warp">
+			<Aside />
+			<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
+				<Header v-if="isFixedHeader" />
+				<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
+					<Header v-if="!isFixedHeader" />
+					<Main />
+				</el-scrollbar>
+			</el-container>
+		</div>
+		<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import Aside from '/@/layout/component/aside.vue';
+import Header from '/@/layout/component/header.vue';
+import Main from '/@/layout/component/main.vue';
+import ColumnsAside from '/@/layout/component/columnsAside.vue';
+export default defineComponent({
+	name: 'layoutColumns',
+	components: { Aside, Header, Main, ColumnsAside },
+	setup() {
+		const store = useStore();
+		const isFixedHeader = computed(() => {
+			return store.state.themeConfig.themeConfig.isFixedHeader;
+		});
+		return {
+			isFixedHeader,
+		};
+	},
+});
+</script>

+ 44 - 0
src/layout/main/defaults.vue

@@ -0,0 +1,44 @@
+<template>
+	<el-container class="layout-container">
+		<Aside />
+		<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
+			<Header v-if="isFixedHeader" />
+			<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
+				<Header v-if="!isFixedHeader" />
+				<Main />
+			</el-scrollbar>
+		</el-container>
+		<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script lang="ts">
+import { computed, getCurrentInstance, watch, defineComponent } from 'vue';
+import { useRoute } from 'vue-router';
+import { useStore } from '/@/store/index';
+import Aside from '/@/layout/component/aside.vue';
+import Header from '/@/layout/component/header.vue';
+import Main from '/@/layout/component/main.vue';
+export default defineComponent({
+	name: 'layoutDefaults',
+	components: { Aside, Header, Main },
+	setup() {
+		const { proxy } = getCurrentInstance() as any;
+		const store = useStore();
+		const route = useRoute();
+		const isFixedHeader = computed(() => {
+			return store.state.themeConfig.themeConfig.isFixedHeader;
+		});
+		// 监听路由的变化
+		watch(
+			() => route.path,
+			() => {
+				proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
+			}
+		);
+		return {
+			isFixedHeader,
+		};
+	},
+});
+</script>

+ 16 - 0
src/layout/main/transverse.vue

@@ -0,0 +1,16 @@
+<template>
+	<el-container class="layout-container flex-center layout-backtop">
+		<Header />
+		<Main />
+		<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script lang="ts">
+import Header from '/@/layout/component/header.vue';
+import Main from '/@/layout/component/main.vue';
+export default {
+	name: 'layoutTransverse',
+	components: { Header, Main },
+};
+</script>

+ 152 - 0
src/layout/navBars/breadcrumb/breadcrumb.vue

@@ -0,0 +1,152 @@
+<template>
+	<div class="layout-navbars-breadcrumb" :style="{ display: isShowBreadcrumb }">
+		<SvgIcon
+			class="layout-navbars-breadcrumb-icon"
+			:name="getThemeConfig.isCollapse ? 'ele-Expand' : 'ele-Fold'"
+			:size="16"
+			@click="onThemeConfigChange"
+		/>
+		<el-breadcrumb class="layout-navbars-breadcrumb-hide">
+			<transition-group name="breadcrumb" mode="out-in">
+				<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.meta.title">
+					<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
+						<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ v.meta.title.indexOf('.')>0?$t(v.meta.title):v.meta.title }}
+					</span>
+					<a v-else @click.prevent="onBreadcrumbClick(v)">
+						<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ v.meta.title.indexOf('.')>0?$t(v.meta.title):v.meta.title }}
+					</a>
+				</el-breadcrumb-item>
+			</transition-group>
+		</el-breadcrumb>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
+import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
+import { useStore } from '/@/store/index';
+import { Local } from '/@/utils/storage';
+
+// 定义接口来定义对象的类型
+interface BreadcrumbState {
+	breadcrumbList: Array<any>;
+	routeSplit: Array<string>;
+	routeSplitFirst: string;
+	routeSplitIndex: number;
+}
+
+export default defineComponent({
+	name: 'layoutBreadcrumb',
+	setup() {
+		const store = useStore();
+		const route = useRoute();
+		const router = useRouter();
+		const state = reactive<BreadcrumbState>({
+			breadcrumbList: [],
+			routeSplit: [],
+			routeSplitFirst: '',
+			routeSplitIndex: 1,
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 动态设置经典、横向布局不显示
+		const isShowBreadcrumb = computed(() => {
+			initRouteSplit(route.path);
+			const { layout, isBreadcrumb } = store.state.themeConfig.themeConfig;
+			if (layout === 'classic' || layout === 'transverse') return 'none';
+			else return isBreadcrumb ? '' : 'none';
+		});
+		// 面包屑点击时
+		const onBreadcrumbClick = (v: any) => {
+			const { redirect, path } = v;
+			if (redirect) router.push(redirect);
+			else router.push(path);
+		};
+		// 展开/收起左侧菜单点击
+		const onThemeConfigChange = () => {
+			store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
+			setLocalThemeConfig();
+		};
+		// 存储布局配置
+		const setLocalThemeConfig = () => {
+			Local.remove('themeConfig');
+			Local.set('themeConfig', getThemeConfig.value);
+		};
+		// 处理面包屑数据
+		const getBreadcrumbList = (arr: Array<object>) => {
+			arr.map((item: any) => {
+				state.routeSplit.map((v: any, k: number, arrs: any) => {
+					if (state.routeSplitFirst === item.path) {
+						state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
+						state.breadcrumbList.push(item);
+						state.routeSplitIndex++;
+						if (item.children) getBreadcrumbList(item.children);
+					}
+				});
+			});
+		};
+		// 当前路由字符串切割成数组,并删除第一项空内容
+		const initRouteSplit = (path: string) => {
+			if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
+			state.breadcrumbList = [store.state.routesList.routesList[0]];
+			state.routeSplit = path.split('/');
+			state.routeSplit.shift();
+			state.routeSplitFirst = `/${state.routeSplit[0]}`;
+			state.routeSplitIndex = 1;
+			getBreadcrumbList(store.state.routesList.routesList);
+		};
+		// 页面加载时
+		onMounted(() => {
+			initRouteSplit(route.path);
+		});
+		// 路由更新时
+		onBeforeRouteUpdate((to) => {
+			initRouteSplit(to.path);
+		});
+		return {
+			onThemeConfigChange,
+			isShowBreadcrumb,
+			getThemeConfig,
+			onBreadcrumbClick,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb {
+	flex: 1;
+	height: inherit;
+	display: flex;
+	align-items: center;
+	padding-left: 15px;
+	.layout-navbars-breadcrumb-icon {
+		cursor: pointer;
+		font-size: 18px;
+		margin-right: 15px;
+		color: var(--next-bg-topBarColor);
+	}
+	.layout-navbars-breadcrumb-span {
+		opacity: 0.7;
+		color: var(--next-bg-topBarColor);
+	}
+	.layout-navbars-breadcrumb-iconfont {
+		font-size: 14px;
+		margin-right: 5px;
+	}
+	::v-deep(.el-breadcrumb__separator) {
+		opacity: 0.7;
+		color: var(--next-bg-topBarColor);
+	}
+	::v-deep(.el-breadcrumb__inner a, .el-breadcrumb__inner.is-link) {
+		font-weight: unset !important;
+		color: var(--next-bg-topBarColor);
+		&:hover {
+			color: var(--el-color-primary) !important;
+		}
+	}
+}
+</style>

+ 63 - 0
src/layout/navBars/breadcrumb/closeFull.vue

@@ -0,0 +1,63 @@
+<template>
+	<div class="layout-navbars-close-full" v-if="isTagsViewCurrenFull">
+		<div class="layout-navbars-close-full-box" :title="$t('message.tagsView.closeFullscreen')" @click="onCloseFullscreen">
+			<SvgIcon name="ele-Close" />
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+export default defineComponent({
+	name: 'layoutCloseFull',
+	setup() {
+		const store = useStore();
+		// 获取卡片全屏信息
+		const isTagsViewCurrenFull = computed(() => {
+			return store.state.tagsViewRoutes.isTagsViewCurrenFull;
+		});
+		// 关闭当前全屏
+		const onCloseFullscreen = () => {
+			store.dispatch('tagsViewRoutes/setCurrenFullscreen', false);
+		};
+		return {
+			isTagsViewCurrenFull,
+			onCloseFullscreen,
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-close-full {
+	position: fixed;
+	z-index: 9999999999;
+	right: -30px;
+	top: -30px;
+	.layout-navbars-close-full-box {
+		width: 60px;
+		height: 60px;
+		border-radius: 100%;
+		position: relative;
+		cursor: pointer;
+		background: rgba(0, 0, 0, 0.1);
+		transition: all 0.3s ease;
+		i {
+			position: absolute;
+			left: 11px;
+			top: 35px;
+			color: #333333;
+			transition: all 0.3s ease;
+		}
+		&:hover {
+			background: rgba(0, 0, 0, 0.2);
+			transition: all 0.3s ease;
+			i {
+				color: var(--el-color-primary);
+				transition: all 0.3s ease;
+			}
+		}
+	}
+}
+</style>

+ 115 - 0
src/layout/navBars/breadcrumb/index.vue

@@ -0,0 +1,115 @@
+<template>
+	<div class="layout-navbars-breadcrumb-index">
+		<Logo v-if="setIsShowLogo" />
+		<Breadcrumb />
+		<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
+		<User />
+	</div>
+</template>
+
+<script lang="ts">
+import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
+import { useRoute } from 'vue-router';
+import { useStore } from '/@/store/index';
+import Breadcrumb from '/@/layout/navBars/breadcrumb/breadcrumb.vue';
+import User from '/@/layout/navBars/breadcrumb/user.vue';
+import Logo from '/@/layout/logo/index.vue';
+import Horizontal from '/@/layout/navMenu/horizontal.vue';
+
+// 定义接口来定义对象的类型
+interface IndexState {
+	menuList: object[];
+}
+
+export default defineComponent({
+	name: 'layoutBreadcrumbIndex',
+	components: { Breadcrumb, User, Logo, Horizontal },
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const store = useStore();
+		const route = useRoute();
+		const state = reactive<IndexState>({
+			menuList: [],
+		});
+		// 设置 logo 显示/隐藏
+		const setIsShowLogo = computed(() => {
+			let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
+			return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
+		});
+		// 设置是否显示横向导航菜单
+		const isLayoutTransverse = computed(() => {
+			let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
+			return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
+		});
+		// 设置/过滤路由(非静态路由/是否显示在菜单中)
+		const setFilterRoutes = () => {
+			let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
+			if (layout === 'classic' && isClassicSplitMenu) {
+				state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
+				const resData = setSendClassicChildren(route.path);
+				proxy.mittBus.emit('setSendClassicChildren', resData);
+			} else {
+				state.menuList = filterRoutesFun(store.state.routesList.routesList);
+			}
+		};
+		// 设置了分割菜单时,删除底下 children
+		const delClassicChildren = (arr: Array<object>) => {
+			arr.map((v: any) => {
+				if (v.children) delete v.children;
+			});
+			return arr;
+		};
+		// 路由过滤递归函数
+		const filterRoutesFun = (arr: Array<object>) => {
+			return arr
+				.filter((item: any) => !item.meta.isHide)
+				.map((item: any) => {
+					item = Object.assign({}, item);
+					if (item.children) item.children = filterRoutesFun(item.children);
+					return item;
+				});
+		};
+		// 传送当前子级数据到菜单中
+		const setSendClassicChildren = (path: string) => {
+			const currentPathSplit = path.split('/');
+			let currentData: any = {};
+			filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
+				if (v.path === `/${currentPathSplit[1]}`) {
+					v['k'] = k;
+					currentData['item'] = [{ ...v }];
+					currentData['children'] = [{ ...v }];
+					if (v.children) currentData['children'] = v.children;
+				}
+			});
+			return currentData;
+		};
+		// 页面加载时
+		onMounted(() => {
+			setFilterRoutes();
+			proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
+				setFilterRoutes();
+			});
+		});
+		// 页面卸载时
+		onUnmounted(() => {
+			proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
+		});
+		return {
+			setIsShowLogo,
+			isLayoutTransverse,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb-index {
+	height: 50px;
+	display: flex;
+	align-items: center;
+	padding-right: 15px;
+	background: var(--next-bg-topBar);
+	border-bottom: 1px solid var(--next-border-color-light);
+}
+</style>

+ 134 - 0
src/layout/navBars/breadcrumb/search.vue

@@ -0,0 +1,134 @@
+<template>
+	<div class="layout-search-dialog">
+		<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
+			<el-autocomplete
+				v-model="menuQuery"
+				:fetch-suggestions="menuSearch"
+				:placeholder="$t('message.user.searchPlaceholder')"
+				ref="layoutMenuAutocompleteRef"
+				@select="onHandleSelect"
+				@blur="onSearchBlur"
+			>
+				<template #prefix>
+					<el-icon class="el-input__icon">
+						<ele-Search />
+					</el-icon>
+				</template>
+				<template #default="{ item }">
+					<div>
+						<SvgIcon :name="item.meta.icon" class="mr5" />
+						{{ item.meta.title.indexOf('.')>0?$t(item.meta.title):item.meta.title }}
+					</div>
+				</template>
+			</el-autocomplete>
+		</el-dialog>
+	</div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
+import { useRouter } from 'vue-router';
+import { useI18n } from 'vue-i18n';
+import { useStore } from '/@/store/index';
+
+// 定义接口来定义对象的类型
+interface SearchState {
+	isShowSearch: boolean;
+	menuQuery: string;
+	tagsViewList: object[];
+}
+interface Restaurant {
+	path: string;
+	meta: {
+		title: string;
+	};
+}
+
+export default defineComponent({
+	name: 'layoutBreadcrumbSearch',
+	setup() {
+		const layoutMenuAutocompleteRef = ref();
+		const { t } = useI18n();
+		const store = useStore();
+		const router = useRouter();
+		const state = reactive<SearchState>({
+			isShowSearch: false,
+			menuQuery: '',
+			tagsViewList: [],
+		});
+		// 搜索弹窗打开
+		const openSearch = () => {
+			state.menuQuery = '';
+			state.isShowSearch = true;
+			initTageView();
+			nextTick(() => {
+				layoutMenuAutocompleteRef.value.focus();
+			});
+		};
+		// 搜索弹窗关闭
+		const closeSearch = () => {
+			state.isShowSearch = false;
+		};
+		// 菜单搜索数据过滤
+		const menuSearch = (queryString: string, cb: Function) => {
+			let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
+			cb(results);
+		};
+		// 菜单搜索过滤
+		const createFilter: any = (queryString: string) => {
+			return (restaurant: Restaurant) => {
+				return (
+					restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
+					restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
+					t(restaurant.meta.title).indexOf(queryString.toLowerCase()) > -1
+				);
+			};
+		};
+		// 初始化菜单数据
+		const initTageView = () => {
+			if (state.tagsViewList.length > 0) return false;
+			store.state.tagsViewRoutes.tagsViewRoutes.map((v: any) => {
+				if (!v.meta.isHide) state.tagsViewList.push({ ...v });
+			});
+		};
+		// 当前菜单选中时
+		const onHandleSelect = (item: any) => {
+			let { path, redirect } = item;
+			if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
+			else if (redirect) router.push(redirect);
+			else router.push(path);
+			closeSearch();
+		};
+		// input 失去焦点时
+		const onSearchBlur = () => {
+			closeSearch();
+		};
+		return {
+			layoutMenuAutocompleteRef,
+			openSearch,
+			closeSearch,
+			menuSearch,
+			onHandleSelect,
+			onSearchBlur,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-search-dialog {
+	::v-deep(.el-dialog) {
+		box-shadow: unset !important;
+		border-radius: 0 !important;
+		background: rgba(0, 0, 0, 0.5);
+	}
+	::v-deep(.el-autocomplete) {
+		width: 560px;
+		position: absolute;
+		top: 100px;
+		left: 50%;
+		transform: translateX(-50%);
+	}
+}
+</style>

+ 795 - 0
src/layout/navBars/breadcrumb/setings.vue

@@ -0,0 +1,795 @@
+<template>
+	<div class="layout-breadcrumb-seting">
+		<el-drawer
+			:title="$t('message.layout.configTitle')"
+			v-model="getThemeConfig.isDrawer"
+			direction="rtl"
+			destroy-on-close
+			size="260px"
+			@close="onDrawerClose"
+		>
+			<el-scrollbar class="layout-breadcrumb-seting-bar">
+				<!-- 全局主题 -->
+				<el-divider content-position="left">{{ $t('message.layout.oneTitle') }}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex">
+					<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker v-model="getThemeConfig.primary" size="default" @change="onColorPickerChange"> </el-color-picker>
+					</div>
+				</div>
+        <!--<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsDark') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isIsDark" size="small" @change="onAddDarkChange"></el-switch>
+					</div>
+				</div>-->
+
+				<!-- 顶栏设置 -->
+				<el-divider content-position="left">{{ $t('message.layout.twoTopTitle') }}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoTopBar') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker v-model="getThemeConfig.topBar" size="default" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoTopBarColor') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker v-model="getThemeConfig.topBarColor" size="default" @change="onBgColorPickerChange('topBarColor')"> </el-color-picker>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt10">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsTopBarColorGradual') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isTopBarColorGradual" size="small" @change="onTopBarGradualChange"></el-switch>
+					</div>
+				</div>
+
+				<!-- 菜单设置 -->
+				<el-divider content-position="left">{{ $t('message.layout.twoMenuTitle') }}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoMenuBar') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker v-model="getThemeConfig.menuBar" size="default" @change="onBgColorPickerChange('menuBar')"> </el-color-picker>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoMenuBarColor') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker v-model="getThemeConfig.menuBarColor" size="default" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt14">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsMenuBarColorGradual') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isMenuBarColorGradual" size="small" @change="onMenuBarGradualChange"></el-switch>
+					</div>
+				</div>
+
+				<!-- 分栏设置 -->
+				<el-divider content-position="left" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">{{
+					$t('message.layout.twoColumnsTitle')
+				}}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoColumnsMenuBar') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker
+							v-model="getThemeConfig.columnsMenuBar"
+							size="default"
+							@change="onBgColorPickerChange('columnsMenuBar')"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						>
+						</el-color-picker>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoColumnsMenuBarColor') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-color-picker
+							v-model="getThemeConfig.columnsMenuBarColor"
+							size="default"
+							@change="onBgColorPickerChange('columnsMenuBarColor')"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						>
+						</el-color-picker>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt14" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsColumnsMenuBarColorGradual') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch
+							v-model="getThemeConfig.isColumnsMenuBarColorGradual"
+							size="small"
+							@change="onColumnsMenuBarGradualChange"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						></el-switch>
+					</div>
+				</div>
+
+				<!-- 界面设置 -->
+				<el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsCollapse') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isCollapse" size="small" @change="onThemeConfigChange"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsUniqueOpened') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isUniqueOpened" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsFixedHeader') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isFixedHeader" size="small" @change="onIsFixedHeaderChange"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout !== 'classic' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsClassicSplitMenu') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch
+							v-model="getThemeConfig.isClassicSplitMenu"
+							:disabled="getThemeConfig.layout !== 'classic'"
+							size="small"
+							@change="onClassicSplitMenuChange"
+						>
+						</el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsLockScreen') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isLockScreen" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt11">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeLockScreenTime') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-input-number
+							v-model="getThemeConfig.lockScreenTime"
+							controls-position="right"
+							:min="1"
+							:max="9999"
+							@change="setLocalThemeConfig"
+							size="default"
+							style="width: 90px"
+						>
+						</el-input-number>
+					</div>
+				</div>
+
+				<!-- 界面显示 -->
+				<el-divider content-position="left">{{ $t('message.layout.fourTitle') }}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShowLogo') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isShowLogo" size="small" @change="onIsShowLogoChange"></el-switch>
+					</div>
+				</div>
+				<div
+					class="layout-breadcrumb-seting-bar-flex mt15"
+					:style="{ opacity: getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse' ? 0.5 : 1 }"
+				>
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumb') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch
+							v-model="getThemeConfig.isBreadcrumb"
+							:disabled="getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse'"
+							size="small"
+							@change="onIsBreadcrumbChange"
+						></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumbIcon') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isBreadcrumbIcon" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsview') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isTagsview" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsviewIcon') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isTagsviewIcon" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsCacheTagsView') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isCacheTagsView" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: isMobile ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsSortableTagsView') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch
+							v-model="getThemeConfig.isSortableTagsView"
+							:disabled="isMobile ? true : false"
+							size="small"
+							@change="onSortableTagsViewChange"
+						></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShareTagsView') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isShareTagsView" size="small" @change="onShareTagsViewChange"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isFooter" size="small" @change="setLocalThemeConfig"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsGrayscale') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isGrayscale" size="small" @change="onAddFilterChange('grayscale')"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsInvert') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isInvert" size="small" @change="onAddFilterChange('invert')"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsWartermark') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch v-model="getThemeConfig.isWartermark" size="small" @change="onWartermarkChange"></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt14">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourWartermarkText') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-input v-model="getThemeConfig.wartermarkText" size="default" style="width: 90px" @input="onWartermarkTextInput($event)"></el-input>
+					</div>
+				</div>
+
+				<!-- 其它设置 -->
+				<el-divider content-position="left">{{ $t('message.layout.fiveTitle') }}</el-divider>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveTagsStyle') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-select v-model="getThemeConfig.tagsStyle" placeholder="请选择" size="default" style="width: 90px" @change="setLocalThemeConfig">
+							<el-option label="风格1" value="tags-style-one"></el-option>
+							<el-option label="风格4" value="tags-style-four"></el-option>
+							<el-option label="风格5" value="tags-style-five"></el-option>
+						</el-select>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveAnimation') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-select v-model="getThemeConfig.animation" placeholder="请选择" size="default" style="width: 90px" @change="setLocalThemeConfig">
+							<el-option label="slide-right" value="slide-right"></el-option>
+							<el-option label="slide-left" value="slide-left"></el-option>
+							<el-option label="opacitys" value="opacitys"></el-option>
+						</el-select>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideStyle') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-select
+							v-model="getThemeConfig.columnsAsideStyle"
+							placeholder="请选择"
+							size="default"
+							style="width: 90px"
+							:disabled="getThemeConfig.layout !== 'columns' ? true : false"
+							@change="setLocalThemeConfig"
+						>
+							<el-option label="圆角" value="columns-round"></el-option>
+							<el-option label="卡片" value="columns-card"></el-option>
+						</el-select>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt15 mb27" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-select
+							v-model="getThemeConfig.columnsAsideLayout"
+							placeholder="请选择"
+							size="default"
+							style="width: 90px"
+							:disabled="getThemeConfig.layout !== 'columns' ? true : false"
+							@change="setLocalThemeConfig"
+						>
+							<el-option label="水平" value="columns-horizontal"></el-option>
+							<el-option label="垂直" value="columns-vertical"></el-option>
+						</el-select>
+					</div>
+				</div>
+
+				<!-- 布局切换 -->
+				<el-divider content-position="left">{{ $t('message.layout.sixTitle') }}</el-divider>
+				<div class="layout-drawer-content-flex">
+					<!-- defaults 布局 -->
+					<div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
+						<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'defaults' }">
+							<aside class="el-aside" style="width: 20px"></aside>
+							<section class="el-container is-vertical">
+								<header class="el-header" style="height: 10px"></header>
+								<main class="el-main"></main>
+							</section>
+						</section>
+						<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'defaults' }">
+							<div class="layout-tips-box">
+								<p class="layout-tips-txt">{{ $t('message.layout.sixDefaults') }}</p>
+							</div>
+						</div>
+					</div>
+					<!-- classic 布局 -->
+					<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
+						<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }">
+							<header class="el-header" style="height: 10px"></header>
+							<section class="el-container">
+								<aside class="el-aside" style="width: 20px"></aside>
+								<section class="el-container is-vertical">
+									<main class="el-main"></main>
+								</section>
+							</section>
+						</section>
+						<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'classic' }">
+							<div class="layout-tips-box">
+								<p class="layout-tips-txt">{{ $t('message.layout.sixClassic') }}</p>
+							</div>
+						</div>
+					</div>
+					<!-- transverse 布局 -->
+					<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
+						<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }">
+							<header class="el-header" style="height: 10px"></header>
+							<section class="el-container">
+								<section class="el-container is-vertical">
+									<main class="el-main"></main>
+								</section>
+							</section>
+						</section>
+						<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'transverse' }">
+							<div class="layout-tips-box">
+								<p class="layout-tips-txt">{{ $t('message.layout.sixTransverse') }}</p>
+							</div>
+						</div>
+					</div>
+					<!-- columns 布局 -->
+					<div class="layout-drawer-content-item" @click="onSetLayout('columns')">
+						<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'columns' }">
+							<aside class="el-aside-dark" style="width: 10px"></aside>
+							<aside class="el-aside" style="width: 20px"></aside>
+							<section class="el-container is-vertical">
+								<header class="el-header" style="height: 10px"></header>
+								<main class="el-main"></main>
+							</section>
+						</section>
+						<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'columns' }">
+							<div class="layout-tips-box">
+								<p class="layout-tips-txt">{{ $t('message.layout.sixColumns') }}</p>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="copy-config">
+					<el-alert :title="$t('message.layout.tipText')" type="warning" :closable="false"> </el-alert>
+					<el-button size="default" class="copy-config-btn" type="primary" ref="copyConfigBtnRef" @click="onCopyConfigClick">
+						<el-icon class="mr5">
+							<ele-CopyDocument />
+						</el-icon>
+						{{ $t('message.layout.copyText') }}
+					</el-button>
+					<el-button size="default" class="copy-config-btn-reset" type="info" @click="onResetConfigClick">
+						<el-icon class="mr5">
+							<ele-RefreshRight />
+						</el-icon>
+						{{ $t('message.layout.resetText') }}
+					</el-button>
+				</div>
+			</el-scrollbar>
+		</el-drawer>
+	</div>
+</template>
+
+<script lang="ts">
+import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
+import { useStore } from '/@/store/index';
+import { getLightColor, getDarkColor } from '/@/utils/theme';
+import { verifyAndSpace } from '/@/utils/toolsValidate';
+import { Local } from '/@/utils/storage';
+import Watermark from '/@/utils/wartermark';
+import commonFunction from '/@/utils/commonFunction';
+import other from '/@/utils/other';
+export default defineComponent({
+	name: 'layoutBreadcrumbSeting',
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const store = useStore();
+		const { copyText } = commonFunction();
+		const state = reactive({
+			isMobile: false,
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 1、全局主题
+		const onColorPickerChange = () => {
+			// 颜色加深
+			document.documentElement.style.setProperty('--el-color-primary-dark-2', `${getDarkColor(getThemeConfig.value.primary, 0.1)}`);
+			document.documentElement.style.setProperty('--el-color-primary', getThemeConfig.value.primary);
+			// 颜色变浅
+			for (let i = 1; i <= 9; i++) {
+				document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(getThemeConfig.value.primary, i / 10)}`);
+			}
+			setDispatchThemeConfig();
+		};
+		// 2、菜单 / 顶栏
+		const onBgColorPickerChange = (bg: string) => {
+			document.documentElement.style.setProperty(`--next-bg-${bg}`, (<any>getThemeConfig.value)[bg]);
+			onTopBarGradualChange();
+			onMenuBarGradualChange();
+			onColumnsMenuBarGradualChange();
+			setDispatchThemeConfig();
+		};
+		// 2、菜单 / 顶栏 --> 顶栏背景渐变
+		const onTopBarGradualChange = () => {
+			setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
+		};
+		// 2、菜单 / 顶栏 --> 菜单背景渐变
+		const onMenuBarGradualChange = () => {
+			setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
+		};
+		// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
+		const onColumnsMenuBarGradualChange = () => {
+			setGraduaFun('.layout-container .layout-columns-aside', getThemeConfig.value.isColumnsMenuBarColorGradual, getThemeConfig.value.columnsMenuBar);
+		};
+		// 2、菜单 / 顶栏 --> 背景渐变函数
+		const setGraduaFun = (el: string, bool: boolean, color: string) => {
+			setTimeout(() => {
+				let els = document.querySelector(el);
+				if (!els) return false;
+				document.documentElement.style.setProperty('--el-menu-bg-color', document.documentElement.style.getPropertyValue('--next-bg-menuBar'));
+				if (bool) els.setAttribute('style', `background:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)}) !important;`);
+				else els.setAttribute('style', ``);
+				setLocalThemeConfig();
+			}, 200);
+		};
+		// 3、界面设置 --> 菜单水平折叠
+		const onThemeConfigChange = () => {
+			setDispatchThemeConfig();
+		};
+		// 3、界面设置 --> 固定 Header
+		const onIsFixedHeaderChange = () => {
+			getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
+			setLocalThemeConfig();
+		};
+		// 3、界面设置 --> 经典布局分割菜单
+		const onClassicSplitMenuChange = () => {
+			getThemeConfig.value.isBreadcrumb = false;
+			setLocalThemeConfig();
+			proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
+		};
+		// 4、界面显示 --> 侧边栏 Logo
+		const onIsShowLogoChange = () => {
+			getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
+			setLocalThemeConfig();
+		};
+		// 4、界面显示 --> 面包屑 Breadcrumb
+		const onIsBreadcrumbChange = () => {
+			if (getThemeConfig.value.layout === 'classic') {
+				getThemeConfig.value.isClassicSplitMenu = false;
+			}
+			setLocalThemeConfig();
+		};
+		// 4、界面显示 --> 开启 TagsView 拖拽
+		const onSortableTagsViewChange = () => {
+			proxy.mittBus.emit('openOrCloseSortable');
+			setLocalThemeConfig();
+		};
+		// 4、界面显示 --> 开启 TagsView 共用
+		const onShareTagsViewChange = () => {
+			proxy.mittBus.emit('openShareTagsView');
+			setLocalThemeConfig();
+		};
+		// 4、界面显示 --> 灰色模式/色弱模式
+		const onAddFilterChange = (attr: string) => {
+			if (attr === 'grayscale') {
+				if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
+			} else {
+				if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
+			}
+			const cssAttr =
+				attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
+			const appEle: any = document.body;
+			appEle.setAttribute('style', `filter: ${cssAttr}`);
+			setLocalThemeConfig();
+		};
+		// 4、界面显示 --> 深色模式
+		const onAddDarkChange = () => {
+			const body = document.documentElement as HTMLElement;
+			if (getThemeConfig.value.isIsDark) body.setAttribute('data-theme', 'dark');
+			else body.setAttribute('data-theme', '');
+		};
+		// 4、界面显示 --> 开启水印
+		const onWartermarkChange = () => {
+			getThemeConfig.value.isWartermark ? Watermark.set(getThemeConfig.value.wartermarkText) : Watermark.del();
+			setLocalThemeConfig();
+		};
+		// 4、界面显示 --> 水印文案
+		const onWartermarkTextInput = (val: any) => {
+			getThemeConfig.value.wartermarkText = verifyAndSpace(val);
+			if (getThemeConfig.value.wartermarkText === '') return false;
+			if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
+			setLocalThemeConfig();
+		};
+		// 5、布局切换
+		const onSetLayout = (layout: string) => {
+			Local.set('oldLayout', layout);
+			if (getThemeConfig.value.layout === layout) return false;
+			getThemeConfig.value.layout = layout;
+			getThemeConfig.value.isDrawer = false;
+			initLayoutChangeFun();
+		};
+		// 设置布局切换函数
+		const initLayoutChangeFun = () => {
+			onBgColorPickerChange('menuBar');
+			onBgColorPickerChange('menuBarColor');
+			onBgColorPickerChange('topBar');
+			onBgColorPickerChange('topBarColor');
+			onBgColorPickerChange('columnsMenuBar');
+			onBgColorPickerChange('columnsMenuBarColor');
+		};
+		// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
+		const onDrawerClose = () => {
+			getThemeConfig.value.isFixedHeaderChange = false;
+			getThemeConfig.value.isShowLogoChange = false;
+			getThemeConfig.value.isDrawer = false;
+			setLocalThemeConfig();
+		};
+		// 布局配置弹窗打开
+		const openDrawer = () => {
+			getThemeConfig.value.isDrawer = true;
+		};
+		// 触发 store 布局配置更新
+		const setDispatchThemeConfig = () => {
+			setLocalThemeConfig();
+			setLocalThemeConfigStyle();
+		};
+		// 存储布局配置
+		const setLocalThemeConfig = () => {
+			Local.remove('themeConfig');
+			Local.set('themeConfig', getThemeConfig.value);
+		};
+		// 存储布局配置全局主题样式(html根标签)
+		const setLocalThemeConfigStyle = () => {
+			Local.set('themeConfigStyle', document.documentElement.style.cssText);
+		};
+		// 一键复制配置
+		const onCopyConfigClick = () => {
+			let copyThemeConfig = Local.get('themeConfig');
+			copyThemeConfig.isDrawer = false;
+			copyText(JSON.stringify(copyThemeConfig)).then(() => {
+				getThemeConfig.value.isDrawer = false;
+			});
+		};
+		// 一键恢复默认
+		const onResetConfigClick = () => {
+			Local.clear();
+			window.location.reload();
+		};
+		// 初始化菜单样式等
+		const initSetStyle = () => {
+			// 2、菜单 / 顶栏 --> 顶栏背景渐变
+			onTopBarGradualChange();
+			// 2、菜单 / 顶栏 --> 菜单背景渐变
+			onMenuBarGradualChange();
+			// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
+			onColumnsMenuBarGradualChange();
+		};
+		onMounted(() => {
+			nextTick(() => {
+				// 判断当前布局是否不相同,不相同则初始化当前布局的样式,防止监听窗口大小改变时,布局配置logo、菜单背景等部分布局失效问题
+				if (!Local.get('frequency')) initLayoutChangeFun();
+				Local.set('frequency', 1);
+				// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
+				proxy.mittBus.on('layoutMobileResize', (res: any) => {
+					getThemeConfig.value.layout = res.layout;
+					getThemeConfig.value.isDrawer = false;
+					initLayoutChangeFun();
+					state.isMobile = other.isMobile();
+				});
+				setTimeout(() => {
+					// 灰色模式
+					if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
+					// 色弱模式
+					if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
+					// 深色模式
+					if (getThemeConfig.value.isIsDark) onAddDarkChange();
+					// 开启水印
+					onWartermarkChange();
+					// 语言国际化
+					if (Local.get('themeConfig')) proxy.$i18n.locale = Local.get('themeConfig').globalI18n;
+					// 初始化菜单样式等
+					initSetStyle();
+				}, 100);
+			});
+		});
+		onUnmounted(() => {
+			proxy.mittBus.off('layoutMobileResize');
+		});
+		return {
+			openDrawer,
+			onColorPickerChange,
+			onBgColorPickerChange,
+			onTopBarGradualChange,
+			onMenuBarGradualChange,
+			onColumnsMenuBarGradualChange,
+			onThemeConfigChange,
+			onIsFixedHeaderChange,
+			onIsShowLogoChange,
+			getThemeConfig,
+			onDrawerClose,
+			onAddFilterChange,
+			onAddDarkChange,
+			onWartermarkChange,
+			onWartermarkTextInput,
+			onSetLayout,
+			setLocalThemeConfig,
+			onClassicSplitMenuChange,
+			onIsBreadcrumbChange,
+			onSortableTagsViewChange,
+			onShareTagsViewChange,
+			onCopyConfigClick,
+			onResetConfigClick,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-breadcrumb-seting-bar {
+	height: calc(100vh - 50px);
+	padding: 0 15px;
+	::v-deep(.el-scrollbar__view) {
+		overflow-x: hidden !important;
+	}
+	.layout-breadcrumb-seting-bar-flex {
+		display: flex;
+		align-items: center;
+		margin-bottom: 5px;
+		&-label {
+			flex: 1;
+			color: var(--el-text-color-primary);
+		}
+	}
+	.layout-drawer-content-flex {
+		overflow: hidden;
+		display: flex;
+		flex-wrap: wrap;
+		align-content: flex-start;
+		margin: 0 -5px;
+		.layout-drawer-content-item {
+			width: 50%;
+			height: 70px;
+			cursor: pointer;
+			border: 1px solid transparent;
+			position: relative;
+			padding: 5px;
+			.el-container {
+				height: 100%;
+				.el-aside-dark {
+					background-color: var(--next-color-seting-header);
+				}
+				.el-aside {
+					background-color: var(--next-color-seting-aside);
+				}
+				.el-header {
+					background-color: var(--next-color-seting-header);
+				}
+				.el-main {
+					background-color: var(--next-color-seting-main);
+				}
+			}
+			.el-circular {
+				border-radius: 2px;
+				overflow: hidden;
+				border: 1px solid transparent;
+				transition: all 0.3s ease-in-out;
+			}
+			.drawer-layout-active {
+				border: 1px solid;
+				border-color: var(--el-color-primary);
+			}
+			.layout-tips-warp,
+			.layout-tips-warp-active {
+				transition: all 0.3s ease-in-out;
+				position: absolute;
+				left: 50%;
+				top: 50%;
+				transform: translate(-50%, -50%);
+				border: 1px solid;
+				border-color: var(--el-color-primary-light-4);
+				border-radius: 100%;
+				padding: 4px;
+				.layout-tips-box {
+					transition: inherit;
+					width: 30px;
+					height: 30px;
+					z-index: 9;
+					border: 1px solid;
+					border-color: var(--el-color-primary-light-4);
+					border-radius: 100%;
+					.layout-tips-txt {
+						transition: inherit;
+						position: relative;
+						top: 5px;
+						font-size: 12px;
+						line-height: 1;
+						letter-spacing: 2px;
+						white-space: nowrap;
+						color: var(--el-color-primary-light-4);
+						text-align: center;
+						transform: rotate(30deg);
+						left: -1px;
+						background-color: var(--next-color-seting-main);
+						width: 32px;
+						height: 17px;
+						line-height: 17px;
+					}
+				}
+			}
+			.layout-tips-warp-active {
+				border: 1px solid;
+				border-color: var(--el-color-primary);
+				.layout-tips-box {
+					border: 1px solid;
+					border-color: var(--el-color-primary);
+					.layout-tips-txt {
+						color: var(--el-color-primary) !important;
+						background-color: var(--next-color-seting-main) !important;
+					}
+				}
+			}
+			&:hover {
+				.el-circular {
+					transition: all 0.3s ease-in-out;
+					border: 1px solid;
+					border-color: var(--el-color-primary);
+				}
+				.layout-tips-warp {
+					transition: all 0.3s ease-in-out;
+					border-color: var(--el-color-primary);
+					.layout-tips-box {
+						transition: inherit;
+						border-color: var(--el-color-primary);
+						.layout-tips-txt {
+							transition: inherit;
+							color: var(--el-color-primary) !important;
+							background-color: var(--next-color-seting-main) !important;
+						}
+					}
+				}
+			}
+		}
+	}
+	.copy-config {
+		margin: 10px 0;
+		.copy-config-btn {
+			width: 100%;
+			margin-top: 15px;
+		}
+		.copy-config-btn-reset {
+			width: 100%;
+			margin: 10px 0 0;
+		}
+	}
+}
+</style>

+ 302 - 0
src/layout/navBars/breadcrumb/user.vue

@@ -0,0 +1,302 @@
+<template>
+	<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
+		<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
+			<div class="layout-navbars-breadcrumb-user-icon">
+				<i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>
+			</div>
+			<template #dropdown>
+				<el-dropdown-menu>
+					<el-dropdown-item command="large" :disabled="disabledSize === 'large'">{{ $t('message.user.dropdownLarge') }}</el-dropdown-item>
+					<el-dropdown-item command="default" :disabled="disabledSize === 'default'">{{ $t('message.user.dropdownDefault') }}</el-dropdown-item>
+					<el-dropdown-item command="small" :disabled="disabledSize === 'small'">{{ $t('message.user.dropdownSmall') }}</el-dropdown-item>
+				</el-dropdown-menu>
+			</template>
+		</el-dropdown>
+		<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
+			<div class="layout-navbars-breadcrumb-user-icon">
+				<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('message.user.title1')"></i>
+			</div>
+			<template #dropdown>
+				<el-dropdown-menu>
+					<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
+					<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
+					<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
+				</el-dropdown-menu>
+			</template>
+		</el-dropdown>
+		<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
+			<el-icon :title="$t('message.user.title2')">
+				<ele-Search />
+			</el-icon>
+		</div>
+		<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
+			<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
+		</div>
+		<div class="layout-navbars-breadcrumb-user-icon">
+			<el-popover placement="bottom" trigger="click" :width="300">
+				<template #reference>
+					<el-badge :is-dot="true">
+						<el-icon :title="$t('message.user.title4')">
+							<ele-Bell />
+						</el-icon>
+					</el-badge>
+				</template>
+				<UserNews />
+			</el-popover>
+		</div>
+		<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
+			<i
+				class="iconfont"
+				:title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
+				:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
+			></i>
+		</div>
+		<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
+			<span class="layout-navbars-breadcrumb-user-link">
+				<img :src="getUserInfos.avatar" class="layout-navbars-breadcrumb-user-link-photo mr5" />
+				{{ getUserInfos.userName === '' ? 'common' : getUserInfos.userName }}
+				<el-icon class="el-icon--right">
+					<ele-ArrowDown />
+				</el-icon>
+			</span>
+			<template #dropdown>
+				<el-dropdown-menu>
+					<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
+					<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
+					<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
+					<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
+					<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item>
+					<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
+				</el-dropdown-menu>
+			</template>
+		</el-dropdown>
+		<Search ref="searchRef" />
+	</div>
+</template>
+
+<script lang="ts">
+import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import screenfull from 'screenfull';
+import { useI18n } from 'vue-i18n';
+import { resetRoute } from '/@/router/index';
+import { useStore } from '/@/store/index';
+import other from '/@/utils/other';
+import { Session, Local } from '/@/utils/storage';
+import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
+import Search from '/@/layout/navBars/breadcrumb/search.vue';
+export default defineComponent({
+	name: 'layoutBreadcrumbUser',
+	components: { UserNews, Search },
+	setup() {
+		const { t } = useI18n();
+		const { proxy } = <any>getCurrentInstance();
+		const router = useRouter();
+		const store = useStore();
+		const searchRef = ref();
+		const state = reactive({
+			isScreenfull: false,
+			disabledI18n: 'zh-cn',
+			disabledSize: 'large',
+		});
+		// 获取用户信息 vuex
+		const getUserInfos = computed(() => {
+			return <any>store.state.userInfos.userInfos;
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 设置分割样式
+		const layoutUserFlexNum = computed(() => {
+			let num: string | number = '';
+			const { layout, isClassicSplitMenu } = getThemeConfig.value;
+			const layoutArr: string[] = ['defaults', 'columns'];
+			if (layoutArr.includes(layout) || (layout === 'classic' && !isClassicSplitMenu)) num = '1';
+			else num = '';
+			return num;
+		});
+		// 全屏点击时
+		const onScreenfullClick = () => {
+			if (!screenfull.isEnabled) {
+				ElMessage.warning('暂不不支持全屏');
+				return false;
+			}
+			screenfull.toggle();
+			screenfull.on('change', () => {
+				if (screenfull.isFullscreen) state.isScreenfull = true;
+				else state.isScreenfull = false;
+			});
+		};
+		// 布局配置 icon 点击时
+		const onLayoutSetingClick = () => {
+			proxy.mittBus.emit('openSetingsDrawer');
+		};
+		// 下拉菜单点击时
+		const onHandleCommandClick = (path: string) => {
+			if (path === 'logOut') {
+				ElMessageBox({
+					closeOnClickModal: false,
+					closeOnPressEscape: false,
+					title: t('message.user.logOutTitle'),
+					message: t('message.user.logOutMessage'),
+					showCancelButton: true,
+					confirmButtonText: t('message.user.logOutConfirm'),
+					cancelButtonText: t('message.user.logOutCancel'),
+					buttonSize: 'default',
+					beforeClose: (action, instance, done) => {
+						if (action === 'confirm') {
+							instance.confirmButtonLoading = true;
+							instance.confirmButtonText = t('message.user.logOutExit');
+							setTimeout(() => {
+								done();
+								setTimeout(() => {
+									instance.confirmButtonLoading = false;
+								}, 300);
+							}, 700);
+						} else {
+							done();
+						}
+					},
+				})
+					.then(async () => {
+						Session.clear(); // 清除缓存/token等
+						await resetRoute(); // 删除/重置路由
+						ElMessage.success(t('message.user.logOutSuccess'));
+						setTimeout(() => {
+							window.location.href = ''; // 去登录页
+						}, 500);
+					})
+					.catch(() => {});
+			} else if (path === 'wareHouse') {
+				window.open('https://github.com/tiger1103/gfast');
+			} else {
+				router.push(path);
+			}
+		};
+		// 菜单搜索点击
+		const onSearchClick = () => {
+			searchRef.value.openSearch();
+		};
+		// 组件大小改变
+		const onComponentSizeChange = (size: string) => {
+			Local.remove('themeConfig');
+			getThemeConfig.value.globalComponentSize = size;
+			Local.set('themeConfig', getThemeConfig.value);
+			initComponentSize();
+			window.location.reload();
+		};
+		// 语言切换
+		const onLanguageChange = (lang: string) => {
+			Local.remove('themeConfig');
+			getThemeConfig.value.globalI18n = lang;
+			Local.set('themeConfig', getThemeConfig.value);
+			proxy.$i18n.locale = lang;
+			initI18n();
+			other.useTitle();
+		};
+		// 设置 element plus 组件的国际化
+		const setI18nConfig = (locale: string) => {
+			proxy.mittBus.emit('getI18nConfig', proxy.$i18n.messages[locale]);
+		};
+		// 初始化言语国际化
+		const initI18n = () => {
+			switch (Local.get('themeConfig').globalI18n) {
+				case 'zh-cn':
+					state.disabledI18n = 'zh-cn';
+					setI18nConfig('zh-cn');
+					break;
+				case 'en':
+					state.disabledI18n = 'en';
+					setI18nConfig('en');
+					break;
+				case 'zh-tw':
+					state.disabledI18n = 'zh-tw';
+					setI18nConfig('zh-tw');
+					break;
+			}
+		};
+		// 初始化全局组件大小
+		const initComponentSize = () => {
+			switch (Local.get('themeConfig').globalComponentSize) {
+				case 'large':
+					state.disabledSize = 'large';
+					break;
+				case 'default':
+					state.disabledSize = 'default';
+					break;
+				case 'small':
+					state.disabledSize = 'small';
+					break;
+			}
+		};
+		// 页面加载时
+		onMounted(() => {
+			if (Local.get('themeConfig')) {
+				initI18n();
+				initComponentSize();
+			}
+		});
+		return {
+			getUserInfos,
+			onLayoutSetingClick,
+			onHandleCommandClick,
+			onScreenfullClick,
+			onSearchClick,
+			onComponentSizeChange,
+			onLanguageChange,
+			searchRef,
+			layoutUserFlexNum,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb-user {
+	display: flex;
+	align-items: center;
+	justify-content: flex-end;
+	&-link {
+		height: 100%;
+		display: flex;
+		align-items: center;
+		white-space: nowrap;
+		&-photo {
+			width: 25px;
+			height: 25px;
+			border-radius: 100%;
+		}
+	}
+	&-icon {
+		padding: 0 10px;
+		cursor: pointer;
+		color: var(--next-bg-topBarColor);
+		height: 50px;
+		line-height: 50px;
+		display: flex;
+		align-items: center;
+		&:hover {
+			background: var(--next-color-user-hover);
+			i {
+				display: inline-block;
+				animation: logoAnimation 0.3s ease-in-out;
+			}
+		}
+	}
+	::v-deep(.el-dropdown) {
+		color: var(--next-bg-topBarColor);
+	}
+	::v-deep(.el-badge) {
+		height: 40px;
+		line-height: 40px;
+		display: flex;
+		align-items: center;
+	}
+	::v-deep(.el-badge__content.is-fixed) {
+		top: 12px;
+	}
+}
+</style>

+ 114 - 0
src/layout/navBars/breadcrumb/userNews.vue

@@ -0,0 +1,114 @@
+<template>
+	<div class="layout-navbars-breadcrumb-user-news">
+		<div class="head-box">
+			<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
+			<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>
+		</div>
+		<div class="content-box">
+			<template v-if="newsList.length > 0">
+				<div class="content-box-item" v-for="(v, k) in newsList" :key="k">
+					<div>{{ v.label }}</div>
+					<div class="content-box-msg">
+						{{ v.value }}
+					</div>
+					<div class="content-box-time">{{ v.time }}</div>
+				</div>
+			</template>
+			<el-empty :description="$t('message.user.newDesc')" v-else></el-empty>
+		</div>
+		<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">{{ $t('message.user.newGo') }}</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs, defineComponent } from 'vue';
+export default defineComponent({
+	name: 'layoutBreadcrumbUserNews',
+	setup() {
+		const state = reactive({
+			newsList: [
+				{
+					label: '关于版本发布的通知',
+					value: 'GFast基于全新Go Frame 2.0+Vue3+Element Plus开发的全栈前后端分离的管理系统,正式发布时间:2022年04月21日!',
+					time: '2022-04-21',
+				},
+				{
+					label: '关于学习交流的通知',
+					value: 'QQ群号码 865697297,欢迎小伙伴入群学习交流探讨!',
+					time: '2022-04-21',
+				},
+			],
+		});
+		// 全部已读点击
+		const onAllReadClick = () => {
+			state.newsList = [];
+		};
+		// 前往通知中心点击
+		const onGoToGiteeClick = () => {
+			window.open('https://github.com/tiger1103/gfast');
+		};
+		return {
+			onAllReadClick,
+			onGoToGiteeClick,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb-user-news {
+	.head-box {
+		display: flex;
+		border-bottom: 1px solid var(--el-border-color-lighter);
+		box-sizing: border-box;
+		color: var(--el-text-color-primary);
+		justify-content: space-between;
+		height: 35px;
+		align-items: center;
+		.head-box-btn {
+			color: var(--el-color-primary);
+			font-size: 13px;
+			cursor: pointer;
+			opacity: 0.8;
+			&:hover {
+				opacity: 1;
+			}
+		}
+	}
+	.content-box {
+		font-size: 13px;
+		.content-box-item {
+			padding-top: 12px;
+			&:last-of-type {
+				padding-bottom: 12px;
+			}
+			.content-box-msg {
+				color: var(--el-text-color-secondary);
+				margin-top: 5px;
+				margin-bottom: 5px;
+			}
+			.content-box-time {
+				color: var(--el-text-color-secondary);
+			}
+		}
+	}
+	.foot-box {
+		height: 35px;
+		color: var(--el-color-primary);
+		font-size: 13px;
+		cursor: pointer;
+		opacity: 0.8;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		border-top: 1px solid var(--el-border-color-lighter);
+		&:hover {
+			opacity: 1;
+		}
+	}
+	::v-deep(.el-empty__description p) {
+		font-size: 13px;
+	}
+}
+</style>

+ 37 - 0
src/layout/navBars/index.vue

@@ -0,0 +1,37 @@
+<template>
+	<div class="layout-navbars-container">
+		<BreadcrumbIndex />
+		<TagsView v-if="setShowTagsView" />
+	</div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import { useStore } from '/@/store/index';
+import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
+import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
+export default defineComponent({
+	name: 'layoutNavBars',
+	components: { BreadcrumbIndex, TagsView },
+	setup() {
+		const store = useStore();
+		// 是否显示 tagsView
+		const setShowTagsView = computed(() => {
+			let { layout, isTagsview } = store.state.themeConfig.themeConfig;
+			return layout !== 'classic' && isTagsview;
+		});
+		return {
+			setShowTagsView,
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-container {
+	display: flex;
+	flex-direction: column;
+	width: 100%;
+	height: 100%;
+}
+</style>

+ 137 - 0
src/layout/navBars/tagsView/contextmenu.vue

@@ -0,0 +1,137 @@
+<template>
+	<transition name="el-zoom-in-center">
+		<div
+			aria-hidden="true"
+			class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
+			role="tooltip"
+			data-popper-placement="bottom"
+			:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
+			:key="Math.random()"
+			v-show="isShow"
+		>
+			<ul class="el-dropdown-menu">
+				<template v-for="(v, k) in dropdownList">
+					<li
+						class="el-dropdown-menu__item"
+						aria-disabled="false"
+						tabindex="-1"
+						:key="k"
+						v-if="!v.affix"
+						@click="onCurrentContextmenuClick(v.contextMenuClickId)"
+					>
+						<SvgIcon :name="v.icon" />
+						<span>{{ $t(v.txt) }}</span>
+					</li>
+				</template>
+			</ul>
+			<div class="el-popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
+		</div>
+	</transition>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted, watch } from 'vue';
+export default defineComponent({
+	name: 'layoutTagsViewContextmenu',
+	props: {
+		dropdown: {
+			type: Object,
+			default: () => {
+				return {
+					x: 0,
+					y: 0,
+				};
+			},
+		},
+	},
+	setup(props, { emit }) {
+		const state = reactive({
+			isShow: false,
+			dropdownList: [
+				{ contextMenuClickId: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'ele-RefreshRight' },
+				{ contextMenuClickId: 1, txt: 'message.tagsView.close', affix: false, icon: 'ele-Close' },
+				{ contextMenuClickId: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'ele-CircleClose' },
+				{ contextMenuClickId: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'ele-FolderDelete' },
+				{
+					contextMenuClickId: 4,
+					txt: 'message.tagsView.fullscreen',
+					affix: false,
+					icon: 'iconfont icon-fullscreen',
+				},
+			],
+			item: {},
+			arrowLeft: 10,
+		});
+		// 父级传过来的坐标 x,y 值
+		const dropdowns = computed(() => {
+			// 117 为 `Dropdown 下拉菜单` 的宽度
+			if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
+				return {
+					x: document.documentElement.clientWidth - 117 - 5,
+					y: props.dropdown.y,
+				};
+			} else {
+				return props.dropdown;
+			}
+		});
+		// 当前项菜单点击
+		const onCurrentContextmenuClick = (contextMenuClickId: number) => {
+			emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
+		};
+		// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
+		const openContextmenu = (item: any) => {
+			state.item = item;
+			item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
+			closeContextmenu();
+			setTimeout(() => {
+				state.isShow = true;
+			}, 10);
+		};
+		// 关闭右键菜单
+		const closeContextmenu = () => {
+			state.isShow = false;
+		};
+		// 监听页面监听进行右键菜单的关闭
+		onMounted(() => {
+			document.body.addEventListener('click', closeContextmenu);
+		});
+		// 页面卸载时,移除右键菜单监听事件
+		onUnmounted(() => {
+			document.body.removeEventListener('click', closeContextmenu);
+		});
+		// 监听下拉菜单位置
+		watch(
+			() => props.dropdown,
+			({ x }) => {
+				if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
+				else state.arrowLeft = 10;
+			},
+			{
+				deep: true,
+			}
+		);
+		return {
+			dropdowns,
+			openContextmenu,
+			closeContextmenu,
+			onCurrentContextmenuClick,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.custom-contextmenu {
+	transform-origin: center top;
+	z-index: 2190;
+	position: fixed;
+	.el-dropdown-menu__item {
+		font-size: 12px !important;
+		white-space: nowrap;
+		i {
+			font-size: 12px !important;
+		}
+	}
+}
+</style>

+ 681 - 0
src/layout/navBars/tagsView/tagsView.vue

@@ -0,0 +1,681 @@
+<template>
+	<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
+		<el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll">
+			<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
+				<li
+					v-for="(v, k) in tagsViewList"
+					:key="k"
+					class="layout-navbars-tagsview-ul-li"
+					:data-url="v.url"
+					:class="{ 'is-active': isActive(v) }"
+					@contextmenu.prevent="onContextmenu(v, $event)"
+					@click="onTagsClick(v, k)"
+					:ref="
+						(el) => {
+							if (el) tagsRefs[k] = el;
+						}
+					"
+				>
+					<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont" v-if="isActive(v)"></i>
+					<SvgIcon :name="v.meta.icon" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon" class="pr5" />
+					<span>{{ v.meta.title.indexOf('.')>0?$t(v.meta.title):v.meta.title }}</span>
+					<template v-if="isActive(v)">
+						<SvgIcon
+							name="ele-RefreshRight"
+							class="ml5 layout-navbars-tagsview-ul-li-refresh"
+							@click.stop="refreshCurrentTagsView($route.fullPath)"
+						/>
+						<SvgIcon
+							name="ele-Close"
+							class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
+							v-if="!v.meta.isAffix"
+							@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
+						/>
+					</template>
+					<SvgIcon
+						name="ele-Close"
+						class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
+						v-if="!v.meta.isAffix"
+						@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
+					/>
+				</li>
+			</ul>
+		</el-scrollbar>
+		<Contextmenu :dropdown="dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
+	</div>
+</template>
+
+<script lang="ts">
+import {
+	toRefs,
+	reactive,
+	onMounted,
+	computed,
+	ref,
+	nextTick,
+	onBeforeUpdate,
+	onBeforeMount,
+	onUnmounted,
+	getCurrentInstance,
+	watch,
+	defineComponent,
+} from 'vue';
+import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
+import Sortable from 'sortablejs';
+import { ElMessage } from 'element-plus';
+import { useStore } from '/@/store/index';
+import { Session } from '/@/utils/storage';
+import { isObjectValueEqual } from '/@/utils/arrayOperation';
+import other from '/@/utils/other';
+import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
+
+// 定义接口来定义对象的类型
+interface TagsViewState {
+	routeActive: string;
+	routePath: string | unknown;
+	dropdown: {
+		x: string | number;
+		y: string | number;
+	};
+	tagsRefsIndex: number;
+	tagsViewList: any[];
+	sortable: any;
+	tagsViewRoutesList: any[];
+}
+interface RouteParams {
+	path: string;
+	url: string;
+}
+interface CurrentContextmenu {
+	meta: {
+		isDynamic: boolean;
+	};
+	params: any;
+	query: any;
+	path: string;
+	contextMenuClickId: string | number;
+}
+
+export default defineComponent({
+	name: 'layoutTagsView',
+	components: { Contextmenu },
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const tagsRefs = ref<any[]>([]);
+		const scrollbarRef = ref();
+		const contextmenuRef = ref();
+		const tagsUlRef = ref();
+		const store = useStore();
+		const route = useRoute();
+		const router = useRouter();
+		const state = reactive<TagsViewState>({
+			routeActive: '',
+			routePath: route.path,
+			dropdown: { x: '', y: '' },
+			tagsRefsIndex: 0,
+			tagsViewList: [],
+			sortable: '',
+			tagsViewRoutesList: [],
+		});
+		// 动态设置 tagsView 风格样式
+		const setTagsStyle = computed(() => {
+			return store.state.themeConfig.themeConfig.tagsStyle;
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 设置 tagsView 高亮
+		const isActive = (v: RouteParams) => {
+			if (getThemeConfig.value.isShareTagsView) {
+				return v.path === state.routePath;
+			} else {
+				return v.url ? v.url === state.routeActive : v.path === state.routeActive;
+			}
+		};
+		// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
+		const addBrowserSetSession = (tagsViewList: Array<object>) => {
+			Session.set('tagsViewList', tagsViewList);
+		};
+		// 获取 vuex 中的 tagsViewRoutes 列表
+		const getTagsViewRoutes = async () => {
+			state.routeActive = await setTagsViewHighlight(route);
+			state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
+			state.tagsViewList = [];
+			state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
+			initTagsView();
+		};
+		// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
+		const initTagsView = async () => {
+			if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
+				state.tagsViewList = await Session.get('tagsViewList');
+			} else {
+				await state.tagsViewRoutesList.map((v: any) => {
+					if (v.meta.isAffix && !v.meta.isHide) {
+						v.url = setTagsViewHighlight(v);
+						state.tagsViewList.push({ ...v });
+					}
+				});
+				await addTagsView(route.path, route);
+			}
+			// 初始化当前元素(li)的下标
+			getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
+		};
+		// 处理可开启多标签详情,单标签详情(动态路由(xxx/:id/:name"),普通路由处理)
+		const solveAddTagsView = async (path: string, to?: any) => {
+			let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
+			let current = state.tagsViewList.filter(
+				(v: any) =>
+					v.path === isDynamicPath &&
+					isObjectValueEqual(
+						to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
+						to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
+					)
+			);
+			if (current.length <= 0) {
+				// 防止:Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
+				let findItem = state.tagsViewRoutesList.find((v: any) => v.path === isDynamicPath);
+				if (findItem.meta.isAffix) return false;
+				if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
+				to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
+				findItem.url = setTagsViewHighlight(findItem);
+				state.tagsViewList.push({ ...findItem });
+				addBrowserSetSession(state.tagsViewList);
+			}
+		};
+		// 处理单标签时,第二次的值未覆盖第一次的 tagsViewList 值(Session Storage)
+		const singleAddTagsView = (path: string, to?: any) => {
+			let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
+			state.tagsViewList.forEach((v) => {
+				if (
+					v.path === isDynamicPath &&
+					!isObjectValueEqual(
+						to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
+						to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
+					)
+				) {
+					to.meta.isDynamic ? (v.params = to.params) : (v.query = to.query);
+					v.url = setTagsViewHighlight(v);
+					addBrowserSetSession(state.tagsViewList);
+				}
+			});
+		};
+		// 1、添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中(可开启多标签详情,单标签详情)
+		const addTagsView = (path: string, to?: any) => {
+			// 防止拿取不到路由信息
+			nextTick(async () => {
+				// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
+				let item: any = '';
+				if (to && to.meta.isDynamic) {
+					// 动态路由(xxx/:id/:name"):参数不同,开启多个 tagsview
+					if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
+					else await singleAddTagsView(path, to);
+					if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) return false;
+					item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath);
+				} else {
+					// 普通路由:参数不同,开启多个 tagsview
+					if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
+					else await singleAddTagsView(path, to);
+					if (state.tagsViewList.some((v: any) => v.path === path)) return false;
+					item = state.tagsViewRoutesList.find((v: any) => v.path === path);
+				}
+				if (item.meta.isLink && !item.meta.isIframe) return false;
+				if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
+				else item.query = to?.query ? to?.query : route.query;
+				item.url = setTagsViewHighlight(item);
+				await state.tagsViewList.push({ ...item });
+				await addBrowserSetSession(state.tagsViewList);
+			});
+		};
+		// 2、刷新当前 tagsView:
+		const refreshCurrentTagsView = (fullPath: string) => {
+			proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
+		};
+		// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
+		const closeCurrentTagsView = (path: string) => {
+			state.tagsViewList.map((v: any, k: number, arr: any) => {
+				if (!v.meta.isAffix) {
+					if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
+						state.tagsViewList.splice(k, 1);
+						setTimeout(() => {
+							if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
+								// 最后一个且高亮时
+								if (arr[arr.length - 1].meta.isDynamic) {
+									// 动态路由(xxx/:id/:name")
+									if (k !== arr.length) router.push({ name: arr[k].name, params: arr[k].params });
+									else router.push({ name: arr[arr.length - 1].name, params: arr[arr.length - 1].params });
+								} else {
+									// 普通路由
+									if (k !== arr.length) router.push({ path: arr[k].path, query: arr[k].query });
+									else router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
+								}
+							} else {
+								// 非最后一个且高亮时,跳转到下一个
+								if (state.tagsViewList.length !== k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
+									if (arr[k].meta.isDynamic) {
+										// 动态路由(xxx/:id/:name")
+										router.push({ name: arr[k].name, params: arr[k].params });
+									} else {
+										// 普通路由
+										router.push({ path: arr[k].path, query: arr[k].query });
+									}
+								}
+							}
+						}, 0);
+					}
+				}
+			});
+			addBrowserSetSession(state.tagsViewList);
+		};
+		// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
+		const closeOtherTagsView = (path: string) => {
+			state.tagsViewList = [];
+			state.tagsViewRoutesList.map((v: any) => {
+				if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
+			});
+			addTagsView(path, route);
+		};
+		// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
+		const closeAllTagsView = () => {
+			state.tagsViewList = [];
+			state.tagsViewRoutesList.map((v: any) => {
+				if (v.meta.isAffix && !v.meta.isHide) {
+					state.tagsViewList.push({ ...v });
+					router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
+				}
+			});
+			addBrowserSetSession(state.tagsViewList);
+		};
+		// 6、开启当前页面全屏
+		const openCurrenFullscreen = async (path: string) => {
+			const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
+			if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
+			else await router.push({ name: item.name, query: item.query });
+			store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
+		};
+		// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
+		// 防止 tagsView 非当前页演示时,操作异常
+		const getCurrentRouteItem = (path: string, cParams: any) => {
+			const itemRoute = Session.get('tagsViewList') ? Session.get('tagsViewList') : state.tagsViewList;
+			return itemRoute.find((v: any) => {
+				if (
+					v.path === path &&
+					isObjectValueEqual(
+						v.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
+						cParams && Object.keys(cParams ? cParams : {}).length > 0 ? cParams : null
+					)
+				) {
+					return v;
+				} else if (v.path === path && Object.keys(cParams ? cParams : {}).length <= 0) {
+					return v;
+				}
+			});
+		};
+		// 当前项右键菜单点击
+		const onCurrentContextmenuClick = async (item: CurrentContextmenu) => {
+			const cParams = item.meta.isDynamic ? item.params : item.query;
+			if (!getCurrentRouteItem(item.path, cParams)) return ElMessage({ type: 'warning', message: '请正确输入路径及完整参数(query、params)' });
+			const { path, name, params, query, meta, url } = getCurrentRouteItem(item.path, cParams);
+			switch (item.contextMenuClickId) {
+				case 0:
+					// 刷新当前
+					if (meta.isDynamic) await router.push({ name, params });
+					else await router.push({ path, query });
+					refreshCurrentTagsView(route.fullPath);
+					break;
+				case 1:
+					// 关闭当前
+					closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
+					break;
+				case 2:
+					// 关闭其它
+					if (meta.isDynamic) await router.push({ name, params });
+					else await router.push({ path, query });
+					closeOtherTagsView(path);
+					break;
+				case 3:
+					// 关闭全部
+					closeAllTagsView();
+					break;
+				case 4:
+					// 开启当前页面全屏
+					openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
+					break;
+			}
+		};
+		// 右键点击时:传 x,y 坐标值到子组件中(props)
+		const onContextmenu = (v: any, e: any) => {
+			const { clientX, clientY } = e;
+			state.dropdown.x = clientX;
+			state.dropdown.y = clientY;
+			contextmenuRef.value.openContextmenu(v);
+		};
+		// 当前的 tagsView 项点击时
+		const onTagsClick = (v: any, k: number) => {
+			state.tagsRefsIndex = k;
+			router.push(v);
+		};
+		// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
+		const setTagsViewHighlight = (v: any) => {
+			let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
+			if (!params || Object.keys(params).length <= 0) return v.path;
+			let path = '';
+			for (let i in params) {
+				path += params[i];
+			}
+			// 判断是否是动态路由(xxx/:id/:name")
+			return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
+		};
+		// 更新滚动条显示
+		const updateScrollbar = () => {
+			proxy.$refs.scrollbarRef.update();
+		};
+		// 鼠标滚轮滚动
+		const onHandleScroll = (e: any) => {
+			proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
+		};
+		// tagsView 横向滚动
+		const tagsViewmoveToCurrentTag = () => {
+			nextTick(() => {
+				if (tagsRefs.value.length <= 0) return false;
+				// 当前 li 元素
+				let liDom = tagsRefs.value[state.tagsRefsIndex];
+				// 当前 li 元素下标
+				let liIndex = state.tagsRefsIndex;
+				// 当前 ul 下 li 元素总长度
+				let liLength = tagsRefs.value.length;
+				// 最前 li
+				let liFirst: any = tagsRefs.value[0];
+				// 最后 li
+				let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
+				// 当前滚动条的值
+				let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
+				// 当前滚动条滚动宽度
+				let scrollS = scrollRefs.scrollWidth;
+				// 当前滚动条偏移宽度
+				let offsetW = scrollRefs.offsetWidth;
+				// 当前滚动条偏移距离
+				let scrollL = scrollRefs.scrollLeft;
+				// 上一个 tags li dom
+				let liPrevTag: any = tagsRefs.value[state.tagsRefsIndex - 1];
+				// 下一个 tags li dom
+				let liNextTag: any = tagsRefs.value[state.tagsRefsIndex + 1];
+				// 上一个 tags li dom 的偏移距离
+				let beforePrevL: any = '';
+				// 下一个 tags li dom 的偏移距离
+				let afterNextL: any = '';
+				if (liDom === liFirst) {
+					// 头部
+					scrollRefs.scrollLeft = 0;
+				} else if (liDom === liLast) {
+					// 尾部
+					scrollRefs.scrollLeft = scrollS - offsetW;
+				} else {
+					// 非头/尾部
+					if (liIndex === 0) beforePrevL = liFirst.offsetLeft - 5;
+					else beforePrevL = liPrevTag?.offsetLeft - 5;
+					if (liIndex === liLength) afterNextL = liLast.offsetLeft + liLast.offsetWidth + 5;
+					else afterNextL = liNextTag.offsetLeft + liNextTag.offsetWidth + 5;
+					if (afterNextL > scrollL + offsetW) {
+						scrollRefs.scrollLeft = afterNextL - offsetW;
+					} else if (beforePrevL < scrollL) {
+						scrollRefs.scrollLeft = beforePrevL;
+					}
+				}
+				// 更新滚动条,防止不出现
+				updateScrollbar();
+			});
+		};
+		// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
+		const getTagsRefsIndex = (path: string | unknown) => {
+			nextTick(async () => {
+				// await 使用该写法,防止拿取不到 tagsViewList 列表数据不完整
+				let tagsViewList = await state.tagsViewList;
+				state.tagsRefsIndex = tagsViewList.findIndex((v: any) => {
+					if (getThemeConfig.value.isShareTagsView) {
+						return v.path === path;
+					} else {
+						return v.url === path;
+					}
+				});
+				// 添加初始化横向滚动条移动到对应位置
+				tagsViewmoveToCurrentTag();
+			});
+		};
+		// 设置 tagsView 可以进行拖拽
+		const initSortable = async () => {
+			const el = <HTMLElement>document.querySelector('.layout-navbars-tagsview-ul');
+			if (!el) return false;
+			state.sortable.el && state.sortable.destroy();
+			state.sortable = Sortable.create(el, {
+				animation: 300,
+				dataIdAttr: 'data-url',
+				disabled: getThemeConfig.value.isSortableTagsView ? false : true,
+				onEnd: () => {
+					const sortEndList: any = [];
+					state.sortable.toArray().map((val: any) => {
+						state.tagsViewList.map((v: any) => {
+							if (v.url === val) sortEndList.push({ ...v });
+						});
+					});
+					addBrowserSetSession(sortEndList);
+				},
+			});
+		};
+		// 拖动问题,https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
+		const onSortableResize = async () => {
+			await initSortable();
+			if (other.isMobile()) state.sortable.el && state.sortable.destroy();
+		};
+		// 页面加载前
+		onBeforeMount(() => {
+			// 初始化,防止手机端直接访问时还可以拖拽
+			onSortableResize();
+			// 拖动问题,https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
+			window.addEventListener('resize', onSortableResize);
+			// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
+			proxy.mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
+				onCurrentContextmenuClick(data);
+			});
+			// 监听布局配置界面开启/关闭拖拽
+			proxy.mittBus.on('openOrCloseSortable', () => {
+				initSortable();
+			});
+			// 监听布局配置开启 TagsView 共用,为了演示还原默认值
+			proxy.mittBus.on('openShareTagsView', () => {
+				if (getThemeConfig.value.isShareTagsView) {
+					router.push('/home');
+					state.tagsViewList = [];
+					state.tagsViewRoutesList.map((v: any) => {
+						if (v.meta.isAffix && !v.meta.isHide) {
+							v.url = setTagsViewHighlight(v);
+							state.tagsViewList.push({ ...v });
+						}
+					});
+				}
+			});
+		});
+		// 页面卸载时
+		onUnmounted(() => {
+			// 取消非本页面调用监听
+			proxy.mittBus.off('onCurrentContextmenuClick');
+			// 取消监听布局配置界面开启/关闭拖拽
+			proxy.mittBus.off('openOrCloseSortable');
+			// 取消监听布局配置开启 TagsView 共用
+			proxy.mittBus.off('openShareTagsView');
+			// 取消窗口 resize 监听
+			window.removeEventListener('resize', onSortableResize);
+		});
+		// 页面更新时
+		onBeforeUpdate(() => {
+			tagsRefs.value = [];
+		});
+		// 页面加载时
+		onMounted(() => {
+			// 初始化 vuex 中的 tagsViewRoutes 列表
+			getTagsViewRoutes();
+			initSortable();
+		});
+		// 路由更新时
+		onBeforeRouteUpdate(async (to) => {
+			state.routeActive = setTagsViewHighlight(to);
+			state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
+			await addTagsView(to.path, to);
+			getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
+		});
+		// 监听路由的变化,动态赋值给 tagsView
+		watch(store.state, (val) => {
+			if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
+			getTagsViewRoutes();
+		});
+		return {
+			isActive,
+			onContextmenu,
+			onTagsClick,
+			tagsRefs,
+			contextmenuRef,
+			scrollbarRef,
+			tagsUlRef,
+			onHandleScroll,
+			getThemeConfig,
+			setTagsStyle,
+			refreshCurrentTagsView,
+			closeCurrentTagsView,
+			onCurrentContextmenuClick,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-tagsview {
+	background-color: var(--el-color-white);
+	border-bottom: 1px solid var(--next-border-color-light);
+	position: relative;
+	z-index: 4;
+	::v-deep(.el-scrollbar__wrap) {
+		overflow-x: auto !important;
+	}
+	&-ul {
+		list-style: none;
+		margin: 0;
+		padding: 0;
+		height: 34px;
+		display: flex;
+		align-items: center;
+		color: var(--el-text-color-regular);
+		font-size: 12px;
+		white-space: nowrap;
+		padding: 0 15px;
+		&-li {
+			height: 26px;
+			line-height: 26px;
+			display: flex;
+			align-items: center;
+			border: 1px solid #e6e6e6;
+			padding: 0 15px;
+			margin-right: 5px;
+			border-radius: 2px;
+			position: relative;
+			z-index: 0;
+			cursor: pointer;
+			justify-content: space-between;
+			&:hover {
+				background-color: var(--el-color-primary-light-9);
+				color: var(--el-color-primary);
+				border-color: var(--el-color-primary-light-6);
+			}
+			&-iconfont {
+				position: relative;
+				left: -5px;
+				font-size: 12px;
+			}
+			&-icon {
+				border-radius: 100%;
+				position: relative;
+				height: 14px;
+				width: 14px;
+				text-align: center;
+				line-height: 14px;
+				right: -5px;
+				&:hover {
+					color: var(--el-color-white);
+					background-color: var(--el-color-primary-light-3);
+				}
+			}
+			.layout-icon-active {
+				display: block;
+			}
+			.layout-icon-three {
+				display: none;
+			}
+		}
+		.is-active {
+			color: var(--el-color-white);
+			background: var(--el-color-primary);
+			border-color: var(--el-color-primary);
+			transition: border-color 3s ease;
+		}
+	}
+	// 风格4
+	.tags-style-four {
+		.layout-navbars-tagsview-ul-li {
+			margin-right: 0 !important;
+			border: none !important;
+			position: relative;
+			border-radius: 3px !important;
+			.layout-icon-active {
+				display: none;
+			}
+			.layout-icon-three {
+				display: block;
+			}
+			&:hover {
+				background: none !important;
+			}
+		}
+		.is-active {
+			background: none !important;
+			color: var(--el-color-primary) !important;
+		}
+	}
+	// 风格5
+	.tags-style-five {
+		align-items: flex-end;
+		.tags-style-five-svg {
+			-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
+				12 27 15;
+		}
+		.layout-navbars-tagsview-ul-li {
+			padding: 0 5px;
+			border-width: 15px 27px 15px;
+			border-style: solid;
+			border-color: transparent;
+			margin: 0 -15px;
+			.layout-icon-active,
+			.layout-navbars-tagsview-ul-li-iconfont,
+			.layout-navbars-tagsview-ul-li-refresh {
+				display: none;
+			}
+			.layout-icon-three {
+				display: block;
+			}
+			&:hover {
+				@extend .tags-style-five-svg;
+				background: var(--el-color-primary-light-9);
+				color: unset;
+			}
+		}
+		.is-active {
+			@extend .tags-style-five-svg;
+			background: var(--el-color-primary-light-9) !important;
+			color: var(--el-color-primary) !important;
+			z-index: 1;
+		}
+	}
+}
+.layout-navbars-tagsview-shadow {
+	box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
+}
+</style>

+ 151 - 0
src/layout/navMenu/horizontal.vue

@@ -0,0 +1,151 @@
+<template>
+	<div class="el-menu-horizontal-warp">
+		<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
+			<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal">
+				<template v-for="val in menuLists">
+					<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
+						<template #title>
+							<SvgIcon :name="val.meta.icon" />
+							<span>{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}</span>
+						</template>
+						<SubItem :chil="val.children" />
+					</el-sub-menu>
+					<template v-else>
+						<el-menu-item :index="val.path" :key="val.path">
+							<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
+								<SvgIcon :name="val.meta.icon" />
+								{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}
+							</template>
+							<template #title v-else>
+								<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
+									<SvgIcon :name="val.meta.icon" />
+									{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}
+								</a>
+							</template>
+						</el-menu-item>
+					</template>
+				</template>
+			</el-menu>
+		</el-scrollbar>
+	</div>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
+import { useRoute, onBeforeRouteUpdate } from 'vue-router';
+import { useStore } from '/@/store/index';
+import SubItem from '/@/layout/navMenu/subItem.vue';
+export default defineComponent({
+	name: 'navMenuHorizontal',
+	components: { SubItem },
+	props: {
+		menuList: {
+			type: Array,
+			default: () => [],
+		},
+	},
+	setup(props) {
+		const { proxy } = <any>getCurrentInstance();
+		const route = useRoute();
+		const store = useStore();
+		const state = reactive({
+			defaultActive: null,
+		});
+		// 获取父级菜单数据
+		const menuLists = computed(() => {
+			return <any>props.menuList;
+		});
+		// 设置横向滚动条可以鼠标滚轮滚动
+		const onElMenuHorizontalScroll = (e: any) => {
+			const eventDelta = e.wheelDelta || -e.deltaY * 40;
+			proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft + eventDelta / 4;
+		};
+		// 初始化数据,页面刷新时,滚动条滚动到对应位置
+		const initElMenuOffsetLeft = () => {
+			nextTick(() => {
+				let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
+				if (!els) return false;
+				proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = els.offsetLeft;
+			});
+		};
+		// 路由过滤递归函数
+		const filterRoutesFun = (arr: Array<object>) => {
+			return arr
+				.filter((item: any) => !item.meta.isHide)
+				.map((item: any) => {
+					item = Object.assign({}, item);
+					if (item.children) item.children = filterRoutesFun(item.children);
+					return item;
+				});
+		};
+		// 传送当前子级数据到菜单中
+		const setSendClassicChildren = (path: string) => {
+			const currentPathSplit = path.split('/');
+			let currentData: any = {};
+			filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
+				if (v.path === `/${currentPathSplit[1]}`) {
+					v['k'] = k;
+					currentData['item'] = [{ ...v }];
+					currentData['children'] = [{ ...v }];
+					if (v.children) currentData['children'] = v.children;
+				}
+			});
+			return currentData;
+		};
+		// 设置页面当前路由高亮
+		const setCurrentRouterHighlight = (currentRoute: any) => {
+			const { path, meta } = currentRoute;
+			if (store.state.themeConfig.themeConfig.layout === 'classic') {
+				(<any>state.defaultActive) = `/${path.split('/')[1]}`;
+			} else {
+				const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
+				if (pathSplit.length >= 4 && meta.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
+				else state.defaultActive = path;
+			}
+		};
+		// 页面加载前
+		onBeforeMount(() => {
+			setCurrentRouterHighlight(route);
+		});
+		// 页面加载时
+		onMounted(() => {
+			initElMenuOffsetLeft();
+		});
+		// 路由更新时
+		onBeforeRouteUpdate((to) => {
+			// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
+			setCurrentRouterHighlight(to);
+			// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
+			let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
+			if (layout === 'classic' && isClassicSplitMenu) {
+				proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
+			}
+		});
+		return {
+			menuLists,
+			onElMenuHorizontalScroll,
+			...toRefs(state),
+		};
+	},
+});
+</script>
+
+<style scoped lang="scss">
+.el-menu-horizontal-warp {
+	flex: 1;
+	overflow: hidden;
+	margin-right: 30px;
+	::v-deep(.el-scrollbar__bar.is-vertical) {
+		display: none;
+	}
+	::v-deep(a) {
+		width: 100%;
+	}
+	.el-menu.el-menu--horizontal {
+		display: flex;
+		height: 100%;
+		width: 100%;
+		box-sizing: border-box;
+	}
+}
+</style>

+ 47 - 0
src/layout/navMenu/subItem.vue

@@ -0,0 +1,47 @@
+<template>
+	<template v-for="val in chils">
+		<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
+			<template #title>
+				<SvgIcon :name="val.meta.icon" />
+				<span>{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}</span>
+			</template>
+			<sub-item :chil="val.children" />
+		</el-sub-menu>
+		<template v-else>
+			<el-menu-item :index="val.path" :key="val.path">
+				<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
+					<SvgIcon :name="val.meta.icon" />
+					<span>{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}</span>
+				</template>
+				<template v-else>
+					<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
+						<SvgIcon :name="val.meta.icon" />
+						{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}
+					</a>
+				</template>
+			</el-menu-item>
+		</template>
+	</template>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+export default defineComponent({
+	name: 'navMenuSubItem',
+	props: {
+		chil: {
+			type: Array,
+			default: () => [],
+		},
+	},
+	setup(props) {
+		// 获取父级菜单数据
+		const chils = computed(() => {
+			return <any>props.chil;
+		});
+		return {
+			chils,
+		};
+	},
+});
+</script>

+ 98 - 0
src/layout/navMenu/vertical.vue

@@ -0,0 +1,98 @@
+<template>
+	<el-menu
+		router
+		:default-active="defaultActive"
+		background-color="transparent"
+		:collapse="isCollapse"
+		:unique-opened="getThemeConfig.isUniqueOpened"
+		:collapse-transition="false"
+	>
+		<template v-for="val in menuLists">
+			<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
+				<template #title>
+					<SvgIcon :name="val.meta.icon" />
+					<span>{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}</span>
+				</template>
+				<SubItem :chil="val.children" />
+			</el-sub-menu>
+			<template v-else>
+				<el-menu-item :index="val.path" :key="val.path">
+					<SvgIcon :name="val.meta.icon" />
+					<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
+						<span>{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}</span>
+					</template>
+					<template #title v-else>
+						<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">{{ val.meta.title.indexOf('.')>0?$t(val.meta.title):val.meta.title }}</a>
+					</template>
+				</el-menu-item>
+			</template>
+		</template>
+	</el-menu>
+</template>
+
+<script lang="ts">
+import { toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
+import { useRoute, onBeforeRouteUpdate } from 'vue-router';
+import { useStore } from '/@/store/index';
+import SubItem from '/@/layout/navMenu/subItem.vue';
+export default defineComponent({
+	name: 'navMenuVertical',
+	components: { SubItem },
+	props: {
+		menuList: {
+			type: Array,
+			default: () => [],
+		},
+	},
+	setup(props) {
+		const store = useStore();
+		const route = useRoute();
+		const state = reactive({
+			// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
+			defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
+			isCollapse: false,
+		});
+		// 获取父级菜单数据
+		const menuLists = computed(() => {
+			return <any>props.menuList;
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 菜单高亮(详情时,父级高亮)
+		const setParentHighlight = (currentRoute: any) => {
+			const { path, meta } = currentRoute;
+			const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
+			if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
+			else return path;
+		};
+		// 设置菜单的收起/展开
+		watch(
+			store.state.themeConfig.themeConfig,
+			() => {
+				document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = getThemeConfig.value.isCollapse);
+			},
+			{
+				immediate: true,
+			}
+		);
+		// 页面加载时
+		onMounted(() => {
+			state.defaultActive = setParentHighlight(route);
+		});
+		// 路由更新时
+		onBeforeRouteUpdate((to) => {
+			// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
+			state.defaultActive = setParentHighlight(to);
+			const clientWidth = document.body.clientWidth;
+			if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
+		});
+		return {
+			menuLists,
+			getThemeConfig,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 60 - 0
src/layout/routerView/iframes.vue

@@ -0,0 +1,60 @@
+<template>
+	<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="iframeLoading">
+		<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" id="iframe" v-show="!iframeLoading"></iframe>
+	</div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
+import { useRoute } from 'vue-router';
+import { useStore } from '/@/store/index';
+export default defineComponent({
+	name: 'layoutIfameView',
+	setup() {
+		const route = useRoute();
+		const store = useStore();
+		const state = reactive({
+			iframeLoading: true,
+			iframeUrl: '',
+		});
+		// 初始化页面加载 loading
+		const initIframeLoad = () => {
+			state.iframeUrl = <any>route.meta.isLink;
+			nextTick(() => {
+				state.iframeLoading = true;
+				const iframe = document.getElementById('iframe');
+				if (!iframe) return false;
+				iframe.onload = () => {
+					state.iframeLoading = false;
+				};
+			});
+		};
+		// 设置 iframe 的高度
+		const setIframeHeight = computed(() => {
+			let { isTagsview } = store.state.themeConfig.themeConfig;
+			let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
+			if (isTagsViewCurrenFull) {
+				return `1px`;
+			} else {
+				if (isTagsview) return `85px`;
+				else return `51px`;
+			}
+		});
+		// 页面加载时
+		onMounted(() => {
+			initIframeLoad();
+		});
+		// 监听路由变化,多个 iframe 时使用
+		watch(
+			() => route.path,
+			() => {
+				initIframeLoad();
+			}
+		);
+		return {
+			setIframeHeight,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 59 - 0
src/layout/routerView/link.vue

@@ -0,0 +1,59 @@
+<template>
+	<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
+		<a :href="currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin">
+			{{ currentRouteMeta.title.indexOf('.')>0?$t(currentRouteMeta.title):currentRouteMeta.title }}:{{ currentRouteMeta.isLink }}
+		</a>
+	</div>
+</template>
+
+<script lang="ts">
+import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
+import { useRoute, RouteMeta } from 'vue-router';
+import { useStore } from '/@/store/index';
+
+// 定义接口来定义对象的类型
+interface LinkViewState {
+	currentRouteMeta: {
+		isLink: string;
+		title: string;
+	};
+}
+interface LinkViewRouteMeta extends RouteMeta {
+	isLink: string;
+	title: string;
+}
+
+export default defineComponent({
+	name: 'layoutLinkView',
+	setup() {
+		const route = useRoute();
+		const store = useStore();
+		const state = reactive<LinkViewState>({
+			currentRouteMeta: {
+				isLink: '',
+				title: '',
+			},
+		});
+		// 设置 link 的高度
+		const setLinkHeight = computed(() => {
+			let { isTagsview } = store.state.themeConfig.themeConfig;
+			if (isTagsview) return `114px`;
+			else return `80px`;
+		});
+		// 监听路由的变化,设置内容
+		watch(
+			() => route.path,
+			() => {
+				state.currentRouteMeta = <LinkViewRouteMeta>route.meta;
+			},
+			{
+				immediate: true,
+			}
+		);
+		return {
+			setLinkHeight,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 83 - 0
src/layout/routerView/parent.vue

@@ -0,0 +1,83 @@
+<template>
+	<div class="h100">
+		<router-view v-slot="{ Component }">
+			<transition :name="setTransitionName" mode="out-in">
+				<keep-alive :include="keepAliveNameList">
+					<component :is="Component" :key="refreshRouterViewKey" class="w100" :style="{ minHeight }" />
+				</keep-alive>
+			</transition>
+		</router-view>
+	</div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
+import { useRoute } from 'vue-router';
+import { useStore } from '/@/store/index';
+
+// 定义接口来定义对象的类型
+interface ParentViewState {
+	refreshRouterViewKey: null | string;
+	keepAliveNameList: string[];
+}
+
+export default defineComponent({
+	name: 'layoutParentView',
+	props: {
+		minHeight: {
+			type: String,
+			default: '',
+		},
+	},
+	setup() {
+		const { proxy } = <any>getCurrentInstance();
+		const route = useRoute();
+		const store = useStore();
+		const state = reactive<ParentViewState>({
+			refreshRouterViewKey: null,
+			keepAliveNameList: [],
+		});
+		// 设置主界面切换动画
+		const setTransitionName = computed(() => {
+			return store.state.themeConfig.themeConfig.animation;
+		});
+		// 获取布局配置信息
+		const getThemeConfig = computed(() => {
+			return store.state.themeConfig.themeConfig;
+		});
+		// 获取组件缓存列表(name值)
+		const getKeepAliveNames = computed(() => {
+			return store.state.keepAliveNames.keepAliveNames;
+		});
+		// 页面加载前,处理缓存,页面刷新时路由缓存处理
+		onBeforeMount(() => {
+			state.keepAliveNameList = getKeepAliveNames.value;
+			proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
+				state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
+				state.refreshRouterViewKey = null;
+				nextTick(() => {
+					state.refreshRouterViewKey = fullPath;
+					state.keepAliveNameList = getKeepAliveNames.value;
+				});
+			});
+		});
+		// 页面卸载时
+		onUnmounted(() => {
+			proxy.mittBus.off('onTagsViewRefreshRouterView');
+		});
+		// 监听路由变化,防止 tagsView 多标签时,切换动画消失
+		watch(
+			() => route.fullPath,
+			() => {
+				state.refreshRouterViewKey = route.fullPath;
+			}
+		);
+		return {
+			getThemeConfig,
+			getKeepAliveNames,
+			setTransitionName,
+			...toRefs(state),
+		};
+	},
+});
+</script>

+ 39 - 0
src/main.ts

@@ -0,0 +1,39 @@
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from './router';
+import { store, key } from './store';
+import { directive } from '/@/utils/directive';
+import { i18n } from '/@/i18n/index';
+import other from '/@/utils/other';
+
+import ElementPlus from 'element-plus';
+import 'element-plus/dist/index.css';
+import '/@/theme/index.scss';
+import mitt from 'mitt';
+import VueGridLayout from 'vue-grid-layout';
+import {getUpFileUrl, handleTree, selectDictLabel} from "/@/utils/gfast";
+import {useDict} from "/@/api/system/dict/data";
+// 分页组件
+import pagination from '/@/components/pagination/index.vue'
+
+const app = createApp(App);
+
+directive(app);
+other.elSvg(app);
+
+app.component('pagination', pagination)
+
+app.use(router)
+    .use(store, key)
+    .use(ElementPlus, { i18n: i18n.global.t })
+    .use(i18n)
+    .use(VueGridLayout)
+    .mount('#app');
+
+
+// 全局挂载
+app.config.globalProperties.getUpFileUrl=getUpFileUrl
+app.config.globalProperties.handleTree=handleTree
+app.config.globalProperties.useDict=useDict
+app.config.globalProperties.selectDictLabel=selectDictLabel
+app.config.globalProperties.mittBus = mitt();

+ 108 - 0
src/router/backEnd.ts

@@ -0,0 +1,108 @@
+import { store } from '/@/store/index.ts';
+import { Session } from '/@/utils/storage';
+import { NextLoading } from '/@/utils/loading';
+import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
+import {demoRoutes, dynamicRoutes} from '/@/router/route';
+import { getUserMenus } from '/@/api/system/menu';
+
+
+
+const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
+const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
+/**
+ * 获取目录下的 .vue、.tsx 全部文件
+ * @method import.meta.glob
+ * @link 参考:https://cn.vitejs.dev/guide/features.html#json
+ */
+const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
+
+/**
+ * 后端控制路由:初始化方法,防止刷新时路由丢失
+ * @method  NextLoading 界面 loading 动画开始执行
+ * @method store.dispatch('userInfos/setUserInfos') 触发初始化用户信息
+ * @method store.dispatch('requestOldRoutes/setBackEndControlRoutes') 存储接口原始路由(未处理component),根据需求选择使用
+ * @method setAddRoute 添加动态路由
+ * @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
+ */
+export async function initBackEndControlRoutes() {
+	// 界面 loading 动画开始执行
+	if (window.nextLoading === undefined) NextLoading.start();
+	// 无 token 停止执行下一步
+	if (!Session.get('token')) return false;
+	// 触发初始化用户信息
+	store.dispatch('userInfos/setUserInfos');
+	store.dispatch('userInfos/setPermissions');
+	let menuRoute = Session.get('userMenu')
+	let permissions = Session.get('permissions')
+	if (!menuRoute || !permissions) {
+		await getBackEndControlRoutes(); // 获取路由
+		menuRoute = Session.get('userMenu')
+	}
+
+	// 获取路由菜单数据
+	// 存储接口原始路由(未处理component),根据需求选择使用
+	store.dispatch('requestOldRoutes/setBackEndControlRoutes', JSON.parse(JSON.stringify(menuRoute)));
+	// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
+	dynamicRoutes[0].children?.push(...await backEndComponent(menuRoute),...demoRoutes) ;
+	// 添加动态路由
+	await setAddRoute();
+	// 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
+	setFilterMenuAndCacheTagsViewRoutes();
+}
+
+/**
+ * 请求后端路由菜单接口
+ * @description isRequestRoutes 为 true,则开启后端控制路由
+ * @returns 返回后端路由菜单数据
+ */
+export async function getBackEndControlRoutes() {
+	return getUserMenus().then((res:any)=>{
+		Session.set('userMenu',res.data.menuList)
+		Session.set('permissions',res.data.permissions)
+		store.dispatch('userInfos/setPermissions',res.data.permissions)
+	})
+}
+
+/**
+ * 重新请求后端路由菜单接口
+ * @description 用于菜单管理界面刷新菜单(未进行测试)
+ * @description 路径:/src/views/system/menu/component/addMenu.vue
+ */
+export function setBackEndControlRefreshRoutes() {
+	getBackEndControlRoutes();
+}
+
+/**
+ * 后端路由 component 转换
+ * @param routes 后端返回的路由表数组
+ * @returns 返回处理成函数后的 component
+ */
+export function backEndComponent(routes: any) {
+	if (!routes) return;
+	return routes.map((item: any) => {
+		if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
+		item.children && backEndComponent(item.children);
+		return item;
+	});
+}
+
+/**
+ * 后端路由 component 转换函数
+ * @param dynamicViewsModules 获取目录下的 .vue、.tsx 全部文件
+ * @param component 当前要处理项 component
+ * @returns 返回处理成函数后的 component
+ */
+export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
+	const keys = Object.keys(dynamicViewsModules);
+	const matchKeys = keys.filter((key) => {
+		const k = key.replace(/..\/views|../, '');
+		return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
+	});
+	if (matchKeys?.length === 1) {
+		const matchKey = matchKeys[0];
+		return dynamicViewsModules[matchKey];
+	}
+	if (matchKeys?.length > 1) {
+		return false;
+	}
+}

+ 25 - 0
src/router/frontEnd.ts

@@ -0,0 +1,25 @@
+import { store } from '/@/store/index';
+import { Session } from '/@/utils/storage';
+import { NextLoading } from '/@/utils/loading';
+import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
+
+/**
+ * 前端控制路由:初始化方法,防止刷新时路由丢失
+ * @method  NextLoading 界面 loading 动画开始执行
+ * @method store.dispatch('userInfos/setUserInfos') 触发初始化用户信息
+ * @method setAddRoute 添加动态路由
+ * @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
+ */
+export async function initFrontEndControlRoutes() {
+	// 界面 loading 动画开始执行
+	if (window.nextLoading === undefined) NextLoading.start();
+	// 无 token 停止执行下一步
+	if (!Session.get('token')) return false;
+	// 触发初始化用户信息
+	store.dispatch('userInfos/setUserInfos');
+	store.dispatch('userInfos/setPermissions');
+	// 添加动态路由
+	await setAddRoute();
+	// 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
+	setFilterMenuAndCacheTagsViewRoutes();
+}

+ 254 - 0
src/router/index.ts

@@ -0,0 +1,254 @@
+import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
+import NProgress from 'nprogress';
+import 'nprogress/nprogress.css';
+import { store } from '/@/store/index.ts';
+import { Session } from '/@/utils/storage';
+import { NextLoading } from '/@/utils/loading';
+import { staticRoutes, dynamicRoutes } from '/@/router/route';
+import { initFrontEndControlRoutes } from '/@/router/frontEnd';
+import { initBackEndControlRoutes } from '/@/router/backEnd';
+
+/**
+ * 创建一个可以被 Vue 应用程序使用的路由实例
+ * @method createRouter(options: RouterOptions): Router
+ * @link 参考:https://next.router.vuejs.org/zh/api/#createrouter
+ */
+const router = createRouter({
+	history: createWebHashHistory(),
+	routes: staticRoutes,
+});
+
+/**
+ * 定义404界面
+ * @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify
+ */
+const pathMatch = {
+	path: '/:path(.*)*',
+	redirect: '/404',
+};
+
+/**
+ * 路由多级嵌套数组处理成一维数组
+ * @param arr 传入路由菜单数据数组
+ * @returns 返回处理后的一维路由菜单数组
+ */
+export function formatFlatteningRoutes(arr: any) {
+	if (arr.length <= 0) return false;
+	for (let i = 0; i < arr.length; i++) {
+		if (arr[i].children) {
+			arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
+		}
+	}
+	return arr;
+}
+
+/**
+ * 一维数组处理成多级嵌套数组(只保留二级:也就是二级以上全部处理成只有二级,keep-alive 支持二级缓存)
+ * @description isKeepAlive 处理 `name` 值,进行缓存。顶级关闭,全部不缓存
+ * @link 参考:https://v3.cn.vuejs.org/api/built-in-components.html#keep-alive
+ * @param arr 处理后的一维路由菜单数组
+ * @returns 返回将一维数组重新处理成 `定义动态路由(dynamicRoutes)` 的格式
+ */
+export function formatTwoStageRoutes(arr: any) {
+	if (arr.length <= 0) return false;
+	const newArr: any = [];
+	const cacheList: Array<string> = [];
+	arr.forEach((v: any) => {
+		if (v.path === '/') {
+			newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
+		} else {
+			// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
+			// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
+			if (v.path.indexOf('/:') > -1) {
+				v.meta['isDynamic'] = true;
+				v.meta['isDynamicPath'] = v.path;
+			}
+			newArr[0].children.push({ ...v });
+			// 存 name 值,keep-alive 中 include 使用,实现路由的缓存
+			// 路径:/@/layout/routerView/parent.vue
+			if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
+				cacheList.push(v.name);
+				store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
+			}
+		}
+	});
+	return newArr;
+}
+
+/**
+ * 缓存多级嵌套数组处理后的一维数组
+ * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
+ */
+export function setCacheTagsViewRoutes() {
+	// 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示
+	let rolesRoutes = dynamicRoutes
+	// 添加到 vuex setTagsViewRoutes 中
+	store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))[0].children);
+}
+
+/**
+ * 判断路由 `meta.roles` 中是否包含当前登录用户权限字段
+ * @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组
+ * @param route 当前循环时的路由项
+ * @returns 返回对比后有权限的路由项
+ */
+export function hasRoles(roles: any, route: any) {
+	if (route.meta && route.meta.roles) return roles.some((role: any) => route.meta.roles.includes(role));
+	else return true;
+}
+
+/**
+ * 获取当前用户权限标识去比对路由表,设置递归过滤有权限的路由
+ * @param routes 当前路由 children
+ * @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组
+ * @returns 返回有权限的路由数组 `meta.roles` 中控制
+ */
+export function setFilterHasRolesMenu(routes: any, roles: any) {
+	const menu: any = [];
+	routes.forEach((route: any) => {
+		const item = { ...route };
+		if (hasRoles(roles, item)) {
+			menu.push(item);
+		}
+	});
+	return menu;
+}
+
+/**
+ * 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
+ * @description 用于左侧菜单、横向菜单的显示
+ * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
+ */
+export function setFilterMenuAndCacheTagsViewRoutes() {
+	store.dispatch('routesList/setRoutesList', dynamicRoutes[0].children);
+	setCacheTagsViewRoutes();
+}
+
+/**
+ * 获取当前用户权限标识去比对路由表(未处理成多级嵌套路由)
+ * @description 这里主要用于动态路由的添加,router.addRoute
+ * @link 参考:https://next.router.vuejs.org/zh/api/#addroute
+ * @param chil dynamicRoutes(/@/router/route)第一个顶级 children 的下路由集合
+ * @returns 返回有当前用户权限标识的路由数组
+ */
+export function setFilterRoute(chil: any) {
+	let filterRoute: any = [];
+	chil.forEach((route: any) => {
+		if (route.meta.roles) {
+			route.meta.roles.forEach((metaRoles: any) => {
+				store.state.userInfos.userInfos.roles.forEach((roles: any) => {
+					if (metaRoles === roles) filterRoute.push({ ...route });
+				});
+			});
+		}
+	});
+	return filterRoute;
+}
+
+/**
+ * 获取有当前用户权限标识的路由数组,进行对原路由的替换
+ * @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
+ * @returns 返回替换后的路由数组
+ */
+export function setFilterRouteEnd() {
+	let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
+	filterRouteEnd[0].children = [...filterRouteEnd[0].children, { ...pathMatch }];
+	return filterRouteEnd;
+}
+
+/**
+ * 添加动态路由
+ * @method router.addRoute
+ * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
+ * @link 参考:https://next.router.vuejs.org/zh/api/#addroute
+ */
+export async function setAddRoute() {
+	await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
+		const routeName: any = route.name;
+		if (!router.hasRoute(routeName)) router.addRoute(route);
+	});
+}
+
+/**
+ * 删除/重置路由
+ * @method router.removeRoute
+ * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
+ * @link 参考:https://next.router.vuejs.org/zh/api/#push
+ */
+export async function resetRoute() {
+	await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
+		const routeName: any = route.name;
+		router.hasRoute(routeName) && router.removeRoute(routeName);
+	});
+}
+
+// isRequestRoutes 为 true,则开启后端控制路由,路径:`/src/store/modules/themeConfig.ts`
+const { isRequestRoutes } = store.state.themeConfig.themeConfig;
+// 前端控制路由:初始化方法,防止刷新时路由丢失
+if (!isRequestRoutes) initFrontEndControlRoutes();
+
+
+import {isInit} from "/@/api/system/dbInit"
+// 路由加载前
+router.beforeEach(async (to, from, next) => {
+	NProgress.configure({ showSpinner: false });
+	if (to.meta.title) NProgress.start();
+	//  系统初始化
+	if (to.path === '/dbInit') {
+		next();
+		NProgress.done();
+	}
+
+	if (Session.get("isInit") !== true) {
+		const res:any  = await isInit()
+		let {code, data}  = res
+		if (code === 0 ) {
+			if (data === false) {
+				next('/dbInit');
+				NProgress.done();
+				return
+			} else {
+				Session.set("isInit", true)
+			}
+		}
+	}
+
+
+	// 正常流程
+	const token = Session.get('token');
+	if (to.path === '/login' && !token) {
+		next();
+		NProgress.done();
+	} else {
+		if (!token) {
+			next(`/login?redirect=${to.path}&params=${JSON.stringify(to.query ? to.query : to.params)}`);
+			Session.clear();
+			resetRoute();
+			NProgress.done();
+		} else if (token && to.path === '/login') {
+			next('/home');
+			NProgress.done();
+		} else {
+			if (store.state.routesList.routesList.length === 0) {
+				if (isRequestRoutes) {
+					// 后端控制路由:路由数据初始化,防止刷新时丢失
+					await initBackEndControlRoutes();
+					// 动态添加路由:防止非首页刷新时跳转回首页的问题
+					// 确保 addRoute() 时动态添加的路由已经被完全加载上去
+					next({ ...to, replace: true });
+				}
+			} else {
+				next();
+			}
+		}
+	}
+});
+
+// 路由加载后
+router.afterEach(() => {
+	NProgress.done();
+	NextLoading.done();
+});
+
+// 导出路由
+export default router;

+ 1028 - 0
src/router/route.ts

@@ -0,0 +1,1028 @@
+import { RouteRecordRaw } from 'vue-router';
+
+/**
+ * 路由meta对象参数说明
+ * meta: {
+ *      title:          菜单栏及 tagsView 栏、菜单搜索名称(国际化)
+ *      isLink:        是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空`
+ *      isHide:        是否隐藏此路由
+ *      isKeepAlive:   是否缓存组件状态
+ *      isAffix:       是否固定在 tagsView 栏上
+ *      isIframe:      是否内嵌窗口,,开启条件,`1、isIframe:true 2、链接地址不为空`
+ *      roles:         当前路由权限标识,取角色管理。控制路由显示、隐藏。超级管理员:admin 普通角色:common
+ *      icon:          菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx`
+ * }
+ */
+
+/**
+ * 定义动态路由
+ * @description 未开启 isRequestRoutes 为 true 时使用(前端控制路由),开启时第一个顶级 children 的路由将被替换成接口请求回来的路由数据
+ * @description 各字段请查看 `/@/views/system/menu/component/addMenu.vue 下的 ruleForm`
+ * @returns 返回路由菜单数据
+ */
+export const dynamicRoutes: Array<RouteRecordRaw> = [
+	{
+		path: '/',
+		name: '/',
+		component: () => import('/@/layout/index.vue'),
+		redirect: '/home',
+		meta: {
+			isKeepAlive: true,
+		},
+		children: [
+			{
+				path: '/home',
+				name: 'home',
+				component: () => import('/@/views/home/index.vue'),
+				meta: {
+					title: 'message.router.home',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: true,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-shouye',
+				},
+			},
+			{
+				path: '/personal',
+				name: 'personal',
+				component: () => import('/@/views/personal/index.vue'),
+				meta: {
+					title: 'message.router.personal',
+					isLink: '',
+					isHide: true,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-gerenzhongxin',
+				},
+			},
+			{
+				path: '/404',
+				name: 'notFound',
+				component: () => import('/@/views/error/404.vue'),
+				meta: {
+					title: 'message.staticRoutes.notFound',
+					isLink: '',
+					isHide: true,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+				},
+			},
+			{
+				path: '/401',
+				name: 'noPower',
+				component: () => import('/@/views/error/401.vue'),
+				meta: {
+					title: 'message.staticRoutes.noPower',
+					isLink: '',
+					isHide: true,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+				},
+			},
+		],
+	},
+];
+
+export const demoRoutes:Array<RouteRecordRaw> = [
+	{
+		path: '/demo',
+		name: 'demo',
+		component: () => import('/@/layout/routerView/parent.vue'),
+		redirect: '/demo/system/menu',
+		meta: {
+			title: '案例演示',
+			isLink: '',
+			isHide: false,
+			isKeepAlive: true,
+			isAffix: false,
+			isIframe: false,
+			roles: ['admin'],
+			icon: 'iconfont icon-diannao',
+		},
+		children: [
+			{
+				path: '/demo/menu',
+				name: 'menu',
+				component: () => import('/@/layout/routerView/parent.vue'),
+				redirect: '/menu/menu1',
+				meta: {
+					title: 'message.router.menu',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-caidan',
+				},
+				children: [
+					{
+						path: '/demo/menu/menu1',
+						name: 'menu1',
+						component: () => import('/@/layout/routerView/parent.vue'),
+						redirect: '/menu/menu1/menu11',
+						meta: {
+							title: 'message.router.menu1',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-caidan',
+						},
+						children: [
+							{
+								path: '/demo/menu/menu1/menu11',
+								name: 'menu11',
+								component: () => import('/@/views/menu/menu1/menu11/index.vue'),
+								meta: {
+									title: 'message.router.menu11',
+									isLink: '',
+									isHide: false,
+									isKeepAlive: true,
+									isAffix: false,
+									isIframe: false,
+									roles: ['admin', 'common'],
+									icon: 'iconfont icon-caidan',
+								},
+							},
+							{
+								path: '/demo/menu/menu1/menu12',
+								name: 'menu12',
+								component: () => import('/@/layout/routerView/parent.vue'),
+								redirect: '/menu/menu1/menu12/menu121',
+								meta: {
+									title: 'message.router.menu12',
+									isLink: '',
+									isHide: false,
+									isKeepAlive: true,
+									isAffix: false,
+									isIframe: false,
+									roles: ['admin', 'common'],
+									icon: 'iconfont icon-caidan',
+								},
+								children: [
+									{
+										path: '/demo/menu/menu1/menu12/menu121',
+										name: 'menu121',
+										component: () => import('/@/views/menu/menu1/menu12/menu121/index.vue'),
+										meta: {
+											title: 'message.router.menu121',
+											isLink: '',
+											isHide: false,
+											isKeepAlive: true,
+											isAffix: false,
+											isIframe: false,
+											roles: ['admin', 'common'],
+											icon: 'iconfont icon-caidan',
+										},
+									},
+									{
+										path: '/demo/menu/menu1/menu12/menu122',
+										name: 'menu122',
+										component: () => import('/@/views/menu/menu1/menu12/menu122/index.vue'),
+										meta: {
+											title: 'message.router.menu122',
+											isLink: '',
+											isHide: false,
+											isKeepAlive: true,
+											isAffix: false,
+											isIframe: false,
+											roles: ['admin', 'common'],
+											icon: 'iconfont icon-caidan',
+										},
+									},
+								],
+							},
+							{
+								path: '/demo/menu/menu1/menu13',
+								name: 'menu13',
+								component: () => import('/@/views/menu/menu1/menu13/index.vue'),
+								meta: {
+									title: 'message.router.menu13',
+									isLink: '',
+									isHide: false,
+									isKeepAlive: true,
+									isAffix: false,
+									isIframe: false,
+									roles: ['admin', 'common'],
+									icon: 'iconfont icon-caidan',
+								},
+							},
+						],
+					},
+					{
+						path: '/demo/menu/menu2',
+						name: 'menu2',
+						component: () => import('/@/views/menu/menu2/index.vue'),
+						meta: {
+							title: 'message.router.menu2',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-caidan',
+						},
+					},
+				],
+			},
+			{
+				path: '/demo/fun',
+				name: 'funIndex',
+				component: () => import('/@/layout/routerView/parent.vue'),
+				redirect: '/fun/tagsView',
+				meta: {
+					title: 'message.router.funIndex',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-crew_feature',
+				},
+				children: [
+					{
+						path: '/demo/fun/tagsView',
+						name: 'funTagsView',
+						component: () => import('/@/views/fun/tagsView/index.vue'),
+						meta: {
+							title: 'message.router.funTagsView',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Pointer',
+						},
+					},
+					{
+						path: '/demo/fun/countup',
+						name: 'funCountup',
+						component: () => import('/@/views/fun/countup/index.vue'),
+						meta: {
+							title: 'message.router.funCountup',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Odometer',
+						},
+					},
+					{
+						path: '/demo/fun/wangEditor',
+						name: 'funWangEditor',
+						component: () => import('/@/views/fun/wangEditor/index.vue'),
+						meta: {
+							title: 'message.router.funWangEditor',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-fuwenbenkuang',
+						},
+					},
+					{
+						path: '/demo/fun/cropper',
+						name: 'funCropper',
+						component: () => import('/@/views/fun/cropper/index.vue'),
+						meta: {
+							title: 'message.router.funCropper',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-caijian',
+						},
+					},
+					{
+						path: '/demo/fun/qrcode',
+						name: 'funQrcode',
+						component: () => import('/@/views/fun/qrcode/index.vue'),
+						meta: {
+							title: 'message.router.funQrcode',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-ico',
+						},
+					},
+					{
+						path: '/demo/fun/echartsMap',
+						name: 'funEchartsMap',
+						component: () => import('/@/views/fun/echartsMap/index.vue'),
+						meta: {
+							title: 'message.router.funEchartsMap',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-ditu',
+						},
+					},
+					{
+						path: '/demo/fun/printJs',
+						name: 'funPrintJs',
+						component: () => import('/@/views/fun/printJs/index.vue'),
+						meta: {
+							title: 'message.router.funPrintJs',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Printer',
+						},
+					},
+					{
+						path: '/demo/fun/clipboard',
+						name: 'funClipboard',
+						component: () => import('/@/views/fun/clipboard/index.vue'),
+						meta: {
+							title: 'message.router.funClipboard',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-DocumentCopy',
+						},
+					},
+					{
+						path: '/demo/fun/gridLayout',
+						name: 'funGridLayout',
+						component: () => import('/@/views/fun/gridLayout/index.vue'),
+						meta: {
+							title: 'message.router.funGridLayout',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-tuodong',
+						},
+					},
+					{
+						path: '/demo/fun/splitpanes',
+						name: 'funSplitpanes',
+						component: () => import('/@/views/fun/splitpanes/index.vue'),
+						meta: {
+							title: 'message.router.funSplitpanes',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon--chaifenlie',
+						},
+					},
+				],
+			},
+			{
+				path: '/demo/pages',
+				name: 'pagesIndex',
+				component: () => import('/@/layout/routerView/parent.vue'),
+				redirect: '/pages/filtering',
+				meta: {
+					title: 'message.router.pagesIndex',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-fuzhiyemian',
+				},
+				children: [
+					{
+						path: '/demo/pages/filtering',
+						name: 'pagesFiltering',
+						component: () => import('/@/views/pages/filtering/index.vue'),
+						meta: {
+							title: 'message.router.pagesFiltering',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Sell',
+						},
+						/**
+						 * 注意此处详情写法:
+						 * 1、嵌套进父级里时,面包屑显示为:首页/页面/过滤筛选组件/过滤筛选组件详情
+						 * 2、不嵌套进父级时,面包屑显示为:首页/页面/过滤筛选组件/过滤筛选组件详情
+						 * 3、想要父级不高亮,面包屑显示为:首页/页面/过滤筛选组件详情,设置路径为:/pages/filteringDetails
+						 */
+						children: [
+							{
+								path: '/demo/pages/filtering/details',
+								name: 'pagesFilteringDetails',
+								component: () => import('/@/views/pages/filtering/details.vue'),
+								meta: {
+									title: 'message.router.pagesFilteringDetails',
+									isLink: '',
+									isHide: true,
+									isKeepAlive: false,
+									isAffix: false,
+									isIframe: false,
+									roles: ['admin', 'common'],
+									icon: 'ele-Sunny',
+								},
+							},
+						],
+					},
+					{
+						path: '/demo/pages/filtering/details1',
+						name: 'pagesFilteringDetails1',
+						component: () => import('/@/views/pages/filtering/details1.vue'),
+						meta: {
+							title: 'message.router.pagesFilteringDetails1',
+							isLink: '',
+							isHide: true,
+							isKeepAlive: false,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Sunny',
+						},
+					},
+					{
+						path: '/demo/pages/iocnfont',
+						name: 'pagesIocnfont',
+						component: () => import('/@/views/pages/iocnfont/index.vue'),
+						meta: {
+							title: 'message.router.pagesIocnfont',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Present',
+						},
+					},
+					{
+						path: '/demo/pages/element',
+						name: 'pagesElement',
+						component: () => import('/@/views/pages/element/index.vue'),
+						meta: {
+							title: 'message.router.pagesElement',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Eleme',
+						},
+					},
+					{
+						path: '/demo/pages/awesome',
+						name: 'pagesAwesome',
+						component: () => import('/@/views/pages/awesome/index.vue'),
+						meta: {
+							title: 'message.router.pagesAwesome',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-SetUp',
+						},
+					},
+					{
+						path: '/demo/pages/formAdapt',
+						name: 'pagesFormAdapt',
+						component: () => import('/@/views/pages/formAdapt/index.vue'),
+						meta: {
+							title: 'message.router.pagesFormAdapt',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-biaodan',
+						},
+					},
+					{
+						path: '/demo/pages/tableRules',
+						name: 'pagesTableRules',
+						component: () => import('/@/views/pages/tableRules/index.vue'),
+						meta: {
+							title: 'message.router.pagesTableRules',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-jiliandongxuanzeqi',
+						},
+					},
+					{
+						path: '/demo/pages/formI18n',
+						name: 'pagesFormI18n',
+						component: () => import('/@/views/pages/formI18n/index.vue'),
+						meta: {
+							title: 'message.router.pagesFormI18n',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-diqiu',
+						},
+					},
+					{
+						path: '/demo/pages/formRules',
+						name: 'pagesFormRules',
+						component: () => import('/@/views/pages/formRules/index.vue'),
+						meta: {
+							title: 'message.router.pagesFormRules',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-shuxing',
+						},
+					},
+					{
+						path: '/demo/pages/listAdapt',
+						name: 'pagesListAdapt',
+						component: () => import('/@/views/pages/listAdapt/index.vue'),
+						meta: {
+							title: 'message.router.pagesListAdapt',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-chazhaobiaodanliebiao',
+						},
+					},
+					{
+						path: '/demo/pages/waterfall',
+						name: 'pagesWaterfall',
+						component: () => import('/@/views/pages/waterfall/index.vue'),
+						meta: {
+							title: 'message.router.pagesWaterfall',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-zidingyibuju',
+						},
+					},
+					{
+						path: '/demo/pages/steps',
+						name: 'pagesSteps',
+						component: () => import('/@/views/pages/steps/index.vue'),
+						meta: {
+							title: 'message.router.pagesSteps',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-step',
+						},
+					},
+					{
+						path: '/demo/pages/preview',
+						name: 'pagesPreview',
+						component: () => import('/@/views/pages/preview/index.vue'),
+						meta: {
+							title: 'message.router.pagesPreview',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-15tupianyulan',
+						},
+					},
+					{
+						path: '/demo/pages/waves',
+						name: 'pagesWaves',
+						component: () => import('/@/views/pages/waves/index.vue'),
+						meta: {
+							title: 'message.router.pagesWaves',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-bolangneng',
+						},
+					},
+					{
+						path: '/demo/pages/tree',
+						name: 'pagesTree',
+						component: () => import('/@/views/pages/tree/index.vue'),
+						meta: {
+							title: 'message.router.pagesTree',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-shuxingtu',
+						},
+					},
+					{
+						path: '/demo/pages/drag',
+						name: 'pagesDrag',
+						component: () => import('/@/views/pages/drag/index.vue'),
+						meta: {
+							title: 'message.router.pagesDrag',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Pointer',
+						},
+					},
+					{
+						path: '/demo/pages/lazyImg',
+						name: 'pagesLazyImg',
+						component: () => import('/@/views/pages/lazyImg/index.vue'),
+						meta: {
+							title: 'message.router.pagesLazyImg',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'ele-PictureFilled',
+						},
+					},
+					{
+						path: '/demo/pages/dynamicForm',
+						name: 'pagesDynamicForm',
+						component: () => import('/@/views/pages/dynamicForm/index.vue'),
+						meta: {
+							title: 'message.router.pagesDynamicForm',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'iconfont icon-diannao',
+						},
+					},
+					{
+						path: '/demo/pages/workflow',
+						name: 'pagesWorkflow',
+						component: () => import('/@/views/pages/workflow/index.vue'),
+						meta: {
+							title: 'message.router.pagesWorkflow',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'ele-Connection',
+						},
+					},
+				],
+			},
+			{
+				path: '/demo/make',
+				name: 'makeIndex',
+				component: () => import('/@/layout/routerView/parent.vue'),
+				redirect: '/make/selector',
+				meta: {
+					title: 'message.router.makeIndex',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin'],
+					icon: 'iconfont icon-siweidaotu',
+				},
+				children: [
+					{
+						path: '/demo/make/selector',
+						name: 'makeSelector',
+						component: () => import('/@/views/make/selector/index.vue'),
+						meta: {
+							title: 'message.router.makeSelector',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'iconfont icon-xuanzeqi',
+						},
+					},
+					{
+						path: '/demo/make/noticeBar',
+						name: 'makeNoticeBar',
+						component: () => import('/@/views/make/noticeBar/index.vue'),
+						meta: {
+							title: 'message.router.makeNoticeBar',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'ele-Bell',
+						},
+					},
+					{
+						path: '/demo/make/svgDemo',
+						name: 'makeSvgDemo',
+						component: () => import('/@/views/make/svgDemo/index.vue'),
+						meta: {
+							title: 'message.router.makeSvgDemo',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin', 'common'],
+							icon: 'fa fa-thumbs-o-up',
+						},
+					},
+				],
+			},
+			{
+				path: '/demo/params',
+				name: 'paramsIndex',
+				component: () => import('/@/layout/routerView/parent.vue'),
+				redirect: '/params/common',
+				meta: {
+					title: 'message.router.paramsIndex',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin'],
+					icon: 'iconfont icon-zhongduancanshu',
+				},
+				children: [
+					{
+						path: '/demo/params/common',
+						name: 'paramsCommon',
+						component: () => import('/@/views/params/common/index.vue'),
+						meta: {
+							title: 'message.router.paramsCommon',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'iconfont icon-putong',
+						},
+					},
+					{
+						path: '/demo/params/common/details',
+						name: 'paramsCommonDetails',
+						component: () => import('/@/views/params/common/details.vue'),
+						meta: {
+							title: 'message.router.paramsCommonDetails',
+							isLink: '',
+							isHide: true,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'ele-Comment',
+						},
+					},
+					{
+						path: '/demo/params/dynamic',
+						name: 'paramsDynamic',
+						component: () => import('/@/views/params/dynamic/index.vue'),
+						meta: {
+							title: 'message.router.paramsDynamic',
+							isLink: '',
+							isHide: false,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'iconfont icon-dongtai',
+						},
+					},
+					{
+						path: '/demo/params/dynamic/details/:t/:id',
+						name: 'paramsDynamicDetails',
+						component: () => import('/@/views/params/dynamic/details.vue'),
+						meta: {
+							title: 'message.router.paramsDynamicDetails',
+							isLink: '',
+							isHide: true,
+							isKeepAlive: true,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'ele-Lightning',
+						},
+					},
+				],
+			},
+			{
+				path: '/demo/visualizing',
+				name: 'visualizingIndex',
+				component: () => import('/@/layout/routerView/parent.vue'),
+				redirect: '/visualizing/visualizingLinkDemo1',
+				meta: {
+					title: 'message.router.visualizingIndex',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin'],
+					icon: 'ele-ChatLineRound',
+				},
+				children: [
+					{
+						path: '/demo/visualizing/visualizingLinkDemo1',
+						name: 'visualizingLinkDemo1',
+						component: () => import('/@/layout/routerView/link.vue'),
+						meta: {
+							title: 'message.router.visualizingLinkDemo1',
+							isLink: `./#/visualizingDemo1`,
+							isHide: false,
+							isKeepAlive: false,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'iconfont icon-caozuo-wailian',
+						},
+					},
+					{
+						path: '/demo/visualizing/visualizingLinkDemo2',
+						name: 'visualizingLinkDemo2',
+						component: () => import('/@/layout/routerView/link.vue'),
+						meta: {
+							title: 'message.router.visualizingLinkDemo2',
+							isLink: `./#/visualizingDemo2`,
+							isHide: false,
+							isKeepAlive: false,
+							isAffix: false,
+							isIframe: false,
+							roles: ['admin'],
+							icon: 'iconfont icon-caozuo-wailian',
+						},
+					},
+				],
+			},
+			{
+				path: '/demo/chart',
+				name: 'chartIndex',
+				component: () => import('/@/views/chart/index.vue'),
+				meta: {
+					title: 'message.router.chartIndex',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-ico_shuju',
+				},
+			},
+			{
+				path: '/demo/tools',
+				name: 'tools',
+				component: () => import('/@/views/tools/index.vue'),
+				meta: {
+					title: 'message.router.tools',
+					isLink: '',
+					isHide: false,
+					isKeepAlive: true,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin', 'common'],
+					icon: 'iconfont icon-gongju',
+				},
+			},
+			{
+				path: '/demo/link',
+				name: 'layoutLinkView',
+				component: () => import('/@/layout/routerView/link.vue'),
+				meta: {
+					title: 'message.router.layoutLinkView',
+					isLink: 'https://element-plus.gitee.io/#/zh-CN/component/installation',
+					isHide: false,
+					isKeepAlive: false,
+					isAffix: false,
+					isIframe: false,
+					roles: ['admin'],
+					icon: 'iconfont icon-caozuo-wailian',
+				},
+			},
+			{
+				path: '/demo/iframes',
+				name: 'layoutIfameView',
+				component: () => import('/@/layout/routerView/iframes.vue'),
+				meta: {
+					title: 'message.router.layoutIfameView',
+					isLink: 'https://nodejs.org/zh-cn/',
+					isHide: false,
+					isKeepAlive: false,
+					isAffix: false,
+					isIframe: true,
+					roles: ['admin'],
+					icon: 'iconfont icon-neiqianshujuchucun',
+				},
+			},
+		],
+	},
+]
+
+/**
+ * 定义静态路由
+ * @description 前端控制直接改 dynamicRoutes 中的路由,后端控制不需要修改,请求接口路由数据时,会覆盖 dynamicRoutes 第一个顶级 children 的内容(全屏,不包含 layout 中的路由出口)
+ * @returns 返回路由菜单数据
+ */
+export const staticRoutes: Array<RouteRecordRaw> = [
+	{
+		path: '/login',
+		name: 'login',
+		component: () => import('/@/views/login/index.vue'),
+		meta: {
+			title: '登录',
+		},
+	},
+	/**
+	 * 提示:写在这里的为全屏界面,不建议写在这里
+	 * 请写在 `dynamicRoutes` 路由数组中
+	 */
+	{
+		path: '/visualizingDemo1',
+		name: 'visualizingDemo1',
+		component: () => import('/@/views/visualizing/demo1.vue'),
+		meta: {
+			title: 'message.router.visualizingLinkDemo1',
+		},
+	},
+	{
+		path: '/visualizingDemo2',
+		name: 'visualizingDemo2',
+		component: () => import('/@/views/visualizing/demo2.vue'),
+		meta: {
+			title: 'message.router.visualizingLinkDemo2',
+		},
+	},
+	{
+		path: '/dbInit',
+		name: 'dbInit',
+		component: () => import('/@/views/dbInit/index.vue'),
+		meta: {
+			title: '系统初始化',
+		},
+	},
+];

+ 27 - 0
src/store/index.ts

@@ -0,0 +1,27 @@
+import { InjectionKey } from 'vue';
+import { createStore, useStore as baseUseStore, Store } from 'vuex';
+import { RootStateTypes } from '/@/store/interface/index';
+
+// Vite supports importing multiple modules from the file system using the special import.meta.glob function
+// see https://cn.vitejs.dev/guide/features.html#glob-import
+const modulesFiles = import.meta.globEager('./modules/*.ts');
+const pathList: string[] = [];
+
+for (const path in modulesFiles) {
+	pathList.push(path);
+}
+
+const modules = pathList.reduce((modules: { [x: string]: any }, modulePath: string) => {
+	const moduleName = modulePath.replace(/^\.\/modules\/(.*)\.\w+$/, '$1');
+	const value = modulesFiles[modulePath];
+	modules[moduleName] = value.default;
+	return modules;
+}, {});
+
+export const key: InjectionKey<Store<RootStateTypes>> = Symbol();
+
+export const store = createStore<RootStateTypes>({ modules });
+
+export function useStore() {
+	return baseUseStore(key);
+}

+ 98 - 0
src/store/interface/index.ts

@@ -0,0 +1,98 @@
+// 接口类型声明
+
+// 布局配置
+export interface ThemeConfigState {
+	themeConfig: {
+		isDrawer: boolean;
+		primary: string;
+		topBar: string;
+		topBarColor: string;
+		isTopBarColorGradual: boolean;
+		menuBar: string;
+		menuBarColor: string;
+		isMenuBarColorGradual: boolean;
+		columnsMenuBar: string;
+		columnsMenuBarColor: string;
+		isColumnsMenuBarColorGradual: boolean;
+		isCollapse: boolean;
+		isUniqueOpened: boolean;
+		isFixedHeader: boolean;
+		isFixedHeaderChange: boolean;
+		isClassicSplitMenu: boolean;
+		isLockScreen: boolean;
+		lockScreenTime: number;
+		isShowLogo: boolean;
+		isShowLogoChange: boolean;
+		isBreadcrumb: boolean;
+		isTagsview: boolean;
+		isBreadcrumbIcon: boolean;
+		isTagsviewIcon: boolean;
+		isCacheTagsView: boolean;
+		isSortableTagsView: boolean;
+		isShareTagsView: boolean;
+		isFooter: boolean;
+		isGrayscale: boolean;
+		isInvert: boolean;
+		isIsDark: boolean;
+		isWartermark: boolean;
+		wartermarkText: string;
+		tagsStyle: string;
+		animation: string;
+		columnsAsideStyle: string;
+		columnsAsideLayout: string;
+		layout: string;
+		isRequestRoutes: boolean;
+		globalTitle: string;
+		globalViceTitle: string;
+		globalI18n: string;
+		globalComponentSize: string;
+	};
+}
+
+// 路由列表
+export interface RoutesListState {
+	routesList: object[];
+	isColumnsMenuHover: Boolean;
+	isColumnsNavHover: Boolean;
+}
+
+// 路由缓存列表
+export interface KeepAliveNamesState {
+	keepAliveNames: string[];
+}
+
+// TagsView 路由列表
+export interface TagsViewRoutesState {
+	tagsViewRoutes: object[];
+	isTagsViewCurrenFull: Boolean;
+}
+
+// 用户信息
+export interface UserInfosState {
+	userInfos: {
+		id:number;
+		userName: string;
+		userNickname:string;
+		avatar: string;
+		roles: string[];
+		time: number;
+	};
+	permissions:string[]
+}
+
+
+
+// 后端返回原始路由(未处理时)
+export interface RequestOldRoutesState {
+	requestOldRoutes: object[];
+}
+
+// 主接口(顶级类型声明)
+export interface RootStateTypes {
+	themeConfig: ThemeConfigState;
+	routesList: RoutesListState;
+	keepAliveNames: KeepAliveNamesState;
+	tagsViewRoutes: TagsViewRoutesState;
+	userInfos: UserInfosState;
+	requestOldRoutes: RequestOldRoutesState;
+}

+ 23 - 0
src/store/modules/keepAliveNames.ts

@@ -0,0 +1,23 @@
+import { Module } from 'vuex';
+import { KeepAliveNamesState, RootStateTypes } from '/@/store/interface/index';
+
+const keepAliveNamesModule: Module<KeepAliveNamesState, RootStateTypes> = {
+	namespaced: true,
+	state: {
+		keepAliveNames: [],
+	},
+	mutations: {
+		// 设置路由缓存(name字段)
+		getCacheKeepAlive(state: any, data: Array<string>) {
+			state.keepAliveNames = data;
+		},
+	},
+	actions: {
+		// 设置路由缓存(name字段)
+		async setCacheKeepAlive({ commit }, data: Array<string>) {
+			commit('getCacheKeepAlive', data);
+		},
+	},
+};
+
+export default keepAliveNamesModule;

+ 23 - 0
src/store/modules/requestOldRoutes.ts

@@ -0,0 +1,23 @@
+import { Module } from 'vuex';
+import { RequestOldRoutesState, RootStateTypes } from '/@/store/interface/index';
+
+const requestOldRoutesModule: Module<RequestOldRoutesState, RootStateTypes> = {
+	namespaced: true,
+	state: {
+		requestOldRoutes: [],
+	},
+	mutations: {
+		// 后端控制路由
+		getBackEndControlRoutes(state: any, data: object) {
+			state.requestOldRoutes = data;
+		},
+	},
+	actions: {
+		// 后端控制路由
+		setBackEndControlRoutes({ commit }, routes: Array<string>) {
+			commit('getBackEndControlRoutes', routes);
+		},
+	},
+};
+
+export default requestOldRoutesModule;

+ 41 - 0
src/store/modules/routesList.ts

@@ -0,0 +1,41 @@
+import { Module } from 'vuex';
+import { RoutesListState, RootStateTypes } from '/@/store/interface/index';
+
+const routesListModule: Module<RoutesListState, RootStateTypes> = {
+	namespaced: true,
+	state: {
+		routesList: [],
+		isColumnsMenuHover: false,
+		isColumnsNavHover: false,
+	},
+	mutations: {
+		// 设置路由,菜单中使用到
+		getRoutesList(state: any, data: Array<object>) {
+			state.routesList = data;
+		},
+		// 设置分栏布局,鼠标是否移入移出(菜单)
+		getColumnsMenuHover(state: any, bool: Boolean) {
+			state.isColumnsMenuHover = bool;
+		},
+		// 设置分栏布局,鼠标是否移入移出(导航)
+		getColumnsNavHover(state: any, bool: Boolean) {
+			state.isColumnsNavHover = bool;
+		},
+	},
+	actions: {
+		// 设置路由,菜单中使用到
+		async setRoutesList({ commit }, data: any) {
+			commit('getRoutesList', data);
+		},
+		// 设置分栏布局,鼠标是否移入移出(菜单)
+		async setColumnsMenuHover({ commit }, bool: Boolean) {
+			commit('getColumnsMenuHover', bool);
+		},
+		// 设置分栏布局,鼠标是否移入移出(菜单)
+		async setColumnsNavHover({ commit }, bool: Boolean) {
+			commit('getColumnsNavHover', bool);
+		},
+	},
+};
+
+export default routesListModule;

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

@@ -0,0 +1,34 @@
+import { Module } from 'vuex';
+import { TagsViewRoutesState, RootStateTypes } from '/@/store/interface/index';
+import { Session } from '/@/utils/storage';
+
+const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
+	namespaced: true,
+	state: {
+		tagsViewRoutes: [],
+		isTagsViewCurrenFull: false,
+	},
+	mutations: {
+		// 设置 TagsView 路由
+		getTagsViewRoutes(state: any, data: Array<string>) {
+			state.tagsViewRoutes = data;
+		},
+		// 设置卡片全屏
+		getCurrenFullscreen(state: any, bool: boolean) {
+			Session.set('isTagsViewCurrenFull', bool);
+			state.isTagsViewCurrenFull = bool;
+		},
+	},
+	actions: {
+		// 设置 TagsView 路由
+		async setTagsViewRoutes({ commit }, data: Array<string>) {
+			commit('getTagsViewRoutes', data);
+		},
+		// 设置卡片全屏
+		setCurrenFullscreen({ commit }, bool: Boolean) {
+			commit('getCurrenFullscreen', bool);
+		},
+	},
+};
+
+export default tagsViewRoutesModule;

+ 153 - 0
src/store/modules/themeConfig.ts

@@ -0,0 +1,153 @@
+import { Module } from 'vuex';
+import { ThemeConfigState, RootStateTypes } from '/@/store/interface/index';
+
+/**
+ * 2020.05.28 by lyt 优化
+ * 修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效
+ * 哪个大佬有解决办法,欢迎pr,感谢💕!
+ */
+const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
+	namespaced: true,
+	state: {
+		themeConfig: {
+			// 是否开启布局配置抽屉
+			isDrawer: false,
+
+			/**
+			 * 全局主题
+			 */
+			// 默认 primary 主题颜色
+			primary: '#409eff',
+
+			/**
+			 * 菜单 / 顶栏
+			 * 注意:v1.0.17 版本去除设置布局切换,重置主题样式(initSetLayoutChange),
+			 * 切换布局需手动设置样式,设置的样式自动同步各布局,
+			 * 代码位置:/@/layout/navBars/breadcrumb/setings.vue
+			 */
+			// 默认顶栏导航背景颜色
+			topBar: '#ffffff',
+			// 默认顶栏导航字体颜色
+			topBarColor: '#606266',
+			// 是否开启顶栏背景颜色渐变
+			isTopBarColorGradual: false,
+			// 默认菜单导航背景颜色
+			menuBar: '#354E67',
+			// 默认菜单导航字体颜色
+			menuBarColor: '#eaeaea',
+			// 是否开启菜单背景颜色渐变
+			isMenuBarColorGradual: false,
+			// 默认分栏菜单背景颜色
+			columnsMenuBar: '#545c64',
+			// 默认分栏菜单字体颜色
+			columnsMenuBarColor: '#e6e6e6',
+			// 是否开启分栏菜单背景颜色渐变
+			isColumnsMenuBarColorGradual: false,
+
+			/**
+			 * 界面设置
+			 */
+			// 是否开启菜单水平折叠效果
+			isCollapse: false,
+			// 是否开启菜单手风琴效果
+			isUniqueOpened: false,
+			// 是否开启固定 Header
+			isFixedHeader: false,
+			// 初始化变量,用于更新菜单 el-scrollbar 的高度,请勿删除
+			isFixedHeaderChange: false,
+			// 是否开启经典布局分割菜单(仅经典布局生效)
+			isClassicSplitMenu: false,
+			// 是否开启自动锁屏
+			isLockScreen: false,
+			// 开启自动锁屏倒计时(s/秒)
+			lockScreenTime: 30,
+
+			/**
+			 * 界面显示
+			 */
+			// 是否开启侧边栏 Logo
+			isShowLogo: true,
+			// 初始化变量,用于 el-scrollbar 的高度更新,请勿删除
+			isShowLogoChange: false,
+			// 是否开启 Breadcrumb,强制经典、横向布局不显示
+			isBreadcrumb: true,
+			// 是否开启 Tagsview
+			isTagsview: true,
+			// 是否开启 Breadcrumb 图标
+			isBreadcrumbIcon: false,
+			// 是否开启 Tagsview 图标
+			isTagsviewIcon: false,
+			// 是否开启 TagsView 缓存
+			isCacheTagsView: false,
+			// 是否开启 TagsView 拖拽
+			isSortableTagsView: true,
+			// 是否开启 TagsView 共用
+			isShareTagsView: false,
+			// 是否开启 Footer 底部版权信息
+			isFooter: true,
+			// 是否开启灰色模式
+			isGrayscale: false,
+			// 是否开启色弱模式
+			isInvert: false,
+			// 是否开启深色模式
+			isIsDark: false,
+			// 是否开启水印
+			isWartermark: false,
+			// 水印文案
+			wartermarkText: 'GFast-v3.0',
+
+			/**
+			 * 其它设置
+			 */
+			// Tagsview 风格:可选值"<tags-style-one|tags-style-four|tags-style-five>",默认 tags-style-five
+			// 定义的值与 `/src/layout/navBars/tagsView/tagsView.vue` 中的 class 同名
+			tagsStyle: 'tags-style-five',
+			// 主页面切换动画:可选值"<slide-right|slide-left|opacitys>",默认 slide-right
+			animation: 'slide-right',
+			// 分栏高亮风格:可选值"<columns-round|columns-card>",默认 columns-round
+			columnsAsideStyle: 'columns-round',
+			// 分栏布局风格:可选值"<columns-horizontal|columns-vertical>",默认 columns-horizontal
+			columnsAsideLayout: 'columns-vertical',
+
+			/**
+			 * 布局切换
+			 * 注意:为了演示,切换布局时,颜色会被还原成默认,代码位置:/@/layout/navBars/breadcrumb/setings.vue
+			 * 中的 `initSetLayoutChange(设置布局切换,重置主题样式)` 方法
+			 */
+			// 布局切换:可选值"<defaults|classic|transverse|columns>",默认 defaults
+			layout: 'defaults',
+
+			/**
+			 * 后端控制路由
+			 */
+			// 是否开启后端控制路由
+			isRequestRoutes: true,
+
+			/**
+			 * 全局网站标题 / 副标题
+			 */
+			// 网站主标题(菜单导航、浏览器当前网页标题)
+			globalTitle: 'gfast3.0后台管理系统',
+			// 网站副标题(登录页顶部文字)
+			globalViceTitle: 'gfast3.0后台管理系统',
+			// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
+			globalI18n: 'zh-cn',
+			// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'
+			globalComponentSize: 'large',
+		},
+	},
+	mutations: {
+		// 设置布局配置
+		getThemeConfig(state: any, data: object) {
+			state.themeConfig = data;
+		},
+	},
+	actions: {
+		// 设置布局配置
+		setThemeConfig({ commit }, data: object) {
+			commit('getThemeConfig', data);
+		},
+	},
+};
+
+export default themeConfigModule;

+ 47 - 0
src/store/modules/userInfos.ts

@@ -0,0 +1,47 @@
+import { Module } from 'vuex';
+import { Session } from '/@/utils/storage';
+import {UserInfosState, RootStateTypes} from '/@/store/interface/index';
+
+const userInfosModule: Module<UserInfosState, RootStateTypes> = {
+	namespaced: true,
+	state: {
+		userInfos: {
+			id:0,
+			userName: '',
+			userNickname:'',
+			avatar: '',
+			roles: [],
+			time: 0
+		},
+		permissions:[],
+	},
+	mutations: {
+		// 设置用户信息
+		getUserInfos(state, data: any) {
+			state.userInfos = data;
+		},
+		// 设置按钮权限
+		getPermissions(state, data: any) {
+			state.permissions = data;
+		},
+	},
+	actions: {
+		// 设置用户信息
+		async setUserInfos({ commit }, data: UserInfosState) {
+			if (data) {
+				commit('getUserInfos', data);
+			} else {
+				if (Session.get('userInfo')) commit('getUserInfos', Session.get('userInfo'));
+			}
+		},
+		async setPermissions({ commit }, data: string[]) {
+			if (data) {
+				commit('getPermissions', data);
+			} else {
+				if (Session.get('permissions')) commit('getPermissions', Session.get('permissions'));
+			}
+		},
+	},
+};
+
+export default userInfosModule;

+ 283 - 0
src/theme/app.scss

@@ -0,0 +1,283 @@
+/* 初始化样式
+------------------------------- */
+* {
+	margin: 0;
+	padding: 0;
+	box-sizing: border-box;
+	outline: none !important;
+}
+
+:root {
+	--next-bg-main-color: #f8f8f8;
+	--next-bg-color: #f5f5ff;
+	--next-border-color-light: #f1f2f3;
+	--next-color-primary-lighter: #ecf5ff;
+	--next-color-dark-hover: #0000001a;
+	--next-color-menu-hover: rgba(0, 0, 0, 0.1);
+	--next-color-user-hover: rgba(0, 0, 0, 0.04);
+	--next-color-seting-main: #e9eef3;
+	--next-color-seting-aside: #d3dce6;
+	--next-color-seting-header: #b3c0d1;
+}
+
+html,
+body,
+#app {
+	margin: 0;
+	padding: 0;
+	width: 100%;
+	height: 100%;
+	font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
+	font-weight: 400;
+	-webkit-font-smoothing: antialiased;
+	-webkit-tap-highlight-color: transparent;
+	background-color: var(--next-bg-main-color);
+	font-size: 14px;
+	overflow: hidden;
+	position: relative;
+}
+
+/* 主布局样式
+------------------------------- */
+.layout-container {
+	width: 100%;
+	height: 100%;
+	.layout-aside {
+		background: var(--next-bg-menuBar);
+		box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
+		height: inherit;
+		position: relative;
+		z-index: 1;
+		display: flex;
+		flex-direction: column;
+		overflow-x: hidden !important;
+		.el-scrollbar__view {
+			overflow: hidden;
+		}
+	}
+	.layout-header {
+		padding: 0 !important;
+	}
+	.layout-main {
+		padding: 0 !important;
+		overflow: hidden;
+		width: 100%;
+		background-color: var(--next-bg-main-color);
+	}
+	.el-scrollbar {
+		width: 100%;
+	}
+	// 此字段多次用到,建议不删除,如需修改,请重写覆盖样式
+	.layout-view-bg-white {
+		background: var(--el-color-white);
+		width: 100%;
+		height: 100%;
+		border-radius: 4px;
+		border: 1px solid var(--el-border-color-light, #ebeef5);
+	}
+	.layout-el-aside-br-color {
+		border-right: 1px solid var(--el-border-color-light, #ebeef5);
+	}
+	// pc端左侧导航样式
+	.layout-aside-pc-220 {
+		width: 220px !important;
+		transition: width 0.3s ease;
+	}
+	.layout-aside-pc-64 {
+		width: 64px !important;
+		transition: width 0.3s ease;
+	}
+	.layout-aside-pc-1 {
+		width: 1px !important;
+		transition: width 0.3s ease;
+	}
+	// 手机端左侧导航样式
+	.layout-aside-mobile {
+		position: fixed;
+		top: 0;
+		left: -220px;
+		width: 220px;
+		z-index: 9999999;
+	}
+	.layout-aside-mobile-close {
+		left: -220px;
+		transition: all 0.3s cubic-bezier(0.39, 0.58, 0.57, 1);
+	}
+	.layout-aside-mobile-open {
+		left: 0;
+		transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
+	}
+	.layout-aside-mobile-mode {
+		position: fixed;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		height: 100%;
+		background-color: rgba(0, 0, 0, 0.5);
+		z-index: 9999998;
+		animation: error-img 0.3s;
+	}
+	.layout-scrollbar {
+		@extend .el-scrollbar;
+		padding: 15px;
+	}
+	.layout-mian-height-50 {
+		height: calc(100vh - 50px);
+	}
+	.layout-columns-warp {
+		flex: 1;
+		display: flex;
+		overflow: hidden;
+	}
+	.layout-hide {
+		display: none;
+	}
+}
+
+/* element plus 全局样式
+------------------------------- */
+.layout-breadcrumb-seting {
+	.el-divider {
+		background-color: rgb(230, 230, 230);
+	}
+}
+
+/* nprogress 进度条跟随主题颜色
+------------------------------- */
+#nprogress {
+	.bar {
+		background: var(--el-color-primary) !important;
+		z-index: 9999999 !important;
+	}
+}
+
+/* flex 弹性布局
+------------------------------- */
+.flex {
+	display: flex;
+}
+.flex-auto {
+	flex: 1;
+	overflow: hidden;
+}
+.flex-center {
+	@extend .flex;
+	flex-direction: column;
+	width: 100%;
+	overflow: hidden;
+}
+.flex-margin {
+	margin: auto;
+}
+.flex-warp {
+	display: flex;
+	flex-wrap: wrap;
+	align-content: flex-start;
+	margin: 0 -5px;
+	.flex-warp-item {
+		padding: 5px;
+		.flex-warp-item-box {
+			width: 100%;
+			height: 100%;
+		}
+	}
+}
+
+/* cursor 鼠标形状
+------------------------------- */
+// 默认
+.cursor-default {
+	cursor: default !important;
+}
+// 帮助
+.cursor-help {
+	cursor: help !important;
+}
+// 手指
+.cursor-pointer {
+	cursor: pointer !important;
+}
+// 移动
+.cursor-move {
+	cursor: move !important;
+}
+
+/* 宽高 100%
+------------------------------- */
+.w100 {
+	width: 100% !important;
+}
+.h100 {
+	height: 100% !important;
+}
+.vh100 {
+	height: 100vh !important;
+}
+.max100vh {
+	max-height: 100vh !important;
+}
+.min100vh {
+	min-height: 100vh !important;
+}
+
+/* 颜色值
+------------------------------- */
+.color-primary {
+	color: var(--el-color-primary);
+}
+.color-success {
+	color: var(--el-color-success);
+}
+.color-warning {
+	color: var(--el-color-warning);
+}
+.color-danger {
+	color: var(--el-color-danger);
+}
+.color-info {
+	color: var(--el-color-info);
+}
+
+/* 字体大小全局样式
+------------------------------- */
+@for $i from 10 through 32 {
+	.font#{$i} {
+		font-size: #{$i}px !important;
+	}
+}
+
+/* 外边距、内边距全局样式
+------------------------------- */
+@for $i from 1 through 35 {
+	.mt#{$i} {
+		margin-top: #{$i}px !important;
+	}
+	.mr#{$i} {
+		margin-right: #{$i}px !important;
+	}
+	.mb#{$i} {
+		margin-bottom: #{$i}px !important;
+	}
+	.ml#{$i} {
+		margin-left: #{$i}px !important;
+	}
+	.pt#{$i} {
+		padding-top: #{$i}px !important;
+	}
+	.pr#{$i} {
+		padding-right: #{$i}px !important;
+	}
+	.pb#{$i} {
+		padding-bottom: #{$i}px !important;
+	}
+	.pl#{$i} {
+		padding-left: #{$i}px !important;
+	}
+}
+
+.link-type, .link-type:focus {
+	color: #337ab7;
+	cursor: pointer;
+	text-decoration: none;
+}

+ 94 - 0
src/theme/common/transition.scss

@@ -0,0 +1,94 @@
+/* 页面切换动画
+------------------------------- */
+.slide-right-enter-active,
+.slide-right-leave-active,
+.slide-left-enter-active,
+.slide-left-leave-active {
+	will-change: transform;
+	transition: all 0.3s ease;
+}
+// slide-right
+.slide-right-enter-from {
+	opacity: 0;
+	transform: translateX(-20px);
+}
+.slide-right-leave-to {
+	opacity: 0;
+	transform: translateX(20px);
+}
+// slide-left
+.slide-left-enter-from {
+	@extend .slide-right-leave-to;
+}
+.slide-left-leave-to {
+	@extend .slide-right-enter-from;
+}
+// opacitys
+.opacitys-enter-active,
+.opacitys-leave-active {
+	will-change: transform;
+	transition: all 0.3s ease;
+}
+.opacitys-enter-from,
+.opacitys-leave-to {
+	opacity: 0;
+}
+
+/* Breadcrumb 面包屑过渡动画
+------------------------------- */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+	transition: all 0.5s ease;
+}
+.breadcrumb-enter-from,
+.breadcrumb-leave-active {
+	opacity: 0;
+	transform: translateX(20px);
+}
+.breadcrumb-leave-active {
+	position: absolute;
+	z-index: -1;
+}
+
+/* logo 过渡动画
+------------------------------- */
+@keyframes logoAnimation {
+	0% {
+		transform: scale(0);
+	}
+	80% {
+		transform: scale(1.2);
+	}
+	100% {
+		transform: scale(1);
+	}
+}
+
+/* 404、401 过渡动画
+------------------------------- */
+@keyframes error-num {
+	0% {
+		transform: translateY(60px);
+		opacity: 0;
+	}
+	100% {
+		transform: translateY(0);
+		opacity: 1;
+	}
+}
+@keyframes error-img {
+	0% {
+		opacity: 0;
+	}
+	100% {
+		opacity: 1;
+	}
+}
+@keyframes error-img-two {
+	0% {
+		opacity: 1;
+	}
+	100% {
+		opacity: 0;
+	}
+}

+ 219 - 0
src/theme/dark.scss

@@ -0,0 +1,219 @@
+/* 深色模式样式
+------------------------------- */
+[data-theme='dark'] {
+	// 变量(自定义时,只需修改这里的值)
+	--next-bg-main: #1f1f1f;
+	--next-color-white: #ffffff;
+	--next-color-disabled: #191919;
+	--next-color-bar: #dadada;
+	--next-color-primary: #303030;
+	--next-border-color: #424242;
+	--next-border-black: #333333;
+	--next-border-columns: #2a2a2a;
+	--next-color-seting: #505050;
+	--next-text-color-regular: #9b9da1;
+	--next-text-color-placeholder: #7a7a7a;
+	--next-color-hover: #3c3c3c;
+	--next-color-hover-rgba: rgba(0, 0, 0, 0.3);
+
+	// root
+	--next-bg-main-color: var(--next-bg-main) !important;
+	--next-bg-topBar: var(--next-color-disabled) !important;
+	--next-bg-topBarColor: var(--next-color-bar) !important;
+	--next-bg-menuBar: var(--next-color-disabled) !important;
+	--next-bg-menuBarColor: var(--next-color-bar) !important;
+	--next-bg-columnsMenuBar: var(--next-color-disabled) !important;
+	--next-bg-columnsMenuBarColor: var(--next-color-bar) !important;
+	--next-border-color-light: var(--next-border-black) !important;
+	--next-color-primary-lighter: var(--next-color-primary) !important;
+	--next-bg-color: var(--next-color-primary) !important;
+	--next-color-dark-hover: var(--next-color-hover) !important;
+	--next-color-menu-hover: var(--next-color-hover-rgba) !important;
+	--next-color-user-hover: var(--next-color-hover-rgba) !important;
+	--next-color-seting-main: var(--next-color-seting) !important;
+	--next-color-seting-aside: var(--next-color-hover) !important;
+	--next-color-seting-header: var(--next-color-primary) !important;
+
+	// element plus
+	--el-color-white: var(--next-color-disabled) !important;
+	--el-text-color-primary: var(--next-color-bar) !important;
+	--el-border-color-base: var(--next-border-black) !important;
+	--el-border-color-light: var(--next-border-black) !important;
+	--el-text-color-regular: var(--next-text-color-regular) !important;
+	--el-bg-color: var(--next-color-hover-rgba) !important;
+	--el-color-success-lighter: var(--next-color-primary) !important;
+	--el-color-warning-lighter: var(--next-color-primary) !important;
+	--el-color-danger-lighter: var(--next-color-primary) !important;
+	--el-color-primary-lighter: var(--next-color-primary) !important;
+	--el-color-primary-light-9: var(--next-color-hover) !important;
+	--el-text-color-disabled-base: var(--el-color-primary) !important;
+	--el-border-color-lighter: var(--next-border-black) !important;
+	--el-text-color-placeholder: var(--next-text-color-placeholder) !important;
+	--el-disabled-bg-color: var(--next-color-disabled) !important;
+	--el-fill-base: var(--next-color-white) !important;
+
+	// button
+	.el-button {
+		&:hover {
+			border-color: var(--next-border-color) !important;
+		}
+	}
+	.el-button--primary,
+	.el-button--info,
+	.el-button--danger,
+	.el-button--success,
+	.el-button--warning {
+		--el-button-text-color: var(--next-color-white) !important;
+		--el-button-hover-text-color: var(--next-color-white) !important;
+		--el-button-disabled-text-color: var(--next-color-white) !important;
+		&:hover {
+			border-color: var(--el-button-hover-border-color, var(--el-button-hover-bg-color)) !important;
+		}
+	}
+
+	// drawer
+	.el-divider__text {
+		background-color: var(--el-color-white) !important;
+	}
+	.el-drawer {
+		border-left: 1px solid var(--next-border-color-light) !important;
+	}
+
+	// tabs
+	.el-tabs--border-card {
+		background-color: var(--el-color-white) !important;
+	}
+	.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
+		background: var(--next-color-primary-lighter);
+	}
+
+	// alert / notice-bar
+	.home-card-item {
+		border: 1px solid var(--next-border-color-light) !important;
+	}
+	.el-alert,
+	.notice-bar {
+		border: 1px solid var(--next-border-color) !important;
+		background-color: var(--next-color-disabled) !important;
+	}
+
+	// menu
+	.layout-aside {
+		border-right: 1px solid var(--next-border-color-light) !important;
+	}
+
+	// colorPicker
+	.el-color-picker__mask {
+		background: unset !important;
+	}
+	.el-color-picker__trigger {
+		border: 1px solid var(--next-border-color-light) !important;
+	}
+
+	// popper / dropdown
+	.el-popper {
+		border: 1px solid var(--next-border-color) !important;
+		color: var(--el-text-color-primary) !important;
+		.el-popper__arrow:before {
+			background: var(--el-color-white) !important;
+			border: 1px solid var(--next-border-color);
+		}
+		a {
+			color: var(--el-text-color-primary) !important;
+		}
+	}
+	.el-popper,
+	.el-dropdown-menu {
+		background: var(--el-color-white) !important;
+	}
+	.el-dropdown-menu__item:hover:not(.is-disabled) {
+		background: var(--el-bg-color) !important;
+	}
+	.el-dropdown-menu__item.is-disabled {
+		font-weight: 700 !important;
+	}
+
+	// input
+	.el-input-group__append,
+	.el-input-group__prepend {
+		border: var(--el-input-border) !important;
+		border-right: none !important;
+		background: var(--next-color-disabled) !important;
+		border-left: 0 !important;
+	}
+	.el-input-number__decrease,
+	.el-input-number__increase {
+		background: var(--next-color-disabled) !important;
+	}
+
+	// tag
+	.el-select .el-select__tags .el-tag {
+		background-color: var(--next-bg-color) !important;
+	}
+
+	// pagination
+	.el-pagination.is-background .el-pager li:not(.disabled).active {
+		color: var(--next-color-white) !important;
+	}
+	.el-pagination.is-background .btn-next,
+	.el-pagination.is-background .btn-prev,
+	.el-pagination.is-background .el-pager li {
+		background-color: var(--next-bg-color);
+	}
+
+	// radio
+	.el-radio-button:not(.is-active) .el-radio-button__inner {
+		border: 1px solid var(--next-border-color-light) !important;
+		border-left: 0 !important;
+	}
+	.el-radio-button.is-active .el-radio-button__inner {
+		color: var(--next-color-white) !important;
+	}
+
+	// countup
+	.countup-card-item-flex {
+		color: var(--el-text-color-primary) !important;
+	}
+
+	// editor
+	.editor-container {
+		.w-e-toolbar {
+			background: var(--el-color-white) !important;
+			border: 1px solid var(--next-border-color-light) !important;
+			.w-e-menu:hover {
+				background: var(--next-color-user-hover) !important;
+				i {
+					color: var(--el-text-color-primary) !important;
+				}
+			}
+		}
+		.w-e-text-container {
+			border: 1px solid var(--next-border-color-light) !important;
+			border-top: none !important;
+			.w-e-text {
+				background: var(--el-color-white) !important;
+			}
+		}
+	}
+
+	// date-picker
+	.el-picker-panel {
+		background: var(--el-color-white) !important;
+	}
+
+	// dialog
+	.el-dialog {
+		border: 1px solid var(--el-border-color-lighter);
+		.el-dialog__header {
+			color: var(--el-text-color-primary) !important;
+		}
+	}
+
+	// columns
+	.layout-columns-aside ul .layout-columns-active {
+		color: var(--next-color-white) !important;
+	}
+	.layout-columns-aside {
+		border-right: 1px solid var(--next-border-columns);
+	}
+}

+ 224 - 0
src/theme/element.scss

@@ -0,0 +1,224 @@
+@import 'mixins/index.scss';
+
+/* Button 按钮
+------------------------------- */
+// 第三方字体图标大小
+.el-button i.el-icon,
+.el-button i.iconfont,
+.el-button i.fa,
+.el-button--default i.iconfont,
+.el-button--default i.fa {
+	font-size: 14px !important;
+	margin-right: 5px;
+}
+.el-button--small i.iconfont,
+.el-button--small i.fa {
+	font-size: 12px !important;
+	margin-right: 5px;
+}
+
+/* Input 输入框、InputNumber 计数器
+------------------------------- */
+// 菜单搜索
+.el-autocomplete-suggestion__wrap {
+	max-height: 280px !important;
+}
+
+/* Alert 警告
+------------------------------- */
+.el-alert {
+	border: 1px solid;
+}
+.el-alert__title {
+	word-break: break-all;
+}
+
+/* Message 消息提示
+------------------------------- */
+.el-message {
+	min-width: unset !important;
+	padding: 15px !important;
+	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
+}
+
+/* NavMenu 导航菜单
+------------------------------- */
+// 鼠标 hover 时颜色
+.el-menu-hover-bg-color {
+	background-color: var(--next-color-menu-hover) !important;
+}
+// 默认样式修改
+.el-menu {
+	border-right: none !important;
+	width: 220px;
+}
+// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
+.el-menu--collapse {
+	width: 64px !important;
+}
+.el-menu-item,
+.el-sub-menu__title {
+	color: var(--next-bg-menuBarColor);
+}
+.el-menu.el-menu--horizontal {
+	border-bottom: none !important;
+}
+.el-menu-item {
+	height: 56px !important;
+	line-height: 56px !important;
+}
+// 外部链接时
+.el-menu-item a,
+.el-menu-item a:hover,
+.el-menu-item i,
+.el-sub-menu__title i {
+	color: inherit;
+	text-decoration: none;
+}
+// 第三方图标字体间距/大小设置
+.el-menu-item .iconfont,
+.el-sub-menu .iconfont,
+.el-menu-item .fa,
+.el-sub-menu .fa {
+	@include generalIcon;
+}
+// 高亮时
+.el-menu-item.is-active,
+.el-sub-menu.is-active .el-sub-menu__title {
+	@extend .el-menu-hover-bg-color;
+	i,
+	span {
+		color: var(--el-menu-active-color) !important;
+	}
+}
+.el-sub-menu.is-active.is-opened {
+	.el-sub-menu__title {
+		background-color: unset !important;
+	}
+	i,
+	span {
+		color: unset !important;
+	}
+}
+// 鼠标 hover 时
+.el-menu-item:hover,
+.el-sub-menu__title:hover {
+	@extend .el-menu-hover-bg-color;
+}
+// 菜单收起时且时 a 链接
+.el-popper.is-dark a {
+	color: var(--el-color-white) !important;
+	text-decoration: none;
+}
+// 菜单收起时鼠标经过背景颜色/字体颜色
+.el-popper.is-light {
+	.el-menu--vertical {
+		.el-menu {
+			background: var(--next-bg-menuBar);
+		}
+	}
+	.el-menu--horizontal {
+		background: var(--next-bg-topBar);
+		.el-menu,
+		.el-menu-item,
+		.el-sub-menu__title {
+			color: var(--next-bg-topBarColor);
+			background: var(--next-bg-topBar);
+		}
+	}
+}
+
+/* Tabs 标签页
+------------------------------- */
+.el-tabs__nav-wrap::after {
+	height: 1px !important;
+}
+
+/* Dropdown 下拉菜单
+------------------------------- */
+.el-dropdown-menu {
+	list-style: none !important; /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
+}
+.el-dropdown-menu .el-dropdown-menu__item {
+	white-space: nowrap;
+}
+
+/* Steps 步骤条
+------------------------------- */
+.el-step__icon-inner {
+	font-size: 30px !important;
+	font-weight: 400 !important;
+}
+.el-step__title {
+	font-size: 14px;
+}
+
+/* Dialog 对话框
+------------------------------- */
+.el-overlay {
+	overflow: hidden;
+	.el-overlay-dialog {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		position: unset !important;
+		width: 100%;
+		height: 100%;
+		.el-dialog {
+			margin: 0 auto !important;
+			position: absolute;
+			.el-dialog__body {
+				padding: 20px !important;
+			}
+		}
+	}
+}
+.el-dialog__body {
+	max-height: calc(90vh - 111px) !important;
+	overflow-y: auto;
+	overflow-x: hidden;
+}
+
+/* Card 卡片
+------------------------------- */
+.el-card__header {
+	padding: 15px 20px;
+}
+
+/* scrollbar
+------------------------------- */
+.el-scrollbar__bar {
+	z-index: 4;
+}
+.el-scrollbar__wrap {
+	max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
+}
+.el-select-dropdown .el-scrollbar__wrap {
+	overflow-x: scroll !important;
+}
+.el-select-dropdown__wrap {
+	max-height: 274px !important; /*修复Select 选择器高度问题*/
+}
+.el-cascader-menu__wrap.el-scrollbar__wrap {
+	height: 204px !important; /*修复Cascader 级联选择器高度问题*/
+}
+
+/* Drawer 抽屉
+------------------------------- */
+.el-drawer {
+	--el-drawer-padding-primary: unset !important;
+	.el-drawer__header {
+		padding: 0 15px !important;
+		height: 50px;
+		display: flex;
+		align-items: center;
+		margin-bottom: 0 !important;
+		border-bottom: 1px solid var(--el-border-color-base);
+		color: var(--el-text-color-primary);
+	}
+	.el-drawer__body {
+		width: 100%;
+		height: 100%;
+		overflow: auto;
+	}
+}

+ 70 - 0
src/theme/iconSelector.scss

@@ -0,0 +1,70 @@
+/* Popover 弹出框(图标选择器)
+------------------------------- */
+.icon-selector-popper {
+	padding: 0 !important;
+	.icon-selector-warp {
+		height: 260px;
+		overflow: hidden;
+		.icon-selector-warp-title {
+			height: 40px;
+			line-height: 40px;
+			padding: 0 15px;
+			.icon-selector-warp-title-tab {
+				span {
+					cursor: pointer;
+					&:hover {
+						color: var(--el-color-primary);
+						text-decoration: underline;
+					}
+				}
+				.span-active {
+					color: var(--el-color-primary);
+					text-decoration: underline;
+				}
+			}
+		}
+		.icon-selector-warp-row {
+			height: 230px;
+			overflow: hidden;
+			border-top: var(--el-border-base);
+			.el-row {
+				padding: 15px;
+			}
+			.el-scrollbar__bar.is-horizontal {
+				display: none;
+			}
+			.icon-selector-warp-item {
+				display: flex;
+				border: var(--el-border-base);
+				padding: 5px;
+				border-radius: 5px;
+				margin-bottom: 10px;
+				.icon-selector-warp-item-value {
+					i {
+						font-size: 20px;
+						color: var(--el-text-color-regular);
+					}
+				}
+				&:hover {
+					cursor: pointer;
+					background-color: var(--el-color-primary-light-9);
+					border: 1px solid var(--el-color-primary-light-6);
+					.icon-selector-warp-item-value {
+						i {
+							color: var(--el-color-primary);
+						}
+					}
+				}
+			}
+			.icon-selector-active {
+				background-color: var(--el-color-primary-light-9);
+				border: 1px solid var(--el-color-primary-light-6);
+				.icon-selector-warp-item-value {
+					i {
+						color: var(--el-color-primary);
+					}
+				}
+			}
+		}
+	}
+}

+ 8 - 0
src/theme/index.scss

@@ -0,0 +1,8 @@
+@import './app.scss';
+@import 'common/transition.scss';
+@import './other.scss';
+@import './element.scss';
+@import './iconSelector.scss';
+@import './media/media.scss';
+@import './waves.scss';
+@import './dark.scss';

Some files were not shown because too many files changed in this diff