RuoYi
2021-11-30 a4656c6154f969127d8c13ccd57f150dd1fce81f
ruoyi
已修改1个文件
已添加235个文件
16071 ■■■■■ 文件已修改
.env.development 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/build.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/package.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/run-web.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
html/ie.html 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/favicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login.js 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/menu.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/cache.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/job.js 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/jobLog.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/logininfor.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/online.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/operlog.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/server.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/config.js 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dept.js 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dict/data.js 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dict/type.js 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/menu.js 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/notice.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/role.js 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/tool/gen.js 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/401_images/401.gif 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/404_images/404.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/404_images/404_cloud.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/404.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/bug.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/build.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/button.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/cascader.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/chart.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/checkbox.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/clipboard.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/code.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/color.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/component.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/dashboard.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/date-range.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/date.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/dict.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/documentation.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/download.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/drag.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/druid.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/edit.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/education.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/email.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/example.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/excel.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/exit-fullscreen.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/eye-open.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/eye.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/form.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/fullscreen.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/github.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/guide.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/icon.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/input.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/international.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/job.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/language.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/link.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/list.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/lock.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/log.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/logininfor.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/message.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/money.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/monitor.svg 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/nested.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/number.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/online.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/password.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/pdf.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/people.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/peoples.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/phone.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/post.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/qq.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/question.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/radio.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/rate.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/redis.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/row.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/search.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/select.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/server.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/shopping.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/size.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/skill.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/slider.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/star.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/swagger.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/switch.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/system.svg 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/tab.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/table.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/textarea.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/theme.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/time-range.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/time.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/tool.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/tree-table.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/tree.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/upload.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/user.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/validCode.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/wechat.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/icons/svg/zip.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/dark.svg 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/light.svg 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/login-background.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/profile.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/logo/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/btn.scss 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/mixin.scss 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/transition.scss 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Breadcrumb/index.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DictTag/index.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Hamburger/index.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/HeaderSearch/index.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/index.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/requireIcons.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Pagination/index.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ParentView/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RightToolbar/index.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Ruoyi/Doc/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Ruoyi/Git/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Screenfull/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SizeSelect/index.vue 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SvgIcon/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SvgIcon/svgicon.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TopNav/index.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TreeSelect/index.vue 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/iFrame/index.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/permission/hasPermi.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/permission/hasRole.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/InnerLink/index.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Settings/index.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Link.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/index.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/tagsView/ScrollPane.vue 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/tagsView/index.vue 330 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/auth.js 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/cache.js 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/download.js 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/index.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/modal.js 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tab.js 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/getters.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/index.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/app.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/permission.js 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.js 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/tagsView.js 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/auth.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dynamicTitle.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/errorCode.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/index.js 390 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jsencrypt.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/permission.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/ruoyi.js 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/scroll-to.js 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.js 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/401.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/404.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 738 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/cache/index.vue 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/druid/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/job/index.vue 498 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/job/log.vue 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 298 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/server/index.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/redirect/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register.vue 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 326 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 332 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 450 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 586 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/authRole.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 641 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/index.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/resetPwd.vue 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userAvatar.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userInfo.vue 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/build/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/basicInfoForm.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/editTable.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/genInfoForm.vue 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/swagger/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/auto-import.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/compression.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/index.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/setup-extend.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/svg-icon.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = è‹¥ä¾ç®¡ç†ç³»ç»Ÿ
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
# è‹¥ä¾ç®¡ç†ç³»ç»Ÿ/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = è‹¥ä¾ç®¡ç†ç³»ç»Ÿ
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
# è‹¥ä¾ç®¡ç†ç³»ç»Ÿ/生产环境
VITE_APP_BASE_API = '/prod-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
VITE_BUILD_COMPRESS = gzip,brotli
.env.staging
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = è‹¥ä¾ç®¡ç†ç³»ç»Ÿ
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'staging'
# è‹¥ä¾ç®¡ç†ç³»ç»Ÿ/生产环境
VITE_APP_BASE_API = '/stage-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
VITE_BUILD_COMPRESS = gzip,brotli
.gitignore
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock
README.md
@@ -1,7 +1,7 @@
## å¹³å°ç®€ä»‹
* æœ¬ä»“库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) ç‰ˆæœ¬ã€‚
* é…å¥—后端代码地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)。
* é…å¥—后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) æˆ– [RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast) ç‰ˆæœ¬ã€‚
* å‰ç«¯æŠ€æœ¯æ ˆï¼ˆ[Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
* é˜¿é‡Œäº‘折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)  
* é˜¿é‡Œäº‘优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)  
bin/build.bat
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
@echo off
echo.
echo [信息] æ‰“包Web工程,生成dist文件。
echo.
%~d0
cd %~dp0
cd ..
yarn build:prod
pause
bin/package.bat
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
@echo off
echo.
echo [信息] å®‰è£…Web工程,生成node_modules文件。
echo.
%~d0
cd %~dp0
cd ..
yarn --registry=https://registry.npm.taobao.org
pause
bin/run-web.bat
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
@echo off
echo.
echo [信息] ä½¿ç”¨ Vue CLI å‘½ä»¤è¿è¡Œ Web å·¥ç¨‹ã€‚
echo.
%~d0
cd %~dp0
cd ..
yarn dev
pause
html/ie.html
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
<!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,iVBORw0KGgoAAAANSUhEUgAAACIAAADMCAYAAAAWCXEwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAC7ESURBVHja5Lx5dFRV1rBfgHwYRQQVtB26ZWhtabtfeUGxGxFbUGZF8RMHGkVbRkekVYiKisicVhE0gEwBokgDAhEMMSSQkAECwcxkrlRSqVTqJqnxzs/vj5t7qUyAvr9e37fWV2vtleSm6p6n9t5nn733OVU2RaUaEP5PiqJSbeMXPBTA5/Xhzk9Vnd9vo3HFx21E2LYJX9IRgh6npvyCe9uaqS4K4C3IpXHFx9S99CTuJ8Z0KLVjRlA7ZgTuJ8ZgXxmJL+kIlwAkXBQk6HFq9pWRVA8fSvXwodYgdS892a6EA1UNvouqwXdR99KTeAtyfz2IL+kI1cOHYh9wqwVwKWJqpXbMCOv19gG3Imzb1JF2OgZxfr/NukH4jcNVfyEAE8IU+4BbKet1PfaVke3BtA/i/H6b8aIBt7a4mWmaC0nr55vmqRp8F5V33Mm5LhHtwbQF8SUdsSDCb1I1+K42g1xIWgOYYh9wK+e6RCBs29QxSIWus37aJM51iWjx4so77mwD1d5AHQ1eecedlN9yuyVlva6nrNf14Q7cEmRn4W7u3T2E9ME3UX7L7W1uZg5Weced1s3sA2613ql5LXzQjuRclwjcT4wxTXQeRHC7GLdnHPeensiCVwa3e0PznZk3EbZtwluQa0kofz8NcVNxr++Ce30XnNuv61Bcu7viXt8Fvyu7JYipjfGHxzD+8Bh2j+7fAiZcC+Y0zPDIbCyD6DyV6DyVeDcIQR2C39J4oieNJ3oSOnkVcnZ35Ozu6MVdDHF0N6S4C43OqJYg/0ydzb27hzDx0FjuPT2R+asfa6OVsl7X40s6QoWus/CQk6fWZPHChhxe3lbMCxtyrN9TyxSQSwidvMoC0XK6tRGybPjSRmOuNUKVo4Zxe8YxIu4+Jh4ay/jDY7j39MQWWjnXJYLGFR9Toes8tSaLiavTrIHDxfxfapkCwW8hy9YuhCmhk1fR1FRnaCS1NM4yy8RDYy2tjIkZRXq/HtYsCnqc2sJDTkYsTrU00J6YkEJQR7M/eEGY0MmrcOenqjZA2JmyzTJLuJiOe65LBHUvPUmGR2bE4lQmrk7jqTVZHcrE1WkMWpRIdJ4KnpUXBCHLRl3e16EWIOEaMU00/vAY9na/gsYVH/NdgYe+8w9bMBeSQYsSWXjICcFvL2ga+dhlFwcJ10rjio/ZklprgbSWiavTWvzdd/5hXt5W/OtATC201sq9u4eQ+PVijmSW0nf+YQYtSmTQosR2gUYsTmXQokT6zj9saeRCpmkJ0hxD2gOZeGgsI+Lu45+ps7FXlFmDmDDtSd/5h+k7/zCpZQpa9cwOQciyIR+77LyzFhXlMyZmFOP2jLP8orVWRsTdR2ppHFtSa+k6ZZM1WHvSdcomwyxySceayO4OWTY88TdirygzUkWf18eL2//RQiutYcwYE/Q4tagDOUQ8uo6uUzbRZ3qMJV2nbCLi0XU8tSbrolNXzu6OfOyylgEN4NOkaO5acw/j9ozr0ET37h5imehIZimPL91rAfSZHsOQBfuISS7E7vaTETeX0MmrOoQInbwK+dhlNKWsahni0zPSuGvNPW1M1BrI1NrOwt0WkCn2ijJSS+MYt2ccuQk3oxd36RCi8URPY+HLT1VbgGiSzPsx71laCddMe2Yygf6ZOtuScXvG0XfJn/n8YL+LQnjibyQ34WZ8Xl/bfKSoKL+FVi4EYwKZcu/uIQzaPoExMaPQcrq1ADFX33AI1+6u1OV9HVI6ShU/TYqm75I/dwjTHtDEQ2MZt2ccg7ZPaGGScIDWEBlxc42UoSMQ00StYdoDCgcbtH0Cbx+8p40ZTIBwiFM7RmB3+y+exZvT2YRpDdR6ZoVrw1xRWwN44m/Euf06A6Ki7NLrmnDNmH7TEdSg7RP4/GA/yLK1GdwEKNzSk1M7RlDlqPl1JefOlG2MXTGmXaAxMaMsB/XE34h4tH+7ANlrB7T2iV8OAlDlqOH9mPcsIBPKlF3R16Ad7GwlxoVberYAKCrKv1ghfmkg5sPldLIzZVsLqLErxpC9doAlp3aMICNurlGyVpRdSAu/HqS1Q58rd1JUlI87P1UtKsrHXlGG3e1HCOoov+x2wiX3RxT+o49L1IgutXxVUCfDIxNfLraQDI+M3e3/NdCXbhohqBNfLrIsVzZqmoT6dmXG0SBLTrmJLxd/CVRLECXcDGFaSC1TmHE0yKg4B0P2uxiy38WoOAePHaptAfHYoVqG7HcxcGc5o+IcfFfgsbQUPoYoSa213BbE78oGucTSwpJTbobFFjNgbQHdvi6g8/Z6Om+vZ8h+VxsQE7T/97UMWFvA+Og0UvIryfDIZBQ4CeXvt8a5IAhAY/RImlJWUaHrPHaolhuXFXHN+8e58qNcbomq5P6t3xG973WePLzPgnnsUG0LiP7f1zJwZzk3LisyctfSOFxOJ4lfLzYToQubxu/KpmpWBFWzInguOokrP8ql7/zDRMxLpFfUabasHwlZNnITbmbgznI6b6+3Bu7/fa2lrW5fF9Ar6jQD1hYwLLaYx5fupdi+EiGok748koa4qa010xKkKWUV2UM7kd6vB7tH9yfpnUFkLzQiZOGWnmgHO9N4oie9ok5bA4YPbkqvqNNc8/5xIuYl8tSaLOLLRXambENXF+PxNJD0ziAanVHhYaEliH1lJD/1iqD0qSsIzu2M/N550TZ3QjvYmS3rR1qDtwdhgpgwnabGMj46zRjQsxJdXYw7P1X1pY0GuaRjkMKxPah5qxuV8y6nct7l1LzVDfdyo6miHexM+ou9mblwKfdv/Y77t37HNe8fbwMQDhIxL5FOU2PZklqLJjUYdU7wWxBuN+ricBAF0KQG6pcNovZpw0fCQao/MEBcu7tSOLYHjnu7EZzbmeDczqyfNokrP8ptMXi4XDnzAJ0n72TIgn1oUoMB4VlpgIjj24I0payi9KkrqHj+Ssth2wM5c38f8p68D2nbHKRtc3h86d42A/eZHsOVMw9Y0nXKJmxDvyS1NA70z8Gz0qh5hNvbzpr6ZYMofzyiBUwLkOVdjfR/eVcao0dSl/d1aHx0GhHzEi0TXDnzAJ2mxtJpaixdp2yypM/0GLrcs5D3Y94ztNDsK7qjuxmzDBBz2rYGqZoVQc1b3dr4yfppk+g8eWeLd91aAxGPrqPbyKV0G7mUiEfXMWdz+nmQ0Jsgn1AbT/SkMXrkeZC6vK9DpU9d0S5I5bzLqf6gq6UV7WBn5q9+zDJBuEQ8us4SE6LLPQvpcs9CjmSW4ndlo1XPNBxWLiE34WbSX+wNapEBEsrfT/njERSO7WGBmDA1b3Wj9KkrSO/Xg1WjBjJl/CT+8sQ8a0BT/eGDhwN0uWchXe94ia07YkE+oSLc3gxyQt2yfiSrRg0E+YRqgRSO7UHh2B4UT7ragqmcdznFk67mp14ROO7txpTxk7AN/bLFgN1GLsU29EvrejiACdG59xQjKgu3GzVP9UwIvcmCVwYb102NmBHVBDFNVDUrgjP39yF98E0E5xox5Dcj5lsDhwOYQObg4dK59xR2RV8D4njEo/0NIEd3dkVfgy9t9HkfMTWSO6pXG63kjupF8aSrqXj+SoJzO1M573KmjJ/Eb0bM5y9PzGPBK4Mp3GKUEFvWj+Q3I+a3AOjcewp/eWKesUQ0T1mz2att7oSU9+F5EE2SqXvpSbKHdrIGNmHCoapmRVgh33LezZ3QNncyloGDnVnwyuA2IFvWj0Q+dplREzu6Wy0r9/KubVvg9pWRpPfrwZn7+1haMSHCxdSM/J4RWWufjiC9Xw/m9PgtN9w0uo1JbrhpNI0njAXTrAIbT/TEvb4LjdEj2641vqQjpPfrQfrgm1qYKHxKlz51BbmjerFj4G2WtAYwtWDKglcGG2ZoXrldu43AWDUrAmnbnLaRVZMayHvyPn7qZThoa38pfeoKap+OIDi3M6tGDeSGm0a3GTT82g03jeaGm0bj3H4d8rHLrN0I93LDpDsG3kb68si2a425hfZTrwjSB9/UBiZcM+YM6ghoyvhJpL/Ym+yFhknc67tYQVF+z3gjc3r8Fuf32zpOFTMeHXpRGDNfMYF2j+7PqlEDWTVqIOkv9rZ8SNvcCff6LlTOu9yK1Okv9mZOj9+S8ehQNKmBDhs17vxU9adeES1gwoHKH49oFyhcwhfKynmXWzOu4vkryR7aieyhnQjl7+84QzNNJGzbxN7uV1gw7WmntYZaLw2mmNdrn44ge2gnztzfx9od7zBnDa9t0pdHtgsTDhRustaaCndwEyLj0aG481PVS9r3FSUJj6eBrConMZHvnodpntrh2gkHCgcLl/TBN7G3+xXGLMlIo0LXjU7ixeoaUZIQ3C7OlTtJya8kJvJddgy8DctvWgGFaylcHPd2Y2/3K5jT47esGjWQrTtiyapy4nI6jUrvUmpfUytFRfmkZ6SxdUcs66dNYsfA2ywNtQBrJeb/dgy8jZjId/kx4YgF4fP6Ln1L3uyhhWvnSGYpOw6lEBP5LuunTWLDAw+x4YGHrAi74YGHWD9tEuunTSIm8l227ohtAyBK0i8/pNDagTVJxuf1YXf7OVfuJKvKMF16RhrpGWkcySwlJb+SrCqn1awRgjqaJP9nO0b/Zxo1v+ahS0ZqKJ9QCX5rJMyhN42aRj6h/udB5BKjiAp+i64uNrJ2M0Vs3rUiy4aU92G42X49iCYZDZjUMoX4ctFIcILfGgVU6E0LwEyCxKP98aWNxpc2GvFof+RjlyHlfdjxWnOxh93tJya5kIWHnDx2qJbnopP4NCmaYvtKC0LL6WYkQps70RA3laaUVbjzU1V7RRn2ijK8BbkWUJsM7VIAog7k8MyuPKtD1AJA/9zQQpYN9/oubFk/kpkLl7J4a0KbtrdZa/vSRrfMWS8GcSSzlGd25TH5VIjptTpR9T5SS+OMsrHZD3RHd7SDnTm1YwSzY2KsTtL46DSei07iSGZpm/tKeR8a5gnf0+vI8zfE5zAstpjptTrvifBJeeZ5LTQDkGXDtbsr0fte59mjDmaWaUyv1ZlZpvH3XJlRcQ6Grj5OTHJhy/t7VhrpwMVAog7kMCrOwcs+nZWaccak2L7S0oLpC6d2jGDJiUyWN8E6FVZqsLwJ5ruwYO5O9jFoUSIb4nPOT+/gtxf3kZjkQobFFreAaHRGGZoQbm+hhWd25fHsUQevHilgbo7bAmoNM2S/i6Grj3Mks9Tolcgn1Hb39MzHuXInw9edZrJd4z3xPISuLrYgCrf0ZOuOWKLzVFLLFDIKmlfr5EJmHMxhfoWvDczkUyELxl5RduFUUZNkIvdm8+BpkZd9eocQPyYc6XDnocpRQ+TebObmuFmptdTK5FMhBqwt4K1vMi4cWTMKnIyKczDZrvFJeWaHEBdrbVc5aphxMIflTR1rJaPA2TFI1IEc7k72tZwdYRCLtyZc6h4MMcmF7WrlwRSRAWsLiNyb3T6Iz+vjmV15jIpztIHwxN/I7JgY4svFS47CHk9DG62Y5hm4s5zx0Wntb0CnlikMiy3m06ToFpFSO9iZnSnbeGZXHkcyS8kocF6SHMksZc7m9AuaJyW/si3IltRaZsfEGNM09KZVs2bEzWV5EyzLlXn1SEG7MuNgTruy5JS73dlzd7IvPMi1BIlJLmRnyjbLJFawar7ZHi5NdrSS9jRyd7KPXlGnzQDXyjSlcYY2mk1SuKUnS05kslI7f9M9/HKgdaoh74nn/cR02NV7M9t2A9A/t/qf2uZOvB/zHvNdxk3Mm0bV+36VzK8wxHTWVutPmEbkE6q1hjQ3/yefCvGeeB7k1SPGlLsUeeubDOtnezJnczpvfZPBuXJnGEjzAqSri9FyulG4pSf3b/3OCvErNQNmxsEczpU70ST5kuWXJc9yiZXemQ3du5N9TK/VedmnW1qZm+M+v3r+gpTS42nA42nA5XRa4vE0hFd8zSDBb63cInvtAAYtSuTuZB+T7ZoFYy7tz+zK6+igQZtHRoGTyL3ZLab4M7vyGB+dxpAF+1i8NaEliLmWyNndsa+MZPi60/T/vpaJhTKT7ZqllZWaoZW3vsnA42m4IMS5cifPRScxN8fNeyK87NOZXqszsdDITa55/3i4dgVb0OPUTG2IR/vjzk9Vt6Qau5R3J/uYWCi3MJEJM2dzOkcyS80Q3WKrPia50IIIX2cmnwrxYIpIr6jTPBed1Mo0apFgpv0NcVMR3C5ESWLO5nS6fV3Ag6fFdmHmV/iYcTCHyL3ZRB3IsSRybzbP7MpjfoWvXYj+39cyZME+c7aEgTQ36smy0RA31dostrv9DF193IIJ9xcTxgSam+O2xAQwg9fMMo2JhTIPnjYgBi1KbC+RPq8REyR8iT9X7rRgWptpvssYLBwqHGB6rc7fc2ULYsh+F4MWJbLjUErH09c8ytcaxNTMCxtyGLC2oIUDT6/VO5TJdkMLJsTAneUMWpTYNotvE0eaj3rKxy6zun2t69mdKdt4fOley4lN35ls11pIOIC51D8XnWQu9xcGUQCteibyscuM5n31TKNqD5fm1H9DfA7PRScxdPVxhsUWMyy22Dq4MGS/i2GxxQxfd9oC2HEopb1WVcdtCU2Sqcv7OmTWpGbRLOV9SCh/P0GPUwvPvDIKnMQkFxK5N5s5m9N5LjqJ56KTeOubDFbvzSQlv7LN1P5FxzZ8Xp918v8SWk5WsWStLbr0a5oLHRdY/+GjPP8vtq7+0yCiJOHz+hDcLlxOJ2bzxeV0Irhdlk/9x0B8Xh9VjhoEt6s5rZTaFU1qQHC7qHLU/PpZ05EGqhw1uJxO0CVESSIlv5KoAznM2ZxufTJgzuZ0og7kkJJfaR1mcjmdVDlqflkc6ahSs1eUWdMzJrmQQYsSrYMJNy4raiHmYQWzD2IC2SvKLpa/dAzi8/qsc6cZBU6GLNjHlTMPcEtUJVMSdd45qRGdp7KxDOvDPu+c1JhxNMgtUZVcOfMAQxbss0K7vaLsQqbq+GCtCbEhPodOU2O58qNcZhwNsrMK4t0Xlp1VMONokCs/yqXT1FgrE7sATPvbJK0hblxWxDsnNWugvc7zcqFry3JlbomqbANzSdskpk9kFDjpOmWTpQnzne6sMgbbWWWYY8kpN0tOuYnOU1v8z9TcOyc1blxWRNcpmwwz6dLFjxr7vD5rY+eO13YSMS+Rh/co1iAby4wBluXKLDnl5rsCD1lVxk7FdwUelpxysyxXbvHcjWUwYb9CxLxE7nhtp7X10spELUHMMiHqQA6dJ+9k8KYaJh1u6ZRLTrnZklrb+hS3lURtSa1lySm39fyNZTAlUWfwpho6T95p1rqtS5LzICapJsmWNkbEBpiSqLMs1/gY3DsntfAuT4tDlkrYtci92bxzUmNjmaG9KYk6I2IDbbTStsBqjhma1EBKfiVdp2xiwNoCHt6jMOmwxjsnNev46KWUkaIksfCQk2W5Mu+c1Jh0WGPCfoUBawvoOmWT1d4Miy3nQczIuXpvJp2mxjJ4Uw0T9hsg09KM6fhcdBIxyYWXJM9FJzHjaJBpaTDpsAEzeFMNnabGGhVec+RtA1LlqAFd4vGley0Q8wZTEnWmpWGdWX3sUC3PHnW0K+b/n0qoZ1oaTEszfCQc5PGle0GXwv0k7PxI87S9EMjMMo35rvMdILPDbErrzlA4iOmw4SBh0/iXgUxLg8mnQvw9V2Zmmdau/D1XtpoxpiYe3qPw8B6FW6IqreOCvwpkWhqMinMwaFEi46PTfrFMXG38HLr6OHe8ttPykXZNYzrr4q0JdJoay4C1BS2cdfCmGuZsTrd6Hv/T5ozZJ7no9L1xWZE1fU0bD193unXx3GESFZNcyIb4nDazaUN8Dh6PkTy1O307CmgT9itM2K9YWnkuOumi26wTV6dZR43NXOXKj3LpPHknEY+us0DaDWiWnwCr92bSdcomBm+q4eE9ShsThTXh2jRn5mxOZ/CmmjYzZkRsgE5TY40Q33bhu/iiF66VcJjh604TuTfbUnnk3myGrzttQZgzZtJhzQrvfabHWGNccNELnz2tfSUcJjxADVhbwIC1BdYsMyOp+fyH9yhWGnAks/TS0gDTV4qK8q2NxU5TY7klqrIFTDhQ6+gZ/hwzdoSbpKgo/9LPj5hnR8yUwEwVw810MRkRG7BSRXPpLyrKv/RUsT2YI5mlLZLnEbEBK1q2lhGxASt5vuO1nZY5ioryL5TJX7icENwuioryjV1rr4+oAzkMWbDvouXEkAX7iDqQg8/rQ5MaLgZxaQWWJslWSWkWWBkFzl9UYP2PvgjFPNrj8/osM/2YcIQfE46QnpFmfL7K7SLocWpBj1Mz6+D0jLQWzzPb3b/6aI8SVnCbvXTTVOZxno6kqCjfKlPNUH4pIP9XPGz/N319UFnrf2iKLGi6LmggqCBoIOi6JuiqIqCrgqIrgqyrgoYu6JpiiK4LKgigCpquCCEdQdVVAU0VdP2iMGW29tplmtbcQNQ1QEXXNDQdQGsWHZBbvdQsKkTQfaiaBJrc/PyLPpQ2zqqbL9U10GV0TUbTZUCyQAoaJPaVinx5RmbVKZnVWRpf56r8WKlQFww2Q4bf8VdMXwsEtfkdGb97xSAb8yRG7df4zYYQ3deEsK2WsK1UsK1U6LIqxJWfKQzcEODVw0GS7KbG1F8Pout6C7WuL5Dpv1PBtlLEFgWXfyHTY61Ery91rvkiwLWfB7h6jcxV/5LoskLF9gl0+tjLI7FesuuxzKnrHeqneQdL143Bjacj6wqg4ZFUph8JYvusCdsXIldvhGvXi/T+SuS6dQrXrZO4fp3Ib76UuH5NiD6fi1z/mcgNnwa5epWMbbHG1StEvsoSjbeoq2i60h6MYNN1XTAhNF1vdlBoVFSG7/Nh+1Ti2o1Brl8v03uDyDVfN3DDVz5u+FKh15cKvdbp9FoHvT5X6PW5wjVr4LrPda6NkugTJdL1EwXbIpkVx5sdGaXZ8S9gGgNIJ6ipPHgghO3TED23h+ixTafXZpmb1ofos0ml+9dw1VcaV3wapMvKIF1WSVz+qULPzxV6faZw9Wc613yq0Xt1iN9Ehei+WMG2QObz03JHDtxsGk07P2XRmZ/hx7ZG5rqtMjdubqTHFonrNov8doPMZRvA9pmPqz8X+MNWhb/tkrg/VuGWaJXLPmmk85Imen6m0+sz6BMlcsNqP9etVujysU63jwIcrwy1N6UFm6Zrgma4KKBxrE7lyq999PnaT58dcMNWjV5bFa7d6sP2lcj/+szP6/FNHK2SqQtpSKqIKItUN2psyJH52yYXtkV+uq9UuP5fMj1XqVy9WuWGFSE6LQgxbHMQv6kVXW92B12wKZouSEjGNNMVJvwgYdugcGOsym+2q/TZqnD9dh3bVz5u3h4guVJtnpJa808zkJlBMMS7SQG6vB/gimUKvVdK9Fmu0nu5zLXLZGzvaWzLDhggmoysqwYIKoKqG+rKqVO5douP62JUfvutxg2xCn1iZTpv0rgpRuF0XQAIgRJElSUURUWWZWRZRpFlgrIKeIEg7yaC7X2FXkslei+XDVkmY1sQ4pFNDaA3hwcdNF0XbGjNZwNQWXZaxrZV5XexMjftFLnpW4ne34rYNvjZUywBQUJqEEkMoEk6oqIgySqipCCKEt6Qis8fRNEaAB+TtijYInV6Lwtx7VKRPstkIj5S6PGBRGFtwFCgApquCDYFTQANXZeZkiARsVPnlu9kfhcr0/cbiYivA4w94DM0oet4VQVJUQiJGiFRIiTKBEMSAX+QhoBIvU/C1SQCfpIKGrl8kZerFitcu0Tkuk9ErlsiYXtDYuMpYyobE0gVbIouC6DiDsgMiwtx406Z/rs0+u6WGPCNSI8tIZbnSoCCEvITkBRkWSMUkAgEJbz+EE2+IA3eAPUNjTR6fNTWSni9PuoFN/d8KtBpkcg1n3jp82GQ3h/6sc33seAHb/P6pYOmCTY0VQCNEkHhrgMhfrdL5k/fafT/XqT/boU+sRI/2r0AhESFYFDCF1TwBSWa/CE8TQHcjQFcDX6cdQGq63w43PWU1AoEmup4emMjtvl+enzop/d7Aa57N4Btvo/Z37jCHBzBpuqaAHDOHWDo/iD99in8+XuZO/er/H6fxsB/h0irDgGqoYGAhOAL0eALUd/oo87TRK2nCUddI3anQKXTTUl1DUVVNXga6nh2mwvb6066L3Bz3btOekU2YXtd5MVNDmuVVtEFm6brAmiUu4OMPODnjv0idx+UGHpQ4q6DEnf928+h0iCg0egN0OgXqW8MUCd4cXm81LgbqHIJlDs9lFd5KK90U1hWQ3GlgLOqlrs/rsQ2q45rFjq57q0yukc6sL3iYc62akBDR0fRNcGmq5oAQYSAyuQEibsPhnjgkMYD8T4ePOTnv/ZrfJrtBTWE4A3ibvRTJ3hx1jdRXddApbOeMoebEruL3Ao3p8vqOVVSR1JuDZkFtbywvgDb0zl0eqmanm+Wct2bFdhmlvP2Po/hH6qIrmiCTdNUAVVElTVeyfTz10My435UGHNE5JGfJIYf1ZiV4kFo8uILBKirD+LwBHC43Dhq6ymurqfAUU9ORS05RSU0NHmQVRW/JCMqOho6354U6DEri04z8+nxWim2fxSx8ZgLEAlJCqoiCzZZUwVZVECDjUVNDD8s8sRRlSmJOs8mwbPHZJ466iO2yI8aDNJU56a8tpGqaicOZx2FVfWcLa8lq7CMBn8IHfAGZQKSik/SQAoBOjEZtdiezqTTS/l0fzmPrFIBNB9CUCcoSYJN0TTBKymgS5TXBXn8pwCTj8lMT1WZmarx+nGR2Rk680+GOFleh9/bgMtZR3V1HYWVLvJKajiTV0pVjRsV8IVEgrLaLApeERSCAAx5/xS2+48zZvlZAmKIQFMTHq+PppAi2DRdFQIyyKIfRImoMz6ePO7lpUyJl08r/PN0iMjTEh9kS6zNEUgp92GvaaDAXstZh4DLG0JoChAMyviDMn5Jxi/K+EISIVnFr0h4JWPZ33a8mNteSCI6vhpZbMDhaqChyYfHHxRsmhYURBECoRDoMvkukVfTFN7IlHk7W+aDXIlVOSHW5ob4qhi2F4v8WNLIiSov5wLgkVVERSUYMqa2LyTjF1UCkkpQ1vGLImJAxCsai2SdKFJQ6aG0ooqK+gBuVxOCTxBsuq4IkqQSFCVCkgyqzg8lXt5J9/H+WViVJ7G+KMSOEoVdJSp77DJxdRrH3Rq5goLDJyMERRqCIt6QbPiHqBAQFSRJJSCrhGSZJklDUs/nIefsNRRXe3DWefE0NjUf21BURFEiGDRWVH9I5Nu8Rt7Pk/lXocbWIpFvKzT2VSr8YJdIcEqk1Svke2TsPhV3SMYTEmkISngDCr6QTFBSCUkqQUnFL2kEJUNLflFF1aGuyUepow6HuxG34DdyVkVRkCQFUVLxBWR0ScEfFPmuuIG1hTIxpSr/rpA46FBIqJHJdGmcqVPJa1Co9MrUBiTcQQlPQKYhoNAUUvCJCn5JJSApBCTZEr8oEVJU/IpKiaOOmnov9Q1+QyOqqiErGqKiIYk6/mAATQ4QalRItPvZU+EnvkrmxxqJRJdIVp1KTr1GQaNChVei2idTE9BwBRTqAzKeoEyjKNMkKvglhaCkNAMZogAeX4DS6npcDQE8jYHmM0aajqLqyLJOSNbwSTJev0woEKCxyU9OdZCEkgAJ1UGSBYWsBo3cRihq0qj0KVT5ZBwBjdqQRn1IRhBVGiWVRlklqOiIikZQ1hAV4ytjJE2n0ummqt6LU/AjNAYEm64jaBqoqo6iaEiKhiirBESVhkAQr9eH0ChSUu3nVGkdGY4mUmt8ZLoC5DWoFDUplHpVKnw6VT6ZWn+IuqCEJ6TQEFINzUgSflXFJ8nUe304XALVdQ3UNwaob/TT5A0ZILoO4TCyrBKSZHxBGcEfxNPgpdETwO32U+ZoIKesnrPlHrLtbn6urCfPXk+B3U2R3cO5qgbOVTVQUilwrkKgtEqguLKe4sp6yhwNlNg9VLkEhKYgjd4QTX6RYFA+X2Dpuo6maaiqiqqqKIqGKKn4QwrekERjIIC70YenMUBjk0S9EKK23our3ovb48Xj8SI0BfD4ROq9IdyNQeoa/Lg8AZxuPzV1PuobRASfguAN0egP4Q1KBEMykqwKNkAxMnpDNM1oSxhQGrKiI6oqTapIkyTiDYUIiDLBkEwoICOGjHghKxqKqqCoEooqEVJFgkqIkBIiKIsEpBB+MYA/FMAXkgiICiHRmK2KoilWo6bZRIKu61bjRdd1QdEQVBVBkzRBlVRBFhVBVTRBUXRBknVB1hAUECQQNF0XUHVB13RB0XRBVDRBUjRBUTVBUlRBlBRBlGQhJGuCJOuCouiCpuqCqqpl/7Eemqor5HnS2Ja/hPezpvCP1PuYlfo3vvo5EnfA0baH9qs+CKZpBIIh7DUuyuw1lNprqHDU4mnwoqoamq5xyn2YVTkv8cKJO3n+TH+eTB7Ao/H9eSr+TnbmrfyfgdiddZzKKaK0yklhuYN6oWVfvabay+6Tu3gzaSJPpPZm9E9XMmnvH1n60wKSanZypuEg35WuZlrCMLb9vPSXgzicdWTkFLX7vya5Dq/spk62s8v1AW+cu53ns29kSd6z/Fi9mZ/L8tpqVFfZeHYxBe7MSwdJy85v8Xd1oJwDFRtZlTeTD88+wcKsMSzMGsv8rL8wNbMnc7LuJN6xg6AcsF6TW1xBkzfQct9P8pDrSkfT1QuDKKrKz8UV1t+V3kKi89/m1YyhvHlyMPOz/ouFZ4fwYe59fJAzjLfO3s66wuep8p7jbF0iUTkzOe76/rzZ6jxUVteGtch06gL2C4PIikJFtcv6e3/ZeuamDOHNU//NivwxfFY8jnXlE/iyYiKflz/Eh4WD2Gv/CL/YQIJjI2+dvJvXTt7FtJS+LPt5OvVBY383KEoUlFaGzSz5wqb5ubC0WSsyG3PfZUbKnXzw8wOsKX6EdWUT+NI+nq8cY1nrGMnikjuJd0Xhld1sr3iTt37+IyuLHmZN0WMszxnPzLSBvJnxMMWNPxv7vUITLrdw8VlzMswnNud+xD+O3cGy3LF8ce5R1pZN4IuKsXzlGM0X1SP4uPJ2jgpraJAcfFb+CJHnbuOz8pF8UT6OL0om8nnRJFblPcrLaXfxxolROHzGd2idq7xIHBEavTQFQwAcLNvMP5Lu5JOcsawpmsRnJROIKnuYtVWjWVP9Vz6q7McRz0pUTSa2Zh6LSgeytOJPfGa/j3UVY1lTMoFPz01kdcEjLM95hNmp/8UHmU+j6MYnlrJyz3UMknHW0IbDW8rLyfexIGs4nxU8zqqi8Xx07gGiKkfyheN+ltnvJEFYGdYOFWlUqjniWcGK8iFElQ1jTek4Pi2awOqCR1iZ9wgfnx3Hs4l9+aHc+BqH2voGRFFqC+JpaEKSjOR2Y84iZqX8majcx1ieN57Xc+/hvXPD+aziAZaX30VGY0yH0/1s00E+KR7KquL7+ezceFbnT2BFzkSW5Uzg7VP38UbKQ3hCdc1aKWoLktHsG06/nbnJ9/H+6VGsyJnIC9l38kreMNaUPsKSkkHsdy26aABMcK3lw4L/5l9FY1mdP56lOeP55Ox4Psh+mOeT7+BAyUZj17O8qiWIKMkUlNoBOFQaw4zkQSw+M5bZp+7in7mPsKnkFVade4DPSsfTJNVeFCSk+lhbPIVl+Q+wMnccS8+OY/GZsXxwZjTTj9/OkqwXACi3O/H5A+dBKhy1lFQac33t2bf5R/KdvJnxFxadnkSyYzuf5j3BssIR/Kt4DBvLp/NF2dOsqXiSNRVPsKbyCeNnxZN8XjaFz4ufJrr4Bf5V8Agr8h5iWc5YPs4ey4enR/P+6YeYnfZn3kh9CAUfqgz2Gtd5kLOFpZTYjUMHH516jmlJA3jjxHCO2XexteBtFpwZyqqC0awo+huLCv7Eu4W38V7x73mvtD/vl/Xl/bJ+vFfye94tuo2F+X/g3dw/szT/b6zIHcMnZ0fz0ZmHWXT6ISKzRvJq5mBeSh5MSeNZyzyyrBggWTlFlNsNssiMKYz9oQe7i/9FmmM/r6bezZKfx7Is5yGW5f+NFYUjWHXuflaXDmN12V+JKhtGVNkwVpX9lZXFw1lRNILl+Q/ySc6DfHRmFIuyRhF5ciRvZ/6NNzPvZ3baIJ5N+AM/1xsfXcg9V47XH2wLMidpFE/9eAcVQg7Lsp7j9fShfHTmIT4+M4rIrKG8ljGAeSf78eaZfszP7sc/z/bln9n9mH+mH29m9eO1jP7MPfF7ZibfxvSE3zP1UD+eiruVxw/cxIT9fRj+764Mje3M6bqjAOQVl+MPhgyQvHPllFQapnkhfgRf5y7haNV3PJvwe945+QDvnnyAf2bcQ0zR22S7fySzbj+Z7n2cdO/jZP1eTtbvI9O9j8y6fWS49pHm3Edq9T6OV+0luXIPRyt2k1C+i/jybzhYupUfSrfjV40wX1zhQNN0A8RR66bEbjjr5p+Xc9IRz9snJvJ88h94O/N+3s64j1dS7mJLXuT/v0e/vT6qa93nnVXXdXLOlRtJi6qSWLmL8Yd682rGvcxLG8qbJ4byRuoQXj56L+UNuRcdoDHk5kDJNvaXbuZA2Rb2l21hX9nX7C3byNaCKJKr4pqnbw3+QLBlQDttxn4dPsh4hseP3sjcjP/m5dRBvJYymNdTBjMtvh8rT865KMja0wsZvqsr4/f3ZNyBnjx88CpGxV3BiAM2bt5iY8PPKwz/KKlsG1lDooTgCRJAYPKR/jyb2pcZaQOZdfyPzDn+J145/l/MSfojU364lW05yzuE2F30FU/80JcZSQN5+fifmH38Tmam3MGM1Dt4LOE6pv90DyHFCGLZ+SXtL3pn88rJCR5hbPy1TEq6jqnJv2XGsduZdfwPzD52By8n/5FZSX9g8sGbeDflGU7VHMUTqKMhVM/Z2hMsSZ/JY3G38I/E25l77I/MOv4HZhy/nRkptzE1+Rbu+beNhMrvjLEKSi+cj0T+8AaPZfTi2eQ/8Gj89fz96C3MSB7AjOTfMzPpNmYn3c7MowN4/IdrmXKoPy8l3MtLP/2Fpw7fxiMHr+HFxH7MTrqNmUm/56XkAbyY3I/pyb/jr/tsRJ542hqnOGydaRdkxv6J/DXBxvflX/Fd0Rru2W3jmYTrmZnUnxlJ/ZhxtB+zjg5g1tH+vJBwM1Pjr+fZ+Ot5PuFmZiX2Y9ZR43kvJfXlpeR+PJ90M3/da2Nm4gME5MZ2c5F2QV5OeYA/7rZxrOYgANE/f8S933ViTFxXZiX1ZfbRvsxK7MusxFuZnXgrs8JkZuKtzEi8lZlHf8espL48Gd+Lu3fbeDVpLA1BY+kvc7T7ZTktQUQlyLQjg/nzv20cyo+zrsdX7OKR/bcybLeNp368hpd+uok5ib9lbuKtzfI75ib+jtmJv2PGT7fwfMJveOj7zty/O4JPs+YjKsYUdTc04Wloav/YRusLz/04lAeTIsgsPENewfnc0is1EH32Qx47MICH913F+O//F+O/t/H4wW7877gIHtnfhXHfd2Hs91cyZl9v3k19lgLPaev15TV1NDR6Oz4/0vrC26ceYVhcL45X/GB4d2Eljf7Q+cJI9pHqiGPVqVeZd+wRZicOZ0bCvbyS9DAfpD3PnnNfUuO3ny9NVI2T+eVI8oVPGrUB2ZsfzX1HehJTtMK6FgyJZOYW0+gXf1EIz8wro9LhvKTn2lrugkMoFOS5n/7C0APXYK8tb3GepMrh5HB8Cmknz5JbXEpBSQVlFbVU2N0UlVWRW1RK1s95/JCQzMkzPyPLMpqm4ff7CQQChEIhJElCURQ0TcPsVOm6fn6tCT+oUOkq4bGE27n/qzv4KeMIwVCQQCBAbV0ttXW1VFRWkJ19lrS0DJKSj5F4NInk5OOcPHmK/Px8amtrcbvd1NTU4HQ6cbvdNDU1WTCyLKOqaguYDmvfgNzE4bIYdpWv4UT5EezuMkQl9B877PT/DQC7cLwx8LR3hQAAAABJRU5ErkJggg==) 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
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,215 @@
<!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>若依管理系统</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;
    }
    #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: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;
    }
    @-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);
      }
    }
    @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);
      }
    }
    #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-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-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-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 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 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.js"></script>
</body>
</html>
package.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
{
  "name": "ruoyi",
  "version": "3.8.0",
  "description": "若依管理系统",
  "author": "若依",
  "license": "MIT",
  "scripts": {
    "dev": "vite",
    "build:prod": "vite build",
    "build:stage": "vite build --mode staging",
    "preview": "vite preview"
  },
  "repository": {
    "type": "git",
    "url": "https://gitee.com/y_project/RuoYi-Vue.git"
  },
  "dependencies": {
    "@element-plus/icons": "0.0.11",
    "@vueuse/core": "6.4.1",
    "axios": "0.24.0",
    "echarts": "5.2.1",
    "element-plus": "1.2.0-beta.3",
    "file-saver": "2.0.5",
    "fuse.js": "6.4.6",
    "js-cookie": "3.0.1",
    "jsencrypt": "3.2.1",
    "nprogress": "0.2.0",
    "vue": "3.2.22",
    "vue-cropper": "1.0.2",
    "vue-router": "4.0.12",
    "vuex": "4.0.2"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "1.9.4",
    "@vue/compiler-sfc": "3.2.22",
    "sass": "1.43.4",
    "unplugin-auto-import": "0.4.18",
    "vite": "2.6.14",
    "vite-plugin-compression": "0.3.5",
    "vite-plugin-svg-icons": "1.0.5",
    "vite-plugin-vue-setup-extend": "0.1.0"
  }
}
public/favicon.ico
src/App.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
<template>
  <router-view />
</template>
src/api/login.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
import request from '@/utils/request'
// ç™»å½•方法
export function login(username, password, code, uuid) {
  const data = {
    username,
    password,
    code,
    uuid
  }
  return request({
    url: '/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  })
}
// æ³¨å†Œæ–¹æ³•
export function register(data) {
  return request({
    url: '/register',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  })
}
// èŽ·å–ç”¨æˆ·è¯¦ç»†ä¿¡æ¯
export function getInfo() {
  return request({
    url: '/getInfo',
    method: 'get'
  })
}
// é€€å‡ºæ–¹æ³•
export function logout() {
  return request({
    url: '/logout',
    method: 'post'
  })
}
// èŽ·å–éªŒè¯ç 
export function getCodeImg() {
  return request({
    url: '/captchaImage',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  })
}
src/api/menu.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from '@/utils/request'
// èŽ·å–è·¯ç”±
export const getRouters = () => {
  return request({
    url: '/getRouters',
    method: 'get'
  })
}
src/api/monitor/cache.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from '@/utils/request'
// æŸ¥è¯¢ç¼“存详细
export function getCache() {
  return request({
    url: '/monitor/cache',
    method: 'get'
  })
}
src/api/monitor/job.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
import request from '@/utils/request'
// æŸ¥è¯¢å®šæ—¶ä»»åŠ¡è°ƒåº¦åˆ—è¡¨
export function listJob(query) {
  return request({
    url: '/monitor/job/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å®šæ—¶ä»»åŠ¡è°ƒåº¦è¯¦ç»†
export function getJob(jobId) {
  return request({
    url: '/monitor/job/' + jobId,
    method: 'get'
  })
}
// æ–°å¢žå®šæ—¶ä»»åŠ¡è°ƒåº¦
export function addJob(data) {
  return request({
    url: '/monitor/job',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å®šæ—¶ä»»åŠ¡è°ƒåº¦
export function updateJob(data) {
  return request({
    url: '/monitor/job',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å®šæ—¶ä»»åŠ¡è°ƒåº¦
export function delJob(jobId) {
  return request({
    url: '/monitor/job/' + jobId,
    method: 'delete'
  })
}
// ä»»åŠ¡çŠ¶æ€ä¿®æ”¹
export function changeJobStatus(jobId, status) {
  const data = {
    jobId,
    status
  }
  return request({
    url: '/monitor/job/changeStatus',
    method: 'put',
    data: data
  })
}
// å®šæ—¶ä»»åŠ¡ç«‹å³æ‰§è¡Œä¸€æ¬¡
export function runJob(jobId, jobGroup) {
  const data = {
    jobId,
    jobGroup
  }
  return request({
    url: '/monitor/job/run',
    method: 'put',
    data: data
  })
}
src/api/monitor/jobLog.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// æŸ¥è¯¢è°ƒåº¦æ—¥å¿—列表
export function listJobLog(query) {
  return request({
    url: '/monitor/jobLog/list',
    method: 'get',
    params: query
  })
}
// åˆ é™¤è°ƒåº¦æ—¥å¿—
export function delJobLog(jobLogId) {
  return request({
    url: '/monitor/jobLog/' + jobLogId,
    method: 'delete'
  })
}
// æ¸…空调度日志
export function cleanJobLog() {
  return request({
    url: '/monitor/jobLog/clean',
    method: 'delete'
  })
}
src/api/monitor/logininfor.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// æŸ¥è¯¢ç™»å½•日志列表
export function list(query) {
  return request({
    url: '/monitor/logininfor/list',
    method: 'get',
    params: query
  })
}
// åˆ é™¤ç™»å½•日志
export function delLogininfor(infoId) {
  return request({
    url: '/monitor/logininfor/' + infoId,
    method: 'delete'
  })
}
// æ¸…空登录日志
export function cleanLogininfor() {
  return request({
    url: '/monitor/logininfor/clean',
    method: 'delete'
  })
}
src/api/monitor/online.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
import request from '@/utils/request'
// æŸ¥è¯¢åœ¨çº¿ç”¨æˆ·åˆ—表
export function list(query) {
  return request({
    url: '/monitor/online/list',
    method: 'get',
    params: query
  })
}
// å¼ºé€€ç”¨æˆ·
export function forceLogout(tokenId) {
  return request({
    url: '/monitor/online/' + tokenId,
    method: 'delete'
  })
}
src/api/monitor/operlog.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// æŸ¥è¯¢æ“ä½œæ—¥å¿—列表
export function list(query) {
  return request({
    url: '/monitor/operlog/list',
    method: 'get',
    params: query
  })
}
// åˆ é™¤æ“ä½œæ—¥å¿—
export function delOperlog(operId) {
  return request({
    url: '/monitor/operlog/' + operId,
    method: 'delete'
  })
}
// æ¸…空操作日志
export function cleanOperlog() {
  return request({
    url: '/monitor/operlog/clean',
    method: 'delete'
  })
}
src/api/monitor/server.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from '@/utils/request'
// èŽ·å–æœåŠ¡ä¿¡æ¯
export function getServer() {
  return request({
    url: '/monitor/server',
    method: 'get'
  })
}
src/api/system/config.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
import request from '@/utils/request'
// æŸ¥è¯¢å‚数列表
export function listConfig(query) {
  return request({
    url: '/system/config/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å‚数详细
export function getConfig(configId) {
  return request({
    url: '/system/config/' + configId,
    method: 'get'
  })
}
// æ ¹æ®å‚数键名查询参数值
export function getConfigKey(configKey) {
  return request({
    url: '/system/config/configKey/' + configKey,
    method: 'get'
  })
}
// æ–°å¢žå‚数配置
export function addConfig(data) {
  return request({
    url: '/system/config',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å‚数配置
export function updateConfig(data) {
  return request({
    url: '/system/config',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å‚数配置
export function delConfig(configId) {
  return request({
    url: '/system/config/' + configId,
    method: 'delete'
  })
}
// åˆ·æ–°å‚数缓存
export function refreshCache() {
  return request({
    url: '/system/config/refreshCache',
    method: 'delete'
  })
}
src/api/system/dept.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
import request from '@/utils/request'
// æŸ¥è¯¢éƒ¨é—¨åˆ—表
export function listDept(query) {
  return request({
    url: '/system/dept/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢éƒ¨é—¨åˆ—表(排除节点)
export function listDeptExcludeChild(deptId) {
  return request({
    url: '/system/dept/list/exclude/' + deptId,
    method: 'get'
  })
}
// æŸ¥è¯¢éƒ¨é—¨è¯¦ç»†
export function getDept(deptId) {
  return request({
    url: '/system/dept/' + deptId,
    method: 'get'
  })
}
// æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
export function treeselect() {
  return request({
    url: '/system/dept/treeselect',
    method: 'get'
  })
}
// æ ¹æ®è§’色ID查询部门树结构
export function roleDeptTreeselect(roleId) {
  return request({
    url: '/system/dept/roleDeptTreeselect/' + roleId,
    method: 'get'
  })
}
// æ–°å¢žéƒ¨é—¨
export function addDept(data) {
  return request({
    url: '/system/dept',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹éƒ¨é—¨
export function updateDept(data) {
  return request({
    url: '/system/dept',
    method: 'put',
    data: data
  })
}
// åˆ é™¤éƒ¨é—¨
export function delDept(deptId) {
  return request({
    url: '/system/dept/' + deptId,
    method: 'delete'
  })
}
src/api/system/dict/data.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import request from '@/utils/request'
// æŸ¥è¯¢å­—典数据列表
export function listData(query) {
  return request({
    url: '/system/dict/data/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å­—典数据详细
export function getData(dictCode) {
  return request({
    url: '/system/dict/data/' + dictCode,
    method: 'get'
  })
}
// æ ¹æ®å­—典类型查询字典数据信息
export function getDicts(dictType) {
  return request({
    url: '/system/dict/data/type/' + dictType,
    method: 'get'
  })
}
// æ–°å¢žå­—典数据
export function addData(data) {
  return request({
    url: '/system/dict/data',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å­—典数据
export function updateData(data) {
  return request({
    url: '/system/dict/data',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å­—典数据
export function delData(dictCode) {
  return request({
    url: '/system/dict/data/' + dictCode,
    method: 'delete'
  })
}
src/api/system/dict/type.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
import request from '@/utils/request'
// æŸ¥è¯¢å­—典类型列表
export function listType(query) {
  return request({
    url: '/system/dict/type/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å­—典类型详细
export function getType(dictId) {
  return request({
    url: '/system/dict/type/' + dictId,
    method: 'get'
  })
}
// æ–°å¢žå­—典类型
export function addType(data) {
  return request({
    url: '/system/dict/type',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å­—典类型
export function updateType(data) {
  return request({
    url: '/system/dict/type',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å­—典类型
export function delType(dictId) {
  return request({
    url: '/system/dict/type/' + dictId,
    method: 'delete'
  })
}
// åˆ·æ–°å­—典缓存
export function refreshCache() {
  return request({
    url: '/system/dict/type/refreshCache',
    method: 'delete'
  })
}
// èŽ·å–å­—å…¸é€‰æ‹©æ¡†åˆ—è¡¨
export function optionselect() {
  return request({
    url: '/system/dict/type/optionselect',
    method: 'get'
  })
}
src/api/system/menu.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
import request from '@/utils/request'
// æŸ¥è¯¢èœå•列表
export function listMenu(query) {
  return request({
    url: '/system/menu/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢èœå•详细
export function getMenu(menuId) {
  return request({
    url: '/system/menu/' + menuId,
    method: 'get'
  })
}
// æŸ¥è¯¢èœå•下拉树结构
export function treeselect() {
  return request({
    url: '/system/menu/treeselect',
    method: 'get'
  })
}
// æ ¹æ®è§’色ID查询菜单下拉树结构
export function roleMenuTreeselect(roleId) {
  return request({
    url: '/system/menu/roleMenuTreeselect/' + roleId,
    method: 'get'
  })
}
// æ–°å¢žèœå•
export function addMenu(data) {
  return request({
    url: '/system/menu',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹èœå•
export function updateMenu(data) {
  return request({
    url: '/system/menu',
    method: 'put',
    data: data
  })
}
// åˆ é™¤èœå•
export function delMenu(menuId) {
  return request({
    url: '/system/menu/' + menuId,
    method: 'delete'
  })
}
src/api/system/notice.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import request from '@/utils/request'
// æŸ¥è¯¢å…¬å‘Šåˆ—表
export function listNotice(query) {
  return request({
    url: '/system/notice/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å…¬å‘Šè¯¦ç»†
export function getNotice(noticeId) {
  return request({
    url: '/system/notice/' + noticeId,
    method: 'get'
  })
}
// æ–°å¢žå…¬å‘Š
export function addNotice(data) {
  return request({
    url: '/system/notice',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å…¬å‘Š
export function updateNotice(data) {
  return request({
    url: '/system/notice',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å…¬å‘Š
export function delNotice(noticeId) {
  return request({
    url: '/system/notice/' + noticeId,
    method: 'delete'
  })
}
src/api/system/post.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import request from '@/utils/request'
// æŸ¥è¯¢å²—位列表
export function listPost(query) {
  return request({
    url: '/system/post/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å²—位详细
export function getPost(postId) {
  return request({
    url: '/system/post/' + postId,
    method: 'get'
  })
}
// æ–°å¢žå²—位
export function addPost(data) {
  return request({
    url: '/system/post',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å²—位
export function updatePost(data) {
  return request({
    url: '/system/post',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å²—位
export function delPost(postId) {
  return request({
    url: '/system/post/' + postId,
    method: 'delete'
  })
}
src/api/system/role.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
import request from '@/utils/request'
// æŸ¥è¯¢è§’色列表
export function listRole(query) {
  return request({
    url: '/system/role/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢è§’色详细
export function getRole(roleId) {
  return request({
    url: '/system/role/' + roleId,
    method: 'get'
  })
}
// æ–°å¢žè§’色
export function addRole(data) {
  return request({
    url: '/system/role',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹è§’色
export function updateRole(data) {
  return request({
    url: '/system/role',
    method: 'put',
    data: data
  })
}
// è§’色数据权限
export function dataScope(data) {
  return request({
    url: '/system/role/dataScope',
    method: 'put',
    data: data
  })
}
// è§’色状态修改
export function changeRoleStatus(roleId, status) {
  const data = {
    roleId,
    status
  }
  return request({
    url: '/system/role/changeStatus',
    method: 'put',
    data: data
  })
}
// åˆ é™¤è§’色
export function delRole(roleId) {
  return request({
    url: '/system/role/' + roleId,
    method: 'delete'
  })
}
// æŸ¥è¯¢è§’色已授权用户列表
export function allocatedUserList(query) {
  return request({
    url: '/system/role/authUser/allocatedList',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢è§’色未授权用户列表
export function unallocatedUserList(query) {
  return request({
    url: '/system/role/authUser/unallocatedList',
    method: 'get',
    params: query
  })
}
// å–消用户授权角色
export function authUserCancel(data) {
  return request({
    url: '/system/role/authUser/cancel',
    method: 'put',
    data: data
  })
}
// æ‰¹é‡å–消用户授权角色
export function authUserCancelAll(data) {
  return request({
    url: '/system/role/authUser/cancelAll',
    method: 'put',
    params: data
  })
}
// æŽˆæƒç”¨æˆ·é€‰æ‹©
export function authUserSelectAll(data) {
  return request({
    url: '/system/role/authUser/selectAll',
    method: 'put',
    params: data
  })
}
src/api/system/user.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
import request from '@/utils/request'
import { praseStrEmpty } from "@/utils/ruoyi";
// æŸ¥è¯¢ç”¨æˆ·åˆ—表
export function listUser(query) {
  return request({
    url: '/system/user/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢ç”¨æˆ·è¯¦ç»†
export function getUser(userId) {
  return request({
    url: '/system/user/' + praseStrEmpty(userId),
    method: 'get'
  })
}
// æ–°å¢žç”¨æˆ·
export function addUser(data) {
  return request({
    url: '/system/user',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹ç”¨æˆ·
export function updateUser(data) {
  return request({
    url: '/system/user',
    method: 'put',
    data: data
  })
}
// åˆ é™¤ç”¨æˆ·
export function delUser(userId) {
  return request({
    url: '/system/user/' + userId,
    method: 'delete'
  })
}
// ç”¨æˆ·å¯†ç é‡ç½®
export function resetUserPwd(userId, password) {
  const data = {
    userId,
    password
  }
  return request({
    url: '/system/user/resetPwd',
    method: 'put',
    data: data
  })
}
// ç”¨æˆ·çŠ¶æ€ä¿®æ”¹
export function changeUserStatus(userId, status) {
  const data = {
    userId,
    status
  }
  return request({
    url: '/system/user/changeStatus',
    method: 'put',
    data: data
  })
}
// æŸ¥è¯¢ç”¨æˆ·ä¸ªäººä¿¡æ¯
export function getUserProfile() {
  return request({
    url: '/system/user/profile',
    method: 'get'
  })
}
// ä¿®æ”¹ç”¨æˆ·ä¸ªäººä¿¡æ¯
export function updateUserProfile(data) {
  return request({
    url: '/system/user/profile',
    method: 'put',
    data: data
  })
}
// ç”¨æˆ·å¯†ç é‡ç½®
export function updateUserPwd(oldPassword, newPassword) {
  const data = {
    oldPassword,
    newPassword
  }
  return request({
    url: '/system/user/profile/updatePwd',
    method: 'put',
    params: data
  })
}
// ç”¨æˆ·å¤´åƒä¸Šä¼ 
export function uploadAvatar(data) {
  return request({
    url: '/system/user/profile/avatar',
    method: 'post',
    data: data
  })
}
// æŸ¥è¯¢æŽˆæƒè§’色
export function getAuthRole(userId) {
  return request({
    url: '/system/user/authRole/' + userId,
    method: 'get'
  })
}
// ä¿å­˜æŽˆæƒè§’色
export function updateAuthRole(data) {
  return request({
    url: '/system/user/authRole',
    method: 'put',
    params: data
  })
}
src/api/tool/gen.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
import request from '@/utils/request'
// æŸ¥è¯¢ç”Ÿæˆè¡¨æ•°æ®
export function listTable(query) {
  return request({
    url: '/tool/gen/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢db数据库列表
export function listDbTable(query) {
  return request({
    url: '/tool/gen/db/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢è¡¨è¯¦ç»†ä¿¡æ¯
export function getGenTable(tableId) {
  return request({
    url: '/tool/gen/' + tableId,
    method: 'get'
  })
}
// ä¿®æ”¹ä»£ç ç”Ÿæˆä¿¡æ¯
export function updateGenTable(data) {
  return request({
    url: '/tool/gen',
    method: 'put',
    data: data
  })
}
// å¯¼å…¥è¡¨
export function importTable(data) {
  return request({
    url: '/tool/gen/importTable',
    method: 'post',
    params: data
  })
}
// é¢„览生成代码
export function previewTable(tableId) {
  return request({
    url: '/tool/gen/preview/' + tableId,
    method: 'get'
  })
}
// åˆ é™¤è¡¨æ•°æ®
export function delTable(tableId) {
  return request({
    url: '/tool/gen/' + tableId,
    method: 'delete'
  })
}
// ç”Ÿæˆä»£ç ï¼ˆè‡ªå®šä¹‰è·¯å¾„)
export function genCode(tableName) {
  return request({
    url: '/tool/gen/genCode/' + tableName,
    method: 'get'
  })
}
// åŒæ­¥æ•°æ®åº“
export function synchDb(tableName) {
  return request({
    url: '/tool/gen/synchDb/' + tableName,
    method: 'get'
  })
}
src/assets/401_images/401.gif
src/assets/404_images/404.png
src/assets/404_images/404_cloud.png
src/assets/icons/svg/404.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>
src/assets/icons/svg/bug.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>
src/assets/icons/svg/build.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899741379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2054" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 591.424V368.96c0-0.288 0.16-0.512 0.16-0.768S960 367.68 960 367.424V192a32 32 0 0 0-32-32H96a32 32 0 0 0-32 32v175.424c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768v222.464c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768V864a32 32 0 0 0 32 32h832a32 32 0 0 0 32-32v-271.04c0-0.288 0.16-0.512 0.16-0.768S960 591.68 960 591.424z m-560-31.232v-160H608v160h-208z m208 64V832h-208v-207.808H608z m-480-224h208v160H128v-160z m544 0h224v160h-224v-160zM896 224v112.192H128V224h768zM128 624.192h208V832H128v-207.808zM672 832v-207.808h224V832h-224z" p-id="2055"></path></svg>
src/assets/icons/svg/button.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588670460195" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1314" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M230.4 307.712c13.824 0 25.088-11.264 25.088-25.088 0-100.352 81.92-182.272 182.272-182.272s182.272 81.408 182.272 182.272c0 13.824 11.264 25.088 25.088 25.088s25.088-11.264 24.576-25.088c0-127.488-103.936-231.936-231.936-231.936S205.824 154.624 205.824 282.624c-0.512 14.336 10.752 25.088 24.576 25.088z m564.736 234.496c-11.264 0-21.504 2.048-31.232 6.144 0-44.544-40.448-81.92-88.064-81.92-14.848 0-28.16 3.584-39.936 10.24-13.824-28.16-44.544-48.128-78.848-48.128-12.288 0-24.576 2.56-35.328 7.68V284.16c0-45.568-37.888-81.92-84.48-81.92s-84.48 36.864-84.48 81.92v348.672l-69.12-112.64c-18.432-28.16-58.368-36.864-91.136-19.968-26.624 14.336-46.592 47.104-30.208 88.064 3.072 8.192 76.8 205.312 171.52 311.296 0 0 28.16 24.576 43.008 58.88 4.096 9.728 13.312 15.36 22.528 15.36 3.072 0 6.656-0.512 9.728-2.048 12.288-5.12 18.432-19.968 12.8-32.256-19.456-44.544-53.76-74.752-53.76-74.752C281.6 768 209.408 573.44 208.384 570.88c-5.12-12.8-2.56-20.992 7.168-26.112 9.216-4.608 21.504-4.608 26.112 2.56l113.152 184.32c4.096 8.704 12.8 14.336 22.528 14.336 13.824 0 25.088-10.752 25.088-25.088V284.16c0-17.92 15.36-32.256 34.816-32.256s34.816 14.336 34.816 32.256v284.16c0 13.824 10.24 25.088 24.576 25.088 13.824 0 25.088-11.264 25.088-25.088v-57.344c0-17.92 15.36-32.768 34.816-32.768 19.968 0 37.376 15.36 37.376 32.768v95.232c0 7.168 3.072 13.312 7.68 17.92 4.608 4.608 10.752 7.168 17.92 7.168 13.824 0 24.576-11.264 24.576-25.088V547.84c0-18.432 13.824-32.256 32.256-32.256 20.48 0 38.912 15.36 38.912 32.256v95.232c0 13.824 11.264 25.088 25.088 25.088s24.576-11.264 25.088-25.088v-18.944c0-18.944 12.8-32.256 30.72-32.256 18.432 0 22.528 18.944 22.528 31.744 0 1.024-11.776 99.84-50.688 173.056-30.72 58.368-45.056 112.128-51.2 146.944-2.56 13.312 6.656 26.112 19.968 28.672 1.536 0 3.072 0.512 4.608 0.512 11.776 0 22.016-8.192 24.064-20.48 5.632-31.232 18.432-79.36 46.08-132.608 43.52-81.92 55.808-186.88 56.32-193.536-0.512-50.688-29.696-83.968-72.704-83.968z"></path></path></svg>
src/assets/icons/svg/cascader.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg>
src/assets/icons/svg/chart.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>
src/assets/icons/svg/checkbox.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575982282951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="902" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M828.40625 90.125H195.59375C137.375 90.125 90.125 137.375 90.125 195.59375v632.8125c0 58.21875 47.25 105.46875 105.46875 105.46875h632.8125c58.21875 0 105.46875-47.25 105.46875-105.46875V195.59375c0-58.21875-47.25-105.46875-105.46875-105.46875z m52.734375 738.28125c0 29.16-23.57015625 52.734375-52.734375 52.734375H195.59375c-29.109375 0-52.734375-23.574375-52.734375-52.734375V195.59375c0-29.109375 23.625-52.734375 52.734375-52.734375h632.8125c29.16 0 52.734375 23.625 52.734375 52.734375v632.8125z" p-id="903"></path><path d="M421.52890625 709.55984375a36.28125 36.28125 0 0 1-27.55265625-12.66890625L205.17453125 476.613125a36.28546875 36.28546875 0 0 1 55.10109375-47.22890625l164.986875 192.4846875 342.16171875-298.48078125a36.2896875 36.2896875 0 0 1 47.70984375 54.68765625L445.3859375 700.6203125a36.3234375 36.3234375 0 0 1-23.85703125 8.93953125z" p-id="904"></path></svg>
src/assets/icons/svg/clipboard.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg>
src/assets/icons/svg/code.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1546567861908" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2422" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M318.577778 819.2L17.066667 512l301.511111-307.2 45.511111 45.511111L96.711111 512l267.377778 261.688889zM705.422222 819.2l-45.511111-45.511111L927.288889 512l-267.377778-261.688889 45.511111-45.511111L1006.933333 512zM540.785778 221.866667l55.751111 11.150222L483.157333 802.133333l-55.751111-11.093333z" fill="#bfbfbf" p-id="2423"></path></svg>
src/assets/icons/svg/color.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577252187056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M747.59340925 691.12859384c11.51396329 0.25305413 22.43746719-0.21087818 40.74171707-1.51832482 29.35428085-2.10878421 35.84933734-2.36183835 46.47761114-0.8856895 24.71495444 3.37405491 41.12129828 21.76265671 32.47528161 47.95376084-85.57447632 258.19957947-442.00123984 249.76444099-628.67084683 50.73735554-153.47733892-159.33976008-153.09775772-414.41833795 0.92786545-573.42069196 159.71934128-162.67163983 424.03439521-166.59397897 565.78689185 0.63263534 80.38686649 94.81095318 108.34934958 169.16669549 89.11723508 230.57450162-15.01454608 47.99593598-50.61082928 77.68762207-119.77896259 114.63352789-4.89237973 2.65706845-29.35428085 15.52065436-35.84933652 19.02123633-46.94154346 25.30541465-63.51659033 41.20565021-62.20914449 58.45550757 2.95229856 39.13904114 24.16667102 52.7196135 70.98168823 53.81618115z m44.41100207 50.10472101c-19.82257471 1.43397372-32.05352527 1.940082-45.63409763 1.6448519-70.34905207-1.60267593-115.98314969-30.91478165-121.38163769-101.64341492-3.45840683-46.05585397 24.7571304-73.13264758 89.24376132-107.96976837 6.7902866-3.66928501 31.37871396-16.57504688 36.06021551-19.06341229 57.69634516-30.83042972 85.15271997-53.73183005 94.76877722-84.47790866 12.77923398-40.78389304-9.10994898-98.94417051-79.24812286-181.6507002-121.17075953-142.97559219-350.14258521-139.60153647-489.2380134 2.06660824-134.49827774 138.84237405-134.79350784 362.12048163-0.42175717 501.637667 158.53842169 168.99799328 451.9968783 181.18676788 534.57688175-11.80919339-4.68150156 0.2952301-10.71262573 0.67481131-18.72600705 1.26527069z" p-id="2509"></path><path d="M346.03865637 637.18588562a78.82636652 78.82636652 0 0 0 78.32025825-79.29029883c0-43.69401562-35.005823-79.29029883-78.32025825-79.29029882a78.82636652 78.82636652 0 0 0-78.36243338 79.29029882c0 43.69401562 35.005823 79.29029883 78.36243338 79.29029883z m0-51.7495729a27.07679361 27.07679361 0 0 1-26.5706845-27.54072593c0-15.30977536 11.97789643-27.54072593 26.5706845-27.54072592 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072592a27.07679361 27.07679361 0 0 1-26.57068533 27.54072593zM475.7289063 807.11174353a78.82636652 78.82636652 0 0 0 78.3624334-79.29029882c0-43.69401562-34.96364785-79.29029883-78.32025825-79.29029883a78.82636652 78.82636652 0 0 0-78.32025742 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029882z m0-51.74957208a27.07679361 27.07679361 0 0 1-26.57068532-27.54072674c0-15.30977536 12.06224753-27.54072593 26.57068532-27.54072593 14.59278892 0 26.57068533 12.23095057 26.57068453 27.54072593a27.07679361 27.07679361 0 0 1-26.57068453 27.54072674zM601.24376214 377.21492718a78.82636652 78.82636652 0 0 0 78.32025742-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025742-79.29029882a78.82636652 78.82636652 0 0 0-78.32025823 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025824 79.29029883z m1e-8-51.74957208a27.07679361 27.07679361 0 0 1-26.57068534-27.54072675c0-15.30977536 11.97789643-27.54072593 26.57068534-27.54072591 14.55061295 0 26.57068533 12.23095057 26.57068451 27.54072592a27.07679361 27.07679361 0 0 1-26.57068451 27.54072674zM378.80916809 433.85687983a78.82636652 78.82636652 0 0 0 78.32025824-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025824-79.29029802a78.82636652 78.82636652 0 0 0-78.32025742 79.29029802c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029883z m0-51.74957209a27.07679361 27.07679361 0 0 1-26.57068451-27.54072674c0-15.30977536 11.97789643-27.54072593 26.57068451-27.54072593 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072593a27.07679361 27.07679361 0 0 1-26.57068533 27.54072674z" p-id="2510"></path></svg>
src/assets/icons/svg/component.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575804206892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3145" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M826.56 470.016c-32.896 0-64.384 12.288-89.984 35.52l0-104.96c0-62.208-50.496-112.832-112.64-113.088L623.936 287.04 519.552 287.104C541.824 262.72 554.56 230.72 554.56 197.12c0-73.536-59.904-133.44-133.504-133.44-73.472 0-133.376 59.904-133.376 133.44 0 32.896 12.224 64.256 35.52 89.984L175.232 287.104l0 0.576C113.728 288.704 64 338.88 64 400.576l0.32 0 0.32 116.48C60.864 544.896 70.592 577.728 100.8 588.48c12.736 4.608 37.632 7.488 60.864-25.28 12.992-18.368 34.24-29.248 56.64-29.248 38.336 0 69.504 31.104 69.504 69.312 0 38.4-31.168 69.504-69.504 69.504-22.656 0-44.032-11.264-57.344-30.4C138.688 610.112 112.576 615.36 102.464 619.136c-29.824 10.752-39.104 43.776-38.144 67.392l0 160.384L64 846.912C64 909.248 114.752 960 177.216 960l446.272 0c62.4 0 113.152-50.752 113.152-113.152l0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.536 0 133.44-59.904 133.44-133.504C960 529.92 900.096 470.016 826.56 470.016zM826.56 672.896c-22.72 0-44.032-11.264-57.344-30.4-22.272-32.384-48.448-27.136-58.56-23.36-29.824 10.752-39.04 43.776-38.08 67.392l0 160.384c0 27.136-22.016 49.152-49.152 49.152L177.216 896.064C150.08 896 128 873.984 128 846.848l0.32 0 0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.6 0 133.504-59.904 133.504-133.504 0-73.472-59.904-133.376-133.504-133.376-32.896 0-64.32 12.288-89.984 35.52l0-104.96L128 400.512c0-27.072 22.08-49.152 49.216-49.152L177.216 351.04 334.656 350.72c3.776 0.512 7.616 0.832 11.52 0.832 24.896 0 50.752-10.816 60.032-37.056 4.544-12.736 7.424-37.568-25.344-60.736C362.624 240.768 351.68 219.52 351.68 197.12c0-38.272 31.104-69.44 69.376-69.44 38.336 0 69.504 31.168 69.504 69.44 0 22.72-11.264 44.032-30.528 57.472C427.968 276.736 433.088 302.784 436.8 313.024c10.752 29.888 43.072 39.232 67.392 38.08l119.232 0 0 0.384c27.136 0 49.152 22.08 49.152 49.152l0.256 116.48c-3.776 27.84 6.016 60.736 36.224 71.488 12.736 4.608 37.632 7.488 60.8-25.28 13.056-18.368 34.24-29.248 56.704-29.248C864.832 534.016 896 565.12 896 603.392 896 641.728 864.832 672.896 826.56 672.896z" p-id="3146"></path></svg>
src/assets/icons/svg/dashboard.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
src/assets/icons/svg/date-range.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774833889" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.466667 192.853333h-100.693334V119.466667c0-10.24-6.826667-17.066667-17.066666-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H303.786667V119.466667c0-10.24-6.826667-17.066667-17.066667-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H168.96c-46.08 0-85.333333 37.546667-85.333333 85.333334V836.266667c0 46.08 37.546667 85.333333 85.333333 85.333333H887.466667c46.08 0 85.333333-37.546667 85.333333-85.333333V278.186667c0-47.786667-37.546667-85.333333-85.333333-85.333334z m-718.506667 34.133334h100.693333v66.56c0 10.24 6.826667 17.066667 17.066667 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56h450.56v66.56c0 10.24 6.826667 17.066667 17.066666 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56H887.466667c27.306667 0 51.2 22.186667 51.2 51.2v88.746666H117.76v-88.746666c0-29.013333 22.186667-51.2 51.2-51.2zM887.466667 887.466667H168.96c-27.306667 0-51.2-22.186667-51.2-51.2V401.066667H938.666667V836.266667c0 27.306667-22.186667 51.2-51.2 51.2z" p-id="1377"></path><path d="M858.453333 493.226667H327.68c-10.24 0-17.066667 6.826667-17.066667 17.066666v114.346667h-116.053333c-10.24 0-17.066667 6.826667-17.066667 17.066667v133.12c0 10.24 6.826667 17.066667 17.066667 17.066666H460.8c10.24 0 17.066667-6.826667 17.066667-17.066666v-114.346667h380.586666c10.24 0 17.066667-6.826667 17.066667-17.066667v-133.12c0-10.24-6.826667-17.066667-17.066667-17.066666z m-413.013333 34.133333v97.28h-98.986667v-97.28h98.986667z m-230.4 131.413333h98.986667v98.986667h-98.986667v-98.986667z m131.413333 97.28v-97.28h98.986667v97.28h-98.986667z m133.12-228.693333h97.28v98.986667h-97.28v-98.986667z m131.413334 0h98.986666v98.986667h-98.986666v-98.986667z m230.4 97.28h-98.986667v-98.986667h98.986667v98.986667z" p-id="1378"></path></svg>
src/assets/icons/svg/date.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577186573535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1068" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M479.85714249 608.42857168h64.28571502c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285751s-12.85714249-32.14285751-32.14285664-32.14285664h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285662s12.85714249 32.14285751 32.14285664 32.14285753z m-2e-8 122.14285665h64.28571504c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285665s-12.85714249-32.14285751-32.14285664-32.14285751h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285751s12.85714249 32.14285751 32.14285664 32.14285664z m353.57142921-559.28571416h-128.57142921v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285664-32.14285753s-32.14285751 12.85714249-32.14285751 32.14285753v32.14285664h-257.14285665v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285752-32.14285753s-32.14285751 12.85714249-32.14285664 32.14285753v32.14285664h-128.57142919c-70.71428585 0-128.57142832 57.85714249-128.57142832 122.14285751v501.42857081c0 70.71428585 57.85714249 128.57142832 128.57142832 122.14285751h642.85714335c70.71428585 0 128.57142832-57.85714249 128.57142833-122.14285751v-501.42857081c0-70.71428585-57.85714249-122.14285753-128.57142833-122.14285751z m64.28571415 623.57142832c0 32.14285751-32.14285751 64.28571415-64.28571416 64.28571504h-642.85714335c-32.14285751 0-64.28571415-25.71428583-64.28571417-64.28571504v-372.85714249h771.42857168v372.85714249z m0-437.14285664h-771.42857168v-64.28571417c0-32.14285751 32.14285751-64.28571415 64.28571417-64.28571415h128.57142919v32.14285664c0 19.28571417 12.85714249 32.14285751 32.14285664 32.14285751s32.14285751-12.85714249 32.14285753-32.14285751v-32.14285664h257.14285665v32.14285664c0 19.28571417 12.85714249 32.14285751 32.1428575 32.14285751s32.14285751-12.85714249 32.14285664-32.14285751v-32.14285664h128.57142921c32.14285751 0 64.28571415 25.71428583 64.28571415 64.28571415v64.28571417z m-610.71428583 372.85714247h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285664s-12.85714249-32.14285751-32.14285753-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285751s12.85714249 32.14285751 32.14285751 32.14285665z m385.71428583-122.14285664h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285751s-12.85714249-32.14285751-32.14285751-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285664s12.85714249 32.14285751 32.14285753 32.14285751z m-385.71428583 0h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285751s-12.85714249-32.14285751-32.14285753-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285664s12.85714249 32.14285751 32.14285751 32.14285751z m385.71428583 122.14285665h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285665s-12.85714249-32.14285751-32.14285751-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285751s12.85714249 32.14285751 32.14285753 32.14285665z" p-id="1069"></path></svg>
src/assets/icons/svg/dict.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035680909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3601" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1002.0848 744.672l-33.568 10.368c0.96 7.264 2.144 14.304 2.144 21.76 0 7.328-1.184 14.432-2.368 21.568l33.792 10.56c7.936 2.24 14.496 7.616 18.336 14.752 3.84 7.328 4.672 15.808 1.952 23.552-5.376 16-23.168 24.672-39.936 19.68l-34.176-10.624c-7.136 12.8-15.776 24.672-26.208 35.2l20.8 27.488a28.96 28.96 0 0 1 5.824 22.816 29.696 29.696 0 0 1-12.704 19.616 32.544 32.544 0 0 1-44.416-6.752l-20.8-27.552c-13.696 6.56-28.192 11.2-43.008 13.888v33.632c0 16.736-14.112 30.432-31.648 30.432-17.6 0-31.872-13.696-31.872-30.432v-33.632a167.616 167.616 0 0 1-42.88-13.888l-20.928 27.552c-10.72 13.76-30.08 16.64-44.288 6.752a29.632 29.632 0 0 1-12.704-19.616 29.28 29.28 0 0 1 5.696-22.816l20.896-27.808a166.72 166.72 0 0 1-27.008-34.688l-33.376 10.432c-16.8 5.184-34.56-3.552-39.936-19.616a29.824 29.824 0 0 1 20.224-38.24l33.472-10.432c-0.8-7.264-2.016-14.304-2.016-21.824 0-7.36 1.184-14.496 2.304-21.632l-33.792-10.368c-16.672-5.376-25.632-22.496-20.224-38.432 5.376-16 23.136-24.672 39.936-19.68l34.016 10.752c7.328-12.672 15.84-24.8 26.336-35.328l-20.8-27.552a29.44 29.44 0 0 1 6.944-42.432 32.704 32.704 0 0 1 44.384 6.752l20.832 27.616c13.696-6.432 28.224-11.2 43.104-13.952v-33.568c0-16.736 14.048-30.432 31.648-30.432 17.536 0 31.808 13.568 31.808 30.432v33.504c15.072 2.688 29.344 7.808 42.848 14.016l20.992-27.616a32.48 32.48 0 0 1 44.224-6.752 29.568 29.568 0 0 1 7.136 42.432l-21.024 27.808c10.432 10.432 19.872 21.888 27.04 34.752l33.376-10.432c16.768-5.12 34.56 3.68 39.936 19.68 5.536 15.936-3.712 33.056-20.32 38.304z m-206.016-74.432c-61.344 0-111.136 47.808-111.136 106.56 0 58.88 49.792 106.496 111.136 106.496 61.312 0 111.104-47.616 111.104-106.496 0-58.752-49.792-106.56-111.104-106.56z" p-id="3602"></path><path d="M802.7888 57.152h-76.448c0-22.08-21.024-38.24-42.848-38.24H39.3968a39.68 39.68 0 0 0-39.36 40.032v795.616s41.888 120.192 110.752 120.192H673.2848a227.488 227.488 0 0 1-107.04-97.44H117.6368s-40.608-13.696-40.608-41.248l470.304-0.256 1.664 3.36a227.68 227.68 0 0 1-12.64-73.632c0-60.576 24-118.624 66.88-161.44a228.352 228.352 0 0 1 123.552-63.392l-3.2 0.288 2.144-424.672h38.208l0.576 421.024c27.04 0 52.672 4.8 76.64 13.344V101.536c0.032 0-6.304-44.384-38.368-44.384zM149.7648 514.336H72.3888v-77.408H149.7648v77.408z m0-144.32H72.3888v-77.44H149.7648v77.44z m0-137.248H72.3888v-77.44H149.7648v77.44z m501.856 281.568H206.0848v-77.408h445.536v77.408z m0-144.32H206.0848v-77.44h445.536v77.44z m0-137.248H206.0848v-77.44h445.536v77.44z" p-id="3603"></path></svg>
src/assets/icons/svg/documentation.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>
src/assets/icons/svg/download.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569915748289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3062" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M768.35456 416a256 256 0 1 0-512 0 192 192 0 1 0 0 384v64a256 256 0 0 1-58.88-505.216 320.128 320.128 0 0 1 629.76 0A256.128 256.128 0 0 1 768.35456 864v-64a192 192 0 0 0 0-384z m-512 384h64v64H256.35456v-64z m448 0h64v64h-64v-64z" fill="#333333" p-id="3063"></path><path d="M539.04256 845.248V512.192a32.448 32.448 0 0 0-32-32.192c-17.664 0-32 14.912-32 32.192v333.056l-36.096-36.096a32.192 32.192 0 0 0-45.056 0.192 31.616 31.616 0 0 0-0.192 45.056l90.88 90.944a31.36 31.36 0 0 0 22.528 9.088 30.08 30.08 0 0 0 22.4-9.088l90.88-90.88a32.192 32.192 0 0 0-0.192-45.12 31.616 31.616 0 0 0-45.056-0.192l-36.096 36.096z" fill="#333333" p-id="3064"></path></svg>
src/assets/icons/svg/drag.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg>
src/assets/icons/svg/druid.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036347051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5853" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M832 128H192a64.19 64.19 0 0 0-64 64v640a64.19 64.19 0 0 0 64 64h640a64.19 64.19 0 0 0 64-64V192a64.19 64.19 0 0 0-64-64z m0 703.89l-0.11 0.11H192.11l-0.11-0.11V768h640zM832 544H720L605.6 696.54 442.18 435.07 333.25 544H192v-64h114.75l147.07-147.07L610.4 583.46 688 480h144z m0-288H192v-63.89l0.11-0.11h639.78l0.11 0.11z" p-id="5854"></path></svg>
src/assets/icons/svg/edit.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>
src/assets/icons/svg/education.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg>
src/assets/icons/svg/email.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg>
src/assets/icons/svg/example.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
src/assets/icons/svg/excel.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg>
src/assets/icons/svg/exit-fullscreen.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg>
src/assets/icons/svg/eye-open.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
src/assets/icons/svg/eye.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
src/assets/icons/svg/form.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
src/assets/icons/svg/fullscreen.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
src/assets/icons/svg/github.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1581238998885" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4187" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M511.542857 14.057143C228.914286 13.942857 0 242.742857 0 525.142857 0 748.457143 143.2 938.285714 342.628571 1008c26.857143 6.742857 22.742857-12.342857 22.742858-25.371429v-88.571428c-155.085714 18.171429-161.371429-84.457143-171.771429-101.6C172.571429 756.571429 122.857143 747.428571 137.714286 730.285714c35.314286-18.171429 71.314286 4.571429 113.028571 66.171429 30.171429 44.685714 89.028571 37.142857 118.857143 29.714286 6.514286-26.857143 20.457143-50.857143 39.657143-69.485715-160.685714-28.8-227.657143-126.857143-227.657143-243.428571 0-56.571429 18.628571-108.571429 55.2-150.514286-23.314286-69.142857 2.171429-128.342857 5.6-137.142857 66.4-5.942857 135.428571 47.542857 140.8 51.771429 37.714286-10.171429 80.8-15.542857 129.028571-15.542858 48.457143 0 91.657143 5.6 129.714286 15.885715 12.914286-9.828571 76.914286-55.771429 138.628572-50.171429 3.314286 8.8 28.228571 66.628571 6.285714 134.857143 37.028571 42.057143 55.885714 94.514286 55.885714 151.2 0 116.8-67.428571 214.971429-228.571428 243.314286a145.714286 145.714286 0 0 1 43.542857 104v128.571428c0.914286 10.285714 0 20.457143 17.142857 20.457143 202.4-68.228571 348.114286-259.428571 348.114286-484.685714 0-282.514286-229.028571-511.2-511.428572-511.2z" p-id="4188"></path></svg>
src/assets/icons/svg/guide.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.482 70.131l36.204 16.18 69.932-65.485-61.38 70.594 46.435 18.735c1.119.425 2.397-.17 2.797-1.363v-.085L127.998.047 1.322 65.874c-1.12.597-1.519 1.959-1.04 3.151.32.511.72.937 1.2 1.107zm44.676 57.821L64.22 107.26l-18.062-7.834v28.527z"/></svg>
src/assets/icons/svg/icon.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 0 1 4.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 0 1 2.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>
src/assets/icons/svg/input.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802859706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="3103"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="3104"></path></svg>
src/assets/icons/svg/international.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M83.287 103.01c-1.57-3.84-6.778-10.414-15.447-19.548-2.327-2.444-2.182-4.306-1.338-9.862v-.64c.553-3.81 1.513-6.05 14.313-8.087 6.516-1.018 8.203 1.57 10.589 5.178l.785 1.193a12.625 12.625 0 0 0 6.43 5.207c1.134.524 2.53 1.164 4.421 2.24 4.596 2.53 4.596 5.41 4.596 11.753v.727a26.91 26.91 0 0 1-5.178 17.454 59.055 59.055 0 0 1-19.025 11.026c3.49-6.546.814-14.313 0-16.553l-.146-.087zM64 5.12a58.502 58.502 0 0 1 25.484 5.818 54.313 54.313 0 0 0-12.859 10.327c-.93 1.28-1.716 2.473-2.472 3.579-2.444 3.694-3.637 5.352-5.818 5.614a25.105 25.105 0 0 1-4.219 0c-4.276-.29-10.094-.64-11.956 4.422-1.193 3.23-1.396 11.956 2.444 16.495.66 1.077.778 2.4.32 3.578a7.01 7.01 0 0 1-2.066 3.229 18.938 18.938 0 0 1-2.909-2.91 18.91 18.91 0 0 0-8.32-6.603c-1.25-.349-2.647-.64-3.985-.93-3.782-.786-8.03-1.688-9.019-3.812a14.895 14.895 0 0 1-.727-5.818 21.935 21.935 0 0 0-1.396-9.25 8.873 8.873 0 0 0-5.557-4.946A58.705 58.705 0 0 1 64 5.12zM0 64c0 35.346 28.654 64 64 64 35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64C28.654 0 0 28.654 0 64z"/></svg>
src/assets/icons/svg/job.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036191400" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5472" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M934.912 1016.832H192c-14.336 0-25.6-11.264-25.6-25.6v-189.44c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v163.84h691.712V64H217.6v148.48c0 14.336-11.264 25.6-25.6 25.6s-25.6-11.264-25.6-25.6v-174.08c0-14.336 11.264-25.6 25.6-25.6h742.912c14.336 0 25.6 11.264 25.6 25.6v952.832c0 14.336-11.264 25.6-25.6 25.6z" p-id="5473"></path><path d="M232.96 371.2h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 540.16h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 698.88h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM574.464 762.88c-134.144 0-243.2-109.056-243.2-243.2S440.32 276.48 574.464 276.48s243.2 109.056 243.2 243.2-109.056 243.2-243.2 243.2z m0-435.2c-105.984 0-192 86.016-192 192S468.48 711.68 574.464 711.68s192-86.016 192-192S680.448 327.68 574.464 327.68z" p-id="5474"></path><path d="M663.04 545.28h-87.04c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h87.04c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6z" p-id="5475"></path><path d="M576 545.28c-14.336 0-25.6-11.264-25.6-25.6v-87.04c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v87.04c0 14.336-11.264 25.6-25.6 25.6z" p-id="5476"></path></svg>
src/assets/icons/svg/language.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.742 36.8c2.398 7.2 5.595 12.8 11.19 18.4 4.795-4.8 7.992-11.2 10.39-18.4h-21.58zm-52.748 40h20.78l-10.39-28-10.39 28z"/><path d="M111.916 0H16.009C7.218 0 .025 7.2.025 16v96c0 8.8 7.193 16 15.984 16h95.907c8.791 0 15.984-7.2 15.984-16V16c0-8.8-6.394-16-15.984-16zM72.754 103.2c-1.598 1.6-3.197 1.6-4.795 1.6-.8 0-2.398 0-3.197-.8-.8-.8-1.599 0-1.599-.8s-.799-1.6-1.598-3.2c-.8-1.6-.8-2.4-1.599-4l-3.196-8.8H28.797L25.6 96c-1.598 3.2-2.398 5.6-3.197 7.2-.8 1.6-2.398 1.6-4.795 1.6-1.599 0-3.197-.8-4.796-1.6-1.598-1.6-2.397-2.4-2.397-4 0-.8 0-1.6.799-3.2.8-1.6.8-2.4 1.598-4l17.583-44.8c.8-1.6.8-3.2 1.599-4.8.799-1.6 1.598-3.2 2.397-4 .8-.8 1.599-2.4 3.197-3.2 1.599-.8 3.197-.8 4.796-.8 1.598 0 3.196 0 4.795.8 1.598.8 2.398 1.6 3.197 3.2.799.8 1.598 2.4 2.397 4 .8 1.6 1.599 3.2 2.398 5.6l17.583 44c1.598 3.2 2.398 5.6 2.398 7.2-.8.8-1.599 2.4-2.398 4zM116.711 72c-8.791-3.2-15.185-7.2-20.78-12-5.594 5.6-12.787 9.6-21.579 12l-2.397-4c8.791-2.4 15.984-5.6 21.579-11.2C87.939 51.2 83.144 44 81.545 36h-7.992v-3.2h21.58c-1.6-2.4-3.198-5.6-4.796-8l2.397-.8c1.599 2.4 3.997 5.6 5.595 8.8h19.98v4h-7.992c-2.397 8-6.393 15.2-11.189 20 5.595 4.8 11.988 8.8 20.78 11.2l-3.197 4z"/></svg>
src/assets/icons/svg/link.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
src/assets/icons/svg/list.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.585 12.087c0 6.616 3.974 11.98 8.877 11.98 4.902 0 8.877-5.364 8.877-11.98 0-6.616-3.975-11.98-8.877-11.98-4.903 0-8.877 5.364-8.877 11.98zM125.86.107H35.613c-1.268 0-2.114 1.426-2.114 2.852v18.255c0 1.712 1.057 2.853 2.114 2.853h90.247c1.268 0 2.114-1.426 2.114-2.853V2.96c0-1.711-1.057-2.852-2.114-2.852zM.106 62.86c0 6.615 3.974 11.979 8.876 11.979 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zM124.17 50.88H33.921c-1.268 0-2.114 1.425-2.114 2.851v18.256c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852V53.73c0-1.426-.846-2.852-2.114-2.852zM.106 115.913c0 6.616 3.974 11.98 8.876 11.98 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zm124.064-11.98H33.921c-1.268 0-2.114 1.426-2.114 2.853v18.255c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852v-18.255c0-1.427-.846-2.853-2.114-2.853z"/></svg>
src/assets/icons/svg/lock.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>
src/assets/icons/svg/log.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035943711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4805" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M208.736 566.336H64.384v59.328h144.352v-59.328z m0-336.096H165.44V74.592c0-7.968 4.896-14.848 10.464-14.848h502.016V0.448H175.936c-38.72 1.248-69.248 34.368-68.192 74.144v155.648H64.384V289.6h144.352V230.24z m0 168.096H64.384v59.328h144.352v-59.328z m714.656 76.576h-57.76v474.496c0 7.936-4.896 14.848-10.464 14.848H175.936c-5.568 0-10.464-6.912-10.464-14.848v-155.68h43.296v-59.296H64.384v59.296h43.328v155.68c-1.024 39.776 29.472 72.896 68.192 74.144h679.232c38.72-1.184 69.248-34.368 68.256-74.144V474.912z m14.944-290.336l-83.072-85.312a71.264 71.264 0 0 0-52.544-21.728 71.52 71.52 0 0 0-51.616 23.872L386.528 507.264a30.496 30.496 0 0 0-6.176 10.72L308.16 740.512a30.016 30.016 0 0 0 6.976 30.24c7.712 7.968 19.2 10.752 29.568 7.2l216.544-74.112a28.736 28.736 0 0 0 12.128-7.936L940.448 287.456a75.552 75.552 0 0 0-2.112-102.88z m-557.12 518.272l39.104-120.64 78.336 80.416-117.44 40.224z m170.048-70.016l-103.552-106.016 200.16-222.4 103.52 106.304-200.128 222.112zM897.952 247.072l-0.256 0.224-107.136 119.168-103.52-106.528 106.432-118.624a14.144 14.144 0 0 1 10.304-4.736 13.44 13.44 0 0 1 10.464 4.288l83.264 85.696c5.472 5.6 5.664 14.72 0.448 20.512z" p-id="4806"></path></svg>
src/assets/icons/svg/logininfor.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036016814" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5261" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 128h-85.333333a42.666667 42.666667 0 0 0 0 85.333333h42.666666v640H170.666667V213.333333h42.666666a42.666667 42.666667 0 0 0 0-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666667v725.333333a42.666667 42.666667 0 0 0 42.666667 42.666667h768a42.666667 42.666667 0 0 0 42.666667-42.666667V170.666667a42.666667 42.666667 0 0 0-42.666667-42.666667z" p-id="5262"></path><path d="M341.333333 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666666 42.666667zM512 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333334 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM682.666667 298.666667a42.666667 42.666667 0 0 0 42.666666-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM341.333333 768a42.666667 42.666667 0 0 0 42.666667-42.666667 128 128 0 0 1 256 0 42.666667 42.666667 0 0 0 85.333333 0 213.333333 213.333333 0 0 0-107.52-184.32A128 128 0 0 0 640 469.333333a128 128 0 0 0-256 0 128 128 0 0 0 22.186667 71.68A213.333333 213.333333 0 0 0 298.666667 725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667z m128-298.666667a42.666667 42.666667 0 1 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667z" p-id="5263"></path></svg>
src/assets/icons/svg/message.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>
src/assets/icons/svg/money.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>
src/assets/icons/svg/monitor.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>
src/assets/icons/svg/nested.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
src/assets/icons/svg/number.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802851180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2867" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M279.272727 791.272727h512a46.545455 46.545455 0 0 1 0 93.090909H279.272727a46.545455 46.545455 0 0 1 0-93.090909z m33.838546-617.984V651.636364H193.722182V395.170909c0-37.003636-0.884364-59.298909-2.653091-66.746182a24.948364 24.948364 0 0 0-14.615273-16.989091c-8.005818-3.863273-25.786182-5.771636-53.341091-5.771636h-11.822545v-55.854545c57.716364-12.381091 101.562182-37.888 131.490909-76.520728h70.283636z m303.709091 396.8V651.636364H354.164364v-68.235637c77.777455-127.255273 124.043636-206.010182 138.705454-236.218182 14.661818-30.254545 22.016-53.853091 22.016-70.74909 0-13.032727-2.234182-22.714182-6.656-29.137455-4.421818-6.376727-11.170909-9.588364-20.247273-9.588364a22.248727 22.248727 0 0 0-20.200727 10.612364c-4.468364 7.121455-6.656 21.178182-6.656 42.263273v45.521454H354.164364v-17.454545c0-26.763636 1.396364-47.941818 4.142545-63.348364 2.746182-15.499636 9.541818-30.72 20.386909-45.661091 10.798545-14.987636 24.901818-26.298182 42.216727-33.978182 17.361455-7.68 38.167273-11.543273 62.37091-11.543272 47.476364 0 83.316364 11.776 107.706181 35.328 24.296727 23.552 36.445091 53.341091 36.445091 89.367272 0 27.368727-6.842182 56.32-20.48 86.853819-13.730909 30.533818-54.039273 95.325091-121.018182 194.420363h130.885819z m270.615272-189.393454c18.152727 6.097455 31.650909 16.104727 40.494546 29.975272 8.843636 13.917091 13.312 46.452364 13.312 97.652364 0 38.027636-4.328727 67.490909-13.032727 88.529455-8.657455 20.945455-23.598545 36.910545-44.869819 47.848727-21.271273 10.938182-48.593455 16.384-81.873454 16.384-37.794909 0-67.490909-6.330182-89.088-19.083636-21.550545-12.660364-35.746909-28.253091-42.542546-46.638546-6.795636-18.432-10.193455-50.362182-10.193454-95.883636v-37.841455h119.389091v77.730909c0 20.666182 1.210182 33.838545 3.723636 39.424 2.420364 5.585455 7.912727 8.424727 16.337455 8.424728 9.309091 0 15.36-3.537455 18.338909-10.612364 2.932364-7.121455 4.421818-25.6 4.421818-55.575273v-33.047273c0-18.338909-2.048-31.744-6.190546-40.215272a30.72 30.72 0 0 0-18.338909-16.709818c-8.052364-2.653091-23.738182-4.189091-46.964363-4.561455V357.050182c28.392727 0 45.893818-1.070545 52.596363-3.258182a22.946909 22.946909 0 0 0 14.475637-14.149818c2.932364-7.307636 4.421818-18.711273 4.421818-34.257455v-26.624c0-16.756364-1.722182-27.741091-5.12-33.047272-3.490909-5.352727-8.843636-8.005818-16.151273-8.005819-8.285091 0-13.963636 2.792727-16.989091 8.378182-3.025455 5.632-4.561455 17.640727-4.561454 35.933091v39.284364h-119.389091v-40.773818c0-45.661091 10.472727-76.567273 31.325091-92.625455 20.898909-16.058182 54.085818-24.064 99.607272-24.064 56.878545 0 95.511273 11.170909 115.805091 33.373091 20.293818 22.248727 30.394182 53.201455 30.394182 92.765091 0 26.810182-3.630545 46.173091-10.891636 58.088727-7.307636 11.915636-20.107636 22.807273-38.446546 32.628364z" p-id="2868"></path></svg>
src/assets/icons/svg/online.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899557259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="535" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286zM263.943926 754.580874c-92.603071-61.111846-145.713686-149.623739-145.713686-242.840794 0-93.195565 53.094242-181.682899 145.667637-242.774279 11.805884-7.79043 15.061021-23.677259 7.269567-35.483142-7.79043-11.805884-23.677259-15.062044-35.483142-7.269567C128.487861 296.954249 67.006602 401.024489 67.006602 511.74008c0 110.73708 61.496609 214.830857 168.721703 285.593504 4.343935 2.867304 9.240455 4.238534 14.08274 4.238534 8.317433 0 16.476253-4.046153 21.400403-11.507078C279.003923 778.258133 275.748786 762.372328 263.943926 754.580874zM788.660552 226.213092c-11.80486-7.791453-27.692712-4.536316-35.483142 7.269567-7.79043 11.805884-4.536316 27.692712 7.269567 35.483142 92.575442 61.092403 145.670707 149.579737 145.670707 242.774279 0 93.216032-53.111638 181.727924-145.715733 242.840794-11.805884 7.79043-15.059997 23.678282-7.269567 35.484166 4.925173 7.461949 13.081946 11.507078 21.400403 11.507078 4.841262 0 9.739828-1.37123 14.083763-4.238534 107.22714-70.761624 168.724773-174.857447 168.724773-285.593504C957.341323 401.025513 895.860063 296.955272 788.660552 226.213092zM790.090111 633.67213c23.865547-37.459147 36.480866-79.617315 36.480866-121.914654 0-42.298362-12.615319-84.45653-36.480866-121.917724-22.598694-35.470863-55.496965-67.115584-95.139847-91.515297-12.047384-7.413853-27.821649-3.659343-35.236526 8.387018-7.414877 12.045337-3.659343 27.821649 8.385994 35.236526 68.156286 41.949414 107.247606 103.842043 107.247606 169.809477 0 65.964364-39.090297 127.85597-107.247606 169.804361-12.045337 7.414877-15.800871 23.190165-8.385994 35.237549 4.838192 7.861038 13.236466 12.190647 21.835308 12.190647 4.579295 0 9.215896-1.227967 13.400195-3.804652C734.591099 700.786691 767.490394 669.142993 790.090111 633.67213zM567.129086 518.274914c24.12342-17.150612 39.887452-45.305859 39.887452-77.07133 0-52.128241-42.452881-94.538143-94.634334-94.538143-52.18043 0-94.633311 42.408879-94.633311 94.538143 0 31.695886 15.696494 59.797921 39.730886 76.958766-49.875944 21.128203-84.917018 70.234621-84.917018 127.301338 0 2.366907 0.061398 4.762467 0.182149 7.119141l1.249457 24.296359 276.373515 0 1.238201-24.308639c0.119727-2.358721 0.181125-4.750187 0.181125-7.106862C651.786185 588.497255 616.865861 539.465538 567.129086 518.274914zM512.381182 397.889079c23.937179 0 43.411719 19.430538 43.411719 43.314505 0 23.882943-19.47454 43.313481-43.411719 43.313481-23.936155 0-43.409672-19.430538-43.409672-43.313481C468.971509 417.320641 488.445026 397.889079 512.381182 397.889079zM426.08884 625.656573c9.119705-38.542828 44.254923-67.337641 86.085634-67.337641s76.966952 28.794813 86.085634 67.337641L426.08884 625.656573z" p-id="536"></path></svg>
src/assets/icons/svg/password.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802846045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2750" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M868.593046 403.832442c-30.081109-28.844955-70.037123-44.753273-112.624057-44.753273L265.949606 359.079168c-42.554188 0-82.510202 15.908318-112.469538 44.690852-30.236652 28.782533-46.857191 67.222007-46.857191 108.198258l0 294.079782c0 40.977273 16.619516 79.414701 46.702672 108.136859 29.959336 28.844955 70.069869 44.814672 112.624057 44.814672l490.019383 0c42.585911 0 82.696444-15.969717 112.624057-44.814672 30.082132-28.844955 46.579875-67.222007 46.579875-108.136859L915.172921 511.968278C915.171897 471.053426 898.675178 432.677397 868.593046 403.832442zM841.821309 806.049083c0 22.098297-8.882298 42.772152-25.099654 58.306964-16.154935 15.661701-37.81935 24.203238-60.752666 24.203238L265.949606 888.559285c-22.934339 0-44.567032-8.54256-60.877509-24.264637-16.186657-15.474436-25.067932-36.148291-25.067932-58.246589L180.004165 511.968278c0-22.035876 8.881274-42.772152 25.192775-58.307987 16.186657-15.536858 37.81935-24.139793 60.753689-24.139793l490.019383 0c22.933315 0 44.597731 8.602935 60.752666 24.139793 16.21838 15.535835 25.099654 36.272112 25.099654 58.307987L841.822332 806.049083zM510.974136 135.440715c114.914216 0 208.318536 89.75214 208.318536 200.055338l73.350588 0c0-149.113109-126.366036-270.496667-281.669124-270.496667-155.333788 0-281.699824 121.383558-281.699824 270.496667l73.350588 0C302.623877 225.193879 396.059919 135.440715 510.974136 135.440715zM474.299865 747.244792l73.350588 0L547.650453 629.576859l-73.350588 0L474.299865 747.244792z" p-id="2751"></path></svg>
src/assets/icons/svg/pdf.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M869.073 277.307H657.111V65.344l211.962 211.963zm-238.232 26.27V65.344l-476.498-.054v416.957h714.73v-178.67H630.841zm-335.836 360.57c-5.07-3.064-10.944-5.133-17.61-6.201-6.67-1.064-13.603-1.6-20.81-1.6h-48.821v85.641h48.822c7.206 0 14.14-.532 20.81-1.6 6.665-1.065 12.54-3.133 17.609-6.202 5.064-3.063 9.134-7.406 12.208-13.007 3.065-5.602 4.6-12.937 4.6-22.011 0-9.07-1.535-16.408-4.6-22.01-3.074-5.603-7.144-9.94-12.208-13.01zM35.82 541.805v416.904h952.358V541.805H35.821zm331.421 191.179c-3.6 11.071-9.343 20.879-17.209 29.413-7.874 8.542-18.078 15.408-30.617 20.61-12.544 5.206-27.747 7.807-45.621 7.807h-66.036v102.45h-62.831V607.517h128.867c17.874 0 33.077 2.6 45.62 7.802 12.541 5.207 22.745 12.076 30.618 20.615 7.866 8.538 13.604 18.277 17.21 29.212 3.6 10.943 5.401 22.278 5.401 34.018 0 11.477-1.8 22.752-5.402 33.819zM644.9 806.417c-5.343 17.61-13.408 32.818-24.212 45.627-10.807 12.803-24.283 22.879-40.423 30.213-16.146 7.343-35.155 11.007-57.03 11.007h-123.26V607.518h123.26c18.41 0 35.552 2.941 51.428 8.808 15.873 5.869 29.618 14.671 41.22 26.412 11.608 11.744 20.674 26.411 27.217 44.02 6.535 17.61 9.803 38.288 9.803 62.035 0 20.81-2.67 40.02-8.003 57.624zm245.362-146.07h-138.07v66.03h119.66v48.829h-119.66v118.058h-62.83V607.518h200.9v52.829h-.001zm-318.2 25.611c-6.402-8.266-14.877-14.604-25.412-19.01-10.544-4.402-23.551-6.602-39.019-6.602h-44.825v180.088h56.029c9.07 0 17.872-1.463 26.415-4.401 8.535-2.932 16.14-7.802 22.812-14.609 6.665-6.8 12.007-15.667 16.007-26.61 4.003-10.94 6.003-24.275 6.003-40.021 0-14.408-1.4-27.416-4.202-39.019-2.8-11.607-7.406-21.542-13.808-29.816zm0 0"/></svg>
src/assets/icons/svg/people.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M104.185 95.254c8.161 7.574 13.145 17.441 13.145 28.28 0 1.508-.098 2.998-.285 4.466h-10.784c.238-1.465.403-2.948.403-4.465 0-8.983-4.36-17.115-11.419-23.216C86 104.66 75.355 107.162 64 107.162c-11.344 0-21.98-2.495-31.22-6.83-7.064 6.099-11.444 14.218-11.444 23.203 0 1.517.165 3 .403 4.465H10.955a35.444 35.444 0 0 1-.285-4.465c0-10.838 4.974-20.713 13.127-28.291C9.294 85.42.003 70.417.003 53.58.003 23.99 28.656.001 64 .001s63.997 23.988 63.997 53.58c0 16.842-9.299 31.85-23.812 41.673zM64 36.867c-29.454 0-53.33-10.077-53.33 15.342 0 25.418 23.876 46.023 53.33 46.023 29.454 0 53.33-20.605 53.33-46.023 0-25.419-23.876-15.342-53.33-15.342zm24.888 25.644c-3.927 0-7.111-2.665-7.111-5.953 0-3.288 3.184-5.954 7.11-5.954 3.928 0 7.111 2.666 7.111 5.954s-3.183 5.953-7.11 5.953zm-3.556 16.372c0 4.11-9.55 7.442-21.332 7.442-11.781 0-21.332-3.332-21.332-7.442 0-1.06.656-2.064 1.8-2.976 3.295 2.626 10.79 4.465 19.532 4.465 8.743 0 16.237-1.84 19.531-4.465 1.145.912 1.801 1.916 1.801 2.976zm-46.22-16.372c-3.927 0-7.11-2.665-7.11-5.953 0-3.288 3.183-5.954 7.11-5.954 3.927 0 7.111 2.666 7.111 5.954s-3.184 5.953-7.11 5.953z"/></svg>
src/assets/icons/svg/peoples.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>
src/assets/icons/svg/phone.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1567417214476" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M761.503029 2.90619 242.121921 2.90619c-32.405037 0-58.932204 26.060539-58.932204 58.527998l0 902.302287c0 32.156374 26.217105 58.216913 58.932204 58.216913l519.381108 0c32.344662 0 58.591443-26.060539 58.591443-58.216913L820.094472 61.123103C820.094472 28.966729 793.847691 2.90619 761.503029 2.90619M452.878996 61.123103l98.147344 0c6.780427 0 12.31549 5.536087 12.31549 12.253068 0 6.748704-5.535063 12.253068-12.31549 12.253068l-98.147344 0c-6.779404 0-12.345166-5.504364-12.345166-12.253068C440.532807 66.659189 446.099592 61.123103 452.878996 61.123103M501.641583 980.593398c-29.636994 0-53.987588-23.946388-53.987588-53.677527 0-29.356608 24.039509-53.614082 53.987588-53.614082 29.91738 0 53.987588 23.883967 53.987588 53.614082C555.629171 956.647009 531.559986 980.593398 501.641583 980.593398M766.35657 803.142893c0 16.23373-13.186324 29.107945-29.233811 29.107945l-470.618521 0c-16.35755 0-29.325909-13.186324-29.325909-29.107945L237.178329 163.500794c0-16.232706 13.279445-29.138644 29.325909-29.138644l470.246037 0c16.420995 0 29.357632 13.1853 29.357632 29.138644l0 639.642099L766.35657 803.142893zM766.35657 803.142893" p-id="2267"></path></svg>
src/assets/icons/svg/post.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035724641" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3998" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M136.4 434.3h77.7c21.5 0 38.9-17.4 38.9-38.9s-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9zM252.9 628.6c0-21.5-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9H214c21.5-0.1 38.9-17.5 38.9-38.9z" p-id="3999"></path><path d="M874.7 97.5H227c-28.6 0-51.8 23.2-51.8 51.8v194.3h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v129.5h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v194.3c0 28.6 23.2 51.8 51.8 51.8h647.7c28.6 0 51.8-23.2 51.8-51.8V149.3c0-28.6-23.2-51.8-51.8-51.8z m-311.3 723c-15.6 0-146.7-71.6-146.7-91 0-19.4 102-368.6 102-368.6l-83.6-104s-12.3-23.1 24.6-23.1h208.9c36.9 0 18.4 23.1 18.4 23.1l-79 104s102 351.3 102 368.6c0.1 17.3-131 91-146.6 91z m169.2-253.6l-27.9 40.2-74.5-240 103.4 171.7c4.6 7.9 4.2 20.6-1 28.1z" p-id="4000"></path></svg>
src/assets/icons/svg/qq.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M18.448 57.545l-.244-.744-.198-.968-.132-.53v-2.181l.236-.859.24-.908.317-.953.428-1.06.561-1.103.794-1.104v-.773l.077-.724.123-.984.34-1.106.313-1.194.25-.548.289-.511.371-.569.405-.423v-2.73l.234-1.407.236-1.633.42-1.955.577-2.035.43-1.118.426-1.217.468-1.135.559-1.216.57-1.332.655-1.247.737-1.331.929-1.33.43-.762.457-.624.995-1.406 1.025-1.403 1.163-1.444 1.246-1.405 1.352-1.384 1.41-1.423 1.708-1.536 1.083-.934 1.322-1.008 1.34-.89 1.448-.855 1.392-.76 1.57-.63 1.667-.775 1.657-.532 1.653-.552 1.787-.548 1.785-.417 1.876-.347L59.128.68l1.879-.245 1.876-.252 2.002-.106h5.912l1.97.243 1.981.231 2.019.207 1.874.441 1.979.413 1.857.475 2.035.53 1.862.646 1.782.738 1.904.78 1.736.853 1.689.95 1.655 1.044 1.425.971.662.548.693.401 1.323 1.1 1.115 1.064 1.112 1.1 1.083 1.214.894 1.178 1.064 1.217.74 1.306.752 1.162.798 1.352.661 1.175 1.113 2.489.546 1.286.428 1.192.428 1.294.384 1.217.267 1.047.347 1.231.607 2.198.388 1.924.253 1.861.217 1.497.342 2.28.077.362.274.41.737 1.18.473.8.42.832.534.892.472 1.07.307 1.093.334 1.2.252 1.232.115.605.106.746v.648l-.106.643v.8l-.192.774-.35 1.5-.403.76-.299.852v.213l.142.264.4.623 1.746 2.53 1.377 1.9.66 1.267.889 1.389.774 1.52.893 1.627.894 1.828 1.006 2.069.567 1.268.518 1.239.447 1.307.44 1.175.336 1.235.342 1.16.432 2.261.343 2.31.235 2.05v2.891l-.158 1.025-.226 1.768-.308 1.59-.48 1.44-.18.588-.336.707-.28.493-.375.607-.33.383-.42.494-.375.4-.401.34-.48.207-.432.207-.355.114h-.543l-.346-.114-.66-.32-.302-.212-.317-.223-.347-.304-.35-.342-.579-.63-.684-.89-.539-.917-.538-.734-.526-.855-.741-1.517-.833-1.579-.098-.055h-.138l-.338.247-.196.415-.326.516-.567 1.533-.856 2.182-1.096 2.626-.824 1.308-.864 1.366-1.027 1.536-1.09 1.503-.557.68-.676.743-1.555 1.497.136.135.21.214.777.446 3.235 1.524 1.41.779 1.347.756 1.332.953 1.187.982.574.443.432.511.445.593.367.643.198.533.242.64.105.554.115.647-.115.433v.44l-.105.454-.242.415-.092.325-.22.394-.587.784-.543.627-.42.47-.35.348-.893.638-1.01.556-1.077.532-1.155.511-1.287.495-.693.207-.608.167-1.496.342-1.545.325-1.552.323-1.689.27-1.74.072-1.785.21h-5.539l-1.998-.114-1.86-.168-2.005-.27-1.99-.209-2.095-.286-2.03-.495-1.981-.374-1.968-.552-2.019-.707-1.98-.585-1.044-.342-.927-.323-.586-.223-.582-.12h-1.647l-1.904-.131-.962-.096-1.24-.135-.795.705-1.085.665-1.471.701-1.628.875-.99.475-1.033.376-2.281.914-1.24.305-1.3.343-1.803.344-1.13.086-1.193.1-1.246.135-1.45.053h-5.926l-3.346-.053-3.25-.321-1.644-.23-1.589-.23-1.546-.227-1.547-.305-1.442-.456-1.434-.325-1.294-.51-1.223-.474-1.142-.533-.99-.583-.984-.71-.336-.343-.44-.415-.334-.362-.3-.417-.278-.415-.215-.42-.311-.89-.109-.46-.138-.51v-.473l.138-.533v-.53l.109-.53v-1.069l.052-.564.259-.647.215-.646.39-.779.286-.3.236-.348.615-.738.49-.38.464-.266.428-.338.676-.21.543-.324.676-.341.77-.227.775-.231.897-.192.85-.11 1.008-.13 1.093-.081.284-.092h.063l.137-.115v-.13l-.2-.266-.58-.27-1.45-1.231-.975-.761-1.127-.967-1.136-1.082-1.181-1.382-1.36-1.558-.508-.843-.672-.87-.58-1.007-.522-1.1-.704-1.047-.459-1.194-.547-1.192-.546-1.33-.397-1.273-.378-1.575-.112-.057h-.115l-.059-.113h-.14l-.23.113-.114.057-.158.264-.057.321-.119.286-.206.477-.664 1.157-.345.701-.546.612-.58.736-.641.816-.677.724-.795.701-.734.658-.814.524-.89.546-.855.325-1.008.247-.99.095h-.233l-.228-.095-.18-.384-.29-.188-.38-.912-.237-.493-.255-.707-.21-.734-.113-.724-.313-1.648-.12-.972v-3.185l.12-2.379.196-1.214.23-1.252.21-1.347.374-1.254.42-1.443.431-1.407.578-1.448.545-1.38.754-1.4.699-1.52.855-1.425 1.006-1.538 1.023-1.382 1.069-1.538.891-1.071 1.142-1.227 1.202-1.237.56-.59.678-.662.985-.836 1.012-.853 1.647-1.446 1.242-.889z"/></svg>
src/assets/icons/svg/question.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1581238842264" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1409" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 0C229.233778 0 0 229.233778 0 512s229.233778 512 512 512 512-229.233778 512-512A512 512 0 0 0 512 0z m0 938.666667C276.366222 938.666667 85.333333 747.633778 85.333333 512 85.333333 276.366222 276.366222 85.333333 512 85.333333c235.633778 0 426.666667 191.032889 426.666667 426.666667a426.666667 426.666667 0 0 1-426.666667 426.666667z m0-717.653334a170.666667 170.666667 0 0 0-170.666667 170.666667 42.666667 42.666667 0 0 0 85.333334 0 85.333333 85.333333 0 1 1 85.333333 85.333333 42.666667 42.666667 0 0 0-42.666667 42.666667v111.36a42.666667 42.666667 0 0 0 85.333334 0v-74.24A170.666667 170.666667 0 0 0 512 221.013333z m-42.666667 542.293334a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 0 0-85.333334 0z" p-id="1410"></path></svg>
src/assets/icons/svg/radio.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575966775973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="879" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M507.39346659 71.84873358c241.53533667 0 437.39770766 195.85422109 437.39770767 437.37442191 0 241.53766571-195.86237099 437.38955776-437.39770767 437.38955776-241.50040803 0-437.34997219-195.85189205-437.34997219-437.38955776C70.0434944 267.70295467 265.89189347 71.84873358 507.39346659 71.84873358L507.39346659 71.84873358zM507.39346659 282.81899805c-125.00686734 0-226.37039389 101.38914133-226.37039388 226.41813048 0 125.01268821 101.36352768 226.39717262 226.37039388 226.39717262 125.04295993 0 226.42395136-101.38448441 226.42395136-226.39717262C733.81625401 384.20813938 632.43642653 282.81899805 507.39346659 282.81899805L507.39346659 282.81899805zM507.39346659 120.78172615c-214.46664192 0-388.42047261 173.95150279-388.4204726 388.44026539 0 214.51204949 173.95499463 388.46122325 388.4204726 388.46122325 214.52369237 0 388.46005817-173.94800981 388.46005818-388.46122325C895.85236082 294.73322894 721.91715897 120.78172615 507.39346659 120.78172615z" p-id="880"></path></svg>
src/assets/icons/svg/rate.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577246781606" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1098" xmlns:xlink="http://www.w3.org/1999/xlink" width="84.5595703125" height="81"><defs><style type="text/css"></style></defs><path d="M633.72929961 378.02038203l9.49872568 18.68789795 20.78025469 2.79745225 206.61592412 27.33248408a11.46496817 11.46496817 0 0 1 6.6095543 19.47324902l-147.2675168 147.35350284-14.89299345 14.89299345 3.8006376 20.68280244 37.84585956 204.89044571a11.46496817 11.46496817 0 0 1-16.4808914 12.2961788L554.68980898 751.84713388l-18.68789794-9.49299345-18.48726123 9.99171915-183.23885392 99.34968163a11.46496817 11.46496817 0 0 1-16.78471347-11.8662416l32.5433127-205.79617881 3.29617793-20.78598692-15.19108243-14.49172002-151.03375839-143.48407587a11.46496817 11.46496817 0 0 1 6.09936328-19.63949062l205.79617881-32.63503185 20.78598691-3.2961788L428.87898125 380.72038203 518.59235674 192.64331182a11.46496817 11.46496817 0 0 1 20.56815264-0.26369385l94.56879023 185.63503183zM496.64840732 85.52038203l-121.75796162 254.98089229L95.76433145 384.76178369A34.3949045 34.3949045 0 0 0 77.46050938 443.66879023l204.87324901 194.66369385-44.16879023 279.1146498a34.3949045 34.3949045 0 0 0 50.36560489 35.61592325l248.4-134.67898038 251.84522285 128.27579591a34.3949045 34.3949045 0 0 0 49.43694287-36.89426777l-51.30573223-277.85350284 199.73120977-199.90891758a34.3949045 34.3949045 0 0 0-19.82866201-58.40827998l-280.11783428-37.03184736L558.32993633 84.71210205a34.3949045 34.3949045 0 0 0-61.68152901 0.80254775z" p-id="1099"></path></svg>
src/assets/icons/svg/redis.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605865043777" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="856" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1023.786667 611.84c-0.426667 9.770667-13.354667 20.693333-39.893334 34.56-54.613333 28.458667-337.749333 144.896-397.994666 176.298667-60.288 31.402667-93.738667 31.104-141.354667 8.32-47.616-22.741333-348.842667-144.469333-403.114667-170.368-27.093333-12.970667-40.917333-23.893333-41.386666-34.218667v103.509333c0 10.325333 14.250667 21.290667 41.386666 34.261334 54.272 25.941333 355.541333 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.362667 60.245333-31.402667 343.338667-147.797333 397.994666-176.298667 27.776-14.464 40.106667-25.728 40.106667-35.925333v-102.058667l-0.213333-0.085333z m0-168.746667c-0.512 9.770667-13.397333 20.650667-39.893334 34.517334-54.613333 28.458667-337.749333 144.896-397.994666 176.298666-60.288 31.402667-93.738667 31.104-141.354667 8.362667-47.616-22.741333-348.842667-144.469333-403.114667-170.410667-27.093333-12.928-40.917333-23.893333-41.386666-34.176v103.509334c0 10.325333 14.250667 21.248 41.386666 34.218666 54.272 25.941333 355.498667 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.32 60.245333-31.402667 343.338667-147.84 397.994666-176.298666 27.776-14.506667 40.106667-25.770667 40.106667-35.968v-102.058667l-0.256-0.042667z m0-175.018666c0.469333-10.410667-13.141333-19.541333-40.533334-29.610667-53.248-19.498667-334.634667-131.498667-388.522666-151.253333-53.888-19.712-75.818667-18.901333-139.093334 3.84C392.234667 113.706667 92.629333 231.253333 39.338667 252.074667c-26.666667 10.496-39.68 20.181333-39.253334 30.506666V386.133333c0 10.325333 14.250667 21.248 41.386667 34.218667 54.272 25.941333 355.498667 147.669333 403.114667 170.410667 47.616 22.741333 81.066667 23.04 141.354666-8.362667 60.245333-31.402667 343.338667-147.84 397.994667-176.298667 27.776-14.506667 40.106667-25.770667 40.106667-35.968V268.074667h-0.341334zM366.677333 366.08l237.269334-36.437333-71.68 105.088-165.546667-68.650667z m524.8-94.634667l-140.330666 55.466667-15.232 5.973333-140.245334-55.466666 155.392-61.44 140.373334 55.466666z m-411.989333-101.674666l-22.954667-42.325334 71.594667 27.989334 67.498667-22.101334-18.261334 43.733334 68.778667 25.770666-88.704 9.216-19.882667 47.786667-32.085333-53.290667-102.4-9.216 76.416-27.562666z m-176.768 59.733333c70.058667 0 126.805333 21.973333 126.805333 49.109333s-56.746667 49.152-126.805333 49.152-126.848-22.058667-126.848-49.152c0-27.136 56.789333-49.152 126.848-49.152z" p-id="857"></path></svg>
src/assets/icons/svg/row.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579339929870" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M152 854.856875h325.7146875V237.715625H134.856875v600q0 6.99375 5.0746875 12.0684375T152 854.856875z m737.143125-17.1421875v-600H546.284375v617.1421875H872q6.99375 0 12.0684375-5.07375t5.0746875-12.0684375z m68.5715625-651.429375V837.715625q0 35.3821875-25.16625 60.5484375T872 923.4284375H152q-35.383125 0-60.5484375-25.1653125T66.284375 837.7146875V186.284375q0-35.3821875 25.16625-60.5484375T152 100.5715625h720q35.383125 0 60.5484375 25.1653125t25.16625 60.5484375z" p-id="1183"></path></svg>
src/assets/icons/svg/search.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
src/assets/icons/svg/select.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575803481213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="804" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62 511.97954521C62 263.86590869 263.90681826 62 511.97954521 62s449.97954521 201.825 449.97954521 449.97954521c0 248.19545479-201.90681826 449.97954521-449.97954521 449.97954521C263.90681826 962 62 760.175 62 511.97954521M901.98636348 511.97954521c0-215.24318174-175.00909131-390.41590869-390.00681827-390.41590869-215.03863652 0-389.96590869 175.17272695-389.96590868 390.41590869 0 215.28409131 175.00909131 390.45681826 389.96590868 390.45681826C727.01818174 902.47727305 901.98636348 727.30454521 901.98636348 511.97954521M264.17272695 430.28409131c0-5.76818174 2.12727305-11.51590869 6.64772696-15.87272696 8.71363652-8.75454521 22.88863652-8.75454521 31.725 0l209.4340913 208.22727305L721.45454521 414.53409131c8.75454521-8.71363652 22.97045479-8.71363652 31.90909132 0 8.71363652 8.75454521 8.71363652 22.88863652 0 31.60227304L511.97954521 685.74090869 270.71818174 446.01363653C266.27954521 441.77954521 264.17272695 436.05227305 264.17272695 430.28409131" p-id="805"></path></svg>
src/assets/icons/svg/server.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547360688278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M890 120H134a70 70 0 0 0-70 70v500a70 70 0 0 0 70 70h756a70 70 0 0 0 70-70V190a70 70 0 0 0-70-70z m-10 520a40 40 0 0 1-40 40H712V448a40 40 0 0 0-80 0v232h-80V368a40 40 0 0 0-80 0v312h-80V512a40 40 0 0 0-80 0v168H184a40 40 0 0 1-40-40V240a40 40 0 0 1 40-40h656a40 40 0 0 1 40 40zM696 824H328a40 40 0 0 0 0 80h368a40 40 0 0 0 0-80z" fill="#bfbfbf" p-id="6718"></path></svg>
src/assets/icons/svg/shopping.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>
src/assets/icons/svg/size.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>
src/assets/icons/svg/skill.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M31.652 93.206h33.401c1.44 2.418 3.077 4.663 4.93 6.692h-38.33v-6.692zm0-10.586h28.914a44.8 44.8 0 0 1-1.264-6.688h-27.65v6.688zm0-17.27H59.39c.288-2.286.714-4.532 1.34-6.687H31.65v6.687h.003zm53.913 44.84v5.85c0 2.798-2.095 5.075-4.667 5.075h-70.07c-2.576 0-4.663-2.277-4.663-5.075V31.26l23.22-20.96v22.25H17.16v6.688h18.39V6.688h45.348c2.576 0 4.667 2.277 4.667 5.066v20.009c1.987-.675 4.053-1.128 6.17-1.445v-18.56C91.738 5.28 86.874 0 80.902 0H31.15L0 28.118v87.917c0 6.48 4.859 11.759 10.832 11.759h70.07c5.974 0 10.837-5.27 10.837-11.759v-4.41c-2.117-.312-4.183-.765-6.17-1.435h-.004zM23.279 58.667h-7.96v6.688h7.96v-6.688zm-7.956 41.23h7.96v-6.691h-7.96v6.692zm7.956-23.96h-7.96v6.687h7.96v-6.688zm89.718-15.042l-4.896-4.07-12.447 17.613-11.19-9.305-3.762 5.311 16.091 13.38 16.204-22.929zM128 70.978c0-18.632-13.97-33.782-31.147-33.782-17.168 0-31.135 15.155-31.135 33.782 0 18.628 13.97 33.783 31.135 33.783 17.172 0 31.143-15.15 31.143-33.783H128zm-6.17 0c0 14.933-11.203 27.1-24.981 27.1-13.77 0-24.987-12.158-24.987-27.1 0-14.941 11.195-27.099 24.987-27.099 13.778 0 24.982 12.158 24.982 27.1z"/></svg>
src/assets/icons/svg/slider.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577185310368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M951.453125 476.84375H523.671875a131.8359375 131.8359375 0 0 0-254.1796875 0H72.546875v70.3125h196.9453125a131.8359375 131.8359375 0 0 0 254.1796875 0H951.453125z" p-id="1239"></path></svg>
src/assets/icons/svg/star.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M70.66 4.328l14.01 29.693c1.088 2.29 3.177 3.882 5.603 4.25l31.347 4.76c6.087.926 8.528 8.756 4.117 13.247L103.05 79.395c-1.75 1.78-2.544 4.352-2.132 6.867l5.352 32.641c1.043 6.337-5.33 11.182-10.778 8.19l-28.039-15.409a7.13 7.13 0 0 0-6.91 0l-28.039 15.41c-5.448 2.99-11.821-1.854-10.777-8.19l5.352-32.642c.415-2.515-.387-5.088-2.136-6.867L2.264 56.278C-2.146 51.787.286 43.957 6.38 43.031l31.343-4.76c2.419-.368 4.51-1.96 5.595-4.25L57.334 4.328c2.728-5.77 10.605-5.77 13.325 0z"/></svg>
src/assets/icons/svg/swagger.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036776944" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6463" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M64 223.995345h168.001164v47.997673c0 26.428509 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.569164 41.984-47.997673v-47.997673h504.003491a32.004655 32.004655 0 0 0 0-64.009309H455.996509V111.988364c0-26.428509-18.878836-47.997673-41.984-47.997673H273.985164c-23.095855 0-41.984 21.569164-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.009309zM288.004655 128h111.997672V256H288.004655V128zM960 479.995345H791.998836v-47.997672c0-26.372655-18.878836-47.997673-41.984-47.997673H609.978182c-23.095855 0-41.984 21.634327-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.00931h504.003491v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h168.001164a32.004655 32.004655 0 1 0-0.009309-64.00931zM735.995345 576H623.997673v-128h111.997672v128zM960 800.293236v-0.288581H455.996509v-47.997673c0-26.363345-18.878836-47.997673-41.984-47.997673H274.050327c-23.105164 0-41.984 21.634327-41.984 47.997673v47.997673H64v0.288581a32.004655 32.004655 0 0 0 0 64.009309c0.986764 0 1.917673-0.195491 2.885818-0.288581h165.115346v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h501.108364c0.968145 0.093091 1.899055 0.288582 2.895127 0.288581a32.004655 32.004655 0 1 0-0.009309-64.009309zM400.002327 896H288.004655V768h111.997672v128z" fill="" p-id="6464"></path></svg>
src/assets/icons/svg/switch.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576042673958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1110" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M692 792H332c-150 0-270-120-270-270s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="1111"></path><path d="M341 522m-150 0a150 150 0 1 0 300 0 150 150 0 1 0-300 0Z" p-id="1112"></path></svg>
src/assets/icons/svg/system.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234" fill="#bfbfbf"></path></svg>
src/assets/icons/svg/tab.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.921.052H49.08c-1.865 0-3.198 1.599-3.198 3.464v6.661c0 1.865 1.6 3.464 3.198 3.464h29.84c1.865 0 3.198-1.599 3.198-3.464V3.516C82.385 1.65 80.786.052 78.92.052zm45.563 0H94.642c-1.865 0-3.464 1.599-3.464 3.464v6.661c0 1.865 1.599 3.464 3.464 3.464h29.842c1.865-.266 3.464-1.599 3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464zm0 22.382H40.02c-1.866 0-3.464-1.599-3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464H3.516C1.65.052.052 1.651.052 3.516V124.75c0 1.598 1.599 3.197 3.464 3.197h120.968c1.865 0 3.464-1.599 3.464-3.464V25.898c0-1.865-1.599-3.464-3.464-3.464z"/></svg>
src/assets/icons/svg/table.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
src/assets/icons/svg/textarea.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802855098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2984" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64z m0 608c0 16-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V256c0-16 12.8-32 32-32h704c19.2 0 32 12.8 32 32v512z" p-id="2985"></path><path d="M224 288c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V320c0-16-12.8-32-32-32z m608 480c19.2 0 32-12.8 32-32V608L704 768h128z" p-id="2986"></path></svg>
src/assets/icons/svg/theme.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M125.5 36.984L95.336 2.83C93.735 1.018 91.565 0 89.3 0c-2.263 0-4.433 1.018-6.033 2.83l-3.786 4.286c-1.6 1.812-3.77 2.83-6.032 2.831H54.553c-2.263 0-4.434-1.018-6.033-2.83L44.734 2.83C43.134 1.018 40.964 0 38.701 0c-2.263 0-4.434 1.018-6.034 2.83L2.5 36.984C.9 38.796 0 41.254 0 43.815c0 2.562.899 5.02 2.5 6.831L14.565 64.31c2.178 2.468 5.367 3.403 8.33 2.444 1.35-.435 2.709.592 2.709 2.18v49.407c0 5.313 3.84 9.66 8.532 9.66h59.726c4.693 0 8.532-4.347 8.532-9.66V68.934c0-1.59 1.36-2.616 2.71-2.181 2.962.96 6.15.024 8.329-2.444L125.5 50.646c1.6-1.811 2.499-4.269 2.499-6.83 0-2.563-.899-5.02-2.5-6.832z"/></svg>
src/assets/icons/svg/time-range.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774825624" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1248" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.595712 482.290351 345.420077 482.290351l0 57.307194 210.477712 0L555.897789 274.196942l-57.301054 0L498.596735 482.290351zM498.595712 482.290351" p-id="1249"></path><path d="M577.685002 644.98478l379.879913 0 0 57.302077L577.685002 702.286858 577.685002 644.98478 577.685002 644.98478zM577.685002 644.98478" p-id="1250"></path><path d="M577.685002 773.764795l379.879913 0 0 57.307194L577.685002 831.071989 577.685002 773.764795 577.685002 773.764795zM577.685002 773.764795" p-id="1251"></path><path d="M577.685002 902.549927l379.879913 0 0 57.307194L577.685002 959.857121 577.685002 902.549927 577.685002 902.549927zM577.685002 902.549927" p-id="1252"></path><path d="M102.523001 382.290823c4.450359 2.615571 9.470699 3.954055 14.530948 3.954055 2.969635 0 5.952572-0.461511 8.836249-1.394766l190.809767-61.886489c15.052834-4.882194 23.297612-21.040199 18.415418-36.08894-4.882194-15.052834-21.040199-23.297612-36.093033-18.415418L175.676092 308.458257c15.994276-26.115797 35.170011-50.537 57.370639-72.743768 73.767074-73.767074 171.845857-114.388237 276.16783-114.388237 104.32095 0 202.39564 40.622186 276.16169 114.388237s114.393353 171.845857 114.393353 276.16783c0 26.427906-2.615571 52.449559-7.709589 77.780481l58.302871 0c4.464685-25.499767 6.708795-51.470255 6.708795-77.780481 0-60.449767-11.845793-119.102608-35.204803-174.336584-22.559808-53.334719-54.850236-101.226472-95.968725-142.349055-41.122583-41.122583-89.017406-73.408917-142.348032-95.968725C628.317169 75.866898 569.659211 64.021106 509.215584 64.021106c-60.448744 0-119.106702 11.845793-174.336584 35.207873-53.334719 22.559808-101.230566 54.846142-142.349055 95.968725-23.980157 23.980157-44.934398 50.278103-62.727647 78.601172l-20.738323-105.655342c-3.043313-15.527648-18.105357-25.642007-33.631982-22.599717-15.527648 3.048429-25.64303 18.105357-22.599717 33.637098l36.102243 183.932126C90.51348 371.153158 95.460142 378.13313 102.523001 382.290823L102.523001 382.290823zM102.523001 382.290823" p-id="1253"></path><path d="M126.020158 587.9416 67.768453 587.9416c5.759167 33.679054 15.368012 66.544579 28.789697 98.278327 22.559808 53.333696 54.850236 101.225449 95.971795 142.348032 41.122583 41.122583 89.014336 73.408917 142.349055 95.968725 54.112432 22.88829 111.517863 34.71157 170.668031 35.18229L505.547031 902.395408c-102.94972-0.941442-199.594851-41.445948-272.499277-114.349351C177.545672 732.543975 140.810003 663.275355 126.020158 587.9416L126.020158 587.9416zM126.020158 587.9416" p-id="1254"></path></svg>
src/assets/icons/svg/time.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577099827399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M520 559h204c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H488c-17.673 0-32-14.327-32-32 0-0.167 0.001-0.334 0.004-0.5a32.65 32.65 0 0 1-0.004-0.5V277c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32v282z m-8 401C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-64c212.077 0 384-171.923 384-384S724.077 128 512 128 128 299.923 128 512s171.923 384 384 384z" p-id="1009"></path></svg>
src/assets/icons/svg/tool.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1553828490559" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1684" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M898.831744 900.517641 103.816972 900.517641c-36.002982 0-65.363683-29.286-65.363683-65.313541l0-554.949184c0-36.041868 29.361725-65.326844 65.363683-65.326844l795.015795 0c36.002982 0 65.198931 29.284977 65.198931 65.326844l0 554.949184C964.030675 871.231641 934.834726 900.517641 898.831744 900.517641L898.831744 900.517641zM103.816972 255.593236c-13.576203 0-24.711821 11.085476-24.711821 24.662703l0 554.949184c0 13.576203 11.136641 24.662703 24.711821 24.662703l795.015795 0c13.577227 0 24.547069-11.086499 24.547069-24.662703l0-554.949184c0-13.577227-10.970866-24.662703-24.547069-24.662703L103.816972 255.593236 103.816972 255.593236zM664.346245 251.774257c-11.161201 0-20.332071-9.080819-20.332071-20.332071l0-101.278661c0-13.576203-11.047614-24.623817-24.699542-24.623817L383.181611 105.539708c-13.576203 0-24.712845 11.04659-24.712845 24.623817l0 101.278661c0 11.252275-9.041934 20.332071-20.332071 20.332071-11.20111 0-20.319791-9.080819-20.319791-20.332071l0-101.278661c0-35.989679 29.323862-65.275679 65.364707-65.275679l236.133022 0c36.06745 0 65.402569 29.284977 65.402569 65.275679l0 101.278661C684.717202 242.694461 675.636383 251.774257 664.346245 251.774257L664.346245 251.774257zM413.233044 521.725502 75.694471 521.725502c-11.163247 0-20.333094-9.117658-20.333094-20.35663 0-11.252275 9.169847-20.332071 20.333094-20.332071l337.538573 0c11.277858 0 20.319791 9.080819 20.319791 20.332071C433.552835 512.607844 424.510902 521.725502 413.233044 521.725502L413.233044 521.725502zM912.894018 521.725502 575.367725 521.725502c-11.213389 0-20.332071-9.117658-20.332071-20.35663 0-11.252275 9.118682-20.332071 20.332071-20.332071l337.526293 0c11.290137 0 20.332071 9.080819 20.332071 20.332071C933.226089 512.607844 924.184155 521.725502 912.894018 521.725502L912.894018 521.725502zM557.56322 634.217552 445.085496 634.217552c-11.213389 0-20.332071-9.079796-20.332071-20.331048l0-168.763658c0-11.251252 9.118682-20.332071 20.332071-20.332071l112.478747 0c11.290137 0 20.370956 9.080819 20.370956 20.332071l0 168.763658C577.934177 625.137757 568.853357 634.217552 557.56322 634.217552L557.56322 634.217552zM465.417567 593.514525l71.827909 0L537.245476 465.454918l-71.827909 0L465.417567 593.514525 465.417567 593.514525z" p-id="1685" fill="#bfbfbf"></path></svg>
src/assets/icons/svg/tree-table.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg>
src/assets/icons/svg/tree.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
src/assets/icons/svg/upload.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg>
src/assets/icons/svg/user.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
src/assets/icons/svg/validCode.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569580729849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M513.3 958.5c-142.2 0-397.9-222.1-401.6-440.5V268c1.7-39.6 31.7-72.3 71.1-77.3 49-4.6 97.1-16.5 142.7-35.3 47.8-14 91.9-38.3 129.4-71.1 30.3-24.4 72.9-26.3 105.3-4.6 39.9 30.7 83.8 55.9 130.5 74.6 48.6 14.7 98.2 25.9 148.4 33.7 38.5 7.6 67.1 40.3 69.5 79.5 3.3 84.9 2.5 169.9-2.6 254.7-33.7 281.6-253.7 436.4-392.7 436.3z m-0.1-813.7c-7.2-0.2-14.3 2-20 6.4-39.7 35.2-86.8 61.1-137.7 75.7-46.8 19.2-96.2 31-146.6 35.2-11 3.2-18.8 13-19.5 24.4v230.1c3.5 180.3 223.3 361 323.9 361s287.3-120.2 317.6-360.5c7.3-142.7 0-228.6 0-229.6-1.3-13.3-11-24.3-24-27.3-49.6-7.7-98.6-19-146.5-33.7-46.3-19.5-89.7-45.3-129-76.7-5.8-3.8-12.7-5.5-19.5-4.9l1.3-0.1z" fill="#C6CCDA" p-id="1940"></path><path d="M750.1 428L490.7 673.2c-11.7 11.1-29.5 12.9-43.1 4.2l-6.8-5.8-141.2-149.4c-9.3-9.3-12.7-22.9-9-35.5 3.8-12.6 14.1-22.1 27-24.8 12.9-2.7 26.1 1.9 34.6 11.9L469 597.5l233.7-221c14.6-12.8 36.8-11.6 49.9 2.7 13.2 14.2 11.5 35.3-2.5 48.8" fill="#C6CCDA" p-id="1941"></path></svg>
src/assets/icons/svg/wechat.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>
src/assets/icons/svg/zip.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.527 116.793c.178.008.348.024.527.024h40.233c4.711-.005 8.53-3.677 8.534-8.21V18.895c-.004-4.532-3.823-8.204-8.534-8.209H79.054c-.179 0-.353.016-.527.024V0L0 10.082v107.406l78.527 10.342v-11.037zm0-101.362c.174-.024.348-.052.527-.052h40.233c2.018 0 3.659 1.578 3.659 3.52v89.713c-.003 1.942-1.64 3.517-3.659 3.519H79.054c-.179 0-.353-.028-.527-.052V15.431zM30.262 75.757l-18.721-.46V72.37l11.3-16.673v-.148l-10.266.164v-4.51l17.504-.44v3.264L18.696 70.76v.144l11.566.176v4.678zm9.419.231l-5.823-.144V50.671l5.823-.144v25.461zm22.255-11.632c-2.168 1.922-5.353 2.76-9.02 2.736-.702.004-1.402-.04-2.097-.131v9.303l-5.997-.148V50.743c1.852-.352 4.473-.647 8.218-.743 3.838-.096 6.608.539 8.48 1.913 1.807 1.306 3.032 3.5 3.032 6.112s-.926 4.833-2.612 6.331h-.004zM53.36 54.45c-.856-.01-1.71.083-2.541.275v7.682c.523.116 1.167.152 2.06.152 3.301-.004 5.36-1.614 5.36-4.314 0-2.425-1.772-3.843-4.875-3.791l-.004-.004zm39.847-37.066h9.564v3.795h-9.564v-3.795zm-9.568 5.68h9.564v3.8h-9.564v-3.8zm9.568 6.216h9.564v3.799h-9.564V29.28zm0 12h9.564v3.794h-9.564V41.28zm-9.568-6.096h9.564v3.795h-9.564v-3.795zm9.472 47.064c2.512 0 4.921-.96 6.697-2.67 1.776-1.708 2.773-4.026 2.772-6.442l-1.748-15.263c0-5.033-2.492-9.112-7.725-9.112-5.232 0-7.72 4.079-7.72 9.112l-1.752 15.263c-.001 2.417.996 4.735 2.773 6.444 1.777 1.71 4.187 2.669 6.7 2.668h.003zm-3.135-16.75h6.27v12.743h-6.27V65.5z"/></svg>
src/assets/images/dark.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
            <feMerge>
                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
                <feMergeNode in="SourceGraphic"></feMergeNode>
            </feMerge>
        </filter>
        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
        </filter>
    </defs>
    <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
            <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
                    <mask id="mask-3" fill="white">
                        <use xlink:href="#path-2"></use>
                    </mask>
                    <g id="Rectangle-18">
                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
                    </g>
                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
                </g>
            </g>
        </g>
    </g>
</svg>
src/assets/images/light.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
            <feMerge>
                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
                <feMergeNode in="SourceGraphic"></feMergeNode>
            </feMerge>
        </filter>
        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
        </filter>
    </defs>
    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
                <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
                    <mask id="mask-3" fill="white">
                        <use xlink:href="#path-2"></use>
                    </mask>
                    <g id="Rectangle-18">
                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
                    </g>
                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
                </g>
            </g>
        </g>
    </g>
</svg>
src/assets/images/login-background.jpg
src/assets/images/profile.jpg
src/assets/logo/logo.png
src/assets/styles/btn.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
@import './variables.module.scss';
@mixin colorBtn($color) {
  background: $color;
  &:hover {
    color: $color;
    &:before,
    &:after {
      background: $color;
    }
  }
}
.blue-btn {
  @include colorBtn($blue)
}
.light-blue-btn {
  @include colorBtn($light-blue)
}
.red-btn {
  @include colorBtn($red)
}
.pink-btn {
  @include colorBtn($pink)
}
.green-btn {
  @include colorBtn($green)
}
.tiffany-btn {
  @include colorBtn($tiffany)
}
.yellow-btn {
  @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;
  &:hover {
    background: #fff;
    &: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;
  }
  &::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;
}
src/assets/styles/element-ui.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
// cover some element-ui styles
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
  font-weight: 400 !important;
}
.el-upload {
  input[type="file"] {
    display: none !important;
  }
}
.el-upload__input {
  display: none;
}
.cell {
  .el-tag {
    margin-right: 0px;
  }
}
.small-padding {
  .cell {
    padding-left: 5px;
    padding-right: 5px;
  }
}
.fixed-width {
  .el-button--mini {
    padding: 7px 10px;
    width: 60px;
  }
}
.status-col {
  .cell {
    padding: 0 10px;
    text-align: center;
    .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;
}
// refine element ui upload
.upload-container {
  .el-upload {
    width: 100%;
    .el-upload-dragger {
      width: 100%;
      height: 200px;
    }
  }
}
// dropdown
.el-dropdown-menu {
  a {
    display: block
  }
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
  display: inline-flex !important;
}
// to fix el-date-picker css style
.el-range-separator {
  box-sizing: content-box;
}
.el-menu--collapse
  > div
  > .el-submenu
  > .el-submenu__title
  .el-submenu__icon-arrow {
  display: none;
}
.el-dropdown .el-dropdown-link{
  color: var(--el-color-primary) !important;
}
src/assets/styles/index.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
@import './variables.module.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';
@import './ruoyi.scss';
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;
}
label {
  font-weight: 700;
}
html {
  height: 100%;
  box-sizing: border-box;
}
#app {
  height: 100%;
}
*,
*:before,
*:after {
  box-sizing: inherit;
}
.no-padding {
  padding: 0px !important;
}
.padding-content {
  padding: 4px 0;
}
a:focus,
a:active {
  outline: none;
}
a,
a:focus,
a:hover {
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}
div:focus {
  outline: none;
}
.fr {
  float: right;
}
.fl {
  float: left;
}
.pr-5 {
  padding-right: 5px;
}
.pl-5 {
  padding-left: 5px;
}
.block {
  display: block;
}
.pointer {
  cursor: pointer;
}
.inlineBlock {
  display: block;
}
.clearfix {
  &: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;
  a {
    color: #337ab7;
    cursor: pointer;
    &:hover {
      color: rgb(32, 160, 255);
    }
  }
}
//main-container全局样式
.app-container {
  padding: 20px;
}
.components-container {
  margin: 30px 50px;
  position: relative;
}
.pagination-container {
  margin-top: 30px;
}
.text-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%);
  .subtitle {
    font-size: 20px;
    color: #fff;
  }
  &.draft {
    background: #d0d0d0;
  }
  &.deleted {
    background: #d0d0d0;
  }
}
.link-type,
.link-type:focus {
  color: #337ab7;
  cursor: pointer;
  &:hover {
    color: rgb(32, 160, 255);
  }
}
.filter-container {
  padding-bottom: 10px;
  .filter-item {
    display: inline-block;
    vertical-align: middle;
    margin-bottom: 10px;
  }
}
//refine vue-multiselect plugin
.multiselect {
  line-height: 16px;
}
.multiselect--active {
  z-index: 1000 !important;
}
src/assets/styles/mixin.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
@mixin clearfix {
  &:after {
    content: "";
    display: table;
    clear: both;
  }
}
@mixin scrollBar {
  &::-webkit-scrollbar-track-piece {
    background: #d3dce6;
  }
  &::-webkit-scrollbar {
    width: 6px;
  }
  &::-webkit-scrollbar-thumb {
    background: #99a9bf;
    border-radius: 20px;
  }
}
@mixin relative {
  position: relative;
  width: 100%;
  height: 100%;
}
@mixin pct($pct) {
  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;
  @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
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,273 @@
 /**
 * é€šç”¨css样式布局处理
 * Copyright (c) 2019 ruoyi
 */
 /** åŸºç¡€é€šç”¨ **/
.pt5 {
    padding-top: 5px;
}
.pr5 {
    padding-right: 5px;
}
.pb5 {
    padding-bottom: 5px;
}
.mt5 {
    margin-top: 5px;
}
.mr5 {
    margin-right: 5px;
}
.mb5 {
    margin-bottom: 5px;
}
.mb8 {
    margin-bottom: 8px;
}
.ml5 {
    margin-left: 5px;
}
.mt10 {
    margin-top: 10px;
}
.mr10 {
    margin-right: 10px;
}
.mb10 {
    margin-bottom: 10px;
}
.ml0 {
    margin-left: 10px;
}
.mt20 {
    margin-top: 20px;
}
.mr20 {
    margin-right: 20px;
}
.mb20 {
    margin-bottom: 20px;
}
.m20 {
    margin-left: 20px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
    font-family: inherit;
    font-weight: 500;
    line-height: 1.1;
    color: inherit;
}
.el-dialog:not(.is-fullscreen) {
    margin-top: 6vh !important;
}
.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body {
    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;
        }
    }
}
/** è¡¨å•布局 **/
.form-header {
    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;
}
/* tree border */
.tree-border {
    margin-top: 5px;
    border: 1px solid #e5e6e7;
    background: #FFFFFF none;
    border-radius:4px;
}
.pagination-container .el-pagination {
    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;
  }
}
.el-table .fixed-width .el-button--mini {
    padding-left: 0;
    padding-right: 0;
    width: inherit;
}
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
    cursor: pointer;
    color: #409EFF;
    margin-left: 5px;
}
.el-table .el-dropdown, .el-icon-arrow-down {
    font-size: 12px;
}
.el-tree-node__content > .el-checkbox {
    margin-right: 8px;
}
.list-group-striped > .list-group-item {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
}
.list-group {
    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;
}
.pull-right {
    float: right !important;
}
.el-card__header {
    padding: 14px 15px 7px;
    min-height: 40px;
}
.el-card__body {
    padding: 15px 20px 20px 20px;
}
.card-box {
    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;
}
.el-button--cyan:focus,
.el-button--cyan:hover {
  background: #48D1CC;
  border-color: #48D1CC;
  color: #FFFFFF;
}
.el-button--cyan {
  background-color: #20B2AA;
  border-color: #20B2AA;
  color: #FFFFFF;
}
/* text color */
.text-navy {
    color: #1ab394;
}
.text-primary {
    color: inherit;
}
.text-success {
    color: #1c84c6;
}
.text-info {
    color: #23c6c8;
}
.text-warning {
    color: #f8ac59;
}
.text-danger {
    color: #ed5565;
}
.text-muted {
    color: #888888;
}
/* image */
.img-circle {
    border-radius: 50%;
}
.img-lg {
    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;
}
/* æ‹–拽列样式 */
.sortable-ghost{
    opacity: .8;
    color: #fff!important;
    background: #42b983!important;
}
.top-right-btn {
    position: absolute;
    right: 0;
}
src/assets/styles/sidebar.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,230 @@
#app {
  .main-container {
    min-height: 100%;
    transition: margin-left .28s;
    margin-left: $base-sidebar-width;
    position: relative;
  }
  .sidebar-container {
    -webkit-transition: width .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,.35);
    box-shadow: 2px 0 6px rgba(0,21,41,.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;
    }
    .scrollbar-wrapper {
      overflow-x: hidden !important;
    }
    .el-scrollbar__bar.is-vertical {
      right: 0px;
    }
    .el-scrollbar {
      height: 100%;
    }
    &.has-logo {
      .el-scrollbar {
        height: calc(100% - 50px);
      }
    }
    .is-horizontal {
      display: none;
    }
    a {
      display: inline-block;
      width: 100%;
      overflow: hidden;
    }
    .svg-icon {
      margin-right: 16px;
    }
    .el-menu {
      border: none;
      height: 100%;
      width: 100% !important;
    }
    .el-menu-item, .el-sub-menu__title {
      overflow: hidden !important;
      text-overflow: ellipsis !important;
      white-space: nowrap !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;
    }
    & .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;
      }
    }
    & .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;
      }
    }
  }
  .hideSidebar {
    .sidebar-container {
      width: 54px !important;
    }
    .main-container {
      margin-left: 54px;
    }
    .sub-menu-title-noDropdown {
      padding: 0 !important;
      position: relative;
      .el-tooltip {
        padding: 0 !important;
        .svg-icon {
          margin-left: 20px;
        }
      }
    }
    .el-sub-menu {
      overflow: hidden;
      &>.el-sub-menu__title {
        padding: 0 !important;
        .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-menu .el-sub-menu {
    min-width: $base-sidebar-width !important;
  }
  // mobile responsive
  .mobile {
    .main-container {
      margin-left: 0px;
    }
    .sidebar-container {
      transition: transform .28s;
      width: $base-sidebar-width !important;
    }
    &.hideSidebar {
      .sidebar-container {
        pointer-events: none;
        transition-duration: 0.3s;
        transform: translate3d(-$base-sidebar-width, 0, 0);
      }
    }
  }
  .withoutAnimation {
    .main-container,
    .sidebar-container {
      transition: none;
    }
  }
}
// when menu collapsed
.el-menu--vertical {
  &>.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;
    }
  }
  // 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 {
      width: 6px;
    }
    &::-webkit-scrollbar-thumb {
      background: #99a9bf;
      border-radius: 20px;
    }
  }
}
src/assets/styles/transition.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
// global transition css
/* fade */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
  opacity: 0;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
  transition: all .5s;
}
.fade-transform-enter {
  opacity: 0;
  transform: translateX(-30px);
}
.fade-transform-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
  transition: all .5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
  opacity: 0;
  transform: translateX(20px);
}
.breadcrumb-move {
  transition: all .5s;
}
.breadcrumb-leave-active {
  position: absolute;
}
src/assets/styles/variables.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
// base color
$blue: #324157;
$light-blue: #3A71A8;
$red: #C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow: #FEC171;
$panGreen: #30B08F;
// é»˜è®¤èœå•主题风格
$base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528;
// è‡ªå®šä¹‰æš—色菜单风格
/**
$base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff;
$base-menu-background:#001529;
$base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528;
*/
$--color-primary: #409EFF;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;
$base-sidebar-width: 200px;
// 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;
}
src/components/Breadcrumb/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
<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>
const route = useRoute();
const router = useRouter();
const levelList = ref([])
function 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: '首页' } }].concat(matched)
  }
  levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
function isDashboard(route) {
  const name = route && route.name
  if (!name) {
    return false
  }
  return name.trim() === 'Index'
}
function handleLink(item) {
  const { redirect, path } = item
  if (redirect) {
    router.push(redirect)
    return
  }
  router.push(path)
}
watchEffect(() => {
  // if you go to the redirect page, do not update the breadcrumbs
  if (route.path.startsWith('/redirect/')) {
    return
  }
  getBreadcrumb()
})
getBreadcrumb();
</script>
<style lang='scss' scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;
  .no-redirect {
    color: #97a8be;
    cursor: text;
  }
}
</style>
src/components/DictTag/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +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.elTagType"
        >{{ item.label }}</span>
        <el-tag
          v-else
          :disable-transitions="true"
          :key="item.value + ''"
          :index="index"
          :type="item.elTagType === 'primary' ? '' : item.elTagType"
          :class="item.elTagType"
        >{{ item.label }}</el-tag>
      </template>
    </template>
  </div>
</template>
<script setup>
const props = defineProps({
  // æ•°æ®
  options: {
    type: Array,
    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 [];
  }
})
</script>
<style scoped>
.el-tag + .el-tag {
  margin-left: 10px;
}
</style>
src/components/Hamburger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
<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>
</template>
<script setup>
defineProps({
  isActive: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits()
const toggleClick = () => {
  emit('toggleClick');
}
</script>
<style scoped>
.hamburger {
  display: inline-block;
  vertical-align: middle;
  width: 20px;
  height: 20px;
}
.hamburger.is-active {
  transform: rotate(180deg);
}
</style>
src/components/HeaderSearch/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,180 @@
<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>
</template>
<script setup>
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate'
const search = ref('');
const options = ref([]);
const searchPool = ref([]);
const show = ref(false);
const fuse = ref(undefined);
const headerSearchSelectRef = ref(null);
const store = useStore();
const router = useRouter();
const routes = computed(() => store.getters.permission_routes);
function click() {
  show.value = !show.value
  if (show.value) {
    headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
  }
};
function close() {
  headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
  options.value = []
  show.value = false
}
function change(val) {
  const path = val.path;
  if (isHttp(path)) {
    // http(s):// è·¯å¾„新窗口打开
    const pindex = path.indexOf("http");
    window.open(path.substr(pindex, path.length), "_blank");
  } else {
    router.push(path)
  }
  search.value = ''
  options.value = []
  nextTick(() => {
    show.value = false
  })
}
function initFuse(list) {
  fuse.value = new Fuse(list, {
    shouldSort: true,
    threshold: 0.4,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [{
      name: 'title',
      weight: 0.7
    }, {
      name: 'path',
      weight: 0.3
    }]
  })
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
function generateRoutes(routes, basePath = '', prefixTitle = []) {
  let res = []
  for (const r of routes) {
    // skip hidden router
    if (r.hidden) { continue }
    const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
    const data = {
      path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
      title: [...prefixTitle]
    }
    if (r.meta && r.meta.title) {
      data.title = [...data.title, r.meta.title]
      if (r.redirect !== 'noRedirect') {
        // only push the routes with title
        // special case: need to exclude parent router without redirect
        res.push(data)
      }
    }
    // recursive child routes
    if (r.children) {
      const tempRoutes = generateRoutes(r.children, data.path, data.title)
      if (tempRoutes.length >= 1) {
        res = [...res, ...tempRoutes]
      }
    }
  }
  return res
}
function querySearch(query) {
  if (query !== '') {
    options.value = fuse.value.search(query)
  } else {
    options.value = []
  }
}
onMounted(() => {
  searchPool.value = generateRoutes(routes.value);
})
watchEffect(() => {
  searchPool.value = generateRoutes(routes.value)
})
watch(show, (value) => {
  if (value) {
    document.body.addEventListener('click', close)
  } else {
    document.body.removeEventListener('click', close)
  }
})
watch(searchPool, (list) => {
  initFuse(list)
})
</script>
<style lang='scss' scoped>
.header-search {
  font-size: 0 !important;
  .search-icon {
    cursor: pointer;
    font-size: 18px;
    vertical-align: middle;
  }
  .header-search-select {
    font-size: 18px;
    transition: width 0.2s;
    width: 0;
    overflow: hidden;
    background: transparent;
    border-radius: 0;
    display: inline-block;
    vertical-align: middle;
    :deep(.el-input__inner) {
      border-radius: 0;
      border: 0;
      padding-left: 0;
      padding-right: 0;
      box-shadow: none !important;
      border-bottom: 1px solid #d9d9d9;
      vertical-align: middle;
    }
  }
  &.show {
    .header-search-select {
      width: 210px;
      margin-left: 10px;
    }
  }
}
</style>
src/components/IconSelect/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
<template>
  <div class="icon-body">
    <el-input
      v-model="iconName"
      style="position: relative;"
      clearable
      placeholder="请输入图标名称"
      @clear="filterIcons"
      @input="filterIcons"
    >
      <template #suffix><i class="el-icon-search el-input__icon" /></template>
    </el-input>
    <div class="icon-list">
      <div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
        <svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
        <span>{{ item }}</span>
      </div>
    </div>
  </div>
</template>
<script setup>
import icons from './requireIcons'
const iconName = ref('');
const iconList = ref(icons);
const emit = defineEmits(['selected']);
function filterIcons() {
  iconList.value = icons
  if (iconName.value) {
    iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
  }
}
function selectedIcon(name) {
  emit('selected', name)
  document.body.click()
}
function reset() {
  iconName.value = ''
  iconList.value = icons
}
defineExpose({
  reset
})
</script>
<style lang='scss' scoped>
.icon-body {
  width: 100%;
  padding: 10px;
  .icon-list {
    height: 200px;
    overflow-y: scroll;
    div {
      height: 30px;
      line-height: 30px;
      margin-bottom: -5px;
      cursor: pointer;
      width: 33%;
      float: left;
    }
    span {
      display: inline-block;
      vertical-align: -0.15em;
      fill: currentColor;
      overflow: hidden;
    }
  }
}
</style>
src/components/IconSelect/requireIcons.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
let icons = []
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);
}
export default icons
src/components/Pagination/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
<template>
  <div :class="{ 'hidden': hidden }" class="pagination-container">
    <el-pagination
      :background="background"
      v-model:current-page="currentPage"
      :page-size="pageSize"
      :layout="layout"
      :page-sizes="pageSizes"
      :pager-count="pagerCount"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>
<script setup>
import { scrollTo } from '@/utils/scroll-to'
const props = defineProps({
  total: {
    required: true,
    type: Number
  },
  page: {
    type: Number,
    default: 1
  },
  limit: {
    type: Number,
    default: 20
  },
  pageSizes: {
    type: Array,
    default() {
      return [10, 20, 30, 50]
    }
  },
  // ç§»åŠ¨ç«¯é¡µç æŒ‰é’®çš„æ•°é‡ç«¯é»˜è®¤å€¼5
  pagerCount: {
    type: Number,
    default: document.body.clientWidth < 992 ? 5 : 7
  },
  layout: {
    type: String,
    default: 'total, sizes, prev, pager, next, jumper'
  },
  background: {
    type: Boolean,
    default: true
  },
  autoScroll: {
    type: Boolean,
    default: true
  },
  hidden: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits();
const currentPage = computed({
  get() {
    return props.page
  },
  set(val) {
    emit('update:page', val)
  }
})
const pageSize = computed({
  get() {
    return props.limit
  },
  set(val){
    emit('update:limit', val)
  }
})
function handleSizeChange(val) {
  emit('pagination', { page: currentPage.value, limit: val })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
function handleCurrentChange(val) {
  emit('pagination', { page: val, limit: pageSize.value })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
</script>
<style scoped>
.pagination-container {
  background: #fff;
  padding: 32px 16px;
}
.pagination-container.hidden {
  display: none;
}
</style>
src/components/ParentView/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
<template >
  <router-view />
</template>
src/components/RightToolbar/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
<template>
  <div class="top-right-btn">
    <el-row>
      <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
        <el-button size="mini" circle icon="Search" @click="toggleSearch()" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="刷新" placement="top">
        <el-button size="mini" circle icon="Refresh" @click="refresh()" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
        <el-button size="mini" 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>
const props = defineProps({
  showSearch: {
    type: Boolean,
    default: true,
  },
  columns: {
    type: Array,
  },
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
// æ˜¾é𐿕°æ®
const value = ref([]);
// å¼¹å‡ºå±‚标题
const title = ref("显示/隐藏");
// æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
const open = ref(false);
// æœç´¢
function toggleSearch() {
  emits("update:showSearch", !props.showSearch);
}
// åˆ·æ–°
function refresh() {
  emits("queryTable");
}
// å³ä¾§åˆ—表元素变化
function dataChange(data) {
  for (let item in props.columns) {
    const key = props.columns[item].key;
    props.columns[item].visible = !data.includes(key);
  }
}
// æ‰“开显隐列dialog
function showColumn() {
  open.value = true;
}
// æ˜¾éšåˆ—初始默认隐藏列
for (let item in props.columns) {
  if (props.columns[item].visible === false) {
    value.value.push(parseInt(item));
  }
}
</script>
<style lang='scss' scoped>
:deep(.el-transfer__button) {
  border-radius: 50%;
  padding: 12px;
  display: block;
  margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
  margin-bottom: 10px;
}
.my-el-transfer {
  text-align: center;
}
</style>
src/components/Ruoyi/Doc/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<template>
  <div>
    <svg-icon icon-class="question" @click="goto" />
  </div>
</template>
<script setup>
const url = ref('http://doc.ruoyi.vip/ruoyi-vue');
function goto() {
  window.open(url.value)
}
</script>
src/components/Ruoyi/Git/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<template>
  <div>
    <svg-icon icon-class="github" @click="goto" />
  </div>
</template>
<script setup>
const url = ref('https://gitee.com/y_project/RuoYi-Vue');
function goto() {
  window.open(url.value)
}
</script>
src/components/Screenfull/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
<template>
  <div>
    <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
  </div>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen();
</script>
<style lang='scss' scoped>
.screenfull-svg {
  display: inline-block;
  cursor: pointer;
  fill: #5a5e66;
  width: 20px;
  height: 20px;
  vertical-align: 10px;
}
</style>
src/components/SizeSelect/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
<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>
</template>
<script setup>
import { ElMessage } from 'element-plus'
const store = useStore();
const size = computed(() => store.getters.size);
const route = useRoute();
const router = useRouter();
const {proxy} = getCurrentInstance();
const sizeOptions = ref([
  { label: 'Large', value: 'large' },
  { label: 'Medium', value: 'medium' },
  { label: 'Small', value: 'small' },
  { label: 'Mini', value: 'mini' }
])
function refreshView() {
  // In order to make the cached page re-rendered
  store.dispatch('tagsView/delAllCachedViews', route)
  const { fullPath } = route
  nextTick(() => {
    router.replace({
      path: '/redirect' + fullPath
    })
  })
}
function handleSetSize(size) {
  proxy.$ELEMENT.size = size;
  store.dispatch('app/setSize', size)
  refreshView()
  ElMessage({
    message: 'Switch Size Success',
    type: 'success'
  })
};
</script>
<style lang='scss' scoped>
.size-icon--style {
  font-size: 18px;
  line-height: 50px;
  padding-right: 7px;
}
</style>
src/components/SvgIcon/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" :fill="color" />
  </svg>
</template>
<script>
export default defineComponent({
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    },
    color: {
      type: String,
      default: ''
    },
  },
  setup(props) {
    return {
      iconName: computed(() => `#icon-${props.iconClass}`),
      svgClass: computed(() => {
        if (props.className) {
          return `svg-icon ${props.className}`
        }
        return 'svg-icon'
      })
    }
  }
})
</script>
<style scope lang="scss">
.sub-el-icon,
.nav-icon {
  display: inline-block;
  font-size: 15px;
  margin-right: 12px;
  position: relative;
}
.svg-icon {
  width: 1em;
  height: 1em;
  position: relative;
  fill: currentColor;
  vertical-align: -2px;
}
</style>
src/components/SvgIcon/svgicon.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import * as components from "@element-plus/icons";
export default {
    install: (app) => {
        for (const key in components) {
            const componentConfig = components[key];
            app.component(componentConfig.name, componentConfig);
        }
    },
};
src/components/TopNav/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,186 @@
<template>
  <el-menu
    :default-active="activeMenu"
    mode="horizontal"
    @select="handleSelect"
  >
    <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.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.icon" />
          {{ item.meta.title }}</el-menu-item
        >
      </template>
    </el-sub-menu>
  </el-menu>
</template>
<script setup>
import { constantRoutes } from "@/router"
import { isHttp } from '@/utils/validate'
// é¡¶éƒ¨æ åˆå§‹æ•°
const visibleNumber = ref(null);
// æ˜¯å¦ä¸ºé¦–次加载
const isFrist = ref(null);
// å½“前激活菜单的 index
const currentIndex = ref(null);
const store = useStore();
const route = useRoute();
// ä¸»é¢˜é¢œè‰²
const theme = computed(() => store.state.settings.theme);
// æ‰€æœ‰çš„路由信息
const routers = computed(() => store.state.permission.topbarRouters);
// é¡¶éƒ¨æ˜¾ç¤ºèœå•
const topMenus = computed(() => {
  let topMenus = [];
  routers.value.map((menu) => {
    if (menu.hidden !== true) {
      // å…¼å®¹é¡¶éƒ¨æ ä¸€çº§èœå•内部跳转
      if (menu.path === "/") {
          topMenus.push(menu.children[0]);
      } else {
          topMenus.push(menu);
      }
    }
  })
  return topMenus;
})
// è®¾ç½®å­è·¯ç”±
const childrenMenus = computed(() => {
  let childrenMenus = [];
  routers.value.map((router) => {
    for (let item in router.children) {
      if (router.children[item].parentPath === undefined) {
        if(router.path === "/") {
          router.children[item].path = "/redirect/" + router.children[item].path;
        } else {
          if(!isHttp(router.children[item].path)) {
            router.children[item].path = router.path + "/" + router.children[item].path;
          }
        }
        router.children[item].parentPath = router.path;
      }
      childrenMenus.push(router.children[item]);
    }
  })
  return constantRoutes.concat(childrenMenus);
})
// é»˜è®¤æ¿€æ´»çš„菜单
const activeMenu = computed(() => {
  const path = route.path;
  let activePath = defaultRouter.value;
  if (path !== undefined && path.lastIndexOf("/") > 0) {
    const tmpPath = path.substring(1, path.length);
    activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
  } else if ("/index" == path || "" == path) {
    if (!isFrist.value) {
      isFrist.value = true;
    } else {
      activePath = "index";
    }
  }
  let routes = activeRoutes(activePath);
  if (routes.length === 0) {
    activePath = currentIndex.value || defaultRouter.value
    activeRoutes(activePath);
  }
  return activePath;
})
// é»˜è®¤æ¿€æ´»çš„路由
const defaultRouter = computed(() => {
  let router;
  Object.keys(routers.value).some((key) => {
    if (!routers.value[key].hidden) {
      router = routers.value[key].path;
      return true;
    }
  });
  return router;
})
function setVisibleNumber() {
  const width = document.body.getBoundingClientRect().width / 3;
  visibleNumber.value = parseInt(width / 85);
}
function handleSelect(key, keyPath) {
  currentIndex.value = key;
  if (isHttp(key)) {
    // http(s):// è·¯å¾„新窗口打开
    window.open(key, "_blank");
  } else if (key.indexOf("/redirect") !== -1) {
    // /redirect è·¯å¾„内部打开
    router.push({ path: key.replace("/redirect", "") });
  } else {
    // æ˜¾ç¤ºå·¦ä¾§è”动菜单
    activeRoutes(key);
  }
}
function activeRoutes(key) {
  let routes = [];
  if (childrenMenus.value && childrenMenus.value.length > 0) {
    childrenMenus.value.map((item) => {
      if (key == item.parentPath || (key == "index" && "" == item.path)) {
        routes.push(item);
      }
    });
  }
  if(routes.length > 0) {
    store.commit("SET_SIDEBAR_ROUTERS", routes);
  }
  return routes;
}
onMounted(() => {
  window.addEventListener('resize', setVisibleNumber)
})
onBeforeUnmount(() => {
  window.removeEventListener('resize', setVisibleNumber)
})
onMounted(() => {
  setVisibleNumber()
})
</script>
<style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item {
  float: left;
  height: 50px !important;
  line-height: 50px !important;
  color: #999093 !important;
  padding: 0 5px !important;
  margin: 0 10px !important;
}
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
  border-bottom: 2px solid #{'var(--theme)'} !important;
  color: #303133;
}
/* sub-menu item */
.topmenu-container.el-menu--horizontal > .el-sub-menu .el-submenu__title {
  float: left;
  height: 50px !important;
  line-height: 50px !important;
  color: #999093 !important;
  padding: 0 5px !important;
  margin: 0 10px !important;
}
</style>
src/components/TreeSelect/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
<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>
</template>
<script setup>
const { proxy } = getCurrentInstance();
const props = defineProps({
  /* é…ç½®é¡¹ */
  objMap: {
    type: Object,
    default: () => {
      return {
        value: 'id', // ID字段名
        label: 'label', // æ˜¾ç¤ºåç§°
        children: 'children' // å­çº§å­—段名
      }
    }
  },
  /* è‡ªåŠ¨æ”¶èµ· */
  accordion: {
    type: Boolean,
    default: () => {
      return false
    }
  },
  /**当前双向数据绑定的值 */
  value: {
    type: [String, Number],
    default: ''
  },
  /**当前的数据 */
  options: {
    type: Array,
    default: () => []
  },
  /**输入框内部的文字 */
  placeholder: {
    type: String,
    default: ''
  }
})
const emit = defineEmits(['update:value']);
const valueId = computed({
  get: () => props.value,
  set: (val) => {
    emit('update:value', val)
  }
});
const valueTitle = ref('');
const defaultExpandedKey = ref([]);
function initHandle() {
  nextTick(() => {
    const selectedValue = valueId.value;
    if(selectedValue !== null && typeof (selectedValue) !== "undefined"){
      const node = proxy.$refs.selectTree.getNode(selectedValue)
      if (node) {
        valueTitle.value = node.data[props.objMap.label]
        proxy.$refs.selectTree.setCurrentKey(selectedValue) // è®¾ç½®é»˜è®¤é€‰ä¸­
        defaultExpandedKey.value = [selectedValue] // è®¾ç½®é»˜è®¤å±•å¼€
      }
    }
  })
}
function handleNodeClick(node) {
  valueTitle.value = node[props.objMap.label]
  valueId.value = node[props.objMap.value];
  defaultExpandedKey.value = [];
  proxy.$refs.treeSelect.blur()
  selectFilterData('')
}
function selectFilterData(val) {
  proxy.$refs.selectTree.filter(val)
}
function filterNode(value, data) {
  if (!value) return true
  return data[props.objMap['label']].indexOf(value) !== -1
}
function clearHandle() {
  valueTitle.value = ''
  valueId.value = ''
  defaultExpandedKey.value = [];
  clearSelected()
}
function clearSelected() {
  const allNode = document.querySelectorAll('#tree-option .el-tree-node')
  allNode.forEach((element) => element.classList.remove('is-current'))
}
onMounted(() => {
  initHandle()
})
watch(valueId, () => {
  initHandle();
})
</script>
<style lang='scss' scoped>
@import "@/assets/styles/variables.module.scss";
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  padding: 0;
  background-color: #fff;
  height: auto;
}
.el-select-dropdown__item.selected {
  font-weight: normal;
}
ul li  .el-tree .el-tree-node__content {
  height: auto;
  padding: 0 20px;
  box-sizing: border-box;
}
:deep(.el-tree-node__content:hover),
:deep(.el-tree-node__content:active),
:deep(.is-current > div:first-child),
:deep(.el-tree-node__content:focus) {
  background-color: mix(#fff, $--color-primary, 90%);
  color: $--color-primary;
}
</style>
src/components/iFrame/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
<template>
  <div v-loading="loading" :style="'height:' + height">
    <iframe
      :src="url"
      frameborder="no"
      style="width: 100%; height: 100%"
      scrolling="auto" />
  </div>
</template>
<script setup>
const props = defineProps({
  src: {
    type: String,
    required: true
  }
})
const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
const loading = ref(true)
const url = computed(() => props.src)
onMounted(() => {
  setTimeout(() => {
    loading.value = false;
  }, 300);
  window.onresize = function temp() {
    height.value = document.documentElement.clientHeight - 94.5 + "px;";
  };
})
</script>
src/directive/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi'
export default function directive(app){
  app.directive('hasRole', hasRole)
  app.directive('hasPermi', hasPermi)
}
src/directive/permission/hasPermi.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
 /**
 * v-hasPermi æ“ä½œæƒé™å¤„理
 * Copyright (c) 2019 ruoyi
 */
import store from '@/store'
export default {
  mounted(el, binding, vnode) {
    const { value } = binding
    const all_permission = "*:*:*";
    const permissions = store.getters && store.getters.permissions
    if (value && value instanceof Array && value.length > 0) {
      const permissionFlag = value
      const hasPermissions = permissions.some(permission => {
        return all_permission === permission || permissionFlag.includes(permission)
      })
      if (!hasPermissions) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置操作权限标签值`)
    }
  }
}
src/directive/permission/hasRole.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
 /**
 * v-hasRole è§’色权限处理
 * Copyright (c) 2019 ruoyi
 */
import store from '@/store'
export default {
  mounted(el, binding, vnode) {
    const { value } = binding
    const super_admin = "admin";
    const roles = store.getters && store.getters.roles
    if (value && value instanceof Array && value.length > 0) {
      const roleFlag = value
      const hasRole = roles.some(role => {
        return super_admin === role || roleFlag.includes(role)
      })
      if (!hasRole) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置角色权限标签值"`)
    }
  }
}
src/layout/components/AppMain.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
<template>
  <section class="app-main">
    <router-view v-slot="{ Component, route }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive :include="cachedViews">
          <component :is="Component" :key="route.path"/>
        </keep-alive>
      </transition>
    </router-view>
  </section>
</template>
<script setup>
let store = useStore()
const cachedViews = computed(() => {
    return store.state.tagsView.cachedViews
})
</script>
<style lang="scss" scoped>
.app-main {
  /* 50= navbar  50  */
  min-height: calc(100vh - 50px);
  width: 100%;
  position: relative;
  overflow: hidden;
}
.fixed-header + .app-main {
  padding-top: 50px;
}
.hasTagsView {
  .app-main {
    /* 84 = navbar + tags-view = 50 + 34 */
    min-height: calc(100vh - 84px);
  }
  .fixed-header + .app-main {
    padding-top: 84px;
  }
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
  .fixed-header {
    padding-right: 17px;
  }
}
</style>
src/layout/components/InnerLink/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
<script>
export default {
  setup() {
    const route = useRoute();
    const link = route.meta.link;
    if (link === "") {
      return "404";
    }
    let url = link;
    const height = document.documentElement.clientHeight - 94.5 + "px";
    const style = { height: height };
    // è¿”回渲染函数
    return () =>
      h(
        "div",
        {
          style: style,
        },
        h("iframe", {
          src: url,
          frameborder: "no",
          width: "100%",
          height: "100%",
          scrolling: "auto",
        })
      );
  },
};
</script>
src/layout/components/Navbar.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,174 @@
<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="getters.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!$store.state.settings.topNav" />
    <top-nav id="topmenu-container" class="topmenu-container" v-if="$store.state.settings.topNav" />
    <div class="right-menu">
      <template v-if="getters.device !== 'mobile'">
        <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>
        <screenfull id="screenfull" class="right-menu-item hover-effect" />
        <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 class="right-menu-item hover-effect" trigger="click">
          <div class="avatar-wrapper">
            <img :src="getters.avatar" class="user-avatar" />
            <el-icon><caret-bottom /></el-icon>
          </div>
          <template #dropdown>
            <el-dropdown-menu>
              <router-link to="/user/profile">
                <el-dropdown-item>个人中心</el-dropdown-item>
              </router-link>
              <el-dropdown-item @click="setLayout">
                <span>布局设置</span>
              </el-dropdown-item>
              <el-dropdown-item divided @click="logout">
                <span>退出登录</span>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ElMessageBox } from 'element-plus'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
const store = useStore();
const getters = computed(() => store.getters);
function toggleSideBar() {
  store.dispatch('app/toggleSideBar')
}
function logout() {
  ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    store.dispatch('LogOut').then(() => {
      location.href = '/index';
    })
  }).catch(() => { });
}
const emits = defineEmits(['setLayout'])
function setLayout() {
  emits('setLayout');
}
</script>
<style lang='scss' scoped>
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background 0.3s;
    -webkit-tap-highlight-color: transparent;
    &:hover {
      background: rgba(0, 0, 0, 0.025);
    }
  }
  .breadcrumb-container {
    float: left;
  }
  .topmenu-container {
    position: absolute;
    left: 50px;
  }
  .errLog-container {
    display: inline-block;
    vertical-align: top;
  }
  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;
    display: flex;
    &:focus {
      outline: none;
    }
    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;
      &.hover-effect {
        cursor: pointer;
        transition: background 0.3s;
        &:hover {
          background: rgba(0, 0, 0, 0.025);
        }
      }
    }
    .avatar-container {
      margin-right: 40px;
      .avatar-wrapper {
        margin-top: 5px;
        position: relative;
        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
        }
        i {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    }
  }
}
</style>
src/layout/components/Settings/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,254 @@
<template>
  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px">
    <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>
    <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>固定 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>动态标题</span>
      <span class="comp-style">
        <el-switch v-model="dynamicTitle" class="drawer-switch" />
      </span>
    </div>
    <el-divider />
    <el-button size="small" type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
    <el-button size="small" plain icon="Refresh" @click="resetSetting">重置配置</el-button>
  </el-drawer>
</template>
<script setup>
import variables from '@/assets/styles/variables.module.scss'
import originElementPlus from 'element-plus/theme-chalk/index.css'
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import { useDynamicTitle } from '@/utils/dynamicTitle'
const { proxy } = getCurrentInstance();
const store = useStore();
const showSettings = ref(false);
const theme = ref(store.state.settings.theme);
const sideTheme = ref(store.state.settings.sideTheme);
const storeSettings = computed(() => store.state.settings);
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
/** æ˜¯å¦éœ€è¦topnav */
const topNav = computed({
  get: () => storeSettings.value.topNav,
  set: (val) => {
    store.dispatch('settings/changeSetting', {
      key: 'topNav',
      value: val
    })
    if (!val) {
      store.commit("SET_SIDEBAR_ROUTERS", store.state.permission.defaultRoutes);
    }
  }
})
/** æ˜¯å¦éœ€è¦tagview */
const tagsView = computed({
  get: () => storeSettings.value.tagsView,
  set: (val) => {
    store.dispatch('settings/changeSetting', {
      key: 'tagsView',
      value: val
    })
  }
})
/**是否需要固定头部 */
const fixedHeader = computed({
  get: () => storeSettings.value.fixedHeader,
  set: (val) => {
    store.dispatch('settings/changeSetting', {
      key: 'fixedHeader',
      value: val
    })
  }
})
/**是否需要侧边栏的logo */
const sidebarLogo = computed({
  get: () => storeSettings.value.sidebarLogo,
  set: (val) => {
    store.dispatch('settings/changeSetting', {
      key: 'sidebarLogo',
      value: val
    })
  }
})
/**是否需要侧边栏的动态网页的title */
const dynamicTitle = computed({
  get: () => storeSettings.value.dynamicTitle,
  set: (val) => {
    store.dispatch('settings/changeSetting', {
      key: 'dynamicTitle',
      value: val
    })
    // åŠ¨æ€è®¾ç½®ç½‘é¡µæ ‡é¢˜
    useDynamicTitle()
  }
})
function themeChange(val) {
  store.dispatch('settings/changeSetting', {
    key: 'theme',
    value: val
  })
  theme.value = val;
}
function handleTheme(val) {
  store.dispatch('settings/changeSetting', {
    key: 'sideTheme',
    value: val
  })
  sideTheme.value = val;
}
function saveSetting() {
  proxy.$modal.loading("正在保存到本地,请稍候...");
  let layoutSetting = {
    "topNav": storeSettings.value.topNav,
    "tagsView": storeSettings.value.tagsView,
    "fixedHeader": storeSettings.value.fixedHeader,
    "sidebarLogo": storeSettings.value.sidebarLogo,
    "dynamicTitle": storeSettings.value.dynamicTitle,
    "sideTheme": storeSettings.value.sideTheme,
    "theme": storeSettings.value.theme
  };
  localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
  setTimeout(proxy.$modal.closeLoading(), 1000)
}
function resetSetting() {
  proxy.$modal.loading("正在清除设置缓存并刷新,请稍候...");
  localStorage.removeItem("layout-setting")
  setTimeout("window.location.reload()", 1000)
}
function openSetting() {
  showSettings.value = true;
}
defineExpose({
  openSetting,
})
</script>
<style lang='scss' scoped>
.setting-drawer-title {
  margin-bottom: 12px;
  color: rgba(0, 0, 0, 0.85);
  line-height: 22px;
  font-weight: bold;
  .drawer-title {
    font-size: 14px;
  }
}
.setting-drawer-block-checbox {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-top: 10px;
  margin-bottom: 20px;
  .setting-drawer-block-checbox-item {
    position: relative;
    margin-right: 16px;
    border-radius: 2px;
    cursor: pointer;
    img {
      width: 48px;
      height: 48px;
    }
    .custom-img {
      width: 48px;
      height: 38px;
      border-radius: 5px;
      box-shadow: 1px 1px 2px #898484;
    }
    .setting-drawer-block-checbox-selectIcon {
      position: absolute;
      top: 0;
      right: 0;
      width: 100%;
      height: 100%;
      padding-top: 15px;
      padding-left: 24px;
      color: #1890ff;
      font-weight: 700;
      font-size: 14px;
    }
  }
}
.drawer-item {
  color: rgba(0, 0, 0, 0.65);
  padding: 12px 0;
  font-size: 14px;
  .comp-style {
    float: right;
    margin: -3px 8px 0px 0px;
  }
}
</style>
src/layout/components/Sidebar/Link.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
<template>
  <component :is="type" v-bind="linkProps()">
    <slot />
  </component>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
const props = defineProps({
  to: {
    type: [String, Object],
    required: true
  }
})
const isExt = computed(() => {
  return isExternal(props.to)
})
const type = computed(() => {
  if (isExt.value) {
    return 'a'
  }
  return 'router-link'
})
function linkProps() {
  if (isExt.value) {
    return {
      href: props.to,
      target: '_blank',
      rel: 'noopener'
    }
  }
  return {
    to: props.to
  }
}
</script>
src/layout/components/Sidebar/Logo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
<template>
  <div class="sidebar-logo-container" :class="{ 'collapse': collapse }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
    <transition name="sidebarLogoFade">
      <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>
<script setup>
import variables from '@/assets/styles/variables.module.scss'
import logo from '@/assets/logo/logo.png'
defineProps({
  collapse: {
    type: Boolean,
    required: true
  }
})
const title = ref('若依管理系统');
const store = useStore();
const sideTheme = computed(() => store.state.settings.sideTheme);
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
  transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
  opacity: 0;
}
.sidebar-logo-container {
  position: relative;
  width: 100%;
  height: 50px;
  line-height: 50px;
  background: #2b2f3a;
  text-align: center;
  overflow: hidden;
  & .sidebar-logo-link {
    height: 100%;
    width: 100%;
    & .sidebar-logo {
      width: 32px;
      height: 32px;
      vertical-align: middle;
      margin-right: 12px;
    }
    & .sidebar-title {
      display: inline-block;
      margin: 0;
      color: #fff;
      font-weight: 600;
      line-height: 50px;
      font-size: 14px;
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
      vertical-align: middle;
    }
  }
  &.collapse {
    .sidebar-logo {
      margin-right: 0px;
    }
  }
}
</style>
src/layout/components/Sidebar/SidebarItem.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
<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>{{ onlyOneChild.meta.title }}</template>
        </el-menu-item>
      </app-link>
    </template>
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template v-if="item.meta" #title>
        <svg-icon :icon-class="item.meta && item.meta.icon" />
        <span>{{ item.meta.title }}</span>
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-sub-menu>
  </div>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi'
const props = defineProps({
  // route object
  item: {
    type: Object,
    required: true
  },
  isNest: {
    type: Boolean,
    default: false
  },
  basePath: {
    type: String,
    default: ''
  }
})
const onlyOneChild = ref({});
function hasOneShowingChild(children = [], parent) {
  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
  }
  // Show parent if there are no child router to display
  if (showingChildren.length === 0) {
    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
    return true
  }
  return false
};
function resolvePath(routePath, routeQuery) {
  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)
}
</script>
src/layout/components/Sidebar/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
<template>
  <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
        :text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
        :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>
    </el-scrollbar>
  </div>
</template>
<script setup>
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/assets/styles/variables.module.scss'
const route = useRoute();
const store = useStore();
const sidebarRouters =  computed(() => store.getters.sidebarRouters);
const showLogo = computed(() => store.state.settings.sidebarLogo);
const sideTheme = computed(() => store.state.settings.sideTheme);
const theme = computed(() => store.state.settings.theme);
const isCollapse = computed(() => !store.state.app.sidebar.opened);
const activeMenu = computed(() => {
  const { meta, path } = route;
  // if set path, the sidebar will highlight the path you set
  if (meta.activeMenu) {
    return meta.activeMenu;
  }
  return path;
})
</script>
src/layout/components/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
export { default as AppMain } from './AppMain'
export { default as Navbar } from './Navbar'
export { default as Settings } from './Settings'
export { default as TagsView } from './TagsView/index.vue'
src/layout/components/tagsView/ScrollPane.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
<template>
  <el-scrollbar
    ref="scrollContainer"
    :vertical="false"
    class="scroll-container"
    @wheel.prevent="handleScroll"
  >
    <slot />
  </el-scrollbar>
</template>
<script setup>
const tagAndTagSpacing = ref(4);
const { proxy } = getCurrentInstance();
const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrap);
onMounted(() => {
  scrollWrapper.value.addEventListener('scroll', emitScroll, true)
})
onBeforeUnmount(() => {
  scrollWrapper.value.removeEventListener('scroll', emitScroll)
})
function handleScroll(e) {
  const eventDelta = e.wheelDelta || -e.deltaY * 40
  const $scrollWrapper = scrollWrapper.value;
  $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
}
const emits = defineEmits()
const emitScroll = () => {
  emits('scroll')
}
const store = useStore();
const visitedViews = computed(() => store.state.tagsView.visitedViews);
function moveToTarget(currentTag) {
  const $container = proxy.$refs.scrollContainer.$el
  const $containerWidth = $container.offsetWidth
  const $scrollWrapper = scrollWrapper.value;
  let firstTag = null
  let lastTag = null
  // find first tag and last tag
  if (visitedViews.value.length > 0) {
    firstTag = visitedViews.value[0]
    lastTag = visitedViews.value[visitedViews.value.length - 1]
  }
  if (firstTag === currentTag) {
    $scrollWrapper.scrollLeft = 0
  } else if (lastTag === currentTag) {
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
  } else {
    const tagListDom = document.getElementsByClassName('tags-view-item');
    const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
    let prevTag = null
    let nextTag = null
    for (const k in tagListDom) {
      if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
          prevTag = tagListDom[k];
        }
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
          nextTag = tagListDom[k];
        }
      }
    }
    // the tag's offsetLeft after of nextTag
    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
    // the tag's offsetLeft before of prevTag
    const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
    if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
      $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
    } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
      $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
    }
  }
}
defineExpose({
  moveToTarget,
})
</script>
<style lang='scss' scoped>
.scroll-container {
  white-space: nowrap;
  position: relative;
  overflow: hidden;
  width: 100%;
  :deep(.el-scrollbar__bar) {
    bottom: 0px;
  }
  :deep(.el-scrollbar__wrap) {
    height: 49px;
  }
}
</style>
src/layout/components/tagsView/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,330 @@
<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, query: tag.query, 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>
import ScrollPane from './ScrollPane'
import { getNormalPath } from '@/utils/ruoyi'
const visible = ref(false);
const top = ref(0);
const left = ref(0);
const selectedTag = ref({});
const affixTags = ref([]);
const scrollPaneRef = ref(null);
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const router = useRouter();
const visitedViews = computed(() => store.state.tagsView.visitedViews);
const routes = computed(() => store.state.permission.routes);
const theme = computed(() => store.state.settings.theme);
watch(route, () => {
  addTags()
  moveToCurrentTag()
})
watch(visible, (value) => {
  if (value) {
    document.body.addEventListener('click', closeMenu)
  } else {
    document.body.removeEventListener('click', closeMenu)
  }
})
onMounted(() => {
  initTags()
  addTags()
})
function isActive(r) {
  return r.path === route.path
}
function activeStyle(tag) {
  if (!isActive(tag)) return {};
  return {
    "background-color": theme.value,
    "border-color": theme.value
  };
}
function isAffix(tag) {
  return tag.meta && tag.meta.affix
}
function isFirstView() {
  try {
    return selectedTag.value.fullPath === visitedViews.value[1].fullPath || selectedTag.value.fullPath === '/index'
  } catch (err) {
    return false
  }
}
function isLastView() {
  try {
    return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
  } catch (err) {
    return false
  }
}
function filterAffixTags(routes, basePath = '') {
  let tags = []
  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
}
function initTags() {
  const res = filterAffixTags(routes.value);
  affixTags.value = res;
  for (const tag of res) {
    // Must have tag name
    if (tag.name) {
      store.dispatch('tagsView/addVisitedView', tag)
    }
  }
}
function addTags() {
  const { name } = route
  if (name) {
    store.dispatch('tagsView/addView', route)
  }
  return false
}
function 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) {
          store.dispatch('tagsView/updateVisitedView', route)
        }
      }
    }
  })
}
function refreshSelectedTag(view) {
  proxy.$tab.refreshPage(view);
}
function closeSelectedTag(view) {
  proxy.$tab.closePage(view).then(({ visitedViews }) => {
    if (isActive(view)) {
      toLastView(visitedViews, view)
    }
  })
}
function closeRightTags() {
  proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
      toLastView(visitedViews)
    }
  })
}
function closeLeftTags() {
  proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
      toLastView(visitedViews)
    }
  })
}
function closeOthersTags() {
  router.push(selectedTag.value).catch(() => { });
  proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
    moveToCurrentTag()
  })
}
function closeAllTags(view) {
  proxy.$tab.closeAllPage().then(({ visitedViews }) => {
    if (affixTags.value.some(tag => tag.path === route.path)) {
      return
    }
    toLastView(visitedViews, view)
  })
}
function toLastView(visitedViews, view) {
  const latestView = visitedViews.slice(-1)[0]
  if (latestView) {
    router.push(latestView.fullPath)
  } 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 })
    } else {
      router.push('/')
    }
  }
}
function openMenu(tag, e) {
  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
  }
  top.value = e.clientY
  visible.value = true
  selectedTag.value = tag
}
function closeMenu() {
  visible.value = false
}
function handleScroll() {
  closeMenu()
}
</script>
<style lang='scss' scoped>
.tags-view-container {
  height: 34px;
  width: 100%;
  background: #fff;
  border-bottom: 1px solid #d8dce5;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 26px;
      line-height: 26px;
      border: 1px solid #d8dce5;
      color: #495060;
      background: #fff;
      padding: 0 8px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
      &:first-of-type {
        margin-left: 15px;
      }
      &:last-of-type {
        margin-right: 15px;
      }
      &.active {
        background-color: #42b983;
        color: #fff;
        border-color: #42b983;
        &::before {
          content: "";
          background: #fff;
          display: inline-block;
          width: 8px;
          height: 8px;
          border-radius: 50%;
          position: relative;
          margin-right: 2px;
        }
      }
    }
  }
  .contextmenu {
    margin: 0;
    background: #fff;
    z-index: 3000;
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
    li {
      margin: 0;
      padding: 7px 16px;
      cursor: pointer;
      &:hover {
        background: #eee;
      }
    }
  }
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
  .tags-view-item {
    .el-icon-close {
      width: 16px;
      height: 16px;
      vertical-align: 2px;
      border-radius: 50%;
      text-align: center;
      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      transform-origin: 100% 50%;
      &:before {
        transform: scale(0.6);
        display: inline-block;
        vertical-align: -3px;
      }
      &:hover {
        background-color: #b4bccc;
        color: #fff;
        width: 12px !important;
        height: 12px !important;
      }
    }
  }
}
</style>
src/layout/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
<template>
  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
    <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
    <sidebar class="sidebar-container" />
    <div :class="{ hasTagsView: needTagsView }" class="main-container">
      <div :class="{ 'fixed-header': fixedHeader }">
        <navbar @setLayout="setLayout" />
        <tags-view v-if="needTagsView" />
      </div>
      <app-main />
      <settings ref="settingRef" />
    </div>
  </div>
</template>
<script setup>
import { useWindowSize } from '@vueuse/core'
import Sidebar from './components/Sidebar/index.vue'
import { AppMain, Navbar, Settings, TagsView } from './components'
import defaultSettings from '@/settings'
const store = useStore();
const theme = computed(() => store.state.settings.theme);
const sideTheme = computed(() => store.state.settings.sideTheme);
const sidebar = computed(() => store.state.app.sidebar);
const device = computed(() => store.state.app.device);
const needTagsView = computed(() => store.state.settings.tagsView);
const fixedHeader = computed(() => store.state.settings.fixedHeader);
const classObj = computed(() => ({
  hideSidebar: !sidebar.value.opened,
  openSidebar: sidebar.value.opened,
  withoutAnimation: sidebar.value.withoutAnimation,
  mobile: device.value === 'mobile'
}))
const { width, height } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design
watchEffect(() => {
  if (device.value === 'mobile' && sidebar.value.opened) {
    store.dispatch('app/closeSideBar', { withoutAnimation: false })
  }
  if (width.value - 1 < WIDTH) {
    store.dispatch('app/toggleDevice', 'mobile')
    store.dispatch('app/closeSideBar', { withoutAnimation: true })
  } else {
    store.dispatch('app/toggleDevice', 'desktop')
  }
})
function handleClickOutside() {
  store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
const settingRef = ref(null);
function setLayout() {
  settingRef.value.openSetting();
}
</script>
<style lang="scss" scoped>
  @import "@/assets/styles/mixin.scss";
  @import "@/assets/styles/variables.module.scss";
.app-wrapper {
  @include clearfix;
  position: relative;
  height: 100%;
  width: 100%;
  &.mobile.openSidebar {
    position: fixed;
    top: 0;
  }
}
.drawer-bg {
  background: #000;
  opacity: 0.3;
  width: 100%;
  top: 0;
  height: 100%;
  position: absolute;
  z-index: 999;
}
.fixed-header {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 9;
  width: calc(100% - #{$base-sidebar-width});
  transition: width 0.28s;
}
.hideSidebar .fixed-header {
  width: calc(100% - 54px);
}
.mobile .fixed-header {
  width: 100%;
}
</style>
src/main.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
import { createApp } from 'vue'
import Cookies from 'js-cookie'
import ElementPlus from 'element-plus'
import locale from 'element-plus/lib/locale/lang/zh-cn' // ä¸­æ–‡è¯­è¨€
import '@/assets/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
// æ³¨å†ŒæŒ‡ä»¤
import plugins from './plugins' // plugins
import { download } from '@/utils/request'
// svg图标
import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon'
import './permission' // permission control
import { useDict } from '@/utils/dict'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel } from '@/utils/ruoyi'
// åˆ†é¡µç»„ä»¶
import Pagination from '@/components/Pagination'
// è‡ªå®šä¹‰è¡¨æ ¼å·¥å…·ç»„ä»¶
import RightToolbar from '@/components/RightToolbar'
// è‡ªå®šä¹‰æ ‘选择组件
import TreeSelect from '@/components/TreeSelect'
// å­—典标签组件
import DictTag from '@/components/DictTag'
const app = createApp(App)
// å…¨å±€æ–¹æ³•挂载
app.config.globalProperties.useDict = useDict
app.config.globalProperties.download = download
app.config.globalProperties.parseTime = parseTime
app.config.globalProperties.resetForm = resetForm
app.config.globalProperties.handleTree = handleTree
app.config.globalProperties.addDateRange = addDateRange
app.config.globalProperties.selectDictLabel = selectDictLabel
// å…¨å±€ç»„件挂载
app.component('DictTag', DictTag)
app.component('Pagination', Pagination)
app.component('TreeSelect', TreeSelect)
app.component('RightToolbar', RightToolbar)
app.use(router)
app.use(store)
app.use(plugins)
app.use(elementIcons)
app.component('svg-icon', SvgIcon)
directive(app)
// ä½¿ç”¨element-plus å¹¶ä¸”设置全局的大小
app.use(ElementPlus, {
  locale: locale,
  size: Cookies.get('size') || 'medium'
})
app.mount('#app')
src/permission.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import router from './router'
import store from './store'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/auth-redirect', '/bind', '/register'];
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.roles.length === 0) {
        // åˆ¤æ–­å½“前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // æ ¹æ®roles权限生成可访问的路由表
            accessRoutes.forEach(route => {
              if (!isHttp(route.path)) {
                router.addRoute(route) // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
              }
            })
            next({ ...to, replace: true }) // hack方法 ç¡®ä¿addRoutes已完成
          })
        }).catch(err => {
          store.dispatch('LogOut').then(() => {
            ElMessage.error(err)
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    // æ²¡æœ‰token
    if (whiteList.indexOf(to.path) !== -1) {
      // åœ¨å…ç™»å½•白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // å¦åˆ™å…¨éƒ¨é‡å®šå‘到登录页
      NProgress.done()
    }
  }
})
router.afterEach(() => {
  NProgress.done()
})
src/plugins/auth.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
import store from '@/store'
function authPermission(permission) {
  const all_permission = "*:*:*";
  const permissions = store.getters && store.getters.permissions
  if (permission && permission.length > 0) {
    return permissions.some(v => {
      return all_permission === v || v === permission
    })
  } else {
    return false
  }
}
function authRole(role) {
  const super_admin = "admin";
  const roles = store.getters && store.getters.roles
  if (role && role.length > 0) {
    return roles.some(v => {
      return super_admin === v || v === role
    })
  } else {
    return false
  }
}
export default {
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å…·å¤‡æŸæƒé™
  hasPermi(permission) {
    return authPermission(permission);
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šæƒé™ï¼Œåªéœ€åŒ…含其中一个
  hasPermiOr(permissions) {
    return permissions.some(item => {
      return authPermission(item)
    })
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šæƒé™ï¼Œå¿…须全部拥有
  hasPermiAnd(permissions) {
    return permissions.every(item => {
      return authPermission(item)
    })
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å…·å¤‡æŸè§’色
  hasRole(role) {
    return authRole(role);
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šè§’色,只需包含其中一个
  hasRoleOr(roles) {
    return roles.some(item => {
      return authRole(item)
    })
  },
  // éªŒè¯ç”¨æˆ·æ˜¯å¦å«æœ‰æŒ‡å®šè§’色,必须全部拥有
  hasRoleAnd(roles) {
    return roles.every(item => {
      return authRole(item)
    })
  }
}
src/plugins/cache.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
const sessionCache = {
  set (key, value) {
    if (!sessionStorage) {
      return
    }
    if (key != null && value != null) {
      sessionStorage.setItem(key, value)
    }
  },
  get (key) {
    if (!sessionStorage) {
      return null
    }
    if (key == null) {
      return null
    }
    return sessionStorage.getItem(key)
  },
  setJSON (key, jsonValue) {
    if (jsonValue != null) {
      this.set(key, JSON.stringify(jsonValue))
    }
  },
  getJSON (key) {
    const value = this.get(key)
    if (value != null) {
      return JSON.parse(value)
    }
  },
  remove (key) {
    sessionStorage.removeItem(key);
  }
}
const localCache = {
  set (key, value) {
    if (!localStorage) {
      return
    }
    if (key != null && value != null) {
      localStorage.setItem(key, value)
    }
  },
  get (key) {
    if (!localStorage) {
      return null
    }
    if (key == null) {
      return null
    }
    return localStorage.getItem(key)
  },
  setJSON (key, jsonValue) {
    if (jsonValue != null) {
      this.set(key, JSON.stringify(jsonValue))
    }
  },
  getJSON (key) {
    const value = this.get(key)
    if (value != null) {
      return JSON.parse(value)
    }
  },
  remove (key) {
    localStorage.removeItem(key);
  }
}
export default {
  /**
   * ä¼šè¯çº§ç¼“å­˜
   */
  session: sessionCache,
  /**
   * æœ¬åœ°ç¼“å­˜
   */
  local: localCache
}
src/plugins/download.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth'
import { blobValidate } from '@/utils/ruoyi'
const baseURL = import.meta.env.VITE_APP_BASE_API
export default {
  name(name, isDelete = true) {
    var url = baseURL + "/common/download?fileName=" + encodeURI(name) + "&delete=" + isDelete
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then(async (res) => {
      const isLogin = await this.blobValidate(res.data);
      if (isLogin) {
        const blob = new Blob([res.data])
        this.saveAs(blob, decodeURI(res.headers['download-filename']))
      } else {
        ElMessage.error('无效的会话,或者会话已过期,请重新登录。');
      }
    })
  },
  resource(resource) {
    var url = baseURL + "/common/download/resource?resource=" + encodeURI(resource);
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then(async (res) => {
      const isLogin = await this.blobValidate(res.data);
      if (isLogin) {
        const blob = new Blob([res.data])
        this.saveAs(blob, decodeURI(res.headers['download-filename']))
      } else {
        ElMessage.error('无效的会话,或者会话已过期,请重新登录。');
      }
    })
  },
  zip(url, name) {
    var url = baseURL + url
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then(async (res) => {
      const isLogin = await blobValidate(res.data);
      if (isLogin) {
        const blob = new Blob([res.data], { type: 'application/zip' })
        this.saveAs(blob, name)
      } else {
        ElMessage.error('无效的会话,或者会话已过期,请重新登录。');
      }
    })
  },
  saveAs(text, name, opts) {
    saveAs(text, name, opts);
  }
}
src/plugins/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
import tab from './tab'
import auth from './auth'
import cache from './cache'
import modal from './modal'
import download from './download'
export default function installPlugins(app){
  // é¡µç­¾æ“ä½œ
  app.config.globalProperties.$tab = tab
  // è®¤è¯å¯¹è±¡
  app.config.globalProperties.$auth = auth
  // ç¼“存对象
  app.config.globalProperties.$cache = cache
  // æ¨¡æ€æ¡†å¯¹è±¡
  app.config.globalProperties.$modal = modal
  // ä¸‹è½½æ–‡ä»¶
  app.config.globalProperties.$download = download
}
src/plugins/modal.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
let loadingInstance;
export default {
  // æ¶ˆæ¯æç¤º
  msg(content) {
    ElMessage.info(content)
  },
  // é”™è¯¯æ¶ˆæ¯
  msgError(content) {
    ElMessage.error(content)
  },
  // æˆåŠŸæ¶ˆæ¯
  msgSuccess(content) {
    ElMessage.success(content)
  },
  // è­¦å‘Šæ¶ˆæ¯
  msgWarning(content) {
    ElMessage.warning(content)
  },
  // å¼¹å‡ºæç¤º
  alert(content) {
    ElMessageBox.alert(content, "系统提示")
  },
  // é”™è¯¯æç¤º
  alertError(content) {
    ElMessageBox.alert(content, "系统提示", { type: 'error' })
  },
  // æˆåŠŸæç¤º
  alertSuccess(content) {
    ElMessageBox.alert(content, "系统提示", { type: 'success' })
  },
  // è­¦å‘Šæç¤º
  alertWarning(content) {
    ElMessageBox.alert(content, "系统提示", { type: 'warning' })
  },
  // é€šçŸ¥æç¤º
  notify(content) {
    ElNotification.info(content)
  },
  // é”™è¯¯é€šçŸ¥
  notifyError(content) {
    ElNotification.error(content);
  },
  // æˆåŠŸé€šçŸ¥
  notifySuccess(content) {
    ElNotification.success(content)
  },
  // è­¦å‘Šé€šçŸ¥
  notifyWarning(content) {
    ElNotification.warning(content)
  },
  // ç¡®è®¤çª—体
  confirm(content) {
    return ElMessageBox.confirm(content, "系统提示", {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: "warning",
    })
  },
  // æäº¤å†…容
  prompt(content) {
    return ElMessageBox.prompt(content, "系统提示", {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: "warning",
    })
  },
  // æ‰“开遮罩层
  loading(content) {
    loadingInstance = ElLoading.service({
      lock: true,
      text: content,
      background: "rgba(0, 0, 0, 0.7)",
    })
  },
  // å…³é—­é®ç½©å±‚
  closeLoading() {
    loadingInstance.close();
  }
}
src/plugins/tab.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
import store from '@/store'
import router from '@/router'
export default {
  // åˆ·æ–°å½“前tab页签
  refreshPage(obj) {
    const { path, 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 };
          }
        }
      });
    }
    return store.dispatch('tagsView/delCachedView', obj).then(() => {
      const { path } = obj
      router.replace({
        path: '/redirect' + path
      })
    })
  },
  // å…³é—­å½“前tab页签,打开新页签
  closeOpenPage(obj) {
    store.dispatch("tagsView/delView", router.currentRoute.value);
    if (obj !== undefined) {
      return router.push(obj);
    }
  },
  // å…³é—­æŒ‡å®štab页签
  closePage(obj) {
    if (obj === undefined) {
      return store.dispatch('tagsView/delView', router.currentRoute.value).then(({ lastPath }) => {
        return router.push(lastPath || '/index');
      });
    }
    return store.dispatch('tagsView/delView', obj);
  },
  // å…³é—­æ‰€æœ‰tab页签
  closeAllPage() {
    return store.dispatch('tagsView/delAllViews');
  },
  // å…³é—­å·¦ä¾§tab页签
  closeLeftPage(obj) {
    return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute.value);
  },
  // å…³é—­å³ä¾§tab页签
  closeRightPage(obj) {
    return store.dispatch('tagsView/delRightTags', obj || router.currentRoute.value);
  },
  // å…³é—­å…¶ä»–tab页签
  closeOtherPage(obj) {
    return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute.value);
  },
  // æ‰“å¼€tab页签
  openPage(url) {
    return router.push(url);
  },
  // ä¿®æ”¹tab页签
  updatePage(obj) {
    return store.dispatch('tagsView/updateVisitedView', obj);
  }
}
src/router/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
import { createWebHistory, createRouter } from 'vue-router'
import Layout from '@/layout'
/**
 * Note: è·¯ç”±é…ç½®é¡¹
 *
 * hidden: true                     // å½“设置 true çš„æ—¶å€™è¯¥è·¯ç”±ä¸ä¼šå†ä¾§è¾¹æ å‡ºçް å¦‚401,login等页面,或者如一些编辑页面/edit/1
 * alwaysShow: true                 // å½“你一个路由下面的 children å£°æ˜Žçš„路由大于1个时,自动会变成嵌套的模式--如组件页面
 *                                  // åªæœ‰ä¸€ä¸ªæ—¶ï¼Œä¼šå°†é‚£ä¸ªå­è·¯ç”±å½“做根路由显示在侧边栏--如引导页面
 *                                  // è‹¥ä½ æƒ³ä¸ç®¡è·¯ç”±ä¸‹é¢çš„ children å£°æ˜Žçš„个数都显示你的根路由
 *                                  // ä½ å¯ä»¥è®¾ç½® alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
 * redirect: noRedirect             // å½“设置 noRedirect çš„æ—¶å€™è¯¥è·¯ç”±åœ¨é¢åŒ…屑导航中不可被点击
 * name:'router-name'               // è®¾å®šè·¯ç”±çš„名字,一定要填写不然使用<keep-alive>时会出现各种问题
 * query: '{"id": 1, "name": "ry"}' // è®¿é—®è·¯ç”±çš„默认传递参数
 * meta : {
    noCache: true                   // å¦‚果设置为true,则不会被 <keep-alive> ç¼“å­˜(默认 false)
    title: 'title'                  // è®¾ç½®è¯¥è·¯ç”±åœ¨ä¾§è¾¹æ å’Œé¢åŒ…屑中展示的名字
    icon: 'svg-name'                // è®¾ç½®è¯¥è·¯ç”±çš„图标,对应路径src/assets/icons/svg
    breadcrumb: false               // å¦‚果设置为false,则不会在breadcrumb面包屑中显示
    activeMenu: '/system/user'      // å½“路由设置了该属性,则会高亮相对应的侧边栏。
  }
 */
// å…¬å…±è·¯ç”±
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index.vue')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login'),
    hidden: true
  },
  {
    path: '/register',
    component: () => import('@/views/register'),
    hidden: true
  },
  {
    path: "/:pathMatch(.*)*",
    component: () => import('@/views/error/404'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error/401'),
    hidden: true
  },
  {
    path: '',
    component: Layout,
    redirect: 'index',
    children: [
      {
        path: '/index',
        component: () => import('@/views/index'),
        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'),
        name: 'Profile',
        meta: { title: '个人中心', icon: 'user' }
      }
    ]
  },
  {
    path: '/system/user-auth',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'role/:userId(\\d+)',
        component: () => import('@/views/system/user/authRole'),
        name: 'AuthRole',
        meta: { title: '分配角色', activeMenu: '/system/user' }
      }
    ]
  },
  {
    path: '/system/role-auth',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'user/:roleId(\\d+)',
        component: () => import('@/views/system/role/authUser'),
        name: 'AuthUser',
        meta: { title: '分配用户', activeMenu: '/system/role' }
      }
    ]
  },
  {
    path: '/system/dict-data',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'index/:dictId(\\d+)',
        component: () => import('@/views/system/dict/data'),
        name: 'Data',
        meta: { title: '字典数据', activeMenu: '/system/dict' }
      }
    ]
  },
  {
    path: '/monitor/job-log',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'index',
        component: () => import('@/views/monitor/job/log'),
        name: 'JobLog',
        meta: { title: '调度日志', activeMenu: '/monitor/job' }
      }
    ]
  },
  {
    path: '/tool/gen-edit',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'index',
        component: () => import('@/views/tool/gen/editTable'),
        name: 'GenEdit',
        meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
      }
    ]
  }
];
const router = createRouter({
  history: createWebHistory(),
  routes: constantRoutes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  },
});
export default router;
src/settings.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
export default {
  /**
   * ç½‘页标题
   */
  title: import.meta.env.VITE_APP_TITLE,
  /**
   * ä¾§è¾¹æ ä¸»é¢˜ æ·±è‰²ä¸»é¢˜theme-dark,浅色主题theme-light
   */
  sideTheme: 'theme-dark',
  /**
   * æ˜¯å¦ç³»ç»Ÿå¸ƒå±€é…ç½®
   */
  showSettings: false,
  /**
   * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
   */
  topNav: false,
  /**
   * æ˜¯å¦æ˜¾ç¤º tagsView
   */
  tagsView: true,
  /**
   * æ˜¯å¦å›ºå®šå¤´éƒ¨
   */
  fixedHeader: false,
  /**
   * æ˜¯å¦æ˜¾ç¤ºlogo
   */
  sidebarLogo: true,
  /**
   * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
   */
  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'
}
src/store/getters.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
const getters = {
  sidebar: state => state.app.sidebar,
  size: state => state.app.size,
  device: state => state.app.device,
  visitedViews: state => state.tagsView.visitedViews,
  cachedViews: state => state.tagsView.cachedViews,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  introduction: state => state.user.introduction,
  roles: state => state.user.roles,
  permissions: state => state.user.permissions,
  permission_routes: state => state.permission.routes,
  topbarRouters:state => state.permission.topbarRouters,
  defaultRoutes:state => state.permission.defaultRoutes,
  sidebarRouters:state => state.permission.sidebarRouters,
}
export default getters
src/store/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
import { createStore } from 'vuex'
import app from './modules/app'
import user from './modules/user'
import tagsView from './modules/tagsView'
import permission from './modules/permission'
import settings from './modules/settings'
import getters from './getters'
const store = createStore({
  modules: {
    app,
    user,
    tagsView,
    permission,
    settings
  },
  getters
});
export default store
src/store/modules/app.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
import Cookies from 'js-cookie'
const state = {
  sidebar: {
    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
    withoutAnimation: false
  },
  device: 'desktop',
  size: Cookies.get('size') || 'medium'
}
const mutations = {
  TOGGLE_SIDEBAR: state => {
    state.sidebar.opened = !state.sidebar.opened
    state.sidebar.withoutAnimation = false
    if (state.sidebar.opened) {
      Cookies.set('sidebarStatus', 1)
    } else {
      Cookies.set('sidebarStatus', 0)
    }
  },
  CLOSE_SIDEBAR: (state, withoutAnimation) => {
    Cookies.set('sidebarStatus', 0)
    state.sidebar.opened = false
    state.sidebar.withoutAnimation = withoutAnimation
  },
  TOGGLE_DEVICE: (state, device) => {
    state.device = device
  },
  SET_SIZE: (state, size) => {
    state.size = size
    Cookies.set('size', size)
  }
}
const actions = {
  toggleSideBar({ commit }) {
    commit('TOGGLE_SIDEBAR')
  },
  closeSideBar({ commit }, { withoutAnimation }) {
    commit('CLOSE_SIDEBAR', withoutAnimation)
  },
  toggleDevice({ commit }, device) {
    commit('TOGGLE_DEVICE', device)
  },
  setSize({ commit }, size) {
    commit('SET_SIZE', size)
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
src/store/modules/permission.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
import { constantRoutes } from '@/router'
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
// åŒ¹é…views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')
const permission = {
  state: {
    routes: [],
    addRoutes: [],
    defaultRoutes: [],
    topbarRouters: [],
    sidebarRouters: []
  },
  mutations: {
    SET_ROUTES: (state, routes) => {
      state.addRoutes = routes
      state.routes = constantRoutes.concat(routes)
    },
    SET_DEFAULT_ROUTES: (state, routes) => {
      state.defaultRoutes = constantRoutes.concat(routes)
    },
    SET_TOPBAR_ROUTES: (state, routes) => {
      // é¡¶éƒ¨å¯¼èˆªèœå•默认添加统计报表栏指向首页
      const index = [{
        path: 'index',
        meta: { title: '统计报表', icon: 'dashboard' }
      }]
      state.topbarRouters = routes.concat(index);
    },
    SET_SIDEBAR_ROUTERS: (state, routes) => {
      state.sidebarRouters = routes
    },
  },
  actions: {
    // ç”Ÿæˆè·¯ç”±
    GenerateRoutes({ commit }) {
      return new Promise(resolve => {
        // å‘后端请求路由数据
        getRouters().then(res => {
          const sdata = JSON.parse(JSON.stringify(res.data))
          const rdata = JSON.parse(JSON.stringify(res.data))
          const defaultData = JSON.parse(JSON.stringify(res.data))
          const sidebarRoutes = filterAsyncRouter(sdata)
          const rewriteRoutes = filterAsyncRouter(rdata, false, true)
          const defaultRoutes = filterAsyncRouter(defaultData)
          commit('SET_ROUTES', rewriteRoutes)
          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
          commit('SET_TOPBAR_ROUTES', defaultRoutes)
          resolve(rewriteRoutes)
        })
      })
    }
  }
}
// éåŽ†åŽå°ä¼ æ¥çš„è·¯ç”±å­—ç¬¦ä¸²ï¼Œè½¬æ¢ä¸ºç»„ä»¶å¯¹è±¡
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
  return asyncRouterMap.filter(route => {
    if (type && route.children) {
      route.children = filterChildren(route.children)
    }
    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
  })
}
function filterChildren(childrenMap, lastRouter = false) {
  var children = []
  childrenMap.forEach((el, index) => {
    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
}
export const loadView = (view) => {
  let res;
  for (const path in modules) {
    const dir = path.split('views/')[1].split('.vue')[0];
    if (dir === view) {
      res = () => modules[path]();
    }
  }
  return res;
}
export default permission
src/store/modules/settings.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import defaultSettings from '@/settings'
import { useDynamicTitle } from '@/utils/dynamicTitle'
const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
const state = {
  title: '',
  theme: storageSetting.theme || '#409EFF',
  sideTheme: storageSetting.sideTheme || sideTheme,
  showSettings: showSettings,
  topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
  tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
  fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
  sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
  dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle
}
const mutations = {
  CHANGE_SETTING: (state, { key, value }) => {
    if (state.hasOwnProperty(key)) {
      state[key] = value
    }
  }
}
const actions = {
  // ä¿®æ”¹å¸ƒå±€è®¾ç½®
  changeSetting({ commit }, data) {
    commit('CHANGE_SETTING', data)
  },
  // è®¾ç½®ç½‘页标题
  setTitle({ commit }, title) {
    state.title = title
    useDynamicTitle();
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
src/store/modules/tagsView.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,207 @@
const state = {
  visitedViews: [],
  cachedViews: []
}
const mutations = {
  ADD_VISITED_VIEW: (state, view) => {
    if (state.visitedViews.some(v => v.path === view.path)) return
    state.visitedViews.push(
      Object.assign({}, view, {
        title: view.meta.title || 'no-name'
      })
    )
  },
  ADD_CACHED_VIEW: (state, view) => {
    if (state.cachedViews.includes(view.name)) return
    if (!view.meta.noCache) {
      state.cachedViews.push(view.name)
    }
  },
  DEL_VISITED_VIEW: (state, view) => {
    for (const [i, v] of state.visitedViews.entries()) {
      if (v.path === view.path) {
        state.visitedViews.splice(i, 1)
        break
      }
    }
  },
  DEL_CACHED_VIEW: (state, view) => {
    const index = state.cachedViews.indexOf(view.name)
    index > -1 && state.cachedViews.splice(index, 1)
  },
  DEL_OTHERS_VISITED_VIEWS: (state, view) => {
    state.visitedViews = state.visitedViews.filter(v => {
      return v.meta.affix || v.path === view.path
    })
  },
  DEL_OTHERS_CACHED_VIEWS: (state, view) => {
    const index = state.cachedViews.indexOf(view.name)
    if (index > -1) {
      state.cachedViews = state.cachedViews.slice(index, index + 1)
    } else {
      state.cachedViews = []
    }
  },
  DEL_ALL_VISITED_VIEWS: state => {
    // keep affix tags
    const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
    state.visitedViews = affixTags
  },
  DEL_ALL_CACHED_VIEWS: state => {
    state.cachedViews = []
  },
  UPDATE_VISITED_VIEW: (state, view) => {
    for (let v of state.visitedViews) {
      if (v.path === view.path) {
        v = Object.assign(v, view)
        break
      }
    }
  },
  DEL_RIGHT_VIEWS: (state, view) => {
    const index = state.visitedViews.findIndex(v => v.path === view.path)
    if (index === -1) {
      return
    }
    state.visitedViews = state.visitedViews.filter((item, idx) => {
      if (idx <= index || (item.meta && item.meta.affix)) {
        return true
      }
      const i = state.cachedViews.indexOf(item.name)
      if (i > -1) {
        state.cachedViews.splice(i, 1)
      }
      return false
    })
  },
  DEL_LEFT_VIEWS: (state, view) => {
    const index = state.visitedViews.findIndex(v => v.path === view.path)
    if (index === -1) {
      return
    }
    state.visitedViews = state.visitedViews.filter((item, idx) => {
      if (idx >= index || (item.meta && item.meta.affix)) {
        return true
      }
      const i = state.cachedViews.indexOf(item.name)
      if (i > -1) {
        state.cachedViews.splice(i, 1)
      }
      return false
    })
  }
}
const actions = {
  addView({ dispatch }, view) {
    dispatch('addVisitedView', view)
    dispatch('addCachedView', view)
  },
  addVisitedView({ commit }, view) {
    commit('ADD_VISITED_VIEW', view)
  },
  addCachedView({ commit }, view) {
    commit('ADD_CACHED_VIEW', view)
  },
  delView({ dispatch, state }, view) {
    return new Promise(resolve => {
      dispatch('delVisitedView', view)
      dispatch('delCachedView', view)
      resolve({
        visitedViews: [...state.visitedViews],
        cachedViews: [...state.cachedViews]
      })
    })
  },
  delVisitedView({ commit, state }, view) {
    return new Promise(resolve => {
      commit('DEL_VISITED_VIEW', view)
      resolve([...state.visitedViews])
    })
  },
  delCachedView({ commit, state }, view) {
    return new Promise(resolve => {
      commit('DEL_CACHED_VIEW', view)
      resolve([...state.cachedViews])
    })
  },
  delOthersViews({ dispatch, state }, view) {
    return new Promise(resolve => {
      dispatch('delOthersVisitedViews', view)
      dispatch('delOthersCachedViews', view)
      resolve({
        visitedViews: [...state.visitedViews],
        cachedViews: [...state.cachedViews]
      })
    })
  },
  delOthersVisitedViews({ commit, state }, view) {
    return new Promise(resolve => {
      commit('DEL_OTHERS_VISITED_VIEWS', view)
      resolve([...state.visitedViews])
    })
  },
  delOthersCachedViews({ commit, state }, view) {
    return new Promise(resolve => {
      commit('DEL_OTHERS_CACHED_VIEWS', view)
      resolve([...state.cachedViews])
    })
  },
  delAllViews({ dispatch, state }, view) {
    return new Promise(resolve => {
      dispatch('delAllVisitedViews', view)
      dispatch('delAllCachedViews', view)
      resolve({
        visitedViews: [...state.visitedViews],
        cachedViews: [...state.cachedViews]
      })
    })
  },
  delAllVisitedViews({ commit, state }) {
    return new Promise(resolve => {
      commit('DEL_ALL_VISITED_VIEWS')
      resolve([...state.visitedViews])
    })
  },
  delAllCachedViews({ commit, state }) {
    return new Promise(resolve => {
      commit('DEL_ALL_CACHED_VIEWS')
      resolve([...state.cachedViews])
    })
  },
  updateVisitedView({ commit }, view) {
    commit('UPDATE_VISITED_VIEW', view)
  },
  delRightTags({ commit }, view) {
    return new Promise(resolve => {
      commit('DEL_RIGHT_VIEWS', view)
      resolve([...state.visitedViews])
    })
  },
  delLeftTags({ commit }, view) {
    return new Promise(resolve => {
      commit('DEL_LEFT_VIEWS', view)
      resolve([...state.visitedViews])
    })
  },
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
src/store/modules/user.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import defAva from '@/assets/images/profile.jpg'
const user = {
  state: {
    token: getToken(),
    name: '',
    avatar: '',
    roles: [],
    permissions: []
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      state.name = name
    },
    SET_AVATAR: (state, avatar) => {
      state.avatar = avatar
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_PERMISSIONS: (state, permissions) => {
      state.permissions = permissions
    }
  },
  actions: {
    // ç™»å½•
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid).then(res => {
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
    // èŽ·å–ç”¨æˆ·ä¿¡æ¯
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo().then(res => {
          const user = res.user
          const avatar = user.avatar == "" ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
          if (res.roles && res.roles.length > 0) { // éªŒè¯è¿”回的roles是否是一个非空数组
            commit('SET_ROLES', res.roles)
            commit('SET_PERMISSIONS', res.permissions)
          } else {
            commit('SET_ROLES', ['ROLE_DEFAULT'])
          }
          commit('SET_NAME', user.userName)
          commit('SET_AVATAR', avatar)
          resolve(res)
        }).catch(error => {
          reject(error)
        })
      })
    },
    // é€€å‡ºç³»ç»Ÿ
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_PERMISSIONS', [])
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
    // å‰ç«¯ ç™»å‡º
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}
export default user
src/utils/auth.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
  return Cookies.get(TokenKey)
}
export function setToken(token) {
  return Cookies.set(TokenKey, token)
}
export function removeToken() {
  return Cookies.remove(TokenKey)
}
src/utils/dict.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
import { getDicts } from '@/api/system/dict/data'
/**
 * èŽ·å–å­—å…¸æ•°æ®
 */
export function useDict(...args) {
  const res = ref({});
  return (() => {
    args.forEach((d, index) => {
      res.value[d] = [];
      getDicts(d).then(resp => {
        res.value[d] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass }))
      })
    })
    return toRefs(res.value);
  })()
}
src/utils/dynamicTitle.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
import store from '@/store'
import defaultSettings from '@/settings'
/**
 * åŠ¨æ€ä¿®æ”¹æ ‡é¢˜
 */
export function useDynamicTitle() {
  if (store.state.settings.dynamicTitle) {
    document.title = store.state.settings.title + ' - ' + defaultSettings.title;
  } else {
    document.title = defaultSettings.title;
  }
}
src/utils/errorCode.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
export default {
  '401': '认证失败,无法访问系统资源',
  '403': '当前操作没有权限',
  '404': '访问资源不存在',
  'default': '系统未知错误,请反馈给管理员'
}
src/utils/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,390 @@
import { parseTime } from '@/ruoyi'
/**
 * è¡¨æ ¼æ—¶é—´æ ¼å¼åŒ–
 */
export function formatDate(cellValue) {
  if (cellValue == null || cellValue == "") return "";
  var date = new Date(cellValue)
  var year = date.getFullYear()
  var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
  var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
  return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
}
/**
 * @param {number} time
 * @param {string} option
 * @returns {string}
 */
export function formatTime(time, option) {
  if (('' + time).length === 10) {
    time = parseInt(time) * 1000
  } else {
    time = +time
  }
  const d = new Date(time)
  const now = Date.now()
  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(time, option)
  } else {
    return (
      d.getMonth() +
      1 +
      '月' +
      d.getDate() +
      '日' +
      d.getHours() +
      '时' +
      d.getMinutes() +
      '分'
    )
  }
}
/**
 * @param {string} url
 * @returns {Object}
 */
export function getQueryObject(url) {
  url = url == null ? window.location.href : url
  const search = url.substring(url.lastIndexOf('?') + 1)
  const obj = {}
  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
}
/**
 * @param {string} input value
 * @returns {number} output value
 */
export function byteLength(str) {
  // returns the byte length of an utf8 string
  let s = str.length
  for (var 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
}
/**
 * @param {Array} actual
 * @returns {Array}
 */
export function cleanArray(actual) {
  const newArray = []
  for (let i = 0; i < actual.length; i++) {
    if (actual[i]) {
      newArray.push(actual[i])
    }
  }
  return newArray
}
/**
 * @param {Object} json
 * @returns {Array}
 */
export function param(json) {
  if (!json) return ''
  return cleanArray(
    Object.keys(json).map(key => {
      if (json[key] === undefined) return ''
      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
    })
  ).join('&')
}
/**
 * @param {string} url
 * @returns {Object}
 */
export function param2Obj(url) {
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
  if (!search) {
    return {}
  }
  const obj = {}
  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
}
/**
 * @param {string} val
 * @returns {string}
 */
export function html2Text(val) {
  const div = document.createElement('div')
  div.innerHTML = val
  return div.textContent || div.innerText
}
/**
 * Merges two objects, giving the last one precedence
 * @param {Object} target
 * @param {(Object|Array)} source
 * @returns {Object}
 */
export function objectMerge(target, source) {
  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
}
/**
 * @param {HTMLElement} element
 * @param {string} className
 */
export function toggleClass(element, className) {
  if (!element || !className) {
    return
  }
  let classString = element.className
  const nameIndex = classString.indexOf(className)
  if (nameIndex === -1) {
    classString += '' + className
  } else {
    classString =
      classString.substr(0, nameIndex) +
      classString.substr(nameIndex + className.length)
  }
  element.className = classString
}
/**
 * @param {string} type
 * @returns {Date}
 */
export function getTime(type) {
  if (type === 'start') {
    return new Date().getTime() - 3600 * 1000 * 24 * 90
  } else {
    return new Date(new Date().toDateString())
  }
}
/**
 * @param {Function} func
 * @param {number} wait
 * @param {boolean} immediate
 * @return {*}
 */
export function debounce(func, wait, immediate) {
  let timeout, args, context, timestamp, result
  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
      }
    }
  }
  return function(...args) {
    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
  }
}
/**
 * This is just a simple version of deep copy
 * Has a lot of edge cases bug
 * If you want to use a perfect deep copy, use lodash's _.cloneDeep
 * @param {Object} source
 * @returns {Object}
 */
export function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone')
  }
  const targetObj = 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
}
/**
 * @param {Array} arr
 * @returns {Array}
 */
export function uniqueArr(arr) {
  return Array.from(new Set(arr))
}
/**
 * @returns {string}
 */
export function createUniqueString() {
  const timestamp = +new Date() + ''
  const randomNum = parseInt((1 + Math.random()) * 65536) + ''
  return (+(randomNum + timestamp)).toString(32)
}
/**
 * Check if an element has a class
 * @param {HTMLElement} elm
 * @param {string} cls
 * @returns {boolean}
 */
export function hasClass(ele, cls) {
  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}
/**
 * Add class to element
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function addClass(ele, cls) {
  if (!hasClass(ele, cls)) ele.className += ' ' + cls
}
/**
 * Remove class from element
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function removeClass(ele, cls) {
  if (hasClass(ele, cls)) {
    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
    ele.className = ele.className.replace(reg, ' ')
  }
}
export function makeMap(str, expectsLowerCase) {
  const map = Object.create(null)
  const list = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase
    ? val => map[val.toLowerCase()]
    : val => map[val]
}
export const exportDefault = 'export default '
export const beautifierConf = {
  html: {
    indent_size: '2',
    indent_char: ' ',
    max_preserve_newlines: '-1',
    preserve_newlines: false,
    keep_array_indentation: false,
    break_chained_methods: false,
    indent_scripts: 'separate',
    brace_style: 'end-expand',
    space_before_conditional: true,
    unescape_strings: false,
    jslint_happy: false,
    end_with_newline: true,
    wrap_line_length: '110',
    indent_inner_html: true,
    comma_first: false,
    e4x: true,
    indent_empty_lines: true
  },
  js: {
    indent_size: '2',
    indent_char: ' ',
    max_preserve_newlines: '-1',
    preserve_newlines: false,
    keep_array_indentation: false,
    break_chained_methods: false,
    indent_scripts: 'normal',
    brace_style: 'end-expand',
    space_before_conditional: true,
    unescape_strings: false,
    jslint_happy: true,
    end_with_newline: true,
    wrap_line_length: '110',
    indent_inner_html: true,
    comma_first: false,
    e4x: true,
    indent_empty_lines: true
  }
}
// é¦–字母大小
export function titleCase(str) {
  return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())
}
// ä¸‹åˆ’转驼峰
export function camelCase(str) {
  return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase())
}
export function isNumberStr(str) {
  return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
}
src/utils/jsencrypt.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
// å¯†é’¥å¯¹ç”Ÿæˆ http://web.chacuo.net/netrsakeypair
const publicKey = '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='
// åР坆
export function encrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // è®¾ç½®å…¬é’¥
  return encryptor.encrypt(txt) // å¯¹æ•°æ®è¿›è¡ŒåР坆
}
// è§£å¯†
export function decrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPrivateKey(privateKey) // è®¾ç½®ç§é’¥
  return encryptor.decrypt(txt) // å¯¹æ•°æ®è¿›è¡Œè§£å¯†
}
src/utils/permission.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
import store from '@/store'
/**
 * å­—符权限校验
 * @param {Array} value æ ¡éªŒå€¼
 * @returns {Boolean}
 */
export function checkPermi(value) {
  if (value && value instanceof Array && value.length > 0) {
    const permissions = store.getters && store.getters.permissions
    const permissionDatas = value
    const all_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
  }
}
/**
 * è§’色权限校验
 * @param {Array} value æ ¡éªŒå€¼
 * @returns {Boolean}
 */
export function checkRole(value) {
  if (value && value instanceof Array && value.length > 0) {
    const roles = store.getters && store.getters.roles
    const permissionRoles = value
    const super_admin = "admin";
    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
  }
}
src/utils/request.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/ruoyi'
import { saveAs } from 'file-saver'
let downloadLoadingInstance;
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// åˆ›å»ºaxios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: import.meta.env.VITE_APP_BASE_API,
  // è¶…æ—¶
  timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
  // æ˜¯å¦éœ€è¦è®¾ç½® token
  const isToken = (config.headers || {}).isToken === false
  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;
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})
// å“åº”拦截器
service.interceptors.response.use(res => {
    // æœªè®¾ç½®çŠ¶æ€ç åˆ™é»˜è®¤æˆåŠŸçŠ¶æ€
    const code = res.data.code || 200;
    // èŽ·å–é”™è¯¯ä¿¡æ¯
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // äºŒè¿›åˆ¶æ•°æ®åˆ™ç›´æŽ¥è¿”回
    if(res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer'){
      return res.data
    }
    if (code === 401) {
      ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).then(() => {
        store.dispatch('LogOut').then(() => {
          location.href = '/index';
        })
      }).catch(() => {});
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    } else if (code === 500) {
      ElMessage({
        message: msg,
        type: 'error'
      })
      return Promise.reject(new Error(msg))
    } else if (code !== 200) {
      ElNotification.error({
        title: msg
      })
      return Promise.reject('error')
    } else {
      return  Promise.resolve(res.data)
    }
  },
  error => {
    console.log('err' + 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, params, filename) {
  downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
  return service.post(url, params, {
    transformRequest: [(params) => { return tansParams(params) }],
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob'
  }).then(async (data) => {
    const isLogin = await blobValidate(data);
    if (isLogin) {
      const blob = new Blob([data])
      saveAs(blob, filename)
    } else {
      ELMessage.error('无效的会话,或者会话已过期,请重新登录。');
    }
    downloadLoadingInstance.close();
  }).catch((r) => {
    console.error(r)
    ELMessage.error('下载文件出现错误,请联系管理员!')
    downloadLoadingInstance.close();
  })
}
export default service
src/utils/ruoyi.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,235 @@
/**
 * é€šç”¨js方法封装处理
 * Copyright (c) 2019 ruoyi
 */
// æ—¥æœŸæ ¼å¼åŒ–
export function parseTime(time, pattern) {
  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 = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    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
  })
  return time_str
}
// è¡¨å•重置
export function resetForm(refName) {
  if (this.$refs[refName]) {
    this.$refs[refName].resetFields();
  }
}
// æ·»åŠ æ—¥æœŸèŒƒå›´
export function addDateRange(params, dateRange, propName) {
  let 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 function selectDictLabel(datas, value) {
  var actions = [];
  Object.keys(datas).some((key) => {
    if (datas[key].value == ('' + value)) {
      actions.push(datas[key].label);
      return true;
    }
  })
  return actions.join('');
}
// å›žæ˜¾æ•°æ®å­—典(字符串数组)
export function selectDictLabels(datas, value, separator) {
  var actions = [];
  var currentSeparator = undefined === separator ? "," : separator;
  var temp = value.split(currentSeparator);
  Object.keys(value.split(currentSeparator)).some((val) => {
    Object.keys(datas).some((key) => {
      if (datas[key].value == ('' + temp[val])) {
        actions.push(datas[key].label + currentSeparator);
      }
    })
  })
  return actions.join('').substring(0, actions.join('').length - 1);
}
// å­—符串格式化(%s )
export function sprintf(str) {
  var args = arguments, flag = true, i = 1;
  str = str.replace(/%s/g, function () {
    var arg = args[i++];
    if (typeof arg === 'undefined') {
      flag = false;
      return '';
    }
    return arg;
  });
  return flag ? str : '';
}
// è½¬æ¢å­—符串,undefined,null等转化为""
export function praseStrEmpty(str) {
  if (!str || str == "undefined" || str == "null") {
    return "";
  }
  return str;
}
// æ•°æ®åˆå¹¶
export function mergeRecursive(source, target) {
  for (var 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;
};
/**
 * æž„造树型结构数据
 * @param {*} data æ•°æ®æº
 * @param {*} id id字段 é»˜è®¤ 'id'
 * @param {*} parentId çˆ¶èŠ‚ç‚¹å­—æ®µ é»˜è®¤ 'parentId'
 * @param {*} children å­©å­èŠ‚ç‚¹å­—æ®µ é»˜è®¤ 'children'
 */
export function handleTree(data, id, parentId, children) {
  let config = {
    id: id || 'id',
    parentId: parentId || 'parentId',
    childrenList: children || 'children'
  };
  var childrenListMap = {};
  var nodeIds = {};
  var tree = [];
  for (let d of data) {
    let parentId = d[config.parentId];
    if (childrenListMap[parentId] == null) {
      childrenListMap[parentId] = [];
    }
    nodeIds[d[config.id]] = d;
    childrenListMap[parentId].push(d);
  }
  for (let d of data) {
    let parentId = d[config.parentId];
    if (nodeIds[parentId] == null) {
      tree.push(d);
    }
  }
  for (let t of tree) {
    adaptToChildrenList(t);
  }
  function adaptToChildrenList(o) {
    if (childrenListMap[o[config.id]] !== null) {
      o[config.childrenList] = childrenListMap[o[config.id]];
    }
    if (o[config.childrenList]) {
      for (let c of o[config.childrenList]) {
        adaptToChildrenList(c);
      }
    }
  }
  return tree;
}
/**
* å‚数处理
* @param {*} params  å‚æ•°
*/
export function tansParams(params) {
  let result = ''
  for (const propName of Object.keys(params)) {
    const value = params[propName];
    var part = encodeURIComponent(propName) + "=";
    if (value !== null && typeof (value) !== "undefined") {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && typeof (value[key]) !== 'undefined') {
            let params = propName + '[' + key + ']';
            var subPart = encodeURIComponent(params) + "=";
            result += subPart + encodeURIComponent(value[key]) + "&";
          }
        }
      } else {
        result += part + encodeURIComponent(value) + "&";
      }
    }
  }
  return result
}
// è¿”回项目路径
export function getNormalPath(p) {
  if (p.length === 0 || !p || p == 'undefined') {
    return p
  };
  let res = p.replace('//', '/')
  if (res[res.length - 1] === '/') {
    return res.slice(0, res.length - 1)
  }
  return res;
}
// éªŒè¯æ˜¯å¦ä¸ºblob格式
export async function blobValidate(data) {
  try {
    const text = await data.text();
    JSON.parse(text);
    return false;
  } catch (error) {
    return true;
  }
}
src/utils/scroll-to.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
Math.easeInOutQuad = function(t, b, c, d) {
  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
var requestAnimFrame = (function() {
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
})()
/**
 * Because it's so fucking difficult to detect the scrolling element, just move them all
 * @param {number} amount
 */
function move(amount) {
  document.documentElement.scrollTop = amount
  document.body.parentNode.scrollTop = amount
  document.body.scrollTop = amount
}
function position() {
  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
}
/**
 * @param {number} to
 * @param {number} duration
 * @param {Function} callback
 */
export function scrollTo(to, duration, callback) {
  const start = position()
  const change = to - start
  const increment = 20
  let currentTime = 0
  duration = (typeof (duration) === 'undefined') ? 500 : duration
  var animateScroll = function() {
    // increment the time
    currentTime += increment
    // find the value with the quadratic in-out easing function
    var val = Math.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/validate.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
/**
 * åˆ¤æ–­url是否是http或https
 * @param {string} path
 * @returns {Boolean}
 */
 export function isHttp(url) {
  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}
/**
 * åˆ¤æ–­path是否为外链
 * @param {string} path
 * @returns {Boolean}
 */
 export function isExternal(path) {
  return /^(https?:|mailto:|tel:)/.test(path)
}
/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validUsername(str) {
  const valid_map = ['admin', 'editor']
  return valid_map.indexOf(str.trim()) >= 0
}
/**
 * @param {string} url
 * @returns {Boolean}
 */
export function validURL(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)
}
/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validLowerCase(str) {
  const reg = /^[a-z]+$/
  return reg.test(str)
}
/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validUpperCase(str) {
  const reg = /^[A-Z]+$/
  return reg.test(str)
}
/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validAlphabets(str) {
  const reg = /^[A-Za-z]+$/
  return reg.test(str)
}
/**
 * @param {string} email
 * @returns {Boolean}
 */
export function validEmail(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)
}
/**
 * @param {string} str
 * @returns {Boolean}
 */
export function isString(str) {
  if (typeof str === 'string' || str instanceof String) {
    return true
  }
  return false
}
/**
 * @param {Array} arg
 * @returns {Boolean}
 */
export function isArray(arg) {
  if (typeof Array.isArray === 'undefined') {
    return Object.prototype.toString.call(arg) === '[object Array]'
  }
  return Array.isArray(arg)
}
src/views/error/401.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
<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>
</template>
<script setup>
import errImage from "@/assets/401_images/401.gif";
let { proxy } = getCurrentInstance();
const errGif = ref(errImage + "?" + +new Date());
function back() {
  if (proxy.$route.query.noGoBack) {
    proxy.$router.push({ path: "/" });
  } else {
    proxy.$router.go(-1);
  }
}
</script>
<style lang="scss" scoped>
.errPage-container {
  width: 800px;
  max-width: 100%;
  margin: 100px auto;
  .pan-back-btn {
    background: #008489;
    color: #fff;
    border: none !important;
  }
  .pan-gif {
    margin: 0 auto;
    display: block;
  }
  .pan-img {
    display: block;
    margin: 0 auto;
    width: 100%;
  }
  .text-jumbo {
    font-size: 60px;
    font-weight: 700;
    color: #484848;
  }
  .list-unstyled {
    font-size: 14px;
    li {
      padding-bottom: 5px;
    }
    a {
      color: #008489;
      text-decoration: none;
      &:hover {
        text-decoration: underline;
      }
    }
  }
}
</style>
src/views/error/404.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,227 @@
<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>
</template>
<script setup>
let message = computed(() => {
  return '找不到网页!'
})
</script>
<style lang="scss" scoped>
.wscn-http404-container{
  transform: translate(-50%,-50%);
  position: absolute;
  top: 40%;
  left: 50%;
}
.wscn-http404 {
  position: relative;
  width: 1200px;
  padding: 0 50px;
  overflow: hidden;
  .pic-404 {
    position: relative;
    float: left;
    width: 600px;
    overflow: hidden;
    &__parent {
      width: 100%;
    }
    &__child {
      position: absolute;
      &.left {
        width: 80px;
        top: 17px;
        left: 220px;
        opacity: 0;
        animation-name: cloudLeft;
        animation-duration: 2s;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-delay: 1s;
      }
      &.mid {
        width: 46px;
        top: 10px;
        left: 420px;
        opacity: 0;
        animation-name: cloudMid;
        animation-duration: 2s;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-delay: 1.2s;
      }
      &.right {
        width: 62px;
        top: 100px;
        left: 500px;
        opacity: 0;
        animation-name: cloudRight;
        animation-duration: 2s;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-delay: 1s;
      }
      @keyframes cloudLeft {
        0% {
          top: 17px;
          left: 220px;
          opacity: 0;
        }
        20% {
          top: 33px;
          left: 188px;
          opacity: 1;
        }
        80% {
          top: 81px;
          left: 92px;
          opacity: 1;
        }
        100% {
          top: 97px;
          left: 60px;
          opacity: 0;
        }
      }
      @keyframes cloudMid {
        0% {
          top: 10px;
          left: 420px;
          opacity: 0;
        }
        20% {
          top: 40px;
          left: 360px;
          opacity: 1;
        }
        70% {
          top: 130px;
          left: 180px;
          opacity: 1;
        }
        100% {
          top: 160px;
          left: 120px;
          opacity: 0;
        }
      }
      @keyframes cloudRight {
        0% {
          top: 100px;
          left: 500px;
          opacity: 0;
        }
        20% {
          top: 120px;
          left: 460px;
          opacity: 1;
        }
        80% {
          top: 180px;
          left: 340px;
          opacity: 1;
        }
        100% {
          top: 200px;
          left: 300px;
          opacity: 0;
        }
      }
    }
  }
  .bullshit {
    position: relative;
    float: left;
    width: 300px;
    padding: 30px 0;
    overflow: hidden;
    &__oops {
      font-size: 32px;
      font-weight: bold;
      line-height: 40px;
      color: #1482f0;
      opacity: 0;
      margin-bottom: 20px;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-fill-mode: forwards;
    }
    &__headline {
      font-size: 20px;
      line-height: 24px;
      color: #222;
      font-weight: bold;
      opacity: 0;
      margin-bottom: 10px;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-delay: 0.1s;
      animation-fill-mode: forwards;
    }
    &__info {
      font-size: 13px;
      line-height: 21px;
      color: grey;
      opacity: 0;
      margin-bottom: 30px;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-delay: 0.2s;
      animation-fill-mode: forwards;
    }
    &__return-home {
      display: block;
      float: left;
      width: 110px;
      height: 36px;
      background: #1482f0;
      border-radius: 100px;
      text-align: center;
      color: #ffffff;
      opacity: 0;
      font-size: 14px;
      line-height: 36px;
      cursor: pointer;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-delay: 0.3s;
      animation-fill-mode: forwards;
    }
    @keyframes slideUp {
      0% {
        transform: translateY(60px);
        opacity: 0;
      }
      100% {
        transform: translateY(0);
        opacity: 1;
      }
    }
  }
}
</style>
src/views/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,738 @@
<template>
  <div class="app-container home">
    <el-row :gutter="20">
      <el-col :sm="24" :lg="24">
        <blockquote class="text-warning" style="font-size: 14px">
          é¢†å–阿里云通用云产品1888优惠券
          <br />
          <el-link
            href="https://www.aliyun.com/minisite/goods?userCode=brki8iof"
            type="primary"
            target="_blank"
            >https://www.aliyun.com/minisite/goods?userCode=brki8iof</el-link
          >
          <br />
          é¢†å–腾讯云通用云产品2860优惠券
          <br />
          <el-link
            href="https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console"
            type="primary"
            target="_blank"
            >https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console</el-link
          >
          <br />
          é˜¿é‡Œäº‘服务器折扣区
          <el-link href="http://aly.ruoyi.vip" type="primary" target="_blank"
            >>☛☛点我进入☚☚</el-link
          >
          &nbsp;&nbsp;&nbsp; è…¾è®¯äº‘服务器秒杀区
          <el-link href="http://txy.ruoyi.vip" type="primary" target="_blank"
            >>☛☛点我进入☚☚</el-link
          ><br />
          <h4 class="text-danger">
            äº‘产品通用红包,可叠加官网常规优惠使用。(仅限新用户)
          </h4>
        </blockquote>
        <hr />
      </el-col>
    </el-row>
    <el-row :gutter="20">
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
        <h2>若依后台管理框架</h2>
        <p>
          ä¸€ç›´æƒ³åšä¸€æ¬¾åŽå°ç®¡ç†ç³»ç»Ÿï¼Œçœ‹äº†å¾ˆå¤šä¼˜ç§€çš„开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了若依管理系统。,她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。
        </p>
        <p>
          <b>当前版本:</b> <span>v{{ version }}</span>
        </p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
        <p>
          <el-button
            type="primary"
            size="mini"
            icon="el-icon-cloudy"
            plain
            @click="goTarget('https://gitee.com/y_project/RuoYi-Vue')"
            >访问码云</el-button
          >
          <el-button
            size="mini"
            icon="el-icon-s-home"
            plain
            @click="goTarget('http://ruoyi.vip')"
            >访问主页</el-button
          >
        </p>
      </el-col>
      <el-col :sm="24" :lg="12" style="padding-left: 50px">
        <el-row>
          <el-col :span="12">
            <h2>技术选型</h2>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="6">
            <h4>后端技术</h4>
            <ul>
              <li>SpringBoot</li>
              <li>Spring Security</li>
              <li>JWT</li>
              <li>MyBatis</li>
              <li>Druid</li>
              <li>Fastjson</li>
              <li>...</li>
            </ul>
          </el-col>
          <el-col :span="6">
            <h4>前端技术</h4>
            <ul>
              <li>Vue</li>
              <li>Vuex</li>
              <li>Element-ui</li>
              <li>Axios</li>
              <li>Sass</li>
              <li>Quill</li>
              <li>...</li>
            </ul>
          </el-col>
        </el-row>
      </el-col>
    </el-row>
    <el-divider />
    <el-row :gutter="20">
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
        <el-card class="update-log">
          <template v-slot:header>
            <div class="clearfix">
              <span>联系信息</span>
            </div>
          </template>
          <div class="body">
            <p>
              <i class="el-icon-s-promotion"></i> å®˜ç½‘:<el-link
                href="http://www.ruoyi.vip"
                target="_blank"
                >http://www.ruoyi.vip</el-link
              >
            </p>
            <p>
              <i class="el-icon-user-solid"></i> QQ群:<s>满937441</s>
              <s>满887144332</s> <s>满180251782</s> <s>满104180207</s>
              <s>满186866453</s> <s>满201396349</s> <s>满101456076</s>
              <a href="https://jq.qq.com/?_wv=1027&k=KmQbXyJ6" target="_blank">
                101539465</a
              >
            </p>
            <p>
              <i class="el-icon-chat-dot-round"></i> å¾®ä¿¡ï¼š<a
                href="javascript:;"
                >/ *若依</a
              >
            </p>
            <p>
              <i class="el-icon-money"></i> æ”¯ä»˜å®ï¼š<a
                href="javascript:;"
                class="支付宝信息"
                >/ *若依</a
              >
            </p>
          </div>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
        <el-card class="update-log">
          <template v-slot:header>
            <div class="clearfix">
              <span>更新日志</span>
            </div>
          </template>
          <el-collapse accordion>
            <el-collapse-item title="v3.7.0 - 2021-09-13">
              <ol>
                <li>参数管理支持配置验证码开关</li>
                <li>新增是否开启用户注册功能</li>
                <li>定时任务支持在线生成cron表达式</li>
                <li>菜单管理支持配置路由参数</li>
                <li>支持自定义注解实现接口限流</li>
                <li>Excel注解支持Image图片导入</li>
                <li>自定义弹层溢出滚动样式</li>
                <li>自定义可拖动弹窗宽度指令</li>
                <li>自定义可拖动弹窗高度指令</li>
                <li>修复任意账户越权问题</li>
                <li>修改时检查用户数据权限范围</li>
                <li>修复保存配置主题颜色失效问题</li>
                <li>新增暗色菜单风格主题</li>
                <li>菜单&部门新增展开/折叠功能</li>
                <li>页签新增关闭左侧&添加图标</li>
                <li>顶部菜单排除隐藏的默认路由</li>
                <li>顶部菜单同步系统主题样式</li>
                <li>跳转路由高亮相对应的菜单栏</li>
                <li>代码生成主子表多选行数据</li>
                <li>日期范围支持添加多组</li>
                <li>升级element-ui到最新版本2.15.5</li>
                <li>升级oshi到最新版本v5.8.0</li>
                <li>升级commons.io到最新版本v2.11.0</li>
                <li>定时任务屏蔽ldap远程调用</li>
                <li>定时任务屏蔽http(s)远程调用</li>
                <li>补充定时任务表字段注释</li>
                <li>定时任务对检查异常进行事务回滚</li>
                <li>启用父部门状态排除顶级节点</li>
                <li>富文本新增上传文件大小限制</li>
                <li>默认首页使用keep-alive缓存</li>
                <li>修改代码生成字典回显样式</li>
                <li>自定义分页合理化传入参数</li>
                <li>修复字典组件值为整形不显示问题</li>
                <li>修复定时任务日志执行状态显示</li>
                <li>角色&菜单新增字段属性提示信息</li>
                <li>修复角色分配用户页面参数类型错误提醒</li>
                <li>优化布局设置动画特效</li>
                <li>优化异常处理信息</li>
                <li>优化错误token导致的解析异常</li>
                <li>密码框新增显示切换密码图标</li>
                <li>定时任务新增更多操作</li>
                <li>更多操作按钮添加权限控制</li>
                <li>导入用户样式优化</li>
                <li>提取通用方法到基类控制器</li>
                <li>优化使用权限工具获取用户信息</li>
                <li>优化用户不能删除自己</li>
                <li>优化XSS跨站脚本过滤</li>
                <li>优化代码生成模板</li>
                <li>验证码默认20s超时</li>
                <li>BLOB下载时清除URL对象引用</li>
                <li>代码生成导入表按创建时间排序</li>
                <li>修复代码生成页面数据编辑保存之后总是跳转第一页的问题</li>
                <li>修复带safari浏览器无法格式化utc日期格式yyyy-MM-dd'T'HH:mm:ss.SSS问题</li>
                <li>多图上传组件移除多余的api地址&验证失败导致图片删除问题&无法删除相应图片修复</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.6.0 - 2021-07-12">
              <ol>
                <li>角色管理新增分配用户功能</li>
                <li>用户管理新增分配角色功能</li>
                <li>日志列表支持排序操作</li>
                <li>优化参数&字典缓存操作</li>
                <li>系统布局配置支持动态标题开关</li>
                <li>菜单路由配置支持内链访问</li>
                <li>默认访问后端首页新增提示语</li>
                <li>富文本默认上传返回url类型</li>
                <li>新增自定义弹窗拖拽指令</li>
                <li>全局注册常用通用组件</li>
                <li>全局挂载字典标签组件</li>
                <li>ImageUpload组件支持多图片上传</li>
                <li>FileUpload组件支持多文件上传</li>
                <li>文件上传组件添加数量限制属性</li>
                <li>富文本编辑组件添加类型属性</li>
                <li>富文本组件工具栏配置视频</li>
                <li>封装通用iframe组件</li>
                <li>限制超级管理员不允许操作</li>
                <li>用户信息长度校验限制</li>
                <li>分页组件新增pagerCount属性</li>
                <li>添加bat脚本执行应用</li>
                <li>升级oshi到最新版本v5.7.4</li>
                <li>升级element-ui到最新版本2.15.2</li>
                <li>升级pagehelper到最新版1.3.1</li>
                <li>升级commons.io到最新版本v2.10.0</li>
                <li>升级commons.fileupload到最新版本v1.4</li>
                <li>升级swagger到最新版本v3.0.0</li>
                <li>修复关闭confirm提示框控制台报错问题</li>
                <li>修复存在的SQL注入漏洞问题</li>
                <li>定时任务屏蔽rmi远程调用</li>
                <li>修复用户搜索分页变量错误</li>
                <li>修复导出角色数据范围翻译缺少仅本人</li>
                <li>修复表单构建选择下拉选择控制台报错问题</li>
                <li>优化图片工具类读取文件</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.5.0 - 2021-05-25">
              <ol>
                <li>新增菜单导航显示风格TopNav(false为左侧导航菜单,true为顶部导航菜单)</li>
                <li>布局设置支持保存&重置配置</li>
                <li>修复树表数据显示不全&加载慢问题</li>
                <li>新增IE浏览器版本过低提示页面</li>
                <li>用户登录后记录最后登录IP&时间</li>
                <li>页面导出按钮点击之后添加遮罩</li>
                <li>富文本编辑器支持自定义上传地址</li>
                <li>富文本编辑组件新增readOnly属性</li>
                <li>页签TagsView新增关闭右侧功能</li>
                <li>显隐列组件加载初始默认隐藏列</li>
                <li>关闭头像上传窗口还原默认图片</li>
                <li>个人信息添加手机&邮箱重复验证</li>
                <li>代码生成模板导出按钮点击后添加遮罩</li>
                <li>代码生成模板树表操作列添加新增按钮</li>
                <li>代码生成模板修复主子表字段重名问题</li>
                <li>升级fastjson到最新版1.2.76</li>
                <li>升级druid到最新版本v1.2.6</li>
                <li>升级mybatis到最新版3.5.6 é˜»æ­¢è¿œç¨‹ä»£ç æ‰§è¡Œæ¼æ´ž</li>
                <li>升级oshi到最新版本v5.6.0</li>
                <li>velocity剔除commons-collections版本,防止3.2.1版本的反序列化漏洞</li>
                <li>数据监控页默认账户密码防止越权访问</li>
                <li>修复firefox下表单构建拖拽会新打卡一个选项卡</li>
                <li>修正后端导入表权限标识</li>
                <li>修正前端操作日志&登录日志权限标识</li>
                <li>设置Redis配置HashKey序列化</li>
                <li>删除操作日志记录信息</li>
                <li>上传媒体类型添加视频格式</li>
                <li>修复请求形参未传值记录日志异常问题</li>
                <li>优化xss校验json请求条件</li>
                <li>树级结构更新子节点使用replaceFirst</li>
                <li>优化ExcelUtil空值处理</li>
                <li>日志记录过滤BindingResult对象,防止异常</li>
                <li>修改主题后mini类型按钮无效问题</li>
                <li>优化通用下载完成后删除节点</li>
                <li>通用Controller添加响应返回消息</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.4.0 - 2021-02-22">
              <ol>
                <li>代码生成模板支持主子表</li>
                <li>表格右侧工具栏组件支持显隐列</li>
                <li>图片组件添加预览&移除功能</li>
                <li>Excel注解支持Image图片导出</li>
                <li>操作按钮组调整为朴素按钮样式</li>
                <li>代码生成支持文件上传组件</li>
                <li>代码生成日期控件区分范围</li>
                <li>代码生成数据库文本类型生成表单文本域</li>
                <li>用户手机邮箱&菜单组件修改允许空字符串</li>
                <li>升级SpringBoot到最新版本2.2.13 æå‡å¯åŠ¨é€Ÿåº¦</li>
                <li>升级druid到最新版本v1.2.4</li>
                <li>升级fastjson到最新版1.2.75</li>
                <li>升级element-ui到最新版本2.15.0</li>
                <li>修复IE11浏览器报错问题</li>
                <li>优化多级菜单之间切换无法缓存的问题</li>
                <li>修复四级菜单无法显示问题</li>
                <li>修正侧边栏静态路由丢失问题</li>
                <li>修复角色管理-编辑角色-功能权限显示异常</li>
                <li>配置文件新增redis数据库索引属性</li>
                <li>权限工具类增加admin判断</li>
                <li>角色非自定义权限范围清空选择值</li>
                <li>修复导入数据为负浮点数时丢失精度问题</li>
                <li>移除path-to-regexp正则匹配插件</li>
                <li>修复生成树表代码异常</li>
                <li>修改ip字段长度防止ipv6地址长度不够</li>
                <li>防止get请求参数值为false或0等特殊值会导致无法正确的传参</li>
                <li>登录后push添加catch防止出现检查错误</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.3.0 - 2020-12-14">
              <ol>
                <li>新增缓存监控功能</li>
                <li>支持主题风格配置</li>
                <li>修复多级菜单之间切换无法缓存的问题</li>
                <li>多级菜单自动配置组件</li>
                <li>代码生成预览支持高亮显示</li>
                <li>支持Get请求映射Params参数</li>
                <li>删除用户和角色解绑关联</li>
                <li>去除用户手机邮箱部门必填验证</li>
                <li>Excel支持注解align对齐方式</li>
                <li>Excel支持导入Boolean型数据</li>
                <li>优化头像样式,鼠标移入悬停遮罩</li>
                <li>代码生成预览提供滚动机制</li>
                <li>代码生成删除多余的数字float类型</li>
                <li>修正转换字符串的目标字符集属性</li>
                <li>回显数据字典防止空值报错</li>
                <li>日志记录增加过滤多文件场景</li>
                <li>修改缓存Set方法可能导致嵌套的问题</li>
                <li>移除前端一些多余的依赖</li>
                <li>防止安全扫描YUI出现的风险提示</li>
                <li>修改node-sass为dart-sass</li>
                <li>升级SpringBoot到最新版本2.1.18</li>
                <li>升级poi到最新版本4.1.2</li>
                <li>升级oshi到最新版本v5.3.6</li>
                <li>升级bitwalker到最新版本1.21</li>
                <li>升级axios到最新版本0.21.0</li>
                <li>升级element-ui到最新版本2.14.1</li>
                <li>升级vue到最新版本2.6.12</li>
                <li>升级vuex到最新版本3.6.0</li>
                <li>升级vue-cli到版本4.5.9</li>
                <li>升级vue-router到最新版本3.4.9</li>
                <li>升级vue-cli到最新版本4.4.6</li>
                <li>升级vue-cropper到最新版本0.5.5</li>
                <li>升级clipboard到最新版本2.0.6</li>
                <li>升级core-js到最新版本3.8.1</li>
                <li>升级echarts到最新版本4.9.0</li>
                <li>升级file-saver到最新版本2.0.4</li>
                <li>升级fuse.js到最新版本6.4.3</li>
                <li>升级js-beautify到最新版本1.13.0</li>
                <li>升级js-cookie到最新版本2.2.1</li>
                <li>升级path-to-regexp到最新版本6.2.0</li>
                <li>升级quill到最新版本1.3.7</li>
                <li>升级screenfull到最新版本5.0.2</li>
                <li>升级sortablejs到最新版本1.10.2</li>
                <li>升级vuedraggable到最新版本2.24.3</li>
                <li>升级chalk到最新版本4.1.0</li>
                <li>升级eslint到最新版本7.15.0</li>
                <li>升级eslint-plugin-vue到最新版本7.2.0</li>
                <li>升级lint-staged到最新版本10.5.3</li>
                <li>升级runjs到最新版本4.4.2</li>
                <li>升级sass-loader到最新版本10.1.0</li>
                <li>升级script-ext-html-webpack-plugin到最新版本2.1.5</li>
                <li>升级svg-sprite-loader到最新版本5.1.1</li>
                <li>升级vue-template-compiler到最新版本2.6.12</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.2.1 - 2020-11-18">
              <ol>
                <li>阻止任意文件下载漏洞</li>
                <li>代码生成支持上传控件</li>
                <li>新增图片上传组件</li>
                <li>调整默认首页</li>
                <li>升级druid到最新版本v1.2.2</li>
                <li>mapperLocations配置支持分隔符</li>
                <li>权限信息调整</li>
                <li>调整sql默认时间</li>
                <li>解决代码生成没有bit类型的问题</li>
                <li>升级pagehelper到最新版1.3.0</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.2.0 - 2020-10-10">
              <ol>
                <li>升级springboot版本到2.1.17 æå‡å®‰å…¨æ€§</li>
                <li>升级oshi到最新版本v5.2.5</li>
                <li>升级druid到最新版本v1.2.1</li>
                <li>升级jjwt到版本0.9.1</li>
                <li>升级fastjson到最新版1.2.74</li>
                <li>修改sass为node-sass,避免el-icon图标乱码</li>
                <li>代码生成支持同步数据库</li>
                <li>代码生成支持富文本控件</li>
                <li>代码生成页面时不忽略remark属性</li>
                <li>代码生成添加select必填选项</li>
                <li>Excel导出类型NUMERIC支持精度浮点类型</li>
                <li>Excel导出targetAttr优化获取值,防止get方法不规范</li>
                <li>Excel注解支持自动统计数据总和</li>
                <li>Excel注解支持设置BigDecimal精度&舍入规则</li>
                <li>菜单&数据权限新增(展开/折叠 å…¨é€‰/全不选 çˆ¶å­è”动)</li>
                <li>允许用户分配到部门父节点</li>
                <li>菜单新增是否缓存keep-alive</li>
                <li>表格操作列间距调整</li>
                <li>限制系统内置参数不允许删除</li>
                <li>富文本组件优化,支持自定义高度&图片冲突问题</li>
                <li>富文本工具栏样式对齐</li>
                <li>导入excel整形值校验优化</li>
                <li>修复页签关闭所有时固定标签路由不刷新问题</li>
                <li>表单构建布局型组件新增按钮</li>
                <li>左侧菜单文字过长显示省略号</li>
                <li>修正根节点为子部门时,树状结构显示问题</li>
                <li>修正调用目标字符串最大长度</li>
                <li>修正菜单提示信息错误</li>
                <li>修正定时任务执行一次权限标识</li>
                <li>修正数据库字符串类型nvarchar</li>
                <li>优化递归子节点</li>
                <li>优化数据权限判断</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.1.0 - 2020-08-13">
              <ol>
                <li>表格工具栏右侧添加刷新&显隐查询组件</li>
                <li>后端支持CORS跨域请求</li>
                <li>代码生成支持选择上级菜单</li>
                <li>代码生成支持自定义路径</li>
                <li>代码生成支持复选框</li>
                <li>Excel导出导入支持dictType字典类型</li>
                <li>Excel支持分割字符串组内容</li>
                <li>验证码类型支持(数组计算、字符验证)</li>
                <li>升级vue-cli版本到4.4.4</li>
                <li>修改 node-sass ä¸º dart-sass</li>
                <li>表单类型为Integer/Long设置整形默认值</li>
                <li>代码生成器默认mapper路径与默认mapperScan路径不一致</li>
                <li>优化防重复提交拦截器</li>
                <li>优化上级菜单不能选择自己</li>
                <li>修复角色的权限分配后,未实时生效问题</li>
                <li>修复在线用户日志记录类型</li>
                <li>修复富文本空格和缩进保存后不生效问题</li>
                <li>修复在线用户判断逻辑</li>
                <li>唯一限制条件只返回单条数据</li>
                <li>添加获取当前的环境配置方法</li>
                <li>超时登录后页面跳转到首页</li>
                <li>全局异常状态汉化拦截处理</li>
                <li>HTML过滤器改为将html转义</li>
                <li>检查字符支持小数点&降级改成异常提醒</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.0.0 - 2020-07-20">
              <ol>
                <li>单应用调整为多模块项目</li>
                <li>升级element-ui版本到2.13.2</li>
                <li>删除babel,提高编译速度。</li>
                <li>新增菜单默认主类目</li>
                <li>编码文件名修改为uuid方式</li>
                <li>定时任务cron表达式验证</li>
                <li>角色权限修改时已有权限未自动勾选异常修复</li>
                <li>防止切换权限用户后登录出现404</li>
                <li>Excel支持sort导出排序</li>
                <li>创建用户不允许选择超级管理员角色</li>
                <li>修复代码生成导入表结构出现异常页面不提醒问题</li>
                <li>修复代码生成点击多次表修改数据不变化的问题</li>
                <li>修复头像上传成功二次打开无法改变裁剪框大小和位置问题</li>
                <li>修复布局为small者mini用户表单显示错位问题</li>
                <li>修复热部署导致的强换异常问题</li>
                <li>修改用户管理复选框宽度,防止部分浏览器出现省略号</li>
                <li>IpUtils工具,清除Xss特殊字符,防止Xff注入攻击</li>
                <li>生成domain å¦‚果是浮点型 ç»Ÿä¸€ç”¨BigDecimal</li>
                <li>定时任务调整label-width,防止部署出现错位</li>
                <li>调整表头固定列默认样式</li>
                <li>代码生成模板调整,字段为String并且必填则加空串条件</li>
                <li>代码生成字典Integer/Long使用parseInt</li>
                <li>
                  ä¿®å¤dict_sort不可update为0的问题&查询返回增加dict_sort升序排序
                </li>
                <li>修正岗位导出权限注解</li>
                <li>禁止加密密文返回前端</li>
                <li>修复代码生成页面中的查询条件创建时间未生效的问题</li>
                <li>修复首页搜索菜单外链无法点击跳转问题</li>
                <li>修复菜单管理选择图标,backspace删除时不过滤数据</li>
                <li>用户管理部门分支节点不可检查&显示计数</li>
                <li>数据范围过滤属性调整</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.3.0 - 2020-06-01">
              <ol>
                <li>升级fastjson到最新版1.2.70 ä¿®å¤é«˜å±å®‰å…¨æ¼æ´ž</li>
                <li>dev启动默认打开浏览器</li>
                <li>vue-cli使用默认source-map</li>
                <li>slidebar eslint报错优化</li>
                <li>当tags-view滚动关闭右键菜单</li>
                <li>字典管理添加缓存读取</li>
                <li>参数管理支持缓存操作</li>
                <li>支持一级菜单(和主页同级)在main区域显示</li>
                <li>限制外链地址必须以http(s)开头</li>
                <li>tagview & sidebar ä¸»é¢˜é¢œè‰²ä¸Želement ui(全局)同步</li>
                <li>修改数据源类型优先级,先根据方法,再根据类</li>
                <li>支持是否需要设置token属性,自定义返回码消息。</li>
                <li>swagger请求前缀加入配置。</li>
                <li>登录地点设置内容过长则隐藏显示</li>
                <li>修复定时任务执行一次按钮后不提示消息问题</li>
                <li>修改上级部门(选择项排除本身和下级)</li>
                <li>通用http发送方法增加参数 contentType ç¼–码类型</li>
                <li>更换IP地址查询接口</li>
                <li>修复页签变量undefined</li>
                <li>添加校验部门包含未停用的子部门</li>
                <li>修改定时任务详情下次执行时间日期显示错误</li>
                <li>角色管理查询设置默认排序字段</li>
                <li>swagger添加enable参数控制是否启用</li>
                <li>只对json类型请求构建可重复读取inputStream的request</li>
                <li>修改代码生成字典字段int类型没有自动选中问题</li>
                <li>vuex用户名取值修正</li>
                <li>表格树模板去掉多余的)</li>
                <li>代码生成序号修正</li>
                <li>全屏情况下不调整上外边距</li>
                <li>代码生成Date字段添加默认格式</li>
                <li>用户管理角色选择权限控制</li>
                <li>修复路由懒加载报错问题</li>
                <li>模板sql.vm添加菜单状态</li>
                <li>设置用户名称不能修改</li>
                <li>dialog添加append-to-body属性,防止ie遮罩</li>
                <li>菜单区分状态和显示隐藏功能</li>
                <li>升级fastjson到最新版1.2.68 ä¿®å¤å®‰å…¨åŠ å›º</li>
                <li>修复代码生成如果选择字典类型缺失逗号问题</li>
                <li>登录请求params更换为data,防止暴露url</li>
                <li>日志返回时间格式处理</li>
                <li>添加handle控制允许拖动的元素</li>
                <li>布局设置点击扩大范围</li>
                <li>代码生成列属性排序查询</li>
                <li>代码生成列支持拖动排序</li>
                <li>修复时间格式不支持ios问题</li>
                <li>表单构建添加父级class,防止冲突</li>
                <li>定时任务并发属性修正</li>
                <li>角色禁用&菜单隐藏不查询权限</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.2.0 - 2020-03-18">
              <ol>
                <li>系统监控新增定时任务功能</li>
                <li>添加一个打包Web工程bat</li>
                <li>修复页签鼠标滚轮按下的时候,可以关闭不可关闭的tag</li>
                <li>修复点击退出登录有时会无提示问题</li>
                <li>修复防重复提交注解无效问题</li>
                <li>修复通知公告批量删除异常问题</li>
                <li>添加菜单时路由地址必填限制</li>
                <li>代码生成字段描述可编辑</li>
                <li>修复用户修改个人信息导致缓存不过期问题</li>
                <li>个人信息创建时间获取正确属性值</li>
                <li>操作日志详细显示正确类型</li>
                <li>导入表单击行数据时选中对应的复选框</li>
                <li>批量替换表前缀逻辑调整</li>
                <li>固定重定向路径表达式</li>
                <li>升级element-ui版本到2.13.0</li>
                <li>操作日志排序调整</li>
                <li>修复charts切换侧边栏或者缩放窗口显示bug</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.1.0 - 2020-02-24">
              <ol>
                <li>新增表单构建</li>
                <li>代码生成支持树表结构</li>
                <li>新增用户导入</li>
                <li>修复动态加载路由页面刷新问题</li>
                <li>修复地址开关无效问题</li>
                <li>汉化错误提示页面</li>
                <li>代码生成已知问题修改</li>
                <li>修复多数据源下配置关闭出现异常处理</li>
                <li>添加HTML过滤器,用于去除XSS漏洞隐患</li>
                <li>修复上传头像控制台出现异常</li>
                <li>修改用户管理分页不正确的问题</li>
                <li>修复验证码记录提示错误</li>
                <li>修复request.js缺少Message引用</li>
                <li>修复表格时间为空出现的异常</li>
                <li>添加Jackson日期反序列化时区配置</li>
                <li>调整根据用户权限加载菜单数据树形结构</li>
                <li>调整成功登录不恢复按钮,防止多次点击</li>
                <li>修改用户个人资料同步缓存信息</li>
                <li>修复页面同时出现el-upload和Editor不显示处理</li>
                <li>修复在角色管理页修改菜单权限偶尔未选中问题</li>
                <li>配置文件新增redis密码属性</li>
                <li>设置mybatis全局的配置文件</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.0.0 - 2019-12-02">
              <ol>
                <li>新增代码生成</li>
                <li>新增@RepeatSubmit注解,防止重复提交</li>
                <li>新增菜单主目录添加/删除操作</li>
                <li>日志记录过滤特殊对象,防止转换异常</li>
                <li>修改代码生成路由脚本错误</li>
                <li>用户上传头像实时同步缓存,无需重新登录</li>
                <li>调整切换页签后不重新加载数据</li>
                <li>添加jsencrypt实现参数的前端加密</li>
                <li>系统退出删除用户缓存记录</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v1.1.0 - 2019-11-11">
              <ol>
                <li>新增在线用户管理</li>
                <li>新增按钮组功能实现(批量删除、导出、清空)</li>
                <li>新增查询条件重置按钮</li>
                <li>新增Swagger全局Token配置</li>
                <li>新增后端参数校验</li>
                <li>修复字典管理页面的日期查询异常</li>
                <li>修改时间函数命名防止冲突</li>
                <li>去除菜单上级校验,默认为顶级</li>
                <li>修复用户密码无法修改问题</li>
                <li>修复菜单类型为按钮时不显示权限标识</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v1.0.0 - 2019-10-08">
              <ol>
                <li>若依前后端分离系统正式发布</li>
              </ol>
            </el-collapse-item>
          </el-collapse>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
        <el-card class="update-log">
          <template v-slot:header>
            <div class="clearfix">
              <span>捐赠支持</span>
            </div>
          </template>
          <div class="body">
            <img
              src="https://oscimg.oschina.net/oscnet/up-d6695f82666e5018f715c41cb7ee60d3b73.png"
              alt="donate"
              style="width:100%"
            />
            <span style="display: inline-block; height: 30px; line-height: 30px"
              >你可以请作者喝杯咖啡表示鼓励</span
            >
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup name="Index">
const version = ref('3.7.0')
function goTarget(url) {
  window.open(url, '__blank')
}
</script>
<style scoped lang="scss">
.home {
  blockquote {
    padding: 10px 20px;
    margin: 0 0 20px;
    font-size: 17.5px;
    border-left: 5px solid #eee;
  }
  hr {
    margin-top: 20px;
    margin-bottom: 20px;
    border: 0;
    border-top: 1px solid #eee;
  }
  .col-item {
    margin-bottom: 20px;
  }
  ul {
    padding: 0;
    margin: 0;
  }
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: #676a6c;
  overflow-x: hidden;
  ul {
    list-style-type: none;
  }
  h4 {
    margin-top: 0px;
  }
  h2 {
    margin-top: 10px;
    font-size: 26px;
    font-weight: 100;
  }
  p {
    margin-top: 10px;
    b {
      font-weight: 700;
    }
  }
  .update-log {
    ol {
      display: block;
      list-style-type: decimal;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0;
      margin-inline-end: 0;
      padding-inline-start: 40px;
    }
  }
}
</style>
src/views/login.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,210 @@
<template>
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">若依后台管理系统</h3>
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          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"
          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="captchaOnOff">
        <el-input
          v-model="loginForm.code"
          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="medium"
          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-2021 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>
<script setup>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from "@/utils/jsencrypt";
const store = useStore();
const router = useRouter();
const { proxy } = getCurrentInstance();
const loginForm = ref({
  username: "admin",
  password: "admin123",
  rememberMe: false,
  code: "",
  uuid: ""
});
const loginRules = {
  username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
  password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
  code: [{ required: true, trigger: "change", message: "请输入验证码" }]
};
const codeUrl = ref("");
const loading = ref(false);
// éªŒè¯ç å¼€å…³
const captchaOnOff = ref(true);
// æ³¨å†Œå¼€å…³
const register = ref(false);
const redirect = ref(undefined);
function handleLogin() {
  proxy.$refs.loginRef.validate(valid => {
    if (valid) {
      loading.value = true;
      // å‹¾é€‰äº†éœ€è¦è®°ä½å¯†ç è®¾ç½®åœ¨cookie中设置记住用户明和名命
      if (loginForm.value.rememberMe) {
        Cookies.set("username", loginForm.value.username, { expires: 30 });
        Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 });
        Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
      } else {
        // å¦åˆ™ç§»é™¤
        Cookies.remove("username");
        Cookies.remove("password");
        Cookies.remove("rememberMe");
      }
      // è°ƒç”¨action的登录方法
      store.dispatch("Login", loginForm.value).then(() => {
        router.push({ path: redirect.value || "/" });
      }).catch(() => {
        loading.value = false;
        // é‡æ–°èŽ·å–éªŒè¯ç 
        if (captchaOnOff.value) {
          getCode();
        }
      });
    }
  });
}
function getCode() {
  getCodeImg().then(res => {
    captchaOnOff.value = res.captchaOnOff === undefined ? true : res.captchaOnOff;
    if (captchaOnOff.value) {
      codeUrl.value = "data:image/gif;base64," + res.img;
      loginForm.value.uuid = res.uuid;
    }
  });
}
function getCookie() {
  const username = Cookies.get("username");
  const password = Cookies.get("password");
  const rememberMe = Cookies.get("rememberMe");
  loginForm.value = {
    username: username === undefined ? loginForm.value.username : username,
    password: password === undefined ? loginForm.value.password : decrypt(password),
    rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
  };
}
getCode();
getCookie();
</script>
<style lang='scss' scoped>
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
}
.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}
.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}
.login-code {
  width: 33%;
  height: 38px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.login-code-img {
  height: 38px;
}
</style>
src/views/monitor/cache/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,131 @@
<template>
  <div class="app-container">
    <el-row>
      <el-col :span="24" class="card-box">
        <el-card>
          <template #header><span>基本信息</span></template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table cellspacing="0" 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><span>命令统计</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>
            <span>内存信息</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>
<script setup>
import { getCache } from '@/api/monitor/cache';
import * as echarts from 'echarts';
const cache = ref([]);
const commandstats = ref(null);
const usedmemory = ref(null);
const { proxy } = getCurrentInstance();
function getList() {
  proxy.$modal.loading("正在加载缓存监控数据,请稍候!");
  getCache().then(response => {
    proxy.$modal.closeLoading();
    cache.value = response.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: response.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: "内存消耗"
            }
          ]
        }
      ],
    })
  })
}
getList();
</script>
src/views/monitor/druid/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<template>
   <div>
      <i-frame v-model:src="url"></i-frame>
   </div>
</template>
<script setup>
import iFrame from '@/components/iFrame'
import { ref } from 'vue';
const url = ref(import.meta.env.VITE_APP_BASE_API + 'druid/login.html');
</script>
src/views/monitor/job/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,498 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="任务名称" prop="jobName">
            <el-input
               v-model="queryParams.jobName"
               placeholder="请输入任务名称"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="任务组名" prop="jobGroup">
            <el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable>
               <el-option
                  v-for="dict in sys_job_group"
                  :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>
               <el-option
                  v-for="dict in sys_job_status"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
               />
            </el-select>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['monitor:job:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :disabled="single"
               @click="handleUpdate"
               v-hasPermi="['monitor:job:edit']"
            >修改</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['monitor:job:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['monitor:job:export']"
            >导出</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="info"
               plain
               icon="Operation"
               size="mini"
               @click="handleJobLog"
               v-hasPermi="['monitor:job:query']"
            >日志</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="任务编号" width="100" align="center" prop="jobId" />
         <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
         <el-table-column label="任务组名" align="center" prop="jobGroup">
            <template #default="scope">
               <dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
            </template>
         </el-table-column>
         <el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
         <el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
         <el-table-column label="状态" align="center">
            <template #default="scope">
               <el-switch
                  v-model="scope.row.status"
                  active-value="0"
                  inactive-value="1"
                  @change="handleStatusChange(scope.row)"
               ></el-switch>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['monitor:job:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['monitor:job:remove']"
               >删除</el-button>
               <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)">
                  <span class="el-dropdown-link" v-hasPermi="['monitor:job:changeStatus', 'monitor:job:query']">
                     <i class="el-icon-d-arrow-right el-icon--right"></i>更多
                  </span>
                  <template #dropdown>
                     <el-dropdown-menu>
                        <el-dropdown-item command="handleRun" icon="CaretRight"
                           v-hasPermi="['monitor:job:changeStatus']">执行一次</el-dropdown-item>
                        <el-dropdown-item command="handleView" icon="View"
                           v-hasPermi="['monitor:job:query']">任务详细</el-dropdown-item>
                        <el-dropdown-item command="handleJobLog" icon="Operation"
                           v-hasPermi="['monitor:job:query']">调度日志</el-dropdown-item>
                     </el-dropdown-menu>
                  </template>
               </el-dropdown>
            </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-dialog :title="title" v-model="open" width="800px" append-to-body>
         <el-form ref="jobRef" :model="form" :rules="rules" label-width="120px">
            <el-row>
               <el-col :span="12">
                  <el-form-item label="任务名称" prop="jobName">
                     <el-input v-model="form.jobName" placeholder="请输入任务名称" />
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="任务分组" prop="jobGroup">
                     <el-select v-model="form.jobGroup" placeholder="请选择">
                        <el-option
                           v-for="dict in sys_job_group"
                           :key="dict.value"
                           :label="dict.label"
                           :value="dict.value"
                        ></el-option>
                     </el-select>
                  </el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item prop="invokeTarget">
                     <template #label>
                        <span>
                           è°ƒç”¨æ–¹æ³•
                           <el-tooltip placement="top">
                              <template #content>
                                 <div>
                                    Bean调用示例:ryTask.ryParams('ry')
                                    <br />Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry')
                                    <br />参数说明:支持字符串,布尔类型,长整型,浮点型,整型
                                 </div>
                              </template>
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                        </span>
                     </template>
                     <el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
                  </el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="cron表达式" prop="cronExpression">
                     <el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式">
                        <template #append>
                           <el-button type="primary" @click="handleShowCron">
                              ç”Ÿæˆè¡¨è¾¾å¼
                              <i class="el-icon-time el-icon--right"></i>
                           </el-button>
                        </template>
                     </el-input>
                  </el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="错误策略" prop="misfirePolicy">
                     <el-radio-group v-model="form.misfirePolicy" size="small">
                        <el-radio-button label="1">立即执行</el-radio-button>
                        <el-radio-button label="2">执行一次</el-radio-button>
                        <el-radio-button label="3">放弃执行</el-radio-button>
                     </el-radio-group>
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="是否并发" prop="concurrent">
                     <el-radio-group v-model="form.concurrent" size="small">
                        <el-radio-button label="0">允许</el-radio-button>
                        <el-radio-button label="1">禁止</el-radio-button>
                     </el-radio-group>
                  </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_job_status"
                           :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>
      <!-- ä»»åŠ¡æ—¥å¿—è¯¦ç»† -->
      <el-dialog title="任务详细" v-model="openView" width="700px" append-to-body>
         <el-form :model="form" label-width="120px" size="mini">
            <el-row>
               <el-col :span="12">
                  <el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
                  <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
                  <el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="下次执行时间:">{{ parseTime(form.nextValidTime) }}</el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
               </el-col>
               <el-col :span="12">
                  <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="12">
                  <el-form-item label="是否并发:">
                     <div v-if="form.concurrent == 0">允许</div>
                     <div v-else-if="form.concurrent == 1">禁止</div>
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="执行策略:">
                     <div v-if="form.misfirePolicy == 0">默认策略</div>
                     <div v-else-if="form.misfirePolicy == 1">立即执行</div>
                     <div v-else-if="form.misfirePolicy == 2">执行一次</div>
                     <div v-else-if="form.misfirePolicy == 3">放弃执行</div>
                  </el-form-item>
               </el-col>
            </el-row>
         </el-form>
         <template #footer>
            <div class="dialog-footer">
               <el-button @click="openView = false">关 é—­</el-button>
            </div>
         </template>
      </el-dialog>
   </div>
</template>
<script setup name="Job">
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job";
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status");
const jobList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const openView = ref(false);
const openCron = ref(false);
const expression = ref("");
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    jobName: undefined,
    jobGroup: undefined,
    status: undefined
  },
  rules: {
    jobName: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
    invokeTarget: [{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }],
    cronExpression: [{ required: true, message: "cron执行表达式不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å®šæ—¶ä»»åŠ¡åˆ—è¡¨ */
function getList() {
  loading.value = true;
  listJob(queryParams.value).then(response => {
    jobList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** ä»»åŠ¡ç»„åå­—å…¸ç¿»è¯‘ */
function jobGroupFormat(row, column) {
  return proxy.selectDictLabel(sys_job_group, row.jobGroup);
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    jobId: undefined,
    jobName: undefined,
    jobGroup: undefined,
    invokeTarget: undefined,
    cronExpression: undefined,
    misfirePolicy: 1,
    concurrent: 1,
    status: "0"
  };
  proxy.resetForm("jobRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.jobId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
// æ›´å¤šæ“ä½œè§¦å‘
function handleCommand(command, row) {
  switch (command) {
    case "handleRun":
      handleRun(row);
      break;
    case "handleView":
      handleView(row);
      break;
    case "handleJobLog":
      handleJobLog(row);
      break;
    default:
      break;
  }
}
// ä»»åŠ¡çŠ¶æ€ä¿®æ”¹
function handleStatusChange(row) {
  let text = row.status === "0" ? "启用" : "停用";
  proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
    return changeJobStatus(row.jobId, row.status);
  }).then(() => {
    proxy.$modal.msgSuccess(text + "成功");
  }).catch(function () {
    row.status = row.status === "0" ? "1" : "0";
  });
}
/* ç«‹å³æ‰§è¡Œä¸€æ¬¡ */
function handleRun(row) {
  proxy.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () {
    return runJob(row.jobId, row.jobGroup);
  }).then(() => {
    proxy.$modal.msgSuccess("执行成功");})
  .catch(() => {});
}
/** ä»»åŠ¡è¯¦ç»†ä¿¡æ¯ */
function handleView(row) {
  getJob(row.jobId).then(response => {
    form.value = response.data;
    openView.value = true;
  });
}
/** cron表达式按钮操作 */
function handleShowCron() {
  expression.value = form.value.cronExpression;
  openCron.value = true;
}
/** ç¡®å®šåŽå›žä¼ å€¼ */
function crontabFill(value) {
  form.value.cronExpression = value;
}
/** ä»»åŠ¡æ—¥å¿—åˆ—è¡¨æŸ¥è¯¢ */
function handleJobLog(row) {
  const jobId = row.jobId || 0;
  router.push({ path: "/monitor/job-log/index", query: { jobId: jobId } });
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  open.value = true;
  title.value = "添加任务";
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset();
  const jobId = row.jobId || ids.value;
  getJob(jobId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改任务";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["jobRef"].validate(valid => {
    if (valid) {
      if (form.value.jobId != undefined) {
        updateJob(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addJob(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const jobIds = row.jobId || ids.value;
  proxy.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () {
    return delJob(jobIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("monitor/job/export", {
    ...queryParams.value,
  }, `job_${new Date().getTime()}.xlsx`);
}
getList();
</script>
src/views/monitor/job/log.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,292 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="任务名称" prop="jobName">
            <el-input
               v-model="queryParams.jobName"
               placeholder="请输入任务名称"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="任务组名" prop="jobGroup">
            <el-select
               v-model="queryParams.jobGroup"
               placeholder="请任务组名"
               clearable
               size="small"
               style="width: 240px"
            >
               <el-option
                  v-for="dict in sys_job_group"
                  :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
               size="small"
               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="执行时间">
            <el-date-picker
               v-model="dateRange"
               size="small"
               style="width: 240px"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['monitor:job:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               @click="handleClean"
               v-hasPermi="['monitor:job:remove']"
            >清空</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['monitor:job:export']"
            >导出</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Close"
               size="mini"
               @click="handleClose"
            >关闭</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
         <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
         <el-table-column label="任务组名" align="center" prop="jobGroup" :show-overflow-tooltip="true">
            <template #default="scope">
               <dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
            </template>
         </el-table-column>
         <el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
         <el-table-column label="日志信息" align="center" prop="jobMessage" :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="createTime" width="180">
            <template #default="scope">
               <span>{{ parseTime(scope.row.createTime) }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="View"
                  @click="handleView(scope.row)"
                  v-hasPermi="['monitor:job:query']"
               >详细</el-button>
            </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-dialog title="调度日志详细" v-model="open" width="700px" append-to-body>
         <el-form :model="form" label-width="100px" size="mini">
            <el-row>
               <el-col :span="12">
                  <el-form-item label="日志序号:">{{ form.jobLogId }}</el-form-item>
                  <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="任务分组:">{{ form.jobGroup }}</el-form-item>
                  <el-form-item label="执行时间:">{{ form.createTime }}</el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="调用方法:">{{ form.invokeTarget }}</el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item>
               </el-col>
               <el-col :span="24">
                  <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="24">
                  <el-form-item label="异常信息:" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item>
               </el-col>
            </el-row>
         </el-form>
         <template #footer>
            <div class="dialog-footer">
               <el-button @click="open = false">关 é—­</el-button>
            </div>
         </template>
      </el-dialog>
   </div>
</template>
<script setup name="JobLog">
import { getJob } from "@/api/monitor/job";
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog";
const { proxy } = getCurrentInstance();
const { sys_common_status, sys_job_group } = proxy.useDict("sys_common_status", "sys_job_group");
const jobLogList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref([]);
const route = useRoute();
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    dictName: undefined,
    dictType: undefined,
    status: undefined
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢è°ƒåº¦æ—¥å¿—列表 */
function getList() {
  loading.value = true;
  listJobLog(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    jobLogList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
// è¿”回按钮
function handleClose() {
  const obj = { path: "/monitor/job" };
  proxy.$tab.closeOpenPage(obj);
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  handleQuery();
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.jobLogId);
  multiple.value = !selection.length;
}
/** è¯¦ç»†æŒ‰é’®æ“ä½œ */
function handleView(row) {
  open.value = true;
  form.value = row;
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  proxy.$modal.confirm('是否确认删除调度日志编号为"' + ids.value + '"的数据项?').then(function () {
    return delJobLog(ids.value);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** æ¸…空按钮操作 */
function handleClean() {
  proxy.$modal.confirm("是否确认清空所有调度日志数据项?").then(function () {
    return cleanJobLog();
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("清空成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("monitor/jobLog/export", {
    ...queryParams.value,
  }, `job_log_${new Date().getTime()}.xlsx`);
}
(() => {
  const jobId = route.query.jobId;
  if (jobId !== undefined && jobId != 0) {
    getJob(jobId).then(response => {
      queryParams.value.jobName = response.data.jobName;
      queryParams.value.jobGroup = response.data.jobGroup;
      getList();
    });
  } else {
    getList();
  }
})();
getList();
</script>
src/views/monitor/logininfor/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,209 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="登录地址" prop="ipaddr">
            <el-input
               v-model="queryParams.ipaddr"
               placeholder="请输入登录地址"
               clearable
               size="small"
               style="width: 240px;"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="用户名称" prop="userName">
            <el-input
               v-model="queryParams.userName"
               placeholder="请输入用户名称"
               clearable
               size="small"
               style="width: 240px;"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select
               v-model="queryParams.status"
               placeholder="登录状态"
               clearable
               size="small"
               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="登录时间">
            <el-date-picker
               v-model="dateRange"
               size="small"
               style="width: 240px"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['system:logininfor:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               @click="handleClean"
               v-hasPermi="['system:logininfor:remove']"
            >清空</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:logininfor:export']"
            >导出</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table ref="logininforRef" v-loading="loading" :data="logininforList" @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" />
         <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"
      />
   </div>
</template>
<script setup name="Logininfor">
import { list, delLogininfor, cleanLogininfor } from "@/api/monitor/logininfor";
const { proxy } = getCurrentInstance();
const { sys_common_status } = proxy.useDict("sys_common_status");
const logininforList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref([]);
const defaultSort = ref({ prop: "loginTime", order: "descending" });
// æŸ¥è¯¢å‚æ•°
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  ipaddr: undefined,
  userName: undefined,
  status: undefined,
  orderByColumn: undefined,
  isAsc: undefined
});
/** æŸ¥è¯¢ç™»å½•日志列表 */
function getList() {
  loading.value = true;
  list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    logininforList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  proxy.$refs["logininforRef"].sort(defaultSort.value.prop, defaultSort.value.order);
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.infoId);
  multiple.value = !selection.length;
}
/** æŽ’序触发事件 */
function handleSortChange(column, prop, order) {
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const infoIds = row.infoId || ids.value;
  proxy.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?').then(function () {
    return delLogininfor(infoIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** æ¸…空按钮操作 */
function handleClean() {
  proxy.$modal.confirm("是否确认清空所有登录日志数据项?").then(function () {
    return cleanLogininfor();
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("清空成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("monitor/logininfor/export", {
    ...queryParams.value,
  }, `config_${new Date().getTime()}.xlsx`);
}
getList();
</script>
src/views/monitor/online/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
         <el-form-item label="登录地址" prop="ipaddr">
            <el-input
               v-model="queryParams.ipaddr"
               placeholder="请输入登录地址"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="用户名称" prop="userName">
            <el-input
               v-model="queryParams.userName"
               placeholder="请输入用户名称"
               clearable
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-table
         v-loading="loading"
         :data="onlineList.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
         style="width: 100%;"
      >
         <el-table-column label="序号" type="index" align="center">
            <template #default="scope">
               <span>{{ (pageNum - 1) * 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-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleForceLogout(scope.row)"
                  v-hasPermi="['monitor:online:forceLogout']"
               >强退</el-button>
            </template>
         </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
   </div>
</template>
<script setup name="Online">
import { forceLogout, list as initData } from "@/api/monitor/online";
const { proxy } = getCurrentInstance();
const onlineList = ref([]);
const loading = ref(true);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
const queryParams = ref({
  ipaddr: undefined,
  userName: undefined
});
/** æŸ¥è¯¢ç™»å½•日志列表 */
function getList() {
  loading.value = true;
  initData(queryParams.value).then(response => {
    onlineList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  pageNum.value = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
/** å¼ºé€€æŒ‰é’®æ“ä½œ */
function handleForceLogout(row) {
  const operIds = row.operId || ids.value;
    proxy.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?').then(function () {
  return forceLogout(row.tokenId);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
getList();
</script>
src/views/monitor/operlog/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,298 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="系统模块" prop="title">
            <el-input
               v-model="queryParams.title"
               placeholder="请输入系统模块"
               clearable
               size="small"
               style="width: 240px;"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="操作人员" prop="operName">
            <el-input
               v-model="queryParams.operName"
               placeholder="请输入操作人员"
               clearable
               size="small"
               style="width: 240px;"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="类型" prop="businessType">
            <el-select
               v-model="queryParams.businessType"
               placeholder="操作类型"
               clearable
               size="small"
               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
               size="small"
               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="操作时间">
            <el-date-picker
               v-model="dateRange"
               size="small"
               style="width: 240px"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['system:operlog:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               @click="handleClean"
               v-hasPermi="['system:operlog:remove']"
            >清空</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:operlog:export']"
            >导出</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table ref="operlogRef" v-loading="loading" :data="operlogList" @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="operId" />
         <el-table-column label="系统模块" align="center" prop="title" />
         <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" prop="requestMethod" />
         <el-table-column label="操作人员" align="center" prop="operName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" width="100" />
         <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" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
            <template #default="scope">
               <span>{{ parseTime(scope.row.operTime) }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="View"
                  @click="handleView(scope.row, scope.index)"
                  v-hasPermi="['system:operlog:query']"
               >详细</el-button>
            </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-dialog title="操作日志详细" v-model="open" width="700px" append-to-body>
         <el-form :model="form" label-width="100px" size="mini">
            <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="12">
                  <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="12">
                  <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="open = false">关 é—­</el-button>
            </div>
         </template>
      </el-dialog>
   </div>
</template>
<script setup name="Operlog">
import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog";
const { proxy } = getCurrentInstance();
const { sys_oper_type, sys_common_status } = proxy.useDict("sys_oper_type","sys_common_status");
const operlogList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const defaultSort = ref({ prop: "operTime", order: "descending" });
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    title: undefined,
    operName: undefined,
    businessType: undefined,
    status: undefined
  }
});
const { queryParams, form } = toRefs(data);
/** æŸ¥è¯¢ç™»å½•日志 */
function getList() {
  loading.value = true;
  list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    operlogList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** æ“ä½œæ—¥å¿—类型字典翻译 */
function typeFormat(row, column) {
  return selectDictLabel(sys_oper_type, row.businessType);
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  proxy.$refs["operlogRef"].sort(defaultSort.value.prop, defaultSort.value.order);
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.operId);
  multiple.value = !selection.length;
}
/** æŽ’序触发事件 */
function handleSortChange(column, prop, order) {
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
}
/** è¯¦ç»†æŒ‰é’®æ“ä½œ */
function handleView(row) {
  open.value = true;
  form.value = row;
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const operIds = row.operId || ids.value;
  proxy.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?').then(function () {
    return delOperlog(operIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** æ¸…空按钮操作 */
function handleClean() {
  proxy.$modal.confirm("是否确认清空所有操作日志数据项?").then(function () {
    return cleanOperlog();
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("清空成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("monitor/operlog/export",{
    ...queryParams.value,
  }, `config_${new Date().getTime()}.xlsx`);
}
getList();
</script>
src/views/monitor/server/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,183 @@
<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12" class="card-box">
        <el-card>
          <template #header><span>CPU</span></template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table cellspacing="0" style="width: 100%;">
              <thead>
                <tr>
                  <th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">值</div></th>
                </tr>
              </thead>
              <tbody>
                <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="server.cpu">{{ server.cpu.cpuNum }}</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="server.cpu">{{ server.cpu.used }}%</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="server.cpu">{{ server.cpu.sys }}%</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="server.cpu">{{ server.cpu.free }}%</div></td>
                </tr>
              </tbody>
            </table>
          </div>
        </el-card>
      </el-col>
      <el-col :span="12" class="card-box">
        <el-card>
          <template #header><span>内存</span></template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table cellspacing="0" style="width: 100%;">
              <thead>
                <tr>
                  <th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">内存</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">JVM</div></th>
                </tr>
              </thead>
              <tbody>
                <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="server.mem">{{ server.mem.total }}G</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</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="server.mem">{{ server.mem.used}}G</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</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="server.mem">{{ server.mem.free }}G</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</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="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
                </tr>
              </tbody>
            </table>
          </div>
        </el-card>
      </el-col>
      <el-col :span="24" class="card-box">
        <el-card>
          <template #header><span>服务器信息</span></template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table cellspacing="0" style="width: 100%;">
              <tbody>
                <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="server.sys">{{ server.sys.computerName }}</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="server.sys">{{ server.sys.osName }}</div></td>
                </tr>
                <tr>
                  <td class="el-table__cell is-leaf"><div class="cell">服务器IP</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</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="server.sys">{{ server.sys.osArch }}</div></td>
                </tr>
              </tbody>
            </table>
          </div>
        </el-card>
      </el-col>
      <el-col :span="24" class="card-box">
        <el-card>
          <template #header><span>Java虚拟机信息</span></template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table cellspacing="0" style="width: 100%;">
              <tbody>
                <tr>
                  <td class="el-table__cell is-leaf"><div class="cell">Java名称</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell">Java版本</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</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="server.jvm">{{ server.jvm.startTime }}</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="server.jvm">{{ server.jvm.runTime }}</div></td>
                </tr>
                <tr>
                  <td colspan="1" class="el-table__cell is-leaf"><div class="cell">安装路径</div></td>
                  <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
                </tr>
                <tr>
                  <td colspan="1" class="el-table__cell is-leaf"><div class="cell">项目路径</div></td>
                  <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
                </tr>
              </tbody>
            </table>
          </div>
        </el-card>
      </el-col>
      <el-col :span="24" class="card-box">
        <el-card>
          <template #header><span>磁盘状态</span></template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <table cellspacing="0" style="width: 100%;">
              <thead>
                <tr>
                  <th class="el-table__cell el-table__cell is-leaf"><div class="cell">盘符路径</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">文件系统</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">盘符类型</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">总大小</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">可用大小</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">已用大小</div></th>
                  <th class="el-table__cell is-leaf"><div class="cell">已用百分比</div></th>
                </tr>
              </thead>
              <tbody v-if="server.sysFiles">
                <tr v-for="(sysFile, index) in server.sysFiles" :key="index">
                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.dirName }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.sysTypeName }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.typeName }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.total }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.free }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.used }}</div></td>
                  <td class="el-table__cell is-leaf"><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
                </tr>
              </tbody>
            </table>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { getServer } from '@/api/monitor/server'
const server = ref([]);
const { proxy } = getCurrentInstance();
function getList() {
  proxy.$modal.loading("正在加载服务监控数据,请稍候!");
  getServer().then(response => {
    server.value = response.data;
    proxy.$modal.closeLoading();
  });
}
getList();
</script>
src/views/redirect/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
<template>
  <div></div>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute();
const router = useRouter();
const { params, query } = route
const { path } = params
router.replace({ path: '/' + path, query })
</script>
src/views/register.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,208 @@
<template>
  <div class="register">
    <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
      <h3 class="title">若依后台管理系统</h3>
      <el-form-item prop="username">
        <el-input v-model="registerForm.username" type="text" 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"
          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"
          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="captchaOnOff">
        <el-input
          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="medium"
          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-2021 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>
<script setup>
import { ElMessageBox } from "element-plus";
import { getCodeImg, register } from "@/api/login";
const router = useRouter();
const { proxy } = getCurrentInstance();
const registerForm = ref({
  username: "",
  password: "",
  confirmPassword: "",
  code: "",
  uuid: ""
});
const equalToPassword = (rule, value, callback) => {
  if (registerForm.value.password !== value) {
    callback(new Error("两次输入的密码不一致"));
  } else {
    callback();
  }
};
const registerRules = {
  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);
const captchaOnOff = ref(true);
function handleRegister() {
  proxy.$refs.registerRef.validate(valid => {
    if (valid) {
      loading.value = true;
      register(registerForm.value).then(res => {
        const username = registerForm.value.username;
        ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " æ³¨å†ŒæˆåŠŸï¼</font>", "系统提示", {
          dangerouslyUseHTMLString: true,
          type: "success",
        }).then(() => {
          router.push("/login");
        }).catch(() => {});
      }).catch(() => {
        loading.value = false;
        if (captchaOnOff) {
          getCode();
        }
      });
    }
  });
}
function getCode() {
  getCodeImg().then(res => {
    captchaOnOff.value = res.captchaOnOff === undefined ? true : res.captchaOnOff;
    if (captchaOnOff.value) {
      codeUrl.value = "data:image/gif;base64," + res.img;
      registerForm.value.uuid = res.uuid;
    }
  });
}
getCode();
</script>
<style lang='scss' scoped>
.register {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
}
.register-form {
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}
.register-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}
.register-code {
  width: 33%;
  height: 38px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.el-register-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.register-code-img {
  height: 38px;
}
</style>
src/views/system/config/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,326 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="参数名称" prop="configName">
            <el-input
               v-model="queryParams.configName"
               placeholder="请输入参数名称"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="参数键名" prop="configKey">
            <el-input
               v-model="queryParams.configKey"
               placeholder="请输入参数键名"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="系统内置" prop="configType">
            <el-select v-model="queryParams.configType" placeholder="系统内置" clearable size="small">
               <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="创建时间">
            <el-date-picker
               v-model="dateRange"
               size="small"
               style="width: 240px"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:config:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :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"
               size="mini"
               :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"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:config:export']"
            >导出</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Refresh"
               size="mini"
               @click="handleRefreshCache"
               v-hasPermi="['system:config:remove']"
            >刷新缓存</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <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" />
         <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" />
         <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-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:config:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:config:remove']"
               >删除</el-button>
            </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-dialog :title="title" v-model="open" width="500px" append-to-body>
         <el-form ref="configRef" :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>
<script setup name="Config">
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config";
const { proxy } = getCurrentInstance();
const { sys_yes_no } = proxy.useDict("sys_yes_no");
const configList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    configName: undefined,
    configKey: undefined,
    configType: undefined
  },
  rules: {
    configName: [{ required: true, message: "参数名称不能为空", trigger: "blur" }],
    configKey: [{ required: true, message: "参数键名不能为空", trigger: "blur" }],
    configValue: [{ required: true, message: "参数键值不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å‚数列表 */
function getList() {
  loading.value = true;
  listConfig(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    configList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    configId: undefined,
    configName: undefined,
    configKey: undefined,
    configValue: undefined,
    configType: "Y",
    remark: undefined
  };
  proxy.resetForm("configRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.configId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  open.value = true;
  title.value = "添加参数";
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset();
  const configId = row.configId || ids.value;
  getConfig(configId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改参数";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["configRef"].validate(valid => {
    if (valid) {
      if (form.value.configId != undefined) {
        updateConfig(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addConfig(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const configIds = row.configId || ids.value;
  proxy.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?').then(function () {
    return delConfig(configIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("system/config/export", {
    ...queryParams.value
  }, `config_${new Date().getTime()}.xlsx`);
}
/** åˆ·æ–°ç¼“存按钮操作 */
function handleRefreshCache() {
  refreshCache().then(() => {
    proxy.$modal.msgSuccess("刷新缓存成功");
  });
}
getList();
</script>
src/views/system/dept/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,293 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
         <el-form-item label="部门名称" prop="deptName">
            <el-input
               v-model="queryParams.deptName"
               placeholder="请输入部门名称"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="部门状态" clearable size="small">
               <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" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:dept:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="info"
               plain
               icon="Sort"
               size="mini"
               @click="toggleExpandAll"
            >展开/折叠</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table
         v-if="refreshTable"
         v-loading="loading"
         :data="deptList"
         row-key="deptId"
         :default-expand-all="isExpandAll"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
         <el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
         <el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
         <el-table-column prop="status" 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 label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:dept:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Plus"
                  @click="handleAdd(scope.row)"
                  v-hasPermi="['system:dept:add']"
               >新增</el-button>
               <el-button
                  v-if="scope.row.parentId != 0"
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:dept:remove']"
               >删除</el-button>
            </template>
         </el-table-column>
      </el-table>
      <!-- æ·»åŠ æˆ–ä¿®æ”¹éƒ¨é—¨å¯¹è¯æ¡† -->
      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
         <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
            <el-row>
               <el-col :span="24" v-if="form.parentId !== 0">
                  <el-form-item label="上级部门" prop="parentId">
                     <tree-select
                        v-model:value="form.parentId"
                        :options="deptOptions"
                        :objMap="{ value: 'deptId', label: 'deptName', children: 'children' }"
                        placeholder="选择上级部门"
                     />
                  </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">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept";
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const deptList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const title = ref("");
const deptOptions = ref([]);
const isExpandAll = ref(true);
const refreshTable = ref(true);
const data = reactive({
  form: {},
  queryParams: {
    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(data);
/** æŸ¥è¯¢éƒ¨é—¨åˆ—表 */
function getList() {
  loading.value = true;
  listDept(queryParams.value).then(response => {
    deptList.value = proxy.handleTree(response.data, "deptId");
    loading.value = false;
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    deptId: undefined,
    parentId: undefined,
    deptName: undefined,
    orderNum: 0,
    leader: undefined,
    phone: undefined,
    email: undefined,
    status: "0"
  };
  proxy.resetForm("deptRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
async function handleAdd(row) {
  reset();
  await listDept().then(response => {
    deptOptions.value = proxy.handleTree(response.data, "deptId");
  });
  if (row != undefined) {
    form.value.parentId = row.deptId;
  }
  open.value = true;
  title.value = "添加部门";
}
/** å±•å¼€/折叠操作 */
function toggleExpandAll() {
  refreshTable.value = false;
  isExpandAll.value = !isExpandAll.value;
  nextTick(() => {
    refreshTable.value = true;
  });
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
async function handleUpdate(row) {
  reset();
  await listDeptExcludeChild(row.deptId).then(response => {
    deptOptions.value = proxy.handleTree(response.data, "deptId");
  });
  getDept(row.deptId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改部门";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["deptRef"].validate(valid => {
    if (valid) {
      if (form.value.deptId != undefined) {
        updateDept(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addDept(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  proxy.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() {
    return delDept(row.deptId);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
getList();
</script>
src/views/system/dict/data.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,363 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="字典名称" prop="dictType">
            <el-select v-model="queryParams.dictType" size="small">
               <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
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="数据状态" clearable size="small">
               <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" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:dict:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :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"
               size="mini"
               :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"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:dict:export']"
            >导出</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Close"
               size="mini"
               @click="handleClose"
            >关闭</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <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" />
         <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="150" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:dict:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:dict:remove']"
               >删除</el-button>
            </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-dialog :title="title" v-model="open" width="500px" append-to-body>
         <el-form ref="dataRef" :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"
                     :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">
import { listType, getType } from "@/api/system/dict/type";
import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data";
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const dataList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const defaultDictType = ref("");
const typeOptions = ref([]);
const route = useRoute();
// æ•°æ®æ ‡ç­¾å›žæ˜¾æ ·å¼
const listClassOptions = ref([
  { value: "default", label: "默认" },
  { value: "primary", label: "主要" },
  { value: "success", label: "成功" },
  { value: "info", label: "信息" },
  { value: "warning", label: "警告" },
  { value: "danger", label: "危险" }
]);
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    dictName: undefined,
    dictType: undefined,
    status: undefined
  },
  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);
/** æŸ¥è¯¢å­—典类型详细 */
function getTypes(dictId) {
  getType(dictId).then(response => {
    queryParams.value.dictType = response.data.dictType;
    defaultDictType.value = response.data.dictType;
    getList();
  });
}
/** æŸ¥è¯¢å­—典类型列表 */
function getTypeList() {
  listType().then(response => {
    typeOptions.value = response.rows;
  });
}
/** æŸ¥è¯¢å­—典数据列表 */
function getList() {
  loading.value = true;
  listData(queryParams.value).then(response => {
    dataList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    dictCode: undefined,
    dictLabel: undefined,
    dictValue: undefined,
    cssClass: undefined,
    listClass: "default",
    dictSort: 0,
    status: "0",
    remark: undefined
  };
  proxy.resetForm("dataRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** è¿”回按钮操作 */
function handleClose() {
  const obj = { path: "/system/dict" };
  proxy.$tab.closeOpenPage(obj);
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  queryParams.value.dictType = defaultDictType;
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  open.value = true;
  title.value = "添加字典数据";
  form.value.dictType = queryParams.value.dictType;
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.dictCode);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset();
  const dictCode = row.dictCode || ids.value;
  getData(dictCode).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改字典数据";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["dataRef"].validate(valid => {
    if (valid) {
      if (form.value.dictCode != undefined) {
        updateData(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addData(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const dictCodes = row.dictCode || ids.value;
  proxy.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?').then(function() {
    return delData(dictCodes);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("system/dict/data/export", {
    ...queryParams.value
  }, `dict_data_${new Date().getTime()}.xlsx`);
}
getTypes(route.params && route.params.dictId);
getTypeList();
</script>
src/views/system/dict/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,332 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="字典名称" prop="dictName">
            <el-input
               v-model="queryParams.dictName"
               placeholder="请输入字典名称"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="字典类型" prop="dictType">
            <el-input
               v-model="queryParams.dictType"
               placeholder="请输入字典类型"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select
               v-model="queryParams.status"
               placeholder="字典状态"
               clearable
               size="small"
               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="创建时间">
            <el-date-picker
               v-model="dateRange"
               size="small"
               style="width: 240px"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:dict:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :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"
               size="mini"
               :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"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:dict:export']"
            >导出</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Refresh"
               size="mini"
               @click="handleRefreshCache"
               v-hasPermi="['system:dict:remove']"
            >刷新缓存</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <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" />
         <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" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:dict:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:dict:remove']"
               >删除</el-button>
            </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-dialog :title="title" v-model="open" width="500px" append-to-body>
         <el-form ref="dictRef" :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">
import { listType, getType, delType, addType, updateType, refreshCache } from "@/api/system/dict/type";
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const typeList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    dictName: undefined,
    dictType: undefined,
    status: undefined
  },
  rules: {
    dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
    dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
  },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å­—典类型列表 */
function getList() {
  loading.value = true;
  listType(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    typeList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    dictId: undefined,
    dictName: undefined,
    dictType: undefined,
    status: "0",
    remark: undefined
  };
  proxy.resetForm("dictRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  open.value = true;
  title.value = "添加字典类型";
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.dictId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset();
  const dictId = row.dictId || ids.value;
  getType(dictId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改字典类型";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["dictRef"].validate(valid => {
    if (valid) {
      if (form.value.dictId != undefined) {
        updateType(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addType(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const dictIds = row.dictId || ids.value;
  proxy.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?').then(function() {
    return delType(dictIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("system/dict/type/export", {
    ...queryParams.value
  }, `dict_${new Date().getTime()}.xlsx`);
}
/** åˆ·æ–°ç¼“存按钮操作 */
function handleRefreshCache() {
  refreshCache().then(() => {
    proxy.$modal.msgSuccess("刷新成功");
  });
}
getList();
</script>
src/views/system/menu/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,450 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
         <el-form-item label="菜单名称" prop="menuName">
            <el-input
               v-model="queryParams.menuName"
               placeholder="请输入菜单名称"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="菜单状态" clearable size="small">
               <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" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:menu:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="info"
               plain
               icon="Sort"
               size="mini"
               @click="toggleExpandAll"
            >展开/折叠</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table
         v-if="refreshTable"
         v-loading="loading"
         :data="menuList"
         row-key="menuId"
         :default-expand-all="isExpandAll"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
         <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
         <el-table-column prop="icon" label="图标" align="center" width="100">
            <template #default="scope">
               <svg-icon :icon-class="scope.row.icon" />
            </template>
         </el-table-column>
         <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
         <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
         <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
         <el-table-column prop="status" label="状态" width="80">
            <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">
            <template #default="scope">
               <span>{{ parseTime(scope.row.createTime) }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:menu:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Plus"
                  @click="handleAdd(scope.row)"
                  v-hasPermi="['system:menu:add']"
               >新增</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:menu:remove']"
               >删除</el-button>
            </template>
         </el-table-column>
      </el-table>
      <!-- æ·»åŠ æˆ–ä¿®æ”¹èœå•å¯¹è¯æ¡† -->
      <el-dialog :title="title" v-model="open" width="680px" append-to-body>
         <el-form ref="menuRef" :model="form" :rules="rules" label-width="100px">
            <el-row>
               <el-col :span="24">
                  <el-form-item label="上级菜单">
                     <tree-select
                        v-model:value="form.parentId"
                        :options="menuOptions"
                        :objMap="{ value: 'menuId', label: 'menuName', children: 'children' }"
                        placeholder="选择上级菜单"
                     />
                  </el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="菜单类型" prop="menuType">
                     <el-radio-group v-model="form.menuType">
                        <el-radio label="M">目录</el-radio>
                        <el-radio label="C">菜单</el-radio>
                        <el-radio label="F">按钮</el-radio>
                     </el-radio-group>
                  </el-form-item>
               </el-col>
               <el-col :span="24" v-if="form.menuType != 'F'">
                  <el-form-item label="菜单图标">
                     <el-popover
                        placement="bottom-start"
                        :width="540"
                        v-model:visible="showChooseIcon"
                        trigger="click"
                        @show="showSelectIcon"
                     >
                        <icon-select ref="iconSelectRef" @selected="selected" />
                        <template #reference>
                           <el-input v-model="form.icon" placeholder="点击选择图标" readonly>
                              <template #prefix>
                                 <svg-icon
                                    v-if="form.icon"
                                    :icon-class="form.icon"
                                    class="el-input__icon"
                                    style="height: 32px;width: 16px;"
                                 />
                                 <i v-else class="el-icon-search el-input__icon" />
                              </template>
                           </el-input>
                        </template>
                     </el-popover>
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="菜单名称" prop="menuName">
                     <el-input v-model="form.menuName" 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" v-if="form.menuType != 'F'">
                  <el-form-item>
                     <template #label>
                        <span>
                           <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>是否外链
                        </span>
                     </template>
                     <el-radio-group v-model="form.isFrame">
                        <el-radio label="0">是</el-radio>
                        <el-radio label="1">否</el-radio>
                     </el-radio-group>
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType != 'F'">
                  <el-form-item prop="path">
                     <template #label>
                        <span>
                           <el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           è·¯ç”±åœ°å€
                        </span>
                     </template>
                     <el-input v-model="form.path" placeholder="请输入路由地址" />
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType == 'C'">
                  <el-form-item prop="component">
                     <template #label>
                        <span>
                           <el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           ç»„件路径
                        </span>
                     </template>
                     <el-input v-model="form.component" placeholder="请输入组件路径" />
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType != 'M'">
                  <el-form-item>
                     <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
                     <template #label>
                        <span>
                           <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           æƒé™å­—符
                        </span>
                     </template>
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType == 'C'">
                  <el-form-item>
                     <el-input v-model="form.query" placeholder="请输入路由参数" maxlength="255" />
                     <template #label>
                        <span>
                           <el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           è·¯ç”±å‚æ•°
                        </span>
                     </template>
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType == 'C'">
                  <el-form-item>
                     <template #label>
                        <span>
                           <el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           æ˜¯å¦ç¼“å­˜
                        </span>
                     </template>
                     <el-radio-group v-model="form.isCache">
                        <el-radio label="0">缓存</el-radio>
                        <el-radio label="1">不缓存</el-radio>
                     </el-radio-group>
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType != 'F'">
                  <el-form-item>
                     <template #label>
                        <span>
                           <el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           æ˜¾ç¤ºçŠ¶æ€
                        </span>
                     </template>
                     <el-radio-group v-model="form.visible">
                        <el-radio
                           v-for="dict in sys_show_hide"
                           :key="dict.value"
                           :label="dict.value"
                        >{{ dict.label }}</el-radio>
                     </el-radio-group>
                  </el-form-item>
               </el-col>
               <el-col :span="12" v-if="form.menuType != 'F'">
                  <el-form-item>
                     <template #label>
                        <span>
                           <el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top">
                              <i class="el-icon-question"></i>
                           </el-tooltip>
                           èœå•状态
                        </span>
                     </template>
                     <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="Menu">
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu";
import SvgIcon from "@/components/SvgIcon";
import IconSelect from "@/components/IconSelect";
const { proxy } = getCurrentInstance();
const { sys_show_hide, sys_normal_disable } = proxy.useDict("sys_show_hide", "sys_normal_disable");
const menuList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const title = ref("");
const menuOptions = ref([]);
const isExpandAll = ref(false);
const refreshTable = ref(true);
const showChooseIcon = ref(false);
const iconSelectRef = ref(null);
const data = reactive({
  form: {},
  queryParams: {
    menuName: undefined,
    visible: undefined
  },
  rules: {
    menuName: [{ required: true, message: "菜单名称不能为空", trigger: "blur" }],
    orderNum: [{ required: true, message: "菜单顺序不能为空", trigger: "blur" }],
    path: [{ required: true, message: "路由地址不能为空", trigger: "blur" }]
  },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢èœå•列表 */
function getList() {
  loading.value = true;
  listMenu(queryParams.value).then(response => {
    menuList.value = proxy.handleTree(response.data, "menuId");
    loading.value = false;
  });
}
/** æŸ¥è¯¢èœå•下拉树结构 */
async function getTreeselect() {
  menuOptions.value = [];
  await listMenu().then(response => {
    const menu = { menuId: 0, menuName: "主类目", children: [] };
    menu.children = proxy.handleTree(response.data, "menuId");
    menuOptions.value.push(menu);
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    menuId: undefined,
    parentId: 0,
    menuName: undefined,
    icon: undefined,
    menuType: "M",
    orderNum: undefined,
    isFrame: "1",
    isCache: "0",
    visible: "0",
    status: "0"
  };
  proxy.resetForm("menuRef");
}
/** å±•示下拉图标 */
function showSelectIcon() {
  iconSelectRef.value.reset();
  showChooseIcon.value = true;
}
/** é€‰æ‹©å›¾æ ‡ */
function selected(name) {
  form.value.icon = name;
  showChooseIcon.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
async function handleAdd(row) {
  reset();
  await getTreeselect();
  if (row != null && row.menuId) {
    form.value.parentId = row.menuId;
  } else {
    form.value.parentId = 0;
  }
  open.value = true;
  title.value = "添加菜单";
}
/** å±•å¼€/折叠操作 */
function toggleExpandAll() {
  refreshTable.value = false;
  isExpandAll.value = !isExpandAll.value;
  nextTick(() => {
    refreshTable.value = true;
  });
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
async function handleUpdate(row) {
  reset();
  await getTreeselect();
  getMenu(row.menuId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改菜单";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["menuRef"].validate(valid => {
    if (valid) {
      if (form.value.menuId != undefined) {
        updateMenu(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addMenu(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  proxy.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?').then(function() {
    return delMenu(row.menuId);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
getList();
</script>
src/views/system/notice/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="公告标题" prop="noticeTitle">
            <el-input
               v-model="queryParams.noticeTitle"
               placeholder="请输入公告标题"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="操作人员" prop="createBy">
            <el-input
               v-model="queryParams.createBy"
               placeholder="请输入操作人员"
               clearable
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="类型" prop="noticeType">
            <el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable size="small">
               <el-option
                  v-for="dict in sys_notice_type"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
               />
            </el-select>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:notice:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :disabled="single"
               @click="handleUpdate"
               v-hasPermi="['system:notice:edit']"
            >修改</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['system:notice:remove']"
            >删除</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="序号" align="center" prop="noticeId" width="100" />
         <el-table-column
            label="公告标题"
            align="center"
            prop="noticeTitle"
            :show-overflow-tooltip="true"
         />
         <el-table-column label="公告类型" align="center" prop="noticeType" width="100">
            <template #default="scope">
               <dict-tag :options="sys_notice_type" :value="scope.row.noticeType" />
            </template>
         </el-table-column>
         <el-table-column label="状态" align="center" prop="status" width="100">
            <template #default="scope">
               <dict-tag :options="sys_notice_status" :value="scope.row.status" />
            </template>
         </el-table-column>
         <el-table-column label="创建者" align="center" prop="createBy" width="100" />
         <el-table-column label="创建时间" align="center" prop="createTime" width="100">
            <template #default="scope">
               <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:notice:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:notice:remove']"
               >删除</el-button>
            </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-dialog :title="title" v-model="open" width="780px" append-to-body>
         <el-form ref="noticeRef" :model="form" :rules="rules" label-width="80px">
            <el-row>
               <el-col :span="12">
                  <el-form-item label="公告标题" prop="noticeTitle">
                     <el-input v-model="form.noticeTitle" placeholder="请输入公告标题" />
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="公告类型" prop="noticeType">
                     <el-select v-model="form.noticeType" placeholder="请选择">
                        <el-option
                           v-for="dict in sys_notice_type"
                           :key="dict.value"
                           :label="dict.label"
                           :value="dict.value"
                        ></el-option>
                     </el-select>
                  </el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="状态">
                     <el-radio-group v-model="form.status">
                        <el-radio
                           v-for="dict in sys_notice_status"
                           :key="dict.value"
                           :label="dict.value"
                        >{{ dict.label }}</el-radio>
                     </el-radio-group>
                  </el-form-item>
               </el-col>
               <el-col :span="24">
                  <el-form-item label="内容">
                     <el-input
                        :rows="6"
                        type="textarea"
                        placeholder="请输入内容"
                        v-model="form.noticeContent"
                     />
                  </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="Notice">
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
const { proxy } = getCurrentInstance();
const { sys_notice_status, sys_notice_type } = proxy.useDict("sys_notice_status", "sys_notice_type");
const noticeList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    noticeTitle: undefined,
    createBy: undefined,
    status: undefined
  },
  rules: {
    noticeTitle: [{ required: true, message: "公告标题不能为空", trigger: "blur" }],
    noticeType: [{ required: true, message: "公告类型不能为空", trigger: "change" }]
  },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å…¬å‘Šåˆ—表 */
function getList() {
  loading.value = true;
  listNotice(queryParams.value).then(response => {
    noticeList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    noticeId: undefined,
    noticeTitle: undefined,
    noticeType: undefined,
    noticeContent: undefined,
    status: "0"
  };
  proxy.resetForm("noticeRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.noticeId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  open.value = true;
  title.value = "添加公告";
}
/**修改按钮操作 */
function handleUpdate(row) {
  reset();
  const noticeId = row.noticeId || ids.value;
  getNotice(noticeId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改公告";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["noticeRef"].validate(valid => {
    if (valid) {
      if (form.value.noticeId != undefined) {
        updateNotice(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addNotice(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const noticeIds = row.noticeId || ids.value
  proxy.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?').then(function() {
    return delNotice(noticeIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
getList();
</script>
src/views/system/post/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,293 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
         <el-form-item label="岗位编码" prop="postCode">
            <el-input
               v-model="queryParams.postCode"
               placeholder="请输入岗位编码"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="岗位名称" prop="postName">
            <el-input
               v-model="queryParams.postName"
               placeholder="请输入岗位名称"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="岗位状态" clearable size="small">
               <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" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:post:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :disabled="single"
               @click="handleUpdate"
               v-hasPermi="['system:post:edit']"
            >修改</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['system:post:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:post:export']"
            >导出</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="岗位编号" align="center" prop="postId" />
         <el-table-column label="岗位编码" align="center" prop="postCode" />
         <el-table-column label="岗位名称" align="center" prop="postName" />
         <el-table-column label="岗位排序" align="center" prop="postSort" />
         <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="createTime" width="180">
            <template #default="scope">
               <span>{{ parseTime(scope.row.createTime) }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:post:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:post:remove']"
               >删除</el-button>
            </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-dialog :title="title" v-model="open" width="500px" append-to-body>
         <el-form ref="postRef" :model="form" :rules="rules" label-width="80px">
            <el-form-item label="岗位名称" prop="postName">
               <el-input v-model="form.postName" placeholder="请输入岗位名称" />
            </el-form-item>
            <el-form-item label="岗位编码" prop="postCode">
               <el-input v-model="form.postCode" placeholder="请输入编码名称" />
            </el-form-item>
            <el-form-item label="岗位顺序" prop="postSort">
               <el-input-number v-model="form.postSort" controls-position="right" :min="0" />
            </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-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="Post">
import { listPost, addPost, delPost, getPost, updatePost } from "@/api/system/post";
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const postList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    postCode: undefined,
    postName: undefined,
    status: undefined
  },
  rules: {
    postName: [{ required: true, message: "岗位名称不能为空", trigger: "blur" }],
    postCode: [{ required: true, message: "岗位编码不能为空", trigger: "blur" }],
    postSort: [{ required: true, message: "岗位顺序不能为空", trigger: "blur" }],
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å²—位列表 */
function getList() {
  loading.value = true;
  listPost(queryParams.value).then(response => {
    postList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
  form.value = {
    postId: undefined,
    postCode: undefined,
    postName: undefined,
    postSort: 0,
    status: "0",
    remark: undefined
  };
  proxy.resetForm("postRef");
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.postId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  open.value = true;
  title.value = "添加岗位";
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset();
  const postId = row.postId || ids.value;
  getPost(postId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改岗位";
  });
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["postRef"].validate(valid => {
    if (valid) {
      if (form.value.postId != undefined) {
        updatePost(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addPost(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const postIds = row.postId || ids.value;
  proxy.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?').then(function() {
    return delPost(postIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("system/post/export", {
    ...queryParams.value
  }, `post_${new Date().getTime()}.xlsx`);
}
getList();
</script>
src/views/system/role/authUser.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,183 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true">
         <el-form-item label="用户名称" prop="userName">
            <el-input
               v-model="queryParams.userName"
               placeholder="请输入用户名称"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="手机号码" prop="phonenumber">
            <el-input
               v-model="queryParams.phonenumber"
               placeholder="请输入手机号码"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="openSelectUser"
               v-hasPermi="['system:role:add']"
            >添加用户</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="CircleClose"
               size="mini"
               :disabled="multiple"
               @click="cancelAuthUserAll"
               v-hasPermi="['system:role:remove']"
            >批量取消授权</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Close"
               size="mini"
               @click="handleClose"
            >关闭</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
         <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
         <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
         <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
         <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="createTime" width="180">
            <template #default="scope">
               <span>{{ parseTime(scope.row.createTime) }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  type="text"
                  icon="CircleClose"
                  @click="cancelAuthUser(scope.row)"
                  v-hasPermi="['system:role:remove']"
               >取消授权</el-button>
            </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"
      />
      <select-user ref="selectRef" :roleId="queryParams.roleId" @ok="handleQuery" />
   </div>
</template>
<script setup name="AuthUser">
import selectUser from "./selectUser";
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
const route = useRoute();
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const userList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const multiple = ref(true);
const total = ref(0);
const userIds = ref([]);
const queryParams = reactive({
  pageNum: 1,
  pageSize: 10,
  roleId: route.params.roleId,
  userName: undefined,
  phonenumber: undefined,
});
/** æŸ¥è¯¢æŽˆæƒç”¨æˆ·åˆ—表 */
function getList() {
  loading.value = true;
  allocatedUserList(queryParams).then(response => {
    userList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
// è¿”回按钮
function handleClose() {
  const obj = { path: "/system/role" };
  proxy.$tab.closeOpenPage(obj);
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  userIds.value = selection.map(item => item.userId);
  multiple.value = !selection.length;
}
/** æ‰“开授权用户表弹窗 */
function openSelectUser() {
  proxy.$refs["selectRef"].show();
}
/** å–消授权按钮操作 */
function cancelAuthUser(row) {
  proxy.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?').then(function () {
    return authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("取消授权成功");
  }).catch(() => {});
}
/** æ‰¹é‡å–消授权按钮操作 */
function cancelAuthUserAll(row) {
  const roleId = queryParams.roleId;
  const uIds = userIds.value.join(",");
  proxy.$modal.confirm("是否取消选中用户授权数据项?").then(function () {
    return authUserCancelAll({ roleId: roleId, userIds: uIds });
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("取消授权成功");
  }).catch(() => {});
}
getList();
</script>
src/views/system/role/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,586 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true">
         <el-form-item label="角色名称" prop="roleName">
            <el-input
               v-model="queryParams.roleName"
               placeholder="请输入角色名称"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="权限字符" prop="roleKey">
            <el-input
               v-model="queryParams.roleKey"
               placeholder="请输入权限字符"
               clearable
               size="small"
               style="width: 240px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select
               v-model="queryParams.status"
               placeholder="角色状态"
               clearable
               size="small"
               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="创建时间">
            <el-date-picker
               v-model="dateRange"
               size="small"
               style="width: 240px"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               size="mini"
               @click="handleAdd"
               v-hasPermi="['system:role:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               size="mini"
               :disabled="single"
               @click="handleUpdate"
               v-hasPermi="['system:role:edit']"
            >修改</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               size="mini"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['system:role:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               size="mini"
               @click="handleExport"
               v-hasPermi="['system:role:export']"
            >导出</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <!-- è¡¨æ ¼æ•°æ® -->
      <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="角色编号" prop="roleId" width="120" />
         <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
         <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
         <el-table-column label="显示顺序" prop="roleSort" width="100" />
         <el-table-column label="状态" align="center" width="100">
            <template #default="scope">
               <el-switch
                  v-model="scope.row.status"
                  active-value="0"
                  inactive-value="1"
                  @change="handleStatusChange(scope.row)"
               ></el-switch>
            </template>
         </el-table-column>
         <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" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button
                  size="mini"
                  v-if="scope.row.roleId !== 1"
                  type="text"
                  icon="Edit"
                  @click="handleUpdate(scope.row)"
                  v-hasPermi="['system:role:edit']"
               >修改</el-button>
               <el-button
                  size="mini"
                  v-if="scope.row.roleId !== 1"
                  type="text"
                  icon="Delete"
                  @click="handleDelete(scope.row)"
                  v-hasPermi="['system:role:remove']"
               >删除</el-button>
               <el-dropdown size="mini" v-if="scope.row.roleId !== 1" @command="(command) => handleCommand(command, scope.row)">
                  <span class="el-dropdown-link" v-hasPermi="['system:role:edit']">
                     <el-icon><d-arrow-right /></el-icon>更多
                  </span>
                  <template #dropdown>
                     <el-dropdown-menu>
                        <el-dropdown-item command="handleDataScope" icon="CircleCheck"
                           v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item>
                        <el-dropdown-item command="handleAuthUser" icon="User"
                           v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item>
                     </el-dropdown-menu>
                  </template>
               </el-dropdown>
            </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-dialog :title="title" v-model="open" width="500px" append-to-body>
         <el-form ref="roleRef" :model="form" :rules="rules" label-width="100px">
            <el-form-item label="角色名称" prop="roleName">
               <el-input v-model="form.roleName" placeholder="请输入角色名称" />
            </el-form-item>
            <el-form-item prop="roleKey">
               <template #label>
                  <span>
                     <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
                        <i class="el-icon-question"></i>
                     </el-tooltip>
                     æƒé™å­—符
                  </span>
               </template>
               <el-input v-model="form.roleKey" placeholder="请输入权限字符" />
            </el-form-item>
            <el-form-item label="角色顺序" prop="roleSort">
               <el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
            </el-form-item>
            <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-form-item label="菜单权限">
               <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
               <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
               <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
               <el-tree
                  class="tree-border"
                  :data="menuOptions"
                  show-checkbox
                  ref="menuRef"
                  node-key="id"
                  :check-strictly="!form.menuCheckStrictly"
                  empty-text="加载中,请稍候"
                  :props="{ label: 'label', children: 'children' }"
               ></el-tree>
            </el-form-item>
            <el-form-item label="备注">
               <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>
      <!-- åˆ†é…è§’色数据权限对话框 -->
      <el-dialog :title="title" v-model="openDataScope" width="500px" append-to-body>
         <el-form :model="form" label-width="80px">
            <el-form-item label="角色名称">
               <el-input v-model="form.roleName" :disabled="true" />
            </el-form-item>
            <el-form-item label="权限字符">
               <el-input v-model="form.roleKey" :disabled="true" />
            </el-form-item>
            <el-form-item label="权限范围">
               <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
                  <el-option
                     v-for="item in dataScopeOptions"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value"
                  ></el-option>
               </el-select>
            </el-form-item>
            <el-form-item label="数据权限" v-show="form.dataScope == 2">
               <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
               <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
               <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
               <el-tree
                  class="tree-border"
                  :data="deptOptions"
                  show-checkbox
                  default-expand-all
                  ref="deptRef"
                  node-key="id"
                  :check-strictly="!form.deptCheckStrictly"
                  empty-text="加载中,请稍候"
                  :props="{ label: 'label', children: 'children' }"
               ></el-tree>
            </el-form-item>
         </el-form>
         <template #footer>
            <div class="dialog-footer">
               <el-button type="primary" @click="submitDataScope">ç¡® å®š</el-button>
               <el-button @click="cancelDataScope">取 æ¶ˆ</el-button>
            </div>
         </template>
      </el-dialog>
   </div>
</template>
<script setup name="Role">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole } from "@/api/system/role";
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu";
import { treeselect as deptTreeselect, roleDeptTreeselect } from "@/api/system/dept";
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const roleList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const menuOptions = ref([]);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const deptExpand = ref(true);
const deptNodeAll = ref(false);
const deptOptions = ref([]);
const openDataScope = ref(false);
const menuRef = ref(null);
const deptRef = ref(null);
/** æ•°æ®èŒƒå›´é€‰é¡¹*/
const dataScopeOptions = ref([
  { value: "1", label: "全部数据权限" },
  { value: "2", label: "自定数据权限" },
  { value: "3", label: "本部门数据权限" },
  { value: "4", label: "本部门及以下数据权限" },
  { value: "5", label: "仅本人数据权限" }
]);
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    roleName: undefined,
    roleKey: undefined,
    status: undefined
  },
  rules: {
    roleName: [{ required: true, message: "角色名称不能为空", trigger: "blur" }],
    roleKey: [{ required: true, message: "权限字符不能为空", trigger: "blur" }],
    roleSort: [{ required: true, message: "角色顺序不能为空", trigger: "blur" }]
  },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢è§’色列表 */
function getList() {
  loading.value = true;
  listRole(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    roleList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  handleQuery();
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const roleIds = row.roleId || ids.value;
  proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () {
    return delRole(roleIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("system/role/export", {
    ...queryParams.value,
  }, `role_${new Date().getTime()}.xlsx`);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.roleId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** è§’色状态修改 */
function handleStatusChange(row) {
  let text = row.status === "0" ? "启用" : "停用";
  proxy.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?').then(function () {
    return changeRoleStatus(row.roleId, row.status);
  }).then(() => {
    proxy.$modal.msgSuccess(text + "成功");
  }).catch(function () {
    row.status = row.status === "0" ? "1" : "0";
  });
}
/** æ›´å¤šæ“ä½œ */
function handleCommand(command, row) {
  switch (command) {
    case "handleDataScope":
      handleDataScope(row);
      break;
    case "handleAuthUser":
      handleAuthUser(row);
      break;
    default:
      break;
  }
}
/** åˆ†é…ç”¨æˆ· */
function handleAuthUser(row) {
  router.push("/system/role-auth/user/" + row.roleId);
}
/** æŸ¥è¯¢èœå•树结构 */
function getMenuTreeselect() {
  menuTreeselect().then(response => {
    menuOptions.value = response.data;
  });
}
/** æ‰€æœ‰éƒ¨é—¨èŠ‚ç‚¹æ•°æ® */
function getDeptAllCheckedKeys() {
  // ç›®å‰è¢«é€‰ä¸­çš„部门节点
  let checkedKeys = deptRef.value.getCheckedKeys();
  // åŠé€‰ä¸­çš„部门节点
  let halfCheckedKeys = deptRef.value.getHalfCheckedKeys();
  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
  return checkedKeys;
}
/** é‡ç½®æ–°å¢žçš„表单以及其他数据  */
function reset() {
  if (menuRef.value != undefined) {
    menuRef.value.setCheckedKeys([]);
  }
  menuExpand.value = false;
  menuNodeAll.value = false;
  deptExpand.value = true;
  deptNodeAll.value = false;
  form.value = {
    roleId: undefined,
    roleName: undefined,
    roleKey: undefined,
    roleSort: 0,
    status: "0",
    menuIds: [],
    deptIds: [],
    menuCheckStrictly: true,
    deptCheckStrictly: true,
    remark: undefined
  };
  proxy.resetForm("roleRef");
}
/** æ·»åŠ è§’è‰² */
function handleAdd() {
  reset();
  getMenuTreeselect();
  open.value = true;
  title.value = "添加角色";
}
/** ä¿®æ”¹è§’色 */
function handleUpdate(row) {
  reset();
  const roleId = row.roleId || ids.value;
  const roleMenu = getRoleMenuTreeselect(roleId);
  getRole(roleId).then(response => {
    form.value = response.data;
    form.value.roleSort = Number(form.value.roleSort);
    open.value = true;
    nextTick(() => {
      roleMenu.then((res) => {
        let checkedKeys = res.checkedKeys;
        checkedKeys.forEach((v) => {
          nextTick(() => {
            menuRef.value.setChecked(v, true, false);
          });
        });
      });
    });
    title.value = "修改角色";
  });
}
/** æ ¹æ®è§’色ID查询菜单树结构 */
function getRoleMenuTreeselect(roleId) {
  return roleMenuTreeselect(roleId).then(response => {
    menuOptions.value = response.menus;
    return response;
  });
}
/** æ ¹æ®è§’色ID查询部门树结构 */
function getRoleDeptTreeselect(roleId) {
  return roleDeptTreeselect(roleId).then(response => {
    deptOptions.value = response.depts;
    return response;
  });
}
/** æ ‘权限(展开/折叠)*/
function handleCheckedTreeExpand(value, type) {
  if (type == "menu") {
    let treeList = menuOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
    }
  } else if (type == "dept") {
    let treeList = deptOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
    }
  }
}
/** æ ‘权限(全选/全不选) */
function handleCheckedTreeNodeAll(value, type) {
  if (type == "menu") {
    menuRef.value.setCheckedNodes(value ? menuOptions.value : []);
  } else if (type == "dept") {
    deptRef.value.setCheckedNodes(value ? deptOptions.value : []);
  }
}
/** æ ‘权限(父子联动) */
function handleCheckedTreeConnect(value, type) {
  if (type == "menu") {
    form.value.menuCheckStrictly = value ? true : false;
  } else if (type == "dept") {
    form.value.deptCheckStrictly = value ? true : false;
  }
}
/** æ‰€æœ‰èœå•节点数据 */
function getMenuAllCheckedKeys() {
  // ç›®å‰è¢«é€‰ä¸­çš„菜单节点
  let checkedKeys = menuRef.value.getCheckedKeys();
  // åŠé€‰ä¸­çš„菜单节点
  let halfCheckedKeys = menuRef.value.getHalfCheckedKeys();
  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
  return checkedKeys;
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["roleRef"].validate(valid => {
    if (valid) {
      if (form.value.roleId != undefined) {
        form.value.menuIds = getMenuAllCheckedKeys();
        updateRole(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        form.value.menuIds = getMenuAllCheckedKeys();
        addRole(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
}
/** é€‰æ‹©è§’色权限范围触发 */
function dataScopeSelectChange(value) {
  if (value !== "2") {
    deptRef.value.setCheckedKeys([]);
  }
}
/** åˆ†é…æ•°æ®æƒé™æ“ä½œ */
function handleDataScope(row) {
  reset();
  const roleDeptTreeselect = getRoleDeptTreeselect(row.roleId);
  getRole(row.roleId).then(response => {
    form.value = response.data;
    openDataScope.value = true;
    nextTick(() => {
      roleDeptTreeselect.then(res => {
        nextTick(() => {
          if (deptRef.value) {
            deptRef.value.setCheckedKeys(res.checkedKeys);
          }
        });
      });
    });
    title.value = "分配数据权限";
  });
}
/** æäº¤æŒ‰é’®ï¼ˆæ•°æ®æƒé™ï¼‰ */
function submitDataScope() {
  if (form.value.roleId != undefined) {
    form.value.deptIds = getDeptAllCheckedKeys();
    dataScope(form.value).then(response => {
      proxy.$modal.msgSuccess("修改成功");
      openDataScope.value = false;
      getList();
    });
  }
}
/** å–消按钮(数据权限)*/
function cancelDataScope() {
  openDataScope.value = false;
  reset();
}
getList();
</script>
src/views/system/role/selectUser.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,140 @@
<template>
   <!-- æŽˆæƒç”¨æˆ· -->
   <el-dialog title="选择用户" v-model="visible" width="800px" top="5vh" append-to-body>
      <el-form :model="queryParams" ref="queryRef" :inline="true">
         <el-form-item label="用户名称" prop="userName">
            <el-input
               v-model="queryParams.userName"
               placeholder="请输入用户名称"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="手机号码" prop="phonenumber">
            <el-input
               v-model="queryParams.phonenumber"
               placeholder="请输入手机号码"
               clearable
               size="small"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row>
         <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px">
            <el-table-column type="selection" width="55"></el-table-column>
            <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
            <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
            <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
            <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
            <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="createTime" width="180">
               <template #default="scope">
                  <span>{{ parseTime(scope.row.createTime) }}</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-row>
      <template #footer>
         <div class="dialog-footer">
            <el-button type="primary" @click="handleSelectUser">ç¡® å®š</el-button>
            <el-button @click="visible = false">取 æ¶ˆ</el-button>
         </div>
      </template>
   </el-dialog>
</template>
<script setup name="SelectUser">
import { authUserSelectAll, unallocatedUserList } from "@/api/system/role";
const props = defineProps({
  roleId: {
    type: [Number, String]
  }
});
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const userList = ref([]);
const visible = ref(false);
const total = ref(0);
const userIds = ref([]);
const queryParams = reactive({
  pageNum: 1,
  pageSize: 10,
  roleId: undefined,
  userName: undefined,
  phonenumber: undefined
});
// æ˜¾ç¤ºå¼¹æ¡†
function show() {
  queryParams.roleId = props.roleId;
  getList();
  visible.value = true;
}
/**选择行 */
function clickRow(row) {
  proxy.$refs["refTable"].toggleRowSelection(row);
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  userIds.value = selection.map(item => item.userId);
}
// æŸ¥è¯¢è¡¨æ•°æ®
function getList() {
  unallocatedUserList(queryParams).then(res => {
    userList.value = res.rows;
    total.value = res.total;
  });
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
const emit = defineEmits(["ok"]);
/** é€‰æ‹©æŽˆæƒç”¨æˆ·æ“ä½œ */
function handleSelectUser() {
  const roleId = queryParams.roleId;
  const uIds = userIds.value.join(",");
  if (uIds == "") {
    proxy.$modal.msgError("请选择要分配的用户");
    return;
  }
  authUserSelectAll({ roleId: roleId, userIds: uIds }).then(res => {
    proxy.$modal.msgSuccess(res.msg);
    if (res.code === 200) {
      visible.value = false;
      emit("ok");
    }
  });
}
defineExpose({
  show,
});
</script>
src/views/system/user/authRole.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
<template>
   <div class="app-container">
      <h4 class="form-header h4">基本信息</h4>
      <el-form :model="form" label-width="80px">
         <el-row>
            <el-col :span="8" :offset="2">
               <el-form-item label="用户昵称" prop="nickName">
                  <el-input v-model="form.nickName" disabled />
               </el-form-item>
            </el-col>
            <el-col :span="8" :offset="2">
               <el-form-item label="登录账号" prop="phonenumber">
                  <el-input v-model="form.userName" disabled />
               </el-form-item>
            </el-col>
         </el-row>
      </el-form>
      <h4 class="form-header h4">角色信息</h4>
      <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
         <el-table-column label="序号" type="index" align="center">
            <template #default="scope">
               <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
            </template>
         </el-table-column>
         <el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
         <el-table-column label="角色编号" align="center" prop="roleId" />
         <el-table-column label="角色名称" align="center" prop="roleName" />
         <el-table-column label="权限字符" align="center" prop="roleKey" />
         <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>
      <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
      <el-form label-width="100px">
         <el-form-item style="text-align: center;margin-left:-120px;margin-top:30px;">
            <el-button type="primary" @click="submitForm()">提交</el-button>
            <el-button @click="close()">返回</el-button>
         </el-form-item>
      </el-form>
   </div>
</template>
<script setup name="AuthRole">
import { getAuthRole, updateAuthRole } from "@/api/system/user";
const route = useRoute();
const { proxy } = getCurrentInstance();
const loading = ref(true);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
const roleIds = ref([]);
const roles = ref([]);
const form = ref({
  nickName: undefined,
  userName: undefined,
  userId: undefined
});
/** å•击选中行数据 */
function clickRow(row) {
  proxy.$refs["roleRef"].toggleRowSelection(row);
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  roleIds.value = selection.map(item => item.roleId);
};
/** ä¿å­˜é€‰ä¸­çš„æ•°æ®ç¼–号 */
function getRowKey(row) {
  return row.roleId;
};
/** å…³é—­æŒ‰é’® */
function close() {
  const obj = { path: "/system/user" };
  proxy.$tab.closeOpenPage(obj);
};
/** æäº¤æŒ‰é’® */
function submitForm() {
  const userId = form.value.userId;
  const rIds = roleIds.value.join(",");
  updateAuthRole({ userId: userId, roleIds: rIds }).then(response => {
    proxy.$modal.msgSuccess("授权成功");
    close();
  });
};
(() => {
  const userId = route.params && route.params.userId;
  if (userId) {
    loading.value = true;
    getAuthRole(userId).then(response => {
      form.value = response.user;
      roles.value = response.roles;
      total.value = roles.value.length;
      nextTick(() => {
        roles.value.forEach(row => {
          if (row.flag) {
            proxy.$refs["roleRef"].toggleRowSelection(row);
          }
        });
      });
      loading.value = false;
    });
  }
})();
</script>
src/views/system/user/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,641 @@
<template>
   <div class="app-container">
      <el-row :gutter="20">
         <!--部门数据-->
         <el-col :span="4" :xs="24">
            <div class="head-container">
               <el-input
                  v-model="deptName"
                  placeholder="请输入部门名称"
                  clearable
                  size="small"
                  prefix-icon="el-icon-search"
                  style="margin-bottom: 20px"
               />
            </div>
            <div class="head-container">
               <el-tree
                  :data="deptOptions"
                  :props="{ label: 'label', children: 'children' }"
                  :expand-on-click-node="false"
                  :filter-node-method="filterNode"
                  ref="deptTreeRef"
                  default-expand-all
                  @node-click="handleNodeClick"
               />
            </div>
         </el-col>
         <!--用户数据-->
         <el-col :span="20" :xs="24">
            <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
               <el-form-item label="用户名称" prop="userName">
                  <el-input
                     v-model="queryParams.userName"
                     placeholder="请输入用户名称"
                     clearable
                     size="small"
                     style="width: 240px"
                     @keyup.enter="handleQuery"
                  />
               </el-form-item>
               <el-form-item label="手机号码" prop="phonenumber">
                  <el-input
                     v-model="queryParams.phonenumber"
                     placeholder="请输入手机号码"
                     clearable
                     size="small"
                     style="width: 240px"
                     @keyup.enter="handleQuery"
                  />
               </el-form-item>
               <el-form-item label="状态" prop="status">
                  <el-select
                     v-model="queryParams.status"
                     placeholder="用户状态"
                     clearable
                     size="small"
                     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="创建时间">
                  <el-date-picker
                     v-model="dateRange"
                     size="small"
                     style="width: 240px"
                     value-format="YYYY-MM-DD"
                     type="daterange"
                     range-separator="-"
                     start-placeholder="开始日期"
                     end-placeholder="结束日期"
                  ></el-date-picker>
               </el-form-item>
               <el-form-item>
                  <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
                  <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
               </el-form-item>
            </el-form>
            <el-row :gutter="10" class="mb8">
               <el-col :span="1.5">
                  <el-button
                     type="primary"
                     plain
                     icon="Plus"
                     size="mini"
                     @click="handleAdd"
                     v-hasPermi="['system:user:add']"
                  >新增</el-button>
               </el-col>
               <el-col :span="1.5">
                  <el-button
                     type="success"
                     plain
                     icon="Edit"
                     size="mini"
                     :disabled="single"
                     @click="handleUpdate"
                     v-hasPermi="['system:user:edit']"
                  >修改</el-button>
               </el-col>
               <el-col :span="1.5">
                  <el-button
                     type="danger"
                     plain
                     icon="Delete"
                     size="mini"
                     :disabled="multiple"
                     @click="handleDelete"
                     v-hasPermi="['system:user:remove']"
                  >删除</el-button>
               </el-col>
               <el-col :span="1.5">
                  <el-button
                     type="info"
                     plain
                     icon="Upload"
                     size="mini"
                     @click="handleImport"
                     v-hasPermi="['system:user:import']"
                  >导入</el-button>
               </el-col>
               <el-col :span="1.5">
                  <el-button
                     type="warning"
                     plain
                     icon="Download"
                     size="mini"
                     @click="handleExport"
                     v-hasPermi="['system:user:export']"
                  >导出</el-button>
               </el-col>
               <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
            </el-row>
            <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
               <el-table-column type="selection" width="50" align="center" />
               <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
               <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
               <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
               <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
               <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
               <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
                  <template #default="scope">
                     <el-switch
                        v-model="scope.row.status"
                        active-value="0"
                        inactive-value="1"
                        @change="handleStatusChange(scope.row)"
                     ></el-switch>
                  </template>
               </el-table-column>
               <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="150">
                  <template #default="scope">
                     <span>{{ parseTime(scope.row.createTime) }}</span>
                  </template>
               </el-table-column>
               <el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-width">
                  <template #default="scope">
                     <el-button
                        size="mini"
                        v-if="scope.row.userId !== 1"
                        type="text"
                        icon="Edit"
                        @click="handleUpdate(scope.row)"
                        v-hasPermi="['system:user:edit']"
                     >修改</el-button>
                     <el-button
                        size="mini"
                        v-if="scope.row.userId !== 1"
                        type="text"
                        icon="Delete"
                        @click="handleDelete(scope.row)"
                        v-hasPermi="['system:user:remove']"
                     >删除</el-button>
                     <el-dropdown v-if="scope.row.userId !== 1" @command="(command) => handleCommand(command, scope.row)">
                        <span class="el-dropdown-link" v-hasPermi="['system:user:resetPwd', 'system:user:edit']">
                           <el-icon><d-arrow-right /></el-icon>更多
                        </span>
                        <template #dropdown>
                           <el-dropdown-menu>
                              <el-dropdown-item command="handleResetPwd" icon="Key"
                                 v-hasPermi="['system:user:resetPwd']">重置密码</el-dropdown-item>
                              <el-dropdown-item command="handleAuthRole" icon="CircleCheck"
                                 v-hasPermi="['system:user:edit']">分配角色</el-dropdown-item>
                           </el-dropdown-menu>
                        </template>
                     </el-dropdown>
                  </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-col>
      </el-row>
      <!-- æ·»åŠ æˆ–ä¿®æ”¹ç”¨æˆ·é…ç½®å¯¹è¯æ¡† -->
      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
         <el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
            <el-row>
               <el-col :span="12">
                  <el-form-item label="用户昵称" prop="nickName">
                     <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="归属部门" prop="deptId">
                     <tree-select
                        v-model:value="form.deptId"
                        :options="deptOptions"
                        placeholder="请选择归属部门"
                        :objMap="{ value: 'id', label: 'label', children: 'children' }"
                     />
                  </el-form-item>
               </el-col>
            </el-row>
            <el-row>
               <el-col :span="12">
                  <el-form-item label="手机号码" prop="phonenumber">
                     <el-input v-model="form.phonenumber" 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-row>
            <el-row>
               <el-col :span="12">
                  <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
                     <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
                     <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
                  </el-form-item>
               </el-col>
            </el-row>
            <el-row>
               <el-col :span="12">
                  <el-form-item label="用户性别">
                     <el-select v-model="form.sex" placeholder="请选择">
                        <el-option
                           v-for="dict in sys_user_sex"
                           :key="dict.value"
                           :label="dict.label"
                           :value="dict.value"
                        ></el-option>
                     </el-select>
                  </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-row>
               <el-col :span="12">
                  <el-form-item label="岗位">
                     <el-select v-model="form.postIds" multiple placeholder="请选择">
                        <el-option
                           v-for="item in postOptions"
                           :key="item.postId"
                           :label="item.postName"
                           :value="item.postId"
                           :disabled="item.status == 1"
                        ></el-option>
                     </el-select>
                  </el-form-item>
               </el-col>
               <el-col :span="12">
                  <el-form-item label="角色">
                     <el-select v-model="form.roleIds" multiple placeholder="请选择">
                        <el-option
                           v-for="item in roleOptions"
                           :key="item.roleId"
                           :label="item.roleName"
                           :value="item.roleId"
                           :disabled="item.status == 1"
                        ></el-option>
                     </el-select>
                  </el-form-item>
               </el-col>
            </el-row>
            <el-row>
               <el-col :span="24">
                  <el-form-item label="备注">
                     <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
                  </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>
      <!-- ç”¨æˆ·å¯¼å…¥å¯¹è¯æ¡† -->
      <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
         >
            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
            <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
            <template #tip>
               <div class="el-upload__tip text-center">
                  <div class="el-upload__tip">
                     <el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
                  </div>
                  <span>仅允许导入xls、xlsx格式文件。</span>
                  <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
               </div>
            </template>
         </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="User">
import { getToken } from "@/utils/auth";
import { treeselect } from "@/api/system/dept";
import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser } from "@/api/system/user";
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex");
const userList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const deptName = ref("");
const deptOptions = ref(undefined);
const initPassword = ref(undefined);
const postOptions = ref([]);
const roleOptions = ref([]);
/*** ç”¨æˆ·å¯¼å…¥å‚æ•° */
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(用户导入)
  open: false,
  // å¼¹å‡ºå±‚标题(用户导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 0,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "system/user/importData"
});
// åˆ—显隐信息
const columns = ref([
  { key: 0, label: `用户编号`, visible: true },
  { key: 1, label: `用户名称`, visible: true },
  { key: 2, label: `用户昵称`, visible: true },
  { key: 3, label: `部门`, visible: true },
  { key: 4, label: `手机号码`, visible: true },
  { key: 5, label: `状态`, visible: true },
  { key: 6, label: `创建时间`, visible: true }
]);
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    userName: undefined,
    phonenumber: undefined,
    status: undefined,
    deptId: undefined
  },
  rules: {
    userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 å’Œ 20 ä¹‹é—´", trigger: "blur" }],
    nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
    password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´", trigger: "blur" }],
    email: [{ type: "email", message: "'请输入正确的邮箱地址", trigger: ["blur", "change"] }],
    phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** é€šè¿‡æ¡ä»¶è¿‡æ»¤èŠ‚ç‚¹  */
const filterNode = (value, data) => {
  if (!value) return true;
  return data.label.indexOf(value) !== -1;
};
/** æ ¹æ®åç§°ç­›é€‰éƒ¨é—¨æ ‘ */
watch(deptName, val => {
  proxy.$refs["deptTreeRef"].filter(val);
});
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
function getTreeselect() {
  treeselect().then(response => {
    deptOptions.value = response.data;
  });
};
/** æŸ¥è¯¢ç”¨æˆ·åˆ—表 */
function getList() {
  loading.value = true;
  listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
    loading.value = false;
    userList.value = res.rows;
    total.value = res.total;
  });
};
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
function handleNodeClick(data) {
  queryParams.value.deptId = data.id;
  getList();
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  handleQuery();
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const userIds = row.userId || ids.value;
  proxy.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function () {
    return delUser(userIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
function handleExport() {
  proxy.download("system/user/export", {
    ...queryParams.value,
  },`user_${new Date().getTime()}.xlsx`);
};
/** ç”¨æˆ·çŠ¶æ€ä¿®æ”¹  */
function handleStatusChange(row) {
  let text = row.status === "0" ? "启用" : "停用";
  proxy.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?').then(function () {
    return changeUserStatus(row.userId, row.status);
  }).then(() => {
    proxy.$modal.msgSuccess(text + "成功");
  }).catch(function () {
    row.status = row.status === "0" ? "1" : "0";
  });
};
/** æ›´å¤šæ“ä½œ */
function handleCommand(command, row) {
  switch (command) {
    case "handleResetPwd":
      handleResetPwd(row);
      break;
    case "handleAuthRole":
      handleAuthRole(row);
      break;
    default:
      break;
  }
};
/** è·³è½¬è§’色分配 */
function handleAuthRole(row) {
  const userId = row.userId;
  router.push("/system/user-auth/role/" + userId);
};
/** é‡ç½®å¯†ç æŒ‰é’®æ“ä½œ */
function handleResetPwd(row) {
  proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    closeOnClickModal: false,
    inputPattern: /^.{5,20}$/,
    inputErrorMessage: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´",
  }).then(({ value }) => {
    resetUserPwd(row.userId, value).then(response => {
      proxy.$modal.msgSuccess("修改成功,新密码是:" + value);
    });
  }).catch(() => {});
};
/** é€‰æ‹©æ¡æ•°  */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.userId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
};
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
  upload.title = "用户导入";
  upload.open = true;
};
/** ä¸‹è½½æ¨¡æ¿æ“ä½œ */
function importTemplate() {
  proxy.download("system/user/importTemplate", {
  }, `user_template_${new Date().getTime()}.xlsx`);
};
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response, file, fileList) => {
  upload.open = false;
  upload.isUploading = false;
  proxy.$refs["uploadRef"].clearFiles();
  proxy.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
  getList();
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  proxy.$refs["uploadRef"].submit();
};
/** åˆå§‹åŒ–部门数据 */
function initTreeData() {
  // åˆ¤æ–­éƒ¨é—¨çš„æ•°æ®æ˜¯å¦å­˜åœ¨ï¼Œå­˜åœ¨ä¸èŽ·å–ï¼Œä¸å­˜åœ¨åˆ™èŽ·å–
  if (deptOptions.value === undefined) {
    treeselect().then(response => {
      deptOptions.value = response.data;
    });
  }
};
/** é‡ç½®æ“ä½œè¡¨å• */
function reset() {
  form.value = {
    userId: undefined,
    deptId: undefined,
    userName: undefined,
    nickName: undefined,
    password: undefined,
    phonenumber: undefined,
    email: undefined,
    sex: undefined,
    status: "0",
    remark: undefined,
    postIds: [],
    roleIds: []
  };
  proxy.resetForm("userRef");
};
/** å–消按钮 */
function cancel() {
  open.value = false;
  reset();
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset();
  initTreeData();
  getUser().then(response => {
    postOptions.value = response.posts;
    roleOptions.value = response.roles;
    open.value = true;
    title.value = "添加用户";
    form.password.value = initPassword.value;
  });
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset();
  initTreeData();
  const userId = row.userId || ids.value;
  getUser(userId).then(response => {
    form.value = response.data;
    postOptions.value = response.posts;
    roleOptions.value = response.roles;
    form.value.postIds = response.postIds;
    form.value.roleIds = response.roleIds;
    open.value = true;
    title.value = "修改用户";
    form.password = "";
  });
};
/** æäº¤æŒ‰é’® */
function submitForm() {
  proxy.$refs["userRef"].validate(valid => {
    if (valid) {
      if (form.value.userId != undefined) {
        updateUser(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          getList();
        });
      } else {
        addUser(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
};
getTreeselect();
getList();
</script>
src/views/system/user/profile/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
<template>
   <div class="app-container">
      <el-row :gutter="20">
         <el-col :span="6" :xs="24">
            <el-card class="box-card">
               <template v-slot:header>
                 <div class="clearfix">
                   <span>个人信息</span>
                 </div>
               </template>
               <div>
                  <div class="text-center">
                     <userAvatar :user="state.user" />
                  </div>
                  <ul class="list-group list-group-striped">
                     <li class="list-group-item">
                        <svg-icon icon-class="user" />用户名称
                        <div class="pull-right">{{ state.user.userName }}</div>
                     </li>
                     <li class="list-group-item">
                        <svg-icon icon-class="phone" />手机号码
                        <div class="pull-right">{{ state.user.phonenumber }}</div>
                     </li>
                     <li class="list-group-item">
                        <svg-icon icon-class="email" />用户邮箱
                        <div class="pull-right">{{ state.user.email }}</div>
                     </li>
                     <li class="list-group-item">
                        <svg-icon icon-class="tree" />所属部门
                        <div class="pull-right" v-if="state.user.dept">{{ state.user.dept.deptName }} / {{ state.postGroup }}</div>
                     </li>
                     <li class="list-group-item">
                        <svg-icon icon-class="peoples" />所属角色
                        <div class="pull-right">{{ state.roleGroup }}</div>
                     </li>
                     <li class="list-group-item">
                        <svg-icon icon-class="date" />创建日期
                        <div class="pull-right">{{ state.user.createTime }}</div>
                     </li>
                  </ul>
               </div>
            </el-card>
         </el-col>
         <el-col :span="18" :xs="24">
            <el-card>
               <template v-slot:header>
                 <div class="clearfix">
                   <span>基本资料</span>
                 </div>
               </template>
               <el-tabs v-model="activeTab">
                  <el-tab-pane label="基本资料" name="userinfo">
                     <userInfo :user="state.user" />
                  </el-tab-pane>
                  <el-tab-pane label="修改密码" name="resetPwd">
                     <resetPwd :user="state.user" />
                  </el-tab-pane>
               </el-tabs>
            </el-card>
         </el-col>
      </el-row>
   </div>
</template>
<script setup name="Profile">
import userAvatar from "./userAvatar";
import userInfo from "./userInfo";
import resetPwd from "./resetPwd";
import { getUserProfile } from "@/api/system/user";
const activeTab = ref("userinfo");
const state = reactive({
  user: {},
  roleGroup: {},
  postGroup: {}
});
function getUser() {
  getUserProfile().then(response => {
    state.user = response.data;
    state.roleGroup = response.roleGroup;
    state.postGroup = response.postGroup;
  });
};
getUser();
</script>
src/views/system/user/profile/resetPwd.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
<template>
   <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
      <el-form-item label="旧密码" prop="oldPassword">
         <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
      </el-form-item>
      <el-form-item label="新密码" prop="newPassword">
         <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
      </el-form-item>
      <el-form-item label="确认密码" prop="confirmPassword">
         <el-input v-model="user.confirmPassword" placeholder="请确认密码" type="password" show-password/>
      </el-form-item>
      <el-form-item>
      <el-button type="primary" size="mini" @click="submit">保存</el-button>
      <el-button type="danger" size="mini" @click="close">关闭</el-button>
      </el-form-item>
   </el-form>
</template>
<script setup>
import { updateUserPwd } from "@/api/system/user";
const { proxy } = getCurrentInstance();
const user = reactive({
  oldPassword: undefined,
  newPassword: undefined,
  confirmPassword: undefined
});
const equalToPassword = (rule, value, callback) => {
  if (user.newPassword !== value) {
    callback(new Error("两次输入的密码不一致"));
  } else {
    callback();
  }
};
const rules = ref({
  oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
  newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 åˆ° 20 ä¸ªå­—符", trigger: "blur" }],
  confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
});
/** æäº¤æŒ‰é’® */
function submit() {
  proxy.$refs.pwdRef.validate(valid => {
    if (valid) {
      updateUserPwd(user.oldPassword, user.newPassword).then(response => {
        proxy.$modal.msgSuccess("修改成功");
      });
    }
  });
};
/** å…³é—­æŒ‰é’® */
function close() {
  proxy.$tab.closePage();
};
</script>
src/views/system/user/profile/userAvatar.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
<template>
  <div class="user-info-head" @click="editCropper()"><img :src="options.img" title="点击上传头像" class="img-circle img-lg" /></div>
  <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened"  @close="closeDialog">
    <el-row>
      <el-col :xs="24" :md="12" :style="{height: '350px'}">
        <vue-cropper
            ref="cropper"
            :img="options.img"
            :info="true"
            :autoCrop="options.autoCrop"
            :autoCropWidth="options.autoCropWidth"
            :autoCropHeight="options.autoCropHeight"
            :fixedBox="options.fixedBox"
            @realTime="realTime"
            v-if="visible"
        />
      </el-col>
      <el-col :xs="24" :md="12" :style="{height: '350px'}">
        <div class="avatar-upload-preview">
          <img :src="options.previews.url" :style="options.previews.img"/>
        </div>
      </el-col>
    </el-row>
    <br/>
    <el-row>
      <el-col :lg="2" :md="2">
        <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
          <el-button size="small">
            é€‰æ‹©
            <el-icon class="el-icon--right"><Upload /></el-icon>
          </el-button>
        </el-upload>
      </el-col>
      <el-col :lg="{span: 1, offset: 2}" :md="2">
        <el-button icon="Plus" size="small" @click="changeScale(1)"></el-button>
      </el-col>
      <el-col :lg="{span: 1, offset: 1}" :md="2">
        <el-button icon="Minus" size="small" @click="changeScale(-1)"></el-button>
      </el-col>
      <el-col :lg="{span: 1, offset: 1}" :md="2">
        <el-button icon="RefreshLeft" size="small" @click="rotateLeft()"></el-button>
      </el-col>
      <el-col :lg="{span: 1, offset: 1}" :md="2">
        <el-button icon="RefreshRight" size="small" @click="rotateRight()"></el-button>
      </el-col>
      <el-col :lg="{span: 2, offset: 6}" :md="2">
        <el-button type="primary" size="small" @click="uploadImg()">提 äº¤</el-button>
      </el-col>
    </el-row>
  </el-dialog>
</template>
<script setup>
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
const store = useStore();
const { proxy } = getCurrentInstance();
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
//图片裁剪数据
const options = reactive({
  img: store.getters.avatar, // è£å‰ªå›¾ç‰‡çš„地址
  autoCrop: true, // æ˜¯å¦é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†
  autoCropWidth: 200, // é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†å®½åº¦
  autoCropHeight: 200, // é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†é«˜åº¦
  fixedBox: true, // å›ºå®šæˆªå›¾æ¡†å¤§å° ä¸å…è®¸æ”¹å˜
  previews: {} //预览数据
});
/** ç¼–辑头像 */
function editCropper() {
  open.value = true;
};
/** æ‰“开弹出层结束时的回调 */
function modalOpened() {
  visible.value = true;
};
/** è¦†ç›–默认上传行为 */
function requestUpload() {
};
/** å‘左旋转 */
function rotateLeft() {
  proxy.$refs.cropper.rotateLeft();
};
/** å‘右旋转 */
function rotateRight() {
  proxy.$refs.cropper.rotateRight();
};
/** å›¾ç‰‡ç¼©æ”¾ */
function changeScale(num) {
  num = num || 1;
  proxy.$refs.cropper.changeScale(num);
};
/** ä¸Šä¼ é¢„处理 */
function beforeUpload(file) {
  if (file.type.indexOf("image/") == -1) {
    proxy.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
  } else {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
      options.img = reader.result;
    };
  }
};
/** ä¸Šä¼ å›¾ç‰‡ */
function uploadImg() {
  proxy.$refs.cropper.getCropBlob(data => {
    let formData = new FormData();
    formData.append("avatarfile", data);
    uploadAvatar(formData).then(response => {
      open.value = false;
      options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl;
      store.commit("SET_AVATAR", options.img);
      proxy.$modal.msgSuccess("修改成功");
      visible.value = false;
    });
  });
};
/** å®žæ—¶é¢„览 */
function realTime(data) {
  options.previews = data;
};
/** å…³é—­çª—口 */
function closeDialog() {
  options.img = store.getters.avatar;
  options.visible = false;
};
</script>
<style lang='scss' scoped>
.user-info-head {
  position: relative;
  display: inline-block;
  height: 120px;
}
.user-info-head:hover:after {
  content: "+";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  color: #eee;
  background: rgba(0, 0, 0, 0.5);
  font-size: 24px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  cursor: pointer;
  line-height: 110px;
  border-radius: 50%;
}
</style>
src/views/system/user/profile/userInfo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
<template>
   <el-form ref="userRef" :model="user" :rules="rules" label-width="80px">
      <el-form-item label="用户昵称" prop="nickName">
         <el-input v-model="user.nickName" maxlength="30" />
      </el-form-item>
      <el-form-item label="手机号码" prop="phonenumber">
         <el-input v-model="user.phonenumber" maxlength="11" />
      </el-form-item>
      <el-form-item label="邮箱" prop="email">
         <el-input v-model="user.email" maxlength="50" />
      </el-form-item>
      <el-form-item label="性别">
         <el-radio-group v-model="user.sex">
            <el-radio label="0">男</el-radio>
            <el-radio label="1">女</el-radio>
         </el-radio-group>
      </el-form-item>
      <el-form-item>
      <el-button type="primary" size="mini" @click="submit">保存</el-button>
      <el-button type="danger" size="mini" @click="close">关闭</el-button>
      </el-form-item>
   </el-form>
</template>
<script setup>
import { updateUserProfile } from "@/api/system/user";
const props = defineProps({
  user: {
    type: Object
  }
});
const { proxy } = getCurrentInstance();
const rules = ref({
  nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
  email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "'请输入正确的邮箱地址", trigger: ["blur", "change"] }],
  phonenumber: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
});
/** æäº¤æŒ‰é’® */
function submit() {
  proxy.$refs.userRef.validate(valid => {
    if (valid) {
      updateUserProfile(props.user).then(response => {
        proxy.$modal.msgSuccess("修改成功");
      });
    }
  });
};
/** å…³é—­æŒ‰é’® */
function close() {
  proxy.$tab.closePage();
};
</script>
src/views/tool/build/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
<template>
   <div> è¡¨å•构建 <svg-icon icon-class="build" /> </div>
</template>
src/views/tool/gen/basicInfoForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
<template>
  <el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
    <el-row>
      <el-col :span="12">
        <el-form-item label="表名称" prop="tableName">
          <el-input placeholder="请输入仓库名称" v-model="info.tableName" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="表描述" prop="tableComment">
          <el-input placeholder="请输入" v-model="info.tableComment" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="实体类名称" prop="className">
          <el-input placeholder="请输入" v-model="info.className" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="作者" prop="functionAuthor">
          <el-input placeholder="请输入" v-model="info.functionAuthor" />
        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="备注" prop="remark">
          <el-input type="textarea" :rows="3" v-model="info.remark"></el-input>
        </el-form-item>
      </el-col>
    </el-row>
  </el-form>
</template>
<script setup>
defineProps({
  info: {
    type: Object,
    default: null
  }
});
// è¡¨å•校验
const rules = ref({
  tableName: [{ required: true, message: "请输入表名称", trigger: "blur" }],
  tableComment: [{ required: true, message: "请输入表描述", trigger: "blur" }],
  className: [{ required: true, message: "请输入实体类名称", trigger: "blur" }],
  functionAuthor: [{ required: true, message: "请输入作者", trigger: "blur" }]
});
</script>
src/views/tool/gen/editTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,197 @@
<template>
  <el-card>
    <el-tabs v-model="activeName">
      <el-tab-pane label="基本信息" name="basic">
        <basic-info-form ref="basicInfo" :info="info" />
      </el-tab-pane>
      <el-tab-pane label="字段信息" name="columnInfo">
        <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
          <el-table-column label="序号" type="index" min-width="5%"/>
          <el-table-column
            label="字段列名"
            prop="columnName"
            min-width="10%"
            :show-overflow-tooltip="true"
          />
          <el-table-column label="字段描述" min-width="10%">
            <template #default="scope">
              <el-input v-model="scope.row.columnComment"></el-input>
            </template>
          </el-table-column>
          <el-table-column
            label="物理类型"
            prop="columnType"
            min-width="10%"
            :show-overflow-tooltip="true"
          />
          <el-table-column label="Java类型" min-width="11%">
            <template #default="scope">
              <el-select v-model="scope.row.javaType">
                <el-option label="Long" value="Long" />
                <el-option label="String" value="String" />
                <el-option label="Integer" value="Integer" />
                <el-option label="Double" value="Double" />
                <el-option label="BigDecimal" value="BigDecimal" />
                <el-option label="Date" value="Date" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="java属性" min-width="10%">
            <template #default="scope">
              <el-input v-model="scope.row.javaField"></el-input>
            </template>
          </el-table-column>
          <el-table-column label="插入" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" v-model="scope.row.isInsert"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="编辑" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" v-model="scope.row.isEdit"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="列表" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" v-model="scope.row.isList"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="查询" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" v-model="scope.row.isQuery"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="查询方式" min-width="10%">
            <template #default="scope">
              <el-select v-model="scope.row.queryType">
                <el-option label="=" value="EQ" />
                <el-option label="!=" value="NE" />
                <el-option label=">" value="GT" />
                <el-option label=">=" value="GTE" />
                <el-option label="<" value="LT" />
                <el-option label="<=" value="LTE" />
                <el-option label="LIKE" value="LIKE" />
                <el-option label="BETWEEN" value="BETWEEN" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="必填" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" v-model="scope.row.isRequired"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="显示类型" min-width="12%">
            <template #default="scope">
              <el-select v-model="scope.row.htmlType">
                <el-option label="文本框" value="input" />
                <el-option label="文本域" value="textarea" />
                <el-option label="下拉框" value="select" />
                <el-option label="单选框" value="radio" />
                <el-option label="复选框" value="checkbox" />
                <el-option label="日期控件" value="datetime" />
                <el-option label="图片上传" value="imageUpload" />
                <el-option label="文件上传" value="fileUpload" />
                <el-option label="富文本控件" value="editor" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="字典类型" min-width="12%">
            <template #default="scope">
              <el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
                <el-option
                  v-for="dict in dictOptions"
                  :key="dict.dictType"
                  :label="dict.dictName"
                  :value="dict.dictType">
                  <span style="float: left">{{ dict.dictName }}</span>
                  <span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
              </el-option>
              </el-select>
            </template>
          </el-table-column>
        </el-table>
      </el-tab-pane>
      <el-tab-pane label="生成信息" name="genInfo">
        <gen-info-form ref="genInfo" :info="info" :tables="tables" />
      </el-tab-pane>
    </el-tabs>
    <el-form label-width="100px">
      <el-form-item style="text-align: center;margin-left:-100px;margin-top:10px;">
        <el-button type="primary" @click="submitForm()">提交</el-button>
        <el-button @click="close()">返回</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>
<script setup name="GenEdit">
import { getGenTable, updateGenTable } from "@/api/tool/gen";
import { optionselect as getDictOptionselect } from "@/api/system/dict/type";
import basicInfoForm from "./basicInfoForm";
import genInfoForm from "./genInfoForm";
const route = useRoute();
const { proxy } = getCurrentInstance();
const activeName = ref("columnInfo");
const tableHeight = ref(document.documentElement.scrollHeight - 245 + "px");
const tables = ref([]);
const columns = ref([]);
const dictOptions = ref([]);
const info = ref({});
/** æäº¤æŒ‰é’® */
function submitForm() {
  const basicForm = proxy.$refs.basicInfo.$refs.basicInfoForm;
  const genForm = proxy.$refs.genInfo.$refs.genInfoForm;
  Promise.all([basicForm, genForm].map(getFormPromise)).then(res => {
    const validateResult = res.every(item => !!item);
    if (validateResult) {
      const genTable = Object.assign({}, basicForm.model, genForm.model);
      genTable.columns = columns.value;
      genTable.params = {
        treeCode: genTable.treeCode,
        treeName: genTable.treeName,
        treeParentCode: genTable.treeParentCode,
        parentMenuId: genTable.parentMenuId
      };
      updateGenTable(genTable).then(res => {
        proxy.$modal.msgSuccess(res.msg);
        if (res.code === 200) {
          close();
        }
      });
    } else {
      proxy.$modal.msgError("表单校验未通过,请重新检查提交内容");
    }
  });
}
function getFormPromise(form) {
  return new Promise(resolve => {
    form.validate(res => {
      resolve(res);
    });
  });
}
function close() {
  const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: route.query.pageNum } };
  proxy.$tab.closeOpenPage(obj);
}
(() => {
  const tableId = route.query && route.query.tableId;
  if (tableId) {
    // èŽ·å–è¡¨è¯¦ç»†ä¿¡æ¯
    getGenTable(tableId).then(res => {
      columns.value = res.data.rows;
      info.value = res.data.info;
      tables.value = res.data.tables;
    });
    /** æŸ¥è¯¢å­—典下拉列表 */
    getDictOptionselect().then(response => {
      dictOptions.value = response.data;
    });
  }
})();
</script>
src/views/tool/gen/genInfoForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,281 @@
<template>
  <el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
    <el-row>
      <el-col :span="12">
        <el-form-item prop="tplCategory">
          <template #label>生成模板</template>
          <el-select v-model="info.tplCategory" @change="tplSelectChange">
            <el-option label="单表(增删改查)" value="crud" />
            <el-option label="树表(增删改查)" value="tree" />
            <el-option label="主子表(增删改查)" value="sub" />
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item prop="packageName">
          <template #label>
            ç”ŸæˆåŒ…路径
            <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <el-input v-model="info.packageName" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item prop="moduleName">
          <template #label>
            ç”Ÿæˆæ¨¡å—名
            <el-tooltip content="可理解为子系统名,例如 system" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <el-input v-model="info.moduleName" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item prop="businessName">
          <template #label>
            ç”Ÿæˆä¸šåŠ¡å
            <el-tooltip content="可理解为功能英文名,例如 user" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <el-input v-model="info.businessName" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item prop="functionName">
          <template #label>
            ç”ŸæˆåŠŸèƒ½å
            <el-tooltip content="用作类描述,例如 ç”¨æˆ·" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <el-input v-model="info.functionName" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item>
          <template #label>
            ä¸Šçº§èœå•
            <el-tooltip content="分配到指定菜单下,例如 ç³»ç»Ÿç®¡ç†" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <tree-select
            v-model:value="info.parentMenuId"
            :options="menuOptions"
            :objMap="{ value: 'menuId', label: 'menuName', children: 'children' }"
            placeholder="请选择系统菜单"
          />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item prop="genType">
          <template #label>
            ç”Ÿæˆä»£ç æ–¹å¼
            <el-tooltip content="默认为zip压缩包下载,也可以自定义生成路径" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <el-radio v-model="info.genType" label="0">zip压缩包</el-radio>
          <el-radio v-model="info.genType" label="1">自定义路径</el-radio>
        </el-form-item>
      </el-col>
      <el-col :span="24" v-if="info.genType == '1'">
        <el-form-item prop="genPath">
          <template #label>
            è‡ªå®šä¹‰è·¯å¾„
            <el-tooltip content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" placement="top">
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
          <el-input v-model="info.genPath">
            <template #append>
              <el-dropdown>
                <el-button type="primary">
                  æœ€è¿‘路径快速选择
                  <i class="el-icon-arrow-down el-icon--right"></i>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item @click="info.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
            </template>
          </el-input>
        </el-form-item>
      </el-col>
    </el-row>
    <template v-if="info.tplCategory == 'tree'">
      <h4 class="form-header">其他信息</h4>
      <el-row v-show="info.tplCategory == 'tree'">
        <el-col :span="12">
          <el-form-item>
            <template #label>
              æ ‘编码字段
              <el-tooltip content="树显示的编码字段名, å¦‚:dept_id" placement="top">
                <i class="el-icon-question"></i>
              </el-tooltip>
            </template>
            <el-select v-model="info.treeCode" placeholder="请选择">
              <el-option
                v-for="(column, index) in info.columns"
                :key="index"
                :label="column.columnName + ':' + column.columnComment"
                :value="column.columnName"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item>
            <template #label>
              æ ‘父编码字段
              <el-tooltip content="树显示的父编码字段名, å¦‚:parent_Id" placement="top">
                <i class="el-icon-question"></i>
              </el-tooltip>
            </template>
            <el-select v-model="info.treeParentCode" placeholder="请选择">
              <el-option
                v-for="(column, index) in info.columns"
                :key="index"
                :label="column.columnName + ':' + column.columnComment"
                :value="column.columnName"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item>
            <template #label>
              æ ‘名称字段
              <el-tooltip content="树节点的显示名称字段名, å¦‚:dept_name" placement="top">
                <i class="el-icon-question"></i>
              </el-tooltip>
            </template>
            <el-select v-model="info.treeName" placeholder="请选择">
              <el-option
                v-for="(column, index) in info.columns"
                :key="index"
                :label="column.columnName + ':' + column.columnComment"
                :value="column.columnName"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
    </template>
    <template v-if="info.tplCategory == 'sub'">
      <h4 class="form-header">关联信息</h4>
      <el-row>
        <el-col :span="12">
          <el-form-item>
            <template #label>
              å…³è”子表的表名
              <el-tooltip content="关联子表的表名, å¦‚:sys_user" placement="top">
                <i class="el-icon-question"></i>
              </el-tooltip>
            </template>
            <el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
              <el-option
                v-for="(table, index) in tables"
                :key="index"
                :label="table.tableName + ':' + table.tableComment"
                :value="table.tableName"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item>
            <template #label>
              å­è¡¨å…³è”的外键名
              <el-tooltip content="子表关联的外键名, å¦‚:user_id" placement="top">
                <i class="el-icon-question"></i>
              </el-tooltip>
            </template>
            <el-select v-model="info.subTableFkName" placeholder="请选择">
              <el-option
                v-for="(column, index) in subColumns"
                :key="index"
                :label="column.columnName + ':' + column.columnComment"
                :value="column.columnName"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
    </template>
  </el-form>
</template>
<script setup>
import { listMenu } from "@/api/system/menu";
const subColumns = ref([]);
const menuOptions = ref({});
const { proxy } = getCurrentInstance();
const props = defineProps({
  info: {
    type: Object,
    default: null
  },
  tables: {
    type: Array,
    default: null
  }
});
// è¡¨å•校验
const rules = ref({
  tplCategory: [{ required: true, message: "请选择生成模板", trigger: "blur" }],
  packageName: [{ required: true, message: "请输入生成包路径", trigger: "blur" }],
  moduleName: [{ required: true, message: "请输入生成模块名", trigger: "blur" }],
  businessName: [{ required: true, message: "请输入生成业务名", trigger: "blur" }],
  functionName: [{ required: true, message: "请输入生成功能名", trigger: "blur" }]
});
function subSelectChange(value) {
  props.info.subTableFkName = "";
}
function tplSelectChange(value) {
  if (value !== "sub") {
    props.info.subTableName = "";
    props.info.subTableFkName = "";
  }
}
function setSubTableColumns(value) {
  for (var item in props.tables) {
    const name = props.tables[item].tableName;
    if (value === name) {
      subColumns.value = props.tables[item].columns;
      break;
    }
  }
}
/** æŸ¥è¯¢èœå•下拉树结构 */
function getMenuTreeselect() {
  listMenu().then(response => {
    menuOptions.value = proxy.handleTree(response.data, "menuId");
  });
}
watch(() => props.info.subTableName, val => {
  setSubTableColumns(val);
});
getMenuTreeselect();
</script>
src/views/tool/gen/importTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,120 @@
<template>
  <!-- å¯¼å…¥è¡¨ -->
  <el-dialog title="导入表" v-model="visible" width="800px" top="5vh" append-to-body>
    <el-form :model="queryParams" ref="queryRef" :inline="true">
      <el-form-item label="表名称" prop="tableName">
        <el-input
          v-model="queryParams.tableName"
          placeholder="请输入表名称"
          clearable
          size="small"
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="表描述" prop="tableComment">
        <el-input
          v-model="queryParams.tableComment"
          placeholder="请输入表描述"
          clearable
          size="small"
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row>
      <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column prop="createTime" label="创建时间"></el-table-column>
        <el-table-column prop="updateTime" label="更新时间"></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-row>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="handleImportTable">ç¡® å®š</el-button>
        <el-button @click="visible = false">取 æ¶ˆ</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { listDbTable, importTable } from "@/api/tool/gen";
const total = ref(0);
const visible = ref(false);
const tables = ref([]);
const dbTableList = ref([]);
const { proxy } = getCurrentInstance();
const queryParams = reactive({
  pageNum: 1,
  pageSize: 10,
  tableName: undefined,
  tableComment: undefined
});
const emit = defineEmits(["ok"]);
/** æŸ¥è¯¢å‚数列表 */
function show() {
  getList();
  visible.value = true;
}
/** å•击选择行 */
function clickRow(row) {
  proxy.$refs.table.toggleRowSelection(row);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
function handleSelectionChange(selection) {
  tables.value = selection.map(item => item.tableName);
}
/** æŸ¥è¯¢è¡¨æ•°æ® */
function getList() {
  listDbTable(queryParams).then(res => {
    dbTableList.value = res.rows;
    total.value = res.total;
  });
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImportTable() {
  const tableNames = tables.value.join(",");
  if (tableNames == "") {
    proxy.$modal.msgError("请选择要导入的表");
    return;
  }
  importTable({ tables: tableNames }).then(res => {
    proxy.$modal.msgSuccess(res.msg);
    if (res.code === 200) {
      visible.value = false;
      emit("ok");
    }
  });
}
defineExpose({
  show,
});
</script>
src/views/tool/gen/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,292 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="表名称" prop="tableName">
        <el-input
          v-model="queryParams.tableName"
          placeholder="请输入表名称"
          clearable
          size="small"
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="表描述" prop="tableComment">
        <el-input
          v-model="queryParams.tableComment"
          placeholder="请输入表描述"
          clearable
          size="small"
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="创建时间">
        <el-date-picker
          v-model="dateRange"
          size="small"
          style="width: 240px"
          value-format="YYYY-MM-DD"
          type="daterange"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
        ></el-date-picker>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="Search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="Download"
          size="mini"
          @click="handleGenTable"
          v-hasPermi="['tool:gen:code']"
        >生成</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="info"
          plain
          icon="Upload"
          size="mini"
          @click="openImportTable"
          v-hasPermi="['tool:gen:import']"
        >导入</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="Edit"
          size="mini"
          :disabled="single"
          @click="handleEditTable"
          v-hasPermi="['tool:gen:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="Delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['tool:gen:remove']"
        >删除</el-button>
      </el-col>
      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" align="center" width="55"></el-table-column>
      <el-table-column label="序号" type="index" width="50" 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="tableName"
        :show-overflow-tooltip="true"
      />
      <el-table-column
        label="表描述"
        align="center"
        prop="tableComment"
        :show-overflow-tooltip="true"
      />
      <el-table-column
        label="实体"
        align="center"
        prop="className"
        :show-overflow-tooltip="true"
      />
      <el-table-column label="创建时间" align="center" prop="createTime" width="150" />
      <el-table-column label="更新时间" align="center" prop="updateTime" width="150" />
      <el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button
            type="text"
            size="small"
            icon="View"
            @click="handlePreview(scope.row)"
            v-hasPermi="['tool:gen:preview']"
          >预览</el-button>
          <el-button
            type="text"
            size="small"
            icon="Edit"
            @click="handleEditTable(scope.row)"
            v-hasPermi="['tool:gen:edit']"
          >编辑</el-button>
          <el-button
            type="text"
            size="small"
            icon="Delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['tool:gen:remove']"
          >删除</el-button>
          <el-button
            type="text"
            size="small"
            icon="Refresh"
            @click="handleSynchDb(scope.row)"
            v-hasPermi="['tool:gen:edit']"
          >同步</el-button>
          <el-button
            type="text"
            size="small"
            icon="Download"
            @click="handleGenTable(scope.row)"
            v-hasPermi="['tool:gen:code']"
          >生成代码</el-button>
        </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-dialog :title="preview.title" v-model="preview.open" width="80%" top="5vh" append-to-body class="scrollbar">
      <el-tabs v-model="preview.activeName">
        <el-tab-pane
          v-for="(value, key) in preview.data"
          :label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
          :name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
          :key="key"
        >
        <pre><code class="hljs" v-html="value"></code></pre>
        </el-tab-pane>
      </el-tabs>
    </el-dialog>
    <import-table ref="importRef" @ok="handleQuery" />
  </div>
</template>
<script setup name="Gen">
import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen";
import router from "@/router";
import importTable from "./importTable";
const route = useRoute();
const { proxy } = getCurrentInstance();
const tableList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const tableNames = ref([]);
const dateRange = ref([]);
const data = reactive({
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    tableName: undefined,
    tableComment: undefined
  },
  preview: {
    open: false,
    title: "代码预览",
    data: {},
    activeName: "domain.java"
  }
});
const { queryParams, preview } = toRefs(data);
/** æŸ¥è¯¢è¡¨é›†åˆ */
function getList() {
  loading.value = true;
  listTable(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    tableList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}
/** ç”Ÿæˆä»£ç æ“ä½œ */
function handleGenTable(row) {
  const tbNames = row.tableName || tableNames.value;
  if (tbNames == "") {
    proxy.$modal.msgError("请选择要生成的数据");
    return;
  }
  if (row.genType === "1") {
    genCode(row.tableName).then(response => {
      proxy.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath);
    });
  } else {
    proxy.$download.zip("/tool/gen/batchGenCode?tables=" + tbNames, "ruoyi");
  }
}
/** åŒæ­¥æ•°æ®åº“操作 */
function handleSynchDb(row) {
  const tableName = row.tableName;
  proxy.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function () {
    return synchDb(tableName);
  }).then(() => {
    proxy.$modal.msgSuccess("同步成功");
  }).catch(() => {});
}
/** æ‰“开导入表弹窗 */
function openImportTable() {
  proxy.$refs["importRef"].show();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  dateRange.value = [];
  proxy.resetForm("queryRef");
  handleQuery();
}
/** é¢„览按钮 */
function handlePreview(row) {
  previewTable(row.tableId).then(response => {
    preview.value.data = response.data;
    preview.value.open = true;
    preview.value.activeName = "domain.java";
  });
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.tableId);
  tableNames.value = selection.map(item => item.tableName);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleEditTable(row) {
  const tableId = row.tableId || ids.value[0];
  router.push({ path: "/tool/gen-edit/index", query: { tableId: tableId, pageNum: queryParams.value.pageNum } });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const tableIds = row.tableId || ids.value;
  proxy.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?').then(function () {
    return delTable(tableIds);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}
getList();
</script>
src/views/tool/swagger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
<template>
   <i-frame v-model:src="url"></i-frame>
</template>
<script setup>
import iFrame from '@/components/iFrame'
const url = ref(import.meta.env.VITE_APP_BASE_API + "/swagger-ui/index.html")
</script>
vite.config.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
import { defineConfig, loadEnv } from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
  const env = loadEnv(mode, process.cwd())
  return {
    plugins: createVitePlugins(env, command === 'build'),
    resolve: {
      // https://cn.vitejs.dev/config/#resolve-alias
      alias: {
        // è®¾ç½®è·¯å¾„
        '~': path.resolve(__dirname, './'),
        // è®¾ç½®åˆ«å
        '@': path.resolve(__dirname, './src')
      },
      // https://cn.vitejs.dev/config/#resolve-extensions
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
    },
    // vite ç›¸å…³é…ç½®
    server: {
      port: 80,
      open: true,
      proxy: {
        // https://cn.vitejs.dev/config/#server-proxy
        '/dev-api': {
          target: 'http://localhost:8080',
          changeOrigin: true,
          rewrite: (p) => p.replace(/^\/dev-api/, '')
        }
      },
    },
  }
})
vite/plugins/auto-import.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
import autoImport from 'unplugin-auto-import/vite'
export default function createAutoImport() {
    return autoImport({
        imports: [
            'vue',
            'vue-router',
            {
                'vuex': ['useStore']
            }
        ],
        dts: false
    })
}
vite/plugins/compression.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import compression from 'vite-plugin-compression'
export default function createCompression(env) {
    const { VITE_BUILD_COMPRESS } = env
    const compressList = VITE_BUILD_COMPRESS.split(',')
    const plugin = []
    if (compressList.includes('gzip')) {
        plugin.push(
            compression({
                ext: '.gz',
                deleteOriginFile: false
            })
        )
    }
    if (compressList.includes('brotli')) {
        plugin.push(
            compression({
                ext: '.br',
                algorithm: 'brotliCompress',
                deleteOriginFile: false
            })
        )
    }
    return plugin
}
vite/plugins/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
import vue from '@vitejs/plugin-vue'
import createAutoImport from './auto-import'
import createSvgIcon from './svg-icon'
import createCompression from './compression'
import createSetupExtend from './setup-extend'
export default function createVitePlugins(viteEnv, isBuild = false) {
    const vitePlugins = [vue()]
    vitePlugins.push(createAutoImport())
    vitePlugins.push(createSetupExtend())
    vitePlugins.push(createSvgIcon(isBuild))
    isBuild && vitePlugins.push(...createCompression(viteEnv))
    return vitePlugins
}
vite/plugins/setup-extend.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
import setupExtend from 'vite-plugin-vue-setup-extend'
export default function createSetupExtend() {
    return setupExtend()
}
vite/plugins/svg-icon.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import svgIcon from 'vite-plugin-svg-icons'
import path from 'path'
export default function createSvgIcon(isBuild) {
    return svgIcon({
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')],
        symbolId: 'icon-[dir]-[name]',
        svgoOptions: isBuild
    })
}