zhouyulong 3 weeks ago
parent
commit
fc5edb7578
  1. 4
      .env.development
  2. 4
      .env.production
  3. 4
      .env.staging
  4. 2
      .github/FUNDING.yml
  5. 2
      LICENSE
  6. 24
      README.md
  7. 10
      package.json
  8. 2
      src/api/system/user.js
  9. 2
      src/assets/styles/index.scss
  10. 308
      src/assets/styles/ruoyi.scss
  11. 402
      src/components/FileUpload/index.vue
  12. 375
      src/components/HeaderSearch/index.vue
  13. 404
      src/components/ImageUpload/index.vue
  14. 8
      src/components/RuoYi/Doc/index.vue
  15. 10
      src/components/RuoYi/Git/index.vue
  16. 2
      src/directive/common/copyText.js
  17. 8
      src/directive/permission/hasPermi.js
  18. 8
      src/directive/permission/hasRole.js
  19. 389
      src/layout/components/Navbar.vue
  20. 136
      src/layout/components/Sidebar/SidebarItem.vue
  21. 566
      src/layout/components/TagsView/index.vue
  22. 2
      src/main.js
  23. 2
      src/plugins/download.js
  24. 4
      src/router/index.js
  25. 4
      src/settings.js
  26. 18
      src/utils/index.js
  27. 72
      src/utils/request.js
  28. 228
      src/utils/ruoyi.js
  29. 1160
      src/views/index.vue
  30. 508
      src/views/monitor/job/index.vue
  31. 171
      src/views/tool/gen/editTable.vue
  32. 165
      src/views/tool/gen/genInfoForm.vue
  33. 398
      src/views/tool/gen/index.vue
  34. 11
      vite.config.js
  35. 2
      vite/plugins/compression.js

4
.env.development

@ -1,8 +1,8 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 美美学堂管理系统
# 开发环境配置 # 开发环境配置
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
# 若依管理系统/开发环境 # 美美学堂管理系统/开发环境
VITE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'

4
.env.production

@ -1,10 +1,10 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 美美学堂管理系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'production' VITE_APP_ENV = 'production'
# 若依管理系统/生产环境 # 美美学堂管理系统/生产环境
VITE_APP_BASE_API = '/prod-api' VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli

4
.env.staging

@ -1,10 +1,10 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 美美学堂管理系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'staging' VITE_APP_ENV = 'staging'
# 若依管理系统/生产环境 # 美美学堂管理系统/生产环境
VITE_APP_BASE_API = '/stage-api' VITE_APP_BASE_API = '/stage-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli

2
.github/FUNDING.yml

@ -1 +1 @@
custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html custom: http://doc.mmxt.vip/mmxt-vue/other/donate.html

2
LICENSE

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2018 RuoYi Copyright (c) 2018 mmxt
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

24
README.md

@ -1,29 +1,29 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.1</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">mmxt v3.9.1</h1>
<h4 align="center">基于SpringBoot+Vue3前后端分离的Java快速开发框架</h4> <h4 align="center">基于SpringBoot+Vue3前后端分离的Java快速开发框架</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/y_project/mmxt-Vue/stargazers"><img src="https://gitee.com/y_project/mmxt-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.1-brightgreen.svg"></a> <a href="https://gitee.com/y_project/mmxt-Vue"><img src="https://img.shields.io/badge/mmxt-v3.9.1-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a> <a href="https://gitee.com/y_project/mmxt-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p> </p>
## 平台简介 ## 平台简介
* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 * 本仓库为前端技术栈 [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-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast) 版本。 * 配套后端代码仓库地址[mmxt-Vue](https://gitee.com/y_project/mmxt-Vue) 或 [mmxt-Vue-fast](https://gitcode.com/yangzongzhuan/mmxt-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)。 * 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[mmxt-Vue](https://gitee.com/y_project/mmxt-Vue/tree/master/mmxt-ui)。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp; * 阿里云折扣场:[点我进入](http://aly.mmxt.vip),腾讯云秒杀场:[点我进入](http://txy.mmxt.vip)&nbsp;&nbsp;
## 前端运行 ## 前端运行
```bash ```bash
# 克隆项目 # 克隆项目
git clone https://github.com/yangzongzhuan/RuoYi-Vue3.git git clone https://github.com/yangzongzhuan/mmxt-Vue3.git
# 进入项目目录 # 进入项目目录
cd RuoYi-Vue3 cd mmxt-Vue3
# 安装依赖 # 安装依赖
yarn --registry=https://registry.npmmirror.com yarn --registry=https://registry.npmmirror.com
@ -62,8 +62,8 @@ yarn dev
- admin/admin123 - admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 - 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址:http://vue.ruoyi.vip 演示地址:http://vue.mmxt.vip
文档地址:http://doc.ruoyi.vip 文档地址:http://doc.mmxt.vip
## 演示图 ## 演示图
@ -103,6 +103,6 @@ yarn dev
</table> </table>
## 若依前后端分离交流群 ## 美美学堂前后端分离交流群
QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) 点击按钮入群。 QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) 点击按钮入群。

10
package.json

@ -1,8 +1,8 @@
{ {
"name": "ruoyi", "name": "mmxt",
"version": "3.9.1", "version": "3.9.1",
"description": "若依管理系统", "description": "美美学堂管理系统",
"author": "若依", "author": "美美学堂",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -13,7 +13,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitee.com/y_project/RuoYi-Vue.git" "url": "https://gitee.com/y_project/mmxt-Vue.git"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
@ -48,4 +48,4 @@
"overrides": { "overrides": {
"quill": "2.0.2" "quill": "2.0.2"
} }
} }

2
src/api/system/user.js

