兰宝车间质量管理系统-前端
疯狂的狮子Li
2023-04-03 1595cb282aab5399862fac6406b5de550863e3b6
update 调整代码格式
已添加7个文件
已删除7个文件
已修改181个文件
24473 ■■■■ 文件已修改
.eslintrc-auto-import.json 558 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.js 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.prettierrc.cjs 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/auto-import.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/components.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/compression.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/icons.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/index.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/svg-icon.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Vite/plugins/unocss.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
commitlint.config.js 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
html/ie.html 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 380 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 156 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/animate.ts 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/demo/demo.ts 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/demo/tree.ts 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/demo/types.ts 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login.ts 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/menu.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/cache/index.ts 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/cache/types.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/loginInfo/index.ts 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/loginInfo/types.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/online/index.ts 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/online/types.ts 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/operlog/index.ts 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/operlog/types.ts 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/config/index.ts 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/config/types.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dept/index.ts 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dept/types.ts 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dict/data/index.ts 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dict/data/types.ts 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dict/type/index.ts 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dict/type/types.ts 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/menu/index.ts 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/menu/types.ts 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/notice/index.ts 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/notice/types.ts 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/oss/index.ts 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/oss/types.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/ossConfig/index.ts 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/ossConfig/types.ts 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post/index.ts 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post/types.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/role/index.ts 132 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/role/types.ts 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/tenant/index.ts 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/tenant/types.ts 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/tenantPackage/index.ts 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/tenantPackage/types.ts 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user/index.ts 154 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user/types.ts 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/tool/gen/index.ts 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/tool/gen/types.ts 304 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/types.ts 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/btn.scss 124 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/mixin.scss 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 244 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 384 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/transition.scss 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Breadcrumb/index.vue 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DictTag/index.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Editor/index.vue 256 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileUpload/index.vue 286 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Hamburger/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/HeaderSearch/index.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/index.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/requireIcons.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImagePreview/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImageUpload/index.vue 298 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Pagination/index.vue 164 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ParentView/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RightToolbar/index.vue 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RuoYiDoc/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RuoYiGit/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Screenfull/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SizeSelect/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SvgIcon/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TopNav/index.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TreeSelect/index.vue 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/iFrame/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/common/copyText.ts 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/index.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/permission/index.ts 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/MenuTypeEnum.ts 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/RespEnum.ts 176 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/SettingTypeEnum.ts 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/layout/LayoutEnum.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/en.ts 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/index.ts 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/zh-cn.ts 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/IframeToggle/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/InnerLink/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Settings/index.vue 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Link.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 160 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/ScrollPane.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 310 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.ts 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/auth.ts 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/cache.ts 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/download.ts 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/index.ts 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/modal.ts 152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/svgicon.ts 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tab.ts 118 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 276 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.ts 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/app.ts 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/dict.ts 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/permission.ts 230 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.ts 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/tagsView.ts 366 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/auto-imports.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/axios.d.ts 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/components.d.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/env.d.ts 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/global.d.ts 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/module.d.ts 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/router.d.ts 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/setting.d.ts 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.ts 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dynamicTitle.ts 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/errorCode.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/i18n.ts 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/index.ts 338 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jsencrypt.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/permission.ts 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 248 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/ruoyi.ts 370 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/scroll-to.ts 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/theme.ts 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.ts 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/demo/index.vue 522 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/tree/index.vue 430 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/401.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/404.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 186 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 228 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/admin/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/cache/index.vue 332 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 280 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 461 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/xxljob/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/redirect/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register.vue 228 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 282 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 438 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 444 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 384 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 684 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 369 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/config.vue 541 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/index.vue 498 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 346 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 726 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenant/index.vue 514 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenantPackage/index.vue 436 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/authRole.vue 172 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 912 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/index.vue 134 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/resetPwd.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userAvatar.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userInfo.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/build/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/basicInfoForm.vue 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/editTable.vue 308 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/genInfoForm.vue 436 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 338 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 208 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/auto-import.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/components.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/compression.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/icons.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/index.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/svg-icon.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/unocss.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc-auto-import.json
@@ -1,281 +1,281 @@
{
    "globals": {
        "useRouter": true,
        "useRoute": true,
        "EffectScope": true,
        "ElTable": true,
        "ElSelect": true,
        "ElUpload": true,
        "ElForm": true,
        "ElTree": true,
        "ElMessage": true,
        "ElMessageBox": true,
        "asyncComputed": true,
        "autoResetRef": true,
        "computed": true,
        "computedAsync": true,
        "computedEager": true,
        "computedInject": true,
        "computedWithControl": true,
        "controlledComputed": true,
        "controlledRef": true,
        "createApp": true,
        "createEventHook": true,
        "createGlobalState": true,
        "createInjectionState": true,
        "createReactiveFn": true,
        "createSharedComposable": true,
        "createUnrefFn": true,
        "customRef": true,
        "debouncedRef": true,
        "debouncedWatch": true,
        "defineAsyncComponent": true,
        "defineComponent": true,
        "eagerComputed": true,
        "effectScope": true,
        "extendRef": true,
        "getCurrentInstance": true,
        "getCurrentScope": true,
        "h": true,
        "ignorableWatch": true,
        "inject": true,
        "isDefined": true,
        "isProxy": true,
        "isReactive": true,
        "isReadonly": true,
        "isRef": true,
        "makeDestructurable": true,
        "markRaw": true,
        "nextTick": true,
        "onActivated": true,
        "onBeforeMount": true,
        "onBeforeUnmount": true,
        "onBeforeUpdate": true,
        "onClickOutside": true,
        "onDeactivated": true,
        "onErrorCaptured": true,
        "onKeyStroke": true,
        "onLongPress": true,
        "onMounted": true,
        "onRenderTracked": true,
        "onRenderTriggered": true,
        "onScopeDispose": true,
        "onServerPrefetch": true,
        "onStartTyping": true,
        "onUnmounted": true,
        "onUpdated": true,
        "pausableWatch": true,
        "provide": true,
        "reactify": true,
        "reactifyObject": true,
        "reactive": true,
        "reactiveComputed": true,
        "reactiveOmit": true,
        "reactivePick": true,
        "readonly": true,
        "ref": true,
        "refAutoReset": true,
        "refDebounced": true,
        "refDefault": true,
        "refThrottled": true,
        "refWithControl": true,
        "resolveComponent": true,
        "resolveDirective": true,
        "resolveRef": true,
        "resolveUnref": true,
        "shallowReactive": true,
        "shallowReadonly": true,
        "shallowRef": true,
        "syncRef": true,
        "syncRefs": true,
        "templateRef": true,
        "throttledRef": true,
        "throttledWatch": true,
        "toRaw": true,
        "toReactive": true,
        "toRef": true,
        "toRefs": true,
        "triggerRef": true,
        "tryOnBeforeMount": true,
        "tryOnBeforeUnmount": true,
        "tryOnMounted": true,
        "tryOnScopeDispose": true,
        "tryOnUnmounted": true,
        "unref": true,
        "unrefElement": true,
        "until": true,
        "useActiveElement": true,
        "useArrayEvery": true,
        "useArrayFilter": true,
        "useArrayFind": true,
        "useArrayFindIndex": true,
        "useArrayFindLast": true,
        "useArrayJoin": true,
        "useArrayMap": true,
        "useArrayReduce": true,
        "useArraySome": true,
        "useArrayUnique": true,
        "useAsyncQueue": true,
        "useAsyncState": true,
        "useAttrs": true,
        "useBase64": true,
        "useBattery": true,
        "useBluetooth": true,
        "useBreakpoints": true,
        "useBroadcastChannel": true,
        "useBrowserLocation": true,
        "useCached": true,
        "useClipboard": true,
        "useCloned": true,
        "useColorMode": true,
        "useConfirmDialog": true,
        "useCounter": true,
        "useCssModule": true,
        "useCssVar": true,
        "useCssVars": true,
        "useCurrentElement": true,
        "useCycleList": true,
        "useDark": true,
        "useDateFormat": true,
        "useDebounce": true,
        "useDebounceFn": true,
        "useDebouncedRefHistory": true,
        "useDeviceMotion": true,
        "useDeviceOrientation": true,
        "useDevicePixelRatio": true,
        "useDevicesList": true,
        "useDisplayMedia": true,
        "useDocumentVisibility": true,
        "useDraggable": true,
        "useDropZone": true,
        "useElementBounding": true,
        "useElementByPoint": true,
        "useElementHover": true,
        "useElementSize": true,
        "useElementVisibility": true,
        "useEventBus": true,
        "useEventListener": true,
        "useEventSource": true,
        "useEyeDropper": true,
        "useFavicon": true,
        "useFetch": true,
        "useFileDialog": true,
        "useFileSystemAccess": true,
        "useFocus": true,
        "useFocusWithin": true,
        "useFps": true,
        "useFullscreen": true,
        "useGamepad": true,
        "useGeolocation": true,
        "useIdle": true,
        "useImage": true,
        "useInfiniteScroll": true,
        "useIntersectionObserver": true,
        "useInterval": true,
        "useIntervalFn": true,
        "useKeyModifier": true,
        "useLastChanged": true,
        "useLocalStorage": true,
        "useMagicKeys": true,
        "useManualRefHistory": true,
        "useMediaControls": true,
        "useMediaQuery": true,
        "useMemoize": true,
        "useMemory": true,
        "useMounted": true,
        "useMouse": true,
        "useMouseInElement": true,
        "useMousePressed": true,
        "useMutationObserver": true,
        "useNavigatorLanguage": true,
        "useNetwork": true,
        "useNow": true,
        "useObjectUrl": true,
        "useOffsetPagination": true,
        "useOnline": true,
        "usePageLeave": true,
        "useParallax": true,
        "usePermission": true,
        "usePointer": true,
        "usePointerLock": true,
        "usePointerSwipe": true,
        "usePreferredColorScheme": true,
        "usePreferredContrast": true,
        "usePreferredDark": true,
        "usePreferredLanguages": true,
        "usePreferredReducedMotion": true,
        "usePrevious": true,
        "useRafFn": true,
        "useRefHistory": true,
        "useResizeObserver": true,
        "useScreenOrientation": true,
        "useScreenSafeArea": true,
        "useScriptTag": true,
        "useScroll": true,
        "useScrollLock": true,
        "useSessionStorage": true,
        "useShare": true,
        "useSlots": true,
        "useSorted": true,
        "useSpeechRecognition": true,
        "useSpeechSynthesis": true,
        "useStepper": true,
        "useStorage": true,
        "useStorageAsync": true,
        "useStyleTag": true,
        "useSupported": true,
        "useSwipe": true,
        "useTemplateRefsList": true,
        "useTextDirection": true,
        "useTextSelection": true,
        "useTextareaAutosize": true,
        "useThrottle": true,
        "useThrottleFn": true,
        "useThrottledRefHistory": true,
        "useTimeAgo": true,
        "useTimeout": true,
        "useTimeoutFn": true,
        "useTimeoutPoll": true,
        "useTimestamp": true,
        "useTitle": true,
        "useToNumber": true,
        "useToString": true,
        "useToggle": true,
        "useTransition": true,
        "useUrlSearchParams": true,
        "useUserMedia": true,
        "useVModel": true,
        "useVModels": true,
        "useVibrate": true,
        "useVirtualList": true,
        "useWakeLock": true,
        "useWebNotification": true,
        "useWebSocket": true,
        "useWebWorker": true,
        "useWebWorkerFn": true,
        "useWindowFocus": true,
        "useWindowScroll": true,
        "useWindowSize": true,
        "watch": true,
        "watchArray": true,
        "watchAtMost": true,
        "watchDebounced": true,
        "watchEffect": true,
        "watchIgnorable": true,
        "watchOnce": true,
        "watchPausable": true,
        "watchPostEffect": true,
        "watchSyncEffect": true,
        "watchThrottled": true,
        "watchTriggerable": true,
        "watchWithFilter": true,
        "whenever": true,
        "ImportOption": true,
        "TreeType": true,
        "FieldOption": true,
        "PageData": true,
        "storeToRefs": true,
        "DictDataOption": true,
        "UploadOption": true
    }
  "globals": {
    "useRouter": true,
    "useRoute": true,
    "EffectScope": true,
    "ElTable": true,
    "ElSelect": true,
    "ElUpload": true,
    "ElForm": true,
    "ElTree": true,
    "ElMessage": true,
    "ElMessageBox": true,
    "asyncComputed": true,
    "autoResetRef": true,
    "computed": true,
    "computedAsync": true,
    "computedEager": true,
    "computedInject": true,
    "computedWithControl": true,
    "controlledComputed": true,
    "controlledRef": true,
    "createApp": true,
    "createEventHook": true,
    "createGlobalState": true,
    "createInjectionState": true,
    "createReactiveFn": true,
    "createSharedComposable": true,
    "createUnrefFn": true,
    "customRef": true,
    "debouncedRef": true,
    "debouncedWatch": true,
    "defineAsyncComponent": true,
    "defineComponent": true,
    "eagerComputed": true,
    "effectScope": true,
    "extendRef": true,
    "getCurrentInstance": true,
    "getCurrentScope": true,
    "h": true,
    "ignorableWatch": true,
    "inject": true,
    "isDefined": true,
    "isProxy": true,
    "isReactive": true,
    "isReadonly": true,
    "isRef": true,
    "makeDestructurable": true,
    "markRaw": true,
    "nextTick": true,
    "onActivated": true,
    "onBeforeMount": true,
    "onBeforeUnmount": true,
    "onBeforeUpdate": true,
    "onClickOutside": true,
    "onDeactivated": true,
    "onErrorCaptured": true,
    "onKeyStroke": true,
    "onLongPress": true,
    "onMounted": true,
    "onRenderTracked": true,
    "onRenderTriggered": true,
    "onScopeDispose": true,
    "onServerPrefetch": true,
    "onStartTyping": true,
    "onUnmounted": true,
    "onUpdated": true,
    "pausableWatch": true,
    "provide": true,
    "reactify": true,
    "reactifyObject": true,
    "reactive": true,
    "reactiveComputed": true,
    "reactiveOmit": true,
    "reactivePick": true,
    "readonly": true,
    "ref": true,
    "refAutoReset": true,
    "refDebounced": true,
    "refDefault": true,
    "refThrottled": true,
    "refWithControl": true,
    "resolveComponent": true,
    "resolveDirective": true,
    "resolveRef": true,
    "resolveUnref": true,
    "shallowReactive": true,
    "shallowReadonly": true,
    "shallowRef": true,
    "syncRef": true,
    "syncRefs": true,
    "templateRef": true,
    "throttledRef": true,
    "throttledWatch": true,
    "toRaw": true,
    "toReactive": true,
    "toRef": true,
    "toRefs": true,
    "triggerRef": true,
    "tryOnBeforeMount": true,
    "tryOnBeforeUnmount": true,
    "tryOnMounted": true,
    "tryOnScopeDispose": true,
    "tryOnUnmounted": true,
    "unref": true,
    "unrefElement": true,
    "until": true,
    "useActiveElement": true,
    "useArrayEvery": true,
    "useArrayFilter": true,
    "useArrayFind": true,
    "useArrayFindIndex": true,
    "useArrayFindLast": true,
    "useArrayJoin": true,
    "useArrayMap": true,
    "useArrayReduce": true,
    "useArraySome": true,
    "useArrayUnique": true,
    "useAsyncQueue": true,
    "useAsyncState": true,
    "useAttrs": true,
    "useBase64": true,
    "useBattery": true,
    "useBluetooth": true,
    "useBreakpoints": true,
    "useBroadcastChannel": true,
    "useBrowserLocation": true,
    "useCached": true,
    "useClipboard": true,
    "useCloned": true,
    "useColorMode": true,
    "useConfirmDialog": true,
    "useCounter": true,
    "useCssModule": true,
    "useCssVar": true,
    "useCssVars": true,
    "useCurrentElement": true,
    "useCycleList": true,
    "useDark": true,
    "useDateFormat": true,
    "useDebounce": true,
    "useDebounceFn": true,
    "useDebouncedRefHistory": true,
    "useDeviceMotion": true,
    "useDeviceOrientation": true,
    "useDevicePixelRatio": true,
    "useDevicesList": true,
    "useDisplayMedia": true,
    "useDocumentVisibility": true,
    "useDraggable": true,
    "useDropZone": true,
    "useElementBounding": true,
    "useElementByPoint": true,
    "useElementHover": true,
    "useElementSize": true,
    "useElementVisibility": true,
    "useEventBus": true,
    "useEventListener": true,
    "useEventSource": true,
    "useEyeDropper": true,
    "useFavicon": true,
    "useFetch": true,
    "useFileDialog": true,
    "useFileSystemAccess": true,
    "useFocus": true,
    "useFocusWithin": true,
    "useFps": true,
    "useFullscreen": true,
    "useGamepad": true,
    "useGeolocation": true,
    "useIdle": true,
    "useImage": true,
    "useInfiniteScroll": true,
    "useIntersectionObserver": true,
    "useInterval": true,
    "useIntervalFn": true,
    "useKeyModifier": true,
    "useLastChanged": true,
    "useLocalStorage": true,
    "useMagicKeys": true,
    "useManualRefHistory": true,
    "useMediaControls": true,
    "useMediaQuery": true,
    "useMemoize": true,
    "useMemory": true,
    "useMounted": true,
    "useMouse": true,
    "useMouseInElement": true,
    "useMousePressed": true,
    "useMutationObserver": true,
    "useNavigatorLanguage": true,
    "useNetwork": true,
    "useNow": true,
    "useObjectUrl": true,
    "useOffsetPagination": true,
    "useOnline": true,
    "usePageLeave": true,
    "useParallax": true,
    "usePermission": true,
    "usePointer": true,
    "usePointerLock": true,
    "usePointerSwipe": true,
    "usePreferredColorScheme": true,
    "usePreferredContrast": true,
    "usePreferredDark": true,
    "usePreferredLanguages": true,
    "usePreferredReducedMotion": true,
    "usePrevious": true,
    "useRafFn": true,
    "useRefHistory": true,
    "useResizeObserver": true,
    "useScreenOrientation": true,
    "useScreenSafeArea": true,
    "useScriptTag": true,
    "useScroll": true,
    "useScrollLock": true,
    "useSessionStorage": true,
    "useShare": true,
    "useSlots": true,
    "useSorted": true,
    "useSpeechRecognition": true,
    "useSpeechSynthesis": true,
    "useStepper": true,
    "useStorage": true,
    "useStorageAsync": true,
    "useStyleTag": true,
    "useSupported": true,
    "useSwipe": true,
    "useTemplateRefsList": true,
    "useTextDirection": true,
    "useTextSelection": true,
    "useTextareaAutosize": true,
    "useThrottle": true,
    "useThrottleFn": true,
    "useThrottledRefHistory": true,
    "useTimeAgo": true,
    "useTimeout": true,
    "useTimeoutFn": true,
    "useTimeoutPoll": true,
    "useTimestamp": true,
    "useTitle": true,
    "useToNumber": true,
    "useToString": true,
    "useToggle": true,
    "useTransition": true,
    "useUrlSearchParams": true,
    "useUserMedia": true,
    "useVModel": true,
    "useVModels": true,
    "useVibrate": true,
    "useVirtualList": true,
    "useWakeLock": true,
    "useWebNotification": true,
    "useWebSocket": true,
    "useWebWorker": true,
    "useWebWorkerFn": true,
    "useWindowFocus": true,
    "useWindowScroll": true,
    "useWindowSize": true,
    "watch": true,
    "watchArray": true,
    "watchAtMost": true,
    "watchDebounced": true,
    "watchEffect": true,
    "watchIgnorable": true,
    "watchOnce": true,
    "watchPausable": true,
    "watchPostEffect": true,
    "watchSyncEffect": true,
    "watchThrottled": true,
    "watchTriggerable": true,
    "watchWithFilter": true,
    "whenever": true,
    "ImportOption": true,
    "TreeType": true,
    "FieldOption": true,
    "PageData": true,
    "storeToRefs": true,
    "DictDataOption": true,
    "UploadOption": true
  }
}
.eslintrc.js
@@ -1,41 +1,41 @@
module.exports = {
    env: {
        browser: true,
        es2021: true,
        node: true
    },
    parser: 'vue-eslint-parser',
    extends: [
        'eslint:recommended',
        'plugin:vue/vue3-essential',
        'plugin:@typescript-eslint/recommended',
        './.eslintrc-auto-import.json',
        'plugin:prettier/recommended'
    ],
    parserOptions: {
        ecmaVersion: '2020',
        sourceType: 'module',
        parser: '@typescript-eslint/parser'
    },
    plugins: ['vue', '@typescript-eslint'],
    rules: {
        'vue/multi-word-component-names': 'off',
        '@typescript-eslint/no-empty-function': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
        'vue/no-v-model-argument': 'off',
        '@typescript-eslint/ban-types': [
            'error',
            {
                // å…³é—­ç©ºç±»åž‹æ£€æŸ¥ {}
                extendDefaults: true,
                types: {
                    '{}': false
                }
            }
        ]
    },
    globals: {
        DialogOption: 'readonly',
        OptionType: 'readonly'
    }
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  parser: 'vue-eslint-parser',
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    './.eslintrc-auto-import.json',
    'plugin:prettier/recommended'
  ],
  parserOptions: {
    ecmaVersion: '2020',
    sourceType: 'module',
    parser: '@typescript-eslint/parser'
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    'vue/multi-word-component-names': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    'vue/no-v-model-argument': 'off',
    '@typescript-eslint/ban-types': [
      'error',
      {
        // å…³é—­ç©ºç±»åž‹æ£€æŸ¥ {}
        extendDefaults: true,
        types: {
          '{}': false
        }
      }
    ]
  },
  globals: {
    DialogOption: 'readonly',
    OptionType: 'readonly'
  }
};
.prettierrc.cjs
@@ -2,45 +2,45 @@
 * ä»£ç æ ¼å¼åŒ–配置
 */
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: 'none',
    // åœ¨å¯¹è±¡æ–‡å­—中的括号之间打印空格
    bracketSpacing: true,
    // jsx æ ‡ç­¾çš„反尖括号需要换行
    jsxBracketSameLine: false,
    embeddedLanguageFormatting: 'off',
    // åœ¨å•独的箭头函数参数周围包括括号 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,
    //在 windows æ“ä½œç³»ç»Ÿä¸­æ¢è¡Œç¬¦é€šå¸¸æ˜¯å›žè½¦ (CR) åŠ æ¢è¡Œåˆ†éš”ç¬¦ (LF),也就是回车换行(CRLF),
    //然而在 Linux å’Œ Unix ä¸­åªä½¿ç”¨ç®€å•的换行分隔符 (LF)。
    //对应的控制字符为 "\n" (LF) å’Œ "\r\n"(CRLF)。auto意为保持现有的行尾
    // æ¢è¡Œç¬¦ä½¿ç”¨ lf ç»“尾是 å¯é€‰å€¼"<auto|lf|crlf|cr>"
    endOfLine: 'auto'
    // ä¸€è¡Œæœ€å¤šå¤šå°‘个字符
    printWidth: 150,
    // æŒ‡å®šæ¯ä¸ªç¼©è¿›çº§åˆ«çš„空格数
    tabWidth: 2,
    // ä½¿ç”¨åˆ¶è¡¨ç¬¦è€Œä¸æ˜¯ç©ºæ ¼ç¼©è¿›è¡Œ
    useTabs: false,
    // åœ¨è¯­å¥æœ«å°¾æ˜¯å¦éœ€è¦åˆ†å·
    semi: true,
    // æ˜¯å¦ä½¿ç”¨å•引号
    singleQuote: true,
    // æ›´æ”¹å¼•用对象属性的时间 å¯é€‰å€¼"<as-needed|consistent|preserve>"
    quoteProps: 'as-needed',
    // åœ¨JSX中使用单引号而不是双引号
    jsxSingleQuote: false,
    // å¤šè¡Œæ—¶å°½å¯èƒ½æ‰“印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) å¯é€‰å€¼"<none|es5|all>",默认none
    trailingComma: 'none',
    // åœ¨å¯¹è±¡æ–‡å­—中的括号之间打印空格
    bracketSpacing: true,
    // jsx æ ‡ç­¾çš„反尖括号需要换行
    jsxBracketSameLine: false,
    embeddedLanguageFormatting: 'off',
    // åœ¨å•独的箭头函数参数周围包括括号 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,
    //在 windows æ“ä½œç³»ç»Ÿä¸­æ¢è¡Œç¬¦é€šå¸¸æ˜¯å›žè½¦ (CR) åŠ æ¢è¡Œåˆ†éš”ç¬¦ (LF),也就是回车换行(CRLF),
    //然而在 Linux å’Œ Unix ä¸­åªä½¿ç”¨ç®€å•的换行分隔符 (LF)。
    //对应的控制字符为 "\n" (LF) å’Œ "\r\n"(CRLF)。auto意为保持现有的行尾
    // æ¢è¡Œç¬¦ä½¿ç”¨ lf ç»“尾是 å¯é€‰å€¼"<auto|lf|crlf|cr>"
    endOfLine: 'auto'
};
README.md
@@ -1,11 +1,9 @@
## å¹³å°ç®€ä»‹
## å¹³å°ç®€ä»‹
- æœ¬ä»“库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) ç‰ˆæœ¬ã€‚
- é…å¥—后端代码仓库地址
- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
* æœ¬ä»“库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) ç‰ˆæœ¬ã€‚
* é…å¥—后端代码仓库地址
* [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus)
* [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
## å‰ç«¯è¿è¡Œ
@@ -19,120 +17,57 @@
# å¯åŠ¨æœåŠ¡
npm run dev
# æž„建测试环境 yarn build:stage
# æž„建生产环境 yarn build:prod
# å‰ç«¯è®¿é—®åœ°å€ http://localhost:80
```
## æœ¬æ¡†æž¶ä¸Ž RuoYi çš„业务差异
## æœ¬æ¡†æž¶ä¸ŽRuoYi的业务差异
| ä¸šåŠ¡         | åŠŸèƒ½è¯´æ˜Ž                                                        | æœ¬æ¡†æž¶ | RuoYi                          |
| ------------ | --------------------------------------------------------------- | ------ | ------------------------------ |
| ç§Ÿæˆ·ç®¡ç†     | ç³»ç»Ÿå†…租户的管理 å¦‚:租户套餐、过期时间、用户数量、企业信息等    | æ”¯æŒ   | æ—                              |
| ç§Ÿæˆ·å¥—餐管理 | ç³»ç»Ÿå†…租户所能使用的套餐管理 å¦‚:套餐内所包含的菜单等            | æ”¯æŒ   | æ—                              |
| ç”¨æˆ·ç®¡ç†     | ç”¨æˆ·çš„管理配置 å¦‚:新增用户、分配用户所属部门、角色、岗位等      | æ”¯æŒ   | æ”¯æŒ                           |
| éƒ¨é—¨ç®¡ç†     | é…ç½®ç³»ç»Ÿç»„织机构(公司、部门、小组) æ ‘结构展现支持数据权限     | æ”¯æŒ   | æ”¯æŒ                           |
| å²—位管理     | é…ç½®ç³»ç»Ÿç”¨æˆ·æ‰€å±žæ‹…任职务                                        | æ”¯æŒ   | æ”¯æŒ                           |
| èœå•管理     | é…ç½®ç³»ç»Ÿèœå•、操作权限、按钮权限标识等                          | æ”¯æŒ   | æ”¯æŒ                           |
| è§’色管理     | è§’色菜单权限分配、设置角色按机构进行数据范围权限划分            | æ”¯æŒ   | æ”¯æŒ                           |
| å­—典管理     | å¯¹ç³»ç»Ÿä¸­ç»å¸¸ä½¿ç”¨çš„一些较为固定的数据进行维护                    | æ”¯æŒ   | æ”¯æŒ                           |
| å‚数管理     | å¯¹ç³»ç»ŸåŠ¨æ€é…ç½®å¸¸ç”¨å‚æ•°                                          | æ”¯æŒ   | æ”¯æŒ                           |
| é€šçŸ¥å…¬å‘Š     | ç³»ç»Ÿé€šçŸ¥å…¬å‘Šä¿¡æ¯å‘布维护                                        | æ”¯æŒ   | æ”¯æŒ                           |
| æ“ä½œæ—¥å¿—     | ç³»ç»Ÿæ­£å¸¸æ“ä½œæ—¥å¿—记录和查询 ç³»ç»Ÿå¼‚常信息日志记录和查询           | æ”¯æŒ   | æ”¯æŒ                           |
| ç™»å½•日志     | ç³»ç»Ÿç™»å½•日志记录查询包含登录异常                                | æ”¯æŒ   | æ”¯æŒ                           |
| æ–‡ä»¶ç®¡ç†     | ç³»ç»Ÿæ–‡ä»¶å±•示、上传、下载、删除等管理                            | æ”¯æŒ   | æ—                              |
| æ–‡ä»¶é…ç½®ç®¡ç† | ç³»ç»Ÿæ–‡ä»¶ä¸Šä¼ ã€ä¸‹è½½æ‰€éœ€è¦çš„配置信息动态添加、修改、删除等管理    | æ”¯æŒ   | æ—                              |
| åœ¨çº¿ç”¨æˆ·ç®¡ç† | å·²ç™»å½•系统的在线用户信息监控与强制踢出操作                      | æ”¯æŒ   | æ”¯æŒ                           |
| å®šæ—¶ä»»åŠ¡     | è¿è¡ŒæŠ¥è¡¨ã€ä»»åŠ¡ç®¡ç†(添加、修改、删除)、日志管理、执行器管理等    | æ”¯æŒ   | ä»…支持任务与日志管理           |
| ä»£ç ç”Ÿæˆ     | å¤šæ•°æ®æºå‰åŽç«¯ä»£ç çš„生成(java、html、xml、sql)支持 CRUD ä¸‹è½½  | æ”¯æŒ   | ä»…支持单数据源                 |
| ç³»ç»ŸæŽ¥å£     | æ ¹æ®ä¸šåŠ¡ä»£ç è‡ªåŠ¨ç”Ÿæˆç›¸å…³çš„ api æŽ¥å£æ–‡æ¡£                         | æ”¯æŒ   | æ”¯æŒ                           |
| æœåŠ¡ç›‘æŽ§     | ç›‘视集群系统 CPU、内存、磁盘、堆栈、在线日志、Spring ç›¸å…³é…ç½®ç­‰ | æ”¯æŒ   | ä»…支持单机 CPU、内存、磁盘监控 |
| ç¼“存监控     | å¯¹ç³»ç»Ÿçš„缓存信息查询,命令统计等。                              | æ”¯æŒ   | æ”¯æŒ                           |
| åœ¨çº¿æž„建器   | æ‹–动表单元素生成相应的 HTML ä»£ç ã€‚                              | æ”¯æŒ   | æ”¯æŒ                           |
| ä½¿ç”¨æ¡ˆä¾‹     | ç³»ç»Ÿçš„一些功能案例                                              | æ”¯æŒ   | ä¸æ”¯æŒ                         |
| ä¸šåŠ¡     | åŠŸèƒ½è¯´æ˜Ž                                    | æœ¬æ¡†æž¶ | RuoYi            |
|--------|-----------------------------------------|-----|------------------|
| ç§Ÿæˆ·ç®¡ç†   | ç³»ç»Ÿå†…租户的管理 å¦‚:租户套餐、过期时间、用户数量、企业信息等         | æ”¯æŒ  | æ—                 |
| ç§Ÿæˆ·å¥—餐管理 | ç³»ç»Ÿå†…租户所能使用的套餐管理 å¦‚:套餐内所包含的菜单等             | æ”¯æŒ  | æ—                 |
| ç”¨æˆ·ç®¡ç†   | ç”¨æˆ·çš„管理配置 å¦‚:新增用户、分配用户所属部门、角色、岗位等          | æ”¯æŒ  | æ”¯æŒ               |
| éƒ¨é—¨ç®¡ç†   | é…ç½®ç³»ç»Ÿç»„织机构(公司、部门、小组) æ ‘结构展现支持数据权限          | æ”¯æŒ  | æ”¯æŒ               |
| å²—位管理   | é…ç½®ç³»ç»Ÿç”¨æˆ·æ‰€å±žæ‹…任职务                            | æ”¯æŒ  | æ”¯æŒ               |
| èœå•管理   | é…ç½®ç³»ç»Ÿèœå•、操作权限、按钮权限标识等                     | æ”¯æŒ  | æ”¯æŒ               |
| è§’色管理   | è§’色菜单权限分配、设置角色按机构进行数据范围权限划分              | æ”¯æŒ  | æ”¯æŒ               |
| å­—典管理   | å¯¹ç³»ç»Ÿä¸­ç»å¸¸ä½¿ç”¨çš„一些较为固定的数据进行维护                  | æ”¯æŒ  | æ”¯æŒ               |
| å‚数管理   | å¯¹ç³»ç»ŸåŠ¨æ€é…ç½®å¸¸ç”¨å‚æ•°                             | æ”¯æŒ  | æ”¯æŒ               |
| é€šçŸ¥å…¬å‘Š   | ç³»ç»Ÿé€šçŸ¥å…¬å‘Šä¿¡æ¯å‘布维护                            | æ”¯æŒ  | æ”¯æŒ               |
| æ“ä½œæ—¥å¿—   | ç³»ç»Ÿæ­£å¸¸æ“ä½œæ—¥å¿—记录和查询 ç³»ç»Ÿå¼‚常信息日志记录和查询             | æ”¯æŒ  | æ”¯æŒ               |
| ç™»å½•日志   | ç³»ç»Ÿç™»å½•日志记录查询包含登录异常                        | æ”¯æŒ  | æ”¯æŒ               |
| æ–‡ä»¶ç®¡ç†   | ç³»ç»Ÿæ–‡ä»¶å±•示、上传、下载、删除等管理                      | æ”¯æŒ  | æ—                 |
| æ–‡ä»¶é…ç½®ç®¡ç† | ç³»ç»Ÿæ–‡ä»¶ä¸Šä¼ ã€ä¸‹è½½æ‰€éœ€è¦çš„配置信息动态添加、修改、删除等管理          | æ”¯æŒ  | æ—                 |
| åœ¨çº¿ç”¨æˆ·ç®¡ç† | å·²ç™»å½•系统的在线用户信息监控与强制踢出操作                   | æ”¯æŒ  | æ”¯æŒ               |
| å®šæ—¶ä»»åŠ¡   | è¿è¡ŒæŠ¥è¡¨ã€ä»»åŠ¡ç®¡ç†(添加、修改、删除)、日志管理、执行器管理等         | æ”¯æŒ  | ä»…支持任务与日志管理       |
| ä»£ç ç”Ÿæˆ   | å¤šæ•°æ®æºå‰åŽç«¯ä»£ç çš„生成(java、html、xml、sql)支持CRUD下载 | æ”¯æŒ  | ä»…支持单数据源          |
| ç³»ç»ŸæŽ¥å£   | æ ¹æ®ä¸šåŠ¡ä»£ç è‡ªåŠ¨ç”Ÿæˆç›¸å…³çš„api接口文档                    | æ”¯æŒ  | æ”¯æŒ               |
| æœåŠ¡ç›‘æŽ§   | ç›‘视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等     | æ”¯æŒ  | ä»…支持单机CPU、内存、磁盘监控 |
| ç¼“存监控   | å¯¹ç³»ç»Ÿçš„缓存信息查询,命令统计等。                       | æ”¯æŒ  | æ”¯æŒ               |
| åœ¨çº¿æž„建器  | æ‹–动表单元素生成相应的HTML代码。                      | æ”¯æŒ  | æ”¯æŒ               |
| ä½¿ç”¨æ¡ˆä¾‹   | ç³»ç»Ÿçš„一些功能案例                               | æ”¯æŒ  | ä¸æ”¯æŒ              |
## æ¼”示图
## æ¼”示图例
- æœ¬ä»“库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) ç‰ˆæœ¬ã€‚
- é…å¥—后端代码仓库地址
- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
## å‰ç«¯è¿è¡Œ
```bash
# å…‹éš†é¡¹ç›®
git clone https://gitee.com/JavaLionLi/plus-ui.git
# å®‰è£…依赖
npm install --registry=https://registry.npmmirror.com
# å¯åŠ¨æœåŠ¡
npm run dev
# æž„建测试环境 yarn build:stage
# æž„建生产环境 yarn build:prod
# å‰ç«¯è®¿é—®åœ°å€ http://localhost:80
```
## åŽç«¯æ”¹é€ 
参考后端代码内 `ruoyi-gen/resources/vm/vue/v3/readme.txt` è¯´æ˜Ž
## å†…置功能
1. ç§Ÿæˆ·ç®¡ç†ï¼šé…ç½®ç³»ç»Ÿç§Ÿæˆ·ï¼Œæ”¯æŒ SaaS åœºæ™¯ä¸‹çš„多租户功能。
2. ç”¨æˆ·ç®¡ç†ï¼šç”¨æˆ·æ˜¯ç³»ç»Ÿæ“ä½œè€…,该功能主要完成系统用户配置。
3. éƒ¨é—¨ç®¡ç†ï¼šé…ç½®ç³»ç»Ÿç»„织机构(公司、部门、小组),树结构展现支持数据权限。
4. å²—位管理:配置系统用户所属担任职务。
5. èœå•管理:配置系统菜单,操作权限,按钮权限标识等。
6. è§’色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
7. å­—典管理:对系统中经常使用的一些较为固定的数据进行维护。
8. å‚数管理:对系统动态配置常用参数。
9. é€šçŸ¥å…¬å‘Šï¼šç³»ç»Ÿé€šçŸ¥å…¬å‘Šä¿¡æ¯å‘布维护。
10. æ“ä½œæ—¥å¿—:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
11. ç™»å½•日志:系统登录日志记录查询包含登录异常。
12. åœ¨çº¿ç”¨æˆ·ï¼šå½“前系统中活跃用户状态监控。
13. å®šæ—¶ä»»åŠ¡ï¼šåœ¨çº¿ï¼ˆæ·»åŠ ã€ä¿®æ”¹ã€åˆ é™¤)任务调度包含执行结果日志。
14. ä»£ç ç”Ÿæˆï¼šå‰åŽç«¯ä»£ç çš„生成(java、html、xml、sql)支持 CRUD ä¸‹è½½ ã€‚
15. ç³»ç»ŸæŽ¥å£ï¼šæ ¹æ®ä¸šåŠ¡ä»£ç è‡ªåŠ¨ç”Ÿæˆç›¸å…³çš„ api æŽ¥å£æ–‡æ¡£ã€‚
16. æœåŠ¡ç›‘æŽ§ï¼šç›‘è§†å½“å‰ç³»ç»Ÿ CPU、内存、磁盘、堆栈等相关信息。
17. ç¼“存监控:对系统的缓存信息查询,命令统计等。
18. åœ¨çº¿æž„建器:拖动表单元素生成相应的 HTML ä»£ç ã€‚
## æ¼”示图
<table>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
    </tr>
    <tr>
        <td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
        <td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
    </tr>
</table>
|                                                                                            |                                                                                            |
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |
Vite/plugins/auto-import.ts
ÎļþÒÑɾ³ý
Vite/plugins/components.ts
ÎļþÒÑɾ³ý
Vite/plugins/compression.ts
ÎļþÒÑɾ³ý
Vite/plugins/icons.ts
ÎļþÒÑɾ³ý
Vite/plugins/index.ts
ÎļþÒÑɾ³ý
Vite/plugins/svg-icon.ts
ÎļþÒÑɾ³ý
Vite/plugins/unocss.ts
ÎļþÒÑɾ³ý
commitlint.config.js
@@ -1,22 +1,22 @@
module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'type-enum': [
            2,
            'always',
            [
                'feat', // æ–°åŠŸèƒ½ feature
                'fix', // ä¿®å¤ bug
                'docs', // æ–‡æ¡£æ³¨é‡Š
                'style', // ä»£ç æ ¼å¼
                'refactor', // é‡æž„
                'perf', // æ€§èƒ½ä¼˜åŒ–
                'test', // å¢žåŠ æµ‹è¯•
                'chore', // æž„建过程或辅助工具的变动
                'revert', // å›žé€€
                'build' // æ‰“包
            ]
        ],
        'subject-case': [0]
    }
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat', // æ–°åŠŸèƒ½ feature
        'fix', // ä¿®å¤ bug
        'docs', // æ–‡æ¡£æ³¨é‡Š
        'style', // ä»£ç æ ¼å¼
        'refactor', // é‡æž„
        'perf', // æ€§èƒ½ä¼˜åŒ–
        'test', // å¢žåŠ æµ‹è¯•
        'chore', // æž„建过程或辅助工具的变动
        'revert', // å›žé€€
        'build' // æ‰“包
      ]
    ],
    'subject-case': [0]
  }
};
html/ie.html
@@ -1,60 +1,60 @@
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8" />
        <title>请升级您的浏览器</title>
        <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
        <meta name="renderer" content="webkit" />
        <base target="_blank" />
        <style type="text/css">
            html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
            a{text-decoration:none;color:#0072c6;}a:hover{text-decoration:none;color:#004d8c;}
            body{width:960px;margin:0 auto;padding:10px;font-size:14px;line-height:24px;color:#454545;font-family:'Microsoft YaHei UI','Microsoft YaHei',DengXian,SimSun,'Segoe UI',Tahoma,Helvetica,sans-serif;overflow-y:scroll}
            h1{font-size:40px;line-height:80px;font-weight:100;margin-bottom:10px;}
            h2{font-size:20px;line-height:25px;font-weight:100;margin:10px 0;}
            em{color:red}
            p{margin-bottom:10px;}
            hr{margin:20px 0;border:0;border-top:1px solid #dadada}
            span{display:block;font-size:12px;line-height:12px;}
            .clean{clear:both;}
            .browser{padding:10px 10px;}
            .browser li{width:auto;padding:0 80px;margin-top:30px;height:34px;line-height:22px;float:left;list-style:none;background:url(data:image/png;base64,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) no-repeat;padding-left:40px}
            .browser .browser-firefox{background-position:0 -34px}
            .browser .browser-ie{background-position:0 -68px;margin-left:0px}
            .browser .browser-360{background-position:0 -170px;margin-left: -27px}
        </style>
    </head>
    <body style="margin-top:50px">
        <h1>请升级您的浏览器,以便我们更好的为您提供服务!</h1>
        <p>您正在使用 Internet Explorer çš„æ—©æœŸç‰ˆæœ¬ï¼ˆIE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。</p>
        <hr />
        <h2>请注意:微软公司对Windows XP åŠ Internet Explorer æ—©æœŸç‰ˆæœ¬çš„æ”¯æŒå·²ç»ç»“束</h2>
        <p>
            è‡ª 2016 å¹´ 1 æœˆ 12 æ—¥èµ·ï¼ŒMicrosoft ä¸å†ä¸º IE 11
            ä»¥ä¸‹ç‰ˆæœ¬æä¾›ç›¸åº”支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅
            <a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support"
                >微软对 Internet Explorer æ—©æœŸç‰ˆæœ¬çš„æ”¯æŒå°†äºŽ 2016 å¹´ 1 æœˆ 12 æ—¥ç»“束的说明</a
            >
            ã€‚
        </p>
        <hr />
        <h2>您可以选择更先进的浏览器</h2>
        <p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p>
        <ul class="browser">
            <li class="browser-chrome">
                <a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1"> è°·æ­Œæµè§ˆå™¨<span>Google Chrome</span></a>
            </li>
            <li class="browser-firefox">
                <a href="https://www.mozilla.org/zh-CN/firefox/new/"> ç«ç‹æµè§ˆå™¨<span>Mozilla Firefox</span></a>
            </li>
            <li class="browser-ie">
                <a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie"> IE 11 æµè§ˆå™¨<span>Internet Explorer</span></a>
            </li>
            <li class="browser-360">
                <a href="http://se.360.cn/"> 360安全浏览器<span>360 Chrome</span></a>
            </li>
            <div class="clean"></div>
        </ul>
        <hr />
    </body>
  <head>
    <meta charset="UTF-8" />
    <title>请升级您的浏览器</title>
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
    <meta name="renderer" content="webkit" />
    <base target="_blank" />
    <style type="text/css">
      html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
      a{text-decoration:none;color:#0072c6;}a:hover{text-decoration:none;color:#004d8c;}
      body{width:960px;margin:0 auto;padding:10px;font-size:14px;line-height:24px;color:#454545;font-family:'Microsoft YaHei UI','Microsoft YaHei',DengXian,SimSun,'Segoe UI',Tahoma,Helvetica,sans-serif;overflow-y:scroll}
      h1{font-size:40px;line-height:80px;font-weight:100;margin-bottom:10px;}
      h2{font-size:20px;line-height:25px;font-weight:100;margin:10px 0;}
      em{color:red}
      p{margin-bottom:10px;}
      hr{margin:20px 0;border:0;border-top:1px solid #dadada}
      span{display:block;font-size:12px;line-height:12px;}
      .clean{clear:both;}
      .browser{padding:10px 10px;}
      .browser li{width:auto;padding:0 80px;margin-top:30px;height:34px;line-height:22px;float:left;list-style:none;background:url(data:image/png;base64,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) no-repeat;padding-left:40px}
      .browser .browser-firefox{background-position:0 -34px}
      .browser .browser-ie{background-position:0 -68px;margin-left:0px}
      .browser .browser-360{background-position:0 -170px;margin-left: -27px}
    </style>
  </head>
  <body style="margin-top:50px">
    <h1>请升级您的浏览器,以便我们更好的为您提供服务!</h1>
    <p>您正在使用 Internet Explorer çš„æ—©æœŸç‰ˆæœ¬ï¼ˆIE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。</p>
    <hr />
    <h2>请注意:微软公司对Windows XP åŠ Internet Explorer æ—©æœŸç‰ˆæœ¬çš„æ”¯æŒå·²ç»ç»“束</h2>
    <p>
      è‡ª 2016 å¹´ 1 æœˆ 12 æ—¥èµ·ï¼ŒMicrosoft ä¸å†ä¸º IE 11
      ä»¥ä¸‹ç‰ˆæœ¬æä¾›ç›¸åº”支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅
      <a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support"
        >微软对 Internet Explorer æ—©æœŸç‰ˆæœ¬çš„æ”¯æŒå°†äºŽ 2016 å¹´ 1 æœˆ 12 æ—¥ç»“束的说明</a
      >
      ã€‚
    </p>
    <hr />
    <h2>您可以选择更先进的浏览器</h2>
    <p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p>
    <ul class="browser">
      <li class="browser-chrome">
        <a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1"> è°·æ­Œæµè§ˆå™¨<span>Google Chrome</span></a>
      </li>
      <li class="browser-firefox">
        <a href="https://www.mozilla.org/zh-CN/firefox/new/"> ç«ç‹æµè§ˆå™¨<span>Mozilla Firefox</span></a>
      </li>
      <li class="browser-ie">
        <a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie"> IE 11 æµè§ˆå™¨<span>Internet Explorer</span></a>
      </li>
      <li class="browser-360">
        <a href="http://se.360.cn/"> 360安全浏览器<span>360 Chrome</span></a>
      </li>
      <div class="clean"></div>
    </ul>
    <hr />
  </body>
</html>
index.html
@@ -1,217 +1,217 @@
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="renderer" content="webkit" />
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
        <link rel="icon" href="/favicon.ico" />
        <title>RuoYi-Vue-Plus多租户管理系统</title>
        <!--[if lt IE 11
            ]><script>
                window.location.href='/html/ie.html';
            </script><!
        [endif]-->
        <style>
            html,
            body,
            #app {
              height: 100%;
              margin: 0px;
              padding: 0px;
            }
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="renderer" content="webkit" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <link rel="icon" href="/favicon.ico" />
    <title>RuoYi-Vue-Plus多租户管理系统</title>
    <!--[if lt IE 11
      ]><script>
        window.location.href='/html/ie.html';
      </script><!
    [endif]-->
    <style>
      html,
      body,
      #app {
        height: 100%;
        margin: 0px;
        padding: 0px;
      }
            .chromeframe {
              margin: 0.2em 0;
              background: #ccc;
              color: #000;
              padding: 0.2em 0;
            }
      .chromeframe {
        margin: 0.2em 0;
        background: #ccc;
        color: #000;
        padding: 0.2em 0;
      }
            #loader-wrapper {
              position: fixed;
              top: 0;
              left: 0;
              width: 100%;
              height: 100%;
              z-index: 999999;
            }
      #loader-wrapper {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 999999;
      }
            #loader {
              display: block;
              position: relative;
              left: 50%;
              top: 50%;
              width: 150px;
              height: 150px;
              margin: -75px 0 0 -75px;
              border-radius: 50%;
              border: 3px solid transparent;
              border-top-color: #FFF;
              -webkit-animation: spin 2s linear infinite;
              -ms-animation: spin 2s linear infinite;
              -moz-animation: spin 2s linear infinite;
              -o-animation: spin 2s linear infinite;
              animation: spin 2s linear infinite;
              z-index: 1001;
            }
      #loader {
        display: block;
        position: relative;
        left: 50%;
        top: 50%;
        width: 150px;
        height: 150px;
        margin: -75px 0 0 -75px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: #FFF;
        -webkit-animation: spin 2s linear infinite;
        -ms-animation: spin 2s linear infinite;
        -moz-animation: spin 2s linear infinite;
        -o-animation: spin 2s linear infinite;
        animation: spin 2s linear infinite;
        z-index: 1001;
      }
            #loader:before {
              content: "";
              position: absolute;
              top: 5px;
              left: 5px;
              right: 5px;
              bottom: 5px;
              border-radius: 50%;
              border: 3px solid transparent;
              border-top-color: #FFF;
              -webkit-animation: spin 3s linear infinite;
              -moz-animation: spin 3s linear infinite;
              -o-animation: spin 3s linear infinite;
              -ms-animation: spin 3s linear infinite;
              animation: spin 3s linear infinite;
            }
      #loader:before {
        content: "";
        position: absolute;
        top: 5px;
        left: 5px;
        right: 5px;
        bottom: 5px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: #FFF;
        -webkit-animation: spin 3s linear infinite;
        -moz-animation: spin 3s linear infinite;
        -o-animation: spin 3s linear infinite;
        -ms-animation: spin 3s linear infinite;
        animation: spin 3s linear infinite;
      }
            #loader:after {
              content: "";
              position: absolute;
              top: 15px;
              left: 15px;
              right: 15px;
              bottom: 15px;
              border-radius: 50%;
              border: 3px solid transparent;
              border-top-color: #FFF;
              -moz-animation: spin 1.5s linear infinite;
              -o-animation: spin 1.5s linear infinite;
              -ms-animation: spin 1.5s linear infinite;
              -webkit-animation: spin 1.5s linear infinite;
              animation: spin 1.5s linear infinite;
            }
      #loader:after {
        content: "";
        position: absolute;
        top: 15px;
        left: 15px;
        right: 15px;
        bottom: 15px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: #FFF;
        -moz-animation: spin 1.5s linear infinite;
        -o-animation: spin 1.5s linear infinite;
        -ms-animation: spin 1.5s linear infinite;
        -webkit-animation: spin 1.5s linear infinite;
        animation: spin 1.5s linear infinite;
      }
            @-webkit-keyframes spin {
              0% {
                -webkit-transform: rotate(0deg);
                -ms-transform: rotate(0deg);
                transform: rotate(0deg);
              }
      @-webkit-keyframes spin {
        0% {
          -webkit-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          transform: rotate(0deg);
        }
              100% {
                -webkit-transform: rotate(360deg);
                -ms-transform: rotate(360deg);
                transform: rotate(360deg);
              }
            }
        100% {
          -webkit-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }
            @keyframes spin {
              0% {
                -webkit-transform: rotate(0deg);
                -ms-transform: rotate(0deg);
                transform: rotate(0deg);
              }
      @keyframes spin {
        0% {
          -webkit-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          transform: rotate(0deg);
        }
              100% {
                -webkit-transform: rotate(360deg);
                -ms-transform: rotate(360deg);
                transform: rotate(360deg);
              }
            }
        100% {
          -webkit-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }
            #loader-wrapper .loader-section {
              position: fixed;
              top: 0;
              width: 51%;
              height: 100%;
              background: #7171C6;
              z-index: 1000;
              -webkit-transform: translateX(0);
              -ms-transform: translateX(0);
              transform: translateX(0);
            }
      #loader-wrapper .loader-section {
        position: fixed;
        top: 0;
        width: 51%;
        height: 100%;
        background: #7171C6;
        z-index: 1000;
        -webkit-transform: translateX(0);
        -ms-transform: translateX(0);
        transform: translateX(0);
      }
            #loader-wrapper .loader-section.section-left {
              left: 0;
            }
      #loader-wrapper .loader-section.section-left {
        left: 0;
      }
            #loader-wrapper .loader-section.section-right {
              right: 0;
            }
      #loader-wrapper .loader-section.section-right {
        right: 0;
      }
            .loaded #loader-wrapper .loader-section.section-left {
              -webkit-transform: translateX(-100%);
              -ms-transform: translateX(-100%);
              transform: translateX(-100%);
              -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
              transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
            }
      .loaded #loader-wrapper .loader-section.section-left {
        -webkit-transform: translateX(-100%);
        -ms-transform: translateX(-100%);
        transform: translateX(-100%);
        -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
        transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
      }
            .loaded #loader-wrapper .loader-section.section-right {
              -webkit-transform: translateX(100%);
              -ms-transform: translateX(100%);
              transform: translateX(100%);
              -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
              transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
            }
      .loaded #loader-wrapper .loader-section.section-right {
        -webkit-transform: translateX(100%);
        -ms-transform: translateX(100%);
        transform: translateX(100%);
        -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
        transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
      }
            .loaded #loader {
              opacity: 0;
              -webkit-transition: all 0.3s ease-out;
              transition: all 0.3s ease-out;
            }
      .loaded #loader {
        opacity: 0;
        -webkit-transition: all 0.3s ease-out;
        transition: all 0.3s ease-out;
      }
            .loaded #loader-wrapper {
              visibility: hidden;
              -webkit-transform: translateY(-100%);
              -ms-transform: translateY(-100%);
              transform: translateY(-100%);
              -webkit-transition: all 0.3s 1s ease-out;
              transition: all 0.3s 1s ease-out;
            }
      .loaded #loader-wrapper {
        visibility: hidden;
        -webkit-transform: translateY(-100%);
        -ms-transform: translateY(-100%);
        transform: translateY(-100%);
        -webkit-transition: all 0.3s 1s ease-out;
        transition: all 0.3s 1s ease-out;
      }
            .no-js #loader-wrapper {
              display: none;
            }
      .no-js #loader-wrapper {
        display: none;
      }
            .no-js h1 {
              color: #222222;
            }
      .no-js h1 {
        color: #222222;
      }
            #loader-wrapper .load_title {
              font-family: 'Open Sans';
              color: #FFF;
              font-size: 19px;
              width: 100%;
              text-align: center;
              z-index: 9999999999999;
              position: absolute;
              top: 60%;
              opacity: 1;
              line-height: 30px;
            }
      #loader-wrapper .load_title {
        font-family: 'Open Sans';
        color: #FFF;
        font-size: 19px;
        width: 100%;
        text-align: center;
        z-index: 9999999999999;
        position: absolute;
        top: 60%;
        opacity: 1;
        line-height: 30px;
      }
            #loader-wrapper .load_title span {
              font-weight: normal;
              font-style: italic;
              font-size: 13px;
              color: #FFF;
              opacity: 0.5;
            }
        </style>
    </head>
      #loader-wrapper .load_title span {
        font-weight: normal;
        font-style: italic;
        font-size: 13px;
        color: #FFF;
        opacity: 0.5;
      }
    </style>
  </head>
    <body>
        <div id="app">
            <div id="loader-wrapper">
                <div id="loader"></div>
                <div class="loader-section section-left"></div>
                <div class="loader-section section-right"></div>
                <div class="load_title">正在加载系统资源,请耐心等待</div>
            </div>
        </div>
        <script type="module" src="/src/main.ts"></script>
    </body>
  <body>
    <div id="app">
      <div id="loader-wrapper">
        <div id="loader"></div>
        <div class="loader-section section-left"></div>
        <div class="loader-section section-right"></div>
        <div class="load_title">正在加载系统资源,请耐心等待</div>
      </div>
    </div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
package.json
@@ -1,80 +1,80 @@
{
    "name": "ruoyi-vue-plus",
    "version": "5.0.0-SNAPSHOT",
    "description": "RuoYi-Vue-Plus多租户管理系统",
    "author": "LionLi",
    "license": "MIT",
    "scripts": {
        "dev": "vite serve --mode development",
        "build:prod": "vite build --mode production &&vue-tsc --noEmit",
        "preview": "vite preview",
        "lint": "eslint src/**/*.{ts,js,vue} --fix",
        "prepare": "husky install",
        "prettier": "prettier --write ."
    },
    "repository": {
        "type": "git",
        "url": "https://gitee.com/JavaLionLi/plus-ui.git"
    },
    "dependencies": {
        "@element-plus/icons-vue": "2.1.0",
        "@vueup/vue-quill": "1.1.0",
        "@vueuse/core": "9.5.0",
        "animate.css": "4.1.1",
        "await-to-js": "^3.0.0",
        "axios": "^1.3.4",
        "echarts": "5.4.0",
        "element-plus": "2.2.27",
        "file-saver": "2.0.5",
        "fuse.js": "6.6.2",
        "js-cookie": "3.0.1",
        "jsencrypt": "3.3.1",
        "nprogress": "0.2.0",
        "path-browserify": "1.0.1",
        "path-to-regexp": "6.2.0",
        "pinia": "2.0.22",
        "screenfull": "6.0.0",
        "vue": "3.2.45",
        "vue-cropper": "1.0.3",
        "vue-i18n": "9.2.2",
        "vue-router": "4.1.4"
    },
    "devDependencies": {
        "@iconify/json": "^2.2.40",
        "@intlify/unplugin-vue-i18n": "0.8.2",
        "@types/file-saver": "2.0.5",
        "@types/js-cookie": "3.0.3",
        "@types/node": "18.14.2",
        "@types/nprogress": "0.2.0",
        "@types/path-browserify": "^1.0.0",
        "@typescript-eslint/eslint-plugin": "5.56.0",
        "@typescript-eslint/parser": "5.56.0",
        "@unocss/preset-attributify": "^0.50.6",
        "@unocss/preset-icons": "^0.50.6",
        "@unocss/preset-uno": "^0.50.6",
        "@vitejs/plugin-vue": "4.0.0",
        "@vue/compiler-sfc": "3.2.45",
        "autoprefixer": "10.4.14",
        "eslint": "8.36.0",
        "eslint-config-prettier": "8.8.0",
        "eslint-plugin-prettier": "4.2.1",
        "eslint-plugin-vue": "9.9.0",
        "fast-glob": "^3.2.11",
        "husky": "7.0.4",
        "postcss": "^8.4.21",
        "prettier": "2.8.6",
        "sass": "1.56.1",
        "typescript": "4.9.5",
        "unocss": "^0.50.6",
        "unplugin-auto-import": "0.13.0",
        "unplugin-icons": "0.15.1",
        "unplugin-vue-components": "0.23.0",
        "vite": "4.1.4",
        "vite-plugin-compression": "0.5.1",
        "vite-plugin-svg-icons": "2.0.1",
        "vite-plugin-vue-setup-extend": "0.4.0",
        "vitest": "^0.29.7",
        "vue-eslint-parser": "9.1.0",
        "vue-tsc": "0.35.0"
    }
  "name": "ruoyi-vue-plus",
  "version": "5.0.0-SNAPSHOT",
  "description": "RuoYi-Vue-Plus多租户管理系统",
  "author": "LionLi",
  "license": "MIT",
  "scripts": {
    "dev": "vite serve --mode development",
    "build:prod": "vite build --mode production &&vue-tsc --noEmit",
    "preview": "vite preview",
    "lint": "eslint src/**/*.{ts,js,vue} --fix",
    "prepare": "husky install",
    "prettier": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "https://gitee.com/JavaLionLi/plus-ui.git"
  },
  "dependencies": {
    "@element-plus/icons-vue": "2.1.0",
    "@vueup/vue-quill": "1.1.0",
    "@vueuse/core": "9.5.0",
    "animate.css": "4.1.1",
    "await-to-js": "^3.0.0",
    "axios": "^1.3.4",
    "echarts": "5.4.0",
    "element-plus": "2.2.27",
    "file-saver": "2.0.5",
    "fuse.js": "6.6.2",
    "js-cookie": "3.0.1",
    "jsencrypt": "3.3.1",
    "nprogress": "0.2.0",
    "path-browserify": "1.0.1",
    "path-to-regexp": "6.2.0",
    "pinia": "2.0.22",
    "screenfull": "6.0.0",
    "vue": "3.2.45",
    "vue-cropper": "1.0.3",
    "vue-i18n": "9.2.2",
    "vue-router": "4.1.4"
  },
  "devDependencies": {
    "@iconify/json": "^2.2.40",
    "@intlify/unplugin-vue-i18n": "0.8.2",
    "@types/file-saver": "2.0.5",
    "@types/js-cookie": "3.0.3",
    "@types/node": "18.14.2",
    "@types/nprogress": "0.2.0",
    "@types/path-browserify": "^1.0.0",
    "@typescript-eslint/eslint-plugin": "5.56.0",
    "@typescript-eslint/parser": "5.56.0",
    "@unocss/preset-attributify": "^0.50.6",
    "@unocss/preset-icons": "^0.50.6",
    "@unocss/preset-uno": "^0.50.6",
    "@vitejs/plugin-vue": "4.0.0",
    "@vue/compiler-sfc": "3.2.45",
    "autoprefixer": "10.4.14",
    "eslint": "8.36.0",
    "eslint-config-prettier": "8.8.0",
    "eslint-plugin-prettier": "4.2.1",
    "eslint-plugin-vue": "9.9.0",
    "fast-glob": "^3.2.11",
    "husky": "7.0.4",
    "postcss": "^8.4.21",
    "prettier": "2.8.6",
    "sass": "1.56.1",
    "typescript": "4.9.5",
    "unocss": "^0.50.6",
    "unplugin-auto-import": "0.13.0",
    "unplugin-icons": "0.15.1",
    "unplugin-vue-components": "0.23.0",
    "vite": "4.1.4",
    "vite-plugin-compression": "0.5.1",
    "vite-plugin-svg-icons": "2.0.1",
    "vite-plugin-vue-setup-extend": "0.4.0",
    "vitest": "^0.29.7",
    "vue-eslint-parser": "9.1.0",
    "vue-tsc": "0.35.0"
  }
}
src/App.vue
@@ -1,7 +1,7 @@
<template>
    <el-config-provider :locale="appStore.locale" :size="size">
        <router-view />
    </el-config-provider>
  <el-config-provider :locale="appStore.locale" :size="size">
    <router-view />
  </el-config-provider>
</template>
<script setup lang="ts">
src/animate.ts
@@ -2,47 +2,47 @@
const animatePrefix = 'animate__animated ';
// å¼€å¯éšæœºåŠ¨ç”» éšæœºåŠ¨ç”»å€¼
const animateList: string[] = [
    animatePrefix + 'animate__pulse',
    animatePrefix + 'animate__rubberBand',
    animatePrefix + 'animate__bounceIn',
    animatePrefix + 'animate__bounceInLeft',
    animatePrefix + 'animate__fadeIn',
    animatePrefix + 'animate__fadeInLeft',
    animatePrefix + 'animate__fadeInDown',
    animatePrefix + 'animate__fadeInUp',
    animatePrefix + 'animate__flipInX',
    animatePrefix + 'animate__lightSpeedInLeft',
    animatePrefix + 'animate__rotateInDownLeft',
    animatePrefix + 'animate__rollIn',
    animatePrefix + 'animate__rotateInDownLeft',
    animatePrefix + 'animate__zoomIn',
    animatePrefix + 'animate__zoomInDown',
    animatePrefix + 'animate__slideInLeft',
    animatePrefix + 'animate__lightSpeedIn'
  animatePrefix + 'animate__pulse',
  animatePrefix + 'animate__rubberBand',
  animatePrefix + 'animate__bounceIn',
  animatePrefix + 'animate__bounceInLeft',
  animatePrefix + 'animate__fadeIn',
  animatePrefix + 'animate__fadeInLeft',
  animatePrefix + 'animate__fadeInDown',
  animatePrefix + 'animate__fadeInUp',
  animatePrefix + 'animate__flipInX',
  animatePrefix + 'animate__lightSpeedInLeft',
  animatePrefix + 'animate__rotateInDownLeft',
  animatePrefix + 'animate__rollIn',
  animatePrefix + 'animate__rotateInDownLeft',
  animatePrefix + 'animate__zoomIn',
  animatePrefix + 'animate__zoomInDown',
  animatePrefix + 'animate__slideInLeft',
  animatePrefix + 'animate__lightSpeedIn'
];
// å…³é—­éšæœºåŠ¨ç”»åŽçš„é»˜è®¤æ•ˆæžœ
const defaultAnimate = animatePrefix + 'animate__fadeIn';
// æœç´¢éšè—æ˜¾ç¤ºåŠ¨ç”»
const searchAnimate = {
    enter: '',
    leave: ''
  enter: '',
  leave: ''
};
// èœå•搜索动画
const menuSearchAnimate = {
    enter: animatePrefix + 'animate__fadeIn',
    leave: animatePrefix + 'animate__fadeOut'
  enter: animatePrefix + 'animate__fadeIn',
  leave: animatePrefix + 'animate__fadeOut'
};
// logo动画
const logoAnimate = {
    enter: animatePrefix + 'animate__fadeIn',
    leave: animatePrefix + 'animate__fadeOut'
  enter: animatePrefix + 'animate__fadeIn',
  leave: animatePrefix + 'animate__fadeOut'
};
export default {
    animateList,
    defaultAnimate,
    searchAnimate,
    menuSearchAnimate,
    logoAnimate
  animateList,
  defaultAnimate,
  searchAnimate,
  menuSearchAnimate,
  logoAnimate
};
src/api/demo/demo.ts
@@ -4,52 +4,52 @@
// æŸ¥è¯¢æµ‹è¯•单表列表
export function listDemo(query: DemoQuery): AxiosPromise<DemoVO[]> {
    return request({
        url: '/demo/demo/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/demo/demo/list',
    method: 'get',
    params: query
  });
}
// è‡ªå®šä¹‰åˆ†é¡µæŽ¥å£
export function pageDemo(query: DemoQuery): AxiosPromise<DemoVO[]> {
    return request({
        url: '/demo/demo/page',
        method: 'get',
        params: query
    });
  return request({
    url: '/demo/demo/page',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢æµ‹è¯•单表详细
export function getDemo(id: string | number): AxiosPromise<DemoVO> {
    return request({
        url: '/demo/demo/' + id,
        method: 'get'
    });
  return request({
    url: '/demo/demo/' + id,
    method: 'get'
  });
}
// æ–°å¢žæµ‹è¯•单表
export function addDemo(data: DemoForm) {
    return request({
        url: '/demo/demo',
        method: 'post',
        data: data
    });
  return request({
    url: '/demo/demo',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹æµ‹è¯•单表
export function updateDemo(data: DemoForm) {
    return request({
        url: '/demo/demo',
        method: 'put',
        data: data
    });
  return request({
    url: '/demo/demo',
    method: 'put',
    data: data
  });
}
// åˆ é™¤æµ‹è¯•单表
export function delDemo(id: string | number | Array<string | number>) {
    return request({
        url: '/demo/demo/' + id,
        method: 'delete'
    });
  return request({
    url: '/demo/demo/' + id,
    method: 'delete'
  });
}
src/api/demo/tree.ts
@@ -4,43 +4,43 @@
// æŸ¥è¯¢æµ‹è¯•树表列表
export function listTree(query?: DemoTreeQuery): AxiosPromise<DemoTreeVO[]> {
    return request({
        url: '/demo/tree/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/demo/tree/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢æµ‹è¯•树表详细
export function getTree(id: string | number): AxiosPromise<DemoTreeVO> {
    return request({
        url: '/demo/tree/' + id,
        method: 'get'
    });
  return request({
    url: '/demo/tree/' + id,
    method: 'get'
  });
}
// æ–°å¢žæµ‹è¯•树表
export function addTree(data: DemoTreeForm) {
    return request({
        url: '/demo/tree',
        method: 'post',
        data: data
    });
  return request({
    url: '/demo/tree',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹æµ‹è¯•树表
export function updateTree(data: DemoTreeForm) {
    return request({
        url: '/demo/tree',
        method: 'put',
        data: data
    });
  return request({
    url: '/demo/tree',
    method: 'put',
    data: data
  });
}
// åˆ é™¤æµ‹è¯•树表
export function delTree(id: string | number | Array<string | number>) {
    return request({
        url: '/demo/tree/' + id,
        method: 'delete'
    });
  return request({
    url: '/demo/tree/' + id,
    method: 'delete'
  });
}
src/api/demo/types.ts
@@ -1,55 +1,55 @@
export interface DemoVO extends BaseEntity {
    id: number | string;
    deptId: number | string;
    userId: number | string;
    orderNum: number;
    testKey: string;
    value: string;
    createByName: string;
    updateByName?: any;
  id: number | string;
  deptId: number | string;
  userId: number | string;
  orderNum: number;
  testKey: string;
  value: string;
  createByName: string;
  updateByName?: any;
}
export interface DemoQuery extends PageQuery {
    testKey: string;
    value: string;
    createTime: string;
  testKey: string;
  value: string;
  createTime: string;
}
export interface DemoForm {
    id: string | number | undefined;
    deptId: string | number | undefined;
    userId: string | number | undefined;
    orderNum: number;
    testKey: string;
    value: string;
    version: string;
    ossConfigId: string | number | undefined;
    createTime?: string;
  id: string | number | undefined;
  deptId: string | number | undefined;
  userId: string | number | undefined;
  orderNum: number;
  testKey: string;
  value: string;
  version: string;
  ossConfigId: string | number | undefined;
  createTime?: string;
}
export interface DemoTreeVO extends BaseEntity {
    id: number | string;
    parentId: number | string;
    deptId: number | string;
    userId: number | string;
    treeName: string;
    children?: DemoTreeVO[];
  id: number | string;
  parentId: number | string;
  deptId: number | string;
  userId: number | string;
  treeName: string;
  children?: DemoTreeVO[];
}
export interface DemoTreeQuery {
    treeName: string;
    createTime: string;
  treeName: string;
  createTime: string;
}
export interface DemoTreeForm {
    id: string | number | undefined;
    parentId: string | number | undefined;
    deptId: string | number | undefined;
    userId: string | number | undefined;
    treeName: string;
  id: string | number | undefined;
  parentId: string | number | undefined;
  deptId: string | number | undefined;
  userId: string | number | undefined;
  treeName: string;
}
export interface DemoTreeOptionsType {
    id: string | number;
    treeName: string;
    children?: DemoTreeOptionsType[];
  id: string | number;
  treeName: string;
  children?: DemoTreeOptionsType[];
}
src/api/login.ts
@@ -8,74 +8,74 @@
 * @returns
 */
export function login(data: LoginData): AxiosPromise<LoginResult> {
    const params = {
        tenantId: data.tenantId,
        username: data.username.trim(),
        password: data.password,
        code: data.code,
        uuid: data.uuid
    };
    return request({
        url: '/auth/login',
        headers: {
            isToken: false
        },
        method: 'post',
        data: params
    });
  const params = {
    tenantId: data.tenantId,
    username: data.username.trim(),
    password: data.password,
    code: data.code,
    uuid: data.uuid
  };
  return request({
    url: '/auth/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: params
  });
}
// æ³¨å†Œæ–¹æ³•
export function register(data: any) {
    return request({
        url: '/auth/register',
        headers: {
            isToken: false
        },
        method: 'post',
        data: data
    });
  return request({
    url: '/auth/register',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  });
}
/**
 * æ³¨é”€
 */
export function logout() {
    return request({
        url: '/auth/logout',
        method: 'post'
    });
  return request({
    url: '/auth/logout',
    method: 'post'
  });
}
/**
 * èŽ·å–éªŒè¯ç 
 */
export function getCodeImg(): AxiosPromise<VerifyCodeResult> {
    return request({
        url: '/code',
        headers: {
            isToken: false
        },
        method: 'get',
        timeout: 20000
    });
  return request({
    url: '/code',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  });
}
// èŽ·å–ç”¨æˆ·è¯¦ç»†ä¿¡æ¯
export function getInfo(): AxiosPromise<UserInfo> {
    return request({
        url: '/system/user/getInfo',
        method: 'get'
    });
  return request({
    url: '/system/user/getInfo',
    method: 'get'
  });
}
// èŽ·å–ç§Ÿæˆ·åˆ—è¡¨
export function getTenantList(): AxiosPromise<TenantInfo> {
    return request({
        url: '/auth/tenant/list',
        headers: {
            isToken: false
        },
        method: 'get'
    });
  return request({
    url: '/auth/tenant/list',
    headers: {
      isToken: false
    },
    method: 'get'
  });
}
src/api/menu.ts
@@ -4,8 +4,8 @@
// èŽ·å–è·¯ç”±
export function getRouters(): AxiosPromise<RouteRecordRaw[]> {
    return request({
        url: '/system/menu/getRouters',
        method: 'get'
    });
  return request({
    url: '/system/menu/getRouters',
    method: 'get'
  });
}
src/api/monitor/cache/index.ts
@@ -4,56 +4,56 @@
// æŸ¥è¯¢ç¼“存详细
export function getCache(): AxiosPromise<CacheVO> {
    return request({
        url: '/monitor/cache',
        method: 'get'
    });
  return request({
    url: '/monitor/cache',
    method: 'get'
  });
}
// æŸ¥è¯¢ç¼“存名称列表
export function listCacheName() {
    return request({
        url: '/monitor/cache/getNames',
        method: 'get'
    });
  return request({
    url: '/monitor/cache/getNames',
    method: 'get'
  });
}
// æŸ¥è¯¢ç¼“存键名列表
export function listCacheKey(cacheName: string) {
    return request({
        url: '/monitor/cache/getKeys/' + cacheName,
        method: 'get'
    });
  return request({
    url: '/monitor/cache/getKeys/' + cacheName,
    method: 'get'
  });
}
// æŸ¥è¯¢ç¼“存内容
export function getCacheValue(cacheName: string, cacheKey: string) {
    return request({
        url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
        method: 'get'
    });
  return request({
    url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
    method: 'get'
  });
}
// æ¸…理指定名称缓存
export function clearCacheName(cacheName: string) {
    return request({
        url: '/monitor/cache/clearCacheName/' + cacheName,
        method: 'delete'
    });
  return request({
    url: '/monitor/cache/clearCacheName/' + cacheName,
    method: 'delete'
  });
}
// æ¸…理指定键名缓存
export function clearCacheKey(cacheName: string, cacheKey: string) {
    return request({
        url: '/monitor/cache/clearCacheKey/' + cacheName + '/' + cacheKey,
        method: 'delete'
    });
  return request({
    url: '/monitor/cache/clearCacheKey/' + cacheName + '/' + cacheKey,
    method: 'delete'
  });
}
// æ¸…理全部缓存
export function clearCacheAll() {
    return request({
        url: '/monitor/cache/clearCacheAll',
        method: 'delete'
    });
  return request({
    url: '/monitor/cache/clearCacheAll',
    method: 'delete'
  });
}
src/api/monitor/cache/types.ts
@@ -1,7 +1,7 @@
export interface CacheVO {
    commandStats: Array<{ name: string; value: string }>;
  commandStats: Array<{ name: string; value: string }>;
    dbSize: number;
  dbSize: number;
    info: { [key: string]: string };
  info: { [key: string]: string };
}
src/api/monitor/loginInfo/index.ts
@@ -4,33 +4,33 @@
// æŸ¥è¯¢ç™»å½•日志列表
export function list(query: LoginInfoQuery): AxiosPromise<LoginInfoVO[]> {
    return request({
        url: '/monitor/logininfor/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/monitor/logininfor/list',
    method: 'get',
    params: query
  });
}
// åˆ é™¤ç™»å½•日志
export function delLoginInfo(infoId: string | number | Array<string | number>) {
    return request({
        url: '/monitor/logininfor/' + infoId,
        method: 'delete'
    });
  return request({
    url: '/monitor/logininfor/' + infoId,
    method: 'delete'
  });
}
// è§£é”ç”¨æˆ·ç™»å½•状态
export function unlockLoginInfo(userName: string | Array<string>) {
    return request({
        url: '/monitor/logininfor/unlock/' + userName,
        method: 'get'
    });
  return request({
    url: '/monitor/logininfor/unlock/' + userName,
    method: 'get'
  });
}
// æ¸…空登录日志
export function cleanLoginInfo() {
    return request({
        url: '/monitor/logininfor/clean',
        method: 'delete'
    });
  return request({
    url: '/monitor/logininfor/clean',
    method: 'delete'
  });
}
src/api/monitor/loginInfo/types.ts
@@ -1,20 +1,20 @@
export interface LoginInfoVO {
    infoId: string | number;
    tenantId: string | number;
    userName: string;
    status: string;
    ipaddr: string;
    loginLocation: string;
    browser: string;
    os: string;
    msg: string;
    loginTime: string;
  infoId: string | number;
  tenantId: string | number;
  userName: string;
  status: string;
  ipaddr: string;
  loginLocation: string;
  browser: string;
  os: string;
  msg: string;
  loginTime: string;
}
export interface LoginInfoQuery extends PageQuery {
    ipaddr: string;
    userName: string;
    status: string;
    orderByColumn: string;
    isAsc: string;
  ipaddr: string;
  userName: string;
  status: string;
  orderByColumn: string;
  isAsc: string;
}
src/api/monitor/online/index.ts
@@ -4,17 +4,17 @@
// æŸ¥è¯¢åœ¨çº¿ç”¨æˆ·åˆ—表
export function list(query: OnlineQuery): AxiosPromise<OnlineVO[]> {
    return request({
        url: '/monitor/online/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/monitor/online/list',
    method: 'get',
    params: query
  });
}
// å¼ºé€€ç”¨æˆ·
export function forceLogout(tokenId: string) {
    return request({
        url: '/monitor/online/' + tokenId,
        method: 'delete'
    });
  return request({
    url: '/monitor/online/' + tokenId,
    method: 'delete'
  });
}
src/api/monitor/online/types.ts
@@ -1,15 +1,15 @@
export interface OnlineQuery extends PageQuery {
    ipaddr: string;
    userName: string;
  ipaddr: string;
  userName: string;
}
export interface OnlineVO extends BaseEntity {
    tokenId: string;
    deptName: string;
    userName: string;
    ipaddr: string;
    loginLocation: string;
    browser: string;
    os: string;
    loginTime: number;
  tokenId: string;
  deptName: string;
  userName: string;
  ipaddr: string;
  loginLocation: string;
  browser: string;
  os: string;
  loginTime: number;
}
src/api/monitor/operlog/index.ts
@@ -4,25 +4,25 @@
// æŸ¥è¯¢æ“ä½œæ—¥å¿—列表
export function list(query: OperLogQuery): AxiosPromise<OperLogVO[]> {
    return request({
        url: '/monitor/operlog/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/monitor/operlog/list',
    method: 'get',
    params: query
  });
}
// åˆ é™¤æ“ä½œæ—¥å¿—
export function delOperlog(operId: string | number | Array<string | number>) {
    return request({
        url: '/monitor/operlog/' + operId,
        method: 'delete'
    });
  return request({
    url: '/monitor/operlog/' + operId,
    method: 'delete'
  });
}
// æ¸…空操作日志
export function cleanOperlog() {
    return request({
        url: '/monitor/operlog/clean',
        method: 'delete'
    });
  return request({
    url: '/monitor/operlog/clean',
    method: 'delete'
  });
}
src/api/monitor/operlog/types.ts
@@ -1,52 +1,52 @@
export interface OperLogQuery extends PageQuery {
    title: string;
    operName: string;
    businessType: string;
    status: string;
    orderByColumn: string;
    isAsc: string;
  title: string;
  operName: string;
  businessType: string;
  status: string;
  orderByColumn: string;
  isAsc: string;
}
export interface OperLogVO extends BaseEntity {
    operId: string | number;
    tenantId: string;
    title: string;
    businessType: number;
    businessTypes: number[] | undefined;
    method: string;
    requestMethod: string;
    operatorType: number;
    operName: string;
    deptName: string;
    operUrl: string;
    operIp: string;
    operLocation: string;
    operParam: string;
    jsonResult: string;
    status: number;
    errorMsg: string;
    operTime: string;
    costTime: number;
  operId: string | number;
  tenantId: string;
  title: string;
  businessType: number;
  businessTypes: number[] | undefined;
  method: string;
  requestMethod: string;
  operatorType: number;
  operName: string;
  deptName: string;
  operUrl: string;
  operIp: string;
  operLocation: string;
  operParam: string;
  jsonResult: string;
  status: number;
  errorMsg: string;
  operTime: string;
  costTime: number;
}
export interface OperLogForm {
    operId: number | string | undefined;
    tenantId: string | number | undefined;
    title: string;
    businessType: number;
    businessTypes: number[] | undefined;
    method: string;
    requestMethod: string;
    operatorType: number;
    operName: string;
    deptName: string;
    operUrl: string;
    operIp: string;
    operLocation: string;
    operParam: string;
    jsonResult: string;
    status: number;
    errorMsg: string;
    operTime: string;
    costTime: number;
  operId: number | string | undefined;
  tenantId: string | number | undefined;
  title: string;
  businessType: number;
  businessTypes: number[] | undefined;
  method: string;
  requestMethod: string;
  operatorType: number;
  operName: string;
  deptName: string;
  operUrl: string;
  operIp: string;
  operLocation: string;
  operParam: string;
  jsonResult: string;
  status: number;
  errorMsg: string;
  operTime: string;
  costTime: number;
}
src/api/system/config/index.ts
@@ -4,71 +4,71 @@
// æŸ¥è¯¢å‚数列表
export function listConfig(query: ConfigQuery): AxiosPromise<ConfigVO[]> {
    return request({
        url: '/system/config/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/config/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢å‚数详细
export function getConfig(configId: string | number): AxiosPromise<ConfigVO> {
    return request({
        url: '/system/config/' + configId,
        method: 'get'
    });
  return request({
    url: '/system/config/' + configId,
    method: 'get'
  });
}
// æ ¹æ®å‚数键名查询参数值
export function getConfigKey(configKey: string): AxiosPromise<ConfigVO> {
    return request({
        url: '/system/config/configKey/' + configKey,
        method: 'get'
    });
  return request({
    url: '/system/config/configKey/' + configKey,
    method: 'get'
  });
}
// æ–°å¢žå‚数配置
export function addConfig(data: ConfigForm) {
    return request({
        url: '/system/config',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/config',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹å‚数配置
export function updateConfig(data: ConfigForm) {
    return request({
        url: '/system/config',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/config',
    method: 'put',
    data: data
  });
}
// ä¿®æ”¹å‚数配置
export function updateConfigByKey(key: string, value: any) {
    return request({
        url: '/system/config/updateByKey',
        method: 'put',
        data: {
            configKey: key,
            configValue: value
        }
    });
  return request({
    url: '/system/config/updateByKey',
    method: 'put',
    data: {
      configKey: key,
      configValue: value
    }
  });
}
// åˆ é™¤å‚数配置
export function delConfig(configId: string | number | Array<string | number>) {
    return request({
        url: '/system/config/' + configId,
        method: 'delete'
    });
  return request({
    url: '/system/config/' + configId,
    method: 'delete'
  });
}
// åˆ·æ–°å‚数缓存
export function refreshCache() {
    return request({
        url: '/system/config/refreshCache',
        method: 'delete'
    });
  return request({
    url: '/system/config/refreshCache',
    method: 'delete'
  });
}
src/api/system/config/types.ts
@@ -1,23 +1,23 @@
export interface ConfigVO extends BaseEntity {
    configId: number | string;
    configName: string;
    configKey: string;
    configValue: string;
    configType: string;
    remark: string;
  configId: number | string;
  configName: string;
  configKey: string;
  configValue: string;
  configType: string;
  remark: string;
}
export interface ConfigForm {
    configId: number | string | undefined;
    configName: string;
    configKey: string;
    configValue: string;
    configType: string;
    remark: string;
  configId: number | string | undefined;
  configName: string;
  configKey: string;
  configValue: string;
  configType: string;
  remark: string;
}
export interface ConfigQuery extends PageQuery {
    configName: string;
    configKey: string;
    configType: string;
  configName: string;
  configKey: string;
  configType: string;
}
src/api/system/dept/index.ts
@@ -4,59 +4,59 @@
// æŸ¥è¯¢éƒ¨é—¨åˆ—表
export const listDept = (query?: DeptQuery) => {
    return request({
        url: '/system/dept/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/dept/list',
    method: 'get',
    params: query
  });
};
// æŸ¥è¯¢éƒ¨é—¨åˆ—表(排除节点)
export const listDeptExcludeChild = (deptId: string | number): AxiosPromise<DeptVO[]> => {
    return request({
        url: '/system/dept/list/exclude/' + deptId,
        method: 'get'
    });
  return request({
    url: '/system/dept/list/exclude/' + deptId,
    method: 'get'
  });
};
// æŸ¥è¯¢éƒ¨é—¨è¯¦ç»†
export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => {
    return request({
        url: '/system/dept/' + deptId,
        method: 'get'
    });
  return request({
    url: '/system/dept/' + deptId,
    method: 'get'
  });
};
// æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
export const treeselect = (): AxiosPromise<DeptVO[]> => {
    return request({
        url: '/system/dept/treeselect',
        method: 'get'
    });
  return request({
    url: '/system/dept/treeselect',
    method: 'get'
  });
};
// æ–°å¢žéƒ¨é—¨
export const addDept = (data: DeptForm) => {
    return request({
        url: '/system/dept',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/dept',
    method: 'post',
    data: data
  });
};
// ä¿®æ”¹éƒ¨é—¨
export const updateDept = (data: DeptForm) => {
    return request({
        url: '/system/dept',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/dept',
    method: 'put',
    data: data
  });
};
// åˆ é™¤éƒ¨é—¨
export const delDept = (deptId: number | string) => {
    return request({
        url: '/system/dept/' + deptId,
        method: 'delete'
    });
  return request({
    url: '/system/dept/' + deptId,
    method: 'delete'
  });
};
src/api/system/dept/types.ts
@@ -2,44 +2,44 @@
 * éƒ¨é—¨æŸ¥è¯¢å‚æ•°
 */
export interface DeptQuery extends PageQuery {
    deptName?: string;
    status?: number;
  deptName?: string;
  status?: number;
}
/**
 * éƒ¨é—¨ç±»åž‹
 */
export interface DeptVO extends BaseEntity {
    id: number | string;
    parentName: string;
    parentId: number | string;
    children: DeptVO[];
    deptId: number | string;
    deptName: string;
    orderNum: number;
    leader: string;
    phone: string;
    email: string;
    status: string;
    delFlag: string;
    ancestors: string;
    menuId: string | number;
  id: number | string;
  parentName: string;
  parentId: number | string;
  children: DeptVO[];
  deptId: number | string;
  deptName: string;
  orderNum: number;
  leader: string;
  phone: string;
  email: string;
  status: string;
  delFlag: string;
  ancestors: string;
  menuId: string | number;
}
/**
 * éƒ¨é—¨è¡¨å•类型
 */
export interface DeptForm {
    parentName?: string;
    parentId?: number | string;
    children?: DeptForm[];
    deptId?: number | string;
    deptName?: string;
    orderNum?: number;
    leader?: string;
    phone?: string;
    email?: string;
    status?: string;
    delFlag?: string;
    ancestors?: string;
  parentName?: string;
  parentId?: number | string;
  children?: DeptForm[];
  deptId?: number | string;
  deptName?: string;
  orderNum?: number;
  leader?: string;
  phone?: string;
  email?: string;
  status?: string;
  delFlag?: string;
  ancestors?: string;
}
src/api/system/dict/data/index.ts
@@ -3,51 +3,51 @@
import { DictDataForm, DictDataQuery, DictDataVO } from './types';
// æ ¹æ®å­—典类型查询字典数据信息
export function getDicts(dictType: string): AxiosPromise<DictDataVO[]> {
    return request({
        url: '/system/dict/data/type/' + dictType,
        method: 'get'
    });
  return request({
    url: '/system/dict/data/type/' + dictType,
    method: 'get'
  });
}
// æŸ¥è¯¢å­—典数据列表
export function listData(query: DictDataQuery): AxiosPromise<DictDataVO[]> {
    return request({
        url: '/system/dict/data/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/dict/data/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢å­—典数据详细
export function getData(dictCode: string | number): AxiosPromise<DictDataVO> {
    return request({
        url: '/system/dict/data/' + dictCode,
        method: 'get'
    });
  return request({
    url: '/system/dict/data/' + dictCode,
    method: 'get'
  });
}
// æ–°å¢žå­—典数据
export function addData(data: DictDataForm) {
    return request({
        url: '/system/dict/data',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/dict/data',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹å­—典数据
export function updateData(data: DictDataForm) {
    return request({
        url: '/system/dict/data',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/dict/data',
    method: 'put',
    data: data
  });
}
// åˆ é™¤å­—典数据
export function delData(dictCode: string | number | Array<string | number>) {
    return request({
        url: '/system/dict/data/' + dictCode,
        method: 'delete'
    });
  return request({
    url: '/system/dict/data/' + dictCode,
    method: 'delete'
  });
}
src/api/system/dict/data/types.ts
@@ -1,29 +1,29 @@
export interface DictDataQuery extends PageQuery {
    dictName: string;
    dictType: string;
    status: string;
    dictLabel: string;
  dictName: string;
  dictType: string;
  status: string;
  dictLabel: string;
}
export interface DictDataVO extends BaseEntity {
    dictCode: string;
    dictLabel: string;
    dictValue: string;
    cssClass: string;
    listClass: ElTagType;
    dictSort: number;
    status: string;
    remark: string;
  dictCode: string;
  dictLabel: string;
  dictValue: string;
  cssClass: string;
  listClass: ElTagType;
  dictSort: number;
  status: string;
  remark: string;
}
export interface DictDataForm {
    dictType?: string;
    dictCode: string | undefined;
    dictLabel: string;
    dictValue: string;
    cssClass: string;
    listClass: ElTagType;
    dictSort: number;
    status: string;
    remark: string;
  dictType?: string;
  dictCode: string | undefined;
  dictLabel: string;
  dictValue: string;
  cssClass: string;
  listClass: ElTagType;
  dictSort: number;
  status: string;
  remark: string;
}
src/api/system/dict/type/index.ts
@@ -4,59 +4,59 @@
// æŸ¥è¯¢å­—典类型列表
export function listType(query: DictTypeQuery): AxiosPromise<DictTypeVO[]> {
    return request({
        url: '/system/dict/type/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/dict/type/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢å­—典类型详细
export function getType(dictId: number | string): AxiosPromise<DictTypeVO> {
    return request({
        url: '/system/dict/type/' + dictId,
        method: 'get'
    });
  return request({
    url: '/system/dict/type/' + dictId,
    method: 'get'
  });
}
// æ–°å¢žå­—典类型
export function addType(data: DictTypeForm) {
    return request({
        url: '/system/dict/type',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/dict/type',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹å­—典类型
export function updateType(data: DictTypeForm) {
    return request({
        url: '/system/dict/type',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/dict/type',
    method: 'put',
    data: data
  });
}
// åˆ é™¤å­—典类型
export function delType(dictId: string | number | Array<string | number>) {
    return request({
        url: '/system/dict/type/' + dictId,
        method: 'delete'
    });
  return request({
    url: '/system/dict/type/' + dictId,
    method: 'delete'
  });
}
// åˆ·æ–°å­—典缓存
export function refreshCache() {
    return request({
        url: '/system/dict/type/refreshCache',
        method: 'delete'
    });
  return request({
    url: '/system/dict/type/refreshCache',
    method: 'delete'
  });
}
// èŽ·å–å­—å…¸é€‰æ‹©æ¡†åˆ—è¡¨
export function optionselect(): AxiosPromise<DictTypeVO[]> {
    return request({
        url: '/system/dict/type/optionselect',
        method: 'get'
    });
  return request({
    url: '/system/dict/type/optionselect',
    method: 'get'
  });
}
src/api/system/dict/type/types.ts
@@ -1,21 +1,21 @@
export interface DictTypeVO extends BaseEntity {
    dictId: number | string;
    dictName: string;
    dictType: string;
    status: string;
    remark: string;
  dictId: number | string;
  dictName: string;
  dictType: string;
  status: string;
  remark: string;
}
export interface DictTypeForm {
    dictId: number | string | undefined;
    dictName: string;
    dictType: string;
    status: string;
    remark: string;
  dictId: number | string | undefined;
  dictName: string;
  dictType: string;
  status: string;
  remark: string;
}
export interface DictTypeQuery extends PageQuery {
    dictName: string;
    dictType: string;
    status: string;
  dictName: string;
  dictType: string;
  status: string;
}
src/api/system/menu/index.ts
@@ -4,67 +4,67 @@
// æŸ¥è¯¢èœå•列表
export const listMenu = (query?: MenuQuery): AxiosPromise<MenuVO[]> => {
    return request({
        url: '/system/menu/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/menu/list',
    method: 'get',
    params: query
  });
};
// æŸ¥è¯¢èœå•详细
export const getMenu = (menuId: string | number): AxiosPromise<MenuVO> => {
    return request({
        url: '/system/menu/' + menuId,
        method: 'get'
    });
  return request({
    url: '/system/menu/' + menuId,
    method: 'get'
  });
};
// æŸ¥è¯¢èœå•下拉树结构
export const treeselect = (): AxiosPromise<MenuTreeOption[]> => {
    return request({
        url: '/system/menu/treeselect',
        method: 'get'
    });
  return request({
    url: '/system/menu/treeselect',
    method: 'get'
  });
};
// æ ¹æ®è§’色ID查询菜单下拉树结构
export const roleMenuTreeselect = (roleId: string | number): AxiosPromise<RoleMenuTree> => {
    return request({
        url: '/system/menu/roleMenuTreeselect/' + roleId,
        method: 'get'
    });
  return request({
    url: '/system/menu/roleMenuTreeselect/' + roleId,
    method: 'get'
  });
};
// æ ¹æ®è§’色ID查询菜单下拉树结构
export const tenantPackageMenuTreeselect = (packageId: string | number): AxiosPromise<RoleMenuTree> => {
    return request({
        url: '/system/menu/tenantPackageMenuTreeselect/' + packageId,
        method: 'get'
    });
  return request({
    url: '/system/menu/tenantPackageMenuTreeselect/' + packageId,
    method: 'get'
  });
};
// æ–°å¢žèœå•
export const addMenu = (data: MenuForm) => {
    return request({
        url: '/system/menu',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/menu',
    method: 'post',
    data: data
  });
};
// ä¿®æ”¹èœå•
export const updateMenu = (data: MenuForm) => {
    return request({
        url: '/system/menu',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/menu',
    method: 'put',
    data: data
  });
};
// åˆ é™¤èœå•
export const delMenu = (menuId: string | number) => {
    return request({
        url: '/system/menu/' + menuId,
        method: 'delete'
    });
  return request({
    url: '/system/menu/' + menuId,
    method: 'delete'
  });
};
src/api/system/menu/types.ts
@@ -4,66 +4,66 @@
 * èœå•树形结构类型
 */
export interface MenuTreeOption {
    id: string | number;
    label: string;
    parentId: string | number;
    weight: number;
    children?: MenuTreeOption[];
  id: string | number;
  label: string;
  parentId: string | number;
  weight: number;
  children?: MenuTreeOption[];
}
export interface RoleMenuTree {
    menus: MenuTreeOption[];
    checkedKeys: string[];
  menus: MenuTreeOption[];
  checkedKeys: string[];
}
/**
 * èœå•查询参数类型
 */
export interface MenuQuery {
    keywords?: string;
    menuName?: string;
    status?: string;
  keywords?: string;
  menuName?: string;
  status?: string;
}
/**
 * èœå•视图对象类型
 */
export interface MenuVO extends BaseEntity {
    parentName: string;
    parentId: string | number;
    children: MenuVO[];
    menuId: string | number;
    menuName: string;
    orderNum: number;
    path: string;
    component: string;
    queryParam: string;
    isFrame: string;
    isCache: string;
    menuType: MenuTypeEnum;
    visible: string;
    status: string;
    icon: string;
    remark: string;
  parentName: string;
  parentId: string | number;
  children: MenuVO[];
  menuId: string | number;
  menuName: string;
  orderNum: number;
  path: string;
  component: string;
  queryParam: string;
  isFrame: string;
  isCache: string;
  menuType: MenuTypeEnum;
  visible: string;
  status: string;
  icon: string;
  remark: string;
}
export interface MenuForm {
    parentName?: string;
    parentId?: string | number;
    children?: MenuForm[];
    menuId?: string | number;
    menuName: string;
    orderNum: number;
    path: string;
    component?: string;
    queryParam?: string;
    isFrame?: string;
    isCache?: string;
    menuType?: MenuTypeEnum;
    visible?: string;
    status?: string;
    icon?: string;
    remark?: string;
    query?: string;
    perms?: string;
  parentName?: string;
  parentId?: string | number;
  children?: MenuForm[];
  menuId?: string | number;
  menuName: string;
  orderNum: number;
  path: string;
  component?: string;
  queryParam?: string;
  isFrame?: string;
  isCache?: string;
  menuType?: MenuTypeEnum;
  visible?: string;
  status?: string;
  icon?: string;
  remark?: string;
  query?: string;
  perms?: string;
}
src/api/system/notice/index.ts
@@ -3,43 +3,43 @@
import { AxiosPromise } from 'axios';
// æŸ¥è¯¢å…¬å‘Šåˆ—表
export function listNotice(query: NoticeQuery): AxiosPromise<NoticeVO[]> {
    return request({
        url: '/system/notice/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/notice/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢å…¬å‘Šè¯¦ç»†
export function getNotice(noticeId: string | number): AxiosPromise<NoticeVO> {
    return request({
        url: '/system/notice/' + noticeId,
        method: 'get'
    });
  return request({
    url: '/system/notice/' + noticeId,
    method: 'get'
  });
}
// æ–°å¢žå…¬å‘Š
export function addNotice(data: NoticeForm) {
    return request({
        url: '/system/notice',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/notice',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹å…¬å‘Š
export function updateNotice(data: NoticeForm) {
    return request({
        url: '/system/notice',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/notice',
    method: 'put',
    data: data
  });
}
// åˆ é™¤å…¬å‘Š
export function delNotice(noticeId: string | number | Array<string | number>) {
    return request({
        url: '/system/notice/' + noticeId,
        method: 'delete'
    });
  return request({
    url: '/system/notice/' + noticeId,
    method: 'delete'
  });
}
src/api/system/notice/types.ts
@@ -1,26 +1,26 @@
export interface NoticeVO extends BaseEntity {
    noticeId: number;
    noticeTitle: string;
    noticeType: string;
    noticeContent: string;
    status: string;
    remark: string;
    createByName: string;
  noticeId: number;
  noticeTitle: string;
  noticeType: string;
  noticeContent: string;
  status: string;
  remark: string;
  createByName: string;
}
export interface NoticeQuery extends PageQuery {
    noticeTitle: string;
    createByName: string;
    status: string;
    noticeType: string;
  noticeTitle: string;
  createByName: string;
  status: string;
  noticeType: string;
}
export interface NoticeForm {
    noticeId: number | string | undefined;
    noticeTitle: string;
    noticeType: string;
    noticeContent: string;
    status: string;
    remark: string;
    createByName: string;
  noticeId: number | string | undefined;
  noticeTitle: string;
  noticeType: string;
  noticeContent: string;
  status: string;
  remark: string;
  createByName: string;
}
src/api/system/oss/index.ts
@@ -4,25 +4,25 @@
// æŸ¥è¯¢OSS对象存储列表
export function listOss(query: OssQuery): AxiosPromise<OssVO[]> {
    return request({
        url: '/system/oss/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/oss/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢OSS对象基于id串
export function listByIds(ossId: string | number): AxiosPromise<OssVO[]> {
    return request({
        url: '/system/oss/listByIds/' + ossId,
        method: 'get'
    });
  return request({
    url: '/system/oss/listByIds/' + ossId,
    method: 'get'
  });
}
// åˆ é™¤OSS对象存储
export function delOss(ossId: string | number | Array<string | number>) {
    return request({
        url: '/system/oss/' + ossId,
        method: 'delete'
    });
  return request({
    url: '/system/oss/' + ossId,
    method: 'delete'
  });
}
src/api/system/oss/types.ts
@@ -1,22 +1,22 @@
export interface OssVO extends BaseEntity {
    ossId: string | number;
    fileName: string;
    originalName: string;
    fileSuffix: string;
    url: string;
    createByName: string;
    service: string;
  ossId: string | number;
  fileName: string;
  originalName: string;
  fileSuffix: string;
  url: string;
  createByName: string;
  service: string;
}
export interface OssQuery extends PageQuery {
    fileName: string;
    originalName: string;
    fileSuffix: string;
    createTime: string;
    service: string;
    orderByColumn: string;
    isAsc: string;
  fileName: string;
  originalName: string;
  fileSuffix: string;
  createTime: string;
  service: string;
  orderByColumn: string;
  isAsc: string;
}
export interface OssForm {
    file: undefined | string;
  file: undefined | string;
}
src/api/system/ossConfig/index.ts
@@ -4,57 +4,57 @@
// æŸ¥è¯¢å¯¹è±¡å­˜å‚¨é…ç½®åˆ—表
export function listOssConfig(query: OssConfigQuery): AxiosPromise<OssConfigVO[]> {
    return request({
        url: '/system/oss/config/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/oss/config/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢å¯¹è±¡å­˜å‚¨é…ç½®è¯¦ç»†
export function getOssConfig(ossConfigId: string | number): AxiosPromise<OssConfigVO> {
    return request({
        url: '/system/oss/config/' + ossConfigId,
        method: 'get'
    });
  return request({
    url: '/system/oss/config/' + ossConfigId,
    method: 'get'
  });
}
// æ–°å¢žå¯¹è±¡å­˜å‚¨é…ç½®
export function addOssConfig(data: OssConfigForm) {
    return request({
        url: '/system/oss/config',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/oss/config',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹å¯¹è±¡å­˜å‚¨é…ç½®
export function updateOssConfig(data: OssConfigForm) {
    return request({
        url: '/system/oss/config',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/oss/config',
    method: 'put',
    data: data
  });
}
// åˆ é™¤å¯¹è±¡å­˜å‚¨é…ç½®
export function delOssConfig(ossConfigId: string | number | Array<string | number>) {
    return request({
        url: '/system/oss/config/' + ossConfigId,
        method: 'delete'
    });
  return request({
    url: '/system/oss/config/' + ossConfigId,
    method: 'delete'
  });
}
// å¯¹è±¡å­˜å‚¨çŠ¶æ€ä¿®æ”¹
export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) {
    const data = {
        ossConfigId,
        status,
        configKey
    };
    return request({
        url: '/system/oss/config/changeStatus',
        method: 'put',
        data: data
    });
  const data = {
    ossConfigId,
    status,
    configKey
  };
  return request({
    url: '/system/oss/config/changeStatus',
    method: 'put',
    data: data
  });
}
src/api/system/ossConfig/types.ts
@@ -1,38 +1,38 @@
export interface OssConfigVO extends BaseEntity {
    ossConfigId: number | string;
    configKey: string;
    accessKey: string;
    secretKey: string;
    bucketName: string;
    prefix: string;
    endpoint: string;
    domain: string;
    isHttps: string;
    region: string;
    status: string;
    ext1: string;
    remark: string;
    accessPolicy: string;
  ossConfigId: number | string;
  configKey: string;
  accessKey: string;
  secretKey: string;
  bucketName: string;
  prefix: string;
  endpoint: string;
  domain: string;
  isHttps: string;
  region: string;
  status: string;
  ext1: string;
  remark: string;
  accessPolicy: string;
}
export interface OssConfigQuery extends PageQuery {
    configKey: string;
    bucketName: string;
    status: string;
  configKey: string;
  bucketName: string;
  status: string;
}
export interface OssConfigForm {
    ossConfigId: string | number | undefined;
    configKey: string;
    accessKey: string;
    secretKey: string;
    bucketName: string;
    prefix: string;
    endpoint: string;
    domain: string;
    isHttps: string;
    accessPolicy: string;
    region: string;
    status: string;
    remark: string;
  ossConfigId: string | number | undefined;
  configKey: string;
  accessKey: string;
  secretKey: string;
  bucketName: string;
  prefix: string;
  endpoint: string;
  domain: string;
  isHttps: string;
  accessPolicy: string;
  region: string;
  status: string;
  remark: string;
}
src/api/system/post/index.ts
@@ -4,43 +4,43 @@
// æŸ¥è¯¢å²—位列表
export function listPost(query: PostQuery): AxiosPromise<PostVO[]> {
    return request({
        url: '/system/post/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/post/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢å²—位详细
export function getPost(postId: string | number): AxiosPromise<PostVO> {
    return request({
        url: '/system/post/' + postId,
        method: 'get'
    });
  return request({
    url: '/system/post/' + postId,
    method: 'get'
  });
}
// æ–°å¢žå²—位
export function addPost(data: PostForm) {
    return request({
        url: '/system/post',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/post',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹å²—位
export function updatePost(data: PostForm) {
    return request({
        url: '/system/post',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/post',
    method: 'put',
    data: data
  });
}
// åˆ é™¤å²—位
export function delPost(postId: string | number | (string | number)[]) {
    return request({
        url: '/system/post/' + postId,
        method: 'delete'
    });
  return request({
    url: '/system/post/' + postId,
    method: 'delete'
  });
}
src/api/system/post/types.ts
@@ -1,23 +1,23 @@
export interface PostVO extends BaseEntity {
    postId: number | string;
    postCode: string;
    postName: string;
    postSort: number;
    status: string;
    remark: string;
  postId: number | string;
  postCode: string;
  postName: string;
  postSort: number;
  status: string;
  remark: string;
}
export interface PostForm {
    postId: number | string | undefined;
    postCode: string;
    postName: string;
    postSort: number;
    status: string;
    remark: string;
  postId: number | string | undefined;
  postCode: string;
  postName: string;
  postSort: number;
  status: string;
  remark: string;
}
export interface PostQuery extends PageQuery {
    postCode: string;
    postName: string;
    status: string;
  postCode: string;
  postName: string;
  status: string;
}
src/api/system/role/index.ts
@@ -5,32 +5,32 @@
import request from '@/utils/request';
export const listRole = (query: RoleQuery): AxiosPromise<RoleVO[]> => {
    return request({
        url: '/system/role/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/role/list',
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢è§’色详细
 */
export const getRole = (roleId: string | number): AxiosPromise<RoleVO> => {
    return request({
        url: '/system/role/' + roleId,
        method: 'get'
    });
  return request({
    url: '/system/role/' + roleId,
    method: 'get'
  });
};
/**
 * æ–°å¢žè§’色
 */
export const addRole = (data: any) => {
    return request({
        url: '/system/role',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/role',
    method: 'post',
    data: data
  });
};
/**
@@ -38,107 +38,107 @@
 * @param data
 */
export const updateRole = (data: any) => {
    return request({
        url: '/system/role',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/role',
    method: 'put',
    data: data
  });
};
/**
 * è§’色数据权限
 */
export const dataScope = (data: any) => {
    return request({
        url: '/system/role/dataScope',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/role/dataScope',
    method: 'put',
    data: data
  });
};
/**
 * è§’色状态修改
 */
export const changeRoleStatus = (roleId: string | number, status: string) => {
    const data = {
        roleId,
        status
    };
    return request({
        url: '/system/role/changeStatus',
        method: 'put',
        data: data
    });
  const data = {
    roleId,
    status
  };
  return request({
    url: '/system/role/changeStatus',
    method: 'put',
    data: data
  });
};
/**
 * åˆ é™¤è§’色
 */
export const delRole = (roleId: Array<string | number> | string | number) => {
    return request({
        url: '/system/role/' + roleId,
        method: 'delete'
    });
  return request({
    url: '/system/role/' + roleId,
    method: 'delete'
  });
};
/**
 * æŸ¥è¯¢è§’色已授权用户列表
 */
export const allocatedUserList = (query: UserQuery): AxiosPromise<UserVO[]> => {
    return request({
        url: '/system/role/authUser/allocatedList',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/role/authUser/allocatedList',
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢è§’色未授权用户列表
 */
export const unallocatedUserList = (query: UserQuery): AxiosPromise<UserVO[]> => {
    return request({
        url: '/system/role/authUser/unallocatedList',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/role/authUser/unallocatedList',
    method: 'get',
    params: query
  });
};
/**
 * å–消用户授权角色
 */
export const authUserCancel = (data: any) => {
    return request({
        url: '/system/role/authUser/cancel',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/role/authUser/cancel',
    method: 'put',
    data: data
  });
};
/**
 * æ‰¹é‡å–消用户授权角色
 */
export const authUserCancelAll = (data: any) => {
    return request({
        url: '/system/role/authUser/cancelAll',
        method: 'put',
        params: data
    });
  return request({
    url: '/system/role/authUser/cancelAll',
    method: 'put',
    params: data
  });
};
/**
 * æŽˆæƒç”¨æˆ·é€‰æ‹©
 */
export const authUserSelectAll = (data: any) => {
    return request({
        url: '/system/role/authUser/selectAll',
        method: 'put',
        params: data
    });
  return request({
    url: '/system/role/authUser/selectAll',
    method: 'put',
    params: data
  });
};
// æ ¹æ®è§’色ID查询部门树结构
export const deptTreeSelect = (roleId: string | number): AxiosPromise<RoleDeptTree> => {
    return request({
        url: '/system/role/deptTree/' + roleId,
        method: 'get'
    });
  return request({
    url: '/system/role/deptTree/' + roleId,
    method: 'get'
  });
};
src/api/system/role/types.ts
@@ -2,51 +2,51 @@
 * èœå•树形结构类型
 */
export interface DeptTreeOption {
    id: string;
    label: string;
    parentId: string;
    weight: number;
    children?: DeptTreeOption[];
  id: string;
  label: string;
  parentId: string;
  weight: number;
  children?: DeptTreeOption[];
}
export interface RoleDeptTree {
    checkedKeys: string[];
    depts: DeptTreeOption[];
  checkedKeys: string[];
  depts: DeptTreeOption[];
}
export interface RoleVO extends BaseEntity {
    roleId: string | number;
    roleName: string;
    roleKey: string;
    roleSort: number;
    dataScope: string;
    menuCheckStrictly: boolean;
    deptCheckStrictly: boolean;
    status: string;
    delFlag: string;
    remark?: any;
    flag: boolean;
    menuIds?: Array<string | number>;
    deptIds?: Array<string | number>;
    admin: boolean;
  roleId: string | number;
  roleName: string;
  roleKey: string;
  roleSort: number;
  dataScope: string;
  menuCheckStrictly: boolean;
  deptCheckStrictly: boolean;
  status: string;
  delFlag: string;
  remark?: any;
  flag: boolean;
  menuIds?: Array<string | number>;
  deptIds?: Array<string | number>;
  admin: boolean;
}
export interface RoleQuery extends PageQuery {
    roleName: string;
    roleKey: string;
    status: string;
  roleName: string;
  roleKey: string;
  status: string;
}
export interface RoleForm {
    roleName: string;
    roleKey: string;
    roleSort: number;
    status: string;
    menuCheckStrictly: boolean;
    deptCheckStrictly: boolean;
    remark: string;
    dataScope?: number;
    roleId: string | undefined;
    menuIds: Array<string | number>;
    deptIds: Array<string | number>;
  roleName: string;
  roleKey: string;
  roleSort: number;
  status: string;
  menuCheckStrictly: boolean;
  deptCheckStrictly: boolean;
  remark: string;
  dataScope?: number;
  roleId: string | undefined;
  menuIds: Array<string | number>;
  deptIds: Array<string | number>;
}
src/api/system/tenant/index.ts
@@ -4,86 +4,86 @@
// æŸ¥è¯¢ç§Ÿæˆ·åˆ—表
export function listTenant(query: TenantQuery): AxiosPromise<TenantVO[]> {
    return request({
        url: '/system/tenant/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/tenant/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢ç§Ÿæˆ·è¯¦ç»†
export function getTenant(id: string | number): AxiosPromise<TenantVO> {
    return request({
        url: '/system/tenant/' + id,
        method: 'get'
    });
  return request({
    url: '/system/tenant/' + id,
    method: 'get'
  });
}
// æ–°å¢žç§Ÿæˆ·
export function addTenant(data: TenantForm) {
    return request({
        url: '/system/tenant',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/tenant',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹ç§Ÿæˆ·
export function updateTenant(data: TenantForm) {
    return request({
        url: '/system/tenant',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/tenant',
    method: 'put',
    data: data
  });
}
// ç§Ÿæˆ·çŠ¶æ€ä¿®æ”¹
export function changeTenantStatus(id: string | number, tenantId: string | number, status: string) {
    const data = {
        id,
        tenantId,
        status
    };
    return request({
        url: '/system/tenant/changeStatus',
        method: 'put',
        data: data
    });
  const data = {
    id,
    tenantId,
    status
  };
  return request({
    url: '/system/tenant/changeStatus',
    method: 'put',
    data: data
  });
}
// åˆ é™¤ç§Ÿæˆ·
export function delTenant(id: string | number | Array<string | number>) {
    return request({
        url: '/system/tenant/' + id,
        method: 'delete'
    });
  return request({
    url: '/system/tenant/' + id,
    method: 'delete'
  });
}
// åŠ¨æ€åˆ‡æ¢ç§Ÿæˆ·
export function dynamicTenant(tenantId: string | number) {
    return request({
        url: '/system/tenant/dynamic/' + tenantId,
        method: 'get'
    });
  return request({
    url: '/system/tenant/dynamic/' + tenantId,
    method: 'get'
  });
}
// æ¸…除动态租户
export function dynamicClear() {
    return request({
        url: '/system/tenant/dynamic/clear',
        method: 'get'
    });
  return request({
    url: '/system/tenant/dynamic/clear',
    method: 'get'
  });
}
// åŒæ­¥ç§Ÿæˆ·å¥—餐
export function syncTenantPackage(tenantId: string | number, packageId: string | number) {
    const data = {
        tenantId,
        packageId
    };
    return request({
        url: '/system/tenant/syncTenantPackage',
        method: 'get',
        params: data
    });
  const data = {
    tenantId,
    packageId
  };
  return request({
    url: '/system/tenant/syncTenantPackage',
    method: 'get',
    params: data
  });
}
src/api/system/tenant/types.ts
@@ -1,46 +1,46 @@
export interface TenantVO extends BaseEntity {
    id: number | string;
    tenantId: number | string;
    username: string;
    contactUserName: string;
    contactPhone: string;
    companyName: string;
    licenseNumber: string;
    address: string;
    domain: string;
    intro: string;
    remark: string;
    packageId: string | number;
    expireTime: string;
    accountCount: number;
    status: string;
  id: number | string;
  tenantId: number | string;
  username: string;
  contactUserName: string;
  contactPhone: string;
  companyName: string;
  licenseNumber: string;
  address: string;
  domain: string;
  intro: string;
  remark: string;
  packageId: string | number;
  expireTime: string;
  accountCount: number;
  status: string;
}
export interface TenantQuery extends PageQuery {
    tenantId: string | number;
  tenantId: string | number;
    contactUserName: string;
  contactUserName: string;
    contactPhone: string;
  contactPhone: string;
    companyName: string;
  companyName: string;
}
export interface TenantForm {
    id: number | string | undefined;
    tenantId: number | string | undefined;
    username: string;
    password: string;
    contactUserName: string;
    contactPhone: string;
    companyName: string;
    licenseNumber: string;
    domain: string;
    address: string;
    intro: string;
    remark: string;
    packageId: string | number;
    expireTime: string;
    accountCount: number;
    status: string;
  id: number | string | undefined;
  tenantId: number | string | undefined;
  username: string;
  password: string;
  contactUserName: string;
  contactPhone: string;
  companyName: string;
  licenseNumber: string;
  domain: string;
  address: string;
  intro: string;
  remark: string;
  packageId: string | number;
  expireTime: string;
  accountCount: number;
  status: string;
}
src/api/system/tenantPackage/index.ts
@@ -4,56 +4,56 @@
// æŸ¥è¯¢ç§Ÿæˆ·å¥—餐列表
export function listTenantPackage(query?: TenantPkgQuery): AxiosPromise<TenantPkgVO[]> {
    return request({
        url: '/system/tenant/package/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/tenant/package/list',
    method: 'get',
    params: query
  });
}
// æŸ¥è¯¢ç§Ÿæˆ·å¥—餐详细
export function getTenantPackage(packageId: string | number): AxiosPromise<TenantPkgVO> {
    return request({
        url: '/system/tenant/package/' + packageId,
        method: 'get'
    });
  return request({
    url: '/system/tenant/package/' + packageId,
    method: 'get'
  });
}
// æ–°å¢žç§Ÿæˆ·å¥—餐
export function addTenantPackage(data: TenantPkgForm) {
    return request({
        url: '/system/tenant/package',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/tenant/package',
    method: 'post',
    data: data
  });
}
// ä¿®æ”¹ç§Ÿæˆ·å¥—餐
export function updateTenantPackage(data: TenantPkgForm) {
    return request({
        url: '/system/tenant/package',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/tenant/package',
    method: 'put',
    data: data
  });
}
// ç§Ÿæˆ·å¥—餐状态修改
export function changePackageStatus(packageId: number | string, status: string) {
    const data = {
        packageId,
        status
    };
    return request({
        url: '/system/tenant/package/changeStatus',
        method: 'put',
        data: data
    });
  const data = {
    packageId,
    status
  };
  return request({
    url: '/system/tenant/package/changeStatus',
    method: 'put',
    data: data
  });
}
// åˆ é™¤ç§Ÿæˆ·å¥—餐
export function delTenantPackage(packageId: string | number | Array<string | number>) {
    return request({
        url: '/system/tenant/package/' + packageId,
        method: 'delete'
    });
  return request({
    url: '/system/tenant/package/' + packageId,
    method: 'delete'
  });
}
src/api/system/tenantPackage/types.ts
@@ -1,22 +1,22 @@
export interface TenantPkgVO extends BaseEntity {
    packageId: string | number;
    packageName: string;
    menuIds: string;
    remark: string;
    menuCheckStrictly: boolean;
    status: string;
  packageId: string | number;
  packageName: string;
  menuIds: string;
  remark: string;
  menuCheckStrictly: boolean;
  status: string;
}
export interface TenantPkgQuery extends PageQuery {
    packageName: string;
    status: string;
  packageName: string;
  status: string;
}
export interface TenantPkgForm {
    packageId: string | number | undefined;
    packageName: string;
    menuIds: string;
    remark: string;
    menuCheckStrictly: boolean;
    status: string;
  packageId: string | number | undefined;
  packageName: string;
  menuIds: string;
  remark: string;
  menuCheckStrictly: boolean;
  status: string;
}
src/api/system/user/index.ts
@@ -10,11 +10,11 @@
 * @param query
 */
export function listUser(query: UserQuery): AxiosPromise<UserVO[]> {
    return request({
        url: '/system/user/list',
        method: 'get',
        params: query
    });
  return request({
    url: '/system/user/list',
    method: 'get',
    params: query
  });
}
/**
@@ -22,32 +22,32 @@
 * @param userId
 */
export function getUser(userId?: string | number): AxiosPromise<UserInfoVO> {
    return request({
        url: '/system/user/' + parseStrEmpty(userId),
        method: 'get'
    });
  return request({
    url: '/system/user/' + parseStrEmpty(userId),
    method: 'get'
  });
}
/**
 * æ–°å¢žç”¨æˆ·
 */
export function addUser(data: UserForm) {
    return request({
        url: '/system/user',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/user',
    method: 'post',
    data: data
  });
}
/**
 * ä¿®æ”¹ç”¨æˆ·
 */
export function updateUser(data: UserForm) {
    return request({
        url: '/system/user',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/user',
    method: 'put',
    data: data
  });
}
/**
@@ -55,10 +55,10 @@
 * @param userId ç”¨æˆ·ID
 */
export function delUser(userId: Array<string | number> | string | number) {
    return request({
        url: '/system/user/' + userId,
        method: 'delete'
    });
  return request({
    url: '/system/user/' + userId,
    method: 'delete'
  });
}
/**
@@ -67,15 +67,15 @@
 * @param password å¯†ç 
 */
export function resetUserPwd(userId: string | number, password: string) {
    const data = {
        userId,
        password
    };
    return request({
        url: '/system/user/resetPwd',
        method: 'put',
        data: data
    });
  const data = {
    userId,
    password
  };
  return request({
    url: '/system/user/resetPwd',
    method: 'put',
    data: data
  });
}
/**
@@ -84,25 +84,25 @@
 * @param status ç”¨æˆ·çŠ¶æ€
 */
export function changeUserStatus(userId: number | string, status: string) {
    const data = {
        userId,
        status
    };
    return request({
        url: '/system/user/changeStatus',
        method: 'put',
        data: data
    });
  const data = {
    userId,
    status
  };
  return request({
    url: '/system/user/changeStatus',
    method: 'put',
    data: data
  });
}
/**
 * æŸ¥è¯¢ç”¨æˆ·ä¸ªäººä¿¡æ¯
 */
export function getUserProfile(): AxiosPromise<UserInfoVO> {
    return request({
        url: '/system/user/profile',
        method: 'get'
    });
  return request({
    url: '/system/user/profile',
    method: 'get'
  });
}
/**
@@ -110,11 +110,11 @@
 * @param data ç”¨æˆ·ä¿¡æ¯
 */
export function updateUserProfile(data: UserForm) {
    return request({
        url: '/system/user/profile',
        method: 'put',
        data: data
    });
  return request({
    url: '/system/user/profile',
    method: 'put',
    data: data
  });
}
/**
@@ -123,15 +123,15 @@
 * @param newPassword æ–°å¯†ç 
 */
export function updateUserPwd(oldPassword: string, newPassword: string) {
    const data = {
        oldPassword,
        newPassword
    };
    return request({
        url: '/system/user/profile/updatePwd',
        method: 'put',
        params: data
    });
  const data = {
    oldPassword,
    newPassword
  };
  return request({
    url: '/system/user/profile/updatePwd',
    method: 'put',
    params: data
  });
}
/**
@@ -139,11 +139,11 @@
 * @param data å¤´åƒæ–‡ä»¶
 */
export function uploadAvatar(data: FormData) {
    return request({
        url: '/system/user/profile/avatar',
        method: 'post',
        data: data
    });
  return request({
    url: '/system/user/profile/avatar',
    method: 'post',
    data: data
  });
}
/**
@@ -151,10 +151,10 @@
 * @param userId ç”¨æˆ·ID
 */
export function getAuthRole(userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> {
    return request({
        url: '/system/user/authRole/' + userId,
        method: 'get'
    });
  return request({
    url: '/system/user/authRole/' + userId,
    method: 'get'
  });
}
/**
@@ -162,19 +162,19 @@
 * @param data ç”¨æˆ·ID
 */
export function updateAuthRole(data: { userId: string; roleIds: string }) {
    return request({
        url: '/system/user/authRole',
        method: 'put',
        params: data
    });
  return request({
    url: '/system/user/authRole',
    method: 'put',
    params: data
  });
}
/**
 * æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
 */
export function deptTreeSelect(): AxiosPromise<DeptVO[]> {
    return request({
        url: '/system/user/deptTree',
        method: 'get'
    });
  return request({
    url: '/system/user/deptTree',
    method: 'get'
  });
}
src/api/system/user/types.ts
@@ -6,79 +6,79 @@
 * ç”¨æˆ·ä¿¡æ¯
 */
export interface UserInfo {
    user: UserVO;
    roles: string[];
    permissions: string[];
  user: UserVO;
  roles: string[];
  permissions: string[];
}
/**
 * ç”¨æˆ·æŸ¥è¯¢å¯¹è±¡ç±»åž‹
 */
export interface UserQuery extends PageQuery {
    userName?: string;
    phonenumber?: string;
    status?: string;
    deptId?: string | number;
    roleId?: string | number;
  userName?: string;
  phonenumber?: string;
  status?: string;
  deptId?: string | number;
  roleId?: string | number;
}
/**
 * ç”¨æˆ·è¿”回对象
 */
export interface UserVO extends BaseEntity {
    userId: string | number;
    deptId: number;
    userName: string;
    nickName: string;
    userType: string;
    email: string;
    phonenumber: string;
    sex: string;
    avatar: string;
    status: string;
    delFlag: string;
    loginIp: string;
    loginDate: string;
    remark: string;
    dept: DeptVO;
    roles: RoleVO[];
    roleIds: any;
    postIds: any;
    roleId: any;
    admin: boolean;
  userId: string | number;
  deptId: number;
  userName: string;
  nickName: string;
  userType: string;
  email: string;
  phonenumber: string;
  sex: string;
  avatar: string;
  status: string;
  delFlag: string;
  loginIp: string;
  loginDate: string;
  remark: string;
  dept: DeptVO;
  roles: RoleVO[];
  roleIds: any;
  postIds: any;
  roleId: any;
  admin: boolean;
}
/**
 * ç”¨æˆ·è¡¨å•类型
 */
export interface UserForm {
    id?: string;
    userId?: string;
    deptId?: number;
    userName: string;
    nickName?: string;
    password: string;
    phonenumber?: string;
    email?: string;
    sex?: string;
    status: string;
    remark?: string;
    postIds: string[];
    roleIds: string[];
  id?: string;
  userId?: string;
  deptId?: number;
  userName: string;
  nickName?: string;
  password: string;
  phonenumber?: string;
  email?: string;
  sex?: string;
  status: string;
  remark?: string;
  postIds: string[];
  roleIds: string[];
}
export interface UserInfoVO {
    user: UserVO;
    roles: RoleVO[];
    roleIds: string[];
    posts: PostVO[];
    postIds: string[];
    roleGroup: string;
    postGroup: string;
  user: UserVO;
  roles: RoleVO[];
  roleIds: string[];
  posts: PostVO[];
  postIds: string[];
  roleGroup: string;
  postGroup: string;
}
export interface ResetPwdForm {
    oldPassword: string;
    newPassword: string;
    confirmPassword: string;
  oldPassword: string;
  newPassword: string;
  confirmPassword: string;
}
src/api/tool/gen/index.ts
@@ -4,84 +4,84 @@
// æŸ¥è¯¢ç”Ÿæˆè¡¨æ•°æ®
export const listTable = (query: TableQuery): AxiosPromise<TableVO[]> => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/list',
        method: 'get',
        params: query
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/list',
    method: 'get',
    params: query
  });
};
// æŸ¥è¯¢db数据库列表
export const listDbTable = (query: DbTableQuery): AxiosPromise<DbTableVO[]> => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/db/list',
        method: 'get',
        params: query
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/db/list',
    method: 'get',
    params: query
  });
};
// æŸ¥è¯¢è¡¨è¯¦ç»†ä¿¡æ¯
export const getGenTable = (tableId: string | number): AxiosPromise<GenTableVO> => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/' + tableId,
        method: 'get'
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/' + tableId,
    method: 'get'
  });
};
// ä¿®æ”¹ä»£ç ç”Ÿæˆä¿¡æ¯
export const updateGenTable = (data: DbTableForm) => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen',
        method: 'put',
        data: data
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen',
    method: 'put',
    data: data
  });
};
// å¯¼å…¥è¡¨
export const importTable = (data: { tables: string }) => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/importTable',
        method: 'post',
        params: data
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/importTable',
    method: 'post',
    params: data
  });
};
// é¢„览生成代码
export const previewTable = (tableId: string | number) => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/preview/' + tableId,
        method: 'get'
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/preview/' + tableId,
    method: 'get'
  });
};
// åˆ é™¤è¡¨æ•°æ®
export const delTable = (tableId: string | number | Array<string | number>) => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/' + tableId,
        method: 'delete'
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/' + tableId,
    method: 'delete'
  });
};
// ç”Ÿæˆä»£ç ï¼ˆè‡ªå®šä¹‰è·¯å¾„)
export const genCode = (tableName: string) => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/genCode/' + tableName,
        method: 'get'
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/genCode/' + tableName,
    method: 'get'
  });
};
// åŒæ­¥æ•°æ®åº“
export const synchDb = (tableName: string) => {
    return request({
        headers: { datasource: localStorage.getItem('dataName') },
        url: '/tool/gen/synchDb/' + tableName,
        method: 'get'
    });
  return request({
    headers: { datasource: localStorage.getItem('dataName') },
    url: '/tool/gen/synchDb/' + tableName,
    method: 'get'
  });
};
src/api/tool/gen/types.ts
@@ -1,178 +1,178 @@
export interface TableVO extends BaseEntity {
    createDept: number | string;
    tableId: string | number;
    tableName: string;
    tableComment: string;
    subTableName?: any;
    subTableFkName?: any;
    className: string;
    tplCategory: string;
    packageName: string;
    moduleName: string;
    businessName: string;
    functionName: string;
    functionAuthor: string;
    genType: string;
    genPath: string;
    pkColumn?: any;
    columns?: any;
    options?: any;
    remark?: any;
    treeCode?: any;
    treeParentCode?: any;
    treeName?: any;
    menuIds?: any;
    parentMenuId?: any;
    parentMenuName?: any;
    tree: boolean;
    crud: boolean;
  createDept: number | string;
  tableId: string | number;
  tableName: string;
  tableComment: string;
  subTableName?: any;
  subTableFkName?: any;
  className: string;
  tplCategory: string;
  packageName: string;
  moduleName: string;
  businessName: string;
  functionName: string;
  functionAuthor: string;
  genType: string;
  genPath: string;
  pkColumn?: any;
  columns?: any;
  options?: any;
  remark?: any;
  treeCode?: any;
  treeParentCode?: any;
  treeName?: any;
  menuIds?: any;
  parentMenuId?: any;
  parentMenuName?: any;
  tree: boolean;
  crud: boolean;
}
export interface TableQuery extends PageQuery {
    tableName: string;
    tableComment: string;
    dataName: string;
  tableName: string;
  tableComment: string;
  dataName: string;
}
export interface DbColumnVO extends BaseEntity {
    createDept?: any;
    columnId?: any;
    tableId?: any;
    columnName?: any;
    columnComment?: any;
    columnType?: any;
    javaType?: any;
    javaField?: any;
    isPk?: any;
    isIncrement?: any;
    isRequired?: any;
    isInsert?: any;
    isEdit?: any;
    isList?: any;
    isQuery?: any;
    queryType?: any;
    htmlType?: any;
    dictType?: any;
    sort?: any;
    increment: boolean;
    capJavaField?: any;
    usableColumn: boolean;
    superColumn: boolean;
    list: boolean;
    pk: boolean;
    insert: boolean;
    edit: boolean;
    query: boolean;
    required: boolean;
  createDept?: any;
  columnId?: any;
  tableId?: any;
  columnName?: any;
  columnComment?: any;
  columnType?: any;
  javaType?: any;
  javaField?: any;
  isPk?: any;
  isIncrement?: any;
  isRequired?: any;
  isInsert?: any;
  isEdit?: any;
  isList?: any;
  isQuery?: any;
  queryType?: any;
  htmlType?: any;
  dictType?: any;
  sort?: any;
  increment: boolean;
  capJavaField?: any;
  usableColumn: boolean;
  superColumn: boolean;
  list: boolean;
  pk: boolean;
  insert: boolean;
  edit: boolean;
  query: boolean;
  required: boolean;
}
export interface DbTableVO {
    createDept?: any;
    tableId?: any;
    tableName: string;
    tableComment: string;
    subTableName?: any;
    subTableFkName?: any;
    className?: any;
    tplCategory?: any;
    packageName?: any;
    moduleName?: any;
    businessName?: any;
    functionName?: any;
    functionAuthor?: any;
    genType?: any;
    genPath?: any;
    pkColumn?: any;
    columns: DbColumnVO[];
    options?: any;
    remark?: any;
    treeCode?: any;
    treeParentCode?: any;
    treeName?: any;
    menuIds?: any;
    parentMenuId?: any;
    parentMenuName?: any;
    tree: boolean;
    crud: boolean;
  createDept?: any;
  tableId?: any;
  tableName: string;
  tableComment: string;
  subTableName?: any;
  subTableFkName?: any;
  className?: any;
  tplCategory?: any;
  packageName?: any;
  moduleName?: any;
  businessName?: any;
  functionName?: any;
  functionAuthor?: any;
  genType?: any;
  genPath?: any;
  pkColumn?: any;
  columns: DbColumnVO[];
  options?: any;
  remark?: any;
  treeCode?: any;
  treeParentCode?: any;
  treeName?: any;
  menuIds?: any;
  parentMenuId?: any;
  parentMenuName?: any;
  tree: boolean;
  crud: boolean;
}
export interface DbTableQuery extends PageQuery {
    tableName: string;
    tableComment: string;
  tableName: string;
  tableComment: string;
}
export interface GenTableVO {
    info: DbTableVO;
    rows: DbColumnVO[];
    tables: DbTableVO[];
  info: DbTableVO;
  rows: DbColumnVO[];
  tables: DbTableVO[];
}
export interface DbColumnForm extends BaseEntity {
    createDept: number;
    columnId: string;
    tableId: string;
    columnName: string;
    columnComment: string;
    columnType: string;
    javaType: string;
    javaField: string;
    isPk: string;
    isIncrement: string;
    isRequired: string;
    isInsert?: any;
    isEdit: string;
    isList: string;
    isQuery?: any;
    queryType: string;
    htmlType: string;
    dictType: string;
    sort: number;
    increment: boolean;
    capJavaField: string;
    usableColumn: boolean;
    superColumn: boolean;
    list: boolean;
    pk: boolean;
    insert: boolean;
    edit: boolean;
    query: boolean;
    required: boolean;
  createDept: number;
  columnId: string;
  tableId: string;
  columnName: string;
  columnComment: string;
  columnType: string;
  javaType: string;
  javaField: string;
  isPk: string;
  isIncrement: string;
  isRequired: string;
  isInsert?: any;
  isEdit: string;
  isList: string;
  isQuery?: any;
  queryType: string;
  htmlType: string;
  dictType: string;
  sort: number;
  increment: boolean;
  capJavaField: string;
  usableColumn: boolean;
  superColumn: boolean;
  list: boolean;
  pk: boolean;
  insert: boolean;
  edit: boolean;
  query: boolean;
  required: boolean;
}
export interface DbParamForm {
    treeCode?: any;
    treeName?: any;
    treeParentCode?: any;
    parentMenuId: string;
  treeCode?: any;
  treeName?: any;
  treeParentCode?: any;
  parentMenuId: string;
}
export interface DbTableForm extends BaseEntity {
    createDept?: any;
    tableId: string | string;
    tableName: string;
    tableComment: string;
    subTableName?: any;
    subTableFkName?: any;
    className: string;
    tplCategory: string;
    packageName: string;
    moduleName: string;
    businessName: string;
    functionName: string;
    functionAuthor: string;
    genType: string;
    genPath: string;
    pkColumn?: any;
    columns: DbColumnForm[];
    options: string;
    remark?: any;
    treeCode?: any;
    treeParentCode?: any;
    treeName?: any;
    menuIds?: any;
    parentMenuId: string;
    parentMenuName?: any;
    tree: boolean;
    crud: boolean;
    params: DbParamForm;
  createDept?: any;
  tableId: string | string;
  tableName: string;
  tableComment: string;
  subTableName?: any;
  subTableFkName?: any;
  className: string;
  tplCategory: string;
  packageName: string;
  moduleName: string;
  businessName: string;
  functionName: string;
  functionAuthor: string;
  genType: string;
  genPath: string;
  pkColumn?: any;
  columns: DbColumnForm[];
  options: string;
  remark?: any;
  treeCode?: any;
  treeParentCode?: any;
  treeName?: any;
  menuIds?: any;
  parentMenuId: string;
  parentMenuName?: any;
  tree: boolean;
  crud: boolean;
  params: DbParamForm;
}
src/api/types.ts
@@ -2,53 +2,53 @@
 * æ³¨å†Œ
 */
export type RegisterForm = {
    tenantId: string;
    username: string;
    password: string;
    confirmPassword?: string;
    code?: string;
    uuid?: string;
    userType?: string;
  tenantId: string;
  username: string;
  password: string;
  confirmPassword?: string;
  code?: string;
  uuid?: string;
  userType?: string;
};
/**
 * ç™»å½•请求
 */
export interface LoginData {
    tenantId: string;
    username: string;
    password: string;
    rememberMe?: boolean;
    code?: string;
    uuid?: string;
  tenantId: string;
  username: string;
  password: string;
  rememberMe?: boolean;
  code?: string;
  uuid?: string;
}
/**
 * ç™»å½•响应
 */
export interface LoginResult {
    token: string;
  token: string;
}
/**
 * éªŒè¯ç è¿”回
 */
export interface VerifyCodeResult {
    captchaEnabled: boolean;
    uuid?: string;
    img?: string;
  captchaEnabled: boolean;
  uuid?: string;
  img?: string;
}
/**
 * ç§Ÿæˆ·
 */
export interface TenantVO {
    companyName: string;
    domain: any;
    tenantId: string;
  companyName: string;
  domain: any;
  tenantId: string;
}
export interface TenantInfo {
    tenantEnabled: boolean;
    voList: TenantVO[];
  tenantEnabled: boolean;
  voList: TenantVO[];
}
src/assets/styles/btn.scss
@@ -1,99 +1,99 @@
@import './variables.module.scss';
@mixin colorBtn($color) {
    background: $color;
  background: $color;
    &:hover {
        color: $color;
  &:hover {
    color: $color;
        &:before,
        &:after {
            background: $color;
        }
    }
    &:before,
    &:after {
      background: $color;
    }
  }
}
.blue-btn {
    @include colorBtn($blue);
  @include colorBtn($blue);
}
.light-blue-btn {
    @include colorBtn($light-blue);
  @include colorBtn($light-blue);
}
.red-btn {
    @include colorBtn($red);
  @include colorBtn($red);
}
.pink-btn {
    @include colorBtn($pink);
  @include colorBtn($pink);
}
.green-btn {
    @include colorBtn($green);
  @include colorBtn($green);
}
.tiffany-btn {
    @include colorBtn($tiffany);
  @include colorBtn($tiffany);
}
.yellow-btn {
    @include colorBtn($yellow);
  @include colorBtn($yellow);
}
.pan-btn {
    font-size: 14px;
    color: #fff;
    padding: 14px 36px;
    border-radius: 8px;
    border: none;
    outline: none;
    transition: 600ms ease all;
    position: relative;
    display: inline-block;
  font-size: 14px;
  color: #fff;
  padding: 14px 36px;
  border-radius: 8px;
  border: none;
  outline: none;
  transition: 600ms ease all;
  position: relative;
  display: inline-block;
    &:hover {
        background: #fff;
  &:hover {
    background: #fff;
        &:before,
        &:after {
            width: 100%;
            transition: 600ms ease all;
        }
    }
    &:before,
    &:after {
      width: 100%;
      transition: 600ms ease all;
    }
  }
    &:before,
    &:after {
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        height: 2px;
        width: 0;
        transition: 400ms ease all;
    }
  &:before,
  &:after {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    height: 2px;
    width: 0;
    transition: 400ms ease all;
  }
    &::after {
        right: inherit;
        top: inherit;
        left: 0;
        bottom: 0;
    }
  &::after {
    right: inherit;
    top: inherit;
    left: 0;
    bottom: 0;
  }
}
.custom-button {
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    background: #fff;
    color: #fff;
    -webkit-appearance: none;
    text-align: center;
    box-sizing: border-box;
    outline: 0;
    margin: 0;
    padding: 10px 15px;
    font-size: 14px;
    border-radius: 4px;
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  background: #fff;
  color: #fff;
  -webkit-appearance: none;
  text-align: center;
  box-sizing: border-box;
  outline: 0;
  margin: 0;
  padding: 10px 15px;
  font-size: 14px;
  border-radius: 4px;
}
src/assets/styles/element-ui.scss
@@ -1,97 +1,97 @@
// cover some element-ui styles
.el-divider--horizontal {
    margin-bottom: 10px;
    margin-top: 10px;
  margin-bottom: 10px;
  margin-top: 10px;
}
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
    font-weight: 400 !important;
  font-weight: 400 !important;
}
.el-upload {
    input[type='file'] {
        display: none !important;
    }
  input[type='file'] {
    display: none !important;
  }
}
.el-upload__input {
    display: none;
  display: none;
}
.cell {
    .el-tag {
        margin-right: 0px;
    }
  .el-tag {
    margin-right: 0px;
  }
}
.small-padding {
    .cell {
        padding-left: 5px;
        padding-right: 5px;
    }
  .cell {
    padding-left: 5px;
    padding-right: 5px;
  }
}
.fixed-width {
    .el-button--mini {
        padding: 7px 10px;
        width: 60px;
    }
  .el-button--mini {
    padding: 7px 10px;
    width: 60px;
  }
}
.status-col {
    .cell {
        padding: 0 10px;
        text-align: center;
  .cell {
    padding: 0 10px;
    text-align: center;
        .el-tag {
            margin-right: 0px;
        }
    }
    .el-tag {
      margin-right: 0px;
    }
  }
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
    transform: none;
    left: 0;
    position: relative;
    margin: 0 auto;
  transform: none;
  left: 0;
  position: relative;
  margin: 0 auto;
}
// refine element ui upload
.upload-container {
    .el-upload {
        width: 100%;
  .el-upload {
    width: 100%;
        .el-upload-dragger {
            width: 100%;
            height: 200px;
        }
    }
    .el-upload-dragger {
      width: 100%;
      height: 200px;
    }
  }
}
// dropdown
.el-dropdown-menu {
    a {
        display: block;
    }
  a {
    display: block;
  }
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
    display: inline-flex !important;
  display: inline-flex !important;
}
// to fix el-date-picker css style
.el-range-separator {
    box-sizing: content-box;
  box-sizing: content-box;
}
.el-menu--collapse>div>.el-submenu>.el-submenu__title .el-submenu__icon-arrow {
    display: none;
  display: none;
}
.el-dropdown .el-dropdown-link {
    color: var(--el-color-primary) !important;
  color: var(--el-color-primary) !important;
}
src/assets/styles/index.scss
@@ -9,201 +9,201 @@
@import 'element-plus/dist/index.css';
body {
    height: 100%;
    margin: 0;
    -moz-osx-font-smoothing: grayscale;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
  height: 100%;
  margin: 0;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
label {
    font-weight: 700;
  font-weight: 700;
}
html {
    height: 100%;
    box-sizing: border-box;
  height: 100%;
  box-sizing: border-box;
}
#app {
    height: 100%;
  height: 100%;
}
*,
*:before,
*:after {
    box-sizing: inherit;
  box-sizing: inherit;
}
.no-padding {
    padding: 0px !important;
  padding: 0px !important;
}
.padding-content {
    padding: 4px 0;
  padding: 4px 0;
}
a:focus,
a:active {
    outline: none;
  outline: none;
}
a,
a:focus,
a:hover {
    cursor: pointer;
    color: inherit;
    text-decoration: none;
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}
div:focus {
    outline: none;
  outline: none;
}
.fr {
    float: right;
  float: right;
}
.fl {
    float: left;
  float: left;
}
.pr-5 {
    padding-right: 5px;
  padding-right: 5px;
}
.pl-5 {
    padding-left: 5px;
  padding-left: 5px;
}
.block {
    display: block;
  display: block;
}
.pointer {
    cursor: pointer;
  cursor: pointer;
}
.inlineBlock {
    display: block;
  display: block;
}
.clearfix {
    &:after {
        visibility: hidden;
        display: block;
        font-size: 0;
        content: ' ';
        clear: both;
        height: 0;
    }
  &:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: ' ';
    clear: both;
    height: 0;
  }
}
aside {
    background: #eef1f6;
    padding: 8px 24px;
    margin-bottom: 20px;
    border-radius: 2px;
    display: block;
    line-height: 32px;
    font-size: 16px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
        sans-serif;
    color: #2c3e50;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  background: #eef1f6;
  padding: 8px 24px;
  margin-bottom: 20px;
  border-radius: 2px;
  display: block;
  line-height: 32px;
  font-size: 16px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  color: #2c3e50;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
    a {
        color: #337ab7;
        cursor: pointer;
  a {
    color: #337ab7;
    cursor: pointer;
        &:hover {
            color: rgb(32, 160, 255);
        }
    }
    &:hover {
      color: rgb(32, 160, 255);
    }
  }
}
//main-container全局样式
.app-container {
    padding: 20px;
  padding: 20px;
}
// search面板样式
.panel,
.search {
    margin-bottom: 0.75rem;
    border-radius: 0.25rem;
    border: 1px solid var(--el-border-color-light);
    background-color: var(--el-bg-color-overlay);
    padding: 0.75rem;
    --tw-shadow: var(--el-box-shadow-light);
    --tw-shadow-colored: var(--el-box-shadow-light);
    box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
  margin-bottom: 0.75rem;
  border-radius: 0.25rem;
  border: 1px solid var(--el-border-color-light);
  background-color: var(--el-bg-color-overlay);
  padding: 0.75rem;
  --tw-shadow: var(--el-box-shadow-light);
  --tw-shadow-colored: var(--el-box-shadow-light);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.components-container {
    margin: 30px 50px;
    position: relative;
  margin: 30px 50px;
  position: relative;
}
.pagination-container {
    margin-top: 30px;
  margin-top: 30px;
}
.text-center {
    text-align: center;
  text-align: center;
}
.sub-navbar {
    height: 50px;
    line-height: 50px;
    position: relative;
    width: 100%;
    text-align: right;
    padding-right: 20px;
    transition: 600ms ease position;
    background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
  height: 50px;
  line-height: 50px;
  position: relative;
  width: 100%;
  text-align: right;
  padding-right: 20px;
  transition: 600ms ease position;
  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
    .subtitle {
        font-size: 20px;
        color: #fff;
    }
  .subtitle {
    font-size: 20px;
    color: #fff;
  }
    &.draft {
        background: #d0d0d0;
    }
  &.draft {
    background: #d0d0d0;
  }
    &.deleted {
        background: #d0d0d0;
    }
  &.deleted {
    background: #d0d0d0;
  }
}
.link-type,
.link-type:focus {
    color: #337ab7;
    cursor: pointer;
  color: #337ab7;
  cursor: pointer;
    &:hover {
        color: rgb(32, 160, 255);
    }
  &:hover {
    color: rgb(32, 160, 255);
  }
}
.filter-container {
    padding-bottom: 10px;
  padding-bottom: 10px;
    .filter-item {
        display: inline-block;
        vertical-align: middle;
        margin-bottom: 10px;
    }
  .filter-item {
    display: inline-block;
    vertical-align: middle;
    margin-bottom: 10px;
  }
}
//refine vue-multiselect plugin
.multiselect {
    line-height: 16px;
  line-height: 16px;
}
.multiselect--active {
    z-index: 1000 !important;
  z-index: 1000 !important;
}
src/assets/styles/mixin.scss
@@ -1,60 +1,60 @@
@mixin clearfix {
    &:after {
        content: '';
        display: table;
        clear: both;
    }
  &:after {
    content: '';
    display: table;
    clear: both;
  }
}
@mixin scrollBar {
    &::-webkit-scrollbar-track-piece {
        background: #d3dce6;
    }
  &::-webkit-scrollbar-track-piece {
    background: #d3dce6;
  }
    &::-webkit-scrollbar {
        width: 6px;
    }
  &::-webkit-scrollbar {
    width: 6px;
  }
    &::-webkit-scrollbar-thumb {
        background: #99a9bf;
        border-radius: 20px;
    }
  &::-webkit-scrollbar-thumb {
    background: #99a9bf;
    border-radius: 20px;
  }
}
@mixin relative {
    position: relative;
    width: 100%;
    height: 100%;
  position: relative;
  width: 100%;
  height: 100%;
}
@mixin pct($pct) {
    width: #{$pct};
    position: relative;
    margin: 0 auto;
  width: #{$pct};
  position: relative;
  margin: 0 auto;
}
@mixin triangle($width, $height, $color, $direction) {
    $width: $width/2;
    $color-border-style: $height solid $color;
    $transparent-border-style: $width solid transparent;
    height: 0;
    width: 0;
  $width: $width/2;
  $color-border-style: $height solid $color;
  $transparent-border-style: $width solid transparent;
  height: 0;
  width: 0;
    @if $direction==up {
        border-bottom: $color-border-style;
        border-left: $transparent-border-style;
        border-right: $transparent-border-style;
    } @else if $direction==right {
        border-left: $color-border-style;
        border-top: $transparent-border-style;
        border-bottom: $transparent-border-style;
    } @else if $direction==down {
        border-top: $color-border-style;
        border-left: $transparent-border-style;
        border-right: $transparent-border-style;
    } @else if $direction==left {
        border-right: $color-border-style;
        border-top: $transparent-border-style;
        border-bottom: $transparent-border-style;
    }
  @if $direction==up {
    border-bottom: $color-border-style;
    border-left: $transparent-border-style;
    border-right: $transparent-border-style;
  } @else if $direction==right {
    border-left: $color-border-style;
    border-top: $transparent-border-style;
    border-bottom: $transparent-border-style;
  } @else if $direction==down {
    border-top: $color-border-style;
    border-left: $transparent-border-style;
    border-right: $transparent-border-style;
  } @else if $direction==left {
    border-right: $color-border-style;
    border-top: $transparent-border-style;
    border-bottom: $transparent-border-style;
  }
}
src/assets/styles/ruoyi.scss
@@ -5,52 +5,52 @@
/** åŸºç¡€é€šç”¨ **/
.pt5 {
    padding-top: 5px;
  padding-top: 5px;
}
.pr5 {
    padding-right: 5px;
  padding-right: 5px;
}
.pb5 {
    padding-bottom: 5px;
  padding-bottom: 5px;
}
.mt5 {
    margin-top: 5px;
  margin-top: 5px;
}
.mr5 {
    margin-right: 5px;
  margin-right: 5px;
}
.mb5 {
    margin-bottom: 5px;
  margin-bottom: 5px;
}
.mb8 {
    margin-bottom: 8px;
  margin-bottom: 8px;
}
.ml5 {
    margin-left: 5px;
  margin-left: 5px;
}
.mt10 {
    margin-top: 10px;
  margin-top: 10px;
}
.mr10 {
    margin-right: 10px;
  margin-right: 10px;
}
.mb10 {
    margin-bottom: 10px;
  margin-bottom: 10px;
}
.ml10 {
    margin-left: 10px;
  margin-left: 10px;
}
.mt20 {
    margin-top: 20px;
  margin-top: 20px;
}
.mr20 {
    margin-right: 20px;
  margin-right: 20px;
}
.mb20 {
    margin-bottom: 20px;
  margin-bottom: 20px;
}
.ml20 {
    margin-left: 20px;
  margin-left: 20px;
}
.h1,
@@ -65,226 +65,226 @@
h4,
h5,
h6 {
    font-family: inherit;
    font-weight: 500;
    line-height: 1.1;
    color: inherit;
  font-family: inherit;
  font-weight: 500;
  line-height: 1.1;
  color: inherit;
}
.el-form .el-form-item__label {
    font-weight: 700;
  font-weight: 700;
}
.el-dialog:not(.is-fullscreen) {
    margin-top: 6vh !important;
  margin-top: 6vh !important;
}
.el-dialog.scrollbar .el-dialog__body {
    overflow: auto;
    overflow-x: hidden;
    max-height: 70vh;
    padding: 10px 20px 0;
  overflow: auto;
  overflow-x: hidden;
  max-height: 70vh;
  padding: 10px 20px 0;
}
.el-table {
    .el-table__header-wrapper,
    .el-table__fixed-header-wrapper {
        th {
            word-break: break-word;
            background-color: #f8f8f9 !important;
            color: #515a6e;
            height: 40px !important;
            font-size: 13px;
        }
    }
    .el-table__body-wrapper {
        .el-button [class*='el-icon-'] + span {
            margin-left: 1px;
        }
    }
  .el-table__header-wrapper,
  .el-table__fixed-header-wrapper {
    th {
      word-break: break-word;
      background-color: #f8f8f9 !important;
      color: #515a6e;
      height: 40px !important;
      font-size: 13px;
    }
  }
  .el-table__body-wrapper {
    .el-button [class*='el-icon-'] + span {
      margin-left: 1px;
    }
  }
}
/** è¡¨å•布局 **/
.form-header {
    font-size: 15px;
    color: #6379bb;
    border-bottom: 1px solid #ddd;
    margin: 8px 10px 25px 10px;
    padding-bottom: 5px;
  font-size: 15px;
  color: #6379bb;
  border-bottom: 1px solid #ddd;
  margin: 8px 10px 25px 10px;
  padding-bottom: 5px;
}
/** è¡¨æ ¼å¸ƒå±€ **/
.pagination-container {
    // position: relative;
    height: 25px;
    margin-bottom: 10px;
    margin-top: 15px;
    padding: 10px 20px !important;
  // position: relative;
  height: 25px;
  margin-bottom: 10px;
  margin-top: 15px;
  padding: 10px 20px !important;
}
/* tree border */
.tree-border {
    margin-top: 5px;
    border: 1px solid #e5e6e7;
    background: #ffffff none;
    border-radius: 4px;
    width: 100%;
  margin-top: 5px;
  border: 1px solid #e5e6e7;
  background: #ffffff none;
  border-radius: 4px;
  width: 100%;
}
.pagination-container .el-pagination {
    //right: 0;
    //position: absolute;
  //right: 0;
  //position: absolute;
}
@media (max-width: 768px) {
    .pagination-container .el-pagination > .el-pagination__jump {
        display: none !important;
    }
    .pagination-container .el-pagination > .el-pagination__sizes {
        display: none !important;
    }
  .pagination-container .el-pagination > .el-pagination__jump {
    display: none !important;
  }
  .pagination-container .el-pagination > .el-pagination__sizes {
    display: none !important;
  }
}
.el-table .fixed-width .el-button--small {
    padding-left: 0;
    padding-right: 0;
    width: inherit;
  padding-left: 0;
  padding-right: 0;
  width: inherit;
}
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
    cursor: pointer;
    color: #409eff;
    margin-left: 10px;
  cursor: pointer;
  color: #409eff;
  margin-left: 10px;
}
.el-table .el-dropdown,
.el-icon-arrow-down {
    font-size: 12px;
  font-size: 12px;
}
.el-tree-node__content > .el-checkbox {
    margin-right: 8px;
  margin-right: 8px;
}
.list-group-striped > .list-group-item {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
  border-left: 0;
  border-right: 0;
  border-radius: 0;
  padding-left: 0;
  padding-right: 0;
}
.list-group {
    padding-left: 0px;
    list-style: none;
  padding-left: 0px;
  list-style: none;
}
.list-group-item {
    border-bottom: 1px solid #e7eaec;
    border-top: 1px solid #e7eaec;
    margin-bottom: -1px;
    padding: 11px 0px;
    font-size: 13px;
  border-bottom: 1px solid #e7eaec;
  border-top: 1px solid #e7eaec;
  margin-bottom: -1px;
  padding: 11px 0px;
  font-size: 13px;
}
.pull-right {
    float: right !important;
  float: right !important;
}
.el-card__header {
    padding: 14px 15px 7px !important;
    min-height: 40px;
  padding: 14px 15px 7px !important;
  min-height: 40px;
}
.el-card__body {
    padding: 15px 20px 20px 20px !important;
  padding: 15px 20px 20px 20px !important;
}
.card-box {
    padding-right: 15px;
    padding-left: 15px;
    margin-bottom: 10px;
  padding-right: 15px;
  padding-left: 15px;
  margin-bottom: 10px;
}
/* button color */
.el-button--cyan.is-active,
.el-button--cyan:active {
    background: #20b2aa;
    border-color: #20b2aa;
    color: #ffffff;
  background: #20b2aa;
  border-color: #20b2aa;
  color: #ffffff;
}
.el-button--cyan:focus,
.el-button--cyan:hover {
    background: #48d1cc;
    border-color: #48d1cc;
    color: #ffffff;
  background: #48d1cc;
  border-color: #48d1cc;
  color: #ffffff;
}
.el-button--cyan {
    background-color: #20b2aa;
    border-color: #20b2aa;
    color: #ffffff;
  background-color: #20b2aa;
  border-color: #20b2aa;
  color: #ffffff;
}
/* text color */
.text-navy {
    color: #1ab394;
  color: #1ab394;
}
.text-primary {
    color: inherit;
  color: inherit;
}
.text-success {
    color: #1c84c6;
  color: #1c84c6;
}
.text-info {
    color: #23c6c8;
  color: #23c6c8;
}
.text-warning {
    color: #f8ac59;
  color: #f8ac59;
}
.text-danger {
    color: #ed5565;
  color: #ed5565;
}
.text-muted {
    color: #888888;
  color: #888888;
}
/* image */
.img-circle {
    border-radius: 50%;
  border-radius: 50%;
}
.img-lg {
    width: 120px;
    height: 120px;
  width: 120px;
  height: 120px;
}
.avatar-upload-preview {
    position: absolute;
    top: 50%;
    transform: translate(50%, -50%);
    width: 200px;
    height: 200px;
    border-radius: 50%;
    box-shadow: 0 0 4px #ccc;
    overflow: hidden;
  position: absolute;
  top: 50%;
  transform: translate(50%, -50%);
  width: 200px;
  height: 200px;
  border-radius: 50%;
  box-shadow: 0 0 4px #ccc;
  overflow: hidden;
}
/* æ‹–拽列样式 */
.sortable-ghost {
    opacity: 0.8;
    color: #fff !important;
    background: #42b983 !important;
  opacity: 0.8;
  color: #fff !important;
  background: #42b983 !important;
}
/* è¡¨æ ¼å³ä¾§å·¥å…·æ æ ·å¼ */
.top-right-btn {
    margin-left: auto;
  margin-left: auto;
}
src/assets/styles/sidebar.scss
@@ -1,236 +1,236 @@
#app {
    .main-container {
        min-height: 100%;
        transition: margin-left 0.28s;
        margin-left: $base-sidebar-width;
        position: relative;
    }
  .main-container {
    min-height: 100%;
    transition: margin-left 0.28s;
    margin-left: $base-sidebar-width;
    position: relative;
  }
    .sidebarHide {
        margin-left: 0 !important;
    }
  .sidebarHide {
    margin-left: 0 !important;
  }
    .sidebar-container {
        -webkit-transition: width 0.28s;
        transition: width 0.28s;
        width: $base-sidebar-width !important;
        background-color: $base-menu-background;
        height: 100%;
        position: fixed;
        font-size: 0px;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1001;
        overflow: hidden;
        -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
        box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
  .sidebar-container {
    -webkit-transition: width 0.28s;
    transition: width 0.28s;
    width: $base-sidebar-width !important;
    background-color: $base-menu-background;
    height: 100%;
    position: fixed;
    font-size: 0px;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;
    -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
    box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
        // reset element-ui css
        .horizontal-collapse-transition {
            transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
        }
    // reset element-ui css
    .horizontal-collapse-transition {
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
    }
        .scrollbar-wrapper {
            overflow-x: hidden !important;
        }
    .scrollbar-wrapper {
      overflow-x: hidden !important;
    }
        .el-scrollbar__bar.is-vertical {
            right: 0px;
        }
    .el-scrollbar__bar.is-vertical {
      right: 0px;
    }
        .el-scrollbar {
            height: 100%;
        }
    .el-scrollbar {
      height: 100%;
    }
        &.has-logo {
            .el-scrollbar {
                height: calc(100% - 50px);
            }
        }
    &.has-logo {
      .el-scrollbar {
        height: calc(100% - 50px);
      }
    }
        .is-horizontal {
            display: none;
        }
    .is-horizontal {
      display: none;
    }
        a {
            display: inline-block;
            width: 100%;
            overflow: hidden;
        }
    a {
      display: inline-block;
      width: 100%;
      overflow: hidden;
    }
        .svg-icon {
            margin-right: 16px;
        }
    .svg-icon {
      margin-right: 16px;
    }
        .el-menu {
            border: none;
            height: 100%;
            width: 100% !important;
        }
    .el-menu {
      border: none;
      height: 100%;
      width: 100% !important;
    }
        .el-menu-item,
        .menu-title {
            overflow: hidden !important;
            text-overflow: ellipsis !important;
            white-space: nowrap !important;
        }
    .el-menu-item,
    .menu-title {
      overflow: hidden !important;
      text-overflow: ellipsis !important;
      white-space: nowrap !important;
    }
        .el-menu-item .el-menu-tooltip__trigger {
            display: inline-block !important;
        }
    .el-menu-item .el-menu-tooltip__trigger {
      display: inline-block !important;
    }
        // menu hover
        .sub-menu-title-noDropdown,
        .el-sub-menu__title {
            &:hover {
                background-color: rgba(0, 0, 0, 0.06) !important;
            }
        }
    // menu hover
    .sub-menu-title-noDropdown,
    .el-sub-menu__title {
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
      }
    }
        & .theme-dark .is-active > .el-sub-menu__title {
            color: $base-menu-color-active !important;
        }
    & .theme-dark .is-active > .el-sub-menu__title {
      color: $base-menu-color-active !important;
    }
        & .nest-menu .el-sub-menu > .el-sub-menu__title,
        & .el-sub-menu .el-menu-item {
            min-width: $base-sidebar-width !important;
    & .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .el-sub-menu .el-menu-item {
      min-width: $base-sidebar-width !important;
            &:hover {
                background-color: rgba(0, 0, 0, 0.06) !important;
            }
        }
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
      }
    }
        & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
        & .theme-dark .el-sub-menu .el-menu-item {
            background-color: $base-sub-menu-background !important;
    & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .theme-dark .el-sub-menu .el-menu-item {
      background-color: $base-sub-menu-background !important;
            &:hover {
                background-color: $base-sub-menu-hover !important;
            }
        }
    }
      &:hover {
        background-color: $base-sub-menu-hover !important;
      }
    }
  }
    .hideSidebar {
        .sidebar-container {
            width: 54px !important;
        }
  .hideSidebar {
    .sidebar-container {
      width: 54px !important;
    }
        .main-container {
            margin-left: 54px;
        }
    .main-container {
      margin-left: 54px;
    }
        .sub-menu-title-noDropdown {
            padding: 0 !important;
            position: relative;
    .sub-menu-title-noDropdown {
      padding: 0 !important;
      position: relative;
            .el-tooltip {
                padding: 0 !important;
      .el-tooltip {
        padding: 0 !important;
                .svg-icon {
                    margin-left: 20px;
                }
            }
        }
        .svg-icon {
          margin-left: 20px;
        }
      }
    }
        .el-sub-menu {
            overflow: hidden;
    .el-sub-menu {
      overflow: hidden;
            & > .el-sub-menu__title {
                padding: 0 !important;
      & > .el-sub-menu__title {
        padding: 0 !important;
                .svg-icon {
                    margin-left: 20px;
                }
            }
        }
        .svg-icon {
          margin-left: 20px;
        }
      }
    }
        .el-menu--collapse {
            .el-sub-menu {
                & > .el-sub-menu__title {
                    & > span {
                        height: 0;
                        width: 0;
                        overflow: hidden;
                        visibility: hidden;
                        display: inline-block;
                    }
                    & > i {
                        height: 0;
                        width: 0;
                        overflow: hidden;
                        visibility: hidden;
                        display: inline-block;
                    }
                }
            }
        }
    }
    .el-menu--collapse {
      .el-sub-menu {
        & > .el-sub-menu__title {
          & > span {
            height: 0;
            width: 0;
            overflow: hidden;
            visibility: hidden;
            display: inline-block;
          }
          & > i {
            height: 0;
            width: 0;
            overflow: hidden;
            visibility: hidden;
            display: inline-block;
          }
        }
      }
    }
  }
    .el-menu--collapse .el-menu .el-sub-menu {
        min-width: $base-sidebar-width !important;
    }
  .el-menu--collapse .el-menu .el-sub-menu {
    min-width: $base-sidebar-width !important;
  }
    // mobile responsive
    .mobile {
        .main-container {
            margin-left: 0px;
        }
  // mobile responsive
  .mobile {
    .main-container {
      margin-left: 0px;
    }
        .sidebar-container {
            transition: transform 0.28s;
            width: $base-sidebar-width !important;
        }
    .sidebar-container {
      transition: transform 0.28s;
      width: $base-sidebar-width !important;
    }
        &.hideSidebar {
            .sidebar-container {
                pointer-events: none;
                transition-duration: 0.3s;
                transform: translate3d(-$base-sidebar-width, 0, 0);
            }
        }
    }
    &.hideSidebar {
      .sidebar-container {
        pointer-events: none;
        transition-duration: 0.3s;
        transform: translate3d(-$base-sidebar-width, 0, 0);
      }
    }
  }
    .withoutAnimation {
        .main-container,
        .sidebar-container {
            transition: none;
        }
    }
  .withoutAnimation {
    .main-container,
    .sidebar-container {
      transition: none;
    }
  }
}
// when menu collapsed
.el-menu--vertical {
    & > .el-menu {
        .svg-icon {
            margin-right: 16px;
        }
    }
  & > .el-menu {
    .svg-icon {
      margin-right: 16px;
    }
  }
    .nest-menu .el-sub-menu > .el-sub-menu__title,
    .el-menu-item {
        &:hover {
            // you can use $sub-menuHover
            background-color: rgba(0, 0, 0, 0.06) !important;
        }
    }
  .nest-menu .el-sub-menu > .el-sub-menu__title,
  .el-menu-item {
    &:hover {
      // you can use $sub-menuHover
      background-color: rgba(0, 0, 0, 0.06) !important;
    }
  }
    // the scroll bar appears when the sub-menu is too long
    > .el-menu--popup {
        max-height: 100vh;
        overflow-y: auto;
  // the scroll bar appears when the sub-menu is too long
  > .el-menu--popup {
    max-height: 100vh;
    overflow-y: auto;
        &::-webkit-scrollbar-track-piece {
            background: #d3dce6;
        }
    &::-webkit-scrollbar-track-piece {
      background: #d3dce6;
    }
        &::-webkit-scrollbar {
            width: 6px;
        }
    &::-webkit-scrollbar {
      width: 6px;
    }
        &::-webkit-scrollbar-thumb {
            background: #99a9bf;
            border-radius: 20px;
        }
    }
    &::-webkit-scrollbar-thumb {
      background: #99a9bf;
      border-radius: 20px;
    }
  }
}
src/assets/styles/transition.scss
@@ -3,51 +3,51 @@
/* fade */
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.28s;
  transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
    opacity: 0;
  opacity: 0;
}
/* fade-transform */
.fade-transform--move,
.fade-transform-leave-active,
.fade-transform-enter-active {
    transition: all 0.5s;
  transition: all 0.5s;
}
.fade-transform-leave-active {
    position: absolute;
  position: absolute;
}
.fade-transform-enter {
    opacity: 0;
    transform: translateX(-30px);
  opacity: 0;
  transform: translateX(-30px);
}
.fade-transform-leave-to {
    opacity: 0;
    transform: translateX(30px);
  opacity: 0;
  transform: translateX(30px);
}
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
    transition: all 0.5s;
  transition: all 0.5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
    opacity: 0;
    transform: translateX(20px);
  opacity: 0;
  transform: translateX(20px);
}
.breadcrumb-move {
    transition: all 0.5s;
  transition: all 0.5s;
}
.breadcrumb-leave-active {
    position: absolute;
  position: absolute;
}
src/assets/styles/variables.module.scss
@@ -47,19 +47,19 @@
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
    menuColor: $base-menu-color;
    menuLightColor: $base-menu-light-color;
    menuColorActive: $base-menu-color-active;
    menuBackground: $base-menu-background;
    menuLightBackground: $base-menu-light-background;
    subMenuBackground: $base-sub-menu-background;
    subMenuHover: $base-sub-menu-hover;
    sideBarWidth: $base-sidebar-width;
    logoTitleColor: $base-logo-title-color;
    logoLightTitleColor: $base-logo-light-title-color;
    primaryColor: $--color-primary;
    successColor: $--color-success;
    dangerColor: $--color-danger;
    infoColor: $--color-info;
    warningColor: $--color-warning;
  menuColor: $base-menu-color;
  menuLightColor: $base-menu-light-color;
  menuColorActive: $base-menu-color-active;
  menuBackground: $base-menu-background;
  menuLightBackground: $base-menu-light-background;
  subMenuBackground: $base-sub-menu-background;
  subMenuHover: $base-sub-menu-hover;
  sideBarWidth: $base-sidebar-width;
  logoTitleColor: $base-logo-title-color;
  logoLightTitleColor: $base-logo-light-title-color;
  primaryColor: $--color-primary;
  successColor: $--color-success;
  dangerColor: $--color-danger;
  infoColor: $--color-info;
  warningColor: $--color-warning;
}
src/components/Breadcrumb/index.vue
@@ -1,3 +1,15 @@
<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <transition-group name="breadcrumb">
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{
          item.meta?.title }}</span>
        <a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
      </el-breadcrumb-item>
    </transition-group>
  </el-breadcrumb>
</template>
<script setup lang="ts">
import { RouteLocationMatched } from 'vue-router'
@@ -6,48 +18,36 @@
const levelList = ref<RouteLocationMatched[]>([])
const getBreadcrumb = () => {
  // only show routes with meta.title
  let matched = route.matched.filter(item => item.meta && item.meta.title);
  const first = matched[0]
  // åˆ¤æ–­æ˜¯å¦ä¸ºé¦–页
  if (!isDashboard(first)) {
    matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched)
  }
  levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
    // only show routes with meta.title
    let matched = route.matched.filter(item => item.meta && item.meta.title);
    const first = matched[0]
    // åˆ¤æ–­æ˜¯å¦ä¸ºé¦–页
    if (!isDashboard(first)) {
        matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched)
    }
    levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
const isDashboard = (route: RouteLocationMatched) => {
  const name = route && route.name as string
  if (!name) {
    return false
  }
  return name.trim() === 'Index'
    const name = route && route.name as string
    if (!name) {
        return false
    }
    return name.trim() === 'Index'
}
const handleLink = (item: RouteLocationMatched) => {
  const { redirect, path } = item
  redirect ? router.push(redirect as string) : router.push(path)
    const { redirect, path } = item
    redirect ? router.push(redirect as string) : router.push(path)
}
watchEffect(() => {
  // if you go to the redirect page, do not update the breadcrumbs
  if (route.path.startsWith('/redirect/')) return
  getBreadcrumb()
    // if you go to the redirect page, do not update the breadcrumbs
    if (route.path.startsWith('/redirect/')) return
    getBreadcrumb()
})
onMounted(() => {
  getBreadcrumb();
    getBreadcrumb();
})
</script>
<template>
    <el-breadcrumb class="app-breadcrumb" separator="/">
        <transition-group name="breadcrumb">
            <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
                <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{
          item.meta?.title }}</span>
                <a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
            </el-breadcrumb-item>
        </transition-group>
    </el-breadcrumb>
</template>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
src/components/DictTag/index.vue
@@ -1,49 +1,49 @@
<template>
  <div>
    <template v-for="(item, index) in options">
      <template v-if="values.includes(item.value)">
        <span
          v-if="item.elTagType == 'default' || item.elTagType == ''"
          :key="item.value"
          :index="index"
          :class="item.elTagClass"
          >{{ item.label }}</span
        >
        <el-tag
          v-else
          :disable-transitions="true"
          :key="item.value + ''"
          :index="index"
          :type="item.elTagType === 'primary' ? '' : item.elTagType"
          :class="item.elTagClass"
          >{{ item.label }}</el-tag
        >
      </template>
    </template>
  </div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
const props = defineProps({
  // æ•°æ®
  options: {
    type: Array as PropType<DictDataOption[]>,
    default: null,
  },
  // å½“前的值
  value: [Number, String, Array],
    // æ•°æ®
    options: {
        type: Array as PropType<DictDataOption[]>,
        default: null,
    },
    // å½“前的值
    value: [Number, String, Array],
})
const values = computed(() => {
    if (props.value !== null && typeof props.value !== 'undefined') {
        return Array.isArray(props.value) ? props.value : [String(props.value)];
    } else {
        return [];
    }
    if (props.value !== null && typeof props.value !== 'undefined') {
        return Array.isArray(props.value) ? props.value : [String(props.value)];
    } else {
        return [];
    }
})
</script>
<template>
    <div>
        <template v-for="(item, index) in options">
            <template v-if="values.includes(item.value)">
                <span
                    v-if="item.elTagType == 'default' || item.elTagType == ''"
                    :key="item.value"
                    :index="index"
                    :class="item.elTagClass"
                    >{{ item.label }}</span
                >
                <el-tag
                    v-else
                    :disable-transitions="true"
                    :key="item.value + ''"
                    :index="index"
                    :type="item.elTagType === 'primary' ? '' : item.elTagType"
                    :class="item.elTagClass"
                    >{{ item.label }}</el-tag
                >
            </template>
        </template>
    </div>
</template>
<style scoped>
.el-tag + .el-tag {
src/components/Editor/index.vue
@@ -1,3 +1,31 @@
<template>
  <div>
    <el-upload
      :action="upload.url"
      :before-upload="handleBeforeUpload"
      :on-success="handleUploadSuccess"
      :on-error="handleUploadError"
      class="editor-img-uploader"
      name="file"
      :show-file-list="false"
      :headers="upload.headers"
      style="display: none"
      v-if="type === 'url'"
    >
    </el-upload>
    <div class="editor">
      <quill-editor
        ref="myQuillEditor"
        v-model:content="content"
        contentType="html"
        @textChange="(e: any) => $emit('update:modelValue', content)"
        :options="options"
        :style="styles"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
@@ -5,165 +33,137 @@
import { ComponentInternalInstance } from "vue";
const props = defineProps({
  /* ç¼–辑器的内容 */
  modelValue: {
    type: String,
  },
  /* é«˜åº¦ */
  height: {
    type: Number,
    default: null,
  },
  /* æœ€å°é«˜åº¦ */
  minHeight: {
    type: Number,
    default: null,
  },
  /* åªè¯» */
  readOnly: {
    type: Boolean,
    default: false,
  },
  /* ä¸Šä¼ æ–‡ä»¶å¤§å°é™åˆ¶(MB) */
  fileSize: {
    type: Number,
    default: 5,
  },
  /* ç±»åž‹ï¼ˆbase64格式、url格式) */
  type: {
    type: String,
    default: "url",
  }
    /* ç¼–辑器的内容 */
    modelValue: {
        type: String,
    },
    /* é«˜åº¦ */
    height: {
        type: Number,
        default: null,
    },
    /* æœ€å°é«˜åº¦ */
    minHeight: {
        type: Number,
        default: null,
    },
    /* åªè¯» */
    readOnly: {
        type: Boolean,
        default: false,
    },
    /* ä¸Šä¼ æ–‡ä»¶å¤§å°é™åˆ¶(MB) */
    fileSize: {
        type: Number,
        default: 5,
    },
    /* ç±»åž‹ï¼ˆbase64格式、url格式) */
    type: {
        type: String,
        default: "url",
    }
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const upload = reactive<UploadOption>({
  headers: { Authorization: "Bearer " + getToken() },
  url: import.meta.env.VITE_APP_BASE_API + '/system/oss/upload'
    headers: { Authorization: "Bearer " + getToken() },
    url: import.meta.env.VITE_APP_BASE_API + '/system/oss/upload'
})
const myQuillEditor = ref();
const options = ref({
  theme: "snow",
  bounds: document.body,
  debug: "warn",
  modules: {
    // å·¥å…·æ é…ç½®
    toolbar: {
      container: [
        ["bold", "italic", "underline", "strike"],       // åŠ ç²— æ–œä½“ ä¸‹åˆ’线 åˆ é™¤çº¿
        ["blockquote", "code-block"],                    // å¼•用  ä»£ç å—
        [{ list: "ordered" }, { list: "bullet"} ],       // æœ‰åºã€æ— åºåˆ—表
        [{ indent: "-1" }, { indent: "+1" }],            // ç¼©è¿›
        [{ size: ["small", false, "large", "huge"] }],   // å­—体大小
        [{ header: [1, 2, 3, 4, 5, 6, false] }],         // æ ‡é¢˜
        [{ color: [] }, { background: [] }],             // å­—体颜色、字体背景颜色
        [{ align: [] }],                                 // å¯¹é½æ–¹å¼
        ["clean"],                                       // æ¸…除文本格式
        ["link", "image", "video"]                       // é“¾æŽ¥ã€å›¾ç‰‡ã€è§†é¢‘
      ],
      handlers: {
        image: function (value: any) {
          if (value) {
            // è°ƒç”¨element图片上传
            (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
          } else {
            Quill.format("image", true);
          }
        },
      },
    }
  },
  placeholder: '请输入内容',
  readOnly: props.readOnly,
    theme: "snow",
    bounds: document.body,
    debug: "warn",
    modules: {
        // å·¥å…·æ é…ç½®
        toolbar: {
            container: [
                ["bold", "italic", "underline", "strike"],       // åŠ ç²— æ–œä½“ ä¸‹åˆ’线 åˆ é™¤çº¿
                ["blockquote", "code-block"],                    // å¼•用  ä»£ç å—
                [{ list: "ordered" }, { list: "bullet"} ],       // æœ‰åºã€æ— åºåˆ—表
                [{ indent: "-1" }, { indent: "+1" }],            // ç¼©è¿›
                [{ size: ["small", false, "large", "huge"] }],   // å­—体大小
                [{ header: [1, 2, 3, 4, 5, 6, false] }],         // æ ‡é¢˜
                [{ color: [] }, { background: [] }],             // å­—体颜色、字体背景颜色
                [{ align: [] }],                                 // å¯¹é½æ–¹å¼
                ["clean"],                                       // æ¸…除文本格式
                ["link", "image", "video"]                       // é“¾æŽ¥ã€å›¾ç‰‡ã€è§†é¢‘
            ],
            handlers: {
                image: function (value: any) {
                    if (value) {
                        // è°ƒç”¨element图片上传
                        (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
                    } else {
                        Quill.format("image", true);
                    }
                },
            },
        }
    },
    placeholder: '请输入内容',
    readOnly: props.readOnly,
});
const styles = computed(() => {
  let style: any = {};
  if (props.minHeight) {
    style.minHeight = `${props.minHeight}px`;
  }
  if (props.height) {
    style.height = `${props.height}px`;
  }
  return style;
    let style: any = {};
    if (props.minHeight) {
        style.minHeight = `${props.minHeight}px`;
    }
    if (props.height) {
        style.height = `${props.height}px`;
    }
    return style;
})
const content = ref("");
watch(() => props.modelValue, (v) => {
  if (v !== content.value) {
    content.value = v === undefined ? "<p></p>" : v;
  }
    if (v !== content.value) {
        content.value = v === undefined ? "<p></p>" : v;
    }
}, { immediate: true });
// å›¾ç‰‡ä¸Šä¼ æˆåŠŸè¿”å›žå›¾ç‰‡åœ°å€
const handleUploadSuccess = (res: any) => {
  // èŽ·å–å¯Œæ–‡æœ¬å®žä¾‹
  let quill = toRaw(myQuillEditor.value).getQuill();
  // å¦‚果上传成功
  if (res.code === 200) {
    // èŽ·å–å…‰æ ‡ä½ç½®
    let length = quill.selection.savedRange.index;
    // æ’入图片,res为服务器返回的图片链接地址
    quill.insertEmbed(length, "image", res.data.url);
    // è°ƒæ•´å…‰æ ‡åˆ°æœ€åŽ
    quill.setSelection(length + 1);
    proxy?.$modal.closeLoading();
  } else {
    proxy?.$modal.loading(res.msg);
    proxy?.$modal.closeLoading();
  }
    // èŽ·å–å¯Œæ–‡æœ¬å®žä¾‹
    let quill = toRaw(myQuillEditor.value).getQuill();
    // å¦‚果上传成功
    if (res.code === 200) {
        // èŽ·å–å…‰æ ‡ä½ç½®
        let length = quill.selection.savedRange.index;
        // æ’入图片,res为服务器返回的图片链接地址
        quill.insertEmbed(length, "image", res.data.url);
        // è°ƒæ•´å…‰æ ‡åˆ°æœ€åŽ
        quill.setSelection(length + 1);
        proxy?.$modal.closeLoading();
    } else {
        proxy?.$modal.loading(res.msg);
        proxy?.$modal.closeLoading();
    }
}
// å›¾ç‰‡ä¸Šä¼ å‰æ‹¦æˆª
const handleBeforeUpload = (file: any) => {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
      return false;
    // æ ¡æ£€æ–‡ä»¶å¤§å°
    if (props.fileSize) {
        const isLt = file.size / 1024 / 1024 < props.fileSize;
        if (!isLt) {
            proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
            return false;
        }
    }
  }
  proxy?.$modal.loading('正在上传文件,请稍候...');
  return true;
    proxy?.$modal.loading('正在上传文件,请稍候...');
    return true;
}
// å›¾ç‰‡å¤±è´¥æ‹¦æˆª
const handleUploadError = (err: any) => {
  console.error(err);
  proxy?.$modal.msgError('上传文件失败');
    console.error(err);
    proxy?.$modal.msgError('上传文件失败');
}
</script>
<template>
    <div>
        <el-upload
            :action="upload.url"
            :before-upload="handleBeforeUpload"
            :on-success="handleUploadSuccess"
            :on-error="handleUploadError"
            class="editor-img-uploader"
            name="file"
            :show-file-list="false"
            :headers="upload.headers"
            style="display: none"
            v-if="type === 'url'"
        >
        </el-upload>
        <div class="editor">
            <quill-editor
                ref="myQuillEditor"
                v-model:content="content"
                contentType="html"
                @textChange="(e: any) => $emit('update:modelValue', content)"
                :options="options"
                :style="styles"
            />
        </div>
    </div>
</template>
<style>
.editor, .ql-toolbar {
src/components/FileUpload/index.vue
@@ -1,3 +1,47 @@
<template>
  <div class="upload-file">
    <el-upload
      multiple
      :action="uploadFileUrl"
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :on-success="handleUploadSuccess"
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      ref="fileUploadRef"
    >
      <!-- ä¸Šä¼ æŒ‰é’® -->
      <el-button type="primary">选取文件</el-button>
    </el-upload>
    <!-- ä¸Šä¼ æç¤º -->
    <div class="el-upload__tip" v-if="showTip">
      è¯·ä¸Šä¼ 
      <template v-if="fileSize">
        å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
      </template>
      çš„æ–‡ä»¶
    </div>
    <!-- æ–‡ä»¶åˆ—表 -->
    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
      <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
        <el-link :href="`${file.url}`" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div class="ele-upload-list__item-content-action">
          <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
        </div>
      </li>
    </transition-group>
  </div>
</template>
<script setup lang="ts">
import { getToken } from "@/utils/auth";
import { listByIds, delOss } from "@/api/system/oss";
@@ -5,27 +49,27 @@
import { ElUpload, UploadFile } from "element-plus";
const props = defineProps({
  modelValue: [String, Object, Array],
  // æ•°é‡é™åˆ¶
  limit: {
    type: Number,
    default: 5,
  },
  // å¤§å°é™åˆ¶(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
  // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
  fileType: {
    type: Array,
    default: () => ["doc", "xls", "ppt", "txt", "pdf"],
  },
  // æ˜¯å¦æ˜¾ç¤ºæç¤º
  isShowTip: {
    type: Boolean,
    default: true
  }
    modelValue: [String, Object, Array],
    // æ•°é‡é™åˆ¶
    limit: {
        type: Number,
        default: 5,
    },
    // å¤§å°é™åˆ¶(MB)
    fileSize: {
        type: Number,
        default: 5,
    },
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: {
        type: Array,
        default: () => ["doc", "xls", "ppt", "txt", "pdf"],
    },
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: {
        type: Boolean,
        default: true
    }
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -39,171 +83,127 @@
const fileList = ref<any[]>([]);
const showTip = computed(
  () => props.isShowTip && (props.fileType || props.fileSize)
    () => props.isShowTip && (props.fileType || props.fileSize)
);
const fileUploadRef = ref(ElUpload);
watch(() => props.modelValue, async val => {
  if (val) {
    let temp = 1;
    // é¦–先将值转为数组
    let list = [];
    if (Array.isArray(val)) {
      list = val;
    } else {
      const res =  await listByIds(val as string)
      list = res.data.map((oss) => {
          const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
          return data;
    if (val) {
        let temp = 1;
        // é¦–先将值转为数组
        let list = [];
        if (Array.isArray(val)) {
            list = val;
        } else {
            const res =  await listByIds(val as string)
            list = res.data.map((oss) => {
                const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
                return data;
            });
        }
        // ç„¶åŽå°†æ•°ç»„转为对象数组
        fileList.value = list.map(item => {
            item = {name: item.name, url: item.url, ossId: item.ossId};
            item.uid = item.uid || new Date().getTime() + temp++;
            return item;
        });
    } else {
        fileList.value = [];
        return [];
    }
    // ç„¶åŽå°†æ•°ç»„转为对象数组
    fileList.value = list.map(item => {
      item = {name: item.name, url: item.url, ossId: item.ossId};
      item.uid = item.uid || new Date().getTime() + temp++;
      return item;
    });
  } else {
    fileList.value = [];
    return [];
  }
},{ deep: true, immediate: true });
// ä¸Šä¼ å‰æ ¡æ£€æ ¼å¼å’Œå¤§å°
const handleBeforeUpload = (file: any) => {
  // æ ¡æ£€æ–‡ä»¶ç±»åž‹
  if (props.fileType.length) {
    const fileName = file.name.split('.');
    const fileExt = fileName[fileName.length - 1];
    const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
    if (!isTypeOk) {
      proxy?.$modal.msgError(`文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join("/")}格式文件!`);
      return false;
    // æ ¡æ£€æ–‡ä»¶ç±»åž‹
    if (props.fileType.length) {
        const fileName = file.name.split('.');
        const fileExt = fileName[fileName.length - 1];
        const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
        if (!isTypeOk) {
            proxy?.$modal.msgError(`文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join("/")}格式文件!`);
            return false;
        }
    }
  }
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
      return false;
    // æ ¡æ£€æ–‡ä»¶å¤§å°
    if (props.fileSize) {
        const isLt = file.size / 1024 / 1024 < props.fileSize;
        if (!isLt) {
            proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
            return false;
        }
    }
  }
  proxy?.$modal.loading("正在上传文件,请稍候...");
  number.value++;
  return true;
    proxy?.$modal.loading("正在上传文件,请稍候...");
    number.value++;
    return true;
}
// æ–‡ä»¶ä¸ªæ•°è¶…出
const handleExceed = () => {
  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
    proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
}
// ä¸Šä¼ å¤±è´¥
const handleUploadError = () => {
  proxy?.$modal.msgError("上传文件失败");
    proxy?.$modal.msgError("上传文件失败");
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
const handleUploadSuccess = (res:any, file: UploadFile) => {
  if (res.code === 200) {
    uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
    uploadedSuccessfully();
  } else {
    number.value--;
    proxy?.$modal.closeLoading();
    proxy?.$modal.msgError(res.msg);
    fileUploadRef.value.handleRemove(file);
    uploadedSuccessfully();
  }
    if (res.code === 200) {
        uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
        uploadedSuccessfully();
    } else {
        number.value--;
        proxy?.$modal.closeLoading();
        proxy?.$modal.msgError(res.msg);
        fileUploadRef.value.handleRemove(file);
        uploadedSuccessfully();
    }
}
// åˆ é™¤æ–‡ä»¶
const handleDelete = (index: number) => {
  let ossId = fileList.value[index].ossId;
  delOss(ossId);
  fileList.value.splice(index, 1);
  emit("update:modelValue", listToString(fileList.value));
    let ossId = fileList.value[index].ossId;
    delOss(ossId);
    fileList.value.splice(index, 1);
    emit("update:modelValue", listToString(fileList.value));
}
// ä¸Šä¼ ç»“束处理
const uploadedSuccessfully =() => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
    uploadList.value = [];
    number.value = 0;
    emit("update:modelValue", listToString(fileList.value));
    proxy?.$modal.closeLoading();
  }
    if (number.value > 0 && uploadList.value.length === number.value) {
        fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
        uploadList.value = [];
        number.value = 0;
        emit("update:modelValue", listToString(fileList.value));
        proxy?.$modal.closeLoading();
    }
}
// èŽ·å–æ–‡ä»¶åç§°
const getFileName = (name: string) => {
  // å¦‚果是url那么取最后的名字 å¦‚果不是直接返回
  if (name.lastIndexOf("/") > -1) {
    return name.slice(name.lastIndexOf("/") + 1);
  } else {
    return name;
  }
    // å¦‚果是url那么取最后的名字 å¦‚果不是直接返回
    if (name.lastIndexOf("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1);
    } else {
        return name;
    }
}
// å¯¹è±¡è½¬æˆæŒ‡å®šå­—符串分隔
const listToString = (list: any[], separator?: string) => {
  let strs = "";
  separator = separator || ",";
  list.forEach(item => {
    if (item.ossId) {
      strs += item.ossId + separator;
    }
  })
  return strs != "" ? strs.substring(0, strs.length - 1) : "";
    let strs = "";
    separator = separator || ",";
    list.forEach(item => {
        if (item.ossId) {
            strs += item.ossId + separator;
        }
    })
    return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
</script>
<template>
    <div class="upload-file">
        <el-upload
            multiple
            :action="uploadFileUrl"
            :before-upload="handleBeforeUpload"
            :file-list="fileList"
            :limit="limit"
            :on-error="handleUploadError"
            :on-exceed="handleExceed"
            :on-success="handleUploadSuccess"
            :show-file-list="false"
            :headers="headers"
            class="upload-file-uploader"
            ref="fileUploadRef"
        >
            <!-- ä¸Šä¼ æŒ‰é’® -->
            <el-button type="primary">选取文件</el-button>
        </el-upload>
        <!-- ä¸Šä¼ æç¤º -->
        <div class="el-upload__tip" v-if="showTip">
            è¯·ä¸Šä¼ 
            <template v-if="fileSize">
                å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
            </template>
            <template v-if="fileType">
                æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
            </template>
            çš„æ–‡ä»¶
        </div>
        <!-- æ–‡ä»¶åˆ—表 -->
        <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
            <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
                <el-link :href="`${file.url}`" :underline="false" target="_blank">
                    <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
                </el-link>
                <div class="ele-upload-list__item-content-action">
                    <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
                </div>
            </li>
        </transition-group>
    </div>
</template>
<style scoped lang="scss">
.upload-file-uploader {
src/components/Hamburger/index.vue
@@ -13,13 +13,13 @@
</script>
<template>
    <div style="padding: 0 15px;" @click="toggleClick">
        <svg :class="{'is-active':isActive}" class="hamburger" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64">
            <path
                d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
            />
        </svg>
    </div>
  <div style="padding: 0 15px;" @click="toggleClick">
    <svg :class="{'is-active':isActive}" class="hamburger" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64">
      <path
        d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
      />
    </svg>
  </div>
</template>
<style scoped>
src/components/HeaderSearch/index.vue
@@ -122,22 +122,22 @@
</script>
<template>
    <div :class="{ 'show': show }" class="header-search">
        <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
        <el-select
            ref="headerSearchSelectRef"
            v-model="search"
            :remote-method="querySearch"
            filterable
            default-first-option
            remote
            placeholder="Search"
            class="header-search-select"
            @change="change"
        >
            <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
        </el-select>
    </div>
  <div :class="{ 'show': show }" class="header-search">
    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
    <el-select
      ref="headerSearchSelectRef"
      v-model="search"
      :remote-method="querySearch"
      filterable
      default-first-option
      remote
      placeholder="Search"
      class="header-search-select"
      @change="change"
    >
      <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
    </el-select>
  </div>
</template>
<style lang="scss" scoped>
src/components/IconSelect/index.vue
@@ -44,34 +44,34 @@
</script>
<template>
    <div class="relative" :style="{ width: width }">
        <el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
            <template #prepend>
                <svg-icon :icon-class="modelValue as string"></svg-icon>
            </template>
        </el-input>
  <div class="relative" :style="{ width: width }">
    <el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
      <template #prepend>
        <svg-icon :icon-class="modelValue as string"></svg-icon>
      </template>
    </el-input>
        <el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" :width="450">
            <template #reference>
                <div @click="visible = !visible" class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]">
                    <i-ep-caret-top v-show="visible"></i-ep-caret-top>
                    <i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom>
                </div>
            </template>
    <el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" :width="450">
      <template #reference>
        <div @click="visible = !visible" class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]">
          <i-ep-caret-top v-show="visible"></i-ep-caret-top>
          <i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom>
        </div>
      </template>
            <el-input class="p-2" v-model="filterValue" placeholder="搜索图标" clearable @input="filterIcons" />
      <el-input class="p-2" v-model="filterValue" placeholder="搜索图标" clearable @input="filterIcons" />
            <el-scrollbar height="w-[200px]">
                <ul class="icon-list">
                    <el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light">
                        <li class="icon-item" @click="selectedIcon(iconName)">
                            <svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" />
                        </li>
                    </el-tooltip>
                </ul>
            </el-scrollbar>
        </el-popover>
    </div>
      <el-scrollbar height="w-[200px]">
        <ul class="icon-list">
          <el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light">
            <li class="icon-item" @click="selectedIcon(iconName)">
              <svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" />
            </li>
          </el-tooltip>
        </ul>
      </el-scrollbar>
    </el-popover>
  </div>
</template>
<style scoped lang="scss">
src/components/IconSelect/requireIcons.ts
@@ -1,7 +1,7 @@
const icons: string[] = [];
const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
for (const path in modules) {
    const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
    icons.push(p);
  const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
  icons.push(p);
}
export default icons;
src/components/ImagePreview/index.vue
@@ -44,13 +44,13 @@
</script>
<template>
    <el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported>
        <template #error>
            <div class="image-slot">
                <el-icon><picture-filled /></el-icon>
            </div>
        </template>
    </el-image>
  <el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported>
    <template #error>
      <div class="image-slot">
        <el-icon><picture-filled /></el-icon>
      </div>
    </template>
  </el-image>
</template>
<style lang="scss" scoped>
src/components/ImageUpload/index.vue
@@ -1,3 +1,42 @@
<template>
  <div class="component-upload-image">
    <el-upload
      multiple
      :action="uploadImgUrl"
      list-type="picture-card"
      :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      ref="imageUpload"
      :before-remove="handleDelete"
      :show-file-list="true"
      :headers="headers"
      :file-list="fileList"
      :on-preview="handlePictureCardPreview"
      :class="{ hide: fileList.length >= limit }"
    >
      <el-icon class="avatar-uploader-icon"><plus /></el-icon>
    </el-upload>
    <!-- ä¸Šä¼ æç¤º -->
    <div class="el-upload__tip" v-if="showTip">
      è¯·ä¸Šä¼ 
      <template v-if="fileSize">
        å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
      </template>
      çš„æ–‡ä»¶
    </div>
    <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
      <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { getToken } from "@/utils/auth";
import { listByIds, delOss } from "@/api/system/oss";
@@ -6,27 +45,27 @@
import { ElUpload, UploadFile } from "element-plus";
const props = defineProps({
  modelValue: [String, Object, Array],
  // å›¾ç‰‡æ•°é‡é™åˆ¶
  limit: {
    type: Number,
    default: 5,
  },
  // å¤§å°é™åˆ¶(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
  // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
  fileType: {
    type: Array as PropType<string[]>,
    default: () => ["png", "jpg", "jpeg"],
  },
  // æ˜¯å¦æ˜¾ç¤ºæç¤º
  isShowTip: {
    type: Boolean,
    default: true
  },
    modelValue: [String, Object, Array],
    // å›¾ç‰‡æ•°é‡é™åˆ¶
    limit: {
        type: Number,
        default: 5,
    },
    // å¤§å°é™åˆ¶(MB)
    fileSize: {
        type: Number,
        default: 5,
    },
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: {
        type: Array as PropType<string[]>,
        default: () => ["png", "jpg", "jpeg"],
    },
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: {
        type: Boolean,
        default: true
    },
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -42,178 +81,139 @@
const fileList = ref<any[]>([]);
const showTip = computed(
  () => props.isShowTip && (props.fileType || props.fileSize)
    () => props.isShowTip && (props.fileType || props.fileSize)
);
const imageUploadRef = ref(ElUpload);
watch(() => props.modelValue, async val => {
  if (val) {
    // é¦–先将值转为数组
    let list:OssVO[] = [];
    if (Array.isArray(val)) {
      list = val as OssVO[];
    if (val) {
        // é¦–先将值转为数组
        let list:OssVO[] = [];
        if (Array.isArray(val)) {
            list = val as OssVO[];
        } else {
            const res = await listByIds(val as string)
            list = res.data
        }
        // ç„¶åŽå°†æ•°ç»„转为对象数组
        fileList.value = list.map(item => {
            // å­—符串回显处理 å¦‚果此处存的是url可直接回显 å¦‚果存的是id需要调用接口查出来
            let itemData;
            if (typeof item === "string") {
                itemData = { name: item, url: item };
            } else {
                // æ­¤å¤„name使用ossId é˜²æ­¢åˆ é™¤å‡ºçŽ°é‡å
                itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
            }
            return itemData;
        });
    } else {
      const res = await listByIds(val as string)
      list = res.data
        fileList.value = [];
        return [];
    }
    // ç„¶åŽå°†æ•°ç»„转为对象数组
    fileList.value = list.map(item => {
      // å­—符串回显处理 å¦‚果此处存的是url可直接回显 å¦‚果存的是id需要调用接口查出来
      let itemData;
      if (typeof item === "string") {
        itemData = { name: item, url: item };
      } else {
        // æ­¤å¤„name使用ossId é˜²æ­¢åˆ é™¤å‡ºçŽ°é‡å
        itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
      }
      return itemData;
    });
  } else {
    fileList.value = [];
    return [];
  }
},{ deep: true, immediate: true });
/** ä¸Šä¼ å‰loading加载 */
const handleBeforeUpload = (file: any) => {
  let isImg = false;
  if (props.fileType.length) {
    let fileExtension = "";
    if (file.name.lastIndexOf(".") > -1) {
      fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
    let isImg = false;
    if (props.fileType.length) {
        let fileExtension = "";
        if (file.name.lastIndexOf(".") > -1) {
            fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        isImg = props.fileType.some((type) => {
            if (file.type.indexOf(type) > -1) return true;
            if (fileExtension && fileExtension.indexOf(type) > -1) return true;
            return false;
        });
    } else {
        isImg = file.type.indexOf("image") > -1;
    }
    isImg = props.fileType.some((type) => {
      if (file.type.indexOf(type) > -1) return true;
      if (fileExtension && fileExtension.indexOf(type) > -1) return true;
      return false;
    });
  } else {
    isImg = file.type.indexOf("image") > -1;
  }
  if (!isImg) {
    proxy?.$modal.msgError(
      `文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join("/")}图片格式文件!`
    );
    return false;
  }
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
      return false;
    if (!isImg) {
        proxy?.$modal.msgError(
            `文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join("/")}图片格式文件!`
        );
        return false;
    }
  }
  proxy?.$modal.loading("正在上传图片,请稍候...");
  number.value++;
    if (props.fileSize) {
        const isLt = file.size / 1024 / 1024 < props.fileSize;
        if (!isLt) {
            proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
            return false;
        }
    }
    proxy?.$modal.loading("正在上传图片,请稍候...");
    number.value++;
}
// æ–‡ä»¶ä¸ªæ•°è¶…出
const handleExceed = () => {
  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
    proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
const handleUploadSuccess = (res: any, file: UploadFile) => {
  if (res.code === 200) {
    uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
    uploadedSuccessfully();
  } else {
    number.value--;
    proxy?.$modal.closeLoading();
    proxy?.$modal.msgError(res.msg);
    imageUploadRef.value.handleRemove(file);
    uploadedSuccessfully();
  }
    if (res.code === 200) {
        uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
        uploadedSuccessfully();
    } else {
        number.value--;
        proxy?.$modal.closeLoading();
        proxy?.$modal.msgError(res.msg);
        imageUploadRef.value.handleRemove(file);
        uploadedSuccessfully();
    }
}
// åˆ é™¤å›¾ç‰‡
const handleDelete = (file: UploadFile): boolean => {
  const findex = fileList.value.map(f => f.name).indexOf(file.name);
  if (findex > -1 && uploadList.value.length === number.value) {
    let ossId = fileList.value[findex].ossId;
    delOss(ossId);
    fileList.value.splice(findex, 1);
    emit("update:modelValue", listToString(fileList.value));
    return false;
  }
  return true;
    const findex = fileList.value.map(f => f.name).indexOf(file.name);
    if (findex > -1 && uploadList.value.length === number.value) {
        let ossId = fileList.value[findex].ossId;
        delOss(ossId);
        fileList.value.splice(findex, 1);
        emit("update:modelValue", listToString(fileList.value));
        return false;
    }
    return true;
}
// ä¸Šä¼ ç»“束处理
const uploadedSuccessfully = () => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
    uploadList.value = [];
    number.value = 0;
    emit("update:modelValue", listToString(fileList.value));
    proxy?.$modal.closeLoading();
  }
    if (number.value > 0 && uploadList.value.length === number.value) {
        fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
        uploadList.value = [];
        number.value = 0;
        emit("update:modelValue", listToString(fileList.value));
        proxy?.$modal.closeLoading();
    }
}
// ä¸Šä¼ å¤±è´¥
const handleUploadError = () => {
  proxy?.$modal.msgError("上传图片失败");
  proxy?.$modal.closeLoading();
    proxy?.$modal.msgError("上传图片失败");
    proxy?.$modal.closeLoading();
}
// é¢„览
const handlePictureCardPreview = (file: any) => {
  dialogImageUrl.value = file.url;
  dialogVisible.value = true;
    dialogImageUrl.value = file.url;
    dialogVisible.value = true;
}
// å¯¹è±¡è½¬æˆæŒ‡å®šå­—符串分隔
const listToString = (list: any[], separator?: string) => {
  let strs = "";
  separator = separator || ",";
  for (let i in list) {
    if(undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
      strs += list[i].ossId + separator;
    let strs = "";
    separator = separator || ",";
    for (let i in list) {
        if(undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
            strs += list[i].ossId + separator;
        }
    }
  }
  return strs != "" ? strs.substring(0, strs.length - 1) : "";
    return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
</script>
<template>
    <div class="component-upload-image">
        <el-upload
            multiple
            :action="uploadImgUrl"
            list-type="picture-card"
            :on-success="handleUploadSuccess"
            :before-upload="handleBeforeUpload"
            :limit="limit"
            :on-error="handleUploadError"
            :on-exceed="handleExceed"
            ref="imageUpload"
            :before-remove="handleDelete"
            :show-file-list="true"
            :headers="headers"
            :file-list="fileList"
            :on-preview="handlePictureCardPreview"
            :class="{ hide: fileList.length >= limit }"
        >
            <el-icon class="avatar-uploader-icon"><plus /></el-icon>
        </el-upload>
        <!-- ä¸Šä¼ æç¤º -->
        <div class="el-upload__tip" v-if="showTip">
            è¯·ä¸Šä¼ 
            <template v-if="fileSize">
                å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
            </template>
            <template v-if="fileType">
                æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
            </template>
            çš„æ–‡ä»¶
        </div>
        <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
            <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
        </el-dialog>
    </div>
</template>
<style scoped lang="scss">
// .el-upload--picture-card æŽ§åˆ¶åŠ å·éƒ¨åˆ†
src/components/Pagination/index.vue
@@ -1,6 +1,22 @@
<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">
export default {
  name: 'Pagination'
    name: 'Pagination'
}
</script>
@@ -9,100 +25,84 @@
import { PropType } from "vue";
const props = defineProps({
  total: {
    required: true,
    type: Number
  },
  page: {
    type: Number,
    default: 1
  },
  limit: {
    type: Number,
    default: 20
  },
  pageSizes: {
    type: Array as PropType<number[]>,
    default() {
      return [10, 20, 30, 50]
    total: {
        required: true,
        type: Number
    },
    page: {
        type: Number,
        default: 1
    },
    limit: {
        type: Number,
        default: 20
    },
    pageSizes: {
        type: Array as PropType<number[]>,
        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
    },
    autoScroll: {
        type: Boolean,
        default: true
    },
    hidden: {
        type: Boolean,
        default: false
    },
    float: {
        type: String,
        default: 'right'
    }
  },
  // ç§»åŠ¨ç«¯é¡µç æŒ‰é’®çš„æ•°é‡ç«¯é»˜è®¤å€¼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
  },
  autoScroll: {
    type: Boolean,
    default: true
  },
  hidden: {
    type: Boolean,
    default: false
  },
  float: {
    type: String,
    default: 'right'
  }
})
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
const currentPage = computed({
  get() {
    return props.page
  },
  set(val) {
    emit('update:page', val)
  }
    get() {
        return props.page
    },
    set(val) {
        emit('update:page', val)
    }
})
const pageSize = computed({
  get() {
    return props.limit
  },
  set(val){
    emit('update:limit', val)
  }
    get() {
        return props.limit
    },
    set(val){
        emit('update:limit', val)
    }
})
function handleSizeChange(val: number) {
  if (currentPage.value * val > props.total) {
    currentPage.value = 1
  }
  emit('pagination', { page: currentPage.value, limit: val })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
    if (currentPage.value * val > props.total) {
        currentPage.value = 1
    }
    emit('pagination', { page: currentPage.value, limit: val })
    if (props.autoScroll) {
        scrollTo(0, 800)
    }
}
function handleCurrentChange(val: number) {
  emit('pagination', { page: val, limit: pageSize.value })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
    emit('pagination', { page: val, limit: pageSize.value })
    if (props.autoScroll) {
        scrollTo(0, 800)
    }
}
</script>
<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>
<style lang="scss" scoped>
.pagination-container {
src/components/ParentView/index.vue
@@ -1,3 +1,3 @@
<template>
    <router-view />
  <router-view />
</template>
src/components/RightToolbar/index.vue
@@ -1,23 +1,42 @@
<template>
  <div class="top-right-btn" :style="style">
    <el-row>
      <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
        <el-button circle icon="Search" @click="toggleSearch()" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="刷新" placement="top">
        <el-button circle icon="Refresh" @click="refresh()" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
        <el-button circle icon="Menu" @click="showColumn()" />
      </el-tooltip>
    </el-row>
    <el-dialog :title="title" v-model="open" append-to-body>
      <el-transfer :titles="['显示', '隐藏']" v-model="value" :data="columns" @change="dataChange"></el-transfer>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { TransferKey } from "element-plus";
import { PropType } from "vue";
const props = defineProps({
  showSearch: {
    type: Boolean,
    default: true,
  },
  columns: {
    type: Array as PropType<FieldOption[]>,
  },
  search: {
    type: Boolean,
    default: true,
  },
  gutter: {
    type: Number,
    default: 10,
  },
    showSearch: {
        type: Boolean,
        default: true,
    },
    columns: {
        type: Array as PropType<FieldOption[]>,
    },
    search: {
        type: Boolean,
        default: true,
    },
    gutter: {
        type: Number,
        default: 10,
    },
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
@@ -30,63 +49,44 @@
const open = ref(false);
const style = computed(() => {
  const ret: any = {};
  if (props.gutter) {
    ret.marginRight = `${props.gutter / 2}px`;
  }
  return ret;
    const ret: any = {};
    if (props.gutter) {
        ret.marginRight = `${props.gutter / 2}px`;
    }
    return ret;
});
// æœç´¢
function toggleSearch() {
  emits("update:showSearch", !props.showSearch);
    emits("update:showSearch", !props.showSearch);
}
// åˆ·æ–°
function refresh() {
  emits("queryTable");
    emits("queryTable");
}
// å³ä¾§åˆ—表元素变化
function dataChange(data: TransferKey[]) {
  props.columns?.forEach((item) => {
    item.visible = !data.includes(item.key);
  })
    props.columns?.forEach((item) => {
        item.visible = !data.includes(item.key);
    })
}
// æ‰“开显隐列dialog
const showColumn = () => {
  open.value = true;
    open.value = true;
}
// æ˜¾éšåˆ—初始默认隐藏列
onMounted(() => {
  props.columns?.forEach((item) => {
    if (!item.visible) {
      value.value.push(item.key);
    }
  })
    props.columns?.forEach((item) => {
        if (!item.visible) {
            value.value.push(item.key);
        }
    })
})
</script>
<template>
    <div class="top-right-btn" :style="style">
        <el-row>
            <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
                <el-button circle icon="Search" @click="toggleSearch()" />
            </el-tooltip>
            <el-tooltip class="item" effect="dark" content="刷新" placement="top">
                <el-button circle icon="Refresh" @click="refresh()" />
            </el-tooltip>
            <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
                <el-button circle icon="Menu" @click="showColumn()" />
            </el-tooltip>
        </el-row>
        <el-dialog :title="title" v-model="open" append-to-body>
            <el-transfer :titles="['显示', '隐藏']" v-model="value" :data="columns" @change="dataChange"></el-transfer>
        </el-dialog>
    </div>
</template>
<style lang="scss" scoped>
:deep(.el-transfer__button) {
src/components/RuoYiDoc/index.vue
@@ -1,7 +1,7 @@
<template>
    <div>
        <svg-icon icon-class="question" @click="goto" />
    </div>
  <div>
    <svg-icon icon-class="question" @click="goto" />
  </div>
</template>
<script setup>
src/components/RuoYiGit/index.vue
@@ -1,7 +1,7 @@
<template>
    <div>
        <svg-icon icon-class="github" @click="goto" />
    </div>
  <div>
    <svg-icon icon-class="github" @click="goto" />
  </div>
</template>
<script setup>
src/components/Screenfull/index.vue
@@ -1,7 +1,7 @@
<template>
    <div>
        <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
    </div>
  <div>
    <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
  </div>
</template>
<script setup lang="ts">
src/components/SizeSelect/index.vue
@@ -16,20 +16,20 @@
</script>
<template>
    <div>
        <el-dropdown trigger="click" @command="handleSetSize">
            <div class="size-icon--style">
                <svg-icon class-name="size-icon" icon-class="size" />
            </div>
            <template #dropdown>
                <el-dropdown-menu>
                    <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value">
                        {{ item.label }}
                    </el-dropdown-item>
                </el-dropdown-menu>
            </template>
        </el-dropdown>
    </div>
  <div>
    <el-dropdown trigger="click" @command="handleSetSize">
      <div class="size-icon--style">
        <svg-icon class-name="size-icon" icon-class="size" />
      </div>
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value">
            {{ item.label }}
          </el-dropdown-item>
        </el-dropdown-menu>
      </template>
    </el-dropdown>
  </div>
</template>
<style lang="scss" scoped>
src/components/SvgIcon/index.vue
@@ -23,9 +23,9 @@
</script>
<template>
    <svg :class="svgClass" aria-hidden="true">
        <use :xlink:href="iconName" :fill="color" />
    </svg>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" :fill="color" />
  </svg>
</template>
<style scope lang="scss">
src/components/TopNav/index.vue
@@ -1,3 +1,23 @@
<template>
    <el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
        <template v-for="(item, index) in topMenus">
            <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
            ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
            >
        </template>
        <!-- é¡¶éƒ¨èœå•超出数量折叠 -->
        <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
            <template #title>更多菜单</template>
            <template v-for="(item, index) in topMenus">
                <el-menu-item :index="item.path" :key="index" v-if="index >= visibleNumber"
                ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
                >
            </template>
        </el-sub-menu>
    </el-menu>
</template>
<script setup lang="ts">
import { constantRoutes } from '@/router';
import { isHttp } from '@/utils/validate';
@@ -129,26 +149,6 @@
  setVisibleNumber()
})
</script>
<template>
    <el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
        <template v-for="(item, index) in topMenus">
            <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
                ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
            >
        </template>
        <!-- é¡¶éƒ¨èœå•超出数量折叠 -->
        <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
            <template #title>更多菜单</template>
            <template v-for="(item, index) in topMenus">
                <el-menu-item :index="item.path" :key="index" v-if="index >= visibleNumber"
                    ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
                >
            </template>
        </el-sub-menu>
    </el-menu>
</template>
<style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item {
src/components/TreeSelect/index.vue
@@ -128,31 +128,31 @@
</style>
<template>
    <div class="el-tree-select">
        <el-select
            style="width: 100%"
            v-model="valueId"
            ref="treeSelect"
            :filterable="true"
            :clearable="true"
            @clear="clearHandle"
            :filter-method="selectFilterData"
            :placeholder="placeholder"
        >
            <el-option :value="valueId" :label="valueTitle">
                <el-tree
                    id="tree-option"
                    ref="selectTree"
                    :accordion="accordion"
                    :data="options"
                    :props="objMap"
                    :node-key="objMap.value"
                    :expand-on-click-node="false"
                    :default-expanded-keys="defaultExpandedKey"
                    :filter-node-method="filterNode"
                    @node-click="handleNodeClick"
                ></el-tree>
            </el-option>
        </el-select>
    </div>
  <div class="el-tree-select">
    <el-select
      style="width: 100%"
      v-model="valueId"
      ref="treeSelect"
      :filterable="true"
      :clearable="true"
      @clear="clearHandle"
      :filter-method="selectFilterData"
      :placeholder="placeholder"
    >
      <el-option :value="valueId" :label="valueTitle">
        <el-tree
          id="tree-option"
          ref="selectTree"
          :accordion="accordion"
          :data="options"
          :props="objMap"
          :node-key="objMap.value"
          :expand-on-click-node="false"
          :default-expanded-keys="defaultExpandedKey"
          :filter-node-method="filterNode"
          @node-click="handleNodeClick"
        ></el-tree>
      </el-option>
    </el-select>
  </div>
</template>
src/components/iFrame/index.vue
@@ -21,7 +21,7 @@
</script>
<template>
    <div v-loading="loading" :style="'height:' + height">
        <iframe :src="url" frameborder="no" style="width: 100%; height: 100%" scrolling="auto" />
    </div>
  <div v-loading="loading" :style="'height:' + height">
    <iframe :src="url" frameborder="no" style="width: 100%; height: 100%" scrolling="auto" />
  </div>
</template>
src/directive/common/copyText.ts
@@ -4,63 +4,63 @@
 */
export default {
    beforeMount(el: any, { value, arg }: any) {
        if (arg === 'callback') {
            el.$copyCallback = value;
        } else {
            el.$copyValue = value;
            const handler = () => {
                copyTextToClipboard(el.$copyValue);
                if (el.$copyCallback) {
                    el.$copyCallback(el.$copyValue);
                }
            };
            el.addEventListener('click', handler);
            el.$destroyCopy = () => el.removeEventListener('click', handler);
        }
    }
  beforeMount(el: any, { value, arg }: any) {
    if (arg === 'callback') {
      el.$copyCallback = value;
    } else {
      el.$copyValue = value;
      const handler = () => {
        copyTextToClipboard(el.$copyValue);
        if (el.$copyCallback) {
          el.$copyCallback(el.$copyValue);
        }
      };
      el.addEventListener('click', handler);
      el.$destroyCopy = () => el.removeEventListener('click', handler);
    }
  }
};
function copyTextToClipboard(input: string, { target = document.body } = {}) {
    const element = document.createElement('textarea');
    const previouslyFocusedElement = document.activeElement as HTMLInputElement;
    element.value = input;
    // Prevent keyboard from showing on mobile
    element.setAttribute('readonly', '');
  const element = document.createElement('textarea');
  const previouslyFocusedElement = document.activeElement as HTMLInputElement;
  element.value = input;
  // Prevent keyboard from showing on mobile
  element.setAttribute('readonly', '');
    element.style.contain = 'strict';
    element.style.position = 'absolute';
    element.style.left = '-9999px';
    element.style.fontSize = '12pt'; // Prevent zooming on iOS
  element.style.contain = 'strict';
  element.style.position = 'absolute';
  element.style.left = '-9999px';
  element.style.fontSize = '12pt'; // Prevent zooming on iOS
    const selection = document.getSelection();
    let originalRange;
    if (selection) {
        originalRange = selection?.rangeCount > 0 && selection.getRangeAt(0);
    }
    target.append(element);
    element.select();
  const selection = document.getSelection();
  let originalRange;
  if (selection) {
    originalRange = selection?.rangeCount > 0 && selection.getRangeAt(0);
  }
  target.append(element);
  element.select();
    // Explicit selection workaround for iOS
    element.selectionStart = 0;
    element.selectionEnd = input.length;
  // Explicit selection workaround for iOS
  element.selectionStart = 0;
  element.selectionEnd = input.length;
    let isSuccess = false;
    try {
        isSuccess = document.execCommand('copy');
    } catch (err) {
        console.error(err);
    }
    element.remove();
  let isSuccess = false;
  try {
    isSuccess = document.execCommand('copy');
  } catch (err) {
    console.error(err);
  }
  element.remove();
    if (originalRange) {
        selection?.removeAllRanges();
        selection?.addRange(originalRange);
    }
  if (originalRange) {
    selection?.removeAllRanges();
    selection?.addRange(originalRange);
  }
    // Get the focus back on the previously focused element, if any
    if (previouslyFocusedElement) {
        previouslyFocusedElement.focus();
    }
    return isSuccess;
  // Get the focus back on the previously focused element, if any
  if (previouslyFocusedElement) {
    previouslyFocusedElement.focus();
  }
  return isSuccess;
}
src/directive/index.ts
@@ -3,7 +3,7 @@
import { App } from 'vue';
export default (app: App) => {
    app.directive('copyText', copyText);
    app.directive('hasPermi', hasPermi);
    app.directive('hasRoles', hasRoles);
  app.directive('copyText', copyText);
  app.directive('hasPermi', hasPermi);
  app.directive('hasRoles', hasRoles);
};
src/directive/permission/index.ts
@@ -4,41 +4,41 @@
 * æ“ä½œæƒé™å¤„理
 */
export const hasPermi: Directive = {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
        const { permissions } = useUserStore();
        // ã€Œå…¶ä»–角色」按钮权限校验
        const { value } = binding;
        if (value && value instanceof Array && value.length > 0) {
            const hasPermission = permissions.some((permi) => {
                return permi === '*:*:*' || value.includes(permi);
            });
            if (!hasPermission) {
                el.parentNode && el.parentNode.removeChild(el);
                return false;
            }
        } else {
            throw new Error("check perms! Like v-has-permi=\"['sys:user:add','sys:user:edit']\"");
        }
    }
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const { permissions } = useUserStore();
    // ã€Œå…¶ä»–角色」按钮权限校验
    const { value } = binding;
    if (value && value instanceof Array && value.length > 0) {
      const hasPermission = permissions.some((permi) => {
        return permi === '*:*:*' || value.includes(permi);
      });
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el);
        return false;
      }
    } else {
      throw new Error("check perms! Like v-has-permi=\"['sys:user:add','sys:user:edit']\"");
    }
  }
};
/**
 * è§’色权限处理
 */
export const hasRoles: Directive = {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
        const { value } = binding;
        const { roles } = useUserStore();
        if (value && value instanceof Array && value.length > 0) {
            const hasRole = roles.some((role) => {
                return role === 'admin' || value.includes(role);
            });
            if (!hasRole) {
                el.parentNode && el.parentNode.removeChild(el);
                return false;
            }
        } else {
            throw new Error("check roles! Like v-has-roles=\"['admin','test']\"");
        }
    }
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const { value } = binding;
    const { roles } = useUserStore();
    if (value && value instanceof Array && value.length > 0) {
      const hasRole = roles.some((role) => {
        return role === 'admin' || value.includes(role);
      });
      if (!hasRole) {
        el.parentNode && el.parentNode.removeChild(el);
        return false;
      }
    } else {
      throw new Error("check roles! Like v-has-roles=\"['admin','test']\"");
    }
  }
};
src/enums/MenuTypeEnum.ts
@@ -1,15 +1,15 @@
export enum MenuTypeEnum {
    /**
     * ç›®å½•
     */
    M = 'M',
    /**
     * èœå•
     */
    C = 'C',
  /**
   * ç›®å½•
   */
  M = 'M',
  /**
   * èœå•
   */
  C = 'C',
    /**
     * æŒ‰é’®
     */
    F = 'F'
  /**
   * æŒ‰é’®
   */
  F = 'F'
}
src/enums/RespEnum.ts
@@ -1,90 +1,90 @@
export enum HttpStatus {
    /**
     * æ“ä½œæˆåŠŸ
     */
    SUCCESS = 200,
    /**
     * å¯¹è±¡åˆ›å»ºæˆåŠŸ
     */
    CREATED = 201,
    /**
     * è¯·æ±‚已经被接受
     */
    ACCEPTED = 202,
    /**
     * æ“ä½œå·²ç»æ‰§è¡ŒæˆåŠŸï¼Œä½†æ˜¯æ²¡æœ‰è¿”å›žæ•°æ®
     */
    NO_CONTENT = 204,
    /**
     * èµ„源已经被移除
     */
    MOVED_PERM = 301,
    /**
     * é‡å®šå‘
     */
    SEE_OTHER = 303,
    /**
     * èµ„源没有被修改
     */
    NOT_MODIFIED = 304,
    /**
     * å‚数列表错误(缺少,格式不匹配)
     */
    PARAM_ERROR = 400,
    /**
     * æœªæŽˆæƒ
     */
    UNAUTHORIZED = 401,
    /**
     * è®¿é—®å—限,授权过期
     */
    FORBIDDEN = 403,
    /**
     * èµ„源,服务未找到
     */
    NOT_FOUND = 404,
    /**
     * ä¸å…è®¸çš„http方法
     */
    BAD_METHOD = 405,
    /**
     * èµ„源冲突,或者资源被锁
     */
    CONFLICT = 409,
    /**
     * ä¸æ”¯æŒçš„æ•°æ®ï¼Œåª’体类型
     */
    UNSUPPORTED_TYPE = 415,
    /**
     * ç³»ç»Ÿå†…部错误
     */
    SERVER_ERROR = 500,
    /**
     * æŽ¥å£æœªå®žçް
     */
    NOT_IMPLEMENTED = 501,
    /**
     * æœåŠ¡ä¸å¯ç”¨ï¼Œè¿‡è½½æˆ–è€…ç»´æŠ¤
     */
    BAD_GATEWAY = 502,
    /**
     * ç½‘关超时
     */
    GATEWAY_TIMEOUT = 504,
    /**
     * æœªçŸ¥é”™è¯¯
     */
    UNKNOWN_ERROR = 520,
    /**
     * æœåŠ¡æœªçŸ¥é”™è¯¯
     */
    SERVICE_ERROR = 521,
    /**
     * æ•°æ®åº“未知错误
     */
    DATABASE_ERROR = 522,
    /**
     * ç³»ç»Ÿè­¦å‘Šæ¶ˆæ¯
     */
    WARN = 601
  /**
   * æ“ä½œæˆåŠŸ
   */
  SUCCESS = 200,
  /**
   * å¯¹è±¡åˆ›å»ºæˆåŠŸ
   */
  CREATED = 201,
  /**
   * è¯·æ±‚已经被接受
   */
  ACCEPTED = 202,
  /**
   * æ“ä½œå·²ç»æ‰§è¡ŒæˆåŠŸï¼Œä½†æ˜¯æ²¡æœ‰è¿”å›žæ•°æ®
   */
  NO_CONTENT = 204,
  /**
   * èµ„源已经被移除
   */
  MOVED_PERM = 301,
  /**
   * é‡å®šå‘
   */
  SEE_OTHER = 303,
  /**
   * èµ„源没有被修改
   */
  NOT_MODIFIED = 304,
  /**
   * å‚数列表错误(缺少,格式不匹配)
   */
  PARAM_ERROR = 400,
  /**
   * æœªæŽˆæƒ
   */
  UNAUTHORIZED = 401,
  /**
   * è®¿é—®å—限,授权过期
   */
  FORBIDDEN = 403,
  /**
   * èµ„源,服务未找到
   */
  NOT_FOUND = 404,
  /**
   * ä¸å…è®¸çš„http方法
   */
  BAD_METHOD = 405,
  /**
   * èµ„源冲突,或者资源被锁
   */
  CONFLICT = 409,
  /**
   * ä¸æ”¯æŒçš„æ•°æ®ï¼Œåª’体类型
   */
  UNSUPPORTED_TYPE = 415,
  /**
   * ç³»ç»Ÿå†…部错误
   */
  SERVER_ERROR = 500,
  /**
   * æŽ¥å£æœªå®žçް
   */
  NOT_IMPLEMENTED = 501,
  /**
   * æœåŠ¡ä¸å¯ç”¨ï¼Œè¿‡è½½æˆ–è€…ç»´æŠ¤
   */
  BAD_GATEWAY = 502,
  /**
   * ç½‘关超时
   */
  GATEWAY_TIMEOUT = 504,
  /**
   * æœªçŸ¥é”™è¯¯
   */
  UNKNOWN_ERROR = 520,
  /**
   * æœåŠ¡æœªçŸ¥é”™è¯¯
   */
  SERVICE_ERROR = 521,
  /**
   * æ•°æ®åº“未知错误
   */
  DATABASE_ERROR = 522,
  /**
   * ç³»ç»Ÿè­¦å‘Šæ¶ˆæ¯
   */
  WARN = 601
}
src/enums/SettingTypeEnum.ts
@@ -1,16 +1,16 @@
export enum SettingTypeEnum {
    TITLE = 'title',
    THEME = 'theme',
    SIDE_THEME = 'sideTheme',
    SHOW_SETTINGS = 'showSettings',
    TOP_NAV = 'topNav',
    TAGS_VIEW = 'tagsView',
    FIXED_HEADER = 'fixedHeader',
    SIDEBAR_LOGO = 'sidebarLogo',
    DYNAMIC_TITLE = 'dynamicTitle',
    ANIMATION_ENABLE = 'animationEnable',
    LAYOUT = 'layout',
    DARK = 'dark',
  TITLE = 'title',
  THEME = 'theme',
  SIDE_THEME = 'sideTheme',
  SHOW_SETTINGS = 'showSettings',
  TOP_NAV = 'topNav',
  TAGS_VIEW = 'tagsView',
  FIXED_HEADER = 'fixedHeader',
  SIDEBAR_LOGO = 'sidebarLogo',
  DYNAMIC_TITLE = 'dynamicTitle',
  ANIMATION_ENABLE = 'animationEnable',
  LAYOUT = 'layout',
  DARK = 'dark',
    LAYOUT_SETTING = 'layout-setting'
  LAYOUT_SETTING = 'layout-setting'
}
src/enums/layout/LayoutEnum.ts
@@ -1,4 +1,4 @@
export enum ThemeEnum {
    DARK = 'theme-dark',
    LIGHT = 'theme-light'
  DARK = 'theme-dark',
  LIGHT = 'theme-light'
}
src/lang/en.ts
@@ -1,25 +1,25 @@
export default {
    // è·¯ç”±å›½é™…化
    route: {
        dashboard: 'Dashboard',
        document: 'Document'
    },
    // ç™»å½•页面国际化
    login: {
        title: 'vue3-element-admin',
        username: 'Username',
        password: 'Password',
        login: 'Login',
        code: 'Verification Code',
        copyright: '',
        icp: '',
        thirdPartyLogin: 'third-party login'
    },
    // å¯¼èˆªæ å›½é™…化
    navbar: {
        dashboard: 'Dashboard',
        logout: 'Logout',
        document: 'Document',
        gitee: 'Gitee'
    }
  // è·¯ç”±å›½é™…化
  route: {
    dashboard: 'Dashboard',
    document: 'Document'
  },
  // ç™»å½•页面国际化
  login: {
    title: 'vue3-element-admin',
    username: 'Username',
    password: 'Password',
    login: 'Login',
    code: 'Verification Code',
    copyright: '',
    icp: '',
    thirdPartyLogin: 'third-party login'
  },
  // å¯¼èˆªæ å›½é™…化
  navbar: {
    dashboard: 'Dashboard',
    logout: 'Logout',
    document: 'Document',
    gitee: 'Gitee'
  }
};
src/lang/index.ts
@@ -6,12 +6,12 @@
import zhCnLocale from './zh-cn';
const messages = {
    'zh-cn': {
        ...zhCnLocale
    },
    en: {
        ...enLocale
    }
  'zh-cn': {
    ...zhCnLocale
  },
  en: {
    ...enLocale
  }
};
/**
@@ -20,26 +20,26 @@
 * @returns zh-cn|en ...
 */
export const getLanguage = () => {
    // æœ¬åœ°ç¼“存获取
    let language = localStorage.getItem('language');
    if (language) {
        return language;
    }
    // æµè§ˆå™¨ä½¿ç”¨è¯­è¨€
    language = navigator.language.toLowerCase();
    const locales = Object.keys(messages);
    for (const locale of locales) {
        if (language.indexOf(locale) > -1) {
            return locale;
        }
    }
    return 'zh-cn';
  // æœ¬åœ°ç¼“存获取
  let language = localStorage.getItem('language');
  if (language) {
    return language;
  }
  // æµè§ˆå™¨ä½¿ç”¨è¯­è¨€
  language = navigator.language.toLowerCase();
  const locales = Object.keys(messages);
  for (const locale of locales) {
    if (language.indexOf(locale) > -1) {
      return locale;
    }
  }
  return 'zh-cn';
};
const i18n = createI18n({
    legacy: false,
    locale: getLanguage(),
    messages: messages
  legacy: false,
  locale: getLanguage(),
  messages: messages
});
export default i18n;
src/lang/zh-cn.ts
@@ -1,24 +1,24 @@
export default {
    // è·¯ç”±å›½é™…化
    route: {
        dashboard: '首页',
        document: '项目文档'
    },
    // ç™»å½•页面国际化
    login: {
        title: 'vue3-element-admin',
        username: '用户名',
        password: '密码',
        login: '登 å½•',
        code: '请输入验证码',
        copyright: '',
        icp: '',
        thirdPartyLogin: '第三方登录'
    },
    navbar: {
        dashboard: '首页',
        logout: '注销',
        document: '项目文档',
        gitee: '码云'
    }
  // è·¯ç”±å›½é™…化
  route: {
    dashboard: '首页',
    document: '项目文档'
  },
  // ç™»å½•页面国际化
  login: {
    title: 'vue3-element-admin',
    username: '用户名',
    password: '密码',
    login: '登 å½•',
    code: '请输入验证码',
    copyright: '',
    icp: '',
    thirdPartyLogin: '第三方登录'
  },
  navbar: {
    dashboard: '首页',
    logout: '注销',
    document: '项目文档',
    gitee: '码云'
  }
};
src/layout/components/AppMain.vue
@@ -1,6 +1,19 @@
<template>
  <section class="app-main">
    <router-view v-slot="{ Component, route }">
      <transition :enter-active-class="animante" mode="out-in">
        <keep-alive :include="tagsViewStore.cachedViews">
          <component v-if="!route.meta.link" :is="Component" :key="route.path" />
        </keep-alive>
      </transition>
    </router-view>
    <iframe-toggle />
  </section>
</template>
<script lang="ts">
export default {
  name: 'AppMin'
    name: 'AppMin'
}
</script>
@@ -16,27 +29,14 @@
const animante = ref<string>('');
const animationEnable = ref(useSettingsStore().animationEnable);
watch(()=> useSettingsStore().animationEnable, (val) => {
  animationEnable.value = val;
  if (val) {
    animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
  } else {
    animante.value = proxy?.animate.defaultAnimate as string;
  }
    animationEnable.value = val;
    if (val) {
        animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
    } else {
        animante.value = proxy?.animate.defaultAnimate as string;
    }
}, { immediate: true });
</script>
<template>
    <section class="app-main">
        <router-view v-slot="{ Component, route }">
            <transition :enter-active-class="animante" mode="out-in">
                <keep-alive :include="tagsViewStore.cachedViews">
                    <component v-if="!route.meta.link" :is="Component" :key="route.path" />
                </keep-alive>
            </transition>
        </router-view>
        <iframe-toggle />
    </section>
</template>
<style lang="scss" scoped>
.app-main {
src/layout/components/IframeToggle/index.vue
@@ -7,13 +7,13 @@
</script>
<template>
    <transition-group name="fade-transform" mode="out-in">
        <inner-link
            v-for="(item, index) in tagsViewStore.iframeViews"
            :key="item.path"
            :iframeId="'iframe' + index"
            v-show="route.path === item.path"
            :src="item.meta ? item.meta.link : ''"
        ></inner-link>
    </transition-group>
  <transition-group name="fade-transform" mode="out-in">
    <inner-link
      v-for="(item, index) in tagsViewStore.iframeViews"
      :key="item.path"
      :iframeId="'iframe' + index"
      v-show="route.path === item.path"
      :src="item.meta ? item.meta.link : ''"
    ></inner-link>
  </transition-group>
</template>
src/layout/components/InnerLink/index.vue
@@ -1,18 +1,18 @@
<template>
  <div :style="'height:' + height">
    <iframe :id="iframeId" style="width: 100%; height: 100%" :src="src" frameborder="no"></iframe>
  </div>
</template>
<script setup lang="ts">
const props = defineProps({
  src: {
    type: String,
    default: "/"
  },
  iframeId: {
    type: String
  }
    src: {
        type: String,
        default: "/"
    },
    iframeId: {
        type: String
    }
});
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
</script>
<template>
    <div :style="'height:' + height">
        <iframe :id="iframeId" style="width: 100%; height: 100%" :src="src" frameborder="no"></iframe>
    </div>
</template>
</script>
src/layout/components/Navbar.vue
@@ -1,3 +1,68 @@
<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
    <top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
    <div class="right-menu flex align-center">
      <template v-if="appStore.device !== 'mobile'">
        <el-select
          v-model="companyName"
          clearable
          filterable
          reserve-keyword
          placeholder="请选择租户"
          v-if="userId === 1 && tenantEnabled"
          @change="dynamicTenantEvent"
          @clear="dynamicClearEvent"
        >
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
        </el-select>
        <header-search id="header-search" class="right-menu-item" />
        <el-tooltip content="源码地址" effect="dark" placement="bottom">
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip content="文档地址" effect="dark" placement="bottom">
          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip content="全屏" effect="dark" placement="bottom">
          <screenfull id="screenfull" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip content="布局大小" effect="dark" placement="bottom">
          <size-select id="size-select" class="right-menu-item hover-effect" />
        </el-tooltip>
      </template>
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
          <div class="avatar-wrapper">
            <img :src="userStore.avatar" class="user-avatar" />
            <el-icon><caret-bottom /></el-icon>
          </div>
          <template #dropdown>
            <el-dropdown-menu>
              <router-link to="/user/profile" v-if="!dynamic">
                <el-dropdown-item>个人中心</el-dropdown-item>
              </router-link>
              <el-dropdown-item command="setLayout">
                <span>布局设置</span>
              </el-dropdown-item>
              <el-dropdown-item divided command="logout">
                <span>退出登录</span>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
@@ -23,129 +88,64 @@
// åŠ¨æ€åˆ‡æ¢
const dynamicTenantEvent = async (tenantId: string) => {
  if (companyName.value != null && companyName.value !== '') {
    await dynamicTenant(tenantId);
    dynamic.value = true;
    proxy?.$tab.closeAllPage();
    proxy?.$router.push('/');
  }
    if (companyName.value != null && companyName.value !== '') {
        await dynamicTenant(tenantId);
        dynamic.value = true;
        proxy?.$tab.closeAllPage();
        proxy?.$router.push('/');
    }
}
const dynamicClearEvent = async () => {
  await dynamicClear();
  dynamic.value = false;
  proxy?.$tab.closeAllPage();
  proxy?.$router.push('/')
    await dynamicClear();
    dynamic.value = false;
    proxy?.$tab.closeAllPage();
    proxy?.$router.push('/')
}
/** ç§Ÿæˆ·åˆ—表 */
const initTenantList = async () => {
  const { data } = await getTenantList();
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
      tenantList.value = data.voList;
  }
    const { data } = await getTenantList();
    tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
    if (tenantEnabled.value) {
        tenantList.value = data.voList;
    }
}
defineExpose({
  initTenantList,
    initTenantList,
})
const toggleSideBar = () => {
  appStore.toggleSideBar()
    appStore.toggleSideBar()
}
const logout = async () => {
  await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
  await userStore.logout()
  location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
    await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    })
    await userStore.logout()
    location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
}
const emits = defineEmits(['setLayout'])
const setLayout = () => {
  emits('setLayout');
    emits('setLayout');
}
// å®šä¹‰Command方法对象 é€šè¿‡key直接调用方法
const commandMap: {[key: string]: any} = {
  setLayout,
  logout
    setLayout,
    logout
};
const handleCommand = (command: string) => {
  // åˆ¤æ–­æ˜¯å¦å­˜åœ¨è¯¥æ–¹æ³•
  if (commandMap[command]) {
    commandMap[command]();
  }
    // åˆ¤æ–­æ˜¯å¦å­˜åœ¨è¯¥æ–¹æ³•
    if (commandMap[command]) {
        commandMap[command]();
    }
}
</script>
<template>
    <div class="navbar">
        <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
        <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
        <top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
        <div class="right-menu flex align-center">
            <template v-if="appStore.device !== 'mobile'">
                <el-select
                    v-model="companyName"
                    clearable
                    filterable
                    reserve-keyword
                    placeholder="请选择租户"
                    v-if="userId === 1 && tenantEnabled"
                    @change="dynamicTenantEvent"
                    @clear="dynamicClearEvent"
                >
                    <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
                    <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
                </el-select>
                <header-search id="header-search" class="right-menu-item" />
                <el-tooltip content="源码地址" effect="dark" placement="bottom">
                    <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
                </el-tooltip>
                <el-tooltip content="文档地址" effect="dark" placement="bottom">
                    <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
                </el-tooltip>
                <el-tooltip content="全屏" effect="dark" placement="bottom">
                    <screenfull id="screenfull" class="right-menu-item hover-effect" />
                </el-tooltip>
                <el-tooltip content="布局大小" effect="dark" placement="bottom">
                    <size-select id="size-select" class="right-menu-item hover-effect" />
                </el-tooltip>
            </template>
            <div class="avatar-container">
                <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
                    <div class="avatar-wrapper">
                        <img :src="userStore.avatar" class="user-avatar" />
                        <el-icon><caret-bottom /></el-icon>
                    </div>
                    <template #dropdown>
                        <el-dropdown-menu>
                            <router-link to="/user/profile" v-if="!dynamic">
                                <el-dropdown-item>个人中心</el-dropdown-item>
                            </router-link>
                            <el-dropdown-item command="setLayout">
                                <span>布局设置</span>
                            </el-dropdown-item>
                            <el-dropdown-item divided command="logout">
                                <span>退出登录</span>
                            </el-dropdown-item>
                        </el-dropdown-menu>
                    </template>
                </el-dropdown>
            </div>
        </div>
    </div>
</template>
<style lang="scss" scoped>
src/layout/components/Settings/index.vue
@@ -101,86 +101,86 @@
</script>
<template>
    <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
        <div class="setting-drawer-title">
            <h3 class="drawer-title">主题风格设置</h3>
        </div>
        <div class="setting-drawer-block-checbox">
            <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
                <img src="@/assets/images/dark.svg" alt="dark" />
                <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
                    <i aria-label="图标: check" class="anticon anticon-check">
                        <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
                            <path
                                d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
                            />
                        </svg>
                    </i>
                </div>
            </div>
            <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
                <img src="@/assets/images/light.svg" alt="light" />
                <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
                    <i aria-label="图标: check" class="anticon anticon-check">
                        <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
                            <path
                                d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
                            />
                        </svg>
                    </i>
                </div>
            </div>
        </div>
        <div class="drawer-item">
            <span>主题颜色</span>
            <span class="comp-style">
                <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />
            </span>
        </div>
        <el-divider />
  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
    <div class="setting-drawer-title">
      <h3 class="drawer-title">主题风格设置</h3>
    </div>
    <div class="setting-drawer-block-checbox">
      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
        <img src="@/assets/images/dark.svg" alt="dark" />
        <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
          <i aria-label="图标: check" class="anticon anticon-check">
            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
              <path
                d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
              />
            </svg>
          </i>
        </div>
      </div>
      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
        <img src="@/assets/images/light.svg" alt="light" />
        <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
          <i aria-label="图标: check" class="anticon anticon-check">
            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
              <path
                d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
              />
            </svg>
          </i>
        </div>
      </div>
    </div>
    <div class="drawer-item">
      <span>主题颜色</span>
      <span class="comp-style">
        <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />
      </span>
    </div>
    <el-divider />
        <h3 class="drawer-title">系统布局配置</h3>
    <h3 class="drawer-title">系统布局配置</h3>
        <div class="drawer-item">
            <span>开启 TopNav</span>
            <span class="comp-style">
                <el-switch v-model="topNav" class="drawer-switch" />
            </span>
        </div>
    <div class="drawer-item">
      <span>开启 TopNav</span>
      <span class="comp-style">
        <el-switch v-model="topNav" class="drawer-switch" />
      </span>
    </div>
        <div class="drawer-item">
            <span>开启 Tags-Views</span>
            <span class="comp-style">
                <el-switch v-model="tagsView" class="drawer-switch" />
            </span>
        </div>
    <div class="drawer-item">
      <span>开启 Tags-Views</span>
      <span class="comp-style">
        <el-switch v-model="tagsView" class="drawer-switch" />
      </span>
    </div>
        <div class="drawer-item">
            <span>固定 Header</span>
            <span class="comp-style">
                <el-switch v-model="fixedHeader" class="drawer-switch" />
            </span>
        </div>
    <div class="drawer-item">
      <span>固定 Header</span>
      <span class="comp-style">
        <el-switch v-model="fixedHeader" class="drawer-switch" />
      </span>
    </div>
        <div class="drawer-item">
            <span>显示 Logo</span>
            <span class="comp-style">
                <el-switch v-model="sidebarLogo" class="drawer-switch" />
            </span>
        </div>
    <div class="drawer-item">
      <span>显示 Logo</span>
      <span class="comp-style">
        <el-switch v-model="sidebarLogo" class="drawer-switch" />
      </span>
    </div>
        <div class="drawer-item">
            <span>动态标题</span>
            <span class="comp-style">
                <el-switch v-model="dynamicTitle" class="drawer-switch" />
            </span>
        </div>
    <div class="drawer-item">
      <span>动态标题</span>
      <span class="comp-style">
        <el-switch v-model="dynamicTitle" class="drawer-switch" />
      </span>
    </div>
        <el-divider />
    <el-divider />
        <el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
        <el-button plain icon="Refresh" @click="resetSetting">重置配置</el-button>
    </el-drawer>
    <el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
    <el-button plain icon="Refresh" @click="resetSetting">重置配置</el-button>
  </el-drawer>
</template>
<style lang="scss" scoped>
src/layout/components/Sidebar/Link.vue
@@ -34,7 +34,7 @@
</script>
<template>
    <component :is="type" v-bind="linkProps()">
        <slot />
    </component>
  <component :is="type" v-bind="linkProps()">
    <slot />
  </component>
</template>
src/layout/components/Sidebar/Logo.vue
@@ -18,26 +18,26 @@
</script>
<template>
    <div
        class="sidebar-logo-container"
        :class="{ 'collapse': collapse }"
        :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
    >
        <transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
            <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
                <img v-if="logo" :src="logo" class="sidebar-logo" />
                <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
                    {{ title }}
                </h1>
            </router-link>
            <router-link v-else key="expand" class="sidebar-logo-link" to="/">
                <img v-if="logo" :src="logo" class="sidebar-logo" />
                <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
                    {{ title }}
                </h1>
            </router-link>
        </transition>
    </div>
  <div
    class="sidebar-logo-container"
    :class="{ 'collapse': collapse }"
    :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
  >
    <transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
        <img v-if="logo" :src="logo" class="sidebar-logo" />
        <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
          {{ title }}
        </h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logo" :src="logo" class="sidebar-logo" />
        <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
          {{ title }}
        </h1>
      </router-link>
    </transition>
  </div>
</template>
<style lang="scss" scoped>
src/layout/components/Sidebar/SidebarItem.vue
@@ -1,3 +1,34 @@
<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
          <template #title>
            <span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
          </template>
        </el-menu-item>
      </app-link>
    </template>
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
      <template v-if="item.meta" #title>
        <svg-icon :icon-class="item.meta ? item.meta.icon : '' " />
        <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child as RouteOption"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-sub-menu>
  </div>
</template>
<script setup lang="ts">
import { isExternal } from '@/utils/validate'
import AppLink from './Link.vue'
@@ -6,100 +37,69 @@
import { PropType } from "vue";
const props = defineProps({
  // route object
  item: {
    type: Object as PropType<RouteOption>,
    required: true
  },
  isNest: {
    type: Boolean,
    default: false
  },
  basePath: {
    type: String,
    default: ''
  }
    // route object
    item: {
        type: Object as PropType<RouteOption>,
        required: true
    },
    isNest: {
        type: Boolean,
        default: false
    },
    basePath: {
        type: String,
        default: ''
    }
})
const onlyOneChild = ref<any>({});
const hasOneShowingChild = (children:RouteOption[] = [], parent: RouteOption) => {
  if (!children) {
    children = [];
  }
  const showingChildren = children.filter(item => {
    if (item.hidden) {
      return false
    } else {
      // Temp set(will be used if only has one showing child)
      onlyOneChild.value = item
      return true
    if (!children) {
        children = [];
    }
  })
    const showingChildren = children.filter(item => {
        if (item.hidden) {
            return false
        } else {
            // Temp set(will be used if only has one showing child)
            onlyOneChild.value = item
            return true
        }
    })
  // When there is only one child router, the child router is displayed by default
  if (showingChildren.length === 1) {
    return true
  }
    // When there is only one child router, the child router is displayed by default
    if (showingChildren.length === 1) {
        return true
    }
  // Show parent if there are no child router to display
  if (showingChildren.length === 0) {
    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
    return true
  }
    // Show parent if there are no child router to display
    if (showingChildren.length === 0) {
        onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
        return true
    }
  return false
    return false
};
const resolvePath = (routePath:string, routeQuery?:string): any => {
  if (isExternal(routePath)) {
    return routePath
  }
  if (isExternal(props.basePath)) {
    return props.basePath
  }
  if (routeQuery) {
    let query = JSON.parse(routeQuery);
    return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
  }
  return getNormalPath(props.basePath + '/' + routePath)
    if (isExternal(routePath)) {
        return routePath
    }
    if (isExternal(props.basePath)) {
        return props.basePath
    }
    if (routeQuery) {
        let query = JSON.parse(routeQuery);
        return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
    }
    return getNormalPath(props.basePath + '/' + routePath)
}
const hasTitle = (title: string | undefined): string => {
  if(!title || title.length <= 5) {
    return "";
  }
  return title;
    if(!title || title.length <= 5) {
        return "";
    }
    return title;
}
</script>
<template>
    <div v-if="!item.hidden">
        <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
            <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
                <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
                    <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
                    <template #title>
                        <span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
                    </template>
                </el-menu-item>
            </app-link>
        </template>
        <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
            <template v-if="item.meta" #title>
                <svg-icon :icon-class="item.meta ? item.meta.icon : '' " />
                <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
            </template>
            <sidebar-item
                v-for="child in item.children"
                :key="child.path"
                :is-nest="true"
                :item="child as RouteOption"
                :base-path="resolvePath(child.path)"
                class="nest-menu"
            />
        </el-sub-menu>
    </div>
</template>
src/layout/components/Sidebar/index.vue
@@ -24,7 +24,7 @@
  // if set path, the sidebar will highlight the path you set
  if (meta.activeMenu) {
    return meta.activeMenu;
    }
  }
  return path;
})
@@ -33,23 +33,23 @@
</script>
<template>
    <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
        <logo v-if="showLogo" :collapse="isCollapse" />
        <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
            <transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
                <el-menu
                    :default-active="activeMenu as string"
                    :collapse="isCollapse"
                    :background-color="bgColor"
                    :text-color="textColor"
                    :unique-opened="true"
                    :active-text-color="theme"
                    :collapse-transition="false"
                    mode="vertical"
                >
                    <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
                </el-menu>
            </transition>
        </el-scrollbar>
    </div>
  <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
      <transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
        <el-menu
          :default-active="activeMenu as string"
          :collapse="isCollapse"
          :background-color="bgColor"
          :text-color="textColor"
          :unique-opened="true"
          :active-text-color="theme"
          :collapse-transition="false"
          mode="vertical"
        >
          <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
        </el-menu>
      </transition>
    </el-scrollbar>
  </div>
</template>
src/layout/components/TagsView/ScrollPane.vue
@@ -81,9 +81,9 @@
</script>
<template>
    <el-scrollbar ref="scrollContainerRef" :vertical="false" class="scroll-container" @wheel.prevent="handleScroll">
        <slot />
    </el-scrollbar>
  <el-scrollbar ref="scrollContainerRef" :vertical="false" class="scroll-container" @wheel.prevent="handleScroll">
    <slot />
  </el-scrollbar>
</template>
<style lang="scss" scoped>
src/layout/components/TagsView/index.vue
@@ -1,3 +1,34 @@
<template>
  <div id="tags-view-container" class="tags-view-container">
    <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
      <router-link
        v-for="tag in visitedViews"
        :key="tag.path"
        :data-path="tag.path"
        :class="isActive(tag) ? 'active' : ''"
        :to="{ path: tag.path ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
        class="tags-view-item"
        :style="activeStyle(tag)"
        @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
        @contextmenu.prevent="openMenu(tag, $event)"
      >
        {{ tag.title }}
        <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
          <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
        </span>
      </router-link>
    </scroll-pane>
    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
      <li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em;" /> åˆ·æ–°é¡µé¢</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><close style="width: 1em; height: 1em;" /> å…³é—­å½“前</li>
      <li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em;" /> å…³é—­å…¶ä»–</li>
      <li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em;" /> å…³é—­å·¦ä¾§</li>
      <li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em;" /> å…³é—­å³ä¾§</li>
      <li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em;" /> å…¨éƒ¨å…³é—­</li>
    </ul>
  </div>
</template>
<script setup lang="ts">
import ScrollPane from './ScrollPane.vue'
import { getNormalPath } from '@/utils/ruoyi'
@@ -23,215 +54,184 @@
const theme = computed(() => useSettingsStore().theme);
watch(route, () => {
  addTags();
  moveToCurrentTag();
    addTags();
    moveToCurrentTag();
})
watch(visible, (value) => {
  if (value) {
    document.body.addEventListener('click', closeMenu);
  } else {
    document.body.removeEventListener('click', closeMenu);
  }
    if (value) {
        document.body.addEventListener('click', closeMenu);
    } else {
        document.body.removeEventListener('click', closeMenu);
    }
})
const isActive = (r: TagView): boolean => {
  return r.path === route.path;
    return r.path === route.path;
}
const activeStyle = (tag: TagView) => {
  if (!isActive(tag)) return {};
  return {
    "background-color": theme.value,
    "border-color": theme.value
  };
    if (!isActive(tag)) return {};
    return {
        "background-color": theme.value,
        "border-color": theme.value
    };
}
const isAffix = (tag: TagView) => {
  return tag.meta && tag.meta.affix;
    return tag.meta && tag.meta.affix;
}
const isFirstView = () => {
  try {
    return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath;
  } catch (err) {
    return false;
  }
    try {
        return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath;
    } catch (err) {
        return false;
    }
}
const isLastView = () => {
  try {
    return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
  } catch (err) {
    return false;
  }
    try {
        return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
    } catch (err) {
        return false;
    }
}
const filterAffixTags = (routes:RouteOption [], basePath = '') => {
  let tags:TagView[] = []
  routes.forEach(route => {
    if (route.meta && route.meta.affix) {
      const tagPath = getNormalPath(basePath + '/' + route.path);
      tags.push({
        fullPath: tagPath,
        path: tagPath,
        name: route.name,
        meta: { ...route.meta }
      })
    }
    if (route.children) {
      const tempTags = filterAffixTags(route.children, route.path);
      if (tempTags.length >= 1) {
        tags = [...tags, ...tempTags];
      }
    }
  })
  return tags
    let tags:TagView[] = []
    routes.forEach(route => {
        if (route.meta && route.meta.affix) {
            const tagPath = getNormalPath(basePath + '/' + route.path);
            tags.push({
                fullPath: tagPath,
                path: tagPath,
                name: route.name,
                meta: { ...route.meta }
            })
        }
        if (route.children) {
            const tempTags = filterAffixTags(route.children, route.path);
            if (tempTags.length >= 1) {
                tags = [...tags, ...tempTags];
            }
        }
    })
    return tags
}
const initTags = () => {
  const res = filterAffixTags(routes.value);
  affixTags.value = res;
  for (const tag of res) {
    // Must have tag name
    if (tag.name) {
      useTagsViewStore().addVisitedView(tag);
    const res = filterAffixTags(routes.value);
    affixTags.value = res;
    for (const tag of res) {
        // Must have tag name
        if (tag.name) {
            useTagsViewStore().addVisitedView(tag);
        }
    }
  }
}
const addTags = () => {
  const { name } = route;
  if (name) {
    useTagsViewStore().addView(route);
    if (route.meta.link) {
      useTagsViewStore().addIframeView(route);
    const { name } = route;
    if (name) {
        useTagsViewStore().addView(route);
        if (route.meta.link) {
            useTagsViewStore().addIframeView(route);
        }
    }
  }
  return false
    return false
}
const moveToCurrentTag = () => {
  nextTick(() => {
    for (const r of visitedViews.value) {
      if (r.path === route.path) {
        scrollPaneRef.value.moveToTarget(r);
        // when query is different then update
        if (r.fullPath !== route.fullPath) {
          useTagsViewStore().updateVisitedView(route);
    nextTick(() => {
        for (const r of visitedViews.value) {
            if (r.path === route.path) {
                scrollPaneRef.value.moveToTarget(r);
                // when query is different then update
                if (r.fullPath !== route.fullPath) {
                    useTagsViewStore().updateVisitedView(route);
                }
            }
        }
      }
    }
  })
    })
}
const refreshSelectedTag = (view: TagView) => {
  proxy?.$tab.refreshPage(view);
  if (route.meta.link) {
    useTagsViewStore().delIframeView(route);
  }
    proxy?.$tab.refreshPage(view);
    if (route.meta.link) {
        useTagsViewStore().delIframeView(route);
    }
}
const closeSelectedTag = (view: TagView) => {
  proxy?.$tab.closePage(view).then(({ visitedViews }: any) => {
    if (isActive(view)) {
      toLastView(visitedViews, view);
    }
  })
    proxy?.$tab.closePage(view).then(({ visitedViews }: any) => {
        if (isActive(view)) {
            toLastView(visitedViews, view);
        }
    })
}
const closeRightTags = () => {
  proxy?.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
      toLastView(visitedViews);
    }
  })
    proxy?.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
            toLastView(visitedViews);
        }
    })
}
const closeLeftTags = () => {
  proxy?.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
      toLastView(visitedViews);
    }
  })
    proxy?.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
            toLastView(visitedViews);
        }
    })
}
const closeOthersTags = () => {
  router.push(selectedTag.value as RouteLocationRaw).catch(() => { });
  proxy?.$tab.closeOtherPage(selectedTag.value).then(() => {
    moveToCurrentTag();
  })
    router.push(selectedTag.value as RouteLocationRaw).catch(() => { });
    proxy?.$tab.closeOtherPage(selectedTag.value).then(() => {
        moveToCurrentTag();
    })
}
const closeAllTags = (view: TagView) => {
  proxy?.$tab.closeAllPage().then(({ visitedViews }) => {
    if (affixTags.value.some(tag => tag.path === route.path)) {
      return;
    }
    toLastView(visitedViews, view);
  })
    proxy?.$tab.closeAllPage().then(({ visitedViews }) => {
        if (affixTags.value.some(tag => tag.path === route.path)) {
            return;
        }
        toLastView(visitedViews, view);
    })
}
const toLastView = (visitedViews:TagView[], view?: TagView) => {
  const latestView = visitedViews.slice(-1)[0];
  if (latestView) {
    router.push(latestView.fullPath as string);
  } else {
    // now the default is to redirect to the home page if there is no tags-view,
    // you can adjust it according to your needs.
    if (view?.name === 'Dashboard') {
      // to reload home page
      router.replace({ path: '/redirect' + view?.fullPath });
    const latestView = visitedViews.slice(-1)[0];
    if (latestView) {
        router.push(latestView.fullPath as string);
    } else {
      router.push('/');
        // now the default is to redirect to the home page if there is no tags-view,
        // you can adjust it according to your needs.
        if (view?.name === 'Dashboard') {
            // to reload home page
            router.replace({ path: '/redirect' + view?.fullPath });
        } else {
            router.push('/');
        }
    }
  }
}
const openMenu = (tag: TagView, e: MouseEvent) => {
  const menuMinWidth = 105;
  const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
  const offsetWidth = proxy?.$el.offsetWidth; // container width
  const maxLeft = offsetWidth - menuMinWidth; // left boundary
  const l = e.clientX - offsetLeft + 15; // 15: margin right
    const menuMinWidth = 105;
    const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
    const offsetWidth = proxy?.$el.offsetWidth; // container width
    const maxLeft = offsetWidth - menuMinWidth; // left boundary
    const l = e.clientX - offsetLeft + 15; // 15: margin right
  if (l > maxLeft) {
    left.value = maxLeft;
  } else {
    left.value = l;
  }
    if (l > maxLeft) {
        left.value = maxLeft;
    } else {
        left.value = l;
    }
  top.value = e.clientY
  visible.value = true;
  selectedTag.value = tag;
    top.value = e.clientY
    visible.value = true;
    selectedTag.value = tag;
}
const closeMenu = () => {
  visible.value = false;
    visible.value = false;
}
const handleScroll = () => {
  closeMenu();
    closeMenu();
}
onMounted(() => {
  initTags();
  addTags();
    initTags();
    addTags();
})
</script>
<template>
    <div id="tags-view-container" class="tags-view-container">
        <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
            <router-link
                v-for="tag in visitedViews"
                :key="tag.path"
                :data-path="tag.path"
                :class="isActive(tag) ? 'active' : ''"
                :to="{ path: tag.path ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
                class="tags-view-item"
                :style="activeStyle(tag)"
                @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
                @contextmenu.prevent="openMenu(tag, $event)"
            >
                {{ tag.title }}
                <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
                    <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
                </span>
            </router-link>
        </scroll-pane>
        <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
            <li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em;" /> åˆ·æ–°é¡µé¢</li>
            <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><close style="width: 1em; height: 1em;" /> å…³é—­å½“前</li>
            <li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em;" /> å…³é—­å…¶ä»–</li>
            <li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em;" /> å…³é—­å·¦ä¾§</li>
            <li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em;" /> å…³é—­å³ä¾§</li>
            <li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em;" /> å…¨éƒ¨å…³é—­</li>
        </ul>
    </div>
</template>
<style lang="scss" scoped>
.tags-view-container {
src/layout/index.vue
@@ -52,18 +52,18 @@
</script>
<template>
    <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
        <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
        <side-bar v-if="!sidebar.hide" class="sidebar-container" />
        <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
            <div :class="{ 'fixed-header': fixedHeader }">
                <navbar ref="navbarRef" @setLayout="setLayout" />
                <tags-view v-if="needTagsView" />
            </div>
            <app-main />
            <settings ref="settingRef" />
        </div>
    </div>
  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
    <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <side-bar v-if="!sidebar.hide" class="sidebar-container" />
    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
      <div :class="{ 'fixed-header': fixedHeader }">
        <navbar ref="navbarRef" @setLayout="setLayout" />
        <tags-view v-if="needTagsView" />
      </div>
      <app-main />
      <settings ref="settingRef" />
    </div>
  </div>
</template>
<style lang="scss" scoped>
src/permission.ts
@@ -13,49 +13,49 @@
const whiteList = ['/login', '/register'];
router.beforeEach(async (to, from, next) => {
    NProgress.start();
    if (getToken()) {
        to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
        /* has token*/
        if (to.path === '/login') {
            next({ path: '/' });
            NProgress.done();
        } else {
            if (useUserStore().roles.length === 0) {
                isRelogin.show = true;
                // åˆ¤æ–­å½“前用户是否已拉取完user_info信息
                const [err] = await tos(useUserStore().getInfo());
                if (err) {
                    await useUserStore().logout();
                    ElMessage.error(err);
                    next({ path: '/' });
                } else {
                    isRelogin.show = false;
                    const accessRoutes = await usePermissionStore().generateRoutes();
                    // æ ¹æ®roles权限生成可访问的路由表
                    accessRoutes.forEach((route) => {
                        if (!isHttp(route.path)) {
                            router.addRoute(route); // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
                        }
                    });
                    next({ ...to, replace: true }); // hack方法 ç¡®ä¿addRoutes已完成
                }
            } else {
                next();
            }
        }
    } else {
        // æ²¡æœ‰token
        if (whiteList.indexOf(to.path) !== -1) {
            // åœ¨å…ç™»å½•白名单,直接进入
            next();
        } else {
            next(`/login?redirect=${to.fullPath}`); // å¦åˆ™å…¨éƒ¨é‡å®šå‘到登录页
            NProgress.done();
        }
    }
  NProgress.start();
  if (getToken()) {
    to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' });
      NProgress.done();
    } else {
      if (useUserStore().roles.length === 0) {
        isRelogin.show = true;
        // åˆ¤æ–­å½“前用户是否已拉取完user_info信息
        const [err] = await tos(useUserStore().getInfo());
        if (err) {
          await useUserStore().logout();
          ElMessage.error(err);
          next({ path: '/' });
        } else {
          isRelogin.show = false;
          const accessRoutes = await usePermissionStore().generateRoutes();
          // æ ¹æ®roles权限生成可访问的路由表
          accessRoutes.forEach((route) => {
            if (!isHttp(route.path)) {
              router.addRoute(route); // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
            }
          });
          next({ ...to, replace: true }); // hack方法 ç¡®ä¿addRoutes已完成
        }
      } else {
        next();
      }
    }
  } else {
    // æ²¡æœ‰token
    if (whiteList.indexOf(to.path) !== -1) {
      // åœ¨å…ç™»å½•白名单,直接进入
      next();
    } else {
      next(`/login?redirect=${to.fullPath}`); // å¦åˆ™å…¨éƒ¨é‡å®šå‘到登录页
      NProgress.done();
    }
  }
});
router.afterEach(() => {
    NProgress.done();
  NProgress.done();
});
src/plugins/auth.ts
@@ -1,60 +1,60 @@
import useUserStore from '@/store/modules/user';
const authPermission = (permission: string): boolean => {
    const all_permission = '*:*:*';
    const permissions: string[] = useUserStore().permissions;
    if (permission && permission.length > 0) {
        return permissions.some((v) => {
            return all_permission === v || v === permission;
        });
    } else {
        return false;
    }
  const all_permission = '*:*:*';
  const permissions: string[] = useUserStore().permissions;
  if (permission && permission.length > 0) {
    return permissions.some((v) => {
      return all_permission === v || v === permission;
    });
  } else {
    return false;
  }
};
const authRole = (role: string): boolean => {
    const super_admin = 'admin';
    const roles = useUserStore().roles;
    if (role && role.length > 0) {
        return roles.some((v) => {
            return super_admin === v || v === role;
        });
    } else {
        return false;
    }
  const super_admin = 'admin';
  const roles = useUserStore().roles;
  if (role && role.length > 0) {
    return roles.some((v) => {
      return super_admin === v || v === role;
    });
  } else {
    return false;
  }
};
export default {
    // éªŒè¯ç”¨æˆ·æ˜¯å¦å…·å¤‡æŸæƒé™
    hasPermi(permission: string): boolean {
        return authPermission(permission);
    },
    // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šæƒé™ï¼Œåªéœ€åŒ…含其中一个
    hasPermiOr(permissions: string[]): boolean {
        return permissions.some((item) => {
            return authPermission(item);
        });
    },
    // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šæƒé™ï¼Œå¿…须全部拥有
    hasPermiAnd(permissions: string[]): boolean {
        return permissions.every((item) => {
            return authPermission(item);
        });
    },
    // éªŒè¯ç”¨æˆ·æ˜¯å¦å…·å¤‡æŸè§’色
    hasRole(role: string): boolean {
        return authRole(role);
    },
    // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šè§’色,只需包含其中一个
    hasRoleOr(roles: string[]): boolean {
        return roles.some((item) => {
            return authRole(item);
        });
    },
    // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šè§’色,必须全部拥有
    hasRoleAnd(roles: string[]): boolean {
        return roles.every((item) => {
            return authRole(item);
        });
    }
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å…·å¤‡æŸæƒé™
  hasPermi(permission: string): boolean {
    return authPermission(permission);
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šæƒé™ï¼Œåªéœ€åŒ…含其中一个
  hasPermiOr(permissions: string[]): boolean {
    return permissions.some((item) => {
      return authPermission(item);
    });
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šæƒé™ï¼Œå¿…须全部拥有
  hasPermiAnd(permissions: string[]): boolean {
    return permissions.every((item) => {
      return authPermission(item);
    });
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å…·å¤‡æŸè§’色
  hasRole(role: string): boolean {
    return authRole(role);
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šè§’色,只需包含其中一个
  hasRoleOr(roles: string[]): boolean {
    return roles.some((item) => {
      return authRole(item);
    });
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šè§’色,必须全部拥有
  hasRoleAnd(roles: string[]): boolean {
    return roles.every((item) => {
      return authRole(item);
    });
  }
};
src/plugins/cache.ts
@@ -1,77 +1,77 @@
const sessionCache = {
    set(key: string, value: any) {
        if (!sessionStorage) {
            return;
        }
        if (key != null && value != null) {
            sessionStorage.setItem(key, value);
        }
    },
    get(key: string) {
        if (!sessionStorage) {
            return null;
        }
        if (key == null) {
            return null;
        }
        return sessionStorage.getItem(key);
    },
    setJSON(key: string, jsonValue: any) {
        if (jsonValue != null) {
            this.set(key, JSON.stringify(jsonValue));
        }
    },
    getJSON(key: string) {
        const value = this.get(key);
        if (value != null) {
            return JSON.parse(value);
        }
    },
    remove(key: string) {
        sessionStorage.removeItem(key);
    }
  set(key: string, value: any) {
    if (!sessionStorage) {
      return;
    }
    if (key != null && value != null) {
      sessionStorage.setItem(key, value);
    }
  },
  get(key: string) {
    if (!sessionStorage) {
      return null;
    }
    if (key == null) {
      return null;
    }
    return sessionStorage.getItem(key);
  },
  setJSON(key: string, jsonValue: any) {
    if (jsonValue != null) {
      this.set(key, JSON.stringify(jsonValue));
    }
  },
  getJSON(key: string) {
    const value = this.get(key);
    if (value != null) {
      return JSON.parse(value);
    }
  },
  remove(key: string) {
    sessionStorage.removeItem(key);
  }
};
const localCache = {
    set(key: string, value: any) {
        if (!localStorage) {
            return;
        }
        if (key != null && value != null) {
            localStorage.setItem(key, value);
        }
    },
    get(key: string) {
        if (!localStorage) {
            return null;
        }
        if (key == null) {
            return null;
        }
        return localStorage.getItem(key);
    },
    setJSON(key: string, jsonValue: any) {
        if (jsonValue != null) {
            this.set(key, JSON.stringify(jsonValue));
        }
    },
    getJSON(key: string) {
        const value = this.get(key);
        if (value != null) {
            return JSON.parse(value);
        }
    },
    remove(key: string) {
        localStorage.removeItem(key);
    }
  set(key: string, value: any) {
    if (!localStorage) {
      return;
    }
    if (key != null && value != null) {
      localStorage.setItem(key, value);
    }
  },
  get(key: string) {
    if (!localStorage) {
      return null;
    }
    if (key == null) {
      return null;
    }
    return localStorage.getItem(key);
  },
  setJSON(key: string, jsonValue: any) {
    if (jsonValue != null) {
      this.set(key, JSON.stringify(jsonValue));
    }
  },
  getJSON(key: string) {
    const value = this.get(key);
    if (value != null) {
      return JSON.parse(value);
    }
  },
  remove(key: string) {
    localStorage.removeItem(key);
  }
};
export default {
    /**
     * ä¼šè¯çº§ç¼“å­˜
     */
    session: sessionCache,
    /**
     * æœ¬åœ°ç¼“å­˜
     */
    local: localCache
  /**
   * ä¼šè¯çº§ç¼“å­˜
   */
  session: sessionCache,
  /**
   * æœ¬åœ°ç¼“å­˜
   */
  local: localCache
};
src/plugins/download.ts
@@ -8,53 +8,53 @@
const baseURL = import.meta.env.VITE_APP_BASE_API;
let downloadLoadingInstance: LoadingInstance;
export default {
    async oss(ossId: string | number) {
        const url = baseURL + '/system/oss/download/' + ossId;
        downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
        try {
            const res = await axios({
                method: 'get',
                url: url,
                responseType: 'blob',
                headers: { Authorization: 'Bearer ' + getToken() }
            });
            const isBlob = blobValidate(res.data);
            if (isBlob) {
                const blob = new Blob([res.data], { type: 'application/octet-stream' });
                FileSaver.saveAs(blob, decodeURIComponent(res.headers['download-filename'] as string));
            } else {
                this.printErrMsg(res.data);
            }
            downloadLoadingInstance.close();
        } catch (r) {
            console.error(r);
            ElMessage.error('下载文件出现错误,请联系管理员!');
            downloadLoadingInstance.close();
        }
    },
    async zip(url: string, name: string) {
        url = baseURL + url;
        const res = await axios({
            method: 'get',
            url: url,
            responseType: 'blob',
            headers: {
                Authorization: 'Bearer ' + getToken(),
                datasource: localStorage.getItem('dataName')
            }
        });
        const isBlob = blobValidate(res.data);
        if (isBlob) {
            const blob = new Blob([res.data], { type: 'application/zip' });
            FileSaver.saveAs(blob, name);
        } else {
            this.printErrMsg(res.data);
        }
    },
    async printErrMsg(data: any) {
        const resText = await data.text();
        const rspObj = JSON.parse(resText);
        const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
        ElMessage.error(errMsg);
    }
  async oss(ossId: string | number) {
    const url = baseURL + '/system/oss/download/' + ossId;
    downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
    try {
      const res = await axios({
        method: 'get',
        url: url,
        responseType: 'blob',
        headers: { Authorization: 'Bearer ' + getToken() }
      });
      const isBlob = blobValidate(res.data);
      if (isBlob) {
        const blob = new Blob([res.data], { type: 'application/octet-stream' });
        FileSaver.saveAs(blob, decodeURIComponent(res.headers['download-filename'] as string));
      } else {
        this.printErrMsg(res.data);
      }
      downloadLoadingInstance.close();
    } catch (r) {
      console.error(r);
      ElMessage.error('下载文件出现错误,请联系管理员!');
      downloadLoadingInstance.close();
    }
  },
  async zip(url: string, name: string) {
    url = baseURL + url;
    const res = await axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: {
        Authorization: 'Bearer ' + getToken(),
        datasource: localStorage.getItem('dataName')
      }
    });
    const isBlob = blobValidate(res.data);
    if (isBlob) {
      const blob = new Blob([res.data], { type: 'application/zip' });
      FileSaver.saveAs(blob, name);
    } else {
      this.printErrMsg(res.data);
    }
  },
  async printErrMsg(data: any) {
    const resText = await data.text();
    const rspObj = JSON.parse(resText);
    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
    ElMessage.error(errMsg);
  }
};
src/plugins/index.ts
@@ -7,18 +7,18 @@
import { App } from 'vue';
export default function installPlugin(app: App) {
    // é¡µç­¾æ“ä½œ
    app.config.globalProperties.$tab = tab;
  // é¡µç­¾æ“ä½œ
  app.config.globalProperties.$tab = tab;
    // æ¨¡æ€æ¡†å¯¹è±¡
    app.config.globalProperties.$modal = modal;
  // æ¨¡æ€æ¡†å¯¹è±¡
  app.config.globalProperties.$modal = modal;
    // ç¼“存对象
    app.config.globalProperties.$cache = cache;
  // ç¼“存对象
  app.config.globalProperties.$cache = cache;
    // ä¸‹è½½æ–‡ä»¶
    app.config.globalProperties.$download = download;
  // ä¸‹è½½æ–‡ä»¶
  app.config.globalProperties.$download = download;
    // è®¤è¯å¯¹è±¡
    app.config.globalProperties.$auth = auth;
  // è®¤è¯å¯¹è±¡
  app.config.globalProperties.$auth = auth;
}
src/plugins/modal.ts
@@ -2,80 +2,80 @@
import { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
let loadingInstance: LoadingInstance;
export default {
    // æ¶ˆæ¯æç¤º
    msg(content: string) {
        ElMessage.info(content);
    },
    // é”™è¯¯æ¶ˆæ¯
    msgError(content: string) {
        ElMessage.error(content);
    },
    // æˆåŠŸæ¶ˆæ¯
    msgSuccess(content: string) {
        ElMessage.success(content);
    },
    // è­¦å‘Šæ¶ˆæ¯
    msgWarning(content: string) {
        ElMessage.warning(content);
    },
    // å¼¹å‡ºæç¤º
    alert(content: string) {
        ElMessageBox.alert(content, '系统提示');
    },
    // é”™è¯¯æç¤º
    alertError(content: string) {
        ElMessageBox.alert(content, '系统提示', { type: 'error' });
    },
    // æˆåŠŸæç¤º
    alertSuccess(content: string, s: string, p: { dangerouslyUseHTMLString: boolean }) {
        ElMessageBox.alert(content, '系统提示', { type: 'success' });
    },
    // è­¦å‘Šæç¤º
    alertWarning(content: string) {
        ElMessageBox.alert(content, '系统提示', { type: 'warning' });
    },
    // é€šçŸ¥æç¤º
    notify(content: string) {
        ElNotification.info(content);
    },
    // é”™è¯¯é€šçŸ¥
    notifyError(content: string) {
        ElNotification.error(content);
    },
    // æˆåŠŸé€šçŸ¥
    notifySuccess(content: string) {
        ElNotification.success(content);
    },
    // è­¦å‘Šé€šçŸ¥
    notifyWarning(content: string) {
        ElNotification.warning(content);
    },
    // ç¡®è®¤çª—体
    confirm(content: string): Promise<MessageBoxData> {
        return ElMessageBox.confirm(content, '系统提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
        });
    },
    // æäº¤å†…容
    prompt(content: string) {
        return ElMessageBox.prompt(content, '系统提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
        });
    },
    // æ‰“开遮罩层
    loading(content: string) {
        loadingInstance = ElLoading.service({
            lock: true,
            text: content,
            background: 'rgba(0, 0, 0, 0.7)'
        });
    },
    // å…³é—­é®ç½©å±‚
    closeLoading() {
        loadingInstance.close();
    }
  // æ¶ˆæ¯æç¤º
  msg(content: string) {
    ElMessage.info(content);
  },
  // é”™è¯¯æ¶ˆæ¯
  msgError(content: string) {
    ElMessage.error(content);
  },
  // æˆåŠŸæ¶ˆæ¯
  msgSuccess(content: string) {
    ElMessage.success(content);
  },
  // è­¦å‘Šæ¶ˆæ¯
  msgWarning(content: string) {
    ElMessage.warning(content);
  },
  // å¼¹å‡ºæç¤º
  alert(content: string) {
    ElMessageBox.alert(content, '系统提示');
  },
  // é”™è¯¯æç¤º
  alertError(content: string) {
    ElMessageBox.alert(content, '系统提示', { type: 'error' });
  },
  // æˆåŠŸæç¤º
  alertSuccess(content: string, s: string, p: { dangerouslyUseHTMLString: boolean }) {
    ElMessageBox.alert(content, '系统提示', { type: 'success' });
  },
  // è­¦å‘Šæç¤º
  alertWarning(content: string) {
    ElMessageBox.alert(content, '系统提示', { type: 'warning' });
  },
  // é€šçŸ¥æç¤º
  notify(content: string) {
    ElNotification.info(content);
  },
  // é”™è¯¯é€šçŸ¥
  notifyError(content: string) {
    ElNotification.error(content);
  },
  // æˆåŠŸé€šçŸ¥
  notifySuccess(content: string) {
    ElNotification.success(content);
  },
  // è­¦å‘Šé€šçŸ¥
  notifyWarning(content: string) {
    ElNotification.warning(content);
  },
  // ç¡®è®¤çª—体
  confirm(content: string): Promise<MessageBoxData> {
    return ElMessageBox.confirm(content, '系统提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    });
  },
  // æäº¤å†…容
  prompt(content: string) {
    return ElMessageBox.prompt(content, '系统提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    });
  },
  // æ‰“开遮罩层
  loading(content: string) {
    loadingInstance = ElLoading.service({
      lock: true,
      text: content,
      background: 'rgba(0, 0, 0, 0.7)'
    });
  },
  // å…³é—­é®ç½©å±‚
  closeLoading() {
    loadingInstance.close();
  }
};
src/plugins/svgicon.ts
@@ -2,9 +2,9 @@
import { App } from 'vue';
export default {
    install: (app: App) => {
        for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
            app.component(key, component);
        }
    }
  install: (app: App) => {
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
      app.component(key, component);
    }
  }
};
src/plugins/tab.ts
@@ -3,63 +3,63 @@
import { TagView, RouteLocationRaw } from 'vue-router';
export default {
    // åˆ·æ–°å½“前tab页签
    async refreshPage(obj: TagView): Promise<void> {
        const { path, query, matched } = router.currentRoute.value;
        if (obj === undefined) {
            matched.forEach((m) => {
                if (m.components && m.components.default && m.components.default.name) {
                    if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
                        obj = { name: m.components.default.name, path: path, query: query };
                    }
                }
            });
        }
        // prettier-ignore
        await useTagsViewStore().delCachedView(obj)
        router.replace({
            path: '/redirect' + obj.path,
            query: obj.query
        });
    },
    // å…³é—­å½“前tab页签,打开新页签
    closeOpenPage(obj: RouteLocationRaw): void {
        useTagsViewStore().delView(router.currentRoute.value);
        if (obj !== undefined) {
            router.push(obj);
        }
    },
    // å…³é—­æŒ‡å®štab页签
    async closePage(obj?: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] } | any> {
        if (obj === undefined) {
            // prettier-ignore
            const { lastPath } = await useTagsViewStore().delView(router.currentRoute.value) as any
            return router.push(lastPath || '/index');
        }
        return useTagsViewStore().delView(obj);
    },
    // å…³é—­æ‰€æœ‰tab页签
    closeAllPage() {
        return useTagsViewStore().delAllViews();
    },
    // å…³é—­å·¦ä¾§tab页签
    closeLeftPage(obj: TagView) {
        return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
    },
    // å…³é—­å³ä¾§tab页签
    closeRightPage(obj: TagView) {
        return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
    },
    // å…³é—­å…¶ä»–tab页签
    closeOtherPage(obj: TagView) {
        return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
    },
    // æ‰“å¼€tab页签
    openPage(url: RouteLocationRaw) {
        return router.push(url);
    },
    // ä¿®æ”¹tab页签
    updatePage(obj: TagView) {
        return useTagsViewStore().updateVisitedView(obj);
    }
  // åˆ·æ–°å½“前tab页签
  async refreshPage(obj: TagView): Promise<void> {
    const { path, query, matched } = router.currentRoute.value;
    if (obj === undefined) {
      matched.forEach((m) => {
        if (m.components && m.components.default && m.components.default.name) {
          if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
            obj = { name: m.components.default.name, path: path, query: query };
          }
        }
      });
    }
    // prettier-ignore
    await useTagsViewStore().delCachedView(obj)
    router.replace({
      path: '/redirect' + obj.path,
      query: obj.query
    });
  },
  // å…³é—­å½“前tab页签,打开新页签
  closeOpenPage(obj: RouteLocationRaw): void {
    useTagsViewStore().delView(router.currentRoute.value);
    if (obj !== undefined) {
      router.push(obj);
    }
  },
  // å…³é—­æŒ‡å®štab页签
  async closePage(obj?: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] } | any> {
    if (obj === undefined) {
      // prettier-ignore
      const { lastPath } = await useTagsViewStore().delView(router.currentRoute.value) as any
      return router.push(lastPath || '/index');
    }
    return useTagsViewStore().delView(obj);
  },
  // å…³é—­æ‰€æœ‰tab页签
  closeAllPage() {
    return useTagsViewStore().delAllViews();
  },
  // å…³é—­å·¦ä¾§tab页签
  closeLeftPage(obj: TagView) {
    return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
  },
  // å…³é—­å³ä¾§tab页签
  closeRightPage(obj: TagView) {
    return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
  },
  // å…³é—­å…¶ä»–tab页签
  closeOtherPage(obj: TagView) {
    return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
  },
  // æ‰“å¼€tab页签
  openPage(url: RouteLocationRaw) {
    return router.push(url);
  },
  // ä¿®æ”¹tab页签
  updatePage(obj: TagView) {
    return useTagsViewStore().updateVisitedView(obj);
  }
};
src/router/index.ts
@@ -26,154 +26,154 @@
// å…¬å…±è·¯ç”±
export const constantRoutes: RouteOption[] = [
    {
        path: '/redirect',
        component: Layout,
        hidden: true,
        children: [
            {
                path: '/redirect/:path(.*)',
                component: () => import('@/views/redirect/index.vue')
            }
        ]
    },
    {
        path: '/login',
        component: () => import('@/views/login.vue'),
        hidden: true
    },
    {
        path: '/register',
        component: () => import('@/views/register.vue'),
        hidden: true
    },
    {
        path: '/:pathMatch(.*)*',
        component: () => import('@/views/error/404.vue'),
        hidden: true
    },
    {
        path: '/401',
        component: () => import('@/views/error/401.vue'),
        hidden: true
    },
    {
        path: '',
        component: Layout,
        redirect: '/index',
        children: [
            {
                path: '/index',
                component: () => import('@/views/index.vue'),
                name: 'Index',
                meta: { title: '首页', icon: 'dashboard', affix: true }
            }
        ]
    },
    {
        path: '/user',
        component: Layout,
        hidden: true,
        redirect: 'noredirect',
        children: [
            {
                path: 'profile',
                component: () => import('@/views/system/user/profile/index.vue'),
                name: 'Profile',
                meta: { title: '个人中心', icon: 'user' }
            }
        ]
    }
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index.vue')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login.vue'),
    hidden: true
  },
  {
    path: '/register',
    component: () => import('@/views/register.vue'),
    hidden: true
  },
  {
    path: '/:pathMatch(.*)*',
    component: () => import('@/views/error/404.vue'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error/401.vue'),
    hidden: true
  },
  {
    path: '',
    component: Layout,
    redirect: '/index',
    children: [
      {
        path: '/index',
        component: () => import('@/views/index.vue'),
        name: 'Index',
        meta: { title: '首页', icon: 'dashboard', affix: true }
      }
    ]
  },
  {
    path: '/user',
    component: Layout,
    hidden: true,
    redirect: 'noredirect',
    children: [
      {
        path: 'profile',
        component: () => import('@/views/system/user/profile/index.vue'),
        name: 'Profile',
        meta: { title: '个人中心', icon: 'user' }
      }
    ]
  }
];
// åŠ¨æ€è·¯ç”±ï¼ŒåŸºäºŽç”¨æˆ·æƒé™åŠ¨æ€åŽ»åŠ è½½
export const dynamicRoutes: RouteOption[] = [
    {
        path: '/system/user-auth',
        component: Layout,
        hidden: true,
        permissions: ['system:user:edit'],
        children: [
            {
                path: 'role/:userId(\\d+)',
                component: () => import('@/views/system/user/authRole.vue'),
                name: 'AuthRole',
                meta: { title: '分配角色', activeMenu: '/system/user', icon: '' }
            }
        ]
    },
    {
        path: '/system/role-auth',
        component: Layout,
        hidden: true,
        permissions: ['system:role:edit'],
        children: [
            {
                path: 'user/:roleId(\\d+)',
                component: () => import('@/views/system/role/authUser.vue'),
                name: 'AuthUser',
                meta: { title: '分配用户', activeMenu: '/system/role', icon: '' }
            }
        ]
    },
    {
        path: '/system/dict-data',
        component: Layout,
        hidden: true,
        permissions: ['system:dict:list'],
        children: [
            {
                path: 'index/:dictId(\\d+)',
                component: () => import('@/views/system/dict/data.vue'),
                name: 'Data',
                meta: { title: '字典数据', activeMenu: '/system/dict', icon: '' }
            }
        ]
    },
    {
        path: '/system/oss-config',
        component: Layout,
        hidden: true,
        permissions: ['monitor:job:list'],
        children: [
            {
                path: 'index',
                component: () => import('@/views/system/oss/config.vue'),
                name: 'OssConfig',
                meta: { title: '配置管理', activeMenu: '/system/oss', icon: '' }
            }
        ]
    },
    {
        path: '/tool/gen-edit',
        component: Layout,
        hidden: true,
        permissions: ['tool:gen:edit'],
        children: [
            {
                path: 'index/:tableId(\\d+)',
                component: () => import('@/views/tool/gen/editTable.vue'),
                name: 'GenEdit',
                meta: { title: '修改生成配置', activeMenu: '/tool/gen', icon: '' }
            }
        ]
    }
  {
    path: '/system/user-auth',
    component: Layout,
    hidden: true,
    permissions: ['system:user:edit'],
    children: [
      {
        path: 'role/:userId(\\d+)',
        component: () => import('@/views/system/user/authRole.vue'),
        name: 'AuthRole',
        meta: { title: '分配角色', activeMenu: '/system/user', icon: '' }
      }
    ]
  },
  {
    path: '/system/role-auth',
    component: Layout,
    hidden: true,
    permissions: ['system:role:edit'],
    children: [
      {
        path: 'user/:roleId(\\d+)',
        component: () => import('@/views/system/role/authUser.vue'),
        name: 'AuthUser',
        meta: { title: '分配用户', activeMenu: '/system/role', icon: '' }
      }
    ]
  },
  {
    path: '/system/dict-data',
    component: Layout,
    hidden: true,
    permissions: ['system:dict:list'],
    children: [
      {
        path: 'index/:dictId(\\d+)',
        component: () => import('@/views/system/dict/data.vue'),
        name: 'Data',
        meta: { title: '字典数据', activeMenu: '/system/dict', icon: '' }
      }
    ]
  },
  {
    path: '/system/oss-config',
    component: Layout,
    hidden: true,
    permissions: ['monitor:job:list'],
    children: [
      {
        path: 'index',
        component: () => import('@/views/system/oss/config.vue'),
        name: 'OssConfig',
        meta: { title: '配置管理', activeMenu: '/system/oss', icon: '' }
      }
    ]
  },
  {
    path: '/tool/gen-edit',
    component: Layout,
    hidden: true,
    permissions: ['tool:gen:edit'],
    children: [
      {
        path: 'index/:tableId(\\d+)',
        component: () => import('@/views/tool/gen/editTable.vue'),
        name: 'GenEdit',
        meta: { title: '修改生成配置', activeMenu: '/tool/gen', icon: '' }
      }
    ]
  }
];
/**
 * åˆ›å»ºè·¯ç”±
 */
const router = createRouter({
    history: createWebHistory(import.meta.env.VITE_APP_CONTEXT_PATH),
    routes: constantRoutes,
    // åˆ·æ–°æ—¶ï¼Œæ»šåŠ¨æ¡ä½ç½®è¿˜åŽŸ
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition;
        } else {
            return { top: 0 };
        }
    }
  history: createWebHistory(import.meta.env.VITE_APP_CONTEXT_PATH),
  routes: constantRoutes,
  // åˆ·æ–°æ—¶ï¼Œæ»šåŠ¨æ¡ä½ç½®è¿˜åŽŸ
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
  }
});
export default router;
src/settings.ts
@@ -1,55 +1,55 @@
const setting: DefaultSettings = {
    /**
     * ç½‘页标题
     */
    title: import.meta.env.VITE_APP_TITLE,
  /**
   * ç½‘页标题
   */
  title: import.meta.env.VITE_APP_TITLE,
    theme: '#409EFF',
  theme: '#409EFF',
    /**
     * ä¾§è¾¹æ ä¸»é¢˜ æ·±è‰²ä¸»é¢˜theme-dark,浅色主题theme-light
     */
    sideTheme: 'theme-dark',
    /**
     * æ˜¯å¦ç³»ç»Ÿå¸ƒå±€é…ç½®
     */
    showSettings: false,
  /**
   * ä¾§è¾¹æ ä¸»é¢˜ æ·±è‰²ä¸»é¢˜theme-dark,浅色主题theme-light
   */
  sideTheme: 'theme-dark',
  /**
   * æ˜¯å¦ç³»ç»Ÿå¸ƒå±€é…ç½®
   */
  showSettings: false,
    /**
     * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
     */
    topNav: false,
  /**
   * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
   */
  topNav: false,
    /**
     * æ˜¯å¦æ˜¾ç¤º tagsView
     */
    tagsView: true,
  /**
   * æ˜¯å¦æ˜¾ç¤º tagsView
   */
  tagsView: true,
    /**
     * æ˜¯å¦å›ºå®šå¤´éƒ¨
     */
    fixedHeader: false,
  /**
   * æ˜¯å¦å›ºå®šå¤´éƒ¨
   */
  fixedHeader: false,
    /**
     * æ˜¯å¦æ˜¾ç¤ºlogo
     */
    sidebarLogo: true,
  /**
   * æ˜¯å¦æ˜¾ç¤ºlogo
   */
  sidebarLogo: true,
    /**
     * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
     */
    dynamicTitle: false,
  /**
   * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
   */
  dynamicTitle: false,
    /**
     * @type {string | array} 'production' | ['production', 'development']
     * @description Need show err logs component.
     * The default is only used in the production env
     * If you want to also use it in dev, you can pass ['production', 'development']
     */
    errorLog: 'production',
  /**
   * @type {string | array} 'production' | ['production', 'development']
   * @description Need show err logs component.
   * The default is only used in the production env
   * If you want to also use it in dev, you can pass ['production', 'development']
   */
  errorLog: 'production',
    animationEnable: false,
  animationEnable: false,
    dark: false
  dark: false
};
export default setting;
src/store/modules/app.ts
@@ -3,71 +3,71 @@
import en from 'element-plus/es/locale/lang/en';
export const useAppStore = defineStore('app', () => {
    const sidebarStatus = Cookies.get('sidebarStatus');
    const sidebar = reactive({
        opened: sidebarStatus ? !!+sidebarStatus : true,
        withoutAnimation: false,
        hide: false
    });
    const device = ref<string>('desktop');
    const size = ref(Cookies.get('size') || 'default');
    // è¯­è¨€
    const language = ref(Cookies.get('language'));
    const locale = computed(() => {
        if (language?.value == 'en') {
            return en;
        } else {
            return zhCn;
        }
    });
  const sidebarStatus = Cookies.get('sidebarStatus');
  const sidebar = reactive({
    opened: sidebarStatus ? !!+sidebarStatus : true,
    withoutAnimation: false,
    hide: false
  });
  const device = ref<string>('desktop');
  const size = ref(Cookies.get('size') || 'default');
  // è¯­è¨€
  const language = ref(Cookies.get('language'));
  const locale = computed(() => {
    if (language?.value == 'en') {
      return en;
    } else {
      return zhCn;
    }
  });
    const toggleSideBar = (withoutAnimation?: boolean) => {
        if (sidebar.hide) {
            return false;
        }
  const toggleSideBar = (withoutAnimation?: boolean) => {
    if (sidebar.hide) {
      return false;
    }
        sidebar.opened = !sidebar.opened;
        sidebar.withoutAnimation = withoutAnimation as boolean;
        if (sidebar.opened) {
            Cookies.set('sidebarStatus', '1');
        } else {
            Cookies.set('sidebarStatus', '0');
        }
    };
    sidebar.opened = !sidebar.opened;
    sidebar.withoutAnimation = withoutAnimation as boolean;
    if (sidebar.opened) {
      Cookies.set('sidebarStatus', '1');
    } else {
      Cookies.set('sidebarStatus', '0');
    }
  };
    const closeSideBar = ({ withoutAnimation }: any): void => {
        Cookies.set('sidebarStatus', '0');
        sidebar.opened = false;
        sidebar.withoutAnimation = withoutAnimation;
    };
    const toggleDevice = (d: string): void => {
        device.value = d;
    };
    const setSize = (s: string): void => {
        size.value = s;
        Cookies.set('size', s);
    };
    const toggleSideBarHide = (status: boolean): void => {
        sidebar.hide = status;
    };
  const closeSideBar = ({ withoutAnimation }: any): void => {
    Cookies.set('sidebarStatus', '0');
    sidebar.opened = false;
    sidebar.withoutAnimation = withoutAnimation;
  };
  const toggleDevice = (d: string): void => {
    device.value = d;
  };
  const setSize = (s: string): void => {
    size.value = s;
    Cookies.set('size', s);
  };
  const toggleSideBarHide = (status: boolean): void => {
    sidebar.hide = status;
  };
    const changeLanguage = (val: string): void => {
        language.value = val;
    };
  const changeLanguage = (val: string): void => {
    language.value = val;
  };
    return {
        device,
        sidebar,
        language,
        locale,
        size,
        changeLanguage,
        toggleSideBar,
        closeSideBar,
        toggleDevice,
        setSize,
        toggleSideBarHide
    };
  return {
    device,
    sidebar,
    language,
    locale,
    size,
    changeLanguage,
    toggleSideBar,
    closeSideBar,
    toggleDevice,
    setSize,
    toggleSideBarHide
  };
});
export default useAppStore;
src/store/modules/dict.ts
@@ -1,78 +1,78 @@
export const useDictStore = defineStore('dict', () => {
    const dict = ref<
        Array<{
            key: string;
            value: DictDataOption[];
        }>
    >([]);
  const dict = ref<
    Array<{
      key: string;
      value: DictDataOption[];
    }>
  >([]);
    /**
     * èŽ·å–å­—å…¸
     * @param _key å­—å…¸key
     */
    const getDict = (_key: string): DictDataOption[] | null => {
        if (_key == null && _key == '') {
            return null;
        }
        try {
            for (let i = 0; i < dict.value.length; i++) {
                if (dict.value[i].key == _key) {
                    return dict.value[i].value;
                }
            }
        } catch (e) {
            return null;
        }
        return null;
    };
  /**
   * èŽ·å–å­—å…¸
   * @param _key å­—å…¸key
   */
  const getDict = (_key: string): DictDataOption[] | null => {
    if (_key == null && _key == '') {
      return null;
    }
    try {
      for (let i = 0; i < dict.value.length; i++) {
        if (dict.value[i].key == _key) {
          return dict.value[i].value;
        }
      }
    } catch (e) {
      return null;
    }
    return null;
  };
    /**
     * è®¾ç½®å­—å…¸
     * @param _key å­—å…¸key
     * @param _value å­—å…¸value
     */
    const setDict = (_key: string, _value: DictDataOption[]) => {
        if (_key !== null && _key !== '') {
            dict.value.push({
                key: _key,
                value: _value
            });
        }
    };
  /**
   * è®¾ç½®å­—å…¸
   * @param _key å­—å…¸key
   * @param _value å­—å…¸value
   */
  const setDict = (_key: string, _value: DictDataOption[]) => {
    if (_key !== null && _key !== '') {
      dict.value.push({
        key: _key,
        value: _value
      });
    }
  };
    /**
     * åˆ é™¤å­—å…¸
     * @param _key
     */
    const removeDict = (_key: string): boolean => {
        let bln = false;
        try {
            for (let i = 0; i < dict.value.length; i++) {
                if (dict.value[i].key == _key) {
                    dict.value.splice(i, 1);
                    return true;
                }
            }
        } catch (e) {
            bln = false;
        }
        return bln;
    };
  /**
   * åˆ é™¤å­—å…¸
   * @param _key
   */
  const removeDict = (_key: string): boolean => {
    let bln = false;
    try {
      for (let i = 0; i < dict.value.length; i++) {
        if (dict.value[i].key == _key) {
          dict.value.splice(i, 1);
          return true;
        }
      }
    } catch (e) {
      bln = false;
    }
    return bln;
  };
    /**
     * æ¸…空字典
     */
    const cleanDict = (): void => {
        dict.value = [];
    };
  /**
   * æ¸…空字典
   */
  const cleanDict = (): void => {
    dict.value = [];
  };
    return {
        dict,
        getDict,
        setDict,
        removeDict,
        cleanDict
    };
  return {
    dict,
    getDict,
    setDict,
    removeDict,
    cleanDict
  };
});
export default useDictStore;
src/store/modules/permission.ts
@@ -11,134 +11,134 @@
const modules = import.meta.glob('./../../views/**/*.vue');
export const usePermissionStore = defineStore('permission', () => {
    const routes = ref<RouteOption[]>([]);
    const addRoutes = ref<RouteOption[]>([]);
    const defaultRoutes = ref<RouteOption[]>([]);
    const topbarRouters = ref<RouteOption[]>([]);
    const sidebarRouters = ref<RouteOption[]>([]);
  const routes = ref<RouteOption[]>([]);
  const addRoutes = ref<RouteOption[]>([]);
  const defaultRoutes = ref<RouteOption[]>([]);
  const topbarRouters = ref<RouteOption[]>([]);
  const sidebarRouters = ref<RouteOption[]>([]);
    const setRoutes = (newRoutes: RouteOption[]): void => {
        addRoutes.value = newRoutes;
        routes.value = constantRoutes.concat(newRoutes);
    };
    const setDefaultRoutes = (routes: RouteOption[]): void => {
        defaultRoutes.value = constantRoutes.concat(routes);
    };
    const setTopbarRoutes = (routes: RouteOption[]): void => {
        topbarRouters.value = routes;
    };
    const setSidebarRouters = (routes: RouteOption[]): void => {
        sidebarRouters.value = routes;
    };
    const generateRoutes = async (): Promise<RouteOption[]> => {
        const res = await getRouters();
        const { data } = res;
        const sdata = JSON.parse(JSON.stringify(data));
        const rdata = JSON.parse(JSON.stringify(data));
        const defaultData = JSON.parse(JSON.stringify(data));
        const sidebarRoutes = filterAsyncRouter(sdata);
        const rewriteRoutes = filterAsyncRouter(rdata, undefined, true);
        const defaultRoutes = filterAsyncRouter(defaultData);
        const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
        asyncRoutes.forEach((route) => {
            router.addRoute(route);
        });
        setRoutes(rewriteRoutes);
        setSidebarRouters(constantRoutes.concat(sidebarRoutes));
        setDefaultRoutes(sidebarRoutes);
        setTopbarRoutes(defaultRoutes);
        return new Promise<RouteOption[]>((resolve) => resolve(rewriteRoutes));
    };
  const setRoutes = (newRoutes: RouteOption[]): void => {
    addRoutes.value = newRoutes;
    routes.value = constantRoutes.concat(newRoutes);
  };
  const setDefaultRoutes = (routes: RouteOption[]): void => {
    defaultRoutes.value = constantRoutes.concat(routes);
  };
  const setTopbarRoutes = (routes: RouteOption[]): void => {
    topbarRouters.value = routes;
  };
  const setSidebarRouters = (routes: RouteOption[]): void => {
    sidebarRouters.value = routes;
  };
  const generateRoutes = async (): Promise<RouteOption[]> => {
    const res = await getRouters();
    const { data } = res;
    const sdata = JSON.parse(JSON.stringify(data));
    const rdata = JSON.parse(JSON.stringify(data));
    const defaultData = JSON.parse(JSON.stringify(data));
    const sidebarRoutes = filterAsyncRouter(sdata);
    const rewriteRoutes = filterAsyncRouter(rdata, undefined, true);
    const defaultRoutes = filterAsyncRouter(defaultData);
    const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
    asyncRoutes.forEach((route) => {
      router.addRoute(route);
    });
    setRoutes(rewriteRoutes);
    setSidebarRouters(constantRoutes.concat(sidebarRoutes));
    setDefaultRoutes(sidebarRoutes);
    setTopbarRoutes(defaultRoutes);
    return new Promise<RouteOption[]>((resolve) => resolve(rewriteRoutes));
  };
    /**
     * éåŽ†åŽå°ä¼ æ¥çš„è·¯ç”±å­—ç¬¦ä¸²ï¼Œè½¬æ¢ä¸ºç»„ä»¶å¯¹è±¡
     * @param asyncRouterMap åŽå°ä¼ æ¥çš„路由字符串
     * @param lastRouter ä¸Šä¸€çº§è·¯ç”±
     * @param type æ˜¯å¦æ˜¯é‡å†™è·¯ç”±
     */
    const filterAsyncRouter = (asyncRouterMap: RouteOption[], lastRouter?: RouteOption, type = false): RouteOption[] => {
        return asyncRouterMap.filter((route) => {
            if (type && route.children) {
                route.children = filterChildren(route.children, undefined);
            }
            if (route.component) {
                // Layout ParentView ç»„件特殊处理
                if (route.component === 'Layout') {
                    route.component = Layout;
                } else if (route.component === 'ParentView') {
                    route.component = ParentView;
                } else if (route.component === 'InnerLink') {
                    route.component = InnerLink;
                } else {
                    route.component = loadView(route.component);
                }
            }
            if (route.children != null && route.children && route.children.length) {
                route.children = filterAsyncRouter(route.children, route, type);
            } else {
                delete route.children;
                delete route.redirect;
            }
            return true;
        });
    };
    const filterChildren = (childrenMap: RouteOption[], lastRouter?: RouteOption): RouteOption[] => {
        let children: RouteOption[] = [];
        childrenMap.forEach((el) => {
            if (el.children && el.children.length) {
                if (el.component === 'ParentView' && !lastRouter) {
                    el.children.forEach((c) => {
                        c.path = el.path + '/' + c.path;
                        if (c.children && c.children.length) {
                            children = children.concat(filterChildren(c.children, c));
                            return;
                        }
                        children.push(c);
                    });
                    return;
                }
            }
            if (lastRouter) {
                el.path = lastRouter.path + '/' + el.path;
            }
            children = children.concat(el);
        });
        return children;
    };
    return { routes, setRoutes, generateRoutes, setSidebarRouters, topbarRouters, sidebarRouters, defaultRoutes };
  /**
   * éåŽ†åŽå°ä¼ æ¥çš„è·¯ç”±å­—ç¬¦ä¸²ï¼Œè½¬æ¢ä¸ºç»„ä»¶å¯¹è±¡
   * @param asyncRouterMap åŽå°ä¼ æ¥çš„路由字符串
   * @param lastRouter ä¸Šä¸€çº§è·¯ç”±
   * @param type æ˜¯å¦æ˜¯é‡å†™è·¯ç”±
   */
  const filterAsyncRouter = (asyncRouterMap: RouteOption[], lastRouter?: RouteOption, type = false): RouteOption[] => {
    return asyncRouterMap.filter((route) => {
      if (type && route.children) {
        route.children = filterChildren(route.children, undefined);
      }
      if (route.component) {
        // Layout ParentView ç»„件特殊处理
        if (route.component === 'Layout') {
          route.component = Layout;
        } else if (route.component === 'ParentView') {
          route.component = ParentView;
        } else if (route.component === 'InnerLink') {
          route.component = InnerLink;
        } else {
          route.component = loadView(route.component);
        }
      }
      if (route.children != null && route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children, route, type);
      } else {
        delete route.children;
        delete route.redirect;
      }
      return true;
    });
  };
  const filterChildren = (childrenMap: RouteOption[], lastRouter?: RouteOption): RouteOption[] => {
    let children: RouteOption[] = [];
    childrenMap.forEach((el) => {
      if (el.children && el.children.length) {
        if (el.component === 'ParentView' && !lastRouter) {
          el.children.forEach((c) => {
            c.path = el.path + '/' + c.path;
            if (c.children && c.children.length) {
              children = children.concat(filterChildren(c.children, c));
              return;
            }
            children.push(c);
          });
          return;
        }
      }
      if (lastRouter) {
        el.path = lastRouter.path + '/' + el.path;
      }
      children = children.concat(el);
    });
    return children;
  };
  return { routes, setRoutes, generateRoutes, setSidebarRouters, topbarRouters, sidebarRouters, defaultRoutes };
});
// åŠ¨æ€è·¯ç”±éåŽ†ï¼ŒéªŒè¯æ˜¯å¦å…·å¤‡æƒé™
export const filterDynamicRoutes = (routes: RouteOption[]) => {
    const res: RouteOption[] = [];
    routes.forEach((route) => {
        if (route.permissions) {
            if (auth.hasPermiOr(route.permissions)) {
                res.push(route);
            }
        } else if (route.roles) {
            if (auth.hasRoleOr(route.roles)) {
                res.push(route);
            }
        }
    });
    return res;
  const res: RouteOption[] = [];
  routes.forEach((route) => {
    if (route.permissions) {
      if (auth.hasPermiOr(route.permissions)) {
        res.push(route);
      }
    } else if (route.roles) {
      if (auth.hasRoleOr(route.roles)) {
        res.push(route);
      }
    }
  });
  return res;
};
export const loadView = (view: any) => {
    let res;
    for (const path in modules) {
        const dir = path.split('views/')[1].split('.vue')[0];
        if (dir === view) {
            res = () => modules[path]();
        }
    }
    return res;
  let res;
  for (const path in modules) {
    const dir = path.split('views/')[1].split('.vue')[0];
    if (dir === view) {
      res = () => modules[path]();
    }
  }
  return res;
};
// éžsetup
export const usePermissionStoreHook = () => {
    return usePermissionStore(store);
  return usePermissionStore(store);
};
export default usePermissionStore;
src/store/modules/settings.ts
@@ -5,50 +5,50 @@
import { Ref } from 'vue';
export const useSettingsStore = defineStore('setting', () => {
    const storageSetting = JSON.parse(localStorage.getItem('layout-setting') || '{}');
  const storageSetting = JSON.parse(localStorage.getItem('layout-setting') || '{}');
    const prop: { [key: string]: Ref<any> } = {
        title: ref<string>(''),
        theme: ref<string>(storageSetting.theme || defaultSettings.theme),
        sideTheme: ref<string>(storageSetting.sideTheme || defaultSettings.sideTheme),
        showSettings: ref<boolean>(storageSetting.showSettings),
        topNav: ref<boolean>(storageSetting.topNav || defaultSettings.topNav),
        tagsView: ref<boolean>(storageSetting.tagsView || defaultSettings.tagsView),
        fixedHeader: ref<boolean>(storageSetting.fixedHeader || defaultSettings.fixedHeader),
        sidebarLogo: ref<boolean>(storageSetting.sidebarLogo || defaultSettings.sidebarLogo),
        dynamicTitle: ref<boolean>(storageSetting.dynamicTitle || defaultSettings.dynamicTitle),
        animationEnable: ref<boolean>(storageSetting.animationEnable || defaultSettings.animationEnable),
        dark: ref<boolean>(storageSetting.dark || defaultSettings.dark)
    };
  const prop: { [key: string]: Ref<any> } = {
    title: ref<string>(''),
    theme: ref<string>(storageSetting.theme || defaultSettings.theme),
    sideTheme: ref<string>(storageSetting.sideTheme || defaultSettings.sideTheme),
    showSettings: ref<boolean>(storageSetting.showSettings),
    topNav: ref<boolean>(storageSetting.topNav || defaultSettings.topNav),
    tagsView: ref<boolean>(storageSetting.tagsView || defaultSettings.tagsView),
    fixedHeader: ref<boolean>(storageSetting.fixedHeader || defaultSettings.fixedHeader),
    sidebarLogo: ref<boolean>(storageSetting.sidebarLogo || defaultSettings.sidebarLogo),
    dynamicTitle: ref<boolean>(storageSetting.dynamicTitle || defaultSettings.dynamicTitle),
    animationEnable: ref<boolean>(storageSetting.animationEnable || defaultSettings.animationEnable),
    dark: ref<boolean>(storageSetting.dark || defaultSettings.dark)
  };
    const { title, theme, sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle, animationEnable, dark } = prop;
  const { title, theme, sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle, animationEnable, dark } = prop;
    // actions
    const changeSetting = (param: { key: SettingTypeEnum; value: any }) => {
        const { key, value } = param;
        if (key in prop) {
            prop[key].value = value;
        }
    };
    const setTitle = (value: string) => {
        title.value = value;
        useDynamicTitle();
    };
    return {
        title,
        theme,
        sideTheme,
        showSettings,
        topNav,
        tagsView,
        fixedHeader,
        sidebarLogo,
        dynamicTitle,
        animationEnable,
        dark,
        changeSetting,
        setTitle
    };
  // actions
  const changeSetting = (param: { key: SettingTypeEnum; value: any }) => {
    const { key, value } = param;
    if (key in prop) {
      prop[key].value = value;
    }
  };
  const setTitle = (value: string) => {
    title.value = value;
    useDynamicTitle();
  };
  return {
    title,
    theme,
    sideTheme,
    showSettings,
    topNav,
    tagsView,
    fixedHeader,
    sidebarLogo,
    dynamicTitle,
    animationEnable,
    dark,
    changeSetting,
    setTitle
  };
});
export default useSettingsStore;
src/store/modules/tagsView.ts
@@ -1,198 +1,198 @@
import { TagView } from 'vue-router';
export const useTagsViewStore = defineStore('tagsView', () => {
    const visitedViews = ref<TagView[]>([]);
    const cachedViews = ref<string[]>([]);
    const iframeViews = ref<TagView[]>([]);
  const visitedViews = ref<TagView[]>([]);
  const cachedViews = ref<string[]>([]);
  const iframeViews = ref<TagView[]>([]);
    const addView = (view: TagView) => {
        addVisitedView(view);
        addCachedView(view);
    };
  const addView = (view: TagView) => {
    addVisitedView(view);
    addCachedView(view);
  };
    const addIframeView = (view: TagView): void => {
        if (iframeViews.value.some((v) => v.path === view.path)) return;
        iframeViews.value.push(
            Object.assign({}, view, {
                title: view.meta?.title || 'no-name'
            })
        );
    };
    const delIframeView = (view: TagView): Promise<TagView[]> => {
        return new Promise((resolve) => {
            iframeViews.value = iframeViews.value.filter((item) => item.path !== view.path);
            resolve([...iframeViews.value]);
        });
    };
    const addVisitedView = (view: TagView): void => {
        if (visitedViews.value.some((v) => v.path === view.path)) return;
        visitedViews.value.push(
            Object.assign({}, view, {
                title: view.meta?.title || 'no-name'
            })
        );
    };
    const delView = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
        return new Promise((resolve) => {
            delVisitedView(view);
            delCachedView(view);
            resolve({
                visitedViews: [...visitedViews.value],
                cachedViews: [...cachedViews.value]
            });
        });
    };
  const addIframeView = (view: TagView): void => {
    if (iframeViews.value.some((v) => v.path === view.path)) return;
    iframeViews.value.push(
      Object.assign({}, view, {
        title: view.meta?.title || 'no-name'
      })
    );
  };
  const delIframeView = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      iframeViews.value = iframeViews.value.filter((item) => item.path !== view.path);
      resolve([...iframeViews.value]);
    });
  };
  const addVisitedView = (view: TagView): void => {
    if (visitedViews.value.some((v) => v.path === view.path)) return;
    visitedViews.value.push(
      Object.assign({}, view, {
        title: view.meta?.title || 'no-name'
      })
    );
  };
  const delView = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
    return new Promise((resolve) => {
      delVisitedView(view);
      delCachedView(view);
      resolve({
        visitedViews: [...visitedViews.value],
        cachedViews: [...cachedViews.value]
      });
    });
  };
    const delVisitedView = (view: TagView): Promise<TagView[]> => {
        return new Promise((resolve) => {
            for (const [i, v] of visitedViews.value.entries()) {
                if (v.path === view.path) {
                    visitedViews.value.splice(i, 1);
                    break;
                }
            }
            resolve([...visitedViews.value]);
        });
    };
    const delCachedView = (view: TagView): Promise<string[]> => {
        const viewName = view.name as string;
        return new Promise((resolve) => {
            const index = cachedViews.value.indexOf(viewName);
            index > -1 && cachedViews.value.splice(index, 1);
            resolve([...cachedViews.value]);
        });
    };
    const delOthersViews = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
        return new Promise((resolve) => {
            delOthersVisitedViews(view);
            delOthersCachedViews(view);
            resolve({
                visitedViews: [...visitedViews.value],
                cachedViews: [...cachedViews.value]
            });
        });
    };
  const delVisitedView = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      for (const [i, v] of visitedViews.value.entries()) {
        if (v.path === view.path) {
          visitedViews.value.splice(i, 1);
          break;
        }
      }
      resolve([...visitedViews.value]);
    });
  };
  const delCachedView = (view: TagView): Promise<string[]> => {
    const viewName = view.name as string;
    return new Promise((resolve) => {
      const index = cachedViews.value.indexOf(viewName);
      index > -1 && cachedViews.value.splice(index, 1);
      resolve([...cachedViews.value]);
    });
  };
  const delOthersViews = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
    return new Promise((resolve) => {
      delOthersVisitedViews(view);
      delOthersCachedViews(view);
      resolve({
        visitedViews: [...visitedViews.value],
        cachedViews: [...cachedViews.value]
      });
    });
  };
    const delOthersVisitedViews = (view: TagView): Promise<TagView[]> => {
        return new Promise((resolve) => {
            visitedViews.value = visitedViews.value.filter((v) => {
                return v.meta?.affix || v.path === view.path;
            });
            resolve([...visitedViews.value]);
        });
    };
    const delOthersCachedViews = (view: TagView): Promise<string[]> => {
        const viewName = view.name as string;
        return new Promise((resolve) => {
            const index = cachedViews.value.indexOf(viewName);
            if (index > -1) {
                cachedViews.value = cachedViews.value.slice(index, index + 1);
            } else {
                cachedViews.value = [];
            }
            resolve([...cachedViews.value]);
        });
    };
  const delOthersVisitedViews = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      visitedViews.value = visitedViews.value.filter((v) => {
        return v.meta?.affix || v.path === view.path;
      });
      resolve([...visitedViews.value]);
    });
  };
  const delOthersCachedViews = (view: TagView): Promise<string[]> => {
    const viewName = view.name as string;
    return new Promise((resolve) => {
      const index = cachedViews.value.indexOf(viewName);
      if (index > -1) {
        cachedViews.value = cachedViews.value.slice(index, index + 1);
      } else {
        cachedViews.value = [];
      }
      resolve([...cachedViews.value]);
    });
  };
    const delAllViews = (): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
        return new Promise((resolve) => {
            delAllVisitedViews();
            delAllCachedViews();
            resolve({
                visitedViews: [...visitedViews.value],
                cachedViews: [...cachedViews.value]
            });
        });
    };
    const delAllVisitedViews = (): Promise<TagView[]> => {
        return new Promise((resolve) => {
            visitedViews.value = visitedViews.value.filter((tag) => tag.meta?.affix);
            resolve([...visitedViews.value]);
        });
    };
  const delAllViews = (): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
    return new Promise((resolve) => {
      delAllVisitedViews();
      delAllCachedViews();
      resolve({
        visitedViews: [...visitedViews.value],
        cachedViews: [...cachedViews.value]
      });
    });
  };
  const delAllVisitedViews = (): Promise<TagView[]> => {
    return new Promise((resolve) => {
      visitedViews.value = visitedViews.value.filter((tag) => tag.meta?.affix);
      resolve([...visitedViews.value]);
    });
  };
    const delAllCachedViews = (): Promise<string[]> => {
        return new Promise((resolve) => {
            cachedViews.value = [];
            resolve([...cachedViews.value]);
        });
    };
  const delAllCachedViews = (): Promise<string[]> => {
    return new Promise((resolve) => {
      cachedViews.value = [];
      resolve([...cachedViews.value]);
    });
  };
    const updateVisitedView = (view: TagView): void => {
        for (let v of visitedViews.value) {
            if (v.path === view.path) {
                v = Object.assign(v, view);
                break;
            }
        }
    };
    const delRightTags = (view: TagView): Promise<TagView[]> => {
        return new Promise((resolve) => {
            const index = visitedViews.value.findIndex((v) => v.path === view.path);
            if (index === -1) {
                return;
            }
            visitedViews.value = visitedViews.value.filter((item, idx) => {
                if (idx <= index || (item.meta && item.meta.affix)) {
                    return true;
                }
                const i = cachedViews.value.indexOf(item.name as string);
                if (i > -1) {
                    cachedViews.value.splice(i, 1);
                }
                return false;
            });
            resolve([...visitedViews.value]);
        });
    };
    const delLeftTags = (view: TagView): Promise<TagView[]> => {
        return new Promise((resolve) => {
            const index = visitedViews.value.findIndex((v) => v.path === view.path);
            if (index === -1) {
                return;
            }
            visitedViews.value = visitedViews.value.filter((item, idx) => {
                if (idx >= index || (item.meta && item.meta.affix)) {
                    return true;
                }
                const i = cachedViews.value.indexOf(item.name as string);
                if (i > -1) {
                    cachedViews.value.splice(i, 1);
                }
                return false;
            });
            resolve([...visitedViews.value]);
        });
    };
  const updateVisitedView = (view: TagView): void => {
    for (let v of visitedViews.value) {
      if (v.path === view.path) {
        v = Object.assign(v, view);
        break;
      }
    }
  };
  const delRightTags = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      const index = visitedViews.value.findIndex((v) => v.path === view.path);
      if (index === -1) {
        return;
      }
      visitedViews.value = visitedViews.value.filter((item, idx) => {
        if (idx <= index || (item.meta && item.meta.affix)) {
          return true;
        }
        const i = cachedViews.value.indexOf(item.name as string);
        if (i > -1) {
          cachedViews.value.splice(i, 1);
        }
        return false;
      });
      resolve([...visitedViews.value]);
    });
  };
  const delLeftTags = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      const index = visitedViews.value.findIndex((v) => v.path === view.path);
      if (index === -1) {
        return;
      }
      visitedViews.value = visitedViews.value.filter((item, idx) => {
        if (idx >= index || (item.meta && item.meta.affix)) {
          return true;
        }
        const i = cachedViews.value.indexOf(item.name as string);
        if (i > -1) {
          cachedViews.value.splice(i, 1);
        }
        return false;
      });
      resolve([...visitedViews.value]);
    });
  };
    const addCachedView = (view: TagView): void => {
        const viewName = view.name as string;
        if (cachedViews.value.includes(viewName)) return;
        if (!view.meta?.noCache) {
            cachedViews.value.push(viewName);
        }
    };
  const addCachedView = (view: TagView): void => {
    const viewName = view.name as string;
    if (cachedViews.value.includes(viewName)) return;
    if (!view.meta?.noCache) {
      cachedViews.value.push(viewName);
    }
  };
    return {
        visitedViews,
        cachedViews,
        iframeViews,
        addVisitedView,
        addCachedView,
        delVisitedView,
        delCachedView,
        updateVisitedView,
        addView,
        delView,
        delAllViews,
        delAllVisitedViews,
        delAllCachedViews,
        delOthersViews,
        delRightTags,
        delLeftTags,
        addIframeView,
        delIframeView
    };
  return {
    visitedViews,
    cachedViews,
    iframeViews,
    addVisitedView,
    addCachedView,
    delVisitedView,
    delCachedView,
    updateVisitedView,
    addView,
    delView,
    delAllViews,
    delAllVisitedViews,
    delAllCachedViews,
    delOthersViews,
    delRightTags,
    delLeftTags,
    addIframeView,
    delIframeView
  };
});
export default useTagsViewStore;
src/store/modules/user.ts
@@ -6,78 +6,78 @@
import { LoginData } from '@/api/types';
export const useUserStore = defineStore('user', () => {
    const token = ref(getToken());
    const name = ref('');
    const nickname = ref('');
    const userId = ref<string | number>('');
    const avatar = ref('');
    const roles = ref<Array<string>>([]); // ç”¨æˆ·è§’色编码集合 â†’ åˆ¤æ–­è·¯ç”±æƒé™
    const permissions = ref<Array<string>>([]); // ç”¨æˆ·æƒé™ç¼–码集合 â†’ åˆ¤æ–­æŒ‰é’®æƒé™
  const token = ref(getToken());
  const name = ref('');
  const nickname = ref('');
  const userId = ref<string | number>('');
  const avatar = ref('');
  const roles = ref<Array<string>>([]); // ç”¨æˆ·è§’色编码集合 â†’ åˆ¤æ–­è·¯ç”±æƒé™
  const permissions = ref<Array<string>>([]); // ç”¨æˆ·æƒé™ç¼–码集合 â†’ åˆ¤æ–­æŒ‰é’®æƒé™
    /**
     * ç™»å½•
     * @param userInfo
     * @returns
     */
    const login = async (userInfo: LoginData): Promise<void> => {
        const [err, res] = await to(loginApi(userInfo));
        if (res) {
            const data = res.data;
            setToken(data.token);
            token.value = data.token;
            return Promise.resolve();
        }
        return Promise.reject(err);
    };
  /**
   * ç™»å½•
   * @param userInfo
   * @returns
   */
  const login = async (userInfo: LoginData): Promise<void> => {
    const [err, res] = await to(loginApi(userInfo));
    if (res) {
      const data = res.data;
      setToken(data.token);
      token.value = data.token;
      return Promise.resolve();
    }
    return Promise.reject(err);
  };
    // èŽ·å–ç”¨æˆ·ä¿¡æ¯
    const getInfo = async (): Promise<void> => {
        const [err, res] = await to(getUserInfo());
        if (res) {
            const data = res.data;
            const user = data.user;
            const profile = user.avatar == '' || user.avatar == null ? defAva : user.avatar;
  // èŽ·å–ç”¨æˆ·ä¿¡æ¯
  const getInfo = async (): Promise<void> => {
    const [err, res] = await to(getUserInfo());
    if (res) {
      const data = res.data;
      const user = data.user;
      const profile = user.avatar == '' || user.avatar == null ? defAva : user.avatar;
            if (data.roles && data.roles.length > 0) {
                // éªŒè¯è¿”回的roles是否是一个非空数组
                roles.value = data.roles;
                permissions.value = data.permissions;
            } else {
                roles.value = ['ROLE_DEFAULT'];
            }
            name.value = user.userName;
            nickname.value = user.nickName;
            avatar.value = profile;
            userId.value = user.userId;
            return Promise.resolve();
        }
        return Promise.reject(err);
    };
      if (data.roles && data.roles.length > 0) {
        // éªŒè¯è¿”回的roles是否是一个非空数组
        roles.value = data.roles;
        permissions.value = data.permissions;
      } else {
        roles.value = ['ROLE_DEFAULT'];
      }
      name.value = user.userName;
      nickname.value = user.nickName;
      avatar.value = profile;
      userId.value = user.userId;
      return Promise.resolve();
    }
    return Promise.reject(err);
  };
    // æ³¨é”€
    const logout = async (): Promise<void> => {
        await logoutApi();
        token.value = '';
        roles.value = [];
        permissions.value = [];
        removeToken();
    };
  // æ³¨é”€
  const logout = async (): Promise<void> => {
    await logoutApi();
    token.value = '';
    roles.value = [];
    permissions.value = [];
    removeToken();
  };
    return {
        userId,
        token,
        nickname,
        avatar,
        roles,
        permissions,
        login,
        getInfo,
        logout
    };
  return {
    userId,
    token,
    nickname,
    avatar,
    roles,
    permissions,
    login,
    getInfo,
    logout
  };
});
export default useUserStore;
// éžsetup
export function useUserStoreHook() {
    return useUserStore(store);
  return useUserStore(store);
}
src/types/auto-imports.d.ts
@@ -2,13 +2,11 @@
export {}
declare global {
  const EffectScope: typeof import('vue')['EffectScope']
  const ElForm: typeof import('element-plus/es')['ElForm']
  const ElLoading: typeof import('element-plus/es')['ElLoading']
  const ElMessage: typeof import('element-plus/es')['ElMessage']
  const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
  const ElNotification: typeof import('element-plus/es')['ElNotification']
  const ElSelect: typeof import('element-plus/es')['ElSelect']
  const ElTable: typeof import('element-plus/es')['ElTable']
  const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
  const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
  const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
@@ -288,13 +286,11 @@
declare module 'vue' {
  interface ComponentCustomProperties {
    readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
    readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']>
    readonly ElLoading: UnwrapRef<typeof import('element-plus/es')['ElLoading']>
    readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']>
    readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']>
    readonly ElNotification: UnwrapRef<typeof import('element-plus/es')['ElNotification']>
    readonly ElSelect: UnwrapRef<typeof import('element-plus/es')['ElSelect']>
    readonly ElTable: UnwrapRef<typeof import('element-plus/es')['ElTable']>
    readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
    readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
    readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
src/types/axios.d.ts
@@ -1,10 +1,10 @@
import axios from 'axios';
declare module 'axios' {
    export interface AxiosResponse<T = any> {
        code: number;
        msg: string;
        rows: T;
        total: number;
    }
  export interface AxiosResponse<T = any> {
    code: number;
    msg: string;
    rows: T;
    total: number;
  }
}
src/types/components.d.ts
@@ -14,7 +14,6 @@
    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCol: typeof import('element-plus/es')['ElCol']
    ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -25,42 +24,26 @@
    ElDropdown: typeof import('element-plus/es')['ElDropdown']
    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
    ElForm: typeof import('element-plus/es')['ElForm']
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElImage: typeof import('element-plus/es')['ElImage']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
    ElLink: typeof import('element-plus/es')['ElLink']
    ElMenu: typeof import('element-plus/es')['ElMenu']
    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElPopover: typeof import('element-plus/es')['ElPopover']
    ElRadio: typeof import('element-plus/es')['ElRadio']
    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
    ElRow: typeof import('element-plus/es')['ElRow']
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    ElSelect: typeof import('element-plus/es')['ElSelect']
    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
    ElSwitch: typeof import('element-plus/es')['ElSwitch']
    ElTable: typeof import('element-plus/es')['ElTable']
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
    ElTabs: typeof import('element-plus/es')['ElTabs']
    ElTag: typeof import('element-plus/es')['ElTag']
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
    ElTransfer: typeof import('element-plus/es')['ElTransfer']
    ElTree: typeof import('element-plus/es')['ElTree']
    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
    ElUpload: typeof import('element-plus/es')['ElUpload']
    FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
    Hamburger: typeof import('./../components/Hamburger/index.vue')['default']
    HeaderSearch: typeof import('./../components/HeaderSearch/index.vue')['default']
    IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
    IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
    IEpCaretTop: typeof import('~icons/ep/caret-top')['default']
    IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']
    IFrame: typeof import('./../components/iFrame/index.vue')['default']
    ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
    ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
src/types/env.d.ts
@@ -1,74 +1,74 @@
declare module '*.vue' {
    import { DefineComponent } from 'vue';
    const component: DefineComponent<{}, {}, any>;
    export default component;
  import { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
declare module '*.avif' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.bmp' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.gif' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.jpg' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.jpeg' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.png' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.webp' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.svg' {
    const src: string;
    export default src;
  const src: string;
  export default src;
}
declare module '*.module.css' {
    const classes: { readonly [key: string]: string };
    export default classes;
  const classes: { readonly [key: string]: string };
  export default classes;
}
declare module '*.module.scss' {
    const classes: { readonly [key: string]: string };
    export default classes;
  const classes: { readonly [key: string]: string };
  export default classes;
}
declare module '*.module.sass' {
    const classes: { readonly [key: string]: string };
    export default classes;
  const classes: { readonly [key: string]: string };
  export default classes;
}
// çŽ¯å¢ƒå˜é‡
interface ImportMetaEnv {
    VITE_APP_TITLE: string;
    VITE_APP_PORT: number;
    VITE_APP_BASE_API: string;
    VITE_APP_BASE_URL: string;
    VITE_APP_CONTEXT_PATH: string;
    VITE_APP_MONITRO_ADMIN: string;
    VITE_APP_XXL_JOB_ADMIN: string;
    VITE_APP_ENV: string;
  VITE_APP_TITLE: string;
  VITE_APP_PORT: number;
  VITE_APP_BASE_API: string;
  VITE_APP_BASE_URL: string;
  VITE_APP_CONTEXT_PATH: string;
  VITE_APP_MONITRO_ADMIN: string;
  VITE_APP_XXL_JOB_ADMIN: string;
  VITE_APP_ENV: string;
}
interface ImportMeta {
    readonly env: ImportMetaEnv;
    // readonly glob: any;
  readonly env: ImportMetaEnv;
  // readonly glob: any;
}
src/types/global.d.ts
@@ -1,83 +1,83 @@
import { FormRules } from 'element-plus';
declare global {
    /**
     * ç•Œé¢å­—段隐藏属性
     */
    interface FieldOption {
        key: number;
        label: string;
        visible: boolean;
    }
  /**
   * ç•Œé¢å­—段隐藏属性
   */
  interface FieldOption {
    key: number;
    label: string;
    visible: boolean;
  }
    /**
     * å¼¹çª—属性
     */
    interface DialogOption {
        /**
         * å¼¹çª—标题
         */
        title?: string;
        /**
         * æ˜¯å¦æ˜¾ç¤º
         */
        visible: boolean;
    }
  /**
   * å¼¹çª—属性
   */
  interface DialogOption {
    /**
     * å¼¹çª—标题
     */
    title?: string;
    /**
     * æ˜¯å¦æ˜¾ç¤º
     */
    visible: boolean;
  }
    interface UploadOption {
        /** è®¾ç½®ä¸Šä¼ çš„请求头部 */
        headers: { [key: string]: any };
  interface UploadOption {
    /** è®¾ç½®ä¸Šä¼ çš„请求头部 */
    headers: { [key: string]: any };
        /** ä¸Šä¼ çš„地址 */
        url: string;
    }
    /** ä¸Šä¼ çš„地址 */
    url: string;
  }
    /**
     * å¯¼å…¥å±žæ€§
     */
    interface ImportOption extends UploadOption {
        /** æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚ */
        open: boolean;
        /** å¼¹å‡ºå±‚标题 */
        title: string;
        /** æ˜¯å¦ç¦ç”¨ä¸Šä¼  */
        isUploading: boolean;
  /**
   * å¯¼å…¥å±žæ€§
   */
  interface ImportOption extends UploadOption {
    /** æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚ */
    open: boolean;
    /** å¼¹å‡ºå±‚标题 */
    title: string;
    /** æ˜¯å¦ç¦ç”¨ä¸Šä¼  */
    isUploading: boolean;
        /** å…¶ä»–参数 */
        [key: string]: any;
    }
    /**
     * å­—典数据  æ•°æ®é…ç½®
     */
    interface DictDataOption {
        label: string;
        value: string;
        elTagType?: ElTagType;
        elTagClass?: string;
    }
    /** å…¶ä»–参数 */
    [key: string]: any;
  }
  /**
   * å­—典数据  æ•°æ®é…ç½®
   */
  interface DictDataOption {
    label: string;
    value: string;
    elTagType?: ElTagType;
    elTagClass?: string;
  }
    interface BaseEntity {
        createBy?: any;
        createTime?: string;
        updateBy?: any;
        updateTime?: any;
    }
  interface BaseEntity {
    createBy?: any;
    createTime?: string;
    updateBy?: any;
    updateTime?: any;
  }
    /**
     * åˆ†é¡µæ•°æ®
     * T : è¡¨å•数据
     * D : æŸ¥è¯¢å‚æ•°
     */
    interface PageData<T, D> {
        form: T;
        queryParams: D;
        rules: FormRules;
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢å‚æ•°
     */
    interface PageQuery {
        pageNum: number;
        pageSize: number;
    }
  /**
   * åˆ†é¡µæ•°æ®
   * T : è¡¨å•数据
   * D : æŸ¥è¯¢å‚æ•°
   */
  interface PageData<T, D> {
    form: T;
    queryParams: D;
    rules: FormRules;
  }
  /**
   * åˆ†é¡µæŸ¥è¯¢å‚æ•°
   */
  interface PageQuery {
    pageNum: number;
    pageSize: number;
  }
}
export {};
src/types/module.d.ts
@@ -8,21 +8,21 @@
import animate from '@/animate';
declare module 'vue' {
    export interface ComponentCustomProperties {
        // å…¨å±€æ–¹æ³•声明
        $modal: typeof modal;
        $tab: typeof tab;
        $download: typeof download;
        animate: typeof animate;
  export interface ComponentCustomProperties {
    // å…¨å±€æ–¹æ³•声明
    $modal: typeof modal;
    $tab: typeof tab;
    $download: typeof download;
    animate: typeof animate;
        useDict: typeof useDict;
        addDateRange: typeof addDateRange;
        download: typeof download1;
        handleTree: typeof handleTree;
        getConfigKey: typeof getConfigKey;
        updateConfigByKey: typeof updateConfigByKey;
        selectDictLabel: typeof selectDictLabel;
        selectDictLabels: typeof selectDictLabels;
        parseTime: typeof parseTime;
    }
    useDict: typeof useDict;
    addDateRange: typeof addDateRange;
    download: typeof download1;
    handleTree: typeof handleTree;
    getConfigKey: typeof getConfigKey;
    updateConfigByKey: typeof updateConfigByKey;
    selectDictLabel: typeof selectDictLabel;
    selectDictLabels: typeof selectDictLabels;
    parseTime: typeof parseTime;
  }
}
src/types/router.d.ts
@@ -1,35 +1,35 @@
import { RouteRecordRaw } from 'vue-router';
declare module 'vue-router' {
    type RouteOption = {
        hidden?: boolean;
        permissions?: string[];
        roles?: string[];
        component?: any;
        children?: RouteOption[];
        alwaysShow?: boolean;
        parentPath?: string;
        meta?: {
            title: string;
            icon: string;
        };
    } & RouteRecordRaw;
  type RouteOption = {
    hidden?: boolean;
    permissions?: string[];
    roles?: string[];
    component?: any;
    children?: RouteOption[];
    alwaysShow?: boolean;
    parentPath?: string;
    meta?: {
      title: string;
      icon: string;
    };
  } & RouteRecordRaw;
    interface _RouteLocationBase {
        children?: RouteOption[];
    }
  interface _RouteLocationBase {
    children?: RouteOption[];
  }
    interface RouteLocationOptions {
        fullPath?: string;
    }
  interface RouteLocationOptions {
    fullPath?: string;
  }
    interface TagView extends Partial<_RouteLocationBase> {
        title?: string;
        meta?: {
            link?: string;
            title?: string;
            affix?: boolean;
            noCache?: boolean;
        };
    }
  interface TagView extends Partial<_RouteLocationBase> {
    title?: string;
    meta?: {
      link?: string;
      title?: string;
      affix?: boolean;
      noCache?: boolean;
    };
  }
}
src/types/setting.d.ts
@@ -1,70 +1,70 @@
declare type DefaultSettings = {
    /**
     * ç½‘页标题
     */
    title?: string;
  /**
   * ç½‘页标题
   */
  title?: string;
    /**
     * ä¾§è¾¹æ ä¸»é¢˜ theme-dark | theme-light
     */
    sideTheme?: string;
  /**
   * ä¾§è¾¹æ ä¸»é¢˜ theme-dark | theme-light
   */
  sideTheme?: string;
    /**
     * æ˜¯å¦æ˜¾ç¤ºç³»ç»Ÿå¸ƒå±€è®¾ç½®
     */
    showSettings?: boolean;
  /**
   * æ˜¯å¦æ˜¾ç¤ºç³»ç»Ÿå¸ƒå±€è®¾ç½®
   */
  showSettings?: boolean;
    /**
     * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
     */
    topNav?: boolean;
  /**
   * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
   */
  topNav?: boolean;
    /**
     * æ˜¯å¦æ˜¾ç¤ºå¤šæ ‡ç­¾å¯¼èˆª
     */
    tagsView?: boolean;
    /**
     * æ˜¯å¦å›ºå®šå¤´éƒ¨
     */
    fixedHeader?: boolean;
    /**
     * æ˜¯å¦æ˜¾ç¤ºä¾§è¾¹æ Logo
     */
    sidebarLogo?: boolean;
    /**
     * å¯¼èˆªæ å¸ƒå±€
     */
    layout?: string;
    /**
     * ä¸»é¢˜æ¨¡å¼
     */
    theme?: string;
  /**
   * æ˜¯å¦æ˜¾ç¤ºå¤šæ ‡ç­¾å¯¼èˆª
   */
  tagsView?: boolean;
  /**
   * æ˜¯å¦å›ºå®šå¤´éƒ¨
   */
  fixedHeader?: boolean;
  /**
   * æ˜¯å¦æ˜¾ç¤ºä¾§è¾¹æ Logo
   */
  sidebarLogo?: boolean;
  /**
   * å¯¼èˆªæ å¸ƒå±€
   */
  layout?: string;
  /**
   * ä¸»é¢˜æ¨¡å¼
   */
  theme?: string;
    /**
     * å¸ƒå±€å¤§å°
     */
    size?: string;
  /**
   * å¸ƒå±€å¤§å°
   */
  size?: string;
    /**
     * è¯­è¨€
     */
    language?: string;
  /**
   * è¯­è¨€
   */
  language?: string;
    /**
     * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
     */
    dynamicTitle?: boolean;
    /**
     * æ˜¯å¦å¯ç”¨åŠ¨ç”»æ•ˆæžœ
     */
    animationEnable?: boolean;
    /**
     *  æ˜¯å¦å¯ç”¨æš—黑模式
     *
     * true:暗黑模式
     * false: æ˜Žäº®æ¨¡å¼
     */
    dark?: boolean;
  /**
   * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
   */
  dynamicTitle?: boolean;
  /**
   * æ˜¯å¦å¯ç”¨åŠ¨ç”»æ•ˆæžœ
   */
  animationEnable?: boolean;
  /**
   *  æ˜¯å¦å¯ç”¨æš—黑模式
   *
   * true:暗黑模式
   * false: æ˜Žäº®æ¨¡å¼
   */
  dark?: boolean;
    errorLog?: string;
  errorLog?: string;
};
src/utils/dict.ts
@@ -4,24 +4,24 @@
 * èŽ·å–å­—å…¸æ•°æ®
 */
export const useDict = (...args: string[]): { [key: string]: DictDataOption[] } => {
    const res = ref<{
        [key: string]: DictDataOption[];
    }>({});
    return (() => {
        args.forEach(async (dictType) => {
            res.value[dictType] = [];
            const dicts = useDictStore().getDict(dictType);
            if (dicts) {
                res.value[dictType] = dicts;
            } else {
                await getDicts(dictType).then((resp) => {
                    res.value[dictType] = resp.data.map(
                        (p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })
                    );
                    useDictStore().setDict(dictType, res.value[dictType]);
                });
            }
        });
        return res.value;
    })();
  const res = ref<{
    [key: string]: DictDataOption[];
  }>({});
  return (() => {
    args.forEach(async (dictType) => {
      res.value[dictType] = [];
      const dicts = useDictStore().getDict(dictType);
      if (dicts) {
        res.value[dictType] = dicts;
      } else {
        await getDicts(dictType).then((resp) => {
          res.value[dictType] = resp.data.map(
            (p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })
          );
          useDictStore().setDict(dictType, res.value[dictType]);
        });
      }
    });
    return res.value;
  })();
};
src/utils/dynamicTitle.ts
@@ -5,10 +5,10 @@
 * åŠ¨æ€ä¿®æ”¹æ ‡é¢˜
 */
export const useDynamicTitle = () => {
    const settingsStore = useSettingsStore();
    if (settingsStore.dynamicTitle) {
        document.title = settingsStore.title + ' - ' + import.meta.env.VITE_APP_TITLE;
    } else {
        document.title = defaultSettings.title as string;
    }
  const settingsStore = useSettingsStore();
  if (settingsStore.dynamicTitle) {
    document.title = settingsStore.title + ' - ' + import.meta.env.VITE_APP_TITLE;
  } else {
    document.title = defaultSettings.title as string;
  }
};
src/utils/errorCode.ts
@@ -1,7 +1,7 @@
export const errorCode: any = {
    '401': '认证失败,无法访问系统资源',
    '403': '当前操作没有权限',
    '404': '访问资源不存在',
    default: '系统未知错误,请反馈给管理员'
  '401': '认证失败,无法访问系统资源',
  '403': '当前操作没有权限',
  '404': '访问资源不存在',
  default: '系统未知错误,请反馈给管理员'
};
export default errorCode;
src/utils/i18n.ts
@@ -2,11 +2,11 @@
import i18n from '@/lang/index';
export const translateRouteTitleI18n = (title: string): string => {
    // åˆ¤æ–­æ˜¯å¦å­˜åœ¨å›½é™…化配置,如果没有原生返回
    const hasKey = i18n.global.te('route.' + title);
    if (hasKey) {
        const translatedTitle = i18n.global.t('route.' + title);
        return translatedTitle;
    }
    return title;
  // åˆ¤æ–­æ˜¯å¦å­˜åœ¨å›½é™…化配置,如果没有原生返回
  const hasKey = i18n.global.te('route.' + title);
  if (hasKey) {
    const translatedTitle = i18n.global.t('route.' + title);
    return translatedTitle;
  }
  return title;
};
src/utils/index.ts
@@ -4,15 +4,15 @@
 * è¡¨æ ¼æ—¶é—´æ ¼å¼åŒ–
 */
export const formatDate = (cellValue: string) => {
    if (cellValue == null || cellValue == '') return '';
    const date = new Date(cellValue);
    const year = date.getFullYear();
    const month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
    const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
    const hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
    const minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
    const seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
    return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
  if (cellValue == null || cellValue == '') return '';
  const date = new Date(cellValue);
  const year = date.getFullYear();
  const month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
  const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
  const hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
  const minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
  const seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
  return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
};
/**
@@ -21,32 +21,32 @@
 * @returns {string}
 */
export const formatTime = (time: string, option: string) => {
    let t: number;
    if (('' + time).length === 10) {
        t = parseInt(time) * 1000;
    } else {
        t = +time;
    }
    const d: any = new Date(t);
    const now = Date.now();
  let t: number;
  if (('' + time).length === 10) {
    t = parseInt(time) * 1000;
  } else {
    t = +time;
  }
  const d: any = new Date(t);
  const now = Date.now();
    const diff = (now - d) / 1000;
  const diff = (now - d) / 1000;
    if (diff < 30) {
        return '刚刚';
    } else if (diff < 3600) {
        // less 1 hour
        return Math.ceil(diff / 60) + '分钟前';
    } else if (diff < 3600 * 24) {
        return Math.ceil(diff / 3600) + '小时前';
    } else if (diff < 3600 * 24 * 2) {
        return '1天前';
    }
    if (option) {
        return parseTime(t, option);
    } else {
        return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分';
    }
  if (diff < 30) {
    return '刚刚';
  } else if (diff < 3600) {
    // less 1 hour
    return Math.ceil(diff / 60) + '分钟前';
  } else if (diff < 3600 * 24) {
    return Math.ceil(diff / 3600) + '小时前';
  } else if (diff < 3600 * 24 * 2) {
    return '1天前';
  }
  if (option) {
    return parseTime(t, option);
  } else {
    return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分';
  }
};
/**
@@ -54,18 +54,18 @@
 * @returns {Object}
 */
export const getQueryObject = (url: string) => {
    url = url == null ? window.location.href : url;
    const search = url.substring(url.lastIndexOf('?') + 1);
    const obj: { [key: string]: string } = {};
    const reg = /([^?&=]+)=([^?&=]*)/g;
    search.replace(reg, (rs, $1, $2) => {
        const name = decodeURIComponent($1);
        let val = decodeURIComponent($2);
        val = String(val);
        obj[name] = val;
        return rs;
    });
    return obj;
  url = url == null ? window.location.href : url;
  const search = url.substring(url.lastIndexOf('?') + 1);
  const obj: { [key: string]: string } = {};
  const reg = /([^?&=]+)=([^?&=]*)/g;
  search.replace(reg, (rs, $1, $2) => {
    const name = decodeURIComponent($1);
    let val = decodeURIComponent($2);
    val = String(val);
    obj[name] = val;
    return rs;
  });
  return obj;
};
/**
@@ -73,15 +73,15 @@
 * @returns {number} output value
 */
export const byteLength = (str: string) => {
    // returns the byte length of an utf8 string
    let s = str.length;
    for (let i = str.length - 1; i >= 0; i--) {
        const code = str.charCodeAt(i);
        if (code > 0x7f && code <= 0x7ff) s++;
        else if (code > 0x7ff && code <= 0xffff) s += 2;
        if (code >= 0xdc00 && code <= 0xdfff) i--;
    }
    return s;
  // returns the byte length of an utf8 string
  let s = str.length;
  for (let i = str.length - 1; i >= 0; i--) {
    const code = str.charCodeAt(i);
    if (code > 0x7f && code <= 0x7ff) s++;
    else if (code > 0x7ff && code <= 0xffff) s += 2;
    if (code >= 0xdc00 && code <= 0xdfff) i--;
  }
  return s;
};
/**
@@ -89,13 +89,13 @@
 * @returns {Array}
 */
export const cleanArray = (actual: Array<any>) => {
    const newArray = [];
    for (let i = 0; i < actual.length; i++) {
        if (actual[i]) {
            newArray.push(actual[i]);
        }
    }
    return newArray;
  const newArray = [];
  for (let i = 0; i < actual.length; i++) {
    if (actual[i]) {
      newArray.push(actual[i]);
    }
  }
  return newArray;
};
/**
@@ -103,13 +103,13 @@
 * @returns {Array}
 */
export const param = (json: any) => {
    if (!json) return '';
    return cleanArray(
        Object.keys(json).map((key) => {
            if (json[key] === undefined) return '';
            return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
        })
    ).join('&');
  if (!json) return '';
  return cleanArray(
    Object.keys(json).map((key) => {
      if (json[key] === undefined) return '';
      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
    })
  ).join('&');
};
/**
@@ -117,21 +117,21 @@
 * @returns {Object}
 */
export const param2Obj = (url: string) => {
    const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ');
    if (!search) {
        return {};
    }
    const obj: any = {};
    const searchArr = search.split('&');
    searchArr.forEach((v) => {
        const index = v.indexOf('=');
        if (index !== -1) {
            const name = v.substring(0, index);
            const val = v.substring(index + 1, v.length);
            obj[name] = val;
        }
    });
    return obj;
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ');
  if (!search) {
    return {};
  }
  const obj: any = {};
  const searchArr = search.split('&');
  searchArr.forEach((v) => {
    const index = v.indexOf('=');
    if (index !== -1) {
      const name = v.substring(0, index);
      const val = v.substring(index + 1, v.length);
      obj[name] = val;
    }
  });
  return obj;
};
/**
@@ -139,9 +139,9 @@
 * @returns {string}
 */
export const html2Text = (val: string) => {
    const div = document.createElement('div');
    div.innerHTML = val;
    return div.textContent || div.innerText;
  const div = document.createElement('div');
  div.innerHTML = val;
  return div.textContent || div.innerText;
};
/**
@@ -151,21 +151,21 @@
 * @returns {Object}
 */
export const objectMerge = (target: any, source: any | any[]) => {
    if (typeof target !== 'object') {
        target = {};
    }
    if (Array.isArray(source)) {
        return source.slice();
    }
    Object.keys(source).forEach((property) => {
        const sourceProperty = source[property];
        if (typeof sourceProperty === 'object') {
            target[property] = objectMerge(target[property], sourceProperty);
        } else {
            target[property] = sourceProperty;
        }
    });
    return target;
  if (typeof target !== 'object') {
    target = {};
  }
  if (Array.isArray(source)) {
    return source.slice();
  }
  Object.keys(source).forEach((property) => {
    const sourceProperty = source[property];
    if (typeof sourceProperty === 'object') {
      target[property] = objectMerge(target[property], sourceProperty);
    } else {
      target[property] = sourceProperty;
    }
  });
  return target;
};
/**
@@ -173,17 +173,17 @@
 * @param {string} className
 */
export const toggleClass = (element: HTMLElement, className: string) => {
    if (!element || !className) {
        return;
    }
    let classString = element.className;
    const nameIndex = classString.indexOf(className);
    if (nameIndex === -1) {
        classString += '' + className;
    } else {
        classString = classString.substring(0, nameIndex) + classString.substring(nameIndex + className.length);
    }
    element.className = classString;
  if (!element || !className) {
    return;
  }
  let classString = element.className;
  const nameIndex = classString.indexOf(className);
  if (nameIndex === -1) {
    classString += '' + className;
  } else {
    classString = classString.substring(0, nameIndex) + classString.substring(nameIndex + className.length);
  }
  element.className = classString;
};
/**
@@ -191,11 +191,11 @@
 * @returns {Date}
 */
export const getTime = (type: string) => {
    if (type === 'start') {
        return new Date().getTime() - 3600 * 1000 * 24 * 90;
    } else {
        return new Date(new Date().toDateString());
    }
  if (type === 'start') {
    return new Date().getTime() - 3600 * 1000 * 24 * 90;
  } else {
    return new Date(new Date().toDateString());
  }
};
/**
@@ -205,37 +205,37 @@
 * @return {*}
 */
export const debounce = (func: any, wait: number, immediate: boolean) => {
    let timeout: any, args: any, context: any, timestamp: any, result: any;
  let timeout: any, args: any, context: any, timestamp: any, result: any;
    const later = function () {
        // æ®ä¸Šä¸€æ¬¡è§¦å‘æ—¶é—´é—´éš”
        const last = +new Date() - timestamp;
  const later = function () {
    // æ®ä¸Šä¸€æ¬¡è§¦å‘æ—¶é—´é—´éš”
    const last = +new Date() - timestamp;
        // ä¸Šæ¬¡è¢«åŒ…装函数被调用时间间隔 last å°äºŽè®¾å®šæ—¶é—´é—´éš” wait
        if (last < wait && last > 0) {
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;
            // å¦‚果设定为immediate===true,因为开始边界已经调用过了此处无需调用
            if (!immediate) {
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };
    // ä¸Šæ¬¡è¢«åŒ…装函数被调用时间间隔 last å°äºŽè®¾å®šæ—¶é—´é—´éš” wait
    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      // å¦‚果设定为immediate===true,因为开始边界已经调用过了此处无需调用
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };
    return (...args: any) => {
        context = this;
        timestamp = +new Date();
        const callNow = immediate && !timeout;
        // å¦‚果延时不存在,重新设定延时
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }
        return result;
    };
  return (...args: any) => {
    context = this;
    timestamp = +new Date();
    const callNow = immediate && !timeout;
    // å¦‚果延时不存在,重新设定延时
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }
    return result;
  };
};
/**
@@ -246,18 +246,18 @@
 * @returns {Object}
 */
export const deepClone = (source: any) => {
    if (!source && typeof source !== 'object') {
        throw new Error('error arguments', 'deepClone' as any);
    }
    const targetObj: any = source.constructor === Array ? [] : {};
    Object.keys(source).forEach((keys) => {
        if (source[keys] && typeof source[keys] === 'object') {
            targetObj[keys] = deepClone(source[keys]);
        } else {
            targetObj[keys] = source[keys];
        }
    });
    return targetObj;
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone' as any);
  }
  const targetObj: any = source.constructor === Array ? [] : {};
  Object.keys(source).forEach((keys) => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys]);
    } else {
      targetObj[keys] = source[keys];
    }
  });
  return targetObj;
};
/**
@@ -265,17 +265,17 @@
 * @returns {Array}
 */
export const uniqueArr = (arr: any) => {
    return Array.from(new Set(arr));
  return Array.from(new Set(arr));
};
/**
 * @returns {string}
 */
export const createUniqueString = (): string => {
    const timestamp = +new Date() + '';
    const num = (1 + Math.random()) * 65536;
    const randomNum = parseInt(num + '');
    return (+(randomNum + timestamp)).toString(32);
  const timestamp = +new Date() + '';
  const num = (1 + Math.random()) * 65536;
  const randomNum = parseInt(num + '');
  return (+(randomNum + timestamp)).toString(32);
};
/**
@@ -285,7 +285,7 @@
 * @returns {boolean}
 */
export const hasClass = (ele: HTMLElement, cls: string): boolean => {
    return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
};
/**
@@ -294,7 +294,7 @@
 * @param {string} cls
 */
export const addClass = (ele: HTMLElement, cls: string) => {
    if (!hasClass(ele, cls)) ele.className += ' ' + cls;
  if (!hasClass(ele, cls)) ele.className += ' ' + cls;
};
/**
@@ -303,10 +303,10 @@
 * @param {string} cls
 */
export const removeClass = (ele: HTMLElement, cls: string) => {
    if (hasClass(ele, cls)) {
        const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
        ele.className = ele.className.replace(reg, ' ');
    }
  if (hasClass(ele, cls)) {
    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
    ele.className = ele.className.replace(reg, ' ');
  }
};
/**
@@ -314,5 +314,5 @@
 * @returns {Boolean}
 */
export const isExternal = (path: string) => {
    return /^(https?:|http?:|mailto:|tel:)/.test(path);
  return /^(https?:|http?:|mailto:|tel:)/.test(path);
};
src/utils/jsencrypt.ts
@@ -2,28 +2,28 @@
// å¯†é’¥å¯¹ç”Ÿæˆ http://web.chacuo.net/netrsakeypair
const publicKey =
    'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==';
  'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==';
const privateKey =
    'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
    '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
    'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
    'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
    'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
    'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
    'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
    'UP8iWi1Qw0Y=';
  'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
  'UP8iWi1Qw0Y=';
// åР坆
export const encrypt = (txt: string) => {
    const encryptor = new JSEncrypt();
    encryptor.setPublicKey(publicKey); // è®¾ç½®å…¬é’¥
    return encryptor.encrypt(txt); // å¯¹æ•°æ®è¿›è¡ŒåР坆
  const encryptor = new JSEncrypt();
  encryptor.setPublicKey(publicKey); // è®¾ç½®å…¬é’¥
  return encryptor.encrypt(txt); // å¯¹æ•°æ®è¿›è¡ŒåР坆
};
// è§£å¯†
export const decrypt = (txt: string) => {
    const encryptor = new JSEncrypt();
    encryptor.setPrivateKey(privateKey); // è®¾ç½®ç§é’¥
    return encryptor.decrypt(txt); // å¯¹æ•°æ®è¿›è¡Œè§£å¯†
  const encryptor = new JSEncrypt();
  encryptor.setPrivateKey(privateKey); // è®¾ç½®ç§é’¥
  return encryptor.decrypt(txt); // å¯¹æ•°æ®è¿›è¡Œè§£å¯†
};
src/utils/permission.ts
@@ -6,23 +6,23 @@
 * @returns {Boolean}
 */
export const checkPermi = (value: any) => {
    if (value && value instanceof Array && value.length > 0) {
        const permissions = useUserStore().permissions;
        const permissionDatas = value;
        const all_permission = '*:*:*';
  if (value && value instanceof Array && value.length > 0) {
    const permissions = useUserStore().permissions;
    const permissionDatas = value;
    const all_permission = '*:*:*';
        const hasPermission = permissions.some((permission) => {
            return all_permission === permission || permissionDatas.includes(permission);
        });
    const hasPermission = permissions.some((permission) => {
      return all_permission === permission || permissionDatas.includes(permission);
    });
        if (!hasPermission) {
            return false;
        }
        return true;
    } else {
        console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
        return false;
    }
    if (!hasPermission) {
      return false;
    }
    return true;
  } else {
    console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
    return false;
  }
};
/**
@@ -31,21 +31,21 @@
 * @returns {Boolean}
 */
export const checkRole = (value: any): boolean => {
    if (value && value instanceof Array && value.length > 0) {
        const roles = useUserStore().roles;
        const permissionRoles = value;
        const super_admin = 'admin';
  if (value && value instanceof Array && value.length > 0) {
    const roles = useUserStore().roles;
    const permissionRoles = value;
    const super_admin = 'admin';
        const hasRole = roles.some((role) => {
            return super_admin === role || permissionRoles.includes(role);
        });
    const hasRole = roles.some((role) => {
      return super_admin === role || permissionRoles.includes(role);
    });
        if (!hasRole) {
            return false;
        }
        return true;
    } else {
        console.error(`need roles! Like checkRole="['admin','editor']"`);
        return false;
    }
    if (!hasRole) {
      return false;
    }
    return true;
  } else {
    console.error(`need roles! Like checkRole="['admin','editor']"`);
    return false;
  }
};
src/utils/request.ts
@@ -17,143 +17,143 @@
axios.defaults.headers['Content-Language'] = 'zh_CN';
// åˆ›å»º axios å®žä¾‹
const service = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 50000
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 50000
});
// è¯·æ±‚拦截器
service.interceptors.request.use(
    (config: InternalAxiosRequestConfig) => {
        const isToken = (config.headers || {}).isToken === false;
        // æ˜¯å¦éœ€è¦é˜²æ­¢æ•°æ®é‡å¤æäº¤
        const isRepeatSubmit = !(config.headers || {}).repeatSubmit;
        if (getToken() && !isToken) {
            config.headers['Authorization'] = 'Bearer ' + getToken(); // è®©æ¯ä¸ªè¯·æ±‚携带自定义token è¯·æ ¹æ®å®žé™…情况自行修改
        }
        // get请求映射params参数
        if (config.method === 'get' && config.params) {
            let url = config.url + '?' + tansParams(config.params);
            url = url.slice(0, -1);
            config.params = {};
            config.url = url;
        }
  (config: InternalAxiosRequestConfig) => {
    const isToken = (config.headers || {}).isToken === false;
    // æ˜¯å¦éœ€è¦é˜²æ­¢æ•°æ®é‡å¤æäº¤
    const isRepeatSubmit = !(config.headers || {}).repeatSubmit;
    if (getToken() && !isToken) {
      config.headers['Authorization'] = 'Bearer ' + getToken(); // è®©æ¯ä¸ªè¯·æ±‚携带自定义token è¯·æ ¹æ®å®žé™…情况自行修改
    }
    // get请求映射params参数
    if (config.method === 'get' && config.params) {
      let url = config.url + '?' + tansParams(config.params);
      url = url.slice(0, -1);
      config.params = {};
      config.url = url;
    }
        if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
            const requestObj = {
                url: config.url,
                data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
                time: new Date().getTime()
            };
            const sessionObj = cache.session.getJSON('sessionObj');
            if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
                cache.session.setJSON('sessionObj', requestObj);
            } else {
                const s_url = sessionObj.url; // è¯·æ±‚地址
                const s_data = sessionObj.data; // è¯·æ±‚数据
                const s_time = sessionObj.time; // è¯·æ±‚æ—¶é—´
                const interval = 500; // é—´é𔿗¶é—´(ms),小于此时间视为重复提交
                if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
                    const message = '数据正在处理,请勿重复提交';
                    console.warn(`[${s_url}]: ` + message);
                    return Promise.reject(new Error(message));
                } else {
                    cache.session.setJSON('sessionObj', requestObj);
                }
            }
        }
        return config;
    },
    (error: any) => {
        console.log(error);
        return Promise.reject(error);
    }
    if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
      const requestObj = {
        url: config.url,
        data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
        time: new Date().getTime()
      };
      const sessionObj = cache.session.getJSON('sessionObj');
      if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
        cache.session.setJSON('sessionObj', requestObj);
      } else {
        const s_url = sessionObj.url; // è¯·æ±‚地址
        const s_data = sessionObj.data; // è¯·æ±‚数据
        const s_time = sessionObj.time; // è¯·æ±‚æ—¶é—´
        const interval = 500; // é—´é𔿗¶é—´(ms),小于此时间视为重复提交
        if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
          const message = '数据正在处理,请勿重复提交';
          console.warn(`[${s_url}]: ` + message);
          return Promise.reject(new Error(message));
        } else {
          cache.session.setJSON('sessionObj', requestObj);
        }
      }
    }
    return config;
  },
  (error: any) => {
    console.log(error);
    return Promise.reject(error);
  }
);
// å“åº”拦截器
service.interceptors.response.use(
    (res) => {
        // æœªè®¾ç½®çŠ¶æ€ç åˆ™é»˜è®¤æˆåŠŸçŠ¶æ€
        const code = res.data.code || HttpStatus.SUCCESS;
        // èŽ·å–é”™è¯¯ä¿¡æ¯
        const msg = errorCode[code] || res.data.msg || errorCode['default'];
        // äºŒè¿›åˆ¶æ•°æ®åˆ™ç›´æŽ¥è¿”回
        if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
            return res.data;
        }
        if (code === 401) {
            // prettier-ignore
            if (!isRelogin.show) {
                isRelogin.show = true;
                ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
                    confirmButtonText: '重新登录',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    isRelogin.show = false;
                    useUserStore().logout().then(() => {
                            location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
                        });
                }).catch(() => {
                    isRelogin.show = false;
                });
            }
            return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
        } else if (code === HttpStatus.SERVER_ERROR) {
            console.log(msg);
            ElMessage({ message: msg, type: 'error' });
            return Promise.reject(new Error(msg));
        } else if (code === HttpStatus.WARN) {
            ElMessage({ message: msg, type: 'warning' });
            return Promise.reject(new Error(msg));
        } else if (code !== HttpStatus.SUCCESS) {
            ElNotification.error({ title: msg });
            return Promise.reject('error');
        } else {
            return Promise.resolve(res.data);
        }
    },
    (error) => {
        let { message } = error;
        if (message == 'Network Error') {
            message = '后端接口连接异常';
        } else if (message.includes('timeout')) {
            message = '系统接口请求超时';
        } else if (message.includes('Request failed with status code')) {
            message = '系统接口' + message.substr(message.length - 3) + '异常';
        }
        ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
        return Promise.reject(error);
    }
  (res) => {
    // æœªè®¾ç½®çŠ¶æ€ç åˆ™é»˜è®¤æˆåŠŸçŠ¶æ€
    const code = res.data.code || HttpStatus.SUCCESS;
    // èŽ·å–é”™è¯¯ä¿¡æ¯
    const msg = errorCode[code] || res.data.msg || errorCode['default'];
    // äºŒè¿›åˆ¶æ•°æ®åˆ™ç›´æŽ¥è¿”回
    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
      return res.data;
    }
    if (code === 401) {
      // prettier-ignore
      if (!isRelogin.show) {
        isRelogin.show = true;
        ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          isRelogin.show = false;
          useUserStore().logout().then(() => {
              location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
            });
        }).catch(() => {
          isRelogin.show = false;
        });
      }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
    } else if (code === HttpStatus.SERVER_ERROR) {
      console.log(msg);
      ElMessage({ message: msg, type: 'error' });
      return Promise.reject(new Error(msg));
    } else if (code === HttpStatus.WARN) {
      ElMessage({ message: msg, type: 'warning' });
      return Promise.reject(new Error(msg));
    } else if (code !== HttpStatus.SUCCESS) {
      ElNotification.error({ title: msg });
      return Promise.reject('error');
    } else {
      return Promise.resolve(res.data);
    }
  },
  (error) => {
    let { message } = error;
    if (message == 'Network Error') {
      message = '后端接口连接异常';
    } else if (message.includes('timeout')) {
      message = '系统接口请求超时';
    } else if (message.includes('Request failed with status code')) {
      message = '系统接口' + message.substr(message.length - 3) + '异常';
    }
    ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
    return Promise.reject(error);
  }
);
// é€šç”¨ä¸‹è½½æ–¹æ³•
export function download(url: string, params: any, fileName: string) {
    downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
    // prettier-ignore
    return service.post(url, params, {
            transformRequest: [
                (params) => {
                    return tansParams(params);
                }
            ],
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            responseType: 'blob'
        }).then(async (resp) => {
            const isLogin = blobValidate(resp);
            if (isLogin) {
                const blob = new Blob([resp as any]);
  downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
  // prettier-ignore
  return service.post(url, params, {
      transformRequest: [
        (params) => {
          return tansParams(params);
        }
      ],
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      responseType: 'blob'
    }).then(async (resp) => {
      const isLogin = blobValidate(resp);
      if (isLogin) {
        const blob = new Blob([resp as any]);
        FileSaver.saveAs(blob, fileName);
            } else {
                const resText = await resp.data.text();
                const rspObj = JSON.parse(resText);
                const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
                ElMessage.error(errMsg);
            }
            downloadLoadingInstance.close();
        }).catch((r) => {
            console.error(r);
            ElMessage.error('下载文件出现错误,请联系管理员!');
            downloadLoadingInstance.close();
        });
      } else {
        const resText = await resp.data.text();
        const rspObj = JSON.parse(resText);
        const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
        ElMessage.error(errMsg);
      }
      downloadLoadingInstance.close();
    }).catch((r) => {
      console.error(r);
      ElMessage.error('下载文件出现错误,请联系管理员!');
      downloadLoadingInstance.close();
    });
}
// å¯¼å‡º axios å®žä¾‹
export default service;
src/utils/ruoyi.ts
@@ -1,46 +1,46 @@
// æ—¥æœŸæ ¼å¼åŒ–
export function parseTime(time: any, pattern?: string) {
    if (arguments.length === 0 || !time) {
        return null;
    }
    const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
    let date;
    if (typeof time === 'object') {
        date = time;
    } else {
        if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
            time = parseInt(time);
        } else if (typeof time === 'string') {
            time = time
                .replace(new RegExp(/-/gm), '/')
                .replace('T', ' ')
                .replace(new RegExp(/\.[\d]{3}/gm), '');
        }
        if (typeof time === 'number' && time.toString().length === 10) {
            time = time * 1000;
        }
        date = new Date(time);
    }
    const formatObj: { [key: string]: any } = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay()
    };
    return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result: string, key: string) => {
        let value = formatObj[key];
        // Note: getDay() returns 0 on Sunday
        if (key === 'a') {
            return ['日', '一', '二', '三', '四', '五', '六'][value];
        }
        if (result.length > 0 && value < 10) {
            value = '0' + value;
        }
        return value || 0;
    });
  if (arguments.length === 0 || !time) {
    return null;
  }
  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
  let date;
  if (typeof time === 'object') {
    date = time;
  } else {
    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
      time = parseInt(time);
    } else if (typeof time === 'string') {
      time = time
        .replace(new RegExp(/-/gm), '/')
        .replace('T', ' ')
        .replace(new RegExp(/\.[\d]{3}/gm), '');
    }
    if (typeof time === 'number' && time.toString().length === 10) {
      time = time * 1000;
    }
    date = new Date(time);
  }
  const formatObj: { [key: string]: any } = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  };
  return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result: string, key: string) => {
    let value = formatObj[key];
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value];
    }
    if (result.length > 0 && value < 10) {
      value = '0' + value;
    }
    return value || 0;
  });
}
/**
@@ -50,102 +50,102 @@
 * @param propName
 */
export const addDateRange = (params: any, dateRange: any[], propName?: string) => {
    const search = params;
    search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
    dateRange = Array.isArray(dateRange) ? dateRange : [];
    if (typeof propName === 'undefined') {
        search.params['beginTime'] = dateRange[0];
        search.params['endTime'] = dateRange[1];
    } else {
        search.params['begin' + propName] = dateRange[0];
        search.params['end' + propName] = dateRange[1];
    }
    return search;
  const search = params;
  search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
  dateRange = Array.isArray(dateRange) ? dateRange : [];
  if (typeof propName === 'undefined') {
    search.params['beginTime'] = dateRange[0];
    search.params['endTime'] = dateRange[1];
  } else {
    search.params['begin' + propName] = dateRange[0];
    search.params['end' + propName] = dateRange[1];
  }
  return search;
};
// å›žæ˜¾æ•°æ®å­—å…¸
export const selectDictLabel = (datas: any, value: number | string) => {
    if (value === undefined) {
        return '';
    }
    const actions = [];
    Object.keys(datas).some((key) => {
        if (datas[key].value == '' + value) {
            actions.push(datas[key].label);
            return true;
        }
    });
    if (actions.length === 0) {
        actions.push(value);
    }
    return actions.join('');
  if (value === undefined) {
    return '';
  }
  const actions = [];
  Object.keys(datas).some((key) => {
    if (datas[key].value == '' + value) {
      actions.push(datas[key].label);
      return true;
    }
  });
  if (actions.length === 0) {
    actions.push(value);
  }
  return actions.join('');
};
// å›žæ˜¾æ•°æ®å­—典(字符串数组)
export const selectDictLabels = (datas: any, value: any, separator: any) => {
    if (value === undefined || value.length === 0) {
        return '';
    }
    if (Array.isArray(value)) {
        value = value.join(',');
    }
    const actions: any[] = [];
    const currentSeparator = undefined === separator ? ',' : separator;
    const temp = value.split(currentSeparator);
    Object.keys(value.split(currentSeparator)).some((val) => {
        let match = false;
        Object.keys(datas).some((key) => {
            if (datas[key].value == '' + temp[val]) {
                actions.push(datas[key].label + currentSeparator);
                match = true;
            }
        });
        if (!match) {
            actions.push(temp[val] + currentSeparator);
        }
    });
    return actions.join('').substring(0, actions.join('').length - 1);
  if (value === undefined || value.length === 0) {
    return '';
  }
  if (Array.isArray(value)) {
    value = value.join(',');
  }
  const actions: any[] = [];
  const currentSeparator = undefined === separator ? ',' : separator;
  const temp = value.split(currentSeparator);
  Object.keys(value.split(currentSeparator)).some((val) => {
    let match = false;
    Object.keys(datas).some((key) => {
      if (datas[key].value == '' + temp[val]) {
        actions.push(datas[key].label + currentSeparator);
        match = true;
      }
    });
    if (!match) {
      actions.push(temp[val] + currentSeparator);
    }
  });
  return actions.join('').substring(0, actions.join('').length - 1);
};
// å­—符串格式化(%s )
export function sprintf(str: string) {
    if (arguments.length !== 0) {
        let flag = true,
            i = 1;
        str = str.replace(/%s/g, function () {
            const arg = arguments[i++];
            if (typeof arg === 'undefined') {
                flag = false;
                return '';
            }
            return arg;
        });
        return flag ? str : '';
    }
  if (arguments.length !== 0) {
    let flag = true,
      i = 1;
    str = str.replace(/%s/g, function () {
      const arg = arguments[i++];
      if (typeof arg === 'undefined') {
        flag = false;
        return '';
      }
      return arg;
    });
    return flag ? str : '';
  }
}
// è½¬æ¢å­—符串,undefined,null等转化为""
export const parseStrEmpty = (str: any) => {
    if (!str || str == 'undefined' || str == 'null') {
        return '';
    }
    return str;
  if (!str || str == 'undefined' || str == 'null') {
    return '';
  }
  return str;
};
// æ•°æ®åˆå¹¶
export const mergeRecursive = (source: any, target: any) => {
    for (const p in target) {
        try {
            if (target[p].constructor == Object) {
                source[p] = mergeRecursive(source[p], target[p]);
            } else {
                source[p] = target[p];
            }
        } catch (e) {
            source[p] = target[p];
        }
    }
    return source;
  for (const p in target) {
    try {
      if (target[p].constructor == Object) {
        source[p] = mergeRecursive(source[p], target[p]);
      } else {
        source[p] = target[p];
      }
    } catch (e) {
      source[p] = target[p];
    }
  }
  return source;
};
/**
@@ -156,51 +156,51 @@
 * @param {*} children å­©å­èŠ‚ç‚¹å­—æ®µ é»˜è®¤ 'children'
 */
export const handleTree = <T>(data: any[], id?: string, parentId?: string, children?: string): T[] => {
    const config: {
        id: string;
        parentId: string;
        childrenList: string;
    } = {
        id: id || 'id',
        parentId: parentId || 'parentId',
        childrenList: children || 'children'
    };
  const config: {
    id: string;
    parentId: string;
    childrenList: string;
  } = {
    id: id || 'id',
    parentId: parentId || 'parentId',
    childrenList: children || 'children'
  };
    const childrenListMap: any = {};
    const nodeIds: any = {};
    const tree: T[] = [];
  const childrenListMap: any = {};
  const nodeIds: any = {};
  const tree: T[] = [];
    for (const d of data) {
        const parentId = d[config.parentId];
        if (childrenListMap[parentId] == null) {
            childrenListMap[parentId] = [];
        }
        nodeIds[d[config.id]] = d;
        childrenListMap[parentId].push(d);
    }
  for (const d of data) {
    const parentId = d[config.parentId];
    if (childrenListMap[parentId] == null) {
      childrenListMap[parentId] = [];
    }
    nodeIds[d[config.id]] = d;
    childrenListMap[parentId].push(d);
  }
    for (const d of data) {
        const parentId = d[config.parentId];
        if (nodeIds[parentId] == null) {
            tree.push(d);
        }
    }
    const adaptToChildrenList = (o: any) => {
        if (childrenListMap[o[config.id]] !== null) {
            o[config.childrenList] = childrenListMap[o[config.id]];
        }
        if (o[config.childrenList]) {
            for (const c of o[config.childrenList]) {
                adaptToChildrenList(c);
            }
        }
    };
  for (const d of data) {
    const parentId = d[config.parentId];
    if (nodeIds[parentId] == null) {
      tree.push(d);
    }
  }
  const adaptToChildrenList = (o: any) => {
    if (childrenListMap[o[config.id]] !== null) {
      o[config.childrenList] = childrenListMap[o[config.id]];
    }
    if (o[config.childrenList]) {
      for (const c of o[config.childrenList]) {
        adaptToChildrenList(c);
      }
    }
  };
    for (const t of tree) {
        adaptToChildrenList(t);
    }
  for (const t of tree) {
    adaptToChildrenList(t);
  }
    return tree;
  return tree;
};
/**
@@ -208,40 +208,40 @@
 * @param {*} params  å‚æ•°
 */
export const tansParams = (params: any) => {
    let result = '';
    for (const propName of Object.keys(params)) {
        const value = params[propName];
        const part = encodeURIComponent(propName) + '=';
        if (value !== null && value !== '' && typeof value !== 'undefined') {
            if (typeof value === 'object') {
                for (const key of Object.keys(value)) {
                    if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
                        const params = propName + '[' + key + ']';
                        const subPart = encodeURIComponent(params) + '=';
                        result += subPart + encodeURIComponent(value[key]) + '&';
                    }
                }
            } else {
                result += part + encodeURIComponent(value) + '&';
            }
        }
    }
    return result;
  let result = '';
  for (const propName of Object.keys(params)) {
    const value = params[propName];
    const part = encodeURIComponent(propName) + '=';
    if (value !== null && value !== '' && typeof value !== 'undefined') {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
            const params = propName + '[' + key + ']';
            const subPart = encodeURIComponent(params) + '=';
            result += subPart + encodeURIComponent(value[key]) + '&';
          }
        }
      } else {
        result += part + encodeURIComponent(value) + '&';
      }
    }
  }
  return result;
};
// è¿”回项目路径
export const getNormalPath = (p: string): string => {
    if (p.length === 0 || !p || p === 'undefined') {
        return p;
    }
    const res = p.replace('//', '/');
    if (res[res.length - 1] === '/') {
        return res.slice(0, res.length - 1);
    }
    return res;
  if (p.length === 0 || !p || p === 'undefined') {
    return p;
  }
  const res = p.replace('//', '/');
  if (res[res.length - 1] === '/') {
    return res.slice(0, res.length - 1);
  }
  return res;
};
// éªŒè¯æ˜¯å¦ä¸ºblob格式
export const blobValidate = (data: any) => {
    return data.type !== 'application/json';
  return data.type !== 'application/json';
};
src/utils/scroll-to.ts
@@ -1,22 +1,22 @@
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
    t /= d / 2;
    if (t < 1) {
        return (c / 2) * t * t + b;
    }
    t--;
    return (-c / 2) * (t * (t - 2) - 1) + b;
  t /= d / 2;
  if (t < 1) {
    return (c / 2) * t * t + b;
  }
  t--;
  return (-c / 2) * (t * (t - 2) - 1) + b;
};
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
const requestAnimFrame = (function () {
    return (
        window.requestAnimationFrame ||
        (window as any).webkitRequestAnimationFrame ||
        (window as any).mozRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        }
    );
  return (
    window.requestAnimationFrame ||
    (window as any).webkitRequestAnimationFrame ||
    (window as any).mozRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60);
    }
  );
})();
/**
@@ -24,13 +24,13 @@
 * @param {number} amount
 */
const move = (amount: number) => {
    document.documentElement.scrollTop = amount;
    (document.body.parentNode as HTMLElement).scrollTop = amount;
    document.body.scrollTop = amount;
  document.documentElement.scrollTop = amount;
  (document.body.parentNode as HTMLElement).scrollTop = amount;
  document.body.scrollTop = amount;
};
const position = () => {
    return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop;
  return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop;
};
/**
@@ -39,27 +39,27 @@
 * @param {Function} callback
 */
export const scrollTo = (to: number, duration: number, callback?: any) => {
    const start = position();
    const change = to - start;
    const increment = 20;
    let currentTime = 0;
    duration = typeof duration === 'undefined' ? 500 : duration;
    const animateScroll = function () {
        // increment the time
        currentTime += increment;
        // find the value with the quadratic in-out easing function
        const val = easeInOutQuad(currentTime, start, change, duration);
        // move the document.body
        move(val);
        // do the animation unless its over
        if (currentTime < duration) {
            requestAnimFrame(animateScroll);
        } else {
            if (callback && typeof callback === 'function') {
                // the animation is done so lets callback
                callback();
            }
        }
    };
    animateScroll();
  const start = position();
  const change = to - start;
  const increment = 20;
  let currentTime = 0;
  duration = typeof duration === 'undefined' ? 500 : duration;
  const animateScroll = function () {
    // increment the time
    currentTime += increment;
    // find the value with the quadratic in-out easing function
    const val = easeInOutQuad(currentTime, start, change, duration);
    // move the document.body
    move(val);
    // do the animation unless its over
    if (currentTime < duration) {
      requestAnimFrame(animateScroll);
    } else {
      if (callback && typeof callback === 'function') {
        // the animation is done so lets callback
        callback();
      }
    }
  };
  animateScroll();
};
src/utils/theme.ts
@@ -1,52 +1,52 @@
// å¤„理主题样式
export const handleThemeStyle = (theme: string) => {
    document.documentElement.style.setProperty('--el-color-primary', theme);
    for (let i = 1; i <= 9; i++) {
        document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`);
    }
    for (let i = 1; i <= 9; i++) {
        document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`);
    }
  document.documentElement.style.setProperty('--el-color-primary', theme);
  for (let i = 1; i <= 9; i++) {
    document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`);
  }
  for (let i = 1; i <= 9; i++) {
    document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`);
  }
};
// hex颜色转rgb颜色
export const hexToRgb = (str: string): string[] => {
    str = str.replace('#', '');
    const hexs = str.match(/../g);
    for (let i = 0; i < 3; i++) {
        if (hexs) {
            hexs[i] = String(parseInt(hexs[i], 16));
        }
    }
    return hexs ? hexs : [];
  str = str.replace('#', '');
  const hexs = str.match(/../g);
  for (let i = 0; i < 3; i++) {
    if (hexs) {
      hexs[i] = String(parseInt(hexs[i], 16));
    }
  }
  return hexs ? hexs : [];
};
// rgb颜色转Hex颜色
export const rgbToHex = (r: string, g: string, b: string) => {
    const hexs = [Number(r).toString(16), Number(g).toString(16), Number(b).toString(16)];
    for (let i = 0; i < 3; i++) {
        if (hexs[i].length == 1) {
            hexs[i] = `0${hexs[i]}`;
        }
    }
    return `#${hexs.join('')}`;
  const hexs = [Number(r).toString(16), Number(g).toString(16), Number(b).toString(16)];
  for (let i = 0; i < 3; i++) {
    if (hexs[i].length == 1) {
      hexs[i] = `0${hexs[i]}`;
    }
  }
  return `#${hexs.join('')}`;
};
// å˜æµ…颜色值
export const getLightColor = (color: string, level: number) => {
    const rgb = hexToRgb(color);
    for (let i = 0; i < 3; i++) {
        const s = (255 - Number(rgb[i])) * level + Number(rgb[i]);
        rgb[i] = String(Math.floor(s));
    }
    return rgbToHex(rgb[0], rgb[1], rgb[2]);
  const rgb = hexToRgb(color);
  for (let i = 0; i < 3; i++) {
    const s = (255 - Number(rgb[i])) * level + Number(rgb[i]);
    rgb[i] = String(Math.floor(s));
  }
  return rgbToHex(rgb[0], rgb[1], rgb[2]);
};
// å˜æ·±é¢œè‰²å€¼
export const getDarkColor = (color: string, level: number) => {
    const rgb = hexToRgb(color);
    for (let i = 0; i < 3; i++) {
        rgb[i] = String(Math.floor(Number(rgb[i]) * (1 - level)));
    }
    return rgbToHex(rgb[0], rgb[1], rgb[2]);
  const rgb = hexToRgb(color);
  for (let i = 0; i < 3; i++) {
    rgb[i] = String(Math.floor(Number(rgb[i]) * (1 - level)));
  }
  return rgbToHex(rgb[0], rgb[1], rgb[2]);
};
src/utils/validate.ts
@@ -4,7 +4,7 @@
 * @param url
 */
export const isHttp = (url: string): boolean => {
    return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
};
/**
@@ -13,7 +13,7 @@
 * @returns {Boolean}
 */
export const isExternal = (path: string) => {
    return /^(https?:|mailto:|tel:)/.test(path);
  return /^(https?:|mailto:|tel:)/.test(path);
};
/**
@@ -21,8 +21,8 @@
 * @returns {Boolean}
 */
export const validUsername = (str: string) => {
    const valid_map = ['admin', 'editor'];
    return valid_map.indexOf(str.trim()) >= 0;
  const valid_map = ['admin', 'editor'];
  return valid_map.indexOf(str.trim()) >= 0;
};
/**
@@ -30,9 +30,9 @@
 * @returns {Boolean}
 */
export const validURL = (url: string) => {
    const reg =
        /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
    return reg.test(url);
  const reg =
    /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
  return reg.test(url);
};
/**
@@ -40,8 +40,8 @@
 * @returns {Boolean}
 */
export const validLowerCase = (str: string) => {
    const reg = /^[a-z]+$/;
    return reg.test(str);
  const reg = /^[a-z]+$/;
  return reg.test(str);
};
/**
@@ -49,8 +49,8 @@
 * @returns {Boolean}
 */
export const validUpperCase = (str: string) => {
    const reg = /^[A-Z]+$/;
    return reg.test(str);
  const reg = /^[A-Z]+$/;
  return reg.test(str);
};
/**
@@ -58,8 +58,8 @@
 * @returns {Boolean}
 */
export const validAlphabets = (str: string) => {
    const reg = /^[A-Za-z]+$/;
    return reg.test(str);
  const reg = /^[A-Za-z]+$/;
  return reg.test(str);
};
/**
@@ -67,9 +67,9 @@
 * @returns {Boolean}
 */
export const validEmail = (email: string) => {
    const reg =
        /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return reg.test(email);
  const reg =
    /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return reg.test(email);
};
/**
@@ -77,7 +77,7 @@
 * @returns {Boolean}
 */
export const isString = (str: any) => {
    return typeof str === 'string' || str instanceof String;
  return typeof str === 'string' || str instanceof String;
};
/**
@@ -85,8 +85,8 @@
 * @returns {Boolean}
 */
export const isArray = (arg: string | string[]) => {
    if (typeof Array.isArray === 'undefined') {
        return Object.prototype.toString.call(arg) === '[object Array]';
    }
    return Array.isArray(arg);
  if (typeof Array.isArray === 'undefined') {
    return Object.prototype.toString.call(arg) === '[object Array]';
  }
  return Array.isArray(arg);
};
src/views/demo/demo/index.vue
@@ -1,3 +1,150 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="key键" prop="testKey">
            <el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="值" prop="value">
            <el-input v-model="queryParams.value" placeholder="请输入值" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="创建时间">
            <el-date-picker
              v-model="daterangeCreateTime"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
            <el-button type="primary" icon="search" @click="handlePage">搜索(自定义分页接口)</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['demo:demo:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['demo:demo:edit']">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['demo:demo:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['demo:demo:import']">导入(校验)</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['demo:demo:export']">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="主键" align="center" prop="id" v-if="columns[0].visible" />
        <el-table-column label="部门id" align="center" prop="deptId" v-if="columns[1].visible" />
        <el-table-column label="用户id" align="center" prop="userId" v-if="columns[2].visible" />
        <el-table-column label="排序号" align="center" prop="orderNum" v-if="columns[3].visible" />
        <el-table-column label="key键" align="center" prop="testKey" v-if="columns[4].visible" />
        <el-table-column label="值" align="center" prop="value" v-if="columns[5].visible" />
        <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="创建人" align="center" prop="createByName" v-if="columns[7].visible" />
        <el-table-column label="更新时间" align="center" prop="updateTime" v-if="columns[8].visible" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="更新人" align="center" prop="updateByName" v-if="columns[9].visible" />
        <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:demo:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:demo:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•å•è¡¨å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="demoFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="部门id" prop="deptId">
          <el-input v-model="form.deptId" placeholder="请输入部门id" />
        </el-form-item>
        <el-form-item label="用户id" prop="userId">
          <el-input v-model="form.userId" placeholder="请输入用户id" />
        </el-form-item>
        <el-form-item label="排序号" prop="orderNum">
          <el-input v-model="form.orderNum" placeholder="请输入排序号" />
        </el-form-item>
        <el-form-item label="key键" prop="testKey">
          <el-input v-model="form.testKey" placeholder="请输入key键" />
        </el-form-item>
        <el-form-item label="值" prop="value">
          <el-input v-model="form.value" placeholder="请输入值" />
        </el-form-item>
        <el-form-item label="创建时间" prop="createTime">
          <el-date-picker clearable v-model="form.createTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择创建时间">
          </el-date-picker>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button :loading="buttonLoading" type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç”¨æˆ·å¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
      <el-upload
        ref="uploadRef"
        :limit="1"
        accept=".xlsx, .xls"
        :headers="upload.headers"
        :action="upload.url + '?updateSupport=' + upload.updateSupport"
        :disabled="upload.isUploading"
        :on-progress="handleFileUploadProgress"
        :on-success="handleFileSuccess"
        :auto-upload="false"
        drag
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Demo" lang="ts">
import { listDemo, pageDemo, getDemo, delDemo, addDemo, updateDemo } from "@/api/demo/demo";
import { getToken } from "@/utils/auth";
@@ -22,336 +169,189 @@
const uploadRef = ref(ElUpload);
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
    visible: false,
    title: ''
});
/** ç”¨æˆ·å¯¼å…¥å‚æ•° */
const upload = reactive<ImportOption>({
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(用户导入)
    open: false,
    // å¼¹å‡ºå±‚标题(用户导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "demo/demo/importData"
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(用户导入)
    open: false,
    // å¼¹å‡ºå±‚标题(用户导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "demo/demo/importData"
})
// åˆ—显隐信息
const columns = ref<FieldOption[]>([
    { key: 0, label: `主键`, visible: false },
    { key: 1, label: `部门id`, visible: true },
    { key: 2, label: `用户id`, visible: true },
    { key: 3, label: `排序号`, visible: true },
    { key: 4, label: `key键`, visible: true },
    { key: 5, label: `值`, visible: true },
    { key: 6, label: `创建时间`, visible: true },
    { key: 7, label: `创建人`, visible: true },
    { key: 8, label: `更新时间`, visible: true },
    { key: 9, label: `更新人`, visible: true }
    { key: 0, label: `主键`, visible: false },
    { key: 1, label: `部门id`, visible: true },
    { key: 2, label: `用户id`, visible: true },
    { key: 3, label: `排序号`, visible: true },
    { key: 4, label: `key键`, visible: true },
    { key: 5, label: `值`, visible: true },
    { key: 6, label: `创建时间`, visible: true },
    { key: 7, label: `创建人`, visible: true },
    { key: 8, label: `更新时间`, visible: true },
    { key: 9, label: `更新人`, visible: true }
]);
const initDataForm: DemoForm = {
    id: undefined,
    deptId: undefined,
    userId: undefined,
    orderNum: 0,
    testKey: '',
    value: '',
    version: '',
    ossConfigId: undefined,
    id: undefined,
    deptId: undefined,
    userId: undefined,
    orderNum: 0,
    testKey: '',
    value: '',
    version: '',
    ossConfigId: undefined,
}
const data = reactive<PageData<DemoForm, DemoQuery>>({
    form: { ...initDataForm },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        testKey: '',
        value: '',
        createTime: '',
    },
    rules: {
        testKey: [{ required: true, message: "key键不能为空", trigger: "blur" }],
        value: [{ required: true, message: "值不能为空", trigger: "blur" }],
    }
    form: { ...initDataForm },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        testKey: '',
        value: '',
        createTime: '',
    },
    rules: {
        testKey: [{ required: true, message: "key键不能为空", trigger: "blur" }],
        value: [{ required: true, message: "值不能为空", trigger: "blur" }],
    }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢OSS对象存储列表 */
const getList = async () => {
    loading.value = true;
    const res = await listDemo(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime"));
    demoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
    loading.value = true;
    const res = await listDemo(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime"));
    demoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
/** è‡ªå®šä¹‰åˆ†é¡µæŸ¥è¯¢ */
const getPage = async () => {
    loading.value = true;
    const res = await pageDemo(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime"));
    demoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
    loading.value = true;
    const res = await pageDemo(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime"));
    demoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    reset();
    dialog.visible = false;
    reset();
    dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = { ...initDataForm };
    demoFormRef.value.resetFields();
    form.value = { ...initDataForm };
    demoFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handlePage = () => {
    queryParams.value.pageNum = 1;
    getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    daterangeCreateTime.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
    daterangeCreateTime.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
}
/** é€‰æ‹©æ¡æ•°  */
const handleSelectionChange = (selection: DemoVO[]) => {
    ids.value = selection.map(item => item.id);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
    ids.value = selection.map(item => item.id);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加测试单表";
    nextTick(() => {
        reset();
    })
    dialog.visible = true;
    dialog.title = "添加测试单表";
    nextTick(() => {
        reset();
    })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: DemoVO) => {
    loading.value = true;
    dialog.visible = true;
    dialog.title = "修改测试单表";
    const _ids = row?.id || ids.value[0];
    const res = await getDemo(_ids);
    nextTick(() => {
        reset();
        Object.assign(form.value, res.data)
        loading.value = false;
    })
    loading.value = true;
    dialog.visible = true;
    dialog.title = "修改测试单表";
    const _ids = row?.id || ids.value[0];
    const res = await getDemo(_ids);
    nextTick(() => {
        reset();
        Object.assign(form.value, res.data)
        loading.value = false;
    })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    demoFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            buttonLoading.value = true;
            if (form.value.ossConfigId) {
                await updateDemo(form.value).finally(() => buttonLoading.value = false);
            } else {
                await addDemo(form.value).finally(() => buttonLoading.value = false);
            }
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
    demoFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            buttonLoading.value = true;
            if (form.value.ossConfigId) {
                await updateDemo(form.value).finally(() => buttonLoading.value = false);
            } else {
                await addDemo(form.value).finally(() => buttonLoading.value = false);
            }
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DemoVO) => {
    const _ids = row?.id || ids.value;
    await proxy?.$modal.confirm('是否确认删除测试单表编号为"' + _ids + '"的数据项?');
    await delDemo(_ids).finally(() => loading.value = false);
    loading.value = false;
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    const _ids = row?.id || ids.value;
    await proxy?.$modal.confirm('是否确认删除测试单表编号为"' + _ids + '"的数据项?');
    await delDemo(_ids).finally(() => loading.value = false);
    loading.value = false;
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
const handleImport = () => {
    upload.title = "测试导入";
    upload.open = true;
    upload.title = "测试导入";
    upload.open = true;
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
    proxy?.download("demo/demo/export", {
        ...queryParams.value,
    }, `demo_${new Date().getTime()}.xlsx`);
    proxy?.download("demo/demo/export", {
        ...queryParams.value,
    }, `demo_${new Date().getTime()}.xlsx`);
}
/**文件上传中处理 */
const handleFileUploadProgress = () => {
    upload.isUploading = true;
    upload.isUploading = true;
}
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (res: any, file: UploadFile, fileList: UploadFiles) => {
    upload.open = false;
    upload.isUploading = false;
    uploadRef.value.clearFiles();
    ElMessageBox.alert(res.msg, "导入结果", { dangerouslyUseHTMLString: true });
    getList();
    upload.open = false;
    upload.isUploading = false;
    uploadRef.value.clearFiles();
    ElMessageBox.alert(res.msg, "导入结果", { dangerouslyUseHTMLString: true });
    getList();
}
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
    uploadRef.value.submit();
    uploadRef.value.submit();
}
onMounted(() => {
    getList()
    getPage()
    getList()
    getPage()
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="key键" prop="testKey">
                        <el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable style="width: 200px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="值" prop="value">
                        <el-input v-model="queryParams.value" placeholder="请输入值" clearable style="width: 200px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="创建时间">
                        <el-date-picker
                            v-model="daterangeCreateTime"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="daterange"
                            range-separator="-"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
                        ></el-date-picker>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
                        <el-button type="primary" icon="search" @click="handlePage">搜索(自定义分页接口)</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['demo:demo:add']">新增</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['demo:demo:edit']">修改</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['demo:demo:remove']">
                            åˆ é™¤
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['demo:demo:import']">导入(校验)</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['demo:demo:export']">导出</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column label="主键" align="center" prop="id" v-if="columns[0].visible" />
                <el-table-column label="部门id" align="center" prop="deptId" v-if="columns[1].visible" />
                <el-table-column label="用户id" align="center" prop="userId" v-if="columns[2].visible" />
                <el-table-column label="排序号" align="center" prop="orderNum" v-if="columns[3].visible" />
                <el-table-column label="key键" align="center" prop="testKey" v-if="columns[4].visible" />
                <el-table-column label="值" align="center" prop="value" v-if="columns[5].visible" />
                <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="创建人" align="center" prop="createByName" v-if="columns[7].visible" />
                <el-table-column label="更新时间" align="center" prop="updateTime" v-if="columns[8].visible" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="更新人" align="center" prop="updateByName" v-if="columns[9].visible" />
                <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:demo:edit']"></el-button>
                        </el-tooltip>
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:demo:remove']"></el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        </el-card>
        <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•å•è¡¨å¯¹è¯æ¡† -->
        <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
            <el-form ref="demoFormRef" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="部门id" prop="deptId">
                    <el-input v-model="form.deptId" placeholder="请输入部门id" />
                </el-form-item>
                <el-form-item label="用户id" prop="userId">
                    <el-input v-model="form.userId" placeholder="请输入用户id" />
                </el-form-item>
                <el-form-item label="排序号" prop="orderNum">
                    <el-input v-model="form.orderNum" placeholder="请输入排序号" />
                </el-form-item>
                <el-form-item label="key键" prop="testKey">
                    <el-input v-model="form.testKey" placeholder="请输入key键" />
                </el-form-item>
                <el-form-item label="值" prop="value">
                    <el-input v-model="form.value" placeholder="请输入值" />
                </el-form-item>
                <el-form-item label="创建时间" prop="createTime">
                    <el-date-picker clearable v-model="form.createTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择创建时间">
                    </el-date-picker>
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button :loading="buttonLoading" type="primary" @click="submitForm">ç¡® å®š</el-button>
                    <el-button @click="cancel">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
        <!-- ç”¨æˆ·å¯¼å…¥å¯¹è¯æ¡† -->
        <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
            <el-upload
                ref="uploadRef"
                :limit="1"
                accept=".xlsx, .xls"
                :headers="upload.headers"
                :action="upload.url + '?updateSupport=' + upload.updateSupport"
                :disabled="upload.isUploading"
                :on-progress="handleFileUploadProgress"
                :on-success="handleFileSuccess"
                :auto-upload="false"
                drag
            >
                <i class="el-icon-upload"></i>
                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
            </el-upload>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
                    <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
src/views/demo/tree/index.vue
@@ -1,3 +1,110 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="qeuryFormRef" :inline="true" label-width="68px">
          <el-form-item label="树节点名" prop="treeName">
            <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="创建时间">
            <el-date-picker
              v-model="daterangeCreateTime"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['demo:tree:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar :columns="columns" v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        v-if="refreshTable"
        v-loading="loading"
        :data="treeList"
        row-key="id"
        :default-expand-all="isExpandAll"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        ref="demoTreeTableRef"
      >
        <el-table-column label="父id" prop="parentId" v-if="columns[0].visible" />
        <el-table-column label="部门id" align="center" prop="deptId" v-if="columns[1].visible" />
        <el-table-column label="用户id" align="center" prop="userId" v-if="columns[2].visible" />
        <el-table-column label="树节点名" align="center" prop="treeName" v-if="columns[3].visible" />
        <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[4].visible" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:tree:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="新增" placement="top">
              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['demo:tree:add']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:tree:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•æ ‘è¡¨å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="treeRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="父id" prop="parentId">
          <el-tree-select
            v-model="form.parentId"
            :data="treeOptions"
            :props="{ value: 'id', label: 'treeName', children: 'children' }"
            value-key="id"
            check-strictly
            placeholder="请选择父id"
          />
        </el-form-item>
        <el-form-item label="部门id" prop="deptId">
          <el-input v-model="form.deptId" placeholder="请输入部门id" />
        </el-form-item>
        <el-form-item label="用户id" prop="userId">
          <el-input v-model="form.userId" placeholder="请输入用户id" />
        </el-form-item>
        <el-form-item label="树节点名" prop="treeName">
          <el-input v-model="form.treeName" placeholder="请输入树节点名" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button :loading="buttonLoading" type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Tree" lang="ts">
import { listTree, getTree, delTree, addTree, updateTree } from '@/api/demo/tree';
import { DemoTreeVO, DemoTreeForm, DemoTreeOptionsType, DemoTreeQuery } from '@/api/demo/types';
@@ -20,274 +127,167 @@
const demoTreeTableRef = ref(ElTable)
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
    visible: false,
    title: ''
});
// åˆ—显隐信息
const columns = ref<FieldOption[]>([
  { key: 0, label: `父id`, visible: false },
  { key: 1, label: `部门id`, visible: true },
  { key: 2, label: `用户id`, visible: true },
  { key: 3, label: `树节点名`, visible: true },
  { key: 4, label: `创建时间`, visible: true }
    { key: 0, label: `父id`, visible: false },
    { key: 1, label: `部门id`, visible: true },
    { key: 2, label: `用户id`, visible: true },
    { key: 3, label: `树节点名`, visible: true },
    { key: 4, label: `创建时间`, visible: true }
]);
const initFormData = {
  id: undefined,
  parentId: undefined,
  deptId: undefined,
  userId: undefined,
  treeName: ''
    id: undefined,
    parentId: undefined,
    deptId: undefined,
    userId: undefined,
    treeName: ''
}
const data = reactive<PageData<DemoTreeForm, DemoTreeQuery>>({
  // æŸ¥è¯¢å‚æ•°
  queryParams: {
    treeName: '',
    createTime: '',
  },
  // è¡¨å•参数
  form: {...initFormData},
  // è¡¨å•校验
  rules: {
    treeName: [{ required: true, message: "树节点名不能为空", trigger: "blur" }],
  }
    // æŸ¥è¯¢å‚æ•°
    queryParams: {
        treeName: '',
        createTime: '',
    },
    // è¡¨å•参数
    form: {...initFormData},
    // è¡¨å•校验
    rules: {
        treeName: [{ required: true, message: "树节点名不能为空", trigger: "blur" }],
    }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢æµ‹è¯•树表列表 */
const getList = () => {
  loading.value = true;
  listTree(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime")).then(res => {
    const data = proxy?.handleTree<DemoTreeVO>(res.data, "id", "parentId");
    if (data) {
      treeList.value = data
    }
    loading.value = false;
  });
    loading.value = true;
    listTree(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime")).then(res => {
        const data = proxy?.handleTree<DemoTreeVO>(res.data, "id", "parentId");
        if (data) {
            treeList.value = data
        }
        loading.value = false;
    });
}
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
const getTreeSelect = async () => {
  listTree(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime")).then(res => {
  const topData: DemoTreeOptionsType = { id: 0, treeName: '顶级节点', children: [] };
  topData.children = proxy?.handleTree<DemoTreeOptionsType>(res.data, "id", "parentId");
  treeOptions.value.push(topData);
  });
    listTree(proxy?.addDateRange(queryParams.value, daterangeCreateTime.value, "CreateTime")).then(res => {
        const topData: DemoTreeOptionsType = { id: 0, treeName: '顶级节点', children: [] };
        topData.children = proxy?.handleTree<DemoTreeOptionsType>(res.data, "id", "parentId");
        treeOptions.value.push(topData);
    });
}
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
    reset();
    dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData}
  treeRef.value.resetFields();
    form.value = {...initFormData}
    treeRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  getList();
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  daterangeCreateTime.value = ['', ''];
  qeuryFormRef.value.resetFields();
  handleQuery();
    daterangeCreateTime.value = ['', ''];
    qeuryFormRef.value.resetFields();
    handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = (row?: DemoTreeVO) => {
  dialog.visible = true;
  dialog.title = "添加测试树表";
  nextTick(() => {
    reset();
    getTreeSelect();
    if (row != null && row.id) {
      form.value.parentId = row.id;
    } else {
      form.value.parentId = 0;
    }
  })
    dialog.visible = true;
    dialog.title = "添加测试树表";
    nextTick(() => {
        reset();
        getTreeSelect();
        if (row != null && row.id) {
            form.value.parentId = row.id;
        } else {
            form.value.parentId = 0;
        }
    })
}
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
  isExpandAll.value = !isExpandAll.value;
  toggleExpandAll(treeList.value, isExpandAll.value)
    isExpandAll.value = !isExpandAll.value;
    toggleExpandAll(treeList.value, isExpandAll.value)
}
/** å±•å¼€/折叠所有 */
const toggleExpandAll = (data: DemoTreeVO[], status: boolean) => {
  data.forEach((item) => {
    demoTreeTableRef.value.toggleRowExpansion(item, status)
    if(item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
    data.forEach((item) => {
        demoTreeTableRef.value.toggleRowExpansion(item, status)
        if(item.children && item.children.length > 0) toggleExpandAll(item.children, status)
    })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: DemoTreeVO) => {
  loading.value = true;
  dialog.visible = true;
  dialog.title = "修改测试树表";
  nextTick(async () => {
    reset();
    getTreeSelect();
    if (row) {
      form.value.parentId = row.id;
    }
    getTree(row.id).then((response) => {
      loading.value = false;
      form.value = response.data;
    loading.value = true;
    dialog.visible = true;
    dialog.title = "修改测试树表";
    nextTick(async () => {
        reset();
        getTreeSelect();
        if (row) {
            form.value.parentId = row.id;
        }
        getTree(row.id).then((response) => {
            loading.value = false;
            form.value = response.data;
    });
  })
        });
    })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  treeRef.value.validate((valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id != null) {
        updateTree(form.value).then(() => {
          proxy?.$modal.msgSuccess("修改成功");
          dialog.visible = false;
          getList();
        }).finally(() => {
          buttonLoading.value = false;
        });
      } else {
        addTree(form.value).then(() => {
          proxy?.$modal.msgSuccess("新增成功");
          dialog.visible = false;
          getList();
        }).finally(() => {
          buttonLoading.value = false;
        });
      }
    }
  });
    treeRef.value.validate((valid: boolean) => {
        if (valid) {
            buttonLoading.value = true;
            if (form.value.id != null) {
                updateTree(form.value).then(() => {
                    proxy?.$modal.msgSuccess("修改成功");
                    dialog.visible = false;
                    getList();
                }).finally(() => {
                    buttonLoading.value = false;
                });
            } else {
                addTree(form.value).then(() => {
                    proxy?.$modal.msgSuccess("新增成功");
                    dialog.visible = false;
                    getList();
                }).finally(() => {
                    buttonLoading.value = false;
                });
            }
        }
    });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = (row: DemoTreeVO) => {
  proxy?.$modal.confirm('是否确认删除测试单表编号为"' + row.id + '"的数据项?').then(() => {
    loading.value = true;
    return delTree(row.id);
  }).then(() => {
    loading.value = false;
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  }).finally(() => {
    loading.value = false;
  });
    proxy?.$modal.confirm('是否确认删除测试单表编号为"' + row.id + '"的数据项?').then(() => {
        loading.value = true;
        return delTree(row.id);
    }).then(() => {
        loading.value = false;
        getList();
        proxy?.$modal.msgSuccess("删除成功");
    }).finally(() => {
        loading.value = false;
    });
}
onMounted(() => {
  getList()
    getList()
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="qeuryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="树节点名" prop="treeName">
                        <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable style="width: 200px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="创建时间">
                        <el-date-picker
                            v-model="daterangeCreateTime"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="daterange"
                            range-separator="-"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
                        ></el-date-picker>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10">
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['demo:tree:add']">新增</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
                    </el-col>
                    <right-toolbar :columns="columns" v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table
                v-if="refreshTable"
                v-loading="loading"
                :data="treeList"
                row-key="id"
                :default-expand-all="isExpandAll"
                :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
                ref="demoTreeTableRef"
            >
                <el-table-column label="父id" prop="parentId" v-if="columns[0].visible" />
                <el-table-column label="部门id" align="center" prop="deptId" v-if="columns[1].visible" />
                <el-table-column label="用户id" align="center" prop="userId" v-if="columns[2].visible" />
                <el-table-column label="树节点名" align="center" prop="treeName" v-if="columns[3].visible" />
                <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[4].visible" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:tree:edit']"></el-button>
                        </el-tooltip>
                        <el-tooltip content="新增" placement="top">
                            <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['demo:tree:add']"></el-button>
                        </el-tooltip>
                        <el-tooltip content="删除" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:tree:remove']"></el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
        </el-card>
        <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•æ ‘è¡¨å¯¹è¯æ¡† -->
        <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
            <el-form ref="treeRef" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="父id" prop="parentId">
                    <el-tree-select
                        v-model="form.parentId"
                        :data="treeOptions"
                        :props="{ value: 'id', label: 'treeName', children: 'children' }"
                        value-key="id"
                        check-strictly
                        placeholder="请选择父id"
                    />
                </el-form-item>
                <el-form-item label="部门id" prop="deptId">
                    <el-input v-model="form.deptId" placeholder="请输入部门id" />
                </el-form-item>
                <el-form-item label="用户id" prop="userId">
                    <el-input v-model="form.userId" placeholder="请输入用户id" />
                </el-form-item>
                <el-form-item label="树节点名" prop="treeName">
                    <el-input v-model="form.treeName" placeholder="请输入树节点名" />
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button :loading="buttonLoading" type="primary" @click="submitForm">ç¡® å®š</el-button>
                    <el-button @click="cancel">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
src/views/error/401.vue
@@ -1,22 +1,22 @@
<template>
    <div class="errPage-container">
        <el-button icon="arrow-left" class="pan-back-btn" @click="back"> è¿”回 </el-button>
        <el-row>
            <el-col :span="12">
                <h1 class="text-jumbo text-ginormous">401错误!</h1>
                <h2>您没有访问权限!</h2>
                <h6>对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面</h6>
                <ul class="list-unstyled">
                    <li class="link-type">
                        <router-link to="/"> å›žé¦–页 </router-link>
                    </li>
                </ul>
            </el-col>
            <el-col :span="12">
                <img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream." />
            </el-col>
        </el-row>
    </div>
  <div class="errPage-container">
    <el-button icon="arrow-left" class="pan-back-btn" @click="back"> è¿”回 </el-button>
    <el-row>
      <el-col :span="12">
        <h1 class="text-jumbo text-ginormous">401错误!</h1>
        <h2>您没有访问权限!</h2>
        <h6>对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面</h6>
        <ul class="list-unstyled">
          <li class="link-type">
            <router-link to="/"> å›žé¦–页 </router-link>
          </li>
        </ul>
      </el-col>
      <el-col :span="12">
        <img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream." />
      </el-col>
    </el-row>
  </div>
</template>
<script setup lang="ts">
src/views/error/404.vue
@@ -1,24 +1,24 @@
<template>
    <div class="wscn-http404-container">
        <div class="wscn-http404">
            <div class="pic-404">
                <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404" />
                <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404" />
                <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404" />
                <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404" />
            </div>
            <div class="bullshit">
                <div class="bullshit__oops">404错误!</div>
                <div class="bullshit__headline">
                    {{ message }}
                </div>
                <div class="bullshit__info">
                    å¯¹ä¸èµ·ï¼Œæ‚¨æ­£åœ¨å¯»æ‰¾çš„页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
                </div>
                <router-link to="/index" class="bullshit__return-home"> è¿”回首页 </router-link>
            </div>
        </div>
    </div>
  <div class="wscn-http404-container">
    <div class="wscn-http404">
      <div class="pic-404">
        <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404" />
        <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404" />
        <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404" />
        <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404" />
      </div>
      <div class="bullshit">
        <div class="bullshit__oops">404错误!</div>
        <div class="bullshit__headline">
          {{ message }}
        </div>
        <div class="bullshit__info">
          å¯¹ä¸èµ·ï¼Œæ‚¨æ­£åœ¨å¯»æ‰¾çš„页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
        </div>
        <router-link to="/index" class="bullshit__return-home"> è¿”回首页 </router-link>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
src/views/index.vue
@@ -1,98 +1,98 @@
<template>
    <div class="app-container home">
        <el-row :gutter="20">
            <el-col :sm="24" :lg="12" style="padding-left: 20px">
                <h2>RuoYi-Vue-Plus多租户管理系统</h2>
                <p>
                    RuoYi-Vue-Plus æ˜¯åŸºäºŽ RuoYi-Vue é’ˆå¯¹ åˆ†å¸ƒå¼é›†ç¾¤ åœºæ™¯å‡çº§(不兼容原框架)
                    <br />
                    * å‰ç«¯å¼€å‘框架 Vue3、TS、Element Plus<br />
                    * åŽç«¯å¼€å‘框架 Spring Boot<br />
                    * å®¹å™¨æ¡†æž¶ Undertow åŸºäºŽ Netty çš„高性能容器<br />
                    * æƒé™è®¤è¯æ¡†æž¶ Sa-Token æ”¯æŒå¤šç»ˆç«¯è®¤è¯ç³»ç»Ÿ<br />
                    * å…³ç³»æ•°æ®åº“ MySQL é€‚配 8.X æœ€ä½Ž 5.7<br />
                    * ç¼“存数据库 Redis é€‚配 6.X æœ€ä½Ž 4.X<br />
                    * æ•°æ®åº“框架 Mybatis-Plus å¿«é€Ÿ CRUD å¢žåŠ å¼€å‘æ•ˆçŽ‡<br />
                    * æ•°æ®åº“框架 p6spy æ›´å¼ºåŠ²çš„ SQL åˆ†æž<br />
                    * å¤šæ•°æ®æºæ¡†æž¶ dynamic-datasource æ”¯æŒä¸»ä»Žä¸Žå¤šç§ç±»æ•°æ®åº“异构<br />
                    * åºåˆ—化框架 Jackson ç»Ÿä¸€ä½¿ç”¨ jackson é«˜æ•ˆå¯é <br />
                    * Redis客户端 Redisson æ€§èƒ½å¼ºåŠ²ã€API丰富<br />
                    * åˆ†å¸ƒå¼é™æµ Redisson å…¨å±€ã€è¯·æ±‚IP、集群ID å¤šç§é™æµ<br />
                    * åˆ†å¸ƒå¼é” Lock4j æ³¨è§£é”ã€å·¥å…·é” å¤šç§å¤šæ ·<br />
                    * åˆ†å¸ƒå¼å¹‚ç­‰ Lock4j åŸºäºŽåˆ†å¸ƒå¼é”å®žçް<br />
                    * åˆ†å¸ƒå¼é“¾è·¯è¿½è¸ª SkyWalking æ”¯æŒé“¾è·¯è¿½è¸ªã€ç½‘格分析、度量聚合、可视化<br />
                    * åˆ†å¸ƒå¼ä»»åŠ¡è°ƒåº¦ Xxl-Job é«˜æ€§èƒ½ é«˜å¯é  æ˜“扩展<br />
                    * æ–‡ä»¶å­˜å‚¨ Minio æœ¬åœ°å­˜å‚¨<br />
                    * æ–‡ä»¶å­˜å‚¨ ä¸ƒç‰›ã€é˜¿é‡Œã€è…¾è®¯ äº‘存储<br />
                    * ç›‘控框架 SpringBoot-Admin å…¨æ–¹ä½æœåŠ¡ç›‘æŽ§<br />
                    * æ ¡éªŒæ¡†æž¶ Validation å¢žå¼ºæŽ¥å£å®‰å…¨æ€§ ä¸¥è°¨æ€§<br />
                    * Excel框架 Alibaba EasyExcel æ€§èƒ½ä¼˜å¼‚ æ‰©å±•性强<br />
                    * æ–‡æ¡£æ¡†æž¶ SpringDoc、javadoc æ— æ³¨è§£é›¶å…¥ä¾µåŸºäºŽjava注释<br />
                    * å·¥å…·ç±»æ¡†æž¶ Hutool、Lombok å‡å°‘代码冗余 å¢žåŠ å®‰å…¨æ€§<br />
                    * ä»£ç ç”Ÿæˆå™¨ é€‚配MP、SpringDoc规范化代码 ä¸€é”®ç”Ÿæˆå‰åŽç«¯ä»£ç <br />
                    * éƒ¨ç½²æ–¹å¼ Docker å®¹å™¨ç¼–排 ä¸€é”®éƒ¨ç½²ä¸šåŠ¡é›†ç¾¤<br />
                    * å›½é™…化 SpringMessage Spring标准国际化方案<br />
                </p>
                <p><b>当前版本:</b> <span>v5.0.0</span></p>
                <p>
                    <el-tag type="danger">&yen;免费开源</el-tag>
                </p>
                <p>
                    <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
                    <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
                    <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://javalionli.gitee.io/plus-doc/#/ruoyi-vue-plus/changlog')"
                        >更新日志</el-button
                    >
                </p>
            </el-col>
  <div class="app-container home">
    <el-row :gutter="20">
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
        <h2>RuoYi-Vue-Plus多租户管理系统</h2>
        <p>
          RuoYi-Vue-Plus æ˜¯åŸºäºŽ RuoYi-Vue é’ˆå¯¹ åˆ†å¸ƒå¼é›†ç¾¤ åœºæ™¯å‡çº§(不兼容原框架)
          <br />
          * å‰ç«¯å¼€å‘框架 Vue3、TS、Element Plus<br />
          * åŽç«¯å¼€å‘框架 Spring Boot<br />
          * å®¹å™¨æ¡†æž¶ Undertow åŸºäºŽ Netty çš„高性能容器<br />
          * æƒé™è®¤è¯æ¡†æž¶ Sa-Token æ”¯æŒå¤šç»ˆç«¯è®¤è¯ç³»ç»Ÿ<br />
          * å…³ç³»æ•°æ®åº“ MySQL é€‚配 8.X æœ€ä½Ž 5.7<br />
          * ç¼“存数据库 Redis é€‚配 6.X æœ€ä½Ž 4.X<br />
          * æ•°æ®åº“框架 Mybatis-Plus å¿«é€Ÿ CRUD å¢žåŠ å¼€å‘æ•ˆçŽ‡<br />
          * æ•°æ®åº“框架 p6spy æ›´å¼ºåŠ²çš„ SQL åˆ†æž<br />
          * å¤šæ•°æ®æºæ¡†æž¶ dynamic-datasource æ”¯æŒä¸»ä»Žä¸Žå¤šç§ç±»æ•°æ®åº“异构<br />
          * åºåˆ—化框架 Jackson ç»Ÿä¸€ä½¿ç”¨ jackson é«˜æ•ˆå¯é <br />
          * Redis客户端 Redisson æ€§èƒ½å¼ºåŠ²ã€API丰富<br />
          * åˆ†å¸ƒå¼é™æµ Redisson å…¨å±€ã€è¯·æ±‚IP、集群ID å¤šç§é™æµ<br />
          * åˆ†å¸ƒå¼é” Lock4j æ³¨è§£é”ã€å·¥å…·é” å¤šç§å¤šæ ·<br />
          * åˆ†å¸ƒå¼å¹‚ç­‰ Lock4j åŸºäºŽåˆ†å¸ƒå¼é”å®žçް<br />
          * åˆ†å¸ƒå¼é“¾è·¯è¿½è¸ª SkyWalking æ”¯æŒé“¾è·¯è¿½è¸ªã€ç½‘格分析、度量聚合、可视化<br />
          * åˆ†å¸ƒå¼ä»»åŠ¡è°ƒåº¦ Xxl-Job é«˜æ€§èƒ½ é«˜å¯é  æ˜“扩展<br />
          * æ–‡ä»¶å­˜å‚¨ Minio æœ¬åœ°å­˜å‚¨<br />
          * æ–‡ä»¶å­˜å‚¨ ä¸ƒç‰›ã€é˜¿é‡Œã€è…¾è®¯ äº‘存储<br />
          * ç›‘控框架 SpringBoot-Admin å…¨æ–¹ä½æœåŠ¡ç›‘æŽ§<br />
          * æ ¡éªŒæ¡†æž¶ Validation å¢žå¼ºæŽ¥å£å®‰å…¨æ€§ ä¸¥è°¨æ€§<br />
          * Excel框架 Alibaba EasyExcel æ€§èƒ½ä¼˜å¼‚ æ‰©å±•性强<br />
          * æ–‡æ¡£æ¡†æž¶ SpringDoc、javadoc æ— æ³¨è§£é›¶å…¥ä¾µåŸºäºŽjava注释<br />
          * å·¥å…·ç±»æ¡†æž¶ Hutool、Lombok å‡å°‘代码冗余 å¢žåŠ å®‰å…¨æ€§<br />
          * ä»£ç ç”Ÿæˆå™¨ é€‚配MP、SpringDoc规范化代码 ä¸€é”®ç”Ÿæˆå‰åŽç«¯ä»£ç <br />
          * éƒ¨ç½²æ–¹å¼ Docker å®¹å™¨ç¼–排 ä¸€é”®éƒ¨ç½²ä¸šåŠ¡é›†ç¾¤<br />
          * å›½é™…化 SpringMessage Spring标准国际化方案<br />
        </p>
        <p><b>当前版本:</b> <span>v5.0.0</span></p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
        <p>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://javalionli.gitee.io/plus-doc/#/ruoyi-vue-plus/changlog')"
            >更新日志</el-button
          >
        </p>
      </el-col>
            <el-col :sm="24" :lg="12" style="padding-left: 20px">
                <h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
                <p>
                    RuoYi-Cloud-Plus å¾®æœåŠ¡é€šç”¨æƒé™ç®¡ç†ç³»ç»Ÿ é‡å†™ RuoYi-Cloud å…¨æ–¹ä½å‡çº§(不兼容原框架)
                    <br />
                    * å‰ç«¯å¼€å‘框架 Vue3、TS、Element UI<br />
                    * åŽç«¯å¼€å‘框架 Spring Boot<br />
                    * å¾®æœåŠ¡å¼€å‘æ¡†æž¶ Spring Cloud、Spring Cloud Alibaba<br />
                    * å®¹å™¨æ¡†æž¶ Undertow åŸºäºŽ XNIO çš„高性能容器<br />
                    * æƒé™è®¤è¯æ¡†æž¶ Sa-Token、Jwt æ”¯æŒå¤šç»ˆç«¯è®¤è¯ç³»ç»Ÿ<br />
                    * å…³ç³»æ•°æ®åº“ MySQL é€‚配 8.X æœ€ä½Ž 5.7<br />
                    * å…³ç³»æ•°æ®åº“ Oracle é€‚配 11g 12c<br />
                    * å…³ç³»æ•°æ®åº“ PostgreSQL é€‚配 13 14<br />
                    * å…³ç³»æ•°æ®åº“ SQLServer é€‚配 2017 2019<br />
                    * ç¼“存数据库 Redis é€‚配 6.X æœ€ä½Ž 5.X<br />
                    * åˆ†å¸ƒå¼æ³¨å†Œä¸­å¿ƒ Alibaba Nacos é‡‡ç”¨2.X åŸºäºŽGRPC通信高性能<br />
                    * åˆ†å¸ƒå¼é…ç½®ä¸­å¿ƒ Alibaba Nacos é‡‡ç”¨2.X åŸºäºŽGRPC通信高性能<br />
                    * æœåŠ¡ç½‘å…³ Spring Cloud Gateway å“åº”式高性能网关<br />
                    * è´Ÿè½½å‡è¡¡ Spring Cloud Loadbalancer è´Ÿè½½å‡è¡¡å¤„理<br />
                    * RPC远程调用 Apache Dubbo åŽŸç”Ÿæ€ä½¿ç”¨ä½“éªŒã€é«˜æ€§èƒ½<br />
                    * åˆ†å¸ƒå¼é™æµç†”æ–­ Alibaba Sentinel æ— ä¾µå…¥ã€é«˜æ‰©å±•<br />
                    * åˆ†å¸ƒå¼äº‹åŠ¡ Alibaba Seata æ— ä¾µå…¥ã€é«˜æ‰©å±• æ”¯æŒ å››ç§æ¨¡å¼<br />
                    * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— Spring Cloud Stream é—¨é¢æ¡†æž¶å…¼å®¹å„种MQ集成<br />
                    * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— Apache Kafka é«˜æ€§èƒ½é«˜é€Ÿåº¦<br />
                    * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— Apache RocketMQ é«˜å¯ç”¨åŠŸèƒ½å¤šæ ·<br />
                    * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— RabbitMQ æ”¯æŒå„种扩展插件功能多样性<br />
                    * åˆ†å¸ƒå¼æœç´¢å¼•擎 ElasticSearch ä¸šç•ŒçŸ¥å<br />
                    * åˆ†å¸ƒå¼é“¾è·¯è¿½è¸ª Apache SkyWalking é“¾è·¯è¿½è¸ªã€ç½‘格分析、度量聚合、可视化<br />
                    * åˆ†å¸ƒå¼æ—¥å¿—中心 ELK ä¸šç•Œæˆç†Ÿè§£å†³æ–¹æ¡ˆ<br />
                    * åˆ†å¸ƒå¼ç›‘控 Prometheus、Grafana å…¨æ–¹ä½æ€§èƒ½ç›‘控<br />
                    * å…¶ä½™ä¸Ž Vue ç‰ˆæœ¬ä¸€è‡´<br />
                </p>
                <p><b>当前版本:</b> <span>v2.0.0</span></p>
                <p>
                    <el-tag type="danger">&yen;免费开源</el-tag>
                </p>
                <p>
                    <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
                    <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
                    <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://javalionli.gitee.io/plus-doc/#/ruoyi-cloud-plus/changlog')"
                        >更新日志</el-button
                    >
                </p>
            </el-col>
        </el-row>
        <el-divider />
    </div>
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
        <h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
        <p>
          RuoYi-Cloud-Plus å¾®æœåŠ¡é€šç”¨æƒé™ç®¡ç†ç³»ç»Ÿ é‡å†™ RuoYi-Cloud å…¨æ–¹ä½å‡çº§(不兼容原框架)
          <br />
          * å‰ç«¯å¼€å‘框架 Vue3、TS、Element UI<br />
          * åŽç«¯å¼€å‘框架 Spring Boot<br />
          * å¾®æœåŠ¡å¼€å‘æ¡†æž¶ Spring Cloud、Spring Cloud Alibaba<br />
          * å®¹å™¨æ¡†æž¶ Undertow åŸºäºŽ XNIO çš„高性能容器<br />
          * æƒé™è®¤è¯æ¡†æž¶ Sa-Token、Jwt æ”¯æŒå¤šç»ˆç«¯è®¤è¯ç³»ç»Ÿ<br />
          * å…³ç³»æ•°æ®åº“ MySQL é€‚配 8.X æœ€ä½Ž 5.7<br />
          * å…³ç³»æ•°æ®åº“ Oracle é€‚配 11g 12c<br />
          * å…³ç³»æ•°æ®åº“ PostgreSQL é€‚配 13 14<br />
          * å…³ç³»æ•°æ®åº“ SQLServer é€‚配 2017 2019<br />
          * ç¼“存数据库 Redis é€‚配 6.X æœ€ä½Ž 5.X<br />
          * åˆ†å¸ƒå¼æ³¨å†Œä¸­å¿ƒ Alibaba Nacos é‡‡ç”¨2.X åŸºäºŽGRPC通信高性能<br />
          * åˆ†å¸ƒå¼é…ç½®ä¸­å¿ƒ Alibaba Nacos é‡‡ç”¨2.X åŸºäºŽGRPC通信高性能<br />
          * æœåŠ¡ç½‘å…³ Spring Cloud Gateway å“åº”式高性能网关<br />
          * è´Ÿè½½å‡è¡¡ Spring Cloud Loadbalancer è´Ÿè½½å‡è¡¡å¤„理<br />
          * RPC远程调用 Apache Dubbo åŽŸç”Ÿæ€ä½¿ç”¨ä½“éªŒã€é«˜æ€§èƒ½<br />
          * åˆ†å¸ƒå¼é™æµç†”æ–­ Alibaba Sentinel æ— ä¾µå…¥ã€é«˜æ‰©å±•<br />
          * åˆ†å¸ƒå¼äº‹åŠ¡ Alibaba Seata æ— ä¾µå…¥ã€é«˜æ‰©å±• æ”¯æŒ å››ç§æ¨¡å¼<br />
          * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— Spring Cloud Stream é—¨é¢æ¡†æž¶å…¼å®¹å„种MQ集成<br />
          * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— Apache Kafka é«˜æ€§èƒ½é«˜é€Ÿåº¦<br />
          * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— Apache RocketMQ é«˜å¯ç”¨åŠŸèƒ½å¤šæ ·<br />
          * åˆ†å¸ƒå¼æ¶ˆæ¯é˜Ÿåˆ— RabbitMQ æ”¯æŒå„种扩展插件功能多样性<br />
          * åˆ†å¸ƒå¼æœç´¢å¼•擎 ElasticSearch ä¸šç•ŒçŸ¥å<br />
          * åˆ†å¸ƒå¼é“¾è·¯è¿½è¸ª Apache SkyWalking é“¾è·¯è¿½è¸ªã€ç½‘格分析、度量聚合、可视化<br />
          * åˆ†å¸ƒå¼æ—¥å¿—中心 ELK ä¸šç•Œæˆç†Ÿè§£å†³æ–¹æ¡ˆ<br />
          * åˆ†å¸ƒå¼ç›‘控 Prometheus、Grafana å…¨æ–¹ä½æ€§èƒ½ç›‘控<br />
          * å…¶ä½™ä¸Ž Vue ç‰ˆæœ¬ä¸€è‡´<br />
        </p>
        <p><b>当前版本:</b> <span>v2.0.0</span></p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
        <p>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://javalionli.gitee.io/plus-doc/#/ruoyi-cloud-plus/changlog')"
            >更新日志</el-button
          >
        </p>
      </el-col>
    </el-row>
    <el-divider />
  </div>
</template>
<script setup name="Index" lang="ts">
src/views/login.vue
@@ -1,3 +1,49 @@
<template>
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
      <el-form-item prop="tenantId" v-if="tenantEnabled">
        <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
        </el-select>
      </el-form-item>
      <el-form-item prop="username">
        <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleLogin">
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
        <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img" />
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
      <el-form-item style="width:100%;">
        <el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleLogin">
          <span v-if="!loading">登 å½•</span>
          <span v-else>登 å½• ä¸­...</span>
        </el-button>
        <div style="float: right;" v-if="register">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
    </el-form>
    <!--  åº•部  -->
    <div class="el-login-footer">
      <span>Copyright Â© 2018-2023 ç–¯ç‹‚的狮子Li All Rights Reserved.</span>
    </div>
  </div>
</template>
<script setup lang="ts">
import { getCodeImg, getTenantList } from '@/api/login';
import Cookies from 'js-cookie';
@@ -11,19 +57,19 @@
const router = useRouter();
const loginForm = ref<LoginData>({
  tenantId: "000000",
  username: 'admin',
  password: 'admin123',
  rememberMe: false,
  code: '',
  uuid: ''
    tenantId: "000000",
    username: 'admin',
    password: 'admin123',
    rememberMe: false,
    code: '',
    uuid: ''
});
const loginRules: FormRules = {
  tenantId: [{ required: true, trigger: "blur", message: "请输入您的租户编号" }],
  username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
  password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
    tenantId: [{ required: true, trigger: "blur", message: "请输入您的租户编号" }],
    username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
    password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
    code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
};
const codeUrl = ref('');
@@ -42,64 +88,64 @@
const tenantList = ref<TenantVO[]>([]);
const handleLogin = () => {
  loginRef.value.validate(async (valid:boolean, fields: any) => {
    if (valid) {
      loading.value = true;
      // å‹¾é€‰äº†éœ€è¦è®°ä½å¯†ç è®¾ç½®åœ¨ cookie ä¸­è®¾ç½®è®°ä½ç”¨æˆ·åå’Œå¯†ç 
      if (loginForm.value.rememberMe) {
        Cookies.set("tenantId", loginForm.value.tenantId, { expires: 30 });
        Cookies.set('username', loginForm.value.username, { expires: 30 });
        Cookies.set('password', String(encrypt(loginForm.value.password)), { expires: 30 });
        Cookies.set('rememberMe', String(loginForm.value.rememberMe), { expires: 30 });
      } else {
        // å¦åˆ™ç§»é™¤
        Cookies.remove("tenantId");
        Cookies.remove('username');
        Cookies.remove('password');
        Cookies.remove('rememberMe');
      }
      // è°ƒç”¨action的登录方法
      // prittier-ignore
      const [err] = await to(userStore.login(loginForm.value));
      if (!err) {
        await router.push({ path: redirect.value || '/' });
      } else {
        loading.value = false;
        // é‡æ–°èŽ·å–éªŒè¯ç 
        if (captchaEnabled.value) {
          await getCode();
    loginRef.value.validate(async (valid:boolean, fields: any) => {
        if (valid) {
            loading.value = true;
            // å‹¾é€‰äº†éœ€è¦è®°ä½å¯†ç è®¾ç½®åœ¨ cookie ä¸­è®¾ç½®è®°ä½ç”¨æˆ·åå’Œå¯†ç 
            if (loginForm.value.rememberMe) {
                Cookies.set("tenantId", loginForm.value.tenantId, { expires: 30 });
                Cookies.set('username', loginForm.value.username, { expires: 30 });
                Cookies.set('password', String(encrypt(loginForm.value.password)), { expires: 30 });
                Cookies.set('rememberMe', String(loginForm.value.rememberMe), { expires: 30 });
            } else {
                // å¦åˆ™ç§»é™¤
                Cookies.remove("tenantId");
                Cookies.remove('username');
                Cookies.remove('password');
                Cookies.remove('rememberMe');
            }
            // è°ƒç”¨action的登录方法
            // prittier-ignore
            const [err] = await to(userStore.login(loginForm.value));
            if (!err) {
                await router.push({ path: redirect.value || '/' });
            } else {
                loading.value = false;
                // é‡æ–°èŽ·å–éªŒè¯ç 
                if (captchaEnabled.value) {
                    await getCode();
                }
            }
        } else {
            console.log('error submit!', fields);
        }
      }
    } else {
      console.log('error submit!', fields);
    }
  });
    });
};
/**
 * èŽ·å–éªŒè¯ç 
 */
const getCode = async () => {
  const res = await getCodeImg();
  const { data } = res;
  captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
  if (captchaEnabled.value) {
    codeUrl.value = 'data:image/gif;base64,' + data.img;
    loginForm.value.uuid = data.uuid;
  }
    const res = await getCodeImg();
    const { data } = res;
    captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
    if (captchaEnabled.value) {
        codeUrl.value = 'data:image/gif;base64,' + data.img;
        loginForm.value.uuid = data.uuid;
    }
};
const getCookie = () => {
  const tenantId = Cookies.get("tenantId");
  const username = Cookies.get('username');
  const password = Cookies.get('password');
  const rememberMe = Cookies.get('rememberMe');
  loginForm.value = {
    tenantId: tenantId === undefined ? loginForm.value.tenantId : tenantId,
    username: username === undefined ? loginForm.value.username : username,
    password: password === undefined ? loginForm.value.password : (decrypt(password) as string),
    rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
  };
    const tenantId = Cookies.get("tenantId");
    const username = Cookies.get('username');
    const password = Cookies.get('password');
    const rememberMe = Cookies.get('rememberMe');
    loginForm.value = {
        tenantId: tenantId === undefined ? loginForm.value.tenantId : tenantId,
        username: username === undefined ? loginForm.value.username : username,
        password: password === undefined ? loginForm.value.password : (decrypt(password) as string),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
    };
}
@@ -107,68 +153,22 @@
 * èŽ·å–ç§Ÿæˆ·åˆ—è¡¨
 */
const initTenantList = async () => {
  const { data } = await getTenantList();
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
    if (tenantList.value != null && tenantList.value.length !== 0) {
      loginForm.value.tenantId = tenantList.value[0].tenantId;
    const { data } = await getTenantList();
    tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
    if (tenantEnabled.value) {
        tenantList.value = data.voList;
        if (tenantList.value != null && tenantList.value.length !== 0) {
            loginForm.value.tenantId = tenantList.value[0].tenantId;
        }
    }
  }
}
onMounted(() => {
  getCode();
  initTenantList();
  getCookie();
    getCode();
    initTenantList();
    getCookie();
});
</script>
<template>
    <div class="login">
        <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
            <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
            <el-form-item prop="tenantId" v-if="tenantEnabled">
                <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
                    <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
                    <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
                </el-select>
            </el-form-item>
            <el-form-item prop="username">
                <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
                    <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
                </el-input>
            </el-form-item>
            <el-form-item prop="password">
                <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleLogin">
                    <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
                </el-input>
            </el-form-item>
            <el-form-item prop="code" v-if="captchaEnabled">
                <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
                    <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
                </el-input>
                <div class="login-code">
                    <img :src="codeUrl" @click="getCode" class="login-code-img" />
                </div>
            </el-form-item>
            <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
            <el-form-item style="width:100%;">
                <el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleLogin">
                    <span v-if="!loading">登 å½•</span>
                    <span v-else>登 å½• ä¸­...</span>
                </el-button>
                <div style="float: right;" v-if="register">
                    <router-link class="link-type" :to="'/register'">立即注册</router-link>
                </div>
            </el-form-item>
        </el-form>
        <!--  åº•部  -->
        <div class="el-login-footer">
            <span>Copyright Â© 2018-2023 ç–¯ç‹‚的狮子Li All Rights Reserved.</span>
        </div>
    </div>
</template>
<style lang="scss" scoped>
.login {
src/views/monitor/admin/index.vue
@@ -1,7 +1,7 @@
<template>
    <div>
        <i-frame v-model:src="url"></i-frame>
    </div>
  <div>
    <i-frame v-model:src="url"></i-frame>
  </div>
</template>
<script setup lang="ts">
src/views/monitor/cache/index.vue
@@ -9,180 +9,180 @@
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const getList = async () => {
    proxy?.$modal.loading("正在加载缓存监控数据,请稍候!");
    const res = await getCache();
    proxy?.$modal.closeLoading();
    cache.value = res.data;
    const commandstatsIntance = echarts.init(commandstats.value, "macarons");
    commandstatsIntance.setOption({
        tooltip: {
            trigger: "item",
            formatter: "{a} <br/>{b} : {c} ({d}%)"
        },
        series: [
            {
                name: "命令",
                type: "pie",
                roseType: "radius",
                radius: [15, 95],
                center: ["50%", "38%"],
                data: res.data.commandStats,
                animationEasing: "cubicInOut",
                animationDuration: 1000
            }
        ]
    });
  proxy?.$modal.loading("正在加载缓存监控数据,请稍候!");
  const res = await getCache();
  proxy?.$modal.closeLoading();
  cache.value = res.data;
  const commandstatsIntance = echarts.init(commandstats.value, "macarons");
  commandstatsIntance.setOption({
    tooltip: {
      trigger: "item",
      formatter: "{a} <br/>{b} : {c} ({d}%)"
    },
    series: [
      {
        name: "命令",
        type: "pie",
        roseType: "radius",
        radius: [15, 95],
        center: ["50%", "38%"],
        data: res.data.commandStats,
        animationEasing: "cubicInOut",
        animationDuration: 1000
      }
    ]
  });
    const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
    usedmemoryInstance.setOption({
        tooltip: {
            formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
        },
        series: [
            {
                name: "峰值",
                type: "gauge",
                min: 0,
                max: 1000,
                detail: {
                    formatter: cache.value.info.used_memory_human
                },
                data: [
                    {
                        value: parseFloat(cache.value.info.used_memory_human),
                        name: "内存消耗"
                    }
                ]
            }
        ]
    })
  const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
  usedmemoryInstance.setOption({
    tooltip: {
      formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
    },
    series: [
      {
        name: "峰值",
        type: "gauge",
        min: 0,
        max: 1000,
        detail: {
          formatter: cache.value.info.used_memory_human
        },
        data: [
          {
            value: parseFloat(cache.value.info.used_memory_human),
            name: "内存消耗"
          }
        ]
      }
    ]
  })
}
onMounted(() => {
    getList();
  getList();
})
</script>
<template>
    <div class="p-2">
        <el-row>
            <el-col :span="24" class="card-box">
                <el-card>
                    <template #header>
                        <Monitor style="width: 1em; height: 1em; vertical-align: middle;" />
                        <span style="vertical-align: middle;">基本信息</span>
                    </template>
  <div class="p-2">
    <el-row>
      <el-col :span="24" class="card-box">
        <el-card>
          <template #header>
            <Monitor style="width: 1em; height: 1em; vertical-align: middle;" />
            <span style="vertical-align: middle;">基本信息</span>
          </template>
                    <div class="el-table el-table--enable-row-hover el-table--medium">
                        <table style="width: 100%">
                            <tbody>
                                <tr>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">Redis版本</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">运行模式</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.redis_mode === "standalone" ? "单机" : "集群" }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">端口</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">客户端数</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div>
                                    </td>
                                </tr>
                                <tr>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">运行时间(天)</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">使用内存</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">使用CPU</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">内存配置</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div>
                                    </td>
                                </tr>
                                <tr>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">AOF是否开启</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.aof_enabled === "0" ? "否" : "是" }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">RDB是否成功</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">Key数量</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.dbSize">{{ cache.dbSize }}</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell">网络入口/出口</div>
                                    </td>
                                    <td class="el-table__cell is-leaf">
                                        <div class="cell" v-if="cache.info">
                                            {{ cache.info.instantaneous_input_kbps }}kps/{{ cache.info.instantaneous_output_kbps }}kps
                                        </div>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </el-card>
            </el-col>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table style="width: 100%">
              <tbody>
                <tr>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">Redis版本</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">运行模式</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.redis_mode === "standalone" ? "单机" : "集群" }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">端口</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">客户端数</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div>
                  </td>
                </tr>
                <tr>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">运行时间(天)</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">使用内存</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">使用CPU</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">内存配置</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div>
                  </td>
                </tr>
                <tr>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">AOF是否开启</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.aof_enabled === "0" ? "否" : "是" }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">RDB是否成功</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">Key数量</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.dbSize">{{ cache.dbSize }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">网络入口/出口</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">
                      {{ cache.info.instantaneous_input_kbps }}kps/{{ cache.info.instantaneous_output_kbps }}kps
                    </div>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </el-card>
      </el-col>
            <el-col :span="12" class="card-box">
                <el-card>
                    <template #header>
                        <PieChart style="width: 1em; height: 1em; vertical-align: middle;" />
                        <span style="vertical-align: middle;">命令统计</span>
                    </template>
                    <div class="el-table el-table--enable-row-hover el-table--medium">
                        <div ref="commandstats" style="height: 420px" />
                    </div>
                </el-card>
            </el-col>
      <el-col :span="12" class="card-box">
        <el-card>
          <template #header>
            <PieChart style="width: 1em; height: 1em; vertical-align: middle;" />
            <span style="vertical-align: middle;">命令统计</span>
          </template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <div ref="commandstats" style="height: 420px" />
          </div>
        </el-card>
      </el-col>
            <el-col :span="12" class="card-box">
                <el-card>
                    <template #header>
                        <Odometer style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">内存信息</span>
                    </template>
                    <div class="el-table el-table--enable-row-hover el-table--medium">
                        <div ref="usedmemory" style="height: 420px" />
                    </div>
                </el-card>
            </el-col>
        </el-row>
    </div>
      <el-col :span="12" class="card-box">
        <el-card>
          <template #header>
            <Odometer style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">内存信息</span>
          </template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <div ref="usedmemory" style="height: 420px" />
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
src/views/monitor/logininfor/index.vue
@@ -1,3 +1,101 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="登录地址" prop="ipaddr">
            <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 240px;" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="用户名称" prop="userName">
            <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px;" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="登录状态" clearable style="width: 240px">
              <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="登录时间" style="width: 308px">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['monitor:logininfor:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" @click="handleClean" v-hasPermi="['monitor:logininfor:remove']">清空</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock" v-hasPermi="['monitor:logininfor:unlock']">
              è§£é”
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['monitor:logininfor:export']">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        ref="loginInfoTableRef"
        v-loading="loading"
        :data="loginInfoList"
        @selection-change="handleSelectionChange"
        :default-sort="defaultSort"
        @sort-change="handleSortChange"
      >
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="访问编号" align="center" prop="infoId" />
        <el-table-column
          label="用户名称"
          align="center"
          prop="userName"
          :show-overflow-tooltip="true"
          sortable="custom"
          :sort-orders="['descending', 'ascending']"
        />
        <el-table-column label="地址" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
        <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
        <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
        <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
        <el-table-column label="登录状态" align="center" prop="status">
          <template #default="scope">
            <dict-tag :options="sys_common_status" :value="scope.row.status" />
          </template>
        </el-table-column>
        <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
        <el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.loginTime) }}</span>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Logininfor" lang="ts">
import { list, delLoginInfo, cleanLoginInfo, unlockLoginInfo } from "@/api/monitor/loginInfo";
import { ComponentInternalInstance } from "vue";
@@ -22,176 +120,78 @@
const loginInfoTableRef = ref(ElTable);
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<LoginInfoQuery>({
  pageNum: 1,
  pageSize: 10,
  ipaddr: '',
  userName: '',
  status: '',
  orderByColumn: defaultSort.value.prop,
  isAsc: defaultSort.value.order
    pageNum: 1,
    pageSize: 10,
    ipaddr: '',
    userName: '',
    status: '',
    orderByColumn: defaultSort.value.prop,
    isAsc: defaultSort.value.order
});
/** æŸ¥è¯¢ç™»å½•日志列表 */
const getList = async () => {
  loading.value = true;
    const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
    loginInfoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
    loading.value = true;
    const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
    loginInfoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value.resetFields();
  queryParams.value.pageNum = 1;
  loginInfoTableRef.value.sort(defaultSort.value.prop, defaultSort.value.order);
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    queryParams.value.pageNum = 1;
    loginInfoTableRef.value.sort(defaultSort.value.prop, defaultSort.value.order);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: LoginInfoVO[]) => {
  ids.value = selection.map(item => item.infoId);
  multiple.value = !selection.length;
  single.value = selection.length != 1;
  selectName.value = selection.map(item => item.userName);
    ids.value = selection.map(item => item.infoId);
    multiple.value = !selection.length;
    single.value = selection.length != 1;
    selectName.value = selection.map(item => item.userName);
}
/** æŽ’序触发事件 */
const handleSortChange = (column: any) => {
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
    queryParams.value.orderByColumn = column.prop;
    queryParams.value.isAsc = column.order;
    getList();
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: LoginInfoVO) => {
  const infoIds = row?.infoId || ids.value;
    await proxy?.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?');
    await delLoginInfo(infoIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    const infoIds = row?.infoId || ids.value;
    await proxy?.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?');
    await delLoginInfo(infoIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
/** æ¸…空按钮操作 */
const handleClean = async () => {
    await proxy?.$modal.confirm("是否确认清空所有登录日志数据项?");
    await cleanLoginInfo();
    getList();
    proxy?.$modal.msgSuccess("清空成功");
    await proxy?.$modal.confirm("是否确认清空所有登录日志数据项?");
    await cleanLoginInfo();
    getList();
    proxy?.$modal.msgSuccess("清空成功");
}
/** è§£é”æŒ‰é’®æ“ä½œ */
const handleUnlock = async () => {
  const username = selectName.value;
    await proxy?.$modal.confirm('是否确认解锁用户"' + username + '"数据项?');
    await unlockLoginInfo(username);
    proxy?.$modal.msgSuccess("用户" + username + "解锁成功");
    const username = selectName.value;
    await proxy?.$modal.confirm('是否确认解锁用户"' + username + '"数据项?');
    await unlockLoginInfo(username);
    proxy?.$modal.msgSuccess("用户" + username + "解锁成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("monitor/logininfor/export", {
    ...queryParams.value,
  }, `config_${new Date().getTime()}.xlsx`);
    proxy?.download("monitor/logininfor/export", {
        ...queryParams.value,
    }, `config_${new Date().getTime()}.xlsx`);
}
onMounted(() => {
  getList();
    getList();
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="登录地址" prop="ipaddr">
                        <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 240px;" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="用户名称" prop="userName">
                        <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px;" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="状态" prop="status">
                        <el-select v-model="queryParams.status" placeholder="登录状态" clearable style="width: 240px">
                            <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="登录时间" style="width: 308px">
                        <el-date-picker
                            v-model="dateRange"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="daterange"
                            range-separator="-"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
                        ></el-date-picker>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['monitor:logininfor:remove']">
                            åˆ é™¤
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" @click="handleClean" v-hasPermi="['monitor:logininfor:remove']">清空</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock" v-hasPermi="['monitor:logininfor:unlock']">
                            è§£é”
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['monitor:logininfor:export']">导出</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table
                ref="loginInfoTableRef"
                v-loading="loading"
                :data="loginInfoList"
                @selection-change="handleSelectionChange"
                :default-sort="defaultSort"
                @sort-change="handleSortChange"
            >
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column label="访问编号" align="center" prop="infoId" />
                <el-table-column
                    label="用户名称"
                    align="center"
                    prop="userName"
                    :show-overflow-tooltip="true"
                    sortable="custom"
                    :sort-orders="['descending', 'ascending']"
                />
                <el-table-column label="地址" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
                <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
                <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
                <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
                <el-table-column label="登录状态" align="center" prop="status">
                    <template #default="scope">
                        <dict-tag :options="sys_common_status" :value="scope.row.status" />
                    </template>
                </el-table-column>
                <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
                <el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.loginTime) }}</span>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        </el-card>
    </div>
</template>
src/views/monitor/online/index.vue
@@ -1,3 +1,57 @@
<template>
  <div class="p-2">
    <div class="search">
      <el-form :model="queryParams" ref="queryFormRef" :inline="true">
        <el-form-item label="登录地址" prop="ipaddr">
          <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 200px" @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item label="用户名称" prop="userName">
          <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="panel">
      <el-table
        v-loading="loading"
        :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
        style="width: 100%;"
      >
        <el-table-column label="序号" width="50" type="index" align="center">
          <template #default="scope">
            <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
          </template>
        </el-table-column>
        <el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
        <el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
        <el-table-column label="所属部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
        <el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
        <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
        <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
        <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
        <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.loginTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="强退" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleForceLogout(scope.row)" v-hasPermi="['monitor:online:forceLogout']">
              </el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" />
    </div>
  </div>
</template>
<script setup name="Online" lang="ts">
import { forceLogout, list as initData } from "@/api/monitor/online";
import { ComponentInternalInstance } from "vue";
@@ -12,93 +66,39 @@
const queryFormRef = ref(ElForm);
const queryParams = ref<OnlineQuery>({
    pageNum: 1,
    pageSize: 10,
    ipaddr: '',
    userName: ''
    pageNum: 1,
    pageSize: 10,
    ipaddr: '',
    userName: ''
});
/** æŸ¥è¯¢ç™»å½•日志列表 */
const getList = async () => {
    loading.value = true;
    const res = await initData(queryParams.value);
    onlineList.value = res.rows;
    total.value = res.total;
    loading.value = false;
    loading.value = true;
    const res = await initData(queryParams.value);
    onlineList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
    queryFormRef.value.resetFields();
    handleQuery();
}
/** å¼ºé€€æŒ‰é’®æ“ä½œ */
const handleForceLogout = async (row: OnlineVO) => {
    await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?');
    await forceLogout(row.tokenId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?');
    await forceLogout(row.tokenId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
    getList();
})
</script>
<template>
    <div class="p-2">
        <div class="search">
            <el-form :model="queryParams" ref="queryFormRef" :inline="true">
                <el-form-item label="登录地址" prop="ipaddr">
                    <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 200px" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="用户名称" prop="userName">
                    <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                    <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                </el-form-item>
            </el-form>
        </div>
        <div class="panel">
            <el-table
                v-loading="loading"
                :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
                style="width: 100%;"
            >
                <el-table-column label="序号" width="50" type="index" align="center">
                    <template #default="scope">
                        <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
                <el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
                <el-table-column label="所属部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
                <el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
                <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
                <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
                <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
                <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.loginTime) }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="强退" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleForceLogout(scope.row)" v-hasPermi="['monitor:online:forceLogout']">
                            </el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" />
        </div>
    </div>
</template>
src/views/monitor/operlog/index.vue
@@ -1,3 +1,168 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="系统模块" prop="title">
            <el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px;" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="操作人员" prop="operName">
            <el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable style="width: 240px;" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="类型" prop="businessType">
            <el-select v-model="queryParams.businessType" placeholder="操作类型" clearable style="width: 240px">
              <el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="操作状态" clearable style="width: 240px">
              <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="操作时间" style="width: 308px">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['monitor:operlog:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="WarnTriangleFilled" @click="handleClean" v-hasPermi="['monitor:operlog:remove']">清空</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['monitor:operlog:export']">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        ref="operLogTableRef"
        v-loading="loading"
        :data="operlogList"
        @selection-change="handleSelectionChange"
        :default-sort="defaultSort"
        @sort-change="handleSortChange"
      >
        <el-table-column type="selection" width="50" align="center" />
        <el-table-column label="日志编号" align="center" prop="operId" />
        <el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
        <el-table-column label="操作类型" align="center" prop="businessType">
          <template #default="scope">
            <dict-tag :options="sys_oper_type" :value="scope.row.businessType" />
          </template>
        </el-table-column>
        <el-table-column
          label="操作人员"
          align="center"
          width="110"
          prop="operName"
          :show-overflow-tooltip="true"
          sortable="custom"
          :sort-orders="['descending', 'ascending']"
        />
        <el-table-column label="主机" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
        <el-table-column label="操作状态" align="center" prop="status">
          <template #default="scope">
            <dict-tag :options="sys_common_status" :value="scope.row.status" />
          </template>
        </el-table-column>
        <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
          <template #default="scope">
            <span>{{ parseTime(scope.row.operTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          label="消耗时间"
          align="center"
          prop="costTime"
          width="110"
          :show-overflow-tooltip="true"
          sortable="custom"
          :sort-orders="['descending', 'ascending']"
        >
          <template #default="scope">
            <span>{{ scope.row.costTime }}毫秒</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="详细" placement="top">
              <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:operlog:query']"> </el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ“ä½œæ—¥å¿—详细 -->
    <el-dialog title="操作日志详细" v-model="dialog.visible" width="700px" append-to-body>
      <el-form :model="form" label-width="100px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
            <el-form-item label="登录信息:">{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item>
            <el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="操作方法:">{{ form.method }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="请求参数:">{{form.operParam}}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="操作状态:">
              <div v-if="form.status === 0">正常</div>
              <div v-else-if="form.status === 1">失败</div>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="消耗时间:">{{ form.costTime }}毫秒</el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialog.visible = false">关 é—­</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Operlog" lang="ts">
import { list, delOperlog, cleanOperlog } from '@/api/monitor/operlog';
import { ComponentInternalInstance } from 'vue';
@@ -20,276 +185,112 @@
const queryFormRef = ref(ElForm);
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
    visible: false,
    title: ''
});
const data = reactive<PageData<OperLogForm, OperLogQuery>>({
  form: {
    operId: undefined,
    tenantId: undefined,
    title: '',
    businessType: 0,
    businessTypes: undefined,
    method: '',
    requestMethod: '',
    operatorType: 0,
    operName: '',
    deptName: '',
    operUrl: '',
    operIp: '',
    operLocation: '',
    operParam: '',
    jsonResult: '',
    status: 0,
    errorMsg: '',
    operTime: '',
    costTime: 0
  },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    title: '',
    operName: '',
    businessType: '',
    status: '',
    orderByColumn: defaultSort.value.prop,
    isAsc: defaultSort.value.order
  },
  rules: {}
    form: {
        operId: undefined,
        tenantId: undefined,
        title: '',
        businessType: 0,
        businessTypes: undefined,
        method: '',
        requestMethod: '',
        operatorType: 0,
        operName: '',
        deptName: '',
        operUrl: '',
        operIp: '',
        operLocation: '',
        operParam: '',
        jsonResult: '',
        status: 0,
        errorMsg: '',
        operTime: '',
        costTime: 0
    },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        title: '',
        operName: '',
        businessType: '',
        status: '',
        orderByColumn: defaultSort.value.prop,
        isAsc: defaultSort.value.order
    },
    rules: {}
});
const { queryParams, form } = toRefs(data);
/** æŸ¥è¯¢ç™»å½•日志 */
const getList = async () => {
  loading.value = true;
    const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
    operlogList.value = res.rows;
    total.value = res.total;
    loading.value = false;
    loading.value = true;
    const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
    operlogList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
/** æ“ä½œæ—¥å¿—类型字典翻译 */
const typeFormat = (row: OperLogForm) => {
  return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
    return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value.resetFields();
  queryParams.value.pageNum = 1;
  operLogTableRef.value.sort(defaultSort.value.prop, defaultSort.value.order);
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    queryParams.value.pageNum = 1;
    operLogTableRef.value.sort(defaultSort.value.prop, defaultSort.value.order);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: OperLogVO[]) => {
  ids.value = selection.map(item => item.operId);
  multiple.value = !selection.length;
    ids.value = selection.map(item => item.operId);
    multiple.value = !selection.length;
}
/** æŽ’序触发事件 */
const handleSortChange = (column: any) => {
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
    queryParams.value.orderByColumn = column.prop;
    queryParams.value.isAsc = column.order;
    getList();
}
/** è¯¦ç»†æŒ‰é’®æ“ä½œ */
const handleView = (row: OperLogVO) => {
  dialog.visible = true;
  form.value = row;
    dialog.visible = true;
    form.value = row;
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: OperLogVO) => {
  const operIds = row?.operId || ids.value;
    await proxy?.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?');
    await delOperlog(operIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    const operIds = row?.operId || ids.value;
    await proxy?.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?');
    await delOperlog(operIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
/** æ¸…空按钮操作 */
const handleClean = async () => {
    await proxy?.$modal.confirm("是否确认清空所有操作日志数据项?");
    await cleanOperlog();
    getList();
    proxy?.$modal.msgSuccess("清空成功");
    await proxy?.$modal.confirm("是否确认清空所有操作日志数据项?");
    await cleanOperlog();
    getList();
    proxy?.$modal.msgSuccess("清空成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("monitor/operlog/export", {
    ...queryParams.value,
  }, `config_${new Date().getTime()}.xlsx`);
    proxy?.download("monitor/operlog/export", {
        ...queryParams.value,
    }, `config_${new Date().getTime()}.xlsx`);
}
onMounted(() => {
  getList();
    getList();
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="系统模块" prop="title">
                        <el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px;" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="操作人员" prop="operName">
                        <el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable style="width: 240px;" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="类型" prop="businessType">
                        <el-select v-model="queryParams.businessType" placeholder="操作类型" clearable style="width: 240px">
                            <el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="状态" prop="status">
                        <el-select v-model="queryParams.status" placeholder="操作状态" clearable style="width: 240px">
                            <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="操作时间" style="width: 308px">
                        <el-date-picker
                            v-model="dateRange"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="daterange"
                            range-separator="-"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
                        ></el-date-picker>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['monitor:operlog:remove']">
                            åˆ é™¤
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="WarnTriangleFilled" @click="handleClean" v-hasPermi="['monitor:operlog:remove']">清空</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['monitor:operlog:export']">导出</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table
                ref="operLogTableRef"
                v-loading="loading"
                :data="operlogList"
                @selection-change="handleSelectionChange"
                :default-sort="defaultSort"
                @sort-change="handleSortChange"
            >
                <el-table-column type="selection" width="50" align="center" />
                <el-table-column label="日志编号" align="center" prop="operId" />
                <el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
                <el-table-column label="操作类型" align="center" prop="businessType">
                    <template #default="scope">
                        <dict-tag :options="sys_oper_type" :value="scope.row.businessType" />
                    </template>
                </el-table-column>
                <el-table-column
                    label="操作人员"
                    align="center"
                    width="110"
                    prop="operName"
                    :show-overflow-tooltip="true"
                    sortable="custom"
                    :sort-orders="['descending', 'ascending']"
                />
                <el-table-column label="主机" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
                <el-table-column label="操作状态" align="center" prop="status">
                    <template #default="scope">
                        <dict-tag :options="sys_common_status" :value="scope.row.status" />
                    </template>
                </el-table-column>
                <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.operTime) }}</span>
                    </template>
                </el-table-column>
                <el-table-column
                    label="消耗时间"
                    align="center"
                    prop="costTime"
                    width="110"
                    :show-overflow-tooltip="true"
                    sortable="custom"
                    :sort-orders="['descending', 'ascending']"
                >
                    <template #default="scope">
                        <span>{{ scope.row.costTime }}毫秒</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="详细" placement="top">
                            <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:operlog:query']"> </el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        </el-card>
        <!-- æ“ä½œæ—¥å¿—详细 -->
        <el-dialog title="操作日志详细" v-model="dialog.visible" width="700px" append-to-body>
            <el-form :model="form" label-width="100px">
                <el-row>
                    <el-col :span="12">
                        <el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
                        <el-form-item label="登录信息:">{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item>
                        <el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item label="操作方法:">{{ form.method }}</el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item label="请求参数:">{{form.operParam}}</el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
                    </el-col>
                    <el-col :span="6">
                        <el-form-item label="操作状态:">
                            <div v-if="form.status === 0">正常</div>
                            <div v-else-if="form.status === 1">失败</div>
                        </el-form-item>
                    </el-col>
                    <el-col :span="8">
                        <el-form-item label="消耗时间:">{{ form.costTime }}毫秒</el-form-item>
                    </el-col>
                    <el-col :span="10">
                        <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
                    </el-col>
                    <el-col :span="24">
                        <el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="dialog.visible = false">关 é—­</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
src/views/monitor/xxljob/index.vue
@@ -1,7 +1,7 @@
<template>
    <div>
        <i-frame v-model:src="url"></i-frame>
    </div>
  <div>
    <i-frame v-model:src="url"></i-frame>
  </div>
</template>
<script setup lang="ts">
src/views/redirect/index.vue
@@ -1,5 +1,5 @@
<template>
    <div></div>
  <div></div>
</template>
<script setup>
src/views/register.vue
@@ -1,3 +1,60 @@
<template>
  <div class="register">
    <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
      <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
      <el-form-item prop="tenantId" v-if="tenantEnabled">
        <el-select v-model="registerForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
        </el-select>
      </el-form-item>
      <el-form-item prop="username">
        <el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="registerForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleRegister">
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="confirmPassword">
        <el-input
          v-model="registerForm.confirmPassword"
          type="password"
          size="large"
          auto-complete="off"
          placeholder="确认密码"
          @keyup.enter="handleRegister"
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
        <el-input size="large" v-model="registerForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleRegister">
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="register-code">
          <img :src="codeUrl" @click="getCode" class="register-code-img" />
        </div>
      </el-form-item>
      <el-form-item style="width:100%;">
        <el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleRegister">
          <span v-if="!loading">注 å†Œ</span>
          <span v-else>注 å†Œ ä¸­...</span>
        </el-button>
        <div style="float: right;">
          <router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
        </div>
      </el-form-item>
    </el-form>
    <!--  åº•部  -->
    <div class="el-register-footer">
      <span>Copyright Â© 2018-2023 ç–¯ç‹‚的狮子Li All Rights Reserved.</span>
    </div>
  </div>
</template>
<script setup lang="ts">
import { getCodeImg, register, getTenantList } from '@/api/login';
import { RegisterForm, TenantVO } from '@/api/types';
@@ -7,13 +64,13 @@
const router = useRouter();
const registerForm = ref<RegisterForm>({
  tenantId: "",
  username: "",
  password: "",
  confirmPassword: "",
  code: "",
  uuid: "",
  userType: "sys_user"
    tenantId: "",
    username: "",
    password: "",
    confirmPassword: "",
    code: "",
    uuid: "",
    userType: "sys_user"
});
// ç§Ÿæˆ·å¼€å…³
@@ -21,30 +78,30 @@
const equalToPassword = (rule: any, value: string, callback: any) => {
  if (registerForm.value.password !== value) {
    callback(new Error("两次输入的密码不一致"));
  } else {
    callback();
  }
    if (registerForm.value.password !== value) {
        callback(new Error("两次输入的密码不一致"));
    } else {
        callback();
    }
};
const registerRules: FormRules = {
  tenantId: [
    { required: true, trigger: "blur", message: "请输入您的租户编号" }
  ],
  username: [
    { required: true, trigger: "blur", message: "请输入您的账号" },
    { min: 2, max: 20, message: "用户账号长度必须介于 2 å’Œ 20 ä¹‹é—´", trigger: "blur" }
  ],
  password: [
    { required: true, trigger: "blur", message: "请输入您的密码" },
    { min: 5, max: 20, message: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´", trigger: "blur" }
  ],
  confirmPassword: [
    { required: true, trigger: "blur", message: "请再次输入您的密码" },
    { required: true, validator: equalToPassword, trigger: "blur" }
  ],
  code: [{ required: true, trigger: "change", message: "请输入验证码" }]
    tenantId: [
        { required: true, trigger: "blur", message: "请输入您的租户编号" }
    ],
    username: [
        { required: true, trigger: "blur", message: "请输入您的账号" },
        { min: 2, max: 20, message: "用户账号长度必须介于 2 å’Œ 20 ä¹‹é—´", trigger: "blur" }
    ],
    password: [
        { required: true, trigger: "blur", message: "请输入您的密码" },
        { min: 5, max: 20, message: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´", trigger: "blur" }
    ],
    confirmPassword: [
        { required: true, trigger: "blur", message: "请再次输入您的密码" },
        { required: true, validator: equalToPassword, trigger: "blur" }
    ],
    code: [{ required: true, trigger: "change", message: "请输入验证码" }]
};
const codeUrl = ref("");
const loading = ref(false);
@@ -54,109 +111,52 @@
const tenantList = ref<TenantVO[]>([]);
const handleRegister = () => {
  registerRef.value.validate(async (valid: boolean) => {
    if (valid) {
      loading.value = true;
      const [err] = await to(register(registerForm.value));
      if (!err) {
        const username = registerForm.value.username;
        await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " æ³¨å†ŒæˆåŠŸï¼</font>", "系统提示", {
          dangerouslyUseHTMLString: true,
          type: "success",
        });
        await router.push("/login");
      } else {
        loading.value = false;
        if (captchaEnabled) {
          getCode();
    registerRef.value.validate(async (valid: boolean) => {
        if (valid) {
            loading.value = true;
            const [err] = await to(register(registerForm.value));
            if (!err) {
                const username = registerForm.value.username;
                await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " æ³¨å†ŒæˆåŠŸï¼</font>", "系统提示", {
                    dangerouslyUseHTMLString: true,
                    type: "success",
                });
                await router.push("/login");
            } else {
                loading.value = false;
                if (captchaEnabled) {
                    getCode();
                }
            }
        }
      }
    }
  });
    });
}
const getCode = async () => {
  const { data } = await getCodeImg();
  captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
    const { data } = await getCodeImg();
    captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
    if (captchaEnabled.value) {
      codeUrl.value = "data:image/gif;base64," + data.img;
      registerForm.value.uuid = data.uuid;
        codeUrl.value = "data:image/gif;base64," + data.img;
        registerForm.value.uuid = data.uuid;
    }
}
const initTenantList = async () => {
  const { data } = await getTenantList();
    const { data } = await getTenantList();
    tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
    if (tenantEnabled.value) {
      tenantList.value = data.voList;
      if (tenantList.value != null && tenantList.value.length !== 0) {
          registerForm.value.tenantId = tenantList.value[0].tenantId;
      }
        tenantList.value = data.voList;
        if (tenantList.value != null && tenantList.value.length !== 0) {
            registerForm.value.tenantId = tenantList.value[0].tenantId;
        }
    }
}
onMounted(() => {
  getCode();
  initTenantList();
    getCode();
    initTenantList();
})
</script>
<template>
    <div class="register">
        <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
            <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
            <el-form-item prop="tenantId" v-if="tenantEnabled">
                <el-select v-model="registerForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
                    <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
                    <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
                </el-select>
            </el-form-item>
            <el-form-item prop="username">
                <el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
                    <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
                </el-input>
            </el-form-item>
            <el-form-item prop="password">
                <el-input v-model="registerForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleRegister">
                    <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
                </el-input>
            </el-form-item>
            <el-form-item prop="confirmPassword">
                <el-input
                    v-model="registerForm.confirmPassword"
                    type="password"
                    size="large"
                    auto-complete="off"
                    placeholder="确认密码"
                    @keyup.enter="handleRegister"
                >
                    <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
                </el-input>
            </el-form-item>
            <el-form-item prop="code" v-if="captchaEnabled">
                <el-input size="large" v-model="registerForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleRegister">
                    <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
                </el-input>
                <div class="register-code">
                    <img :src="codeUrl" @click="getCode" class="register-code-img" />
                </div>
            </el-form-item>
            <el-form-item style="width:100%;">
                <el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleRegister">
                    <span v-if="!loading">注 å†Œ</span>
                    <span v-else>注 å†Œ ä¸­...</span>
                </el-button>
                <div style="float: right;">
                    <router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
                </div>
            </el-form-item>
        </el-form>
        <!--  åº•部  -->
        <div class="el-register-footer">
            <span>Copyright Â© 2018-2023 ç–¯ç‹‚的狮子Li All Rights Reserved.</span>
        </div>
    </div>
</template>
<style lang="scss" scoped>
.register {
src/views/system/config/index.vue
@@ -51,10 +51,10 @@
/** æŸ¥è¯¢å‚数列表 */
const getList = async () => {
  loading.value = true;
    const res = await listConfig(proxy?.addDateRange(queryParams.value, dateRange.value));
    configList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  const res = await listConfig(proxy?.addDateRange(queryParams.value, dateRange.value));
  configList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
@@ -63,7 +63,7 @@
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = {...initFormData};
  form.value = {...initFormData};
  configFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -93,33 +93,33 @@
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: ConfigVO) => {
    dialog.visible = true;
    dialog.title = "修改参数";
    const configId = row?.configId || ids.value[0];
  dialog.visible = true;
  dialog.title = "修改参数";
  const configId = row?.configId || ids.value[0];
  nextTick(async () => {
        reset();
        const res = await getConfig(configId);
        form.value = res.data;
    })
    reset();
    const res = await getConfig(configId);
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  configFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
    if (valid) {
      form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      getList();
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: ConfigVO) => {
  const configIds = row?.configId || ids.value;
    await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
    await delConfig(configIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
  await delConfig(configIds);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
@@ -129,133 +129,133 @@
}
/** åˆ·æ–°ç¼“存按钮操作 */
const handleRefreshCache = async () => {
    await refreshCache();
    proxy?.$modal.msgSuccess("刷新缓存成功");
  await refreshCache();
  proxy?.$modal.msgSuccess("刷新缓存成功");
}
onMounted(() => {
    getList();
  getList();
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="参数名称" prop="configName">
                        <el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="参数键名" prop="configKey">
                        <el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable style="width: 240px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="系统内置" prop="configType">
                        <el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
                            <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="创建时间" style="width: 308px;">
                        <el-date-picker
                            v-model="dateRange"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="daterange"
                            range-separator="-"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
                        ></el-date-picker>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:config:add']">新增</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:config:edit']">
                            ä¿®æ”¹
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:config:remove']">
                            åˆ é™¤
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:config:export']">导出</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:config:remove']">刷新缓存</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="参数名称" prop="configName">
            <el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="参数键名" prop="configKey">
            <el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="系统内置" prop="configType">
            <el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
              <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="创建时间" style="width: 308px;">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:config:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:config:edit']">
              ä¿®æ”¹
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:config:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:config:export']">导出</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:config:remove']">刷新缓存</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
            <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column label="参数主键" align="center" prop="configId" v-if="false" />
                <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
                <el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
                <el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
                <el-table-column label="系统内置" align="center" prop="configType">
                    <template #default="scope">
                        <dict-tag :options="sys_yes_no" :value="scope.row.configType" />
                    </template>
                </el-table-column>
                <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
                <el-table-column label="创建时间" align="center" prop="createTime" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.createTime) }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:config:edit']"></el-button>
                        </el-tooltip>
                        <el-tooltip content="删除" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']"></el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        </el-card>
      <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="参数主键" align="center" prop="configId" v-if="false" />
        <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
        <el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
        <el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
        <el-table-column label="系统内置" align="center" prop="configType">
          <template #default="scope">
            <dict-tag :options="sys_yes_no" :value="scope.row.configType" />
          </template>
        </el-table-column>
        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:config:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
        <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
        <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
            <el-form ref="configFormRef" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="参数名称" prop="configName">
                    <el-input v-model="form.configName" placeholder="请输入参数名称" />
                </el-form-item>
                <el-form-item label="参数键名" prop="configKey">
                    <el-input v-model="form.configKey" placeholder="请输入参数键名" />
                </el-form-item>
                <el-form-item label="参数键值" prop="configValue">
                    <el-input v-model="form.configValue" placeholder="请输入参数键值" />
                </el-form-item>
                <el-form-item label="系统内置" prop="configType">
                    <el-radio-group v-model="form.configType">
                        <el-radio v-for="dict in sys_yes_no" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item label="备注" prop="remark">
                    <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
                    <el-button @click="cancel">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="configFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="参数名称" prop="configName">
          <el-input v-model="form.configName" placeholder="请输入参数名称" />
        </el-form-item>
        <el-form-item label="参数键名" prop="configKey">
          <el-input v-model="form.configKey" placeholder="请输入参数键名" />
        </el-form-item>
        <el-form-item label="参数键值" prop="configValue">
          <el-input v-model="form.configValue" placeholder="请输入参数键值" />
        </el-form-item>
        <el-form-item label="系统内置" prop="configType">
          <el-radio-group v-model="form.configType">
            <el-radio v-for="dict in sys_yes_no" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
src/views/system/dept/index.vue
@@ -1,12 +1,142 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
          <el-form-item label="菜单名称" prop="menuName">
            <el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="部门状态" clearable>
              <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['system:dept:add']">新增 </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        v-loading="loading"
        :data="deptList"
        row-key="deptId"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        ref="deptTableRef"
        :default-expand-all="isExpandAll"
      >
        <el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
        <el-table-column prop="orderNum" align="center" label="排序" width="200"></el-table-column>
        <el-table-column prop="status" align="center" label="状态" width="100">
          <template #default="scope">
            <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
          </template>
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime" width="200">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column fixed="right" align="center" label="操作">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']" />
            </el-tooltip>
            <el-tooltip content="新增" placement="top">
              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']" />
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']" />
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="600px">
      <el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
        <el-row>
          <el-col :span="24" v-if="form.parentId !== 0">
            <el-form-item label="上级部门" prop="parentId">
              <el-tree-select
                v-model="form.parentId"
                :data="deptOptions"
                :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
                value-key="deptId"
                placeholder="选择上级部门"
                check-strictly
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="部门名称" prop="deptName">
              <el-input v-model="form.deptName" placeholder="请输入部门名称" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="显示排序" prop="orderNum">
              <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="负责人" prop="leader">
              <el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="部门状态">
              <el-radio-group v-model="form.status">
                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label
                }}</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Dept" lang="ts">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
import { ComponentInternalInstance } from 'vue';
import { DeptForm, DeptQuery, DeptVO } from "@/api/system/dept/types";
interface DeptOptionsType {
  deptId: number | string;
  deptName: string;
  children: DeptOptionsType[];
    deptId: number | string;
    deptName: string;
    children: DeptOptionsType[];
}
@@ -21,8 +151,8 @@
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
    visible: false,
    title: ''
});
const deptTableRef = ref(ElTable);
@@ -30,263 +160,133 @@
const deptFormRef = ref(ElForm);
const initFormData: DeptForm = {
  deptId: undefined,
  parentId: undefined,
  deptName: undefined,
  orderNum: 0,
  leader: undefined,
  phone: undefined,
  email: undefined,
  status: "0"
    deptId: undefined,
    parentId: undefined,
    deptName: undefined,
    orderNum: 0,
    leader: undefined,
    phone: undefined,
    email: undefined,
    status: "0"
}
const data = reactive<PageData<DeptForm, DeptQuery>>({
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    deptName: undefined,
    status: undefined
  },
  rules: {
    parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }],
    deptName: [{ required: true, message: "部门名称不能为空", trigger: "blur" }],
    orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }],
    email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
    phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
  },
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        deptName: undefined,
        status: undefined
    },
    rules: {
        parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }],
        deptName: [{ required: true, message: "部门名称不能为空", trigger: "blur" }],
        orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }],
        email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
        phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
    },
})
const { queryParams, form, rules } = toRefs<PageData<DeptForm, DeptQuery>>(data)
/** æŸ¥è¯¢èœå•列表 */
const getList = async () => {
    loading.value = true;
  const res = await listDept(queryParams.value);
    const data = proxy?.handleTree<DeptVO>(res.data, "deptId")
    if (data) {
        deptList.value = data
    }
    loading.value = false
    loading.value = true;
    const res = await listDept(queryParams.value);
    const data = proxy?.handleTree<DeptVO>(res.data, "deptId")
    if (data) {
        deptList.value = data
    }
    loading.value = false
}
/** å–消按钮 */
const cancel = () => {
  reset()
  dialog.visible = false
    reset()
    dialog.visible = false
}
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
  deptFormRef.value.resetFields();
    form.value = {...initFormData};
    deptFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  getList();
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery()
    queryFormRef.value.resetFields();
    handleQuery()
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = (row?: DeptVO) => {
  listDept().then(res => {
    const data = proxy?.handleTree<DeptOptionsType>(res.data, "deptId");
    if (data) {
      deptOptions.value = data
      dialog.visible = true;
      dialog.title = "添加部门";
      nextTick(() => {
        reset();
        if (row && row.deptId) {
          form.value.parentId = row?.parentId;
    listDept().then(res => {
        const data = proxy?.handleTree<DeptOptionsType>(res.data, "deptId");
        if (data) {
            deptOptions.value = data
            dialog.visible = true;
            dialog.title = "添加部门";
            nextTick(() => {
                reset();
                if (row && row.deptId) {
                    form.value.parentId = row?.parentId;
                }
            })
        }
      })
    }
  })
    })
}
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
  isExpandAll.value = !isExpandAll.value;
  toggleExpandAll(deptList.value, isExpandAll.value)
    isExpandAll.value = !isExpandAll.value;
    toggleExpandAll(deptList.value, isExpandAll.value)
}
/** å±•å¼€/折叠所有 */
const toggleExpandAll = (data: DeptVO[], status: boolean) => {
  data.forEach((item) => {
    deptTableRef.value.toggleRowExpansion(item, status)
    if(item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
    data.forEach((item) => {
        deptTableRef.value.toggleRowExpansion(item, status)
        if(item.children && item.children.length > 0) toggleExpandAll(item.children, status)
    })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: DeptVO) => {
    const res = await getDept(row.deptId);
    dialog.visible = true;
    dialog.title = "修改部门";
    nextTick(async () => {
        reset();
        form.value = res.data
        const response = await listDeptExcludeChild(row.deptId);
        const data = proxy?.handleTree<DeptOptionsType>(response.data, "deptId")
        if (data) {
            deptOptions.value = data;
            if (data.length === 0) {
                const noResultsOptions: DeptOptionsType = { deptId: res.data.parentId, deptName: res.data.parentName, children: [] };
                deptOptions.value.push(noResultsOptions);
            }
        }
    })
    const res = await getDept(row.deptId);
    dialog.visible = true;
    dialog.title = "修改部门";
    nextTick(async () => {
        reset();
        form.value = res.data
        const response = await listDeptExcludeChild(row.deptId);
        const data = proxy?.handleTree<DeptOptionsType>(response.data, "deptId")
        if (data) {
            deptOptions.value = data;
            if (data.length === 0) {
                const noResultsOptions: DeptOptionsType = { deptId: res.data.parentId, deptName: res.data.parentName, children: [] };
                deptOptions.value.push(noResultsOptions);
            }
        }
    })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  deptFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
    }
  })
    deptFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: DeptVO) => {
    await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
    await delDept(row.deptId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
    await delDept(row.deptId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
  getList();
    getList();
});
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
                    <el-form-item label="菜单名称" prop="menuName">
                        <el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="状态" prop="status">
                        <el-select v-model="queryParams.status" placeholder="部门状态" clearable>
                            <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10">
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['system:dept:add']">新增 </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table
                v-loading="loading"
                :data="deptList"
                row-key="deptId"
                :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
                ref="deptTableRef"
                :default-expand-all="isExpandAll"
            >
                <el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
                <el-table-column prop="orderNum" align="center" label="排序" width="200"></el-table-column>
                <el-table-column prop="status" align="center" label="状态" width="100">
                    <template #default="scope">
                        <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
                    </template>
                </el-table-column>
                <el-table-column label="创建时间" align="center" prop="createTime" width="200">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.createTime) }}</span>
                    </template>
                </el-table-column>
                <el-table-column fixed="right" align="center" label="操作">
                    <template #default="scope">
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']" />
                        </el-tooltip>
                        <el-tooltip content="新增" placement="top">
                            <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']" />
                        </el-tooltip>
                        <el-tooltip content="删除" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']" />
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
        </el-card>
        <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="600px">
            <el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
                <el-row>
                    <el-col :span="24" v-if="form.parentId !== 0">
                        <el-form-item label="上级部门" prop="parentId">
                            <el-tree-select
                                v-model="form.parentId"
                                :data="deptOptions"
                                :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
                                value-key="deptId"
                                placeholder="选择上级部门"
                                check-strictly
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="部门名称" prop="deptName">
                            <el-input v-model="form.deptName" placeholder="请输入部门名称" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="显示排序" prop="orderNum">
                            <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="负责人" prop="leader">
                            <el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="联系电话" prop="phone">
                            <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="邮箱" prop="email">
                            <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="部门状态">
                            <el-radio-group v-model="form.status">
                                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label
                                }}</el-radio>
                            </el-radio-group>
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
                    <el-button @click="cancel">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
src/views/system/dict/data.vue
@@ -1,3 +1,136 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="字典名称" prop="dictType">
            <el-select v-model="queryParams.dictType" style="width: 200px">
              <el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
            </el-select>
          </el-form-item>
          <el-form-item label="字典标签" prop="dictLabel">
            <el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
              <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:dict:edit']">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:dict:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="字典编码" align="center" prop="dictCode" v-if="false" />
        <el-table-column label="字典标签" align="center" prop="dictLabel">
          <template #default="scope">
            <span v-if="scope.row.listClass === '' || scope.row.listClass === 'default'">{{ scope.row.dictLabel }}</span>
            <el-tag v-else :type="scope.row.listClass === 'primary' ? '' : scope.row.listClass">{{ scope.row.dictLabel
            }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="字典键值" align="center" prop="dictValue" />
        <el-table-column label="字典排序" align="center" prop="dictSort" />
        <el-table-column label="状态" align="center" prop="status">
          <template #default="scope">
            <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
          </template>
        </el-table-column>
        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="字典类型">
          <el-input v-model="form.dictType" :disabled="true" />
        </el-form-item>
        <el-form-item label="数据标签" prop="dictLabel">
          <el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
        </el-form-item>
        <el-form-item label="数据键值" prop="dictValue">
          <el-input v-model="form.dictValue" placeholder="请输入数据键值" />
        </el-form-item>
        <el-form-item label="样式属性" prop="cssClass">
          <el-input v-model="form.cssClass" placeholder="请输入样式属性" />
        </el-form-item>
        <el-form-item label="显示排序" prop="dictSort">
          <el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
        </el-form-item>
        <el-form-item label="回显样式" prop="listClass">
          <el-select v-model="form.listClass">
            <el-option
              v-for="item in listClassOptions"
              :key="item.value"
              :label="item.label + '(' + item.value + ')'"
              :value="item.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Data" lang="ts">
import useDictStore from '@/store/modules/dict'
import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type";
@@ -26,287 +159,154 @@
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
    visible: false,
    title: ''
});
// æ•°æ®æ ‡ç­¾å›žæ˜¾æ ·å¼
const listClassOptions = ref<Array<{ value: string, label: string }>>([
    { value: "default", label: "默认" },
    { value: "primary", label: "主要" },
    { value: "success", label: "成功" },
    { value: "info", label: "信息" },
    { value: "warning", label: "警告" },
    { value: "danger", label: "危险" }
    { value: "default", label: "默认" },
    { value: "primary", label: "主要" },
    { value: "success", label: "成功" },
    { value: "info", label: "信息" },
    { value: "warning", label: "警告" },
    { value: "danger", label: "危险" }
]);
const initFormData: DictDataForm = {
    dictCode: undefined,
    dictLabel: '',
    dictValue: '',
    cssClass: '',
    listClass: "default",
    dictSort: 0,
    status: "0",
    remark: ''
    dictCode: undefined,
    dictLabel: '',
    dictValue: '',
    cssClass: '',
    listClass: "default",
    dictSort: 0,
    status: "0",
    remark: ''
}
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
    form: { ...initFormData },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        dictName: '',
        dictType: '',
        status: '',
        dictLabel: ''
    },
    rules: {
        dictLabel: [{ required: true, message: "数据标签不能为空", trigger: "blur" }],
        dictValue: [{ required: true, message: "数据键值不能为空", trigger: "blur" }],
        dictSort: [{ required: true, message: "数据顺序不能为空", trigger: "blur" }]
    }
    form: { ...initFormData },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        dictName: '',
        dictType: '',
        status: '',
        dictLabel: ''
    },
    rules: {
        dictLabel: [{ required: true, message: "数据标签不能为空", trigger: "blur" }],
        dictValue: [{ required: true, message: "数据键值不能为空", trigger: "blur" }],
        dictSort: [{ required: true, message: "数据顺序不能为空", trigger: "blur" }]
    }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å­—典类型详细 */
const getTypes = async (dictId: string | number) => {
    const { data } = await getType(dictId);
    queryParams.value.dictType = data.dictType;
    defaultDictType.value = data.dictType;
    getList();
    const { data } = await getType(dictId);
    queryParams.value.dictType = data.dictType;
    defaultDictType.value = data.dictType;
    getList();
}
/** æŸ¥è¯¢å­—典类型列表 */
const getTypeList = async () => {
    const res = await getDictOptionselect()
    typeOptions.value = res.data;
    const res = await getDictOptionselect()
    typeOptions.value = res.data;
}
/** æŸ¥è¯¢å­—典数据列表 */
const getList = async () => {
    loading.value = true;
    const res = await listData(queryParams.value);
    dataList.value = res.rows;
    total.value = res.total;
    loading.value = false;
    loading.value = true;
    const res = await listData(queryParams.value);
    dataList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    dialog.visible = false;
    reset();
    dialog.visible = false;
    reset();
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = { ...initFormData };
    dataFormRef.value.resetFields();
    form.value = { ...initFormData };
    dataFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** è¿”回按钮操作 */
const handleClose = () => {
    const obj = { path: "/system/dict" };
    proxy?.$tab.closeOpenPage(obj);
    const obj = { path: "/system/dict" };
    proxy?.$tab.closeOpenPage(obj);
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    queryParams.value.dictType = defaultDictType.value;
    handleQuery();
    queryFormRef.value.resetFields();
    queryParams.value.dictType = defaultDictType.value;
    handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加字典数据";
    nextTick(() => {
        reset();
        form.value.dictType = queryParams.value.dictType;
    })
    dialog.visible = true;
    dialog.title = "添加字典数据";
    nextTick(() => {
        reset();
        form.value.dictType = queryParams.value.dictType;
    })
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DictDataVO[]) => {
    ids.value = selection.map(item => item.dictCode);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
    ids.value = selection.map(item => item.dictCode);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: DictDataVO) => {
    const dictCode = row?.dictCode || ids.value[0];
    dialog.visible = true;
    dialog.title = "修改字典数据";
    nextTick(async () => {
        const res =  await getData(dictCode);
        reset();
        form.value = res.data;
    })
    const dictCode = row?.dictCode || ids.value[0];
    dialog.visible = true;
    dialog.title = "修改字典数据";
    nextTick(async () => {
        const res =  await getData(dictCode);
        reset();
        form.value = res.data;
    })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    dataFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.dictCode ? await updateData(form.value) : await addData(form.value);
            useDictStore().removeDict(queryParams.value.dictType);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
    dataFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.dictCode ? await updateData(form.value) : await addData(form.value);
            useDictStore().removeDict(queryParams.value.dictType);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
        }
    });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DictDataVO) => {
    const dictCodes = row?.dictCode || ids.value;
    await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
    await delData(dictCodes);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    useDictStore().removeDict(queryParams.value.dictType);
    const dictCodes = row?.dictCode || ids.value;
    await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
    await delData(dictCodes);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    useDictStore().removeDict(queryParams.value.dictType);
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
    proxy?.download("system/dict/data/export", {
        ...queryParams.value
    }, `dict_data_${new Date().getTime()}.xlsx`);
    proxy?.download("system/dict/data/export", {
        ...queryParams.value
    }, `dict_data_${new Date().getTime()}.xlsx`);
}
onMounted(() => {
    getTypes(route.params && route.params.dictId as string);
    getTypeList();
    getTypes(route.params && route.params.dictId as string);
    getTypeList();
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="字典名称" prop="dictType">
                        <el-select v-model="queryParams.dictType" style="width: 200px">
                            <el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="字典标签" prop="dictLabel">
                        <el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable style="width: 200px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="状态" prop="status">
                        <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
                            <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:dict:edit']">修改</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:dict:remove']">
                            åˆ é™¤
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column label="字典编码" align="center" prop="dictCode" v-if="false" />
                <el-table-column label="字典标签" align="center" prop="dictLabel">
                    <template #default="scope">
                        <span v-if="scope.row.listClass === '' || scope.row.listClass === 'default'">{{ scope.row.dictLabel }}</span>
                        <el-tag v-else :type="scope.row.listClass === 'primary' ? '' : scope.row.listClass">{{ scope.row.dictLabel
                        }}</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="字典键值" align="center" prop="dictValue" />
                <el-table-column label="字典排序" align="center" prop="dictSort" />
                <el-table-column label="状态" align="center" prop="status">
                    <template #default="scope">
                        <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
                    </template>
                </el-table-column>
                <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
                <el-table-column label="创建时间" align="center" prop="createTime" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.createTime) }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
                        </el-tooltip>
                        <el-tooltip content="删除" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        </el-card>
        <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
        <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
            <el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="字典类型">
                    <el-input v-model="form.dictType" :disabled="true" />
                </el-form-item>
                <el-form-item label="数据标签" prop="dictLabel">
                    <el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
                </el-form-item>
                <el-form-item label="数据键值" prop="dictValue">
                    <el-input v-model="form.dictValue" placeholder="请输入数据键值" />
                </el-form-item>
                <el-form-item label="样式属性" prop="cssClass">
                    <el-input v-model="form.cssClass" placeholder="请输入样式属性" />
                </el-form-item>
                <el-form-item label="显示排序" prop="dictSort">
                    <el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
                </el-form-item>
                <el-form-item label="回显样式" prop="listClass">
                    <el-select v-model="form.listClass">
                        <el-option
                            v-for="item in listClassOptions"
                            :key="item.value"
                            :label="item.label + '(' + item.value + ')'"
                            :value="item.value"
                        ></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="状态" prop="status">
                    <el-radio-group v-model="form.status">
                        <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item label="备注" prop="remark">
                    <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
                    <el-button @click="cancel">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
src/views/system/dict/index.vue
@@ -1,3 +1,125 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form-item label="字典名称" prop="dictName">
            <el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="字典类型" prop="dictType">
            <el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="字典状态" clearable style="width: 240px">
              <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="创建时间" style="width: 308px">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      </div>
    </transition>
    <el-card shadow="never">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:dict:edit']">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:dict:remove']">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:dict:remove']">刷新缓存</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="字典编号" align="center" prop="dictId" v-if="false" />
        <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
        <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
          <template #default="scope">
            <router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
              <span>{{ scope.row.dictType }}</span>
            </router-link>
          </template>
        </el-table-column>
        <el-table-column label="状态" align="center" prop="status">
          <template #default="scope">
            <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
          </template>
        </el-table-column>
        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="字典名称" prop="dictName">
          <el-input v-model="form.dictName" placeholder="请输入字典名称" />
        </el-form-item>
        <el-form-item label="字典类型" prop="dictType">
          <el-input v-model="form.dictType" placeholder="请输入字典类型" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="Dict" lang="ts">
import useDictStore from '@/store/modules/dict'
import { listType, getType, delType, addType, updateType, refreshCache } from "@/api/system/dict/type";
@@ -22,245 +144,123 @@
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
    visible: false,
    title: ''
});
const initFormData: DictTypeForm = {
  dictId: undefined,
  dictName: '',
  dictType: '',
  status: "0",
  remark: ''
}
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    dictId: undefined,
    dictName: '',
    dictType: '',
    status: ''
  },
  rules: {
    dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
    dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
  },
    status: "0",
    remark: ''
}
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        dictName: '',
        dictType: '',
        status: ''
    },
    rules: {
        dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
        dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
    },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å­—典类型列表 */
const getList = () => {
  loading.value = true;
  listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
    typeList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  });
    loading.value = true;
    listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
        typeList.value = res.rows;
        total.value = res.total;
        loading.value = false;
    });
}
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
    reset();
    dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = {...initFormData};
    dictFormRef.value.resetFields();
    form.value = {...initFormData};
    dictFormRef.value.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
    queryParams.value.pageNum = 1;
    getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value.resetFields();
  handleQuery();
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  dialog.visible = true;
  dialog.title = "添加字典类型";
    nextTick(() => {
        reset();
    })
    dialog.visible = true;
    dialog.title = "添加字典类型";
    nextTick(() => {
        reset();
    })
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DictTypeVO[]) =>  {
  ids.value = selection.map(item => item.dictId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
    ids.value = selection.map(item => item.dictId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: DictTypeVO) => {
    dialog.visible = true;
    dialog.title = "修改字典类型";
    const dictId = row?.dictId || ids.value[0];
    nextTick(async () => {
        reset();
        const res = await getType(dictId);
    form.value = res.data;
    })
    dialog.visible = true;
    dialog.title = "修改字典类型";
    const dictId = row?.dictId || ids.value[0];
    nextTick(async () => {
        reset();
        const res = await getType(dictId);
        form.value = res.data;
    })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  dictFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.dictId ? await updateType(form.value) : await addType(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
    }
  });
    dictFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.dictId ? await updateType(form.value) : await addType(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DictTypeVO) => {
  const dictIds = row?.dictId || ids.value;
    await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
    await delType(dictIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    const dictIds = row?.dictId || ids.value;
    await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
    await delType(dictIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/dict/type/export", {
    ...queryParams.value
  }, `dict_${new Date().getTime()}.xlsx`);
    proxy?.download("system/dict/type/export", {
        ...queryParams.value
    }, `dict_${new Date().getTime()}.xlsx`);
}
/** åˆ·æ–°ç¼“存按钮操作 */
const handleRefreshCache = async () => {
    await refreshCache();
    proxy?.$modal.msgSuccess("刷新成功");
    useDictStore().cleanDict();
    await refreshCache();
    proxy?.$modal.msgSuccess("刷新成功");
    useDictStore().cleanDict();
}
onMounted(()=>{
  getList();
    getList();
})
</script>
<template>
    <div class="p-2">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
            <div class="search" v-show="showSearch">
                <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
                    <el-form-item label="字典名称" prop="dictName">
                        <el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="字典类型" prop="dictType">
                        <el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable style="width: 240px" @keyup.enter="handleQuery" />
                    </el-form-item>
                    <el-form-item label="状态" prop="status">
                        <el-select v-model="queryParams.status" placeholder="字典状态" clearable style="width: 240px">
                            <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="创建时间" style="width: 308px">
                        <el-date-picker
                            v-model="dateRange"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="daterange"
                            range-separator="-"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
                        ></el-date-picker>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </transition>
        <el-card shadow="never">
            <template #header>
                <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:dict:edit']">修改</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:dict:remove']">
                            åˆ é™¤
                        </el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
                    </el-col>
                    <el-col :span="1.5">
                        <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:dict:remove']">刷新缓存</el-button>
                    </el-col>
                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                </el-row>
            </template>
            <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column label="字典编号" align="center" prop="dictId" v-if="false" />
                <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
                <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
                    <template #default="scope">
                        <router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
                            <span>{{ scope.row.dictType }}</span>
                        </router-link>
                    </template>
                </el-table-column>
                <el-table-column label="状态" align="center" prop="status">
                    <template #default="scope">
                        <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
                    </template>
                </el-table-column>
                <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
                <el-table-column label="创建时间" align="center" prop="createTime" width="180">
                    <template #default="scope">
                        <span>{{ parseTime(scope.row.createTime) }}</span>
                    </template>
                </el-table-column>
                <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
                    <template #default="scope">
                        <el-tooltip content="修改" placement="top">
                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
                        </el-tooltip>
                        <el-tooltip content="删除" placement="top">
                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
                        </el-tooltip>
                    </template>
                </el-table-column>
            </el-table>
            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        </el-card>
        <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
        <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
            <el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="字典名称" prop="dictName">
                    <el-input v-model="form.dictName" placeholder="请输入字典名称" />
                </el-form-item>
                <el-form-item label="字典类型" prop="dictType">
                    <el-input v-model="form.dictType" placeholder="请输入字典类型" />
                </el-form-item>
                <el-form-item label="状态" prop="status">
                    <el-radio-group v-model="form.status">
                        <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item label="备注" prop="remark">
                    <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
                </el-form-item>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
                    <el-button @click="cancel">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
在上述文件截断后对比
src/views/system/menu/index.vue src/views/system/notice/index.vue src/views/system/oss/config.vue src/views/system/oss/index.vue src/views/system/post/index.vue src/views/system/role/authUser.vue src/views/system/role/index.vue src/views/system/role/selectUser.vue src/views/system/tenant/index.vue src/views/system/tenantPackage/index.vue src/views/system/user/authRole.vue src/views/system/user/index.vue src/views/system/user/profile/index.vue src/views/system/user/profile/resetPwd.vue src/views/system/user/profile/userAvatar.vue src/views/system/user/profile/userInfo.vue src/views/tool/build/index.vue src/views/tool/gen/basicInfoForm.vue src/views/tool/gen/editTable.vue src/views/tool/gen/genInfoForm.vue src/views/tool/gen/importTable.vue src/views/tool/gen/index.vue tsconfig.json vite.config.ts vite/plugins/auto-import.ts vite/plugins/components.ts vite/plugins/compression.ts vite/plugins/icons.ts vite/plugins/index.ts vite/plugins/svg-icon.ts vite/plugins/unocss.ts