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_BASE_API = '/dev-api'

4
.env.production

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

4
.env.staging

@ -1,10 +1,10 @@
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 美美学堂管理系统
# 生产环境配置
VITE_APP_ENV = 'staging'
# 若依管理系统/生产环境
# 美美学堂管理系统/生产环境
VITE_APP_BASE_API = '/stage-api'
# 是否在打包时开启压缩,支持 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)
Copyright (c) 2018 RuoYi
Copyright (c) 2018 mmxt
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

24
README.md

@ -1,29 +1,29 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</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>
<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/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-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/stargazers"><img src="https://gitee.com/y_project/mmxt-Vue/badge/star.svg?theme=dark"></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/mmxt-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介
* 本仓库为前端技术栈 [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) 版本。
* 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
* 配套后端代码仓库地址[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)),请移步[mmxt-Vue](https://gitee.com/y_project/mmxt-Vue/tree/master/mmxt-ui)。
* 阿里云折扣场:[点我进入](http://aly.mmxt.vip),腾讯云秒杀场:[点我进入](http://txy.mmxt.vip)&nbsp;&nbsp;
## 前端运行
```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
@ -62,8 +62,8 @@ yarn dev
- admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址:http://vue.ruoyi.vip
文档地址:http://doc.ruoyi.vip
演示地址:http://vue.mmxt.vip
文档地址:http://doc.mmxt.vip
## 演示图
@ -103,6 +103,6 @@ yarn dev
</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) 点击按钮入群。

10
package.json

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

2
src/api/system/user.js

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

2
src/assets/styles/index.scss

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

375
src/components/HeaderSearch/index.vue

@ -1,30 +1,16 @@
<template>
<div class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-dialog
v-model="show"
width="600"
@close="close"
: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-dialog v-model="show" width="600" @close="close" :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>
<div class="result-wrap">
<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">
<svg-icon class="menu-icon" :icon-class="item.icon" />
</div>
@ -36,7 +22,7 @@
{{ item.path }}
</div>
</div>
<svg-icon icon-class="enter" v-show="index === activeIndex"/>
<svg-icon icon-class="enter" v-show="index === activeIndex" />
</div>
</el-scrollbar>
</div>
@ -45,208 +31,209 @@
</template>
<script setup>
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
const search = ref('')
const options = ref([])
const searchPool = ref([])
const activeIndex = ref(-1)
const show = ref(false)
const fuse = ref(undefined)
const headerSearchSelectRef = ref(null)
const router = useRouter()
const theme = computed(() => useSettingsStore().theme)
const routes = computed(() => usePermissionStore().defaultRoutes)
function click() {
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
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)
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/mmxt'
import { isHttp } from '@/utils/validate'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
const search = ref('')
const options = ref([])
const searchPool = ref([])
const activeIndex = ref(-1)
const show = ref(false)
const fuse = ref(undefined)
const headerSearchSelectRef = ref(null)
const router = useRouter()
const theme = computed(() => useSettingsStore().theme)
const routes = computed(() => usePermissionStore().defaultRoutes)
function click() {
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
options.value = searchPool.value
}
}
search.value = ''
options.value = []
nextTick(() => {
function close() {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
search.value = ''
options.value = []
show.value = false
})
}
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: ''
}
activeIndex.value = -1
}
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)
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)
}
}
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]
search.value = ''
options.value = []
nextTick(() => {
show.value = false
})
}
function initFuse(list) {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
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
if (query !== '') {
options.value = fuse.value.search(query).map((item) => item.item) ?? searchPool.value
} else {
options.value = searchPool.value
function querySearch(query) {
activeIndex.value = -1
if (query !== '') {
options.value = fuse.value.search(query).map((item) => item.item) ?? searchPool.value
} else {
options.value = searchPool.value
}
}
}
function activeStyle(index) {
if (index !== activeIndex.value) return {}
return {
"background-color": theme.value,
"color": "#fff"
function activeStyle(index) {
if (index !== activeIndex.value) return {}
return {
"background-color": theme.value,
"color": "#fff"
}
}
}
function navigateResult(direction) {
if (direction === "up") {
activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1
} else if (direction === "down") {
activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1
function navigateResult(direction) {
if (direction === "up") {
activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1
} else if (direction === "down") {
activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1
}
}
}
function selectActiveResult() {
if (options.value.length > 0 && activeIndex.value >= 0) {
change(options.value[activeIndex.value])
function selectActiveResult() {
if (options.value.length > 0 && activeIndex.value >= 0) {
change(options.value[activeIndex.value])
}
}
}
onMounted(() => {
searchPool.value = generateRoutes(routes.value)
})
onMounted(() => {
searchPool.value = generateRoutes(routes.value)
})
watch(searchPool, (list) => {
initFuse(list)
})
watch(searchPool, (list) => {
initFuse(list)
})
</script>
<style lang='scss' scoped>
.header-search {
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
.header-search {
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
}
}
.result-wrap {
height: 280px;
margin: 6px 0;
.search-item {
display: flex;
height: 48px;
align-items: center;
padding-right: 10px;
.result-wrap {
height: 280px;
margin: 6px 0;
.left {
width: 60px;
text-align: center;
.menu-icon {
width: 18px;
height: 18px;
}
}
.search-info {
padding-left: 5px;
margin-top: 10px;
width: 100%;
.search-item {
display: flex;
flex-direction: column;
justify-content: flex-start;
flex: 1;
.menu-title,
.menu-path {
height: 20px;
height: 48px;
align-items: center;
padding-right: 10px;
.left {
width: 60px;
text-align: center;
.menu-icon {
width: 18px;
height: 18px;
}
}
.menu-path {
color: #ccc;
font-size: 10px;
.search-info {
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 {
cursor: pointer;
.search-item:hover {
cursor: pointer;
}
}
}
</style>
</style>

404
src/components/ImageUpload/index.vue

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

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

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

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

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

2
src/directive/common/copyText.js

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

8
src/directive/permission/hasPermi.js

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

8
src/directive/permission/hasRole.js

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

389
src/layout/components/Navbar.vue

@ -1,6 +1,7 @@
<template>
<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" />
<top-nav v-if="settingsStore.navType == 2" id="topmenu-container" class="topmenu-container" />
<template v-if="settingsStore.navType == 3">
@ -12,14 +13,6 @@
<template v-if="appStore.device !== 'mobile'">
<header-search id="header-search" class="right-menu-item" />
<el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="文档地址" effect="dark" placement="bottom">
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
</el-tooltip>
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="主题模式" effect="dark" placement="bottom">
@ -45,8 +38,8 @@
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<span>布局设置</span>
</el-dropdown-item>
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
@ -58,233 +51,233 @@
</template>
<script setup>
import { ElMessageBox } from 'element-plus'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import TopBar from './TopBar'
import Logo from './Sidebar/Logo'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
function toggleSideBar() {
appStore.toggleSideBar()
}
function handleCommand(command) {
switch (command) {
case "setLayout":
setLayout()
break
case "logout":
logout()
break
default:
break
import { ElMessageBox } from 'element-plus'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import TopBar from './TopBar'
import Logo from './Sidebar/Logo'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch'
import mmxtGit from '@/components/RuoYi/Git'
import mmxtDoc from '@/components/RuoYi/Doc'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
function toggleSideBar() {
appStore.toggleSideBar()
}
function handleCommand(command) {
switch (command) {
case "setLayout":
setLayout()
break
case "logout":
logout()
break
default:
break
}
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userStore.logOut().then(() => {
location.href = '/index'
})
}).catch(() => { })
}
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
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
const emits = defineEmits(['setLayout'])
function setLayout() {
emits('setLayout')
}
try {
const transition = document.startViewTransition(async () => {
await new Promise((resolve) => setTimeout(resolve, 10))
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()
await nextTick()
})
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
}, {
return
}
try {
const transition = document.startViewTransition(async () => {
await new Promise((resolve) => setTimeout(resolve, 10))
settingsStore.toggleTheme()
await nextTick()
})
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,
easing: "cubic-bezier(0.4, 0, 0.2, 1)",
fill: "forwards",
pseudoElement: !wasDark ? "::view-transition-old(root)" : "::view-transition-new(root)"
}
)
await transition.finished
} catch (error) {
console.warn("View transition failed, falling back to immediate toggle:", error)
settingsStore.toggleTheme()
)
await transition.finished
} catch (error) {
console.warn("View transition failed, falling back to immediate toggle:", error)
settingsStore.toggleTheme()
}
}
}
</script>
<style lang='scss' scoped>
.navbar.nav3 {
.hamburger-container {
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);
.navbar.nav3 {
.hamburger-container {
display: none !important;
}
}
.breadcrumb-container {
flex-shrink: 0;
}
.topmenu-container {
position: absolute;
left: 50px;
}
.topbar-container {
flex: 1;
min-width: 0;
.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;
overflow: hidden;
margin-left: 8px;
}
// padding: 0 8px;
box-sizing: border-box;
.errLog-container {
display: inline-block;
vertical-align: top;
}
.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);
}
}
.right-menu {
height: 100%;
line-height: 50px;
display: flex;
align-items: center;
margin-left: auto;
.breadcrumb-container {
flex-shrink: 0;
}
&:focus {
outline: none;
.topmenu-container {
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;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
vertical-align: top;
}
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
.right-menu {
height: 100%;
line-height: 50px;
display: flex;
align-items: center;
margin-left: auto;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
&:focus {
outline: none;
}
&.theme-switch-wrapper {
display: flex;
align-items: center;
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
svg {
transition: transform 0.3s;
&:hover {
transform: scale(1.15);
background: rgba(0, 0, 0, 0.025);
}
}
}
}
.avatar-container {
margin-right: 0px;
padding-right: 0px;
&.theme-switch-wrapper {
display: flex;
align-items: center;
.avatar-wrapper {
margin-top: 10px;
right: 8px;
position: relative;
svg {
transition: transform 0.3s;
.user-avatar {
cursor: pointer;
width: 30px;
height: 30px;
margin-right: 8px;
border-radius: 50%;
&:hover {
transform: scale(1.15);
}
}
}
}
.avatar-container {
margin-right: 0px;
padding-right: 0px;
.user-nickname{
.avatar-wrapper {
margin-top: 10px;
right: 8px;
position: relative;
left: 0px;
bottom: 10px;
font-size: 14px;
font-weight: bold;
}
i {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
.user-avatar {
cursor: pointer;
width: 30px;
height: 30px;
margin-right: 8px;
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>
<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)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
<template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{
onlyOneChild.meta.title }}</span></template>
</el-menu-item>
</app-link>
</template>
@ -15,86 +17,80 @@
<span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
</template>
<sidebar-item
v-for="(child, index) in item.children"
:key="child.path + index"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
<sidebar-item v-for="(child, index) in item.children" :key="child.path + index" :is-nest="true" :item="child"
:base-path="resolvePath(child.path)" class="nest-menu" />
</el-sub-menu>
</div>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi'
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/mmxt'
const props = defineProps({
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
})
const onlyOneChild = ref({})
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = []
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
const props = defineProps({
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
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
}
const onlyOneChild = ref({})
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
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
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) {
if (isExternal(routePath)) {
return routePath
return false
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery)
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
function resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery)
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
return getNormalPath(props.basePath + '/' + routePath)
}
return getNormalPath(props.basePath + '/' + routePath)
}
function hasTitle(title){
if (title.length > 5) {
return title
} else {
return ""
function hasTitle(title) {
if (title.length > 5) {
return title
} else {
return ""
}
}
}
</script>
</script>

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

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

2
src/main.js

@ -27,7 +27,7 @@ import './permission' // permission control
import { useDict } from '@/utils/dict'
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'

2
src/plugins/download.js

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

4
src/router/index.js

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

4
src/settings.js

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

72
src/utils/request.js

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

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

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

@ -6,19 +6,15 @@
</el-tab-pane>
<el-tab-pane label="字段信息" name="columnInfo">
<el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
<el-table-column label="序号" type="index" min-width="5%" class-name="allowDrag"/>
<el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true" 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="字段描述" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.columnComment"></el-input>
</template>
</el-table-column>
<el-table-column
label="物理类型"
prop="columnType"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="物理类型" prop="columnType" min-width="10%" :show-overflow-tooltip="true" />
<el-table-column label="Java类型" min-width="11%">
<template #default="scope">
<el-select v-model="scope.row.javaType">
@ -95,14 +91,11 @@
<el-table-column label="字典类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option
v-for="dict in dictOptions"
:key="dict.dictType"
:label="dict.dictName"
<el-option v-for="dict in dictOptions" :key="dict.dictType" :label="dict.dictName"
:value="dict.dictType">
<span style="float: left">{{ dict.dictName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
</el-option>
</el-option>
</el-select>
</template>
</el-table-column>
@ -122,90 +115,90 @@
</template>
<script setup name="GenEdit">
import { getGenTable, updateGenTable } from "@/api/tool/gen"
import { optionselect as getDictOptionselect } from "@/api/system/dict/type"
import basicInfoForm from "./basicInfoForm"
import genInfoForm from "./genInfoForm"
import Sortable from 'sortablejs'
import { getGenTable, updateGenTable } from "@/api/tool/gen"
import { optionselect as getDictOptionselect } from "@/api/system/dict/type"
import basicInfoForm from "./basicInfoForm"
import genInfoForm from "./genInfoForm"
// import Sortable from 'sortablejs'
const route = useRoute()
const { proxy } = getCurrentInstance()
const route = useRoute()
const { proxy } = getCurrentInstance()
const activeName = ref("columnInfo")
const tableHeight = ref(document.documentElement.scrollHeight - 245 + "px")
const tables = ref([])
const columns = ref([])
const dictOptions = ref([])
const info = ref({})
const activeName = ref("columnInfo")
const tableHeight = ref(document.documentElement.scrollHeight - 245 + "px")
const tables = ref([])
const columns = ref([])
const dictOptions = ref([])
const info = ref({})
/** 提交按钮 */
function submitForm() {
const basicForm = proxy.$refs.basicInfo.$refs.basicInfoForm
const genForm = proxy.$refs.genInfo.$refs.genInfoForm
Promise.all([basicForm, genForm].map(getFormPromise)).then(res => {
const validateResult = res.every(item => !!item)
if (validateResult) {
const genTable = Object.assign({}, info.value)
genTable.columns = columns.value
genTable.params = {
treeCode: info.value.treeCode,
treeName: info.value.treeName,
treeParentCode: info.value.treeParentCode,
parentMenuId: info.value.parentMenuId
}
updateGenTable(genTable).then(res => {
proxy.$modal.msgSuccess(res.msg)
if (res.code === 200) {
close()
/** 提交按钮 */
function submitForm() {
const basicForm = proxy.$refs.basicInfo.$refs.basicInfoForm
const genForm = proxy.$refs.genInfo.$refs.genInfoForm
Promise.all([basicForm, genForm].map(getFormPromise)).then(res => {
const validateResult = res.every(item => !!item)
if (validateResult) {
const genTable = Object.assign({}, info.value)
genTable.columns = columns.value
genTable.params = {
treeCode: info.value.treeCode,
treeName: info.value.treeName,
treeParentCode: info.value.treeParentCode,
parentMenuId: info.value.parentMenuId
}
})
} else {
proxy.$modal.msgError("表单校验未通过,请重新检查提交内容")
}
})
}
function getFormPromise(form) {
return new Promise(resolve => {
form.validate(res => {
resolve(res)
updateGenTable(genTable).then(res => {
proxy.$modal.msgSuccess(res.msg)
if (res.code === 200) {
close()
}
})
} else {
proxy.$modal.msgError("表单校验未通过,请重新检查提交内容")
}
})
})
}
function close() {
const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: route.query.pageNum } }
proxy.$tab.closeOpenPage(obj)
}
}
(() => {
const tableId = route.params && route.params.tableId
if (tableId) {
//
getGenTable(tableId).then(res => {
columns.value = res.data.rows
info.value = res.data.info
tables.value = res.data.tables
})
/** 查询字典下拉列表 */
getDictOptionselect().then(response => {
dictOptions.value = response.data
function getFormPromise(form) {
return new Promise(resolve => {
form.validate(res => {
resolve(res)
})
})
}
})()
//
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
}
function close() {
const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: route.query.pageNum } }
proxy.$tab.closeOpenPage(obj)
}
(() => {
const tableId = route.params && route.params.tableId
if (tableId) {
//
getGenTable(tableId).then(res => {
columns.value = res.data.rows
info.value = res.data.info
tables.value = res.data.tables
})
/** 查询字典下拉列表 */
getDictOptionselect().then(response => {
dictOptions.value = response.data
})
}
})()
//
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">
<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-tooltip>
</template>
@ -91,14 +91,9 @@
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-tree-select
v-model="info.parentMenuId"
:data="menuOptions"
:props="{ value: 'menuId', label: 'menuName', children: 'children' }"
value-key="menuId"
placeholder="请选择系统菜单"
check-strictly
/>
<el-tree-select v-model="info.parentMenuId" :data="menuOptions"
:props="{ value: 'menuId', label: 'menuName', children: 'children' }" value-key="menuId"
placeholder="请选择系统菜单" check-strictly />
</el-form-item>
</el-col>
@ -128,7 +123,7 @@
</el-form-item>
</el-col>
</el-row>
<template v-if="info.tplCategory == 'tree'">
<h4 class="form-header">其他信息</h4>
<el-row v-show="info.tplCategory == 'tree'">
@ -141,12 +136,8 @@
</el-tooltip>
</template>
<el-select v-model="info.treeCode" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
<el-option v-for="(column, index) in info.columns" :key="index"
:label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -159,12 +150,8 @@
</el-tooltip>
</template>
<el-select v-model="info.treeParentCode" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
<el-option v-for="(column, index) in info.columns" :key="index"
:label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -177,12 +164,8 @@
</el-tooltip>
</template>
<el-select v-model="info.treeName" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
<el-option v-for="(column, index) in info.columns" :key="index"
:label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -201,12 +184,8 @@
</el-tooltip>
</template>
<el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
<el-option
v-for="(table, index) in tables"
:key="index"
:label="table.tableName + ':' + table.tableComment"
:value="table.tableName"
></el-option>
<el-option v-for="(table, index) in tables" :key="index"
:label="table.tableName + ':' + table.tableComment" :value="table.tableName"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -219,12 +198,8 @@
</el-tooltip>
</template>
<el-select v-model="info.subTableFkName" placeholder="请选择">
<el-option
v-for="(column, index) in subColumns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
></el-option>
<el-option v-for="(column, index) in subColumns" :key="index"
:label="column.columnName + ':' + column.columnComment" :value="column.columnName"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -235,71 +210,71 @@
</template>
<script setup>
import { listMenu } from "@/api/system/menu"
const subColumns = ref([])
const menuOptions = ref([])
const { proxy } = getCurrentInstance()
import { listMenu } from "@/api/system/menu"
const props = defineProps({
info: {
type: Object,
default: null
},
tables: {
type: Array,
default: null
}
})
const subColumns = ref([])
const menuOptions = ref([])
const { proxy } = getCurrentInstance()
//
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" }]
})
const props = defineProps({
info: {
type: Object,
default: null
},
tables: {
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) {
if (value !== "sub") {
props.info.subTableName = ""
function subSelectChange(value) {
props.info.subTableFkName = ""
}
}
function setSubTableColumns(value) {
for (var item in props.tables) {
const name = props.tables[item].tableName
if (value === name) {
subColumns.value = props.tables[item].columns
break
function tplSelectChange(value) {
if (value !== "sub") {
props.info.subTableName = ""
props.info.subTableFkName = ""
}
}
}
/** 查询菜单下拉树结构 */
function getMenuTreeselect() {
listMenu().then(response => {
menuOptions.value = proxy.handleTree(response.data, "menuId")
})
}
function setSubTableColumns(value) {
for (var item in props.tables) {
const name = props.tables[item].tableName
if (value === name) {
subColumns.value = props.tables[item].columns
break
}
}
}
onMounted(() => {
getMenuTreeselect()
})
/** 查询菜单下拉树结构 */
function getMenuTreeselect() {
listMenu().then(response => {
menuOptions.value = proxy.handleTree(response.data, "menuId")
})
}
watch(() => props.info.subTableName, val => {
setSubTableColumns(val)
})
onMounted(() => {
getMenuTreeselect()
})
watch(() => props.info.tplWebType, val => {
if (val === '') {
props.info.tplWebType = "element-plus"
}
})
</script>
watch(() => props.info.subTableName, val => {
setSubTableColumns(val)
})
watch(() => props.info.tplWebType, val => {
if (val === '') {
props.info.tplWebType = "element-plus"
}
})
</script>

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

@ -2,32 +2,16 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="表名称" prop="tableName">
<el-input
v-model="queryParams.tableName"
placeholder="请输入表名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable style="width: 200px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input
v-model="queryParams.tableComment"
placeholder="请输入表描述"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable style="width: 200px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-"
start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@ -37,57 +21,29 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Download"
:disabled="multiple"
@click="handleGenTable"
v-hasPermi="['tool:gen:code']"
>生成</el-button>
<el-button type="primary" plain icon="Download" :disabled="multiple" @click="handleGenTable"
v-hasPermi="['tool:gen:code']">生成</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="openCreateTable"
v-hasRole="['admin']"
>创建</el-button>
<el-button type="primary" plain icon="Plus" @click="openCreateTable" v-hasRole="['admin']">创建</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Upload"
@click="openImportTable"
v-hasPermi="['tool:gen:import']"
>导入</el-button>
<el-button type="info" plain icon="Upload" @click="openImportTable"
v-hasPermi="['tool:gen:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleEditTable"
v-hasPermi="['tool:gen:edit']"
>修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleEditTable"
v-hasPermi="['tool:gen:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['tool:gen:remove']"
>删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
v-hasPermi="['tool:gen:remove']">删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table 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 label="序号" type="index" width="50" align="center">
<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="tableComment" :show-overflow-tooltip="true" />
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160" sortable="custom" :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" prop="createTime" width="160" sortable="custom"
: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">
<template #default="scope">
<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 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 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 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 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>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<!-- 预览界面 -->
<el-dialog :title="preview.title" v-model="preview.open" width="80%" top="5vh" append-to-body class="scrollbar">
<el-tabs v-model="preview.activeName">
<el-tab-pane
v-for="(value, key) in preview.data"
<el-tab-pane v-for="(value, key) in preview.data"
:label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
:name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
:key="value"
>
<el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">&nbsp;复制</el-link>
:name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" :key="value">
<el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess"
style="float:right">&nbsp;复制</el-link>
<pre>{{ value }}</pre>
</el-tab-pane>
</el-tabs>
@ -146,164 +102,164 @@
</template>
<script setup name="Gen">
import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen"
import router from "@/router"
import importTable from "./importTable"
import createTable from "./createTable"
import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen"
import router from "@/router"
import importTable from "./importTable"
import createTable from "./createTable"
const route = useRoute()
const { proxy } = getCurrentInstance()
const route = useRoute()
const { proxy } = getCurrentInstance()
const tableList = ref([])
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const tableNames = ref([])
const dateRange = ref([])
const uniqueId = ref("")
const defaultSort = ref({ prop: "createTime", order: "descending" })
const tableList = ref([])
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const tableNames = ref([])
const dateRange = ref([])
const uniqueId = ref("")
const defaultSort = ref({ prop: "createTime", order: "descending" })
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
tableName: undefined,
tableComment: undefined,
orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order
},
preview: {
open: false,
title: "代码预览",
data: {},
activeName: "domain.java"
}
})
const { queryParams, preview } = toRefs(data)
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
tableName: undefined,
tableComment: undefined,
orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order
},
preview: {
open: false,
title: "代码预览",
data: {},
activeName: "domain.java"
}
})
onActivated(() => {
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()
}
})
const { queryParams, preview } = toRefs(data)
/** 查询表集合 */
function getList() {
loading.value = true
listTable(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
tableList.value = response.rows
total.value = response.total
loading.value = false
onActivated(() => {
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()
}
})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 生成代码操作 */
function handleGenTable(row) {
const tbNames = row.tableName || tableNames.value
if (tbNames == "") {
proxy.$modal.msgError("请选择要生成的数据")
return
}
if (row.genType === "1") {
genCode(row.tableName).then(response => {
proxy.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath)
/** 查询表集合 */
function getList() {
loading.value = true
listTable(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
tableList.value = response.rows
total.value = response.total
loading.value = false
})
} else {
const zipName = Array.isArray(tbNames) ? "ruoyi.zip" : tbNames + ".zip"
proxy.$download.zip("/tool/gen/batchGenCode?tables=" + tbNames, zipName)
}
}
/** 同步数据库操作 */
function handleSynchDb(row) {
const tableName = row.tableName
proxy.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function () {
return synchDb(tableName)
}).then(() => {
proxy.$modal.msgSuccess("同步成功")
}).catch(() => {})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 打开导入表弹窗 */
function openImportTable() {
proxy.$refs["importRef"].show()
}
/** 生成代码操作 */
function handleGenTable(row) {
const tbNames = row.tableName || tableNames.value
if (tbNames == "") {
proxy.$modal.msgError("请选择要生成的数据")
return
}
if (row.genType === "1") {
genCode(row.tableName).then(response => {
proxy.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath)
})
} else {
const zipName = Array.isArray(tbNames) ? "mmxt.zip" : tbNames + ".zip"
proxy.$download.zip("/tool/gen/batchGenCode?tables=" + tbNames, zipName)
}
}
/** 打开创建表弹窗 */
function openCreateTable() {
proxy.$refs["createRef"].show()
}
/** 同步数据库操作 */
function handleSynchDb(row) {
const tableName = row.tableName
proxy.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function () {
return synchDb(tableName)
}).then(() => {
proxy.$modal.msgSuccess("同步成功")
}).catch(() => { })
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
proxy.resetForm("queryRef")
queryParams.value.pageNum = 1
proxy.$refs["genRef"].sort(defaultSort.value.prop, defaultSort.value.order)
}
/** 打开导入表弹窗 */
function openImportTable() {
proxy.$refs["importRef"].show()
}
/** 预览按钮 */
function handlePreview(row) {
previewTable(row.tableId).then(response => {
preview.value.data = response.data
preview.value.open = true
preview.value.activeName = "domain.java"
})
}
/** 打开创建表弹窗 */
function openCreateTable() {
proxy.$refs["createRef"].show()
}
/** 复制代码成功 */
function copyTextSuccess() {
proxy.$modal.msgSuccess("复制成功")
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
proxy.resetForm("queryRef")
queryParams.value.pageNum = 1
proxy.$refs["genRef"].sort(defaultSort.value.prop, defaultSort.value.order)
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.tableId)
tableNames.value = selection.map(item => item.tableName)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 预览按钮 */
function handlePreview(row) {
previewTable(row.tableId).then(response => {
preview.value.data = response.data
preview.value.open = true
preview.value.activeName = "domain.java"
})
}
/** 排序触发事件 */
function handleSortChange(column, prop, order) {
queryParams.value.orderByColumn = column.prop
queryParams.value.isAsc = column.order
getList()
}
/** 复制代码成功 */
function copyTextSuccess() {
proxy.$modal.msgSuccess("复制成功")
}
/** 修改按钮操作 */
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)
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.tableId)
tableNames.value = selection.map(item => item.tableName)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 删除按钮操作 */
function handleDelete(row) {
const tableIds = row.tableId || ids.value
proxy.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?').then(function () {
return delTable(tableIds)
}).then(() => {
/** 排序触发事件 */
function handleSortChange(column, prop, order) {
queryParams.value.orderByColumn = column.prop
queryParams.value.isAsc = column.order
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 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/
export default defineConfig(({ mode, command }) => {
@ -11,8 +12,8 @@ export default defineConfig(({ mode, command }) => {
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
base: VITE_APP_ENV === 'production' ? '/' : '/',
// 例如 https://www.mmxt.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.mmxt.vip/admin/,则设置 baseUrl 为 /admin/。
base: VITE_APP_ENV === 'production' ? './' : '/',
plugins: createVitePlugins(env, command === 'build'),
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias
@ -52,8 +53,8 @@ export default defineConfig(({ mode, command }) => {
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
},
// springdoc proxy
'^/v3/api-docs/(.*)': {
// springdoc proxy
'^/v3/api-docs/(.*)': {
target: baseUrl,
changeOrigin: true,
}

2
vite/plugins/compression.js

@ -6,7 +6,7 @@ export default function createCompression(env) {
if (VITE_BUILD_COMPRESS) {
const compressList = VITE_BUILD_COMPRESS.split(',')
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(
compression({
ext: '.gz',

Loading…
Cancel
Save