@ -1,5 +1,5 @@
import request from '@/utils/request' import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi"; import { parseStrEmpty } from "@/utils/mmxt";
// 查询用户列表 // 查询用户列表
export function listUser(query) { export function listUser(query) {

2
src/assets/styles/index.scss

@ -3,7 +3,7 @@
@use './element-ui.scss'; @use './element-ui.scss';
@use './sidebar.scss'; @use './sidebar.scss';
@use './btn.scss'; @use './btn.scss';
@use './ruoyi.scss'; @use './mmxt.scss';
body { body {
height: 100%; height: 100%;

308
src/assets/styles/ruoyi.scss

@ -1,308 +0,0 @@
/**
* 通用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;
}
.ml10 {
margin-left: 10px;
}
.mt20 {
margin-top: 20px;
}
.mr20 {
margin-right: 20px;
}
.mb20 {
margin-bottom: 20px;
}
.ml20 {
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-form--inline {
.el-form-item {
.el-input, .el-cascader, .el-select, .el-autocomplete {
width: 200px;
}
}
}
.el-form .el-form-item__label {
font-weight: 700;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
}
.el-dialog.scrollbar .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 {
display: flex;
justify-content: flex-end;
margin-top: 20px;
background-color: transparent !important;
}
/* 弹窗中的分页器 */
.el-dialog .pagination-container {
position: static !important;
margin: 10px 0 0 0;
padding: 0 !important;
.el-pagination {
position: static;
}
}
/* 移动端适配 */
@media (max-width: 768px) {
.pagination-container {
.el-pagination {
> .el-pagination__jump {
display: none !important;
}
> .el-pagination__sizes {
display: none !important;
}
}
}
}
/* tree border */
.tree-border {
margin-top: 5px;
border: 1px solid var(--el-border-color-light, #e5e6e7);
background: var(--el-bg-color, #FFFFFF) none;
border-radius:4px;
width: 100%;
}
.el-table .fixed-width .el-button--small {
padding-left: 0;
padding-right: 0;
width: inherit;
}
/* horizontal el menu */
.el-menu--horizontal .el-menu-item .svg-icon + span,
.el-menu--horizontal .el-sub-menu__title .svg-icon + span {
margin-left: 3px;
}
.el-menu--horizontal .el-menu--popup {
min-width: 120px !important;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link {
cursor: pointer;
color: #409EFF;
margin-left: 10px;
}
.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 !important;
min-height: 40px;
}
.el-card__body {
padding: 15px 20px 20px 20px !important;
}
.card-box {
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 {
margin-left: auto;
}
/* 分割面板样式 */
.splitpanes.default-theme .splitpanes__pane {
background-color: var(--splitpanes-default-bg) !important;
}

402
src/components/FileUpload/index.vue

@ -1,21 +1,8 @@
<template> <template>
<div class="upload-file"> <div class="upload-file">
<el-upload <el-upload multiple :action="uploadFileUrl" :before-upload="handleBeforeUpload" :file-list="fileList" :data="data"
multiple :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed" :on-success="handleUploadSuccess"
:action="uploadFileUrl" :show-file-list="false" :headers="headers" class="upload-file-uploader" ref="fileUpload" v-if="!disabled">
:before-upload="handleBeforeUpload"
:file-list="fileList"
:data="data"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
class="upload-file-uploader"
ref="fileUpload"
v-if="!disabled"
>
<!-- 上传按钮 --> <!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button> <el-button type="primary">选取文件</el-button>
</el-upload> </el-upload>
@ -27,7 +14,8 @@
的文件 的文件
</div> </div>
<!-- 文件列表 --> <!-- 文件列表 -->
<transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> <transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text"
name="el-fade-in-linear" tag="ul">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank"> <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span> <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
@ -41,216 +29,220 @@
</template> </template>
<script setup> <script setup>
import { getToken } from "@/utils/auth" import { getToken } from "@/utils/auth"
import Sortable from 'sortablejs' // import Sortable from 'sortablejs'
const props = defineProps({ const props = defineProps({
modelValue: [String, Object, Array], modelValue: [String, Object, Array],
// //
action: { action: {
type: String, type: String,
default: "/common/upload" default: "/common/upload"
}, },
// //
data: { data: {
type: Object type: Object
}, },
// //
limit: { limit: {
type: Number, type: Number,
default: 5 default: 5
}, },
// (MB) // (MB)
fileSize: { fileSize: {
type: Number, type: Number,
default: 5 default: 5
}, },
// , ['png', 'jpg', 'jpeg'] // , ['png', 'jpg', 'jpeg']
fileType: { fileType: {
type: Array, type: Array,
default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"] default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"]
}, },
// //
isShowTip: { isShowTip: {
type: Boolean, type: Boolean,
default: true default: true
}, },
// //
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
}, },
// //
drag: { drag: {
type: Boolean, type: Boolean,
default: true default: true
} }
}) })
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const emit = defineEmits() const emit = defineEmits()
const number = ref(0) const number = ref(0)
const uploadList = ref([]) const uploadList = ref([])
const baseUrl = import.meta.env.VITE_APP_BASE_API const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) //
const headers = ref({ Authorization: "Bearer " + getToken() }) const headers = ref({ Authorization: "Bearer " + getToken() })
const fileList = ref([]) const fileList = ref([])
const showTip = computed( const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize) () => props.isShowTip && (props.fileType || props.fileSize)
) )
watch(() => props.modelValue, val => { watch(() => props.modelValue, val => {
if (val) { if (val) {
let temp = 1 let temp = 1
// //
const list = Array.isArray(val) ? val : props.modelValue.split(',') const list = Array.isArray(val) ? val : props.modelValue.split(',')
// //
fileList.value = list.map(item => { fileList.value = list.map(item => {
if (typeof item === "string") { if (typeof item === "string") {
item = { name: item, url: item } item = { name: item, url: item }
} }
item.uid = item.uid || new Date().getTime() + temp++ item.uid = item.uid || new Date().getTime() + temp++
return item return item
}) })
} else { } else {
fileList.value = [] fileList.value = []
return [] return []
} }
},{ deep: true, immediate: true }) }, { deep: true, immediate: true })
// //
function handleBeforeUpload(file) { function handleBeforeUpload(file) {
// //
if (props.fileType.length) { if (props.fileType.length) {
const fileName = file.name.split('.') const fileName = file.name.split('.')
const fileExt = fileName[fileName.length - 1] const fileExt = fileName[fileName.length - 1]
const isTypeOk = props.fileType.indexOf(fileExt) >= 0 const isTypeOk = props.fileType.indexOf(fileExt) >= 0
if (!isTypeOk) { if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`) proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`)
return false return false
}
} }
} //
// if (file.name.includes(',')) {
if (file.name.includes(',')) { proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
return false
}
//
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
return false return false
} }
//
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
return false
}
}
proxy.$modal.loading("正在上传文件,请稍候...")
number.value++
return true
} }
proxy.$modal.loading("正在上传文件,请稍候...")
number.value++
return true
}
// //
function handleExceed() { function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`) proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`)
} }
//
function handleUploadError(err) {
proxy.$modal.msgError("上传文件失败")
proxy.$modal.closeLoading()
}
// //
function handleUploadSuccess(res, file) { function handleUploadError(err) {
if (res.code === 200) { proxy.$modal.msgError("上传文件失败")
uploadList.value.push({ name: res.fileName, url: res.fileName })
uploadedSuccessfully()
} else {
number.value--
proxy.$modal.closeLoading() proxy.$modal.closeLoading()
proxy.$modal.msgError(res.msg)
proxy.$refs.fileUpload.handleRemove(file)
uploadedSuccessfully()
} }
}
// //
function handleDelete(index) { function handleUploadSuccess(res, file) {
fileList.value.splice(index, 1) if (res.code === 200) {
emit("update:modelValue", listToString(fileList.value)) uploadList.value.push({ name: res.fileName, url: res.fileName })
} uploadedSuccessfully()
} else {
number.value--
proxy.$modal.closeLoading()
proxy.$modal.msgError(res.msg)
proxy.$refs.fileUpload.handleRemove(file)
uploadedSuccessfully()
}
}
// //
function uploadedSuccessfully() { function handleDelete(index) {
if (number.value > 0 && uploadList.value.length === number.value) { fileList.value.splice(index, 1)
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
uploadList.value = []
number.value = 0
emit("update:modelValue", listToString(fileList.value)) emit("update:modelValue", listToString(fileList.value))
proxy.$modal.closeLoading()
} }
}
// //
function getFileName(name) { function uploadedSuccessfully() {
// url if (number.value > 0 && uploadList.value.length === number.value) {
if (name.lastIndexOf("/") > -1) { fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
return name.slice(name.lastIndexOf("/") + 1) uploadList.value = []
} else { number.value = 0
return name emit("update:modelValue", listToString(fileList.value))
proxy.$modal.closeLoading()
}
} }
}
// //
function listToString(list, separator) { function getFileName(name) {
let strs = "" // url
separator = separator || "," if (name.lastIndexOf("/") > -1) {
for (let i in list) { return name.slice(name.lastIndexOf("/") + 1)
if (list[i].url) { } else {
strs += list[i].url + separator return name
} }
} }
return strs != '' ? strs.substr(0, strs.length - 1) : ''
}
// //
onMounted(() => { function listToString(list, separator) {
if (props.drag && !props.disabled) { let strs = ""
nextTick(() => { separator = separator || ","
const element = proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList for (let i in list) {
Sortable.create(element, { if (list[i].url) {
ghostClass: 'file-upload-darg', strs += list[i].url + separator
onEnd: (evt) => { }
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0] }
fileList.value.splice(evt.newIndex, 0, movedItem) return strs != '' ? strs.substr(0, strs.length - 1) : ''
emit('update:modelValue', listToString(fileList.value))
}
})
})
} }
})
//
onMounted(() => {
if (props.drag && !props.disabled) {
// nextTick(() => {
// const element = proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList
// Sortable.create(element, {
// ghostClass: 'file-upload-darg',
// onEnd: (evt) => {
// const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
// fileList.value.splice(evt.newIndex, 0, movedItem)
// emit('update:modelValue', listToString(fileList.value))
// }
// })
// })
}
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.file-upload-darg { .file-upload-darg {
opacity: 0.5; opacity: 0.5;
background: #c8ebfb; background: #c8ebfb;
} }
.upload-file-uploader {
margin-bottom: 5px; .upload-file-uploader {
} margin-bottom: 5px;
.upload-file-list .el-upload-list__item { }
border: 1px solid #e4e7ed;
line-height: 2; .upload-file-list .el-upload-list__item {
margin-bottom: 10px; border: 1px solid #e4e7ed;
position: relative; line-height: 2;
transition: none !important; margin-bottom: 10px;
} position: relative;
.upload-file-list .ele-upload-list__item-content { transition: none !important;
display: flex; }
justify-content: space-between;
align-items: center; .upload-file-list .ele-upload-list__item-content {
color: inherit; display: flex;
} justify-content: space-between;
.ele-upload-list__item-content-action .el-link { align-items: center;
margin-right: 10px; color: inherit;
} }
</style>
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>

375
src/components/HeaderSearch/index.vue

@ -1,30 +1,16 @@
<template> <template>
<div class="header-search"> <div class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-dialog <el-dialog v-model="show" width="600" @close="close" :show-close="false" append-to-body>
v-model="show" <el-input v-model="search" ref="headerSearchSelectRef" size="large" @input="querySearch" prefix-icon="Search"
width="600" placeholder="菜单搜索,支持标题、URL模糊查询" clearable @keyup.enter="selectActiveResult"
@close="close" @keydown.up.prevent="navigateResult('up')" @keydown.down.prevent="navigateResult('down')">
:show-close="false"
append-to-body
>
<el-input
v-model="search"
ref="headerSearchSelectRef"
size="large"
@input="querySearch"
prefix-icon="Search"
placeholder="菜单搜索,支持标题、URL模糊查询"
clearable
@keyup.enter="selectActiveResult"
@keydown.up.prevent="navigateResult('up')"
@keydown.down.prevent="navigateResult('down')"
>
</el-input> </el-input>
<div class="result-wrap"> <div class="result-wrap">
<el-scrollbar> <el-scrollbar>
<div class="search-item" tabindex="1" v-for="(item, index) in options" :key="item.path" :style="activeStyle(index)" @mouseenter="activeIndex = index" @mouseleave="activeIndex = -1"> <div class="search-item" tabindex="1" v-for="(item, index) in options" :key="item.path"
:style="activeStyle(index)" @mouseenter="activeIndex = index" @mouseleave="activeIndex = -1">
<div class="left"> <div class="left">
<svg-icon class="menu-icon" :icon-class="item.icon" /> <svg-icon class="menu-icon" :icon-class="item.icon" />
</div> </div>
@ -36,7 +22,7 @@
{{ item.path }} {{ item.path }}
</div> </div>
</div> </div>
<svg-icon icon-class="enter" v-show="index === activeIndex"/> <svg-icon icon-class="enter" v-show="index === activeIndex" />
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
@ -45,208 +31,209 @@
</template> </template>
<script setup> <script setup>
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi' import { getNormalPath } from '@/utils/mmxt'
import { isHttp } from '@/utils/validate' import { isHttp } from '@/utils/validate'
import useSettingsStore from '@/store/modules/settings' import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission' import usePermissionStore from '@/store/modules/permission'
const search = ref('') const search = ref('')
const options = ref([]) const options = ref([])
const searchPool = ref([]) const searchPool = ref([])
const activeIndex = ref(-1) const activeIndex = ref(-1)
const show = ref(false) const show = ref(false)
const fuse = ref(undefined) const fuse = ref(undefined)
const headerSearchSelectRef = ref(null) const headerSearchSelectRef = ref(null)
const router = useRouter() const router = useRouter()
const theme = computed(() => useSettingsStore().theme) const theme = computed(() => useSettingsStore().theme)
const routes = computed(() => usePermissionStore().defaultRoutes) const routes = computed(() => usePermissionStore().defaultRoutes)
function click() { function click() {
show.value = !show.value show.value = !show.value
if (show.value) { if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus() headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
options.value = searchPool.value options.value = searchPool.value
}
}
function close() {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
search.value = ''
options.value = []
show.value = false
activeIndex.value = -1
}
function change(val) {
const path = val.path
const query = val.query
if (isHttp(path)) {
// http(s)://
const pindex = path.indexOf("http")
window.open(path.substr(pindex, path.length), "_blank")
} else {
if (query) {
router.push({ path: path, query: JSON.parse(query) })
} else {
router.push(path)
} }
} }
search.value = '' function close() {
options.value = [] headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
nextTick(() => { search.value = ''
options.value = []
show.value = false show.value = false
}) activeIndex.value = -1
} }
function initFuse(list) {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
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],
icon: ''
}
if (r.meta && r.meta.title) { function change(val) {
data.title = [...data.title, r.meta.title] const path = val.path
data.icon = r.meta.icon const query = val.query
if (r.redirect !== "noRedirect") { if (isHttp(path)) {
// only push the routes with title // http(s)://
// special case: need to exclude parent router without redirect const pindex = path.indexOf("http")
res.push(data) window.open(path.substr(pindex, path.length), "_blank")
} else {
if (query) {
router.push({ path: path, query: JSON.parse(query) })
} else {
router.push(path)
} }
} }
if (r.query) {
data.query = r.query
}
// recursive child routes search.value = ''
if (r.children) { options.value = []
const tempRoutes = generateRoutes(r.children, data.path, data.title) nextTick(() => {
if (tempRoutes.length >= 1) { show.value = false
res = [...res, ...tempRoutes] })
}
function initFuse(list) {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
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],
icon: ''
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title]
data.icon = r.meta.icon
if (r.redirect !== "noRedirect") {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}
if (r.query) {
data.query = r.query
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
} }
} }
return res
} }
return res
} function querySearch(query) {
activeIndex.value = -1
function querySearch(query) { if (query !== '') {
activeIndex.value = -1 options.value = fuse.value.search(query).map((item) => item.item) ?? searchPool.value
if (query !== '') { } else {
options.value = fuse.value.search(query).map((item) => item.item) ?? searchPool.value options.value = searchPool.value
} else { }
options.value = searchPool.value
} }
}
function activeStyle(index) { function activeStyle(index) {
if (index !== activeIndex.value) return {} if (index !== activeIndex.value) return {}
return { return {
"background-color": theme.value, "background-color": theme.value,
"color": "#fff" "color": "#fff"
}
} }
}
function navigateResult(direction) { function navigateResult(direction) {
if (direction === "up") { if (direction === "up") {
activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1 activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1
} else if (direction === "down") { } else if (direction === "down") {
activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1 activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1
}
} }
}
function selectActiveResult() { function selectActiveResult() {
if (options.value.length > 0 && activeIndex.value >= 0) { if (options.value.length > 0 && activeIndex.value >= 0) {
change(options.value[activeIndex.value]) change(options.value[activeIndex.value])
}
} }
}
onMounted(() => { onMounted(() => {
searchPool.value = generateRoutes(routes.value) searchPool.value = generateRoutes(routes.value)
}) })
watch(searchPool, (list) => { watch(searchPool, (list) => {
initFuse(list) initFuse(list)
}) })
</script> </script>
<style lang='scss' scoped> <style lang='scss' scoped>
.header-search { .header-search {
.search-icon { .search-icon {
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
vertical-align: middle; vertical-align: middle;
}
} }
}
.result-wrap {
height: 280px;
margin: 6px 0;
.search-item { .result-wrap {
display: flex; height: 280px;
height: 48px; margin: 6px 0;
align-items: center;
padding-right: 10px;
.left { .search-item {
width: 60px;
text-align: center;
.menu-icon {
width: 18px;
height: 18px;
}
}
.search-info {
padding-left: 5px;
margin-top: 10px;
width: 100%;
display: flex; display: flex;
flex-direction: column; height: 48px;
justify-content: flex-start; align-items: center;
flex: 1; padding-right: 10px;
.menu-title, .left {
.menu-path { width: 60px;
height: 20px; text-align: center;
.menu-icon {
width: 18px;
height: 18px;
}
} }
.menu-path {
color: #ccc; .search-info {
font-size: 10px; padding-left: 5px;
margin-top: 10px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
flex: 1;
.menu-title,
.menu-path {
height: 20px;
}
.menu-path {
color: #ccc;
font-size: 10px;
}
} }
} }
}
.search-item:hover { .search-item:hover {
cursor: pointer; cursor: pointer;
}
} }
} </style>
</style>

404
src/components/ImageUpload/index.vue

@ -1,25 +1,13 @@
<template> <template>
<div class="component-upload-image"> <div class="component-upload-image">
<el-upload <el-upload multiple :disabled="disabled" :action="uploadImgUrl" list-type="picture-card"
multiple :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :data="data" :limit="limit"
:disabled="disabled" :on-error="handleUploadError" :on-exceed="handleExceed" ref="imageUpload" :before-remove="handleDelete"
:action="uploadImgUrl" :show-file-list="true" :headers="headers" :file-list="fileList" :on-preview="handlePictureCardPreview"
list-type="picture-card" :class="{ hide: fileList.length >= limit }">
:on-success="handleUploadSuccess" <el-icon class="avatar-uploader-icon">
:before-upload="handleBeforeUpload" <plus />
:data="data" </el-icon>
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
ref="imageUpload"
:before-remove="handleDelete"
:show-file-list="true"
:headers="headers"
:file-list="fileList"
:on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= limit }"
>
<el-icon class="avatar-uploader-icon"><plus /></el-icon>
</el-upload> </el-upload>
<!-- 上传提示 --> <!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip && !disabled"> <div class="el-upload__tip" v-if="showTip && !disabled">
@ -33,226 +21,218 @@
的文件 的文件
</div> </div>
<el-dialog <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
v-model="dialogVisible" <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
title="预览"
width="800px"
append-to-body
>
<img
:src="dialogImageUrl"
style="display: block; max-width: 100%; margin: 0 auto"
/>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { getToken } from "@/utils/auth" import { getToken } from "@/utils/auth"
import { isExternal } from "@/utils/validate" import { isExternal } from "@/utils/validate"
import Sortable from 'sortablejs' // import Sortable from 'sortablejs'
const props = defineProps({ const props = defineProps({
modelValue: [String, Object, Array], modelValue: [String, Object, Array],
// //
action: { action: {
type: String, type: String,
default: "/common/upload" default: "/common/upload"
}, },
// //
data: { data: {
type: Object type: Object
}, },
// //
limit: { limit: {
type: Number, type: Number,
default: 5 default: 5
}, },
// (MB) // (MB)
fileSize: { fileSize: {
type: Number, type: Number,
default: 5 default: 5
}, },
// , ['png', 'jpg', 'jpeg'] // , ['png', 'jpg', 'jpeg']
fileType: { fileType: {
type: Array, type: Array,
default: () => ["png", "jpg", "jpeg"] default: () => ["png", "jpg", "jpeg"]
}, },
// //
isShowTip: { isShowTip: {
type: Boolean, type: Boolean,
default: true default: true
}, },
// //
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
}, },
// //
drag: { drag: {
type: Boolean, type: Boolean,
default: true default: true
} }
}) })
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const emit = defineEmits() const emit = defineEmits()
const number = ref(0) const number = ref(0)
const uploadList = ref([]) const uploadList = ref([])
const dialogImageUrl = ref("") const dialogImageUrl = ref("")
const dialogVisible = ref(false) const dialogVisible = ref(false)
const baseUrl = import.meta.env.VITE_APP_BASE_API const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) //
const headers = ref({ Authorization: "Bearer " + getToken() }) const headers = ref({ Authorization: "Bearer " + getToken() })
const fileList = ref([]) const fileList = ref([])
const showTip = computed( const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize) () => props.isShowTip && (props.fileType || props.fileSize)
) )
watch(() => props.modelValue, val => { watch(() => props.modelValue, val => {
if (val) { if (val) {
// //
const list = Array.isArray(val) ? val : props.modelValue.split(",") const list = Array.isArray(val) ? val : props.modelValue.split(",")
// //
fileList.value = list.map(item => { fileList.value = list.map(item => {
if (typeof item === "string") { if (typeof item === "string") {
if (item.indexOf(baseUrl) === -1 && !isExternal(item)) { if (item.indexOf(baseUrl) === -1 && !isExternal(item)) {
item = { name: baseUrl + item, url: baseUrl + item } item = { name: baseUrl + item, url: baseUrl + item }
} else { } else {
item = { name: item, url: item } item = { name: item, url: item }
}
} }
return item
})
} else {
fileList.value = []
return []
}
}, { deep: true, immediate: true })
// loading
function handleBeforeUpload(file) {
let isImg = false
if (props.fileType.length) {
let fileExtension = ""
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1)
} }
return item isImg = props.fileType.some(type => {
}) if (file.type.indexOf(type) > -1) return true
} else { if (fileExtension && fileExtension.indexOf(type) > -1) return true
fileList.value = [] return false
return [] })
} } else {
},{ deep: true, immediate: true }) isImg = file.type.indexOf("image") > -1
// loading
function handleBeforeUpload(file) {
let isImg = false
if (props.fileType.length) {
let fileExtension = ""
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1)
} }
isImg = props.fileType.some(type => { if (!isImg) {
if (file.type.indexOf(type) > -1) return true proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`)
if (fileExtension && fileExtension.indexOf(type) > -1) return true
return false return false
}) }
} else { if (file.name.includes(',')) {
isImg = file.type.indexOf("image") > -1 proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
}
if (!isImg) {
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`)
return false
}
if (file.name.includes(',')) {
proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
return false
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`)
return false return false
} }
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`)
return false
}
}
proxy.$modal.loading("正在上传图片,请稍候...")
number.value++
} }
proxy.$modal.loading("正在上传图片,请稍候...")
number.value++
}
// //
function handleExceed() { function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`) proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`)
} }
// //
function handleUploadSuccess(res, file) { function handleUploadSuccess(res, file) {
if (res.code === 200) { if (res.code === 200) {
uploadList.value.push({ name: res.fileName, url: res.fileName }) uploadList.value.push({ name: res.fileName, url: res.fileName })
uploadedSuccessfully() uploadedSuccessfully()
} else { } else {
number.value-- number.value--
proxy.$modal.closeLoading() proxy.$modal.closeLoading()
proxy.$modal.msgError(res.msg) proxy.$modal.msgError(res.msg)
proxy.$refs.imageUpload.handleRemove(file) proxy.$refs.imageUpload.handleRemove(file)
uploadedSuccessfully() uploadedSuccessfully()
}
}
//
function handleDelete(file) {
const findex = fileList.value.map(f => f.name).indexOf(file.name)
if (findex > -1 && uploadList.value.length === number.value) {
fileList.value.splice(findex, 1)
emit("update:modelValue", listToString(fileList.value))
return false
}
} }
}
// //
function handleDelete(file) { function uploadedSuccessfully() {
const findex = fileList.value.map(f => f.name).indexOf(file.name) if (number.value > 0 && uploadList.value.length === number.value) {
if (findex > -1 && uploadList.value.length === number.value) { fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
fileList.value.splice(findex, 1) uploadList.value = []
emit("update:modelValue", listToString(fileList.value)) number.value = 0
return false emit("update:modelValue", listToString(fileList.value))
proxy.$modal.closeLoading()
}
} }
}
// //
function uploadedSuccessfully() { function handleUploadError() {
if (number.value > 0 && uploadList.value.length === number.value) { proxy.$modal.msgError("上传图片失败")
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
uploadList.value = []
number.value = 0
emit("update:modelValue", listToString(fileList.value))
proxy.$modal.closeLoading() proxy.$modal.closeLoading()
} }
}
//
function handleUploadError() {
proxy.$modal.msgError("上传图片失败")
proxy.$modal.closeLoading()
}
// //
function handlePictureCardPreview(file) { function handlePictureCardPreview(file) {
dialogImageUrl.value = file.url dialogImageUrl.value = file.url
dialogVisible.value = true dialogVisible.value = true
} }
// //
function listToString(list, separator) { function listToString(list, separator) {
let strs = "" let strs = ""
separator = separator || "," separator = separator || ","
for (let i in list) { for (let i in list) {
if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) { if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
strs += list[i].url.replace(baseUrl, "") + separator strs += list[i].url.replace(baseUrl, "") + separator
}
} }
return strs != "" ? strs.substr(0, strs.length - 1) : ""
} }
return strs != "" ? strs.substr(0, strs.length - 1) : ""
}
// //
onMounted(() => { onMounted(() => {
if (props.drag && !props.disabled) { if (props.drag && !props.disabled) {
nextTick(() => { // nextTick(() => {
const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list') // const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
Sortable.create(element, { // Sortable.create(element, {
onEnd: (evt) => { // onEnd: (evt) => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0] // const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem) // fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value)) // emit('update:modelValue', listToString(fileList.value))
} // }
}) // })
}) // })
} }
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// .el-upload--picture-card // .el-upload--picture-card
:deep(.hide .el-upload--picture-card) { :deep(.hide .el-upload--picture-card) {
display: none; display: none;
} }
:deep(.el-upload.el-upload--picture-card.is-disabled) { :deep(.el-upload.el-upload--picture-card.is-disabled) {
display: none !important; display: none !important;
} }
</style> </style>

8
src/components/RuoYi/Doc/index.vue

@ -5,9 +5,9 @@
</template> </template>
<script setup> <script setup>
const url = ref('http://doc.ruoyi.vip/ruoyi-vue') const url = ref('http://doc.mmxt.vip/mmxt-vue')
function goto() { function goto() {
window.open(url.value) window.open(url.value)
} }
</script> </script>

10
src/components/RuoYi/Git/index.vue

@ -5,9 +5,9 @@
</template> </template>
<script setup> <script setup>
const url = ref('https://gitee.com/y_project/RuoYi-Vue') const url = ref('https://gitee.com/y_project/mmxt-Vue')
function goto() { function goto() {
window.open(url.value) window.open(url.value)
} }
</script> </script>

2
src/directive/common/copyText.js

@ -1,6 +1,6 @@
/** /**
* v-copyText 复制文本内容 * v-copyText 复制文本内容
* Copyright (c) 2022 ruoyi * Copyright (c) 2022 mmxt
*/ */
export default { export default {
beforeMount(el, { value, arg }) { beforeMount(el, { value, arg }) {

8
src/directive/permission/hasPermi.js

@ -1,7 +1,7 @@
/** /**
* v-hasPermi 操作权限处理 * v-hasPermi 操作权限处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 mmxt
*/ */
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
export default { export default {

8
src/directive/permission/hasRole.js

@ -1,7 +1,7 @@
/** /**
* v-hasRole 角色权限处理 * v-hasRole 角色权限处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 mmxt
*/ */
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
export default { export default {

389
src/layout/components/Navbar.vue

@ -1,6 +1,7 @@
<template> <template>
<div class="navbar" :class="'nav' + settingsStore.navType"> <div class="navbar" :class="'nav' + settingsStore.navType">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
@toggleClick="toggleSideBar" />
<breadcrumb v-if="settingsStore.navType == 1" id="breadcrumb-container" class="breadcrumb-container" /> <breadcrumb v-if="settingsStore.navType == 1" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="settingsStore.navType == 2" id="topmenu-container" class="topmenu-container" /> <top-nav v-if="settingsStore.navType == 2" id="topmenu-container" class="topmenu-container" />
<template v-if="settingsStore.navType == 3"> <template v-if="settingsStore.navType == 3">
@ -12,14 +13,6 @@
<template v-if="appStore.device !== 'mobile'"> <template v-if="appStore.device !== 'mobile'">
<header-search id="header-search" class="right-menu-item" /> <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" /> <screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="主题模式" effect="dark" placement="bottom"> <el-tooltip content="主题模式" effect="dark" placement="bottom">
@ -45,8 +38,8 @@
<el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item>个人中心</el-dropdown-item>
</router-link> </router-link>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings"> <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<span>布局设置</span> <span>布局设置</span>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item divided command="logout"> <el-dropdown-item divided command="logout">
<span>退出登录</span> <span>退出登录</span>
</el-dropdown-item> </el-dropdown-item>
@ -58,233 +51,233 @@
</template> </template>
<script setup> <script setup>
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import Breadcrumb from '@/components/Breadcrumb' import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav' import TopNav from '@/components/TopNav'
import TopBar from './TopBar' import TopBar from './TopBar'
import Logo from './Sidebar/Logo' import Logo from './Sidebar/Logo'
import Hamburger from '@/components/Hamburger' import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull' import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect' import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch' import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git' import mmxtGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc' import mmxtDoc from '@/components/RuoYi/Doc'
import useAppStore from '@/store/modules/app' import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings' import useSettingsStore from '@/store/modules/settings'
const appStore = useAppStore() const appStore = useAppStore()
const userStore = useUserStore() const userStore = useUserStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
function toggleSideBar() { function toggleSideBar() {
appStore.toggleSideBar() appStore.toggleSideBar()
} }
function handleCommand(command) { function handleCommand(command) {
switch (command) { switch (command) {
case "setLayout": case "setLayout":
setLayout() setLayout()
break break
case "logout": case "logout":
logout() logout()
break break
default: default:
break break
}
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userStore.logOut().then(() => {
location.href = '/index'
})
}).catch(() => { })
} }
}
const emits = defineEmits(['setLayout'])
function logout() { function setLayout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', { emits('setLayout')
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userStore.logOut().then(() => {
location.href = '/index'
})
}).catch(() => { })
}
const emits = defineEmits(['setLayout'])
function setLayout() {
emits('setLayout')
}
async function toggleTheme(event) {
const x = event?.clientX || window.innerWidth / 2
const y = event?.clientY || window.innerHeight / 2
const wasDark = settingsStore.isDark
const isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
const isSupported = document.startViewTransition && !isReducedMotion
if (!isSupported) {
settingsStore.toggleTheme()
return
} }
try { async function toggleTheme(event) {
const transition = document.startViewTransition(async () => { const x = event?.clientX || window.innerWidth / 2
await new Promise((resolve) => setTimeout(resolve, 10)) const y = event?.clientY || window.innerHeight / 2
const wasDark = settingsStore.isDark
const isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
const isSupported = document.startViewTransition && !isReducedMotion
if (!isSupported) {
settingsStore.toggleTheme() settingsStore.toggleTheme()
await nextTick() return
}) }
await transition.ready
try {
const endRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y)) const transition = document.startViewTransition(async () => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`] await new Promise((resolve) => setTimeout(resolve, 10))
document.documentElement.animate( settingsStore.toggleTheme()
{ await nextTick()
clipPath: !wasDark ? [...clipPath].reverse() : clipPath })
}, { await transition.ready
const endRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y))
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`]
document.documentElement.animate(
{
clipPath: !wasDark ? [...clipPath].reverse() : clipPath
}, {
duration: 650, duration: 650,
easing: "cubic-bezier(0.4, 0, 0.2, 1)", easing: "cubic-bezier(0.4, 0, 0.2, 1)",
fill: "forwards", fill: "forwards",
pseudoElement: !wasDark ? "::view-transition-old(root)" : "::view-transition-new(root)" pseudoElement: !wasDark ? "::view-transition-old(root)" : "::view-transition-new(root)"
} }
) )
await transition.finished await transition.finished
} catch (error) { } catch (error) {
console.warn("View transition failed, falling back to immediate toggle:", error) console.warn("View transition failed, falling back to immediate toggle:", error)
settingsStore.toggleTheme() settingsStore.toggleTheme()
}
} }
}
</script> </script>
<style lang='scss' scoped> <style lang='scss' scoped>
.navbar.nav3 { .navbar.nav3 {
.hamburger-container { .hamburger-container {
display: none !important; display: none !important;
}
}
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: var(--navbar-bg);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
display: flex;
align-items: center;
// padding: 0 8px;
box-sizing: border-box;
.hamburger-container {
line-height: 46px;
height: 100%;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
&:hover {
background: rgba(0, 0, 0, 0.025);
} }
} }
.breadcrumb-container { .navbar {
flex-shrink: 0; height: 50px;
} overflow: hidden;
position: relative;
.topmenu-container { background: var(--navbar-bg);
position: absolute; box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
left: 50px;
}
.topbar-container {
flex: 1;
min-width: 0;
display: flex; display: flex;
align-items: center; align-items: center;
overflow: hidden; // padding: 0 8px;
margin-left: 8px; box-sizing: border-box;
}
.errLog-container { .hamburger-container {
display: inline-block; line-height: 46px;
vertical-align: top; height: 100%;
} cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.right-menu { .breadcrumb-container {
height: 100%; flex-shrink: 0;
line-height: 50px; }
display: flex;
align-items: center;
margin-left: auto;
&:focus { .topmenu-container {
outline: none; position: absolute;
left: 50px;
} }
.right-menu-item { .topbar-container {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
overflow: hidden;
margin-left: 8px;
}
.errLog-container {
display: inline-block; display: inline-block;
padding: 0 8px; vertical-align: top;
height: 100%; }
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect { .right-menu {
cursor: pointer; height: 100%;
transition: background 0.3s; line-height: 50px;
display: flex;
align-items: center;
margin-left: auto;
&:hover { &:focus {
background: rgba(0, 0, 0, 0.025); outline: none;
}
} }
&.theme-switch-wrapper { .right-menu-item {
display: flex; display: inline-block;
align-items: center; padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
svg {
transition: transform 0.3s;
&:hover { &:hover {
transform: scale(1.15); background: rgba(0, 0, 0, 0.025);
} }
} }
}
}
.avatar-container { &.theme-switch-wrapper {
margin-right: 0px; display: flex;
padding-right: 0px; align-items: center;
.avatar-wrapper { svg {
margin-top: 10px; transition: transform 0.3s;
right: 8px;
position: relative;
.user-avatar { &:hover {
cursor: pointer; transform: scale(1.15);
width: 30px; }
height: 30px; }
margin-right: 8px;
border-radius: 50%;
} }
}
.avatar-container {
margin-right: 0px;
padding-right: 0px;
.user-nickname{ .avatar-wrapper {
margin-top: 10px;
right: 8px;
position: relative; position: relative;
left: 0px;
bottom: 10px;
font-size: 14px;
font-weight: bold;
}
i { .user-avatar {
cursor: pointer; cursor: pointer;
position: absolute; width: 30px;
right: -20px; height: 30px;
top: 25px; margin-right: 8px;
font-size: 12px; border-radius: 50%;
}
.user-nickname {
position: relative;
left: 0px;
bottom: 10px;
font-size: 14px;
font-weight: bold;
}
i {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
} }
} }
} }
} }
} </style>
</style>

136
src/layout/components/Sidebar/SidebarItem.vue

@ -1,10 +1,12 @@
<template> <template>
<div v-if="!item.hidden"> <div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow"> <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)"> <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }"> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/> <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template> <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{
onlyOneChild.meta.title }}</span></template>
</el-menu-item> </el-menu-item>
</app-link> </app-link>
</template> </template>
@ -15,86 +17,80 @@
<span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span> <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
</template> </template>
<sidebar-item <sidebar-item v-for="(child, index) in item.children" :key="child.path + index" :is-nest="true" :item="child"
v-for="(child, index) in item.children" :base-path="resolvePath(child.path)" class="nest-menu" />
:key="child.path + index"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu> </el-sub-menu>
</div> </div>
</template> </template>
<script setup> <script setup>
import { isExternal } from '@/utils/validate' import { isExternal } from '@/utils/validate'
import AppLink from './Link' import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi' import { getNormalPath } from '@/utils/mmxt'
const props = defineProps({ const props = defineProps({
// route object // route object
item: { item: {
type: Object, type: Object,
required: true required: true
}, },
isNest: { isNest: {
type: Boolean, type: Boolean,
default: false default: false
}, },
basePath: { basePath: {
type: String, type: String,
default: '' default: ''
}
})
const onlyOneChild = ref({})
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = []
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} }
onlyOneChild.value = item
return true
}) })
// When there is only one child router, the child router is displayed by default const onlyOneChild = ref({})
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display function hasOneShowingChild(children = [], parent) {
if (showingChildren.length === 0) { if (!children) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true } children = []
return true }
} const showingChildren = children.filter(item => {
if (item.hidden) {
return false
}
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
}
return false // Show parent if there are no child router to display
} if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
function resolvePath(routePath, routeQuery) { return false
if (isExternal(routePath)) {
return routePath
} }
if (isExternal(props.basePath)) {
return props.basePath function resolvePath(routePath, routeQuery) {
} if (isExternal(routePath)) {
if (routeQuery) { return routePath
let query = JSON.parse(routeQuery) }
return { path: getNormalPath(props.basePath + '/' + routePath), query: query } 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)
} }
return getNormalPath(props.basePath + '/' + routePath)
}
function hasTitle(title){ function hasTitle(title) {
if (title.length > 5) { if (title.length > 5) {
return title return title
} else { } else {
return "" return ""
}
} }
} </script>
</script>

566
src/layout/components/TagsView/index.vue

@ -1,17 +1,11 @@
<template> <template>
<div id="tags-view-container" class="tags-view-container"> <div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll"> <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
<router-link <router-link v-for="tag in visitedViews" :key="tag.path" :data-path="tag.path"
v-for="tag in visitedViews"
:key="tag.path"
:data-path="tag.path"
:class="{ 'active': isActive(tag), 'has-icon': tagsIcon }" :class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" class="tags-view-item"
class="tags-view-item" :style="activeStyle(tag)" @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
:style="activeStyle(tag)" @contextmenu.prevent="openMenu(tag, $event)">
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" /> <svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
{{ tag.title }} {{ tag.title }}
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)"> <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
@ -43,329 +37,329 @@
</template> </template>
<script setup> <script setup>
import ScrollPane from './ScrollPane' import ScrollPane from './ScrollPane'
import { getNormalPath } from '@/utils/ruoyi' import { getNormalPath } from '@/utils/mmxt'
import useTagsViewStore from '@/store/modules/tagsView' import useTagsViewStore from '@/store/modules/tagsView'
import useSettingsStore from '@/store/modules/settings' import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission' import usePermissionStore from '@/store/modules/permission'
const visible = ref(false) const visible = ref(false)
const top = ref(0) const top = ref(0)
const left = ref(0) const left = ref(0)
const selectedTag = ref({}) const selectedTag = ref({})
const affixTags = ref([]) const affixTags = ref([])
const scrollPaneRef = ref(null) const scrollPaneRef = ref(null)
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const visitedViews = computed(() => useTagsViewStore().visitedViews) const visitedViews = computed(() => useTagsViewStore().visitedViews)
const routes = computed(() => usePermissionStore().routes) const routes = computed(() => usePermissionStore().routes)
const theme = computed(() => useSettingsStore().theme) const theme = computed(() => useSettingsStore().theme)
const tagsIcon = computed(() => useSettingsStore().tagsIcon) const tagsIcon = computed(() => useSettingsStore().tagsIcon)
watch(route, () => { watch(route, () => {
addTags() addTags()
moveToCurrentTag() moveToCurrentTag()
}) })
watch(visible, (value) => { watch(visible, (value) => {
if (value) { if (value) {
document.body.addEventListener('click', closeMenu) document.body.addEventListener('click', closeMenu)
} else { } else {
document.body.removeEventListener('click', closeMenu) document.body.removeEventListener('click', closeMenu)
} }
}) })
onMounted(() => { onMounted(() => {
initTags() initTags()
addTags() addTags()
}) })
function isActive(r) { function isActive(r) {
return r.path === route.path return r.path === route.path
}
function activeStyle(tag) {
if (!isActive(tag)) return {}
return {
"background-color": theme.value,
"border-color": theme.value
} }
}
function isAffix(tag) { function activeStyle(tag) {
return tag.meta && tag.meta.affix if (!isActive(tag)) return {}
} return {
"background-color": theme.value,
"border-color": theme.value
}
}
function isFirstView() { function isAffix(tag) {
try { return tag.meta && tag.meta.affix
return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath
} catch (err) {
return false
} }
}
function isLastView() { function isFirstView() {
try { try {
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath
} catch (err) { } catch (err) {
return false return false
}
} }
}
function isLastView() {
function filterAffixTags(routes, basePath = '') { try {
let tags = [] return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
routes.forEach(route => { } catch (err) {
if (route.meta && route.meta.affix) { return false
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) { function filterAffixTags(routes, basePath = '') {
tags = [...tags, ...tempTags] 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) {
useTagsViewStore().addVisitedView(tag)
} }
}
})
return tags
}
function initTags() {
const res = filterAffixTags(routes.value)
affixTags.value = res
for (const tag of res) {
// Must have tag name
if (tag.name) {
useTagsViewStore().addVisitedView(tag)
} }
} }
}
function addTags() { function addTags() {
const { name } = route const { name } = route
if (name) { if (name) {
useTagsViewStore().addView(route) useTagsViewStore().addView(route)
}
} }
}
function moveToCurrentTag() {
function moveToCurrentTag() { nextTick(() => {
nextTick(() => { for (const r of visitedViews.value) {
for (const r of visitedViews.value) { if (r.path === route.path) {
if (r.path === route.path) { scrollPaneRef.value.moveToTarget(r)
scrollPaneRef.value.moveToTarget(r) // when query is different then update
// when query is different then update if (r.fullPath !== route.fullPath) {
if (r.fullPath !== route.fullPath) { useTagsViewStore().updateVisitedView(route)
useTagsViewStore().updateVisitedView(route) }
} }
} }
})
}
function refreshSelectedTag(view) {
proxy.$tab.refreshPage(view)
if (route.meta.link) {
useTagsViewStore().delIframeView(route)
} }
}) }
}
function refreshSelectedTag(view) { function closeSelectedTag(view) {
proxy.$tab.refreshPage(view) proxy.$tab.closePage(view).then(({ visitedViews }) => {
if (route.meta.link) { if (isActive(view)) {
useTagsViewStore().delIframeView(route) toLastView(visitedViews, view)
}
})
} }
}
function closeSelectedTag(view) { function closeRightTags() {
proxy.$tab.closePage(view).then(({ visitedViews }) => { proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
if (isActive(view)) { if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
toLastView(visitedViews, view) toLastView(visitedViews)
} }
}) })
} }
function closeRightTags() { function closeLeftTags() {
proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => { proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) { if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
toLastView(visitedViews) toLastView(visitedViews)
} }
}) })
} }
function closeLeftTags() { function closeOthersTags() {
proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => { router.push(selectedTag.value).catch(() => { })
if (!visitedViews.find(i => i.fullPath === route.fullPath)) { proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
toLastView(visitedViews) moveToCurrentTag()
} })
}) }
}
function closeOthersTags() { function closeAllTags(view) {
router.push(selectedTag.value).catch(() => { }) proxy.$tab.closeAllPage().then(({ visitedViews }) => {
proxy.$tab.closeOtherPage(selectedTag.value).then(() => { if (affixTags.value.some(tag => tag.path === route.path)) {
moveToCurrentTag() return
}) }
} toLastView(visitedViews, view)
})
}
function closeAllTags(view) { function toLastView(visitedViews, view) {
proxy.$tab.closeAllPage().then(({ visitedViews }) => { const latestView = visitedViews.slice(-1)[0]
if (affixTags.value.some(tag => tag.path === route.path)) { if (latestView) {
return router.push(latestView.fullPath)
}
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 { } else {
router.push('/') // now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
router.replace({ path: '/redirect' + view.fullPath })
} else {
router.push('/')
}
} }
} }
}
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 function openMenu(tag, e) {
visible.value = true const menuMinWidth = 105
selectedTag.value = tag 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
function closeMenu() { if (l > maxLeft) {
visible.value = false left.value = maxLeft
} } else {
left.value = l
function handleScroll() { }
closeMenu()
}
</script>
<style lang="scss" scoped> top.value = e.clientY
.tags-view-container { visible.value = true
height: 34px; selectedTag.value = tag
width: 100%; }
background: var(--tags-bg, #fff);
border-bottom: 1px solid var(--tags-item-border, #d8dce5);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper { function closeMenu() {
.tags-view-item { visible.value = false
display: inline-block; }
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid var(--tags-item-border, #d8dce5);
color: var(--tags-item-text, #495060);
background: var(--tags-item-bg, #fff);
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type { function handleScroll() {
margin-left: 15px; closeMenu()
} }
</script>
&:last-of-type { <style lang="scss" scoped>
margin-right: 15px; .tags-view-container {
} height: 34px;
width: 100%;
background: var(--tags-bg, #fff);
border-bottom: 1px solid var(--tags-item-border, #d8dce5);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(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 var(--tags-item-border, #d8dce5);
color: var(--tags-item-text, #495060);
background: var(--tags-item-bg, #fff);
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&.active { &:last-of-type {
background-color: #42b983; margin-right: 15px;
color: #fff; }
border-color: #42b983;
&::before { &.active {
content: ''; background-color: #42b983;
background: #fff; color: #fff;
display: inline-block; border-color: #42b983;
width: 8px;
height: 8px; &::before {
border-radius: 50%; content: '';
position: relative; background: #fff;
margin-right: 5px; display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 5px;
}
} }
} }
} }
}
.tags-view-item.active.has-icon::before { .tags-view-item.active.has-icon::before {
content: none !important; content: none !important;
} }
.contextmenu { .contextmenu {
margin: 0;
background: var(--el-bg-color-overlay, #fff);
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: var(--tags-item-text, #333);
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
border: 1px solid var(--el-border-color-light, #e4e7ed);
li {
margin: 0; margin: 0;
padding: 7px 16px; background: var(--el-bg-color-overlay, #fff);
cursor: pointer; z-index: 3000;
position: absolute;
&:hover { list-style-type: none;
background: var(--tags-item-hover, #eee); padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: var(--tags-item-text, #333);
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
border: 1px solid var(--el-border-color-light, #e4e7ed);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: var(--tags-item-hover, #eee);
}
} }
} }
} }
}
</style> </style>
<style lang="scss"> <style lang="scss">
//reset element css of el-icon-close //reset element css of el-icon-close
.tags-view-wrapper { .tags-view-wrapper {
.tags-view-item { .tags-view-item {
.el-icon-close { .el-icon-close {
width: 16px; width: 16px;
height: 16px; height: 16px;
vertical-align: 2px; vertical-align: 2px;
border-radius: 50%; border-radius: 50%;
text-align: center; text-align: center;
transition: all .3s cubic-bezier(.645, .045, .355, 1); transition: all .3s cubic-bezier(.645, .045, .355, 1);
transform-origin: 100% 50%; transform-origin: 100% 50%;
&:before { &:before {
transform: scale(.6); transform: scale(.6);
display: inline-block; display: inline-block;
vertical-align: -3px; vertical-align: -3px;
} }
&:hover { &:hover {
background-color: var(--tags-close-hover, #b4bccc); background-color: var(--tags-close-hover, #b4bccc);
color: #fff; color: #fff;
width: 12px !important; width: 12px !important;
height: 12px !important; height: 12px !important;
}
} }
} }
} }
}
</style> </style>

2
src/main.js

@ -27,7 +27,7 @@ import './permission' // permission control
import { useDict } from '@/utils/dict' import { useDict } from '@/utils/dict'
import { getConfigKey } from "@/api/system/config" import { getConfigKey } from "@/api/system/config"
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/mmxt'
// 分页组件 // 分页组件
import Pagination from '@/components/Pagination' import Pagination from '@/components/Pagination'

2
src/plugins/download.js

@ -3,7 +3,7 @@ import { ElLoading, ElMessage } from 'element-plus'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode' import errorCode from '@/utils/errorCode'
import { blobValidate } from '@/utils/ruoyi' import { blobValidate } from '@/utils/mmxt'
const baseURL = import.meta.env.VITE_APP_BASE_API const baseURL = import.meta.env.VITE_APP_BASE_API
let downloadLoadingInstance let downloadLoadingInstance

4
src/router/index.js

@ -1,4 +1,4 @@
import { createWebHistory, createRouter } from 'vue-router' import { createWebHashHistory, createRouter } from 'vue-router'
/* Layout */ /* Layout */
import Layout from '@/layout' import Layout from '@/layout'
@ -161,7 +161,7 @@ export const dynamicRoutes = [
] ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHashHistory(),
routes: constantRoutes, routes: constantRoutes,
scrollBehavior(to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
if (savedPosition) { if (savedPosition) {

4
src/settings.js

@ -23,7 +23,7 @@ export default {
* 是否显示 tagsView * 是否显示 tagsView
*/ */
tagsView: true, tagsView: true,
/** /**
* 显示页签图标 * 显示页签图标
*/ */
@ -52,6 +52,6 @@ export default {
/** /**
* 底部版权文本内容 * 底部版权文本内容
*/ */
footerContent: 'Copyright © 2018-2026 RuoYi. All Rights Reserved.' footerContent: 'Copyright © 2018-2026 mmxt. All Rights Reserved.'
} }

18
src/utils/index.js

@ -1,16 +1,16 @@
import { parseTime } from './ruoyi' import { parseTime } from './mmxt'
/** /**
* 表格时间格式化 * 表格时间格式化
*/ */
export function formatDate(cellValue) { export function formatDate(cellValue) {
if (cellValue == null || cellValue == "") return "" if (cellValue == null || cellValue == "") return ""
var date = new Date(cellValue) var date = new Date(cellValue)
var year = date.getFullYear() var year = date.getFullYear()
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
} }
@ -218,7 +218,7 @@ export function getTime(type) {
export function debounce(func, wait, immediate) { export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result let timeout, args, context, timestamp, result
const later = function() { const later = function () {
// 据上一次触发时间间隔 // 据上一次触发时间间隔
const last = +new Date() - timestamp const last = +new Date() - timestamp
@ -235,7 +235,7 @@ export function debounce(func, wait, immediate) {
} }
} }
return function(...args) { return function (...args) {
context = this context = this
timestamp = +new Date() timestamp = +new Date()
const callNow = immediate && !timeout const callNow = immediate && !timeout
@ -330,7 +330,7 @@ export function makeMap(str, expectsLowerCase) {
? val => map[val.toLowerCase()] ? val => map[val.toLowerCase()]
: val => map[val] : val => map[val]
} }
export const exportDefault = 'export default ' export const exportDefault = 'export default '
export const beautifierConf = { export const beautifierConf = {
@ -387,4 +387,4 @@ export function camelCase(str) {
export function isNumberStr(str) { export function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
} }

72
src/utils/request.js

@ -1,8 +1,8 @@
import axios from 'axios' import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus' import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode' import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/ruoyi' import { tansParams, blobValidate } from '@/utils/mmxt'
import cache from '@/plugins/cache' import cache from '@/plugins/cache'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
@ -29,6 +29,10 @@ service.interceptors.request.use(config => {
if (getToken() && !isToken) { if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
} }
// ✅ FormData 情况下不要设置 Content-Type
if (config.data instanceof FormData) {
delete config.headers['Content-Type']
}
// get请求映射params参数 // get请求映射params参数
if (config.method === 'get' && config.params) { if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params) let url = config.url + '?' + tansParams(config.params)
@ -67,46 +71,46 @@ service.interceptors.request.use(config => {
} }
return config return config
}, error => { }, error => {
console.log(error) console.log(error)
Promise.reject(error) Promise.reject(error)
}) })
// 响应拦截器 // 响应拦截器
service.interceptors.response.use(res => { service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态 // 未设置状态码则默认成功状态
const code = res.data.code || 200 const code = res.data.code || 200
// 获取错误信息 // 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default'] const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回 // 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data return res.data
} }
if (code === 401) { if (code === 401) {
if (!isRelogin.show) { if (!isRelogin.show) {
isRelogin.show = true isRelogin.show = true
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false isRelogin.show = false
useUserStore().logOut().then(() => { useUserStore().logOut().then(() => {
location.href = '/index' location.href = '/index'
}) })
}).catch(() => { }).catch(() => {
isRelogin.show = false isRelogin.show = false
}) })
} }
return Promise.reject('无效的会话,或者会话已过期,请重新登录。') return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) { } else if (code === 500) {
ElMessage({ message: msg, type: 'error' }) ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
} else if (code === 601) { } else if (code === 601) {
ElMessage({ message: msg, type: 'warning' }) ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
} else if (code !== 200) { } else if (code !== 200) {
ElNotification.error({ title: msg }) ElNotification.error({ title: msg })
return Promise.reject('error') return Promise.reject('error')
} else { } else {
return Promise.resolve(res.data) return Promise.resolve(res.data)
} }
}, },
error => { error => {
console.log('err' + error) console.log('err' + error)
let { message } = error let { message } = error

228
src/utils/ruoyi.js

@ -1,228 +0,0 @@
/**
* 通用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) {
if (value === undefined) {
return ""
}
var actions = []
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) {
actions.push(datas[key].label)
return true
}
})
if (actions.length === 0) {
actions.push(value)
}
return actions.join('')
}
// 回显数据字典(字符串、数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length ===0) {
return ""
}
if (Array.isArray(value)) {
value = value.join(",")
}
var actions = []
var currentSeparator = undefined === separator ? "," : separator
var temp = value.split(currentSeparator)
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) {
actions.push(datas[key].label + currentSeparator)
match = true
}
})
if (!match) {
actions.push(temp[val] + currentSeparator)
}
})
return actions.join('').substring(0, actions.join('').length - 1)
}
// 字符串格式化(%s )
export function sprintf(str) {
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 parseStrEmpty(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 tree = []
for (let d of data) {
let id = d[config.id]
childrenListMap[id] = d
if (!d[config.childrenList]) {
d[config.childrenList] = []
}
}
for (let d of data) {
let parentId = d[config.parentId]
let parentObj = childrenListMap[parentId]
if (!parentObj) {
tree.push(d)
} else {
parentObj[config.childrenList].push(d)
}
}
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 && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
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 function blobValidate(data) {
return data.type !== 'application/json'
}

1160
src/views/index.vue

File diff suppressed because it is too large

508
src/views/monitor/job/index.vue

@ -2,32 +2,17 @@
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="任务名称" prop="jobName"> <el-form-item label="任务名称" prop="jobName">
<el-input <el-input v-model="queryParams.jobName" placeholder="请输入任务名称" clearable style="width: 200px"
v-model="queryParams.jobName" @keyup.enter="handleQuery" />
placeholder="请输入任务名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="任务组名" prop="jobGroup"> <el-form-item label="任务组名" prop="jobGroup">
<el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable style="width: 200px"> <el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable style="width: 200px">
<el-option <el-option v-for="dict in sys_job_group" :key="dict.value" :label="dict.label" :value="dict.value" />
v-for="dict in sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="任务状态" prop="status"> <el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable style="width: 200px"> <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable style="width: 200px">
<el-option <el-option v-for="dict in sys_job_status" :key="dict.value" :label="dict.label" :value="dict.value" />
v-for="dict in sys_job_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -38,51 +23,24 @@
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="primary" plain icon="Plus" @click="handleAdd"
type="primary" v-hasPermi="['monitor:job:add']">新增</el-button>
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['monitor:job:add']"
>新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate"
type="success" v-hasPermi="['monitor:job:edit']">修改</el-button>
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['monitor:job:edit']"
>修改</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
type="danger" v-hasPermi="['monitor:job:remove']">删除</el-button>
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:job:remove']"
>删除</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="warning" plain icon="Download" @click="handleExport"
type="warning" v-hasPermi="['monitor:job:export']">导出</el-button>
plain
icon="Download"
@click="handleExport"
v-hasPermi="['monitor:job:export']"
>导出</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="info" plain icon="Operation" @click="handleJobLog"
type="info" v-hasPermi="['monitor:job:query']">日志</el-button>
plain
icon="Operation"
@click="handleJobLog"
v-hasPermi="['monitor:job:query']"
>日志</el-button>
</el-col> </el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
@ -100,42 +58,38 @@
<el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" /> <el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center"> <el-table-column label="状态" align="center">
<template #default="scope"> <template #default="scope">
<el-switch <el-switch v-model="scope.row.status" active-value="0" inactive-value="1"
v-model="scope.row.status" @change="handleStatusChange(scope.row)"></el-switch>
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['monitor:job:edit']"></el-button> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
v-hasPermi="['monitor:job:edit']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['monitor:job:remove']"></el-button> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-hasPermi="['monitor:job:remove']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="执行一次" placement="top"> <el-tooltip content="执行一次" placement="top">
<el-button link type="primary" icon="CaretRight" @click="handleRun(scope.row)" v-hasPermi="['monitor:job:changeStatus']"></el-button> <el-button link type="primary" icon="CaretRight" @click="handleRun(scope.row)"
v-hasPermi="['monitor:job:changeStatus']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="任务详细" placement="top"> <el-tooltip content="任务详细" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:job:query']"></el-button> <el-button link type="primary" icon="View" @click="handleView(scope.row)"
v-hasPermi="['monitor:job:query']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="调度日志" placement="top"> <el-tooltip content="调度日志" placement="top">
<el-button link type="primary" icon="Operation" @click="handleJobLog(scope.row)" v-hasPermi="['monitor:job:query']"></el-button> <el-button link type="primary" icon="Operation" @click="handleJobLog(scope.row)"
v-hasPermi="['monitor:job:query']"></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-show="total > 0" v-model:limit="queryParams.pageSize" @pagination="getList" />
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改定时任务对话框 --> <!-- 添加或修改定时任务对话框 -->
<el-dialog :title="title" v-model="open" width="820px" append-to-body> <el-dialog :title="title" v-model="open" width="820px" append-to-body>
@ -149,12 +103,8 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="任务分组" prop="jobGroup"> <el-form-item label="任务分组" prop="jobGroup">
<el-select v-model="form.jobGroup" placeholder="请选择"> <el-select v-model="form.jobGroup" placeholder="请选择">
<el-option <el-option v-for="dict in sys_job_group" :key="dict.value" :label="dict.label"
v-for="dict in sys_job_group" :value="dict.value"></el-option>
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -167,7 +117,7 @@
<template #content> <template #content>
<div> <div>
Bean调用示例ryTask.ryParams('ry') Bean调用示例ryTask.ryParams('ry')
<br />Class类调用示例com.ruoyi.quartz.task.RyTask.ryParams('ry') <br />Class类调用示例com.mmxt.quartz.task.RyTask.ryParams('ry')
<br />参数说明支持字符串布尔类型长整型浮点型整型 <br />参数说明支持字符串布尔类型长整型浮点型整型
</div> </div>
</template> </template>
@ -193,11 +143,8 @@
<el-col :span="24" v-if="form.jobId !== undefined"> <el-col :span="24" v-if="form.jobId !== undefined">
<el-form-item label="状态"> <el-form-item label="状态">
<el-radio-group v-model="form.status"> <el-radio-group v-model="form.status">
<el-radio <el-radio v-for="dict in sys_job_status" :key="dict.value" :value="dict.value">{{ dict.label
v-for="dict in sys_job_status" }}</el-radio>
:key="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -228,9 +175,9 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog title="Cron表达式生成器" v-model="openCron" append-to-body destroy-on-close> <el-dialog title="Cron表达式生成器" v-model="openCron" append-to-body destroy-on-close>
<crontab ref="crontabRef" @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab> <crontab ref="crontabRef" @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
</el-dialog> </el-dialog>
<!-- 任务日志详细 --> <!-- 任务日志详细 -->
<el-dialog title="任务详细" v-model="openView" width="700px" append-to-body> <el-dialog title="任务详细" v-model="openView" width="700px" append-to-body>
@ -285,218 +232,219 @@
</template> </template>
<script setup name="Job"> <script setup name="Job">
import Crontab from '@/components/Crontab' import Crontab from '@/components/Crontab'
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job" import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job"
const router = useRouter() const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status") const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status")
const jobList = ref([]) const jobList = ref([])
const open = ref(false) const open = ref(false)
const loading = ref(true) const loading = ref(true)
const showSearch = ref(true) const showSearch = ref(true)
const ids = ref([]) const ids = ref([])
const single = ref(true) const single = ref(true)
const multiple = ref(true) const multiple = ref(true)
const total = ref(0) const total = ref(0)
const title = ref("") const title = ref("")
const openView = ref(false) const openView = ref(false)
const openCron = ref(false) const openCron = ref(false)
const expression = ref("") const expression = ref("")
const data = reactive({ const data = reactive({
form: {}, form: {},
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
jobName: undefined, jobName: undefined,
jobGroup: undefined, jobGroup: undefined,
status: undefined status: undefined
}, },
rules: { rules: {
jobName: [{ required: true, message: "任务名称不能为空", trigger: "blur" }], jobName: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
invokeTarget: [{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }], invokeTarget: [{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }],
cronExpression: [{ required: true, message: "cron执行表达式不能为空", trigger: "change" }] cronExpression: [{ required: true, message: "cron执行表达式不能为空", trigger: "change" }]
} }
}) })
const { queryParams, form, rules } = toRefs(data) const { queryParams, form, rules } = toRefs(data)
/** 查询定时任务列表 */ /** 查询定时任务列表 */
function getList() { function getList() {
loading.value = true loading.value = true
listJob(queryParams.value).then(response => { listJob(queryParams.value).then(response => {
jobList.value = response.rows jobList.value = response.rows
total.value = response.total total.value = response.total
loading.value = false loading.value = false
}) })
} }
/** 任务组名字典翻译 */ /** 任务组名字典翻译 */
function jobGroupFormat(row, column) { function jobGroupFormat(row, column) {
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup) return proxy.selectDictLabel(sys_job_group.value, row.jobGroup)
} }
/** 取消按钮 */ /** 取消按钮 */
function cancel() { function cancel() {
open.value = false open.value = false
reset() reset()
} }
/** 表单重置 */ /** 表单重置 */
function reset() { function reset() {
form.value = { form.value = {
jobId: undefined, jobId: undefined,
jobName: undefined, jobName: undefined,
jobGroup: undefined, jobGroup: undefined,
invokeTarget: undefined, invokeTarget: undefined,
cronExpression: undefined, cronExpression: undefined,
misfirePolicy: 1, misfirePolicy: 1,
concurrent: 1, concurrent: 1,
status: "0" status: "0"
} }
proxy.resetForm("jobRef") proxy.resetForm("jobRef")
} }
/** 搜索按钮操作 */ /** 搜索按钮操作 */
function handleQuery() { function handleQuery() {
queryParams.value.pageNum = 1 queryParams.value.pageNum = 1
getList() getList()
} }
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
proxy.resetForm("queryRef") proxy.resetForm("queryRef")
handleQuery() handleQuery()
} }
// //
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
ids.value = selection.map(item => item.jobId) ids.value = selection.map(item => item.jobId)
single.value = selection.length != 1 single.value = selection.length != 1
multiple.value = !selection.length multiple.value = !selection.length
} }
// //
function handleCommand(command, row) { function handleCommand(command, row) {
switch (command) { switch (command) {
case "handleRun": case "handleRun":
handleRun(row) handleRun(row)
break break
case "handleView": case "handleView":
handleView(row) handleView(row)
break break
case "handleJobLog": case "handleJobLog":
handleJobLog(row) handleJobLog(row)
break break
default: default:
break break
} }
} }
// //
function handleStatusChange(row) { function handleStatusChange(row) {
let text = row.status === "0" ? "启用" : "停用" let text = row.status === "0" ? "启用" : "停用"
proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () { proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
return changeJobStatus(row.jobId, row.status) return changeJobStatus(row.jobId, row.status)
}).then(() => { }).then(() => {
proxy.$modal.msgSuccess(text + "成功") proxy.$modal.msgSuccess(text + "成功")
}).catch(function () { }).catch(function () {
row.status = row.status === "0" ? "1" : "0" row.status = row.status === "0" ? "1" : "0"
}) })
} }
/* 立即执行一次 */ /* 立即执行一次 */
function handleRun(row) { function handleRun(row) {
proxy.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () { proxy.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () {
return runJob(row.jobId, row.jobGroup) return runJob(row.jobId, row.jobGroup)
}).then(() => { }).then(() => {
proxy.$modal.msgSuccess("执行成功")}) proxy.$modal.msgSuccess("执行成功")
.catch(() => {}) })
} .catch(() => { })
}
/** 任务详细信息 */ /** 任务详细信息 */
function handleView(row) { function handleView(row) {
getJob(row.jobId).then(response => { getJob(row.jobId).then(response => {
form.value = response.data form.value = response.data
openView.value = true openView.value = true
}) })
} }
/** cron表达式按钮操作 */ /** cron表达式按钮操作 */
function handleShowCron() { function handleShowCron() {
expression.value = form.value.cronExpression expression.value = form.value.cronExpression
openCron.value = true openCron.value = true
} }
/** 确定后回传值 */ /** 确定后回传值 */
function crontabFill(value) { function crontabFill(value) {
form.value.cronExpression = value form.value.cronExpression = value
} }
/** 任务日志列表查询 */ /** 任务日志列表查询 */
function handleJobLog(row) { function handleJobLog(row) {
const jobId = row.jobId || 0 const jobId = row.jobId || 0
router.push('/monitor/job-log/index/' + jobId) router.push('/monitor/job-log/index/' + jobId)
} }
/** 新增按钮操作 */ /** 新增按钮操作 */
function handleAdd() { function handleAdd() {
reset() reset()
open.value = true open.value = true
title.value = "添加任务" title.value = "添加任务"
} }
/** 修改按钮操作 */ /** 修改按钮操作 */
function handleUpdate(row) { function handleUpdate(row) {
reset() reset()
const jobId = row.jobId || ids.value const jobId = row.jobId || ids.value
getJob(jobId).then(response => { getJob(jobId).then(response => {
form.value = response.data form.value = response.data
open.value = true open.value = true
title.value = "修改任务" title.value = "修改任务"
}) })
} }
/** 提交按钮 */ /** 提交按钮 */
function submitForm() { function submitForm() {
proxy.$refs["jobRef"].validate(valid => { proxy.$refs["jobRef"].validate(valid => {
if (valid) { if (valid) {
if (form.value.jobId != undefined) { if (form.value.jobId != undefined) {
updateJob(form.value).then(response => { updateJob(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功") proxy.$modal.msgSuccess("修改成功")
open.value = false open.value = false
getList() getList()
}) })
} else { } else {
addJob(form.value).then(response => { addJob(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功") proxy.$modal.msgSuccess("新增成功")
open.value = false open.value = false
getList() getList()
}) })
} }
} }
}) })
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
function handleDelete(row) { function handleDelete(row) {
const jobIds = row.jobId || ids.value const jobIds = row.jobId || ids.value
proxy.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () { proxy.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () {
return delJob(jobIds) return delJob(jobIds)
}).then(() => { }).then(() => {
getList() getList()
proxy.$modal.msgSuccess("删除成功") proxy.$modal.msgSuccess("删除成功")
}).catch(() => {}) }).catch(() => { })
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
function handleExport() { function handleExport() {
proxy.download("monitor/job/export", { proxy.download("monitor/job/export", {
...queryParams.value, ...queryParams.value,
}, `job_${new Date().getTime()}.xlsx`) }, `job_${new Date().getTime()}.xlsx`)
} }
getList() getList()
</script> </script>

171
src/views/tool/gen/editTable.vue

@ -6,19 +6,15 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="字段信息" name="columnInfo"> <el-tab-pane label="字段信息" name="columnInfo">
<el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight"> <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
<el-table-column label="序号" type="index" min-width="5%" class-name="allowDrag"/> <el-table-column label="序号" type="index" min-width="5%" class-name="allowDrag" />
<el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true" class-name="allowDrag"/> <el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true"
class-name="allowDrag" />
<el-table-column label="字段描述" min-width="10%"> <el-table-column label="字段描述" min-width="10%">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.columnComment"></el-input> <el-input v-model="scope.row.columnComment"></el-input>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column label="物理类型" prop="columnType" min-width="10%" :show-overflow-tooltip="true" />
label="物理类型"
prop="columnType"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="Java类型" min-width="11%"> <el-table-column label="Java类型" min-width="11%">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.javaType"> <el-select v-model="scope.row.javaType">
@ -95,14 +91,11 @@
<el-table-column label="字典类型" min-width="12%"> <el-table-column label="字典类型" min-width="12%">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择"> <el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option <el-option v-for="dict in dictOptions" :key="dict.dictType" :label="dict.dictName"
v-for="dict in dictOptions"
:key="dict.dictType"
:label="dict.dictName"
:value="dict.dictType"> :value="dict.dictType">
<span style="float: left">{{ dict.dictName }}</span> <span style="float: left">{{ dict.dictName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span> <span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
</el-option> </el-option>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
@ -122,90 +115,90 @@
</template> </template>
<script setup name="GenEdit"> <script setup name="GenEdit">
import { getGenTable, updateGenTable } from "@/api/tool/gen" import { getGenTable, updateGenTable } from "@/api/tool/gen"
import { optionselect as getDictOptionselect } from "@/api/system/dict/type" import { optionselect as getDictOptionselect } from "@/api/system/dict/type"
import basicInfoForm from "./basicInfoForm" import basicInfoForm from "./basicInfoForm"
import genInfoForm from "./genInfoForm" import genInfoForm from "./genInfoForm"
import Sortable from 'sortablejs' // import Sortable from 'sortablejs'
const route = useRoute() const route = useRoute()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const activeName = ref("columnInfo") const activeName = ref("columnInfo")
const tableHeight = ref(document.documentElement.scrollHeight - 245 + "px") const tableHeight = ref(document.documentElement.scrollHeight - 245 + "px")
const tables = ref([]) const tables = ref([])
const columns = ref([]) const columns = ref([])
const dictOptions = ref([]) const dictOptions = ref([])
const info = ref({}) const info = ref({})
/** 提交按钮 */ /** 提交按钮 */
function submitForm() { function submitForm() {
const basicForm = proxy.$refs.basicInfo.$refs.basicInfoForm const basicForm = proxy.$refs.basicInfo.$refs.basicInfoForm
const genForm = proxy.$refs.genInfo.$refs.genInfoForm const genForm = proxy.$refs.genInfo.$refs.genInfoForm
Promise.all([basicForm, genForm].map(getFormPromise)).then(res => { Promise.all([basicForm, genForm].map(getFormPromise)).then(res => {
const validateResult = res.every(item => !!item) const validateResult = res.every(item => !!item)
if (validateResult) { if (validateResult) {
const genTable = Object.assign({}, info.value) const genTable = Object.assign({}, info.value)
genTable.columns = columns.value genTable.columns = columns.value
genTable.params = { genTable.params = {
treeCode: info.value.treeCode, treeCode: info.value.treeCode,
treeName: info.value.treeName, treeName: info.value.treeName,
treeParentCode: info.value.treeParentCode, treeParentCode: info.value.treeParentCode,
parentMenuId: info.value.parentMenuId parentMenuId: info.value.parentMenuId
}
updateGenTable(genTable).then(res => {
proxy.$modal.msgSuccess(res.msg)
if (res.code === 200) {
close()
} }
}) updateGenTable(genTable).then(res => {
} else { proxy.$modal.msgSuccess(res.msg)
proxy.$modal.msgError("表单校验未通过,请重新检查提交内容") if (res.code === 200) {
} close()
}) }
} })
} else {
function getFormPromise(form) { proxy.$modal.msgError("表单校验未通过,请重新检查提交内容")
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)
}
(() => { function getFormPromise(form) {
const tableId = route.params && route.params.tableId return new Promise(resolve => {
if (tableId) { form.validate(res => {
// resolve(res)
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
}) })
} }
})()
// function close() {
onMounted(() => { const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: route.query.pageNum } }
const element = document.querySelector('.el-table__body > tbody') proxy.$tab.closeOpenPage(obj)
Sortable.create(element, { }
handle: ".allowDrag",
onEnd: (evt) => { (() => {
const targetRow = columns.value.splice(evt.oldIndex, 1)[0] const tableId = route.params && route.params.tableId
columns.value.splice(evt.newIndex, 0, targetRow) if (tableId) {
for (const index in columns.value) { //
columns.value[index].sort = parseInt(index) + 1 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
})
} }
})()
//
onMounted(() => {
const element = document.querySelector('.el-table__body > tbody')
// Sortable.create(element, {
// handle: ".allowDrag",
// onEnd: (evt) => {
// const targetRow = columns.value.splice(evt.oldIndex, 1)[0]
// columns.value.splice(evt.newIndex, 0, targetRow)
// for (const index in columns.value) {
// columns.value[index].sort = parseInt(index) + 1
// }
// }
// })
}) })
}) </script>
</script>

165
src/views/tool/gen/genInfoForm.vue

@ -26,7 +26,7 @@
<el-form-item prop="packageName"> <el-form-item prop="packageName">
<template #label> <template #label>
生成包路径 生成包路径
<el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top"> <el-tooltip content="生成在哪个java包下,例如 com.mmxt.system" placement="top">
<el-icon><question-filled /></el-icon> <el-icon><question-filled /></el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
@ -91,14 +91,9 @@
<el-icon><question-filled /></el-icon> <el-icon><question-filled /></el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
<el-tree-select <el-tree-select v-model="info.parentMenuId" :data="menuOptions"
v-model="info.parentMenuId" :props="{ value: 'menuId', label: 'menuName', children: 'children' }" value-key="menuId"
:data="menuOptions" placeholder="请选择系统菜单" check-strictly />
:props="{ value: 'menuId', label: 'menuName', children: 'children' }"
value-key="menuId"
placeholder="请选择系统菜单"
check-strictly
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -128,7 +123,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<template v-if="info.tplCategory == 'tree'"> <template v-if="info.tplCategory == 'tree'">
<h4 class="form-header">其他信息</h4> <h4 class="form-header">其他信息</h4>
<el-row v-show="info.tplCategory == 'tree'"> <el-row v-show="info.tplCategory == 'tree'">
@ -141,12 +136,8 @@
</el-tooltip> </el-tooltip>
</template> </template>
<el-select v-model="info.treeCode" placeholder="请选择"> <el-select v-model="info.treeCode" placeholder="请选择">
<el-option <el-option v-for="(column, index) in info.columns" :key="index"
v-for="(column, index) in info.columns" :label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -159,12 +150,8 @@
</el-tooltip> </el-tooltip>
</template> </template>
<el-select v-model="info.treeParentCode" placeholder="请选择"> <el-select v-model="info.treeParentCode" placeholder="请选择">
<el-option <el-option v-for="(column, index) in info.columns" :key="index"
v-for="(column, index) in info.columns" :label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -177,12 +164,8 @@
</el-tooltip> </el-tooltip>
</template> </template>
<el-select v-model="info.treeName" placeholder="请选择"> <el-select v-model="info.treeName" placeholder="请选择">
<el-option <el-option v-for="(column, index) in info.columns" :key="index"
v-for="(column, index) in info.columns" :label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -201,12 +184,8 @@
</el-tooltip> </el-tooltip>
</template> </template>
<el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange"> <el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
<el-option <el-option v-for="(table, index) in tables" :key="index"
v-for="(table, index) in tables" :label="table.tableName + ':' + table.tableComment" :value="table.tableName"></el-option>
:key="index"
:label="table.tableName + ':' + table.tableComment"
:value="table.tableName"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -219,12 +198,8 @@
</el-tooltip> </el-tooltip>
</template> </template>
<el-select v-model="info.subTableFkName" placeholder="请选择"> <el-select v-model="info.subTableFkName" placeholder="请选择">
<el-option <el-option v-for="(column, index) in subColumns" :key="index"
v-for="(column, index) in subColumns" :label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -235,71 +210,71 @@
</template> </template>
<script setup> <script setup>
import { listMenu } from "@/api/system/menu" import { listMenu } from "@/api/system/menu"
const subColumns = ref([])
const menuOptions = ref([])
const { proxy } = getCurrentInstance()
const props = defineProps({ const subColumns = ref([])
info: { const menuOptions = ref([])
type: Object, const { proxy } = getCurrentInstance()
default: null
},
tables: {
type: Array,
default: null
}
})
// const props = defineProps({
const rules = ref({ info: {
tplCategory: [{ required: true, message: "请选择生成模板", trigger: "blur" }], type: Object,
packageName: [{ required: true, message: "请输入生成包路径", trigger: "blur" }], default: null
moduleName: [{ required: true, message: "请输入生成模块名", trigger: "blur" }], },
businessName: [{ required: true, message: "请输入生成业务名", trigger: "blur" }], tables: {
functionName: [{ required: true, message: "请输入生成功能名", trigger: "blur" }] type: Array,
}) default: null
}
})
function subSelectChange(value) { //
props.info.subTableFkName = "" 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 tplSelectChange(value) { function subSelectChange(value) {
if (value !== "sub") {
props.info.subTableName = ""
props.info.subTableFkName = "" props.info.subTableFkName = ""
} }
}
function setSubTableColumns(value) { function tplSelectChange(value) {
for (var item in props.tables) { if (value !== "sub") {
const name = props.tables[item].tableName props.info.subTableName = ""
if (value === name) { props.info.subTableFkName = ""
subColumns.value = props.tables[item].columns
break
} }
} }
}
/** 查询菜单下拉树结构 */ function setSubTableColumns(value) {
function getMenuTreeselect() { for (var item in props.tables) {
listMenu().then(response => { const name = props.tables[item].tableName
menuOptions.value = proxy.handleTree(response.data, "menuId") if (value === name) {
}) subColumns.value = props.tables[item].columns
} break
}
}
}
onMounted(() => { /** 查询菜单下拉树结构 */
getMenuTreeselect() function getMenuTreeselect() {
}) listMenu().then(response => {
menuOptions.value = proxy.handleTree(response.data, "menuId")
})
}
watch(() => props.info.subTableName, val => { onMounted(() => {
setSubTableColumns(val) getMenuTreeselect()
}) })
watch(() => props.info.tplWebType, val => { watch(() => props.info.subTableName, val => {
if (val === '') { setSubTableColumns(val)
props.info.tplWebType = "element-plus" })
}
}) watch(() => props.info.tplWebType, val => {
</script> if (val === '') {
props.info.tplWebType = "element-plus"
}
})
</script>

398
src/views/tool/gen/index.vue

@ -2,32 +2,16 @@
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="表名称" prop="tableName"> <el-form-item label="表名称" prop="tableName">
<el-input <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable style="width: 200px"
v-model="queryParams.tableName" @keyup.enter="handleQuery" />
placeholder="请输入表名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="表描述" prop="tableComment"> <el-form-item label="表描述" prop="tableComment">
<el-input <el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable style="width: 200px"
v-model="queryParams.tableComment" @keyup.enter="handleQuery" />
placeholder="请输入表描述"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="创建时间" style="width: 308px"> <el-form-item label="创建时间" style="width: 308px">
<el-date-picker <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-"
v-model="dateRange" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@ -37,57 +21,29 @@
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="primary" plain icon="Download" :disabled="multiple" @click="handleGenTable"
type="primary" v-hasPermi="['tool:gen:code']">生成</el-button>
plain
icon="Download"
:disabled="multiple"
@click="handleGenTable"
v-hasPermi="['tool:gen:code']"
>生成</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="primary" plain icon="Plus" @click="openCreateTable" v-hasRole="['admin']">创建</el-button>
type="primary"
plain
icon="Plus"
@click="openCreateTable"
v-hasRole="['admin']"
>创建</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="info" plain icon="Upload" @click="openImportTable"
type="info" v-hasPermi="['tool:gen:import']">导入</el-button>
plain
icon="Upload"
@click="openImportTable"
v-hasPermi="['tool:gen:import']"
>导入</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="success" plain icon="Edit" :disabled="single" @click="handleEditTable"
type="success" v-hasPermi="['tool:gen:edit']">修改</el-button>
plain
icon="Edit"
:disabled="single"
@click="handleEditTable"
v-hasPermi="['tool:gen:edit']"
>修改</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
type="danger" v-hasPermi="['tool:gen:remove']">删除</el-button>
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['tool:gen:remove']"
>删除</el-button>
</el-col> </el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
<el-table ref="genRef" v-loading="loading" :data="tableList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange"> <el-table ref="genRef" v-loading="loading" :data="tableList" @selection-change="handleSelectionChange"
:default-sort="defaultSort" @sort-change="handleSortChange">
<el-table-column type="selection" align="center" width="55"></el-table-column> <el-table-column type="selection" align="center" width="55"></el-table-column>
<el-table-column label="序号" type="index" width="50" align="center"> <el-table-column label="序号" type="index" width="50" align="center">
<template #default="scope"> <template #default="scope">
@ -97,45 +53,45 @@
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" /> <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="tableComment" :show-overflow-tooltip="true" />
<el-table-column label="实体" align="center" prop="className" :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="160" sortable="custom" :sort-orders="['descending', 'ascending']" /> <el-table-column label="创建时间" align="center" prop="createTime" width="160" sortable="custom"
<el-table-column label="更新时间" align="center" prop="updateTime" width="160" sortable="custom" :sort-orders="['descending', 'ascending']" /> :sort-orders="['descending', 'ascending']" />
<el-table-column label="更新时间" align="center" prop="updateTime" width="160" sortable="custom"
:sort-orders="['descending', 'ascending']" />
<el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="预览" placement="top"> <el-tooltip content="预览" placement="top">
<el-button link type="primary" icon="View" @click="handlePreview(scope.row)" v-hasPermi="['tool:gen:preview']"></el-button> <el-button link type="primary" icon="View" @click="handlePreview(scope.row)"
v-hasPermi="['tool:gen:preview']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="编辑" placement="top"> <el-tooltip content="编辑" placement="top">
<el-button link type="primary" icon="Edit" @click="handleEditTable(scope.row)" v-hasPermi="['tool:gen:edit']"></el-button> <el-button link type="primary" icon="Edit" @click="handleEditTable(scope.row)"
v-hasPermi="['tool:gen:edit']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['tool:gen:remove']"></el-button> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-hasPermi="['tool:gen:remove']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="同步" placement="top"> <el-tooltip content="同步" placement="top">
<el-button link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['tool:gen:edit']"></el-button> <el-button link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)"
v-hasPermi="['tool:gen:edit']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="生成代码" placement="top"> <el-tooltip content="生成代码" placement="top">
<el-button link type="primary" icon="Download" @click="handleGenTable(scope.row)" v-hasPermi="['tool:gen:code']"></el-button> <el-button link type="primary" icon="Download" @click="handleGenTable(scope.row)"
v-hasPermi="['tool:gen:code']"></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination v-show="total>0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
v-show="total>0" @pagination="getList" />
: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-dialog :title="preview.title" v-model="preview.open" width="80%" top="5vh" append-to-body class="scrollbar">
<el-tabs v-model="preview.activeName"> <el-tabs v-model="preview.activeName">
<el-tab-pane <el-tab-pane v-for="(value, key) in preview.data"
v-for="(value, key) in preview.data"
:label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" :label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
:name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" :name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" :key="value">
:key="value" <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess"
> style="float:right">&nbsp;复制</el-link>
<el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">&nbsp;复制</el-link>
<pre>{{ value }}</pre> <pre>{{ value }}</pre>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -146,164 +102,164 @@
</template> </template>
<script setup name="Gen"> <script setup name="Gen">
import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen" import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen"
import router from "@/router" import router from "@/router"
import importTable from "./importTable" import importTable from "./importTable"
import createTable from "./createTable" import createTable from "./createTable"
const route = useRoute() const route = useRoute()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const tableList = ref([]) const tableList = ref([])
const loading = ref(true) const loading = ref(true)
const showSearch = ref(true) const showSearch = ref(true)
const ids = ref([]) const ids = ref([])
const single = ref(true) const single = ref(true)
const multiple = ref(true) const multiple = ref(true)
const total = ref(0) const total = ref(0)
const tableNames = ref([]) const tableNames = ref([])
const dateRange = ref([]) const dateRange = ref([])
const uniqueId = ref("") const uniqueId = ref("")
const defaultSort = ref({ prop: "createTime", order: "descending" }) const defaultSort = ref({ prop: "createTime", order: "descending" })
const data = reactive({ const data = reactive({
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
tableName: undefined, tableName: undefined,
tableComment: undefined, tableComment: undefined,
orderByColumn: defaultSort.value.prop, orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order isAsc: defaultSort.value.order
}, },
preview: { preview: {
open: false, open: false,
title: "代码预览", title: "代码预览",
data: {}, data: {},
activeName: "domain.java" activeName: "domain.java"
} }
}) })
const { queryParams, preview } = toRefs(data)
onActivated(() => { const { queryParams, preview } = toRefs(data)
const time = route.query.t
if (time != null && time != uniqueId.value) {
uniqueId.value = time
queryParams.value.pageNum = Number(route.query.pageNum)
dateRange.value = []
proxy.resetForm("queryForm")
getList()
}
})
/** 查询表集合 */ onActivated(() => {
function getList() { const time = route.query.t
loading.value = true if (time != null && time != uniqueId.value) {
listTable(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => { uniqueId.value = time
tableList.value = response.rows queryParams.value.pageNum = Number(route.query.pageNum)
total.value = response.total dateRange.value = []
loading.value = false proxy.resetForm("queryForm")
getList()
}
}) })
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 生成代码操作 */ /** 查询表集合 */
function handleGenTable(row) { function getList() {
const tbNames = row.tableName || tableNames.value loading.value = true
if (tbNames == "") { listTable(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
proxy.$modal.msgError("请选择要生成的数据") tableList.value = response.rows
return total.value = response.total
} loading.value = false
if (row.genType === "1") {
genCode(row.tableName).then(response => {
proxy.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath)
}) })
} else {
const zipName = Array.isArray(tbNames) ? "ruoyi.zip" : tbNames + ".zip"
proxy.$download.zip("/tool/gen/batchGenCode?tables=" + tbNames, zipName)
} }
}
/** 同步数据库操作 */ /** 搜索按钮操作 */
function handleSynchDb(row) { function handleQuery() {
const tableName = row.tableName queryParams.value.pageNum = 1
proxy.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function () { getList()
return synchDb(tableName) }
}).then(() => {
proxy.$modal.msgSuccess("同步成功")
}).catch(() => {})
}
/** 打开导入表弹窗 */ /** 生成代码操作 */
function openImportTable() { function handleGenTable(row) {
proxy.$refs["importRef"].show() 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 {
const zipName = Array.isArray(tbNames) ? "mmxt.zip" : tbNames + ".zip"
proxy.$download.zip("/tool/gen/batchGenCode?tables=" + tbNames, zipName)
}
}
/** 打开创建表弹窗 */ /** 同步数据库操作 */
function openCreateTable() { function handleSynchDb(row) {
proxy.$refs["createRef"].show() const tableName = row.tableName
} proxy.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function () {
return synchDb(tableName)
}).then(() => {
proxy.$modal.msgSuccess("同步成功")
}).catch(() => { })
}
/** 重置按钮操作 */ /** 打开导入表弹窗 */
function resetQuery() { function openImportTable() {
dateRange.value = [] proxy.$refs["importRef"].show()
proxy.resetForm("queryRef") }
queryParams.value.pageNum = 1
proxy.$refs["genRef"].sort(defaultSort.value.prop, defaultSort.value.order)
}
/** 预览按钮 */ /** 打开创建表弹窗 */
function handlePreview(row) { function openCreateTable() {
previewTable(row.tableId).then(response => { proxy.$refs["createRef"].show()
preview.value.data = response.data }
preview.value.open = true
preview.value.activeName = "domain.java"
})
}
/** 复制代码成功 */ /** 重置按钮操作 */
function copyTextSuccess() { function resetQuery() {
proxy.$modal.msgSuccess("复制成功") dateRange.value = []
} proxy.resetForm("queryRef")
queryParams.value.pageNum = 1
proxy.$refs["genRef"].sort(defaultSort.value.prop, defaultSort.value.order)
}
// /** 预览按钮 */
function handleSelectionChange(selection) { function handlePreview(row) {
ids.value = selection.map(item => item.tableId) previewTable(row.tableId).then(response => {
tableNames.value = selection.map(item => item.tableName) preview.value.data = response.data
single.value = selection.length != 1 preview.value.open = true
multiple.value = !selection.length preview.value.activeName = "domain.java"
} })
}
/** 排序触发事件 */ /** 复制代码成功 */
function handleSortChange(column, prop, order) { function copyTextSuccess() {
queryParams.value.orderByColumn = column.prop proxy.$modal.msgSuccess("复制成功")
queryParams.value.isAsc = column.order }
getList()
}
/** 修改按钮操作 */ //
function handleEditTable(row) { function handleSelectionChange(selection) {
const tableId = row.tableId || ids.value[0] ids.value = selection.map(item => item.tableId)
const tableName = row.tableName || tableNames.value[0] tableNames.value = selection.map(item => item.tableName)
const params = { pageNum: queryParams.value.pageNum } single.value = selection.length != 1
proxy.$tab.openPage("修改[" + tableName + "]生成配置", '/tool/gen-edit/index/' + tableId, params) multiple.value = !selection.length
} }
/** 删除按钮操作 */ /** 排序触发事件 */
function handleDelete(row) { function handleSortChange(column, prop, order) {
const tableIds = row.tableId || ids.value queryParams.value.orderByColumn = column.prop
proxy.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?').then(function () { queryParams.value.isAsc = column.order
return delTable(tableIds)
}).then(() => {
getList() getList()
proxy.$modal.msgSuccess("删除成功") }
}).catch(() => {})
} /** 修改按钮操作 */
function handleEditTable(row) {
const tableId = row.tableId || ids.value[0]
const tableName = row.tableName || tableNames.value[0]
const params = { pageNum: queryParams.value.pageNum }
proxy.$tab.openPage("修改[" + tableName + "]生成配置", '/tool/gen-edit/index/' + tableId, params)
}
getList() /** 删除按钮操作 */
</script> 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>

11
vite.config.js

@ -2,7 +2,8 @@ import { defineConfig, loadEnv } from 'vite'
import path from 'path' import path from 'path'
import createVitePlugins from './vite/plugins' import createVitePlugins from './vite/plugins'
const baseUrl = 'http://localhost:8080' // 后端接口 const baseUrl = 'http://192.168.0.107:9020' // 后端接口
// const baseUrl = 'https://mmxt.arts-press.com/admin' // 后端接口
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => { export default defineConfig(({ mode, command }) => {
@ -11,8 +12,8 @@ export default defineConfig(({ mode, command }) => {
return { return {
// 部署生产环境和开发环境下的URL。 // 部署生产环境和开发环境下的URL。
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 // 例如 https://www.mmxt.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.mmxt.vip/admin/,则设置 baseUrl 为 /admin/。
base: VITE_APP_ENV === 'production' ? '/' : '/', base: VITE_APP_ENV === 'production' ? './' : '/',
plugins: createVitePlugins(env, command === 'build'), plugins: createVitePlugins(env, command === 'build'),
resolve: { resolve: {
// https://cn.vitejs.dev/config/#resolve-alias // https://cn.vitejs.dev/config/#resolve-alias
@ -52,8 +53,8 @@ export default defineConfig(({ mode, command }) => {
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '') rewrite: (p) => p.replace(/^\/dev-api/, '')
}, },
// springdoc proxy // springdoc proxy
'^/v3/api-docs/(.*)': { '^/v3/api-docs/(.*)': {
target: baseUrl, target: baseUrl,
changeOrigin: true, changeOrigin: true,
} }

2
vite/plugins/compression.js

@ -6,7 +6,7 @@ export default function createCompression(env) {
if (VITE_BUILD_COMPRESS) { if (VITE_BUILD_COMPRESS) {
const compressList = VITE_BUILD_COMPRESS.split(',') const compressList = VITE_BUILD_COMPRESS.split(',')
if (compressList.includes('gzip')) { if (compressList.includes('gzip')) {
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 // http://doc.mmxt.vip/mmxt-vue/other/faq.html#使用gzip解压缩静态文件
plugin.push( plugin.push(
compression({ compression({
ext: '.gz', ext: '.gz',

Loading…
Cancel
Save