46 Commits

Author SHA1 Message Date
c0e188e414 update README.md 2026-01-28 14:28:30 +08:00
e8945ad50e update README.md 2026-01-28 14:20:39 +08:00
b1bc7033c4 添加新群号:127358632 2026-01-28 14:19:10 +08:00
604b6877f0 优化代码 2026-01-28 14:18:51 +08:00
4a0faad4bc 添加菜单路由地址和名称的校验规则 2026-01-09 11:14:38 +08:00
eafe812ae7 优化防重提交间隔时间可自定义 2026-01-08 13:39:32 +08:00
70d125a6f4 修复Excel自定义格式样式污染问题 2026-01-06 13:53:23 +08:00
493dd513e5 copyright 2026 2026-01-04 16:08:32 +08:00
8df58e7512 将isAdmin方法统一到SecurityUtils 2026-01-04 16:07:06 +08:00
6a35245aaf 修正UserAgent解析逻辑,正确设置浏览器和操作系统字段 2025-12-31 09:29:10 +08:00
b2cd0b51c2 修正UserAgent解析逻辑,正确设置浏览器和操作系统字段 2025-12-21 10:51:25 +08:00
a0be25948d 优化topbar顶部菜单样式 2025-12-18 14:00:02 +08:00
ab1bbf21ed 若依 3.9.1 2025-12-18 09:06:24 +08:00
2c82a847eb 默认固定头部 2025-12-18 09:05:51 +08:00
2b3b29a4a3 优化字典组件值宽松匹配 2025-12-16 16:41:56 +08:00
f57dea5332 菜单导航设置支持纯顶部 2025-12-16 13:02:05 +08:00
fab921a1d2 升级quartz到最新版本2.5.2 2025-12-12 11:04:33 +08:00
336a47660f 升级spring-boot到最新版本3.5.8 2025-12-11 14:40:16 +08:00
575b1b727f 升级spring-boot-mybatis到最新版3.0.5 2025-12-11 14:37:58 +08:00
542979e703 升级springdoc到最新版本2.8.14 2025-12-11 14:37:19 +08:00
c2202fee27 升级druid到最新版本1.2.27 2025-12-11 14:34:31 +08:00
17a726942f 升级oshi到最新版本6.9.1 2025-12-11 14:34:14 +08:00
362db4a071 升级commons.io到最新版本2.21.0 2025-12-11 14:33:38 +08:00
771b9af7c0 升级fastjson到最新版2.0.60 2025-12-11 14:33:14 +08:00
7a787a919e update sqlkeyword 2025-12-11 14:33:05 +08:00
8fdbd05ce7 使用yauaa代替bitwalker 2025-12-09 14:29:23 +08:00
970275755d 优化用户密码字段序列化配置 2025-12-05 14:59:52 +08:00
516116f125 优化数据权限控制逻辑,放开permission限制 2025-12-04 17:33:15 +08:00
d3493e94d6 支持Excel导出对象的多个子列表 2025-12-04 16:32:48 +08:00
3beb3ae3f9 优化表单构建关闭页签销毁复制插件 2025-12-04 13:53:57 +08:00
f77c0ec97d 修复优雅停机出现的冲突问题 2025-12-04 13:51:36 +08:00
55854ec195 忽略用户密码字段的JSON序列化 2025-12-03 14:38:31 +08:00
088e9afbc9 优化代码 2025-12-03 11:48:40 +08:00
456e1534ee 优化生成代码下载的zip文件名 2025-12-03 10:27:04 +08:00
352ed6eee7 网页标题设置新增SET_TITLE方法 2025-12-02 19:30:01 +08:00
0cb12f5440 支持Excel导出对象的多个子列表 2025-12-02 19:13:23 +08:00
169ca65ed3 登录/注册页面底部版权信息修改为读取配置 2025-12-02 15:29:35 +08:00
cd6c2fc5f1 修复v3时间控件between选择后清空报错问题 2025-12-02 14:57:45 +08:00
e24918b054 修复表单构建移除所有控件后切换路由回来空白问题 2025-12-02 13:08:27 +08:00
2d36603c46 修复comboReadDict属性下多个sheet出现的报错(ICWQ8E) 2025-11-13 11:34:45 +08:00
964569f715 update ruoyi-admin pom.xml 2025-10-14 12:14:12 +08:00
e37b8ff9b8 添加新群号:174569686 2025-10-05 20:11:22 +08:00
5896895bab 升级fastjson到最新版2.0.58 2025-09-05 09:17:39 +08:00
0c8e9ad74e 修复固定头部时出现的导航栏偏移问题(ICV9OH) 2025-09-04 19:58:58 +08:00
f0346b3def 支持防盗链功能 2025-09-02 11:31:18 +08:00
d1cdbbd22c 优化代码 2025-09-02 08:53:31 +08:00
77 changed files with 1227 additions and 396 deletions

View File

@ -1,11 +1,11 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.0</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.1</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4> <h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/y_project/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.0-brightgreen.svg"></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/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p> </p>
@ -13,14 +13,12 @@
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* 前端采用Vue、Element UI * 本仓库为RuoYi-Vue的Spring Boot 3 的版本,保持同步更新
* 后端采用Spring Boot、Spring Security、Redis & Jwt。 * 后端采用Spring Boot3、Spring Security、Redis & Jwt。
* 权限认证使用Jwt支持多终端认证系统。 * 权限认证使用Jwt支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。 * 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。 * 高效率开发,使用代码生成器可以一键生成前后端代码。
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3)保持同步更新。 * 提供了技术栈Vue3 Element Plus Vite的 [RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3)版本以及技术栈TypeScript[RuoYi-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Vue3/tree/typescript)版本,两者保持同步更新。
* 提供了单应用版本[RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast)Oracle版本[RuoYi-Vue-Oracle](https://gitcode.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp; * 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
## 内置功能 ## 内置功能
@ -44,6 +42,21 @@
17. 在线构建器拖动表单元素生成相应的HTML代码。 17. 在线构建器拖动表单元素生成相应的HTML代码。
18. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。 18. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
# 版本对比
RuoYi-Vue 前端项目的三个主要演进版本,方便你直观对比其技术栈差异(并行开发维护)。
| 项目名称 | **RuoYi-Vue** | **RuoYi-Vue3** | **RuoYi-Vue3-TypeScript** |
| :--- | :--- | :--- | :--- |
| **前端框架** | Vue 2 | Vue 3 | Vue 3 |
| **脚本语言** | JavaScript | JavaScript | TypeScript |
| **构建工具** | Vue CLI | Vite | Vite |
| **UI 组件库** | Element UI | Element Plus | Element Plus |
| **状态管理** | Vuex | Pinia | Pinia |
| **路由管理** | Vue Router 3 | Vue Router 4 | Vue Router 4 |
| **核心特点** | 1. 技术栈经典稳定<br>2. 社区资料丰富<br>3. 当前维护重心已转移 | 1. 现代前端技术栈<br>2. 开发体验与性能更优<br>3. 官方主推的活跃版本 | 1. 类型加持,减少沟通成本<br>2. 开发时有提示,效率更高<br>3. 多人协作企业级开发项目 |
| **仓库地址** | [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) | [RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3) | [RuoYi-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Vue3/tree/typescript) |
## 在线体验 ## 在线体验
- admin/admin123 - admin/admin123
@ -92,4 +105,4 @@
## 若依前后端分离交流群 ## 若依前后端分离交流群
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群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/已满-174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) [![加入QQ群](https://img.shields.io/badge/127358632-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=M9y5NjAl44lAL_Vh2crmEehZU_PMU6KS&authKey=ZSDz8hEREWSaPuxQV3gEwqGIaGjfRNnkB4rJjf0IvXhrSUGSGwQFmBA%2Boe8HFxyl&noverify=0&group_code=127358632) 点击按钮入群。

34
pom.xml
View File

@ -6,34 +6,35 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
<name>ruoyi</name> <name>ruoyi</name>
<url>http://www.ruoyi.vip</url> <url>http://www.ruoyi.vip</url>
<description>若依管理系统</description> <description>若依管理系统</description>
<properties> <properties>
<ruoyi.version>3.9.0</ruoyi.version> <ruoyi.version>3.9.1</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version> <java.version>17</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<mybatis-spring-boot.version>3.0.4</mybatis-spring-boot.version> <mybatis-spring-boot.version>3.0.5</mybatis-spring-boot.version>
<druid.version>1.2.23</druid.version> <druid.version>1.2.27</druid.version>
<bitwalker.version>1.21</bitwalker.version> <yauaa.version>7.32.0</yauaa.version>
<swagger.version>3.0.0</swagger.version> <swagger.version>3.0.0</swagger.version>
<kaptcha.version>2.3.3</kaptcha.version> <kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>2.1.1</pagehelper.boot.version> <pagehelper.boot.version>2.1.1</pagehelper.boot.version>
<fastjson.version>2.0.57</fastjson.version> <fastjson.version>2.0.60</fastjson.version>
<oshi.version>6.8.3</oshi.version> <oshi.version>6.9.1</oshi.version>
<commons.io.version>2.19.0</commons.io.version> <commons.io.version>2.21.0</commons.io.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version> <jwt.version>0.9.1</jwt.version>
<quartz.version>2.5.2</quartz.version>
<mysql.version>8.2.0</mysql.version> <mysql.version>8.2.0</mysql.version>
<jaxb-api.version>2.3.1</jaxb-api.version> <jaxb-api.version>2.3.1</jaxb-api.version>
<jakarta.version>6.0.0</jakarta.version> <jakarta.version>6.0.0</jakarta.version>
<springdoc.version>2.8.9</springdoc.version> <springdoc.version>2.8.14</springdoc.version>
</properties> </properties>
<!-- 依赖声明 --> <!-- 依赖声明 -->
@ -44,7 +45,7 @@
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId> <artifactId>spring-boot-dependencies</artifactId>
<version>3.5.4</version> <version>3.5.8</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -58,9 +59,9 @@
<!-- 解析客户端操作系统、浏览器等 --> <!-- 解析客户端操作系统、浏览器等 -->
<dependency> <dependency>
<groupId>eu.bitwalker</groupId> <groupId>nl.basjes.parse.useragent</groupId>
<artifactId>UserAgentUtils</artifactId> <artifactId>yauaa</artifactId>
<version>${bitwalker.version}</version> <version>${yauaa.version}</version>
</dependency> </dependency>
<!-- pagehelper 分页插件 --> <!-- pagehelper 分页插件 -->
@ -129,6 +130,13 @@
<version>${velocity.version}</version> <version>${velocity.version}</version>
</dependency> </dependency>
<!-- 定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
<!-- 阿里JSON解析器 --> <!-- 阿里JSON解析器 -->
<dependency> <dependency>
<groupId>com.alibaba.fastjson2</groupId> <groupId>com.alibaba.fastjson2</groupId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>
@ -61,9 +61,9 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version> <version>3.5.4</version>
<configuration> <configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 --> <addResources>true</addResources>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>

View File

@ -34,7 +34,7 @@ public class CommonController
@Autowired @Autowired
private ServerConfig serverConfig; private ServerConfig serverConfig;
private static final String FILE_DELIMETER = ","; private static final String FILE_DELIMITER = ",";
/** /**
* 通用下载请求 * 通用下载请求
@ -119,10 +119,10 @@ public class CommonController
originalFilenames.add(file.getOriginalFilename()); originalFilenames.add(file.getOriginalFilename());
} }
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); ajax.put("urls", StringUtils.join(urls, FILE_DELIMITER));
ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMITER));
ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMITER));
ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMITER));
return ajax; return ajax;
} }
catch (Exception e) catch (Exception e)

View File

@ -93,6 +93,10 @@ public class SysMenuController extends BaseController
{ {
return error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头"); return error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} }
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setCreateBy(getUsername()); menu.setCreateBy(getUsername());
return toAjax(menuService.insertMenu(menu)); return toAjax(menuService.insertMenu(menu));
} }
@ -117,6 +121,10 @@ public class SysMenuController extends BaseController
{ {
return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
} }
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setUpdateBy(getUsername()); menu.setUpdateBy(getUsername());
return toAjax(menuService.updateMenu(menu)); return toAjax(menuService.updateMenu(menu));
} }

View File

@ -96,7 +96,8 @@ public class SysProfileController extends BaseController
String newPassword = params.get("newPassword"); String newPassword = params.get("newPassword");
LoginUser loginUser = getLoginUser(); LoginUser loginUser = getLoginUser();
Long userId = loginUser.getUserId(); Long userId = loginUser.getUserId();
String password = loginUser.getPassword(); SysUser user = userService.selectUserById(userId);
String password = user.getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password)) if (!SecurityUtils.matchesPassword(oldPassword, password))
{ {
return error("修改密码失败,旧密码错误"); return error("修改密码失败,旧密码错误");

View File

@ -111,7 +111,7 @@ public class SysUserController extends BaseController
ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
} }
List<SysRole> roles = roleService.selectRoleAll(); List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll()); ajax.put("posts", postService.selectPostAll());
return ajax; return ajax;
} }
@ -226,7 +226,7 @@ public class SysUserController extends BaseController
SysUser user = userService.selectUserById(userId); SysUser user = userService.selectUserById(userId);
List<SysRole> roles = roleService.selectRolesByUserId(userId); List<SysRole> roles = roleService.selectRolesByUserId(userId);
ajax.put("user", user); ajax.put("user", user);
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return ajax; return ajax;
} }

View File

@ -3,9 +3,9 @@ ruoyi:
# 名称 # 名称
name: RuoYi name: RuoYi
# 版本 # 版本
version: 3.9.0 version: 3.9.1
# 版权年份 # 版权年份
copyrightYear: 2025 copyrightYear: 2026
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath # 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath profile: D:/ruoyi/uploadPath
# 获取ip地址开关 # 获取ip地址开关
@ -127,6 +127,13 @@ springdoc:
paths-to-match: '/**' paths-to-match: '/**'
packages-to-scan: com.ruoyi.web.controller.tool packages-to-scan: com.ruoyi.web.controller.tool
# 防盗链配置
referer:
# 防盗链开关
enabled: false
# 允许的域名列表
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
# 防止XSS攻击 # 防止XSS攻击
xss: xss:
# 过滤开关 # 过滤开关

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -77,12 +77,6 @@
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
</dependency> </dependency>
<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- Token生成与解析--> <!-- Token生成与解析-->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
@ -109,8 +103,8 @@
<!-- 解析客户端操作系统、浏览器等 --> <!-- 解析客户端操作系统、浏览器等 -->
<dependency> <dependency>
<groupId>eu.bitwalker</groupId> <groupId>nl.basjes.parse.useragent</groupId>
<artifactId>UserAgentUtils</artifactId> <artifactId>yauaa</artifactId>
</dependency> </dependency>
<!-- servlet包 --> <!-- servlet包 -->

View File

@ -83,12 +83,12 @@ public class Constants
/** /**
* 角色权限分隔符 * 角色权限分隔符
*/ */
public static final String ROLE_DELIMETER = ","; public static final String ROLE_DELIMITER = ",";
/** /**
* 权限标识分隔符 * 权限标识分隔符
*/ */
public static final String PERMISSION_DELIMETER = ","; public static final String PERMISSION_DELIMITER = ",";
/** /**
* 验证码有效期(分钟) * 验证码有效期(分钟)

View File

@ -5,11 +5,13 @@ import java.util.List;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.ColumnType;
import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.xss.Xss; import com.ruoyi.common.xss.Xss;
/** /**
@ -114,12 +116,7 @@ public class SysUser extends BaseEntity
public boolean isAdmin() public boolean isAdmin()
{ {
return isAdmin(this.userId); return SecurityUtils.isAdmin(this.userId);
}
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
} }
public Long getDeptId() public Long getDeptId()
@ -200,6 +197,7 @@ public class SysUser extends BaseEntity
this.avatar = avatar; this.avatar = avatar;
} }
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String getPassword() public String getPassword()
{ {
return password; return password;

View File

@ -3,7 +3,7 @@ package com.ruoyi.common.exception.file;
import java.util.Arrays; import java.util.Arrays;
/** /**
* 文件上传异常类 * 文件上传无效扩展名异常类
* *
* @author ruoyi * @author ruoyi
*/ */

View File

@ -0,0 +1,77 @@
package com.ruoyi.common.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 防盗链过滤器
*
* @author ruoyi
*/
public class RefererFilter implements Filter
{
/**
* 允许的域名列表
*/
public List<String> allowedDomains;
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
String domains = filterConfig.getInitParameter("allowedDomains");
this.allowedDomains = Arrays.asList(domains.split(","));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String referer = req.getHeader("Referer");
// 如果Referer为空拒绝访问
if (referer == null || referer.isEmpty())
{
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer header is required");
return;
}
// 检查Referer是否在允许的域名列表中
boolean allowed = false;
for (String domain : allowedDomains)
{
if (referer.contains(domain))
{
allowed = true;
break;
}
}
// 根据检查结果决定是否放行
if (allowed)
{
chain.doFilter(request, response);
}
else
{
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer '" + referer + "' is not allowed");
}
}
@Override
public void destroy()
{
}
}

View File

@ -1,7 +1,9 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.domain.entity.SysDictData;
@ -89,37 +91,25 @@ public class DictUtils
*/ */
public static String getDictLabel(String dictType, String dictValue, String separator) public static String getDictLabel(String dictType, String dictValue, String separator)
{ {
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas)) if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictValue))
{ {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
if (StringUtils.containsAny(separator, dictValue)) Map<String, String> dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictValue(), dict.getDictLabel()), Map::putAll);
if (!StringUtils.contains(dictValue, separator))
{ {
for (SysDictData dict : datas) return dictMap.getOrDefault(dictValue, StringUtils.EMPTY);
}
StringBuilder labelBuilder = new StringBuilder();
for (String seperatedValue : dictValue.split(separator))
{
if (dictMap.containsKey(seperatedValue))
{ {
for (String value : dictValue.split(separator)) labelBuilder.append(dictMap.get(seperatedValue)).append(separator);
{
if (value.equals(dict.getDictValue()))
{
propertyString.append(dict.getDictLabel()).append(separator);
break;
}
}
} }
} }
else return StringUtils.removeEnd(labelBuilder.toString(), separator);
{
for (SysDictData dict : datas)
{
if (dictValue.equals(dict.getDictValue()))
{
return dict.getDictLabel();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/** /**
@ -132,37 +122,25 @@ public class DictUtils
*/ */
public static String getDictValue(String dictType, String dictLabel, String separator) public static String getDictValue(String dictType, String dictLabel, String separator)
{ {
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas)) if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictLabel))
{ {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
if (StringUtils.containsAny(separator, dictLabel)) Map<String, String> dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictLabel(), dict.getDictValue()), Map::putAll);
if (!StringUtils.contains(dictLabel, separator))
{ {
for (SysDictData dict : datas) return dictMap.getOrDefault(dictLabel, StringUtils.EMPTY);
}
StringBuilder valueBuilder = new StringBuilder();
for (String seperatedValue : dictLabel.split(separator))
{
if (dictMap.containsKey(seperatedValue))
{ {
for (String label : dictLabel.split(separator)) valueBuilder.append(dictMap.get(seperatedValue)).append(separator);
{
if (label.equals(dict.getDictLabel()))
{
propertyString.append(dict.getDictValue()).append(separator);
break;
}
}
} }
} }
else return StringUtils.removeEnd(valueBuilder.toString(), separator);
{
for (SysDictData dict : datas)
{
if (dictLabel.equals(dict.getDictLabel()))
{
return dict.getDictValue();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/** /**

View File

@ -114,6 +114,16 @@ public class SecurityUtils
return passwordEncoder.matches(rawPassword, encodedPassword); return passwordEncoder.matches(rawPassword, encodedPassword);
} }
/**
* 是否为管理员
*
* @return 结果
*/
public static boolean isAdmin()
{
return isAdmin(getUserId());
}
/** /**
* 是否为管理员 * 是否为管理员
* *

View File

@ -0,0 +1,254 @@
package com.ruoyi.common.utils.http;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ruoyi.common.utils.StringUtils;
import nl.basjes.parse.useragent.UserAgent;
import nl.basjes.parse.useragent.UserAgentAnalyzer;
/**
* UserAgent解析工具类
*
* @author ruoyi
*/
public class UserAgentUtils
{
public static final String UNKNOWN = "";
// 浏览器正则表达式模式
private static final Pattern CHROME_PATTERN = Pattern.compile("Chrome/(\\d+)(?:\\.\\d+)*");
private static final Pattern FIREFOX_PATTERN = Pattern.compile("Firefox/(\\d+)(?:\\.\\d+)*");
private static final Pattern EDGE_PATTERN = Pattern.compile("Edg(?:e)?/(\\d+)(?:\\.\\d+)*");
private static final Pattern SAFARI_PATTERN = Pattern.compile("Version/(\\d+)(?:\\.\\d+)*");
private static final Pattern OPERA_PATTERN = Pattern.compile("Opera/(\\d+)(?:\\.\\d+)*");
private static final Pattern IE_PATTERN = Pattern.compile("(?:MSIE |Trident/.*rv:)(\\d+)(?:\\.\\d+)*");
private static final Pattern SAMSUNG_PATTERN = Pattern.compile("SamsungBrowser/(\\d+)(?:\\.\\d+)*");
private static final Pattern UC_PATTERN = Pattern.compile("UCBrowser/(\\d+)(?:\\.\\d+)*");
private static final Pattern QQ_PATTERN = Pattern.compile("QQBrowser/(\\d+)(?:\\.\\d+)*");
private static final Pattern WECHAT_PATTERN = Pattern.compile("MicroMessenger/(\\d+)(?:\\.\\d+)*");
private static final Pattern BAIDU_PATTERN = Pattern.compile("baidubrowser/(\\d+)(?:\\.\\d+)*");
// 操作系统正则表达式模式
private static final Pattern WINDOWS_PATTERN = Pattern.compile("Windows NT (\\d+\\.\\d+)");
private static final Pattern MACOS_PATTERN = Pattern.compile("Mac OS X (\\d+[_\\d]*)");
private static final Pattern ANDROID_PATTERN = Pattern.compile("Android (\\d+)(?:\\.\\d+)*");
private static final Pattern IOS_PATTERN = Pattern.compile("OS[\\s_](\\d+)(?:_\\d+)*");
private static final Pattern LINUX_PATTERN = Pattern.compile("Linux");
private static final Pattern CHROMEOS_PATTERN = Pattern.compile("CrOS");
private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer
.newBuilder().hideMatcherLoadStats()
.withCache(5000)
.showMinimalVersion()
.withField(UserAgent.AGENT_NAME_VERSION)
.withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION)
.build();
/**
* 获取客户端浏览器
*/
public static String getBrowser(String userAgent)
{
UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent);
String agentNameVersion = iua.get(UserAgent.AGENT_NAME_VERSION).getValue();
if (StringUtils.isBlank(agentNameVersion) || agentNameVersion.contains("??"))
{
return formatBrowser(userAgent);
}
return agentNameVersion;
}
/**
* 获取客户端操作系统
*/
public static String getOperatingSystem(String userAgent)
{
UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent);
String operatingSystemNameVersion = iua.get(UserAgent.OPERATING_SYSTEM_NAME_VERSION).getValue();
if (StringUtils.isBlank(operatingSystemNameVersion) || operatingSystemNameVersion.contains("??"))
{
return formatOperatingSystem(userAgent);
}
return operatingSystemNameVersion;
}
/**
* 全面浏览器检测
*/
private static String formatBrowser(String browser)
{
// Chrome系列浏览器
Matcher chromeMatcher = CHROME_PATTERN.matcher(browser);
if (chromeMatcher.find() && (browser.contains("Chrome") || browser.contains("CriOS")))
{
return "Chrome" + chromeMatcher.group(1);
}
// Firefox
Matcher firefoxMatcher = FIREFOX_PATTERN.matcher(browser);
if (firefoxMatcher.find())
{
return "Firefox" + firefoxMatcher.group(1);
}
// Edge浏览器
Matcher edgeMatcher = EDGE_PATTERN.matcher(browser);
if (edgeMatcher.find())
{
return "Edge" + edgeMatcher.group(1);
}
// Safari浏览器需排除Chrome
Matcher safariMatcher = SAFARI_PATTERN.matcher(browser);
if (safariMatcher.find() && !browser.contains("Chrome"))
{
return "Safari" + safariMatcher.group(1);
}
// 微信内置浏览器
Matcher wechatMatcher = WECHAT_PATTERN.matcher(browser);
if (wechatMatcher.find())
{
return "WeChat" + wechatMatcher.group(1);
}
// UC浏览器
Matcher ucMatcher = UC_PATTERN.matcher(browser);
if (ucMatcher.find())
{
return "UC Browser" + ucMatcher.group(1);
}
// QQ浏览器
Matcher qqMatcher = QQ_PATTERN.matcher(browser);
if (qqMatcher.find())
{
return "QQ Browser" + qqMatcher.group(1);
}
// 百度浏览器
Matcher baiduMatcher = BAIDU_PATTERN.matcher(browser);
if (baiduMatcher.find())
{
return "Baidu Browser" + baiduMatcher.group(1);
}
// Samsung浏览器
Matcher samsungMatcher = SAMSUNG_PATTERN.matcher(browser);
if (samsungMatcher.find())
{
return "Samsung Browser" + samsungMatcher.group(1);
}
// Opera浏览器
Matcher operaMatcher = OPERA_PATTERN.matcher(browser);
if (operaMatcher.find())
{
return "Opera" + operaMatcher.group(1);
}
// IE浏览器
Matcher ieMatcher = IE_PATTERN.matcher(browser);
if (ieMatcher.find())
{
return "Internet Explorer" + ieMatcher.group(1);
}
return UNKNOWN;
}
/**
* 检测操作系统
*/
private static String formatOperatingSystem(String operatingSystem)
{
// Windows系统
Matcher windowsMatcher = WINDOWS_PATTERN.matcher(operatingSystem);
if (windowsMatcher.find())
{
return "Windows" + getWindowsVersionDisplay(windowsMatcher.group(1));
}
// macOS系统
Matcher macMatcher = MACOS_PATTERN.matcher(operatingSystem);
if (macMatcher.find())
{
String version = macMatcher.group(1).replace("_", ".");
return "macOS" + extractMajorVersion(version);
}
// Android系统
Matcher androidMatcher = ANDROID_PATTERN.matcher(operatingSystem);
if (androidMatcher.find())
{
return "Android" + extractMajorVersion(androidMatcher.group(1));
}
// iOS系统
Matcher iosMatcher = IOS_PATTERN.matcher(operatingSystem);
if (iosMatcher.find() && (operatingSystem.contains("iPhone") || operatingSystem.contains("iPad")))
{
return "iOS" + extractMajorVersion(iosMatcher.group(1));
}
// Linux系统
if (LINUX_PATTERN.matcher(operatingSystem).find() && !operatingSystem.contains("Android"))
{
return "Linux";
}
// Chrome OS
if (CHROMEOS_PATTERN.matcher(operatingSystem).find())
{
return "Chrome OS";
}
return UNKNOWN;
}
/**
* 提取优化的主版本号
*/
private static String extractMajorVersion(String fullVersion)
{
if (StringUtils.isEmpty(fullVersion))
{
return StringUtils.EMPTY;
}
try
{
// 清理版本号中的非数字字符
String cleanVersion = fullVersion.replaceAll("[^0-9.]", "");
String[] parts = cleanVersion.split("\\.");
if (parts.length > 0)
{
String firstPart = parts[0];
if (firstPart.matches("\\d+"))
{
int version = Integer.parseInt(firstPart);
// 处理三位数版本号如142 -> 14
if (version >= 100)
{
return String.valueOf(version / 10);
}
return firstPart;
}
}
}
catch (NumberFormatException e)
{
// 版本号解析失败,返回原始值
}
return fullVersion;
}
/**
* Windows版本号显示优化
*/
private static String getWindowsVersionDisplay(String version)
{
switch (version)
{
case "10.0":
return "10";
case "6.3":
return "8.1";
case "6.2":
return "8";
case "6.1":
return "7";
case "6.0":
return "Vista";
case "5.1":
return "XP";
case "5.0":
return "2000";
default:
return extractMajorVersion(version);
}
}
}

View File

@ -174,12 +174,12 @@ public class ExcelUtil<T>
/** /**
* 对象的子列表方法 * 对象的子列表方法
*/ */
private Method subMethod; private Map<String, Method> subMethods;
/** /**
* 对象的子列表属性 * 对象的子列表属性
*/ */
private List<Field> subFields; private Map<String, List<Field>> subFieldsMap;
/** /**
* 统计列表 * 统计列表
@ -252,7 +252,10 @@ public class ExcelUtil<T>
int titleLastCol = this.fields.size() - 1; int titleLastCol = this.fields.size() - 1;
if (isSubList()) if (isSubList())
{ {
titleLastCol = titleLastCol + subFields.size() - 1; for (List<Field> currentSubFields : subFieldsMap.values())
{
titleLastCol = titleLastCol + currentSubFields.size() - 1;
}
} }
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
titleRow.setHeightInPoints(30); titleRow.setHeightInPoints(30);
@ -272,16 +275,17 @@ public class ExcelUtil<T>
{ {
Row subRow = sheet.createRow(rownum); Row subRow = sheet.createRow(rownum);
int column = 0; int column = 0;
int subFieldSize = subFields != null ? subFields.size() : 0;
for (Object[] objects : fields) for (Object[] objects : fields)
{ {
Field field = (Field) objects[0]; Field field = (Field) objects[0];
Excel attr = (Excel) objects[1]; Excel attr = (Excel) objects[1];
CellStyle cellStyle = styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()));
if (Collection.class.isAssignableFrom(field.getType())) if (Collection.class.isAssignableFrom(field.getType()))
{ {
Cell cell = subRow.createCell(column); Cell cell = subRow.createCell(column);
cell.setCellValue(attr.name()); cell.setCellValue(attr.name());
cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); cell.setCellStyle(cellStyle);
int subFieldSize = subFieldsMap != null ? subFieldsMap.get(field.getName()).size() : 0;
if (subFieldSize > 1) if (subFieldSize > 1)
{ {
CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1);
@ -293,7 +297,7 @@ public class ExcelUtil<T>
{ {
Cell cell = subRow.createCell(column++); Cell cell = subRow.createCell(column++);
cell.setCellValue(attr.name()); cell.setCellValue(attr.name());
cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); cell.setCellStyle(cellStyle);
} }
} }
rownum++; rownum++;
@ -374,7 +378,11 @@ public class ExcelUtil<T>
Map<String, Integer> cellMap = new HashMap<String, Integer>(); Map<String, Integer> cellMap = new HashMap<String, Integer>();
// 获取表头 // 获取表头
Row heard = sheet.getRow(titleNum); Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) if (heard == null)
{
throw new UtilException("文件标题行为空请检查Excel文件格式");
}
for (int i = 0; i < heard.getLastCellNum(); i++)
{ {
Cell cell = heard.getCell(i); Cell cell = heard.getCell(i);
if (StringUtils.isNotNull(cell)) if (StringUtils.isNotNull(cell))
@ -382,10 +390,6 @@ public class ExcelUtil<T>
String value = this.getCellValue(heard, i).toString(); String value = this.getCellValue(heard, i).toString();
cellMap.put(value, i); cellMap.put(value, i);
} }
else
{
cellMap.put(null, i);
}
} }
// 有数据时才处理 得到类的所有field. // 有数据时才处理 得到类的所有field.
List<Object[]> fields = this.getFields(); List<Object[]> fields = this.getFields();
@ -697,7 +701,8 @@ public class ExcelUtil<T>
Excel excel = (Excel) os[1]; Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType())) if (Collection.class.isAssignableFrom(field.getType()))
{ {
for (Field subField : subFields) List<Field> currentSubFields = subFieldsMap.get(field.getName());
for (Field subField : currentSubFields)
{ {
Excel subExcel = subField.getAnnotation(Excel.class); Excel subExcel = subField.getAnnotation(Excel.class);
this.createHeadCell(subExcel, row, column++); this.createHeadCell(subExcel, row, column++);
@ -710,7 +715,7 @@ public class ExcelUtil<T>
} }
if (Type.EXPORT.equals(type)) if (Type.EXPORT.equals(type))
{ {
fillExcelData(index, row); fillExcelData(index);
addStatisticsRow(); addStatisticsRow();
} }
} }
@ -720,10 +725,9 @@ public class ExcelUtil<T>
* 填充excel数据 * 填充excel数据
* *
* @param index 序号 * @param index 序号
* @param row 单元格行
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void fillExcelData(int index, Row row) public void fillExcelData(int index)
{ {
int startNo = index * sheetSize; int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size()); int endNo = Math.min(startNo + sheetSize, list.size());
@ -731,7 +735,7 @@ public class ExcelUtil<T>
for (int i = startNo; i < endNo; i++) for (int i = startNo; i < endNo; i++)
{ {
row = sheet.createRow(currentRowNum); Row row = sheet.createRow(currentRowNum);
T vo = (T) list.get(i); T vo = (T) list.get(i);
int column = 0; int column = 0;
int maxSubListSize = getCurrentMaxSubListSize(vo); int maxSubListSize = getCurrentMaxSubListSize(vo);
@ -744,6 +748,7 @@ public class ExcelUtil<T>
try try
{ {
Collection<?> subList = (Collection<?>) getTargetValue(vo, field, excel); Collection<?> subList = (Collection<?>) getTargetValue(vo, field, excel);
List<Field> currentSubFields = subFieldsMap.get(field.getName());
if (subList != null && !subList.isEmpty()) if (subList != null && !subList.isEmpty())
{ {
int subIndex = 0; int subIndex = 0;
@ -756,15 +761,15 @@ public class ExcelUtil<T>
} }
int subColumn = column; int subColumn = column;
for (Field subField : subFields) for (Field subField : currentSubFields)
{ {
Excel subExcel = subField.getAnnotation(Excel.class); Excel subExcel = subField.getAnnotation(Excel.class);
addCell(subExcel, subRow, (T) subVo, subField, subColumn++); addCell(subExcel, subRow, (T) subVo, subField, subColumn++);
} }
subIndex++; subIndex++;
} }
column += subFields.size();
} }
column += currentSubFields.size();
} }
catch (Exception e) catch (Exception e)
{ {
@ -1120,7 +1125,7 @@ public class ExcelUtil<T>
* 添加单元格 * 添加单元格
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public Cell addCell(Excel attr, Row row, T vo, Field field, int column) public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
{ {
Cell cell = null; Cell cell = null;
try try
@ -1132,7 +1137,7 @@ public class ExcelUtil<T>
{ {
// 创建cell // 创建cell
cell = row.createCell(column); cell = row.createCell(column);
if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) if (isSubListValue(vo) && getListCellValue(vo) > 1 && attr.needMerge())
{ {
if (subMergedLastRowNum >= subMergedFirstRowNum) if (subMergedLastRowNum >= subMergedFirstRowNum)
{ {
@ -1149,7 +1154,7 @@ public class ExcelUtil<T>
String dictType = attr.dictType(); String dictType = attr.dictType();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{ {
cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); cell.setCellStyle(createCellStyle(cell.getCellStyle(), dateFormat));
cell.setCellValue(parseDateToStr(dateFormat, value)); cell.setCellValue(parseDateToStr(dateFormat, value));
} }
else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
@ -1188,6 +1193,21 @@ public class ExcelUtil<T>
return cell; return cell;
} }
/**
* 使用自定义格式,同时避免样式污染
*
* @param cellStyle 从此样式复制
* @param format 格式匹配的字符串
* @return 格式化后CellStyle对象
*/
private CellStyle createCellStyle(CellStyle cellStyle, String format)
{
CellStyle style = wb.createCellStyle();
style.cloneStyleFrom(cellStyle);
style.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(format));
return style;
}
/** /**
* 设置 POI XSSFSheet 单元格提示或选择框 * 设置 POI XSSFSheet 单元格提示或选择框
* *
@ -1239,18 +1259,36 @@ public class ExcelUtil<T>
public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol)
{ {
String hideSheetName = "combo_" + firstCol + "_" + endCol; String hideSheetName = "combo_" + firstCol + "_" + endCol;
Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 Sheet hideSheet = null;
for (int i = 0; i < textlist.length; i++) String hideSheetDataName = hideSheetName + "_data";
Name name = wb.getName(hideSheetDataName);
if (name != null)
{ {
hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); // 名称已存在尝试从名称的引用中找到sheet名称
String refersToFormula = name.getRefersToFormula();
if (StringUtils.isNotEmpty(refersToFormula) && refersToFormula.contains("!"))
{
String sheetNameFromFormula = refersToFormula.substring(0, refersToFormula.indexOf("!"));
hideSheet = wb.getSheet(sheetNameFromFormula);
}
} }
// 创建名称,可被其他单元格引用
Name name = wb.createName(); if (hideSheet == null)
name.setNameName(hideSheetName + "_data"); {
name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据
for (int i = 0; i < textlist.length; i++)
{
hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]);
}
// 创建名称,可被其他单元格引用
name = wb.createName();
name.setNameName(hideSheetDataName);
name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length);
}
DataValidationHelper helper = sheet.getDataValidationHelper(); DataValidationHelper helper = sheet.getDataValidationHelper();
// 加载下拉列表内容 // 加载下拉列表内容
DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetDataName);
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
// 数据有效性对象 // 数据有效性对象
@ -1538,6 +1576,8 @@ public class ExcelUtil<T>
{ {
List<Object[]> fields = new ArrayList<Object[]>(); List<Object[]> fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>(); List<Field> tempFields = new ArrayList<>();
subFieldsMap = new HashMap<>();
subMethods = new HashMap<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
if (StringUtils.isNotEmpty(includeFields)) if (StringUtils.isNotEmpty(includeFields))
@ -1585,10 +1625,11 @@ public class ExcelUtil<T>
} }
if (Collection.class.isAssignableFrom(field.getType())) if (Collection.class.isAssignableFrom(field.getType()))
{ {
subMethod = getSubMethod(field.getName(), clazz); String fieldName = field.getName();
subMethods.put(fieldName, getSubMethod(fieldName, clazz));
ParameterizedType pt = (ParameterizedType) field.getGenericType(); ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0]; Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); subFieldsMap.put(fieldName, FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class));
} }
} }
@ -1657,7 +1698,8 @@ public class ExcelUtil<T>
{ {
this.sheet = wb.createSheet(); this.sheet = wb.createSheet();
this.createTitle(); this.createTitle();
wb.setSheetName(index, sheetName + index); int actualIndex = wb.getSheetIndex(this.sheet);
wb.setSheetName(actualIndex, sheetName + index);
} }
} }
@ -1840,7 +1882,7 @@ public class ExcelUtil<T>
*/ */
public boolean isSubList() public boolean isSubList()
{ {
return StringUtils.isNotNull(subFields) && subFields.size() > 0; return !StringUtils.isEmpty(subFieldsMap);
} }
/** /**
@ -1848,24 +1890,32 @@ public class ExcelUtil<T>
*/ */
public boolean isSubListValue(T vo) public boolean isSubListValue(T vo)
{ {
return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; return !StringUtils.isEmpty(subFieldsMap) && getListCellValue(vo) > 0;
} }
/** /**
* 获取集合的值 * 获取集合的值
*/ */
public Collection<?> getListCellValue(Object obj) public int getListCellValue(Object obj)
{ {
Object value; Collection<?> value;
int max = 0;
try try
{ {
value = subMethod.invoke(obj, new Object[] {}); for (String s : subMethods.keySet())
{
value = (Collection<?>) subMethods.get(s).invoke(obj);
if (value.size() > max)
{
max = value.size();
}
}
} }
catch (Exception e) catch (Exception e)
{ {
return new ArrayList<Object>(); return 0;
} }
return (Collection<?>) value; return max;
} }
/** /**

View File

@ -13,7 +13,7 @@ public class SqlUtil
/** /**
* 定义常用的 sql关键字 * 定义常用的 sql关键字
*/ */
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/** /**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -94,7 +94,7 @@ public class DataScopeAspect
List<String> conditions = new ArrayList<String>(); List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>(); List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> { user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && (StringUtils.isEmpty(permission) || StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))))
{ {
scopeCustomIds.add(Convert.toStr(role.getRoleId())); scopeCustomIds.add(Convert.toStr(role.getRoleId()));
} }
@ -107,7 +107,7 @@ public class DataScopeAspect
{ {
continue; continue;
} }
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{ {
continue; continue;
} }

View File

@ -50,6 +50,9 @@ public class LogAspect
/** 计算操作消耗时间 */ /** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time"); private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/** 参数最大长度限制 */
private static final int PARAM_MAX_LENGTH = 2000;
/** /**
* 处理请求前执行 * 处理请求前执行
*/ */
@ -172,16 +175,16 @@ public class LogAspect
*/ */
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{ {
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod(); String requestMethod = operLog.getRequestMethod();
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name()))
{ {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000)); operLog.setOperParam(params);
} }
else else
{ {
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, PARAM_MAX_LENGTH));
} }
} }
@ -190,7 +193,7 @@ public class LogAspect
*/ */
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{ {
String params = ""; StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0) if (paramsArray != null && paramsArray.length > 0)
{ {
for (Object o : paramsArray) for (Object o : paramsArray)
@ -200,15 +203,20 @@ public class LogAspect
try try
{ {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " "; params.append(jsonObj).append(" ");
if (params.length() >= PARAM_MAX_LENGTH)
{
return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH);
}
} }
catch (Exception e) catch (Exception e)
{ {
log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e);
} }
} }
} }
} }
return params.trim(); return params.toString();
} }
/** /**

View File

@ -8,6 +8,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.filter.RefererFilter;
import com.ruoyi.common.filter.RepeatableFilter; import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter; import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@ -26,6 +28,9 @@ public class FilterConfig
@Value("${xss.urlPatterns}") @Value("${xss.urlPatterns}")
private String urlPatterns; private String urlPatterns;
@Value("${referer.allowed-domains}")
private String allowedDomains;
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
@Bean @Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
@ -43,6 +48,23 @@ public class FilterConfig
return registration; return registration;
} }
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(value = "referer.enabled", havingValue = "true")
public FilterRegistrationBean refererFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new RefererFilter());
registration.addUrlPatterns(Constants.RESOURCE_PREFIX + "/*");
registration.setName("refererFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("allowedDomains", allowedDomains);
registration.setInitParameters(initParameters);
return registration;
}
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
@Bean @Bean
public FilterRegistrationBean someFilterRegistration() public FilterRegistrationBean someFilterRegistration()

View File

@ -37,7 +37,7 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
@Override @Override
public void afterPropertiesSet() public void afterPropertiesSet()
{ {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods(); Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> { map.keySet().forEach(info -> {

View File

@ -7,6 +7,7 @@ import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.LogUtils; import com.ruoyi.common.utils.LogUtils;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.UserAgentUtils;
import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
@ -14,7 +15,6 @@ import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysLogininforService; import com.ruoyi.system.service.ISysLogininforService;
import com.ruoyi.system.service.ISysOperLogService; import com.ruoyi.system.service.ISysOperLogService;
import eu.bitwalker.useragentutils.UserAgent;
/** /**
* 异步工厂(产生任务用) * 异步工厂(产生任务用)
@ -37,7 +37,7 @@ public class AsyncFactory
public static TimerTask recordLogininfor(final String username, final String status, final String message, public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args) final Object... args)
{ {
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); final String userAgent = ServletUtils.getRequest().getHeader("User-Agent");
final String ip = IpUtils.getIpAddr(); final String ip = IpUtils.getIpAddr();
return new TimerTask() return new TimerTask()
{ {
@ -54,9 +54,9 @@ public class AsyncFactory
// 打印信息到日志 // 打印信息到日志
sys_user_logger.info(s.toString(), args); sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统 // 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName(); String os = UserAgentUtils.getOperatingSystem(userAgent);
// 获取客户端浏览器 // 获取客户端浏览器
String browser = userAgent.getBrowser().getName(); String browser = UserAgentUtils.getBrowser(userAgent);
// 封装对象 // 封装对象
SysLogininfor logininfor = new SysLogininfor(); SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username); logininfor.setUserName(username);

View File

@ -53,7 +53,7 @@ public class PermissionService
/** /**
* 验证用户是否具有以下任意一个权限 * 验证用户是否具有以下任意一个权限
* *
* @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 * @param permissions 以 PERMISSION_DELIMITER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限 * @return 用户是否具有以下任意一个权限
*/ */
public boolean hasAnyPermi(String permissions) public boolean hasAnyPermi(String permissions)
@ -69,7 +69,7 @@ public class PermissionService
} }
PermissionContextHolder.setContext(permissions); PermissionContextHolder.setContext(permissions);
Set<String> authorities = loginUser.getPermissions(); Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)) for (String permission : permissions.split(Constants.PERMISSION_DELIMITER))
{ {
if (permission != null && hasPermissions(authorities, permission)) if (permission != null && hasPermissions(authorities, permission))
{ {
@ -121,7 +121,7 @@ public class PermissionService
/** /**
* 验证用户是否具有以下任意一个角色 * 验证用户是否具有以下任意一个角色
* *
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @param roles 以 ROLE_DELIMITER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色 * @return 用户是否具有以下任意一个角色
*/ */
public boolean hasAnyRoles(String roles) public boolean hasAnyRoles(String roles)
@ -135,7 +135,7 @@ public class PermissionService
{ {
return false; return false;
} }
for (String role : roles.split(Constants.ROLE_DELIMETER)) for (String role : roles.split(Constants.ROLE_DELIMITER))
{ {
if (hasRole(role)) if (hasRole(role))
{ {

View File

@ -1,6 +1,6 @@
package com.ruoyi.framework.web.service; package com.ruoyi.framework.web.service;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;

View File

@ -6,6 +6,7 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
@ -39,7 +40,7 @@ public class SysPermissionService
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin())
{ {
roles.add("admin"); roles.add(Constants.SUPER_ADMIN);
} }
else else
{ {
@ -60,7 +61,7 @@ public class SysPermissionService
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin())
{ {
perms.add("*:*:*"); perms.add(Constants.ALL_PERMISSION);
} }
else else
{ {

View File

@ -15,10 +15,10 @@ import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.UserAgentUtils;
import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
@ -161,12 +161,12 @@ public class TokenService
*/ */
public void setUserAgent(LoginUser loginUser) public void setUserAgent(LoginUser loginUser)
{ {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); String userAgent = ServletUtils.getRequest().getHeader("User-Agent");
String ip = IpUtils.getIpAddr(); String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip); loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName()); loginUser.setBrowser(UserAgentUtils.getBrowser(userAgent));
loginUser.setOs(userAgent.getOperatingSystem().getName()); loginUser.setOs(UserAgentUtils.getOperatingSystem(userAgent));
} }
/** /**

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -334,7 +334,7 @@ function getList() {
#foreach ($column in $columns) #foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
if (null != daterange${AttrName} && '' != daterange${AttrName}) { if (null != daterange${AttrName}.value && '' != daterange${AttrName}.value) {
queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0] queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0]
queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1] queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1]
} }

View File

@ -415,7 +415,7 @@ function getList() {
#foreach ($column in $columns) #foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
if (null != daterange${AttrName} && '' != daterange${AttrName}) { if (null != daterange${AttrName}.value && '' != daterange${AttrName}.value) {
queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0] queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0]
queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1] queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1]
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -21,12 +21,6 @@
<dependency> <dependency>
<groupId>org.quartz-scheduler</groupId> <groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId> <artifactId>quartz</artifactId>
<exclusions>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- 通用工具--> <!-- 通用工具-->

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -122,4 +122,13 @@ public interface SysMenuMapper
* @return 结果 * @return 结果
*/ */
public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId);
/**
* 根据路由路径或名称查询菜单信息(用于唯一性校验)
*
* @param path 路由地址
* @param routeName 路由名称
* @return 匹配的菜单列表
*/
public List<SysMenu> selectMenusByPathOrRouteName(@Param("path") String path, @Param("routeName") String routeName);
} }

View File

@ -141,4 +141,12 @@ public interface ISysMenuService
* @return 结果 * @return 结果
*/ */
public boolean checkMenuNameUnique(SysMenu menu); public boolean checkMenuNameUnique(SysMenu menu);
/**
* 校验路由组合是否唯一
*
* @param menu 菜单信息
* @return 结果
*/
public boolean checkRouteConfigUnique(SysMenu menu);
} }

View File

@ -11,7 +11,6 @@ import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.TreeSelect; import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
@ -190,7 +189,7 @@ public class SysDeptServiceImpl implements ISysDeptService
@Override @Override
public void checkDeptDataScope(Long deptId) public void checkDeptDataScope(Long deptId)
{ {
if (!SysUser.isAdmin(SecurityUtils.getUserId()) && StringUtils.isNotNull(deptId)) if (!SecurityUtils.isAdmin() && StringUtils.isNotNull(deptId))
{ {
SysDept dept = new SysDept(); SysDept dept = new SysDept();
dept.setDeptId(deptId); dept.setDeptId(deptId);

View File

@ -8,6 +8,8 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
@ -15,7 +17,6 @@ import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.TreeSelect; import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.vo.MetaVo; import com.ruoyi.system.domain.vo.MetaVo;
@ -33,8 +34,12 @@ import com.ruoyi.system.service.ISysMenuService;
@Service @Service
public class SysMenuServiceImpl implements ISysMenuService public class SysMenuServiceImpl implements ISysMenuService
{ {
private static final Logger log = LoggerFactory.getLogger(SysMenuServiceImpl.class);
public static final String PREMISSION_STRING = "perms[\"{0}\"]"; public static final String PREMISSION_STRING = "perms[\"{0}\"]";
public static final Long MENU_ROOT_ID = 0L;
@Autowired @Autowired
private SysMenuMapper menuMapper; private SysMenuMapper menuMapper;
@ -67,7 +72,7 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
List<SysMenu> menuList = null; List<SysMenu> menuList = null;
// 管理员显示所有菜单信息 // 管理员显示所有菜单信息
if (SysUser.isAdmin(userId)) if (SecurityUtils.isAdmin(userId))
{ {
menuList = menuMapper.selectMenuList(menu); menuList = menuMapper.selectMenuList(menu);
} }
@ -139,7 +144,7 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
menus = menuMapper.selectMenuTreeByUserId(userId); menus = menuMapper.selectMenuTreeByUserId(userId);
} }
return getChildPerms(menus, 0); return getChildPerms(menus, MENU_ROOT_ID);
} }
/** /**
@ -194,7 +199,7 @@ public class SysMenuServiceImpl implements ISysMenuService
childrenList.add(children); childrenList.add(children);
router.setChildren(childrenList); router.setChildren(childrenList);
} }
else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) else if (menu.getParentId().intValue() == MENU_ROOT_ID && isInnerLink(menu))
{ {
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
router.setPath("/"); router.setPath("/");
@ -346,6 +351,47 @@ public class SysMenuServiceImpl implements ISysMenuService
return UserConstants.UNIQUE; return UserConstants.UNIQUE;
} }
/**
* 校验路由名称是否唯一
*
* @param menu 菜单信息
* @return 结果
*/
@Override
public boolean checkRouteConfigUnique(SysMenu menu)
{
Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
Long parentId = menu.getParentId();
String path = menu.getPath();
String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName();
List<SysMenu> sysMenuList = menuMapper.selectMenusByPathOrRouteName(path, routeName);
for (SysMenu sysMenu : sysMenuList)
{
if (sysMenu.getMenuId().longValue() != menuId.longValue())
{
Long dbParentId = sysMenu.getParentId();
String dbPath = sysMenu.getPath();
String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName();
if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == dbParentId.longValue())
{
log.warn("[同级路由冲突] 同级下已存在相同路由路径 '{}',冲突菜单:{}", dbPath, sysMenu.getMenuName());
return UserConstants.NOT_UNIQUE;
}
else if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == MENU_ROOT_ID)
{
log.warn("[根目录路由冲突] 根目录下路由 '{}' 必须唯一,已被菜单 '{}' 占用", path, sysMenu.getMenuName());
return UserConstants.NOT_UNIQUE;
}
else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName))
{
log.warn("[路由名称冲突] 路由名称 '{}' 需全局唯一,已被菜单 '{}' 使用", routeName, sysMenu.getMenuName());
return UserConstants.NOT_UNIQUE;
}
}
}
return UserConstants.UNIQUE;
}
/** /**
* 获取路由名称 * 获取路由名称
* *
@ -385,12 +431,12 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
String routerPath = menu.getPath(); String routerPath = menu.getPath();
// 内链打开外网方式 // 内链打开外网方式
if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) if (menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu))
{ {
routerPath = innerLinkReplaceEach(routerPath); routerPath = innerLinkReplaceEach(routerPath);
} }
// 非外链并且是一级目录(类型为目录) // 非外链并且是一级目录(类型为目录)
if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) if (MENU_ROOT_ID == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
&& UserConstants.NO_FRAME.equals(menu.getIsFrame())) && UserConstants.NO_FRAME.equals(menu.getIsFrame()))
{ {
routerPath = "/" + menu.getPath(); routerPath = "/" + menu.getPath();
@ -416,7 +462,7 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
component = menu.getComponent(); component = menu.getComponent();
} }
else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu))
{ {
component = UserConstants.INNER_LINK; component = UserConstants.INNER_LINK;
} }
@ -435,10 +481,21 @@ public class SysMenuServiceImpl implements ISysMenuService
*/ */
public boolean isMenuFrame(SysMenu menu) public boolean isMenuFrame(SysMenu menu)
{ {
return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) return menu.getParentId().intValue() == MENU_ROOT_ID && UserConstants.TYPE_MENU.equals(menu.getMenuType())
&& menu.getIsFrame().equals(UserConstants.NO_FRAME); && menu.getIsFrame().equals(UserConstants.NO_FRAME);
} }
/**
* 是否为parent_view组件
*
* @param menu 菜单信息
* @return 结果
*/
public boolean isParentView(SysMenu menu)
{
return menu.getParentId().intValue() != MENU_ROOT_ID && UserConstants.TYPE_DIR.equals(menu.getMenuType());
}
/** /**
* 是否为内链组件 * 是否为内链组件
* *
@ -450,17 +507,6 @@ public class SysMenuServiceImpl implements ISysMenuService
return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());
} }
/**
* 是否为parent_view组件
*
* @param menu 菜单信息
* @return 结果
*/
public boolean isParentView(SysMenu menu)
{
return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());
}
/** /**
* 根据父节点的ID获取所有子节点 * 根据父节点的ID获取所有子节点
* *
@ -468,7 +514,7 @@ public class SysMenuServiceImpl implements ISysMenuService
* @param parentId 传入的父节点ID * @param parentId 传入的父节点ID
* @return String * @return String
*/ */
public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) public List<SysMenu> getChildPerms(List<SysMenu> list, long parentId)
{ {
List<SysMenu> returnList = new ArrayList<SysMenu>(); List<SysMenu> returnList = new ArrayList<SysMenu>();
for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();) for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)

View File

@ -11,7 +11,6 @@ import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@ -197,7 +196,7 @@ public class SysRoleServiceImpl implements ISysRoleService
@Override @Override
public void checkRoleDataScope(Long... roleIds) public void checkRoleDataScope(Long... roleIds)
{ {
if (!SysUser.isAdmin(SecurityUtils.getUserId())) if (!SecurityUtils.isAdmin())
{ {
for (Long roleId : roleIds) for (Long roleId : roleIds)
{ {

View File

@ -239,7 +239,7 @@ public class SysUserServiceImpl implements ISysUserService
@Override @Override
public void checkUserDataScope(Long userId) public void checkUserDataScope(Long userId)
{ {
if (!SysUser.isAdmin(SecurityUtils.getUserId())) if (!SecurityUtils.isAdmin())
{ {
SysUser user = new SysUser(); SysUser user = new SysUser();
user.setUserId(userId); user.setUserId(userId);

View File

@ -130,7 +130,12 @@
<select id="checkMenuNameUnique" parameterType="SysMenu" resultMap="SysMenuResult"> <select id="checkMenuNameUnique" parameterType="SysMenu" resultMap="SysMenuResult">
<include refid="selectMenuVo"/> <include refid="selectMenuVo"/>
where menu_name=#{menuName} and parent_id = #{parentId} limit 1 where menu_name= #{menuName} and parent_id = #{parentId} limit 1
</select>
<select id="selectMenusByPathOrRouteName" parameterType="SysMenu" resultMap="SysMenuResult">
<include refid="selectMenuVo"/>
where menu_type in ('M', 'C') and (path = #{path} or path = #{routeName} or route_name = #{path} or route_name = #{routeName})
</select> </select>
<update id="updateMenu" parameterType="SysMenu"> <update id="updateMenu" parameterType="SysMenu">

View File

@ -1,6 +1,6 @@
{ {
"name": "ruoyi", "name": "ruoyi",
"version": "3.9.0", "version": "3.9.1",
"description": "若依管理系统", "description": "若依管理系统",
"author": "若依", "author": "若依",
"license": "MIT", "license": "MIT",

View File

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

View File

@ -130,6 +130,16 @@
border-radius: 4px; border-radius: 4px;
} }
/* horizontal el menu */
.el-menu--horizontal .el-menu-item .svg-icon + span,
.el-menu--horizontal .el-submenu__title .svg-icon + span {
margin-left: 3px;
}
.el-menu--horizontal .el-menu--popup {
min-width: 120px !important;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.pagination-container .el-pagination > .el-pagination__jump { .pagination-container .el-pagination > .el-pagination__jump {
display: none !important; display: none !important;

View File

@ -61,7 +61,7 @@
} }
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 10px !important;
} }
.el-menu { .el-menu {
@ -116,15 +116,17 @@
margin-left: 54px; margin-left: 54px;
} }
.submenu-title-noDropdown { .el-menu:not(.el-menu--horizontal) {
padding: 0 !important; .submenu-title-noDropdown {
position: relative;
.el-tooltip {
padding: 0 !important; padding: 0 !important;
position: relative;
.svg-icon { .el-tooltip {
margin-left: 20px; padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
} }
} }
} }

View File

@ -94,7 +94,6 @@ export default {
display: inline-block; display: inline-block;
font-size: 14px; font-size: 14px;
line-height: 50px; line-height: 50px;
margin-left: 8px;
.no-redirect { .no-redirect {
color: #97a8be; color: #97a8be;
cursor: text; cursor: text;

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<template v-for="(item, index) in options"> <template v-for="(item, index) in options">
<template v-if="values.includes(item.value)"> <template v-if="isValueMatch(item.value)">
<span <span
v-if="(item.raw.listClass == 'default' || item.raw.listClass == '') && (item.raw.cssClass == '' || item.raw.cssClass == null)" v-if="(item.raw.listClass == 'default' || item.raw.listClass == '') && (item.raw.cssClass == '' || item.raw.cssClass == null)"
:key="item.value" :key="item.value"
@ -36,7 +36,6 @@ export default {
default: null, default: null,
}, },
value: [Number, String, Array], value: [Number, String, Array],
// 当未找到匹配的数据时显示value
showValue: { showValue: {
type: Boolean, type: Boolean,
default: true, default: true,
@ -54,6 +53,7 @@ export default {
computed: { computed: {
values() { values() {
if (this.value === null || typeof this.value === 'undefined' || this.value === '') return [] if (this.value === null || typeof this.value === 'undefined' || this.value === '') return []
if (typeof this.value === 'number' || typeof this.value === 'boolean') return [this.value]
return Array.isArray(this.value) ? this.value.map(item => '' + item) : String(this.value).split(this.separator) return Array.isArray(this.value) ? this.value.map(item => '' + item) : String(this.value).split(this.separator)
}, },
unmatch() { unmatch() {
@ -63,14 +63,18 @@ export default {
// 传入值为数组 // 传入值为数组
let unmatch = false // 添加一个标志来判断是否有未匹配项 let unmatch = false // 添加一个标志来判断是否有未匹配项
this.values.forEach(item => { this.values.forEach(item => {
if (!this.options.some(v => v.value === item)) { if (!this.options.some(v => v.value == item)) {
this.unmatchArray.push(item) this.unmatchArray.push(item)
unmatch = true // 如果有未匹配项将标志设置为true unmatch = true // 如果有未匹配项将标志设置为true
} }
}) })
return unmatch // 返回标志的值 return unmatch // 返回标志的值
}, },
},
methods: {
isValueMatch(itemValue) {
return this.values.some(val => val == itemValue)
}
}, },
filters: { filters: {
handleArray(array) { handleArray(array) {

View File

@ -118,8 +118,6 @@ export default {
this.fuse = new Fuse(list, { this.fuse = new Fuse(list, {
shouldSort: true, shouldSort: true,
threshold: 0.4, threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1, minMatchCharLength: 1,
keys: [{ keys: [{
name: 'title', name: 'title',

View File

@ -162,7 +162,7 @@ export default {
this.$store.dispatch('app/toggleSideBarHide', true) this.$store.dispatch('app/toggleSideBarHide', true)
} }
} }
}, }
} }
</script> </script>
@ -171,7 +171,7 @@ export default {
float: left; float: left;
height: 50px !important; height: 50px !important;
line-height: 50px !important; line-height: 50px !important;
color: #999093 !important; color: #303133 !important;
padding: 0 5px !important; padding: 0 5px !important;
margin: 0 10px !important; margin: 0 10px !important;
} }
@ -186,7 +186,7 @@ export default {
float: left; float: left;
height: 50px !important; height: 50px !important;
line-height: 50px !important; line-height: 50px !important;
color: #999093 !important; color: #303133 !important;
padding: 0 5px !important; padding: 0 5px !important;
margin: 0 10px !important; margin: 0 10px !important;
} }

View File

@ -53,12 +53,19 @@ export default {
overflow: hidden; overflow: hidden;
} }
.fixed-header + .app-main {
overflow-y: auto;
scrollbar-gutter: auto;
height: calc(100vh - 50px);
min-height: 0px;
}
.app-main:has(.copyright) { .app-main:has(.copyright) {
padding-bottom: 36px; padding-bottom: 36px;
} }
.fixed-header + .app-main { .fixed-header + .app-main {
padding-top: 50px; margin-top: 50px;
} }
.hasTagsView { .hasTagsView {
@ -68,19 +75,47 @@ export default {
} }
.fixed-header + .app-main { .fixed-header + .app-main {
padding-top: 84px; margin-top: 84px;
height: calc(100vh - 84px);
min-height: 0px;
}
}
/* 移动端fixed-header优化 */
@media screen and (max-width: 991px) {
.fixed-header + .app-main {
padding-bottom: max(60px, calc(constant(safe-area-inset-bottom) + 40px));
padding-bottom: max(60px, calc(env(safe-area-inset-bottom) + 40px));
overscroll-behavior-y: none;
}
.hasTagsView .fixed-header + .app-main {
padding-bottom: max(60px, calc(constant(safe-area-inset-bottom) + 40px));
padding-bottom: max(60px, calc(env(safe-area-inset-bottom) + 40px));
overscroll-behavior-y: none;
}
}
@supports (-webkit-touch-callout: none) {
@media screen and (max-width: 991px) {
.fixed-header + .app-main {
padding-bottom: max(17px, calc(constant(safe-area-inset-bottom) + 10px));
padding-bottom: max(17px, calc(env(safe-area-inset-bottom) + 10px));
height: calc(100svh - 50px);
height: calc(100dvh - 50px);
}
.hasTagsView .fixed-header + .app-main {
padding-bottom: max(17px, calc(constant(safe-area-inset-bottom) + 10px));
padding-bottom: max(17px, calc(env(safe-area-inset-bottom) + 10px));
height: calc(100svh - 84px);
height: calc(100dvh - 84px);
}
} }
} }
</style> </style>
<style lang="scss"> <style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 6px;
}
}
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px; height: 6px;

View File

@ -1,10 +1,13 @@
<template> <template>
<div class="navbar"> <div class="navbar" :class="'nav' + navType">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" /> <breadcrumb v-if="navType == 1" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="topNav" id="topmenu-container" class="topmenu-container" /> <top-nav v-if="navType == 2" id="topmenu-container" class="topmenu-container" />
<template v-if="navType == 3">
<logo v-show="showLogo" :collapse="false"></logo>
<top-bar id="topbar-container" class="topbar-container" />
</template>
<div class="right-menu"> <div class="right-menu">
<template v-if="device!=='mobile'"> <template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" /> <search id="header-search" class="right-menu-item" />
@ -50,6 +53,8 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb' import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav' import TopNav from '@/components/TopNav'
import TopBar from './TopBar'
import Logo from './Sidebar/Logo'
import Hamburger from '@/components/Hamburger' import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull' import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect' import SizeSelect from '@/components/SizeSelect'
@ -61,7 +66,9 @@ export default {
emits: ['setLayout'], emits: ['setLayout'],
components: { components: {
Breadcrumb, Breadcrumb,
Logo,
TopNav, TopNav,
TopBar,
Hamburger, Hamburger,
Screenfull, Screenfull,
SizeSelect, SizeSelect,
@ -81,9 +88,14 @@ export default {
return this.$store.state.settings.showSettings return this.$store.state.settings.showSettings
} }
}, },
topNav: { navType: {
get() { get() {
return this.$store.state.settings.topNav return this.$store.state.settings.navType
}
},
showLogo: {
get() {
return this.$store.state.settings.sidebarLogo
} }
} }
}, },
@ -110,20 +122,33 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.navbar.nav3 {
.hamburger-container {
display: none !important;
}
}
.navbar { .navbar {
height: 50px; height: 50px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: #fff; background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08); box-shadow: 0 1px 4px rgba(0,21,41,.08);
display: flex;
align-items: center;
// padding: 0 8px;
box-sizing: border-box;
.hamburger-container { .hamburger-container {
line-height: 46px; line-height: 46px;
height: 100%; height: 100%;
float: left;
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background .3s;
-webkit-tap-highlight-color:transparent; -webkit-tap-highlight-color:transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, .025)
@ -131,7 +156,7 @@ export default {
} }
.breadcrumb-container { .breadcrumb-container {
float: left; flex-shrink: 0;
} }
.topmenu-container { .topmenu-container {
@ -139,15 +164,26 @@ export default {
left: 50px; left: 50px;
} }
.topbar-container {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
overflow: hidden;
margin-left: 8px;
}
.errLog-container { .errLog-container {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
} }
.right-menu { .right-menu {
float: right;
height: 100%; height: 100%;
line-height: 50px; line-height: 50px;
display: flex;
align-items: center;
margin-left: auto;
&:focus { &:focus {
outline: none; outline: none;

View File

@ -3,6 +3,27 @@
<div class="drawer-container"> <div class="drawer-container">
<div> <div>
<div class="setting-drawer-content"> <div class="setting-drawer-content">
<div class="setting-drawer-title">
<h3 class="drawer-title">菜单导航设置</h3>
</div>
<div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom">
<div class="item left" @click="handleNavType(1)" :style="{'--theme': theme}" :class="{ activeItem: navType == 1 }">
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="混合菜单" placement="bottom">
<div class="item mix" @click="handleNavType(2)" :style="{'--theme': theme}" :class="{ activeItem: navType == 2 }">
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="顶部菜单" placement="bottom">
<div class="item top" @click="handleNavType(3)" :style="{'--theme': theme}" :class="{ activeItem: navType == 3 }">
<b></b><b></b>
</div>
</el-tooltip>
</div>
<div class="setting-drawer-title"> <div class="setting-drawer-title">
<h3 class="drawer-title">主题风格设置</h3> <h3 class="drawer-title">主题风格设置</h3>
</div> </div>
@ -39,11 +60,6 @@
<h3 class="drawer-title">系统布局配置</h3> <h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>开启 TopNav</span>
<el-switch v-model="topNav" class="drawer-switch" />
</div>
<div class="drawer-item"> <div class="drawer-item">
<span>开启 Tags-Views</span> <span>开启 Tags-Views</span>
<el-switch v-model="tagsView" class="drawer-switch" /> <el-switch v-model="tagsView" class="drawer-switch" />
@ -93,6 +109,7 @@ export default {
return { return {
theme: this.$store.state.settings.theme, theme: this.$store.state.settings.theme,
sideTheme: this.$store.state.settings.sideTheme, sideTheme: this.$store.state.settings.sideTheme,
navType: this.$store.state.settings.navType,
showSettings: false showSettings: false
} }
}, },
@ -108,21 +125,6 @@ export default {
}) })
} }
}, },
topNav: {
get() {
return this.$store.state.settings.topNav
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'topNav',
value: val
})
if (!val) {
this.$store.dispatch('app/toggleSideBarHide', false)
this.$store.commit("SET_SIDEBAR_ROUTERS", this.$store.state.permission.defaultRoutes)
}
}
},
tagsView: { tagsView: {
get() { get() {
return this.$store.state.settings.tagsView return this.$store.state.settings.tagsView
@ -180,6 +182,25 @@ export default {
} }
} }
}, },
watch: {
navType: {
handler(val) {
if (val == 1) {
this.$store.dispatch("app/toggleSideBarHide", false)
}
if (val == 2) {
}
if (val == 3) {
this.$store.dispatch("app/toggleSideBarHide", true)
}
if ([1, 3].includes(val)) {
this.$store.commit("SET_SIDEBAR_ROUTERS",this.$store.state.permission.defaultRoutes)
}
},
immediate: true,
deep: true
}
},
methods: { methods: {
themeChange(val) { themeChange(val) {
this.$store.dispatch('settings/changeSetting', { this.$store.dispatch('settings/changeSetting', {
@ -195,6 +216,13 @@ export default {
}) })
this.sideTheme = val this.sideTheme = val
}, },
handleNavType(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'navType',
value: val
})
this.navType = val
},
openSetting() { openSetting() {
this.showSettings = true this.showSettings = true
}, },
@ -206,7 +234,7 @@ export default {
this.$cache.local.set( this.$cache.local.set(
"layout-setting", "layout-setting",
`{ `{
"topNav":${this.topNav}, "navType":${this.navType},
"tagsView":${this.tagsView}, "tagsView":${this.tagsView},
"tagsIcon":${this.tagsIcon}, "tagsIcon":${this.tagsIcon},
"fixedHeader":${this.fixedHeader}, "fixedHeader":${this.fixedHeader},
@ -229,70 +257,133 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.setting-drawer-content { .setting-drawer-content {
.setting-drawer-title { .setting-drawer-title {
margin-bottom: 12px; margin-bottom: 12px;
color: rgba(0, 0, 0, .85); color: rgba(0, 0, 0, .85);
font-size: 14px; font-size: 14px;
line-height: 22px; line-height: 22px;
font-weight: bold; font-weight: bold;
} }
.setting-drawer-block-checbox { .setting-drawer-block-checbox {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
margin-top: 10px; margin-top: 10px;
margin-bottom: 20px; margin-bottom: 20px;
.setting-drawer-block-checbox-item { .setting-drawer-block-checbox-item {
position: relative; position: relative;
margin-right: 16px; margin-right: 16px;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
img { img {
width: 48px; width: 48px;
height: 48px; height: 48px;
} }
.setting-drawer-block-checbox-selectIcon { .setting-drawer-block-checbox-selectIcon {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-top: 15px; padding-top: 15px;
padding-left: 24px; padding-left: 24px;
color: #1890ff; color: #1890ff;
font-weight: 700; font-weight: 700;
font-size: 14px; font-size: 14px;
}
} }
} }
} }
}
.drawer-container { .drawer-container {
padding: 20px; padding: 20px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
.drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, .85);
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 22px;
word-wrap: break-word; }
.drawer-title { .drawer-item {
margin-bottom: 12px; color: rgba(0, 0, 0, .65);
color: rgba(0, 0, 0, .85); font-size: 14px;
font-size: 14px; padding: 12px 0;
line-height: 22px; }
.drawer-switch {
float: right
}
}
// 导航模式
.nav-wrap {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
margin-bottom: 20px;
.activeItem {
border: 2px solid #{'var(--theme)'} !important;
}
.item {
position: relative;
margin-right: 16px;
cursor: pointer;
width: 56px;
height: 48px;
border-radius: 4px;
background: #f0f2f5;
border: 2px solid transparent;
}
.left {
b:first-child {
display: block;
height: 30%;
background: #fff;
} }
b:last-child {
.drawer-item { width: 30%;
color: rgba(0, 0, 0, .65); background: #1b2a47;
font-size: 14px; position: absolute;
padding: 12px 0; height: 100%;
} top: 0;
border-radius: 4px 0 0 4px;
.drawer-switch {
float: right
} }
} }
.mix {
b:first-child {
border-radius: 4px 4px 0 0;
display: block;
height: 30%;
background: #1b2a47;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 70%;
border-radius: 0 0 0 4px;
}
}
.top {
b:first-child {
display: block;
height: 30%;
background: #1b2a47;
border-radius: 4px 4px 0 0;
}
}
}
</style> </style>

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"> <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' && navType !== 3 ? variables.menuBackground : variables.menuLightBackground }">
<transition name="sidebarLogoFade"> <transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1> <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
</router-link> </router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> <router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1> <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
</router-link> </router-link>
</transition> </transition>
</div> </div>
@ -31,6 +31,9 @@ export default {
}, },
sideTheme() { sideTheme() {
return this.$store.state.settings.sideTheme return this.$store.state.settings.sideTheme
},
navType() {
return this.$store.state.settings.navType
} }
}, },
data() { data() {
@ -54,7 +57,6 @@ export default {
.sidebar-logo-container { .sidebar-logo-container {
position: relative; position: relative;
width: 100%;
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
background: #2b2f3a; background: #2b2f3a;

View File

@ -0,0 +1,98 @@
<template>
<el-menu class="topbar-menu" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
<el-submenu index="more" class="el-submenu__hide-arrow" v-if="moreRoutes.length > 0">
<template slot="title">更多菜单</template>
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
</el-submenu>
</el-menu>
</template>
<script>
import SidebarItem from '../Sidebar/SidebarItem'
export default {
components: { SidebarItem },
data() {
return {
// 顶部栏初始数
visibleNumber: 5
}
},
computed: {
theme() {
return this.$store.state.settings.theme
},
topMenus() {
return this.$store.state.permission.sidebarRouters.filter((f) => !f.hidden).slice(0, this.visibleNumber)
},
moreRoutes() {
const sidebarRouters = this.$store.state.permission.sidebarRouters;
return sidebarRouters.filter((f) => !f.hidden).slice(this.visibleNumber, sidebarRouters.length - this.visibleNumber)
},
// 默认激活的菜单
activeMenu() {
const { meta, path } = this.$route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
},
beforeMount() {
window.addEventListener('resize', this.setVisibleNumber)
},
beforeDestroy() {
window.removeEventListener('resize', this.setVisibleNumber)
},
mounted() {
this.setVisibleNumber()
},
methods: {
// 根据宽度计算设置显示栏数
setVisibleNumber() {
const width = document.body.getBoundingClientRect().width / 3
this.visibleNumber = parseInt(width / 85)
}
}
}
</script>
<style lang="scss">
/* menu item */
.topbar-menu.el-menu--horizontal .el-submenu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
padding: 0 10px !important;
}
.el-menu--horizontal .el-menu--popup .el-menu-item:hover {
background-color: #f5f7fa !important;
}
/* submenu item */
.topbar-menu.el-menu--horizontal > .el-submenu .el-submenu__title {
float: left;
height: 47px !important;
line-height: 50px !important;
color: #303133;
margin: 0 15px !important;
}
/* topbar more arrow */
.topbar-menu .el-submenu .el-submenu__icon-arrow {
position: static;
vertical-align: middle;
margin-left: 8px;
margin-top: 0px;
}
/* menu__title el-menu-item */
.topbar-menu.el-menu--horizontal .el-submenu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
height: 55px;
}
.el-menu--horizontal .el-menu .el-menu-item, .el-menu--horizontal .el-menu .el-submenu__title{
color: #303133;
}
</style>

View File

@ -77,6 +77,11 @@ export default {
} }
} }
.main-container:has(.fixed-header) {
height: 100vh;
overflow: hidden;
}
.drawer-bg { .drawer-bg {
background: #000; background: #000;
opacity: 0.3; opacity: 0.3;

View File

@ -15,9 +15,9 @@ module.exports = {
showSettings: true, showSettings: true,
/** /**
* 是否显示顶部导航 * 菜单导航模式 1、纯左侧 2、混合左侧+顶部) 3、纯顶部
*/ */
topNav: false, navType: 1,
/** /**
* 是否显示 tagsView * 是否显示 tagsView
@ -32,7 +32,7 @@ module.exports = {
/** /**
* 是否固定头部 * 是否固定头部
*/ */
fixedHeader: false, fixedHeader: true,
/** /**
* 是否显示logo * 是否显示logo
@ -52,5 +52,5 @@ module.exports = {
/** /**
* 底部版权文本内容 * 底部版权文本内容
*/ */
footerContent: 'Copyright © 2018-2025 RuoYi. All Rights Reserved.' footerContent: 'Copyright © 2018-2026 RuoYi. All Rights Reserved.'
} }

View File

@ -1,7 +1,7 @@
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
import { useDynamicTitle } from '@/utils/dynamicTitle' import { useDynamicTitle } from '@/utils/dynamicTitle'
const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings const { sideTheme, showSettings, navType, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
const state = { const state = {
@ -9,7 +9,7 @@ const state = {
theme: storageSetting.theme || '#409EFF', theme: storageSetting.theme || '#409EFF',
sideTheme: storageSetting.sideTheme || sideTheme, sideTheme: storageSetting.sideTheme || sideTheme,
showSettings: showSettings, showSettings: showSettings,
topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, navType: storageSetting.navType === undefined ? navType : storageSetting.navType,
tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon, tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
@ -23,6 +23,9 @@ const mutations = {
if (state.hasOwnProperty(key)) { if (state.hasOwnProperty(key)) {
state[key] = value state[key] = value
} }
},
SET_TITLE: (state, title) => {
state.title = title
} }
} }
@ -33,7 +36,7 @@ const actions = {
}, },
// 设置网页标题 // 设置网页标题
setTitle({ commit }, title) { setTitle({ commit }, title) {
state.title = title commit('SET_TITLE', title)
useDynamicTitle() useDynamicTitle()
} }
} }

View File

@ -1,29 +1,37 @@
export default [ export const drawingDefaultValue = []
{
layout: 'colFormItem', export function initDrawingDefaultValue() {
tagIcon: 'input', if (drawingDefaultValue.length === 0) {
label: '手机号', drawingDefaultValue.push({
vModel: 'mobile', layout: 'colFormItem',
formId: 6, tagIcon: 'input',
tag: 'el-input', label: '手机号',
placeholder: '请输入手机号', vModel: 'mobile',
defaultValue: '', formId: 6,
span: 24, tag: 'el-input',
style: { width: '100%' }, placeholder: '请输入手机号',
clearable: true, defaultValue: '',
prepend: '', span: 24,
append: '', style: {width: '100%'},
'prefix-icon': 'el-icon-mobile', clearable: true,
'suffix-icon': '', prepend: '',
maxlength: 11, append: '',
'show-word-limit': true, 'prefix-icon': 'el-icon-mobile',
readonly: false, 'suffix-icon': '',
disabled: false, maxlength: 11,
required: true, 'show-word-limit': true,
changeTag: true, readonly: false,
regList: [{ disabled: false,
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', required: true,
message: '手机号格式错误' changeTag: true,
}] regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
})
} }
] }
export function cleanDrawingDefaultValue() {
drawingDefaultValue.splice(0, drawingDefaultValue.length)
}

View File

@ -26,6 +26,8 @@ service.interceptors.request.use(config => {
const isToken = (config.headers || {}).isToken === false const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交 // 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
// 间隔时间(ms),小于此时间视为重复提交
const interval = (config.headers || {}).interval || 1000
if (getToken() && !isToken) { if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
} }
@ -55,7 +57,6 @@ service.interceptors.request.use(config => {
const s_url = sessionObj.url // 请求地址 const s_url = sessionObj.url // 请求地址
const s_data = sessionObj.data // 请求数据 const s_data = sessionObj.data // 请求数据
const s_time = sessionObj.time // 请求时间 const s_time = sessionObj.time // 请求时间
const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交' const message = '数据正在处理,请勿重复提交'
console.warn(`[${s_url}]: ` + message) console.warn(`[${s_url}]: ` + message)
@ -115,7 +116,7 @@ service.interceptors.response.use(res => {
} else if (message.includes("timeout")) { } else if (message.includes("timeout")) {
message = "系统接口请求超时" message = "系统接口请求超时"
} else if (message.includes("Request failed with status code")) { } else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常" message = "系统接口" + message.slice(-3) + "异常"
} }
Message({ message: message, type: 'error', duration: 5 * 1000 }) Message({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error) return Promise.reject(error)

View File

@ -87,7 +87,8 @@
<s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s> <s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s>
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s> <s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s>
<s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s> <s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s>
<s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766" target="_blank">191164766</a> <s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s> <s> 191164766 </s>
<s> 满174569686 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=M9y5NjAl44lAL_Vh2crmEehZU_PMU6KS&authKey=ZSDz8hEREWSaPuxQV3gEwqGIaGjfRNnkB4rJjf0IvXhrSUGSGwQFmBA%2Boe8HFxyl&noverify=0&group_code=127358632" target="_blank">127358632</a>
</p> </p>
<p> <p>
<i class="el-icon-chat-dot-round"></i> 微信<a <i class="el-icon-chat-dot-round"></i> 微信<a
@ -111,6 +112,42 @@
<span>更新日志</span> <span>更新日志</span>
</div> </div>
<el-collapse accordion> <el-collapse accordion>
<el-collapse-item title="v3.9.1 - 2025-12-18">
<ol>
<li>支持防盗链功能</li>
<li>菜单导航设置支持纯顶部</li>
<li>使用yauaa代替bitwalker</li>
<li>用户头像更换后移除旧头像文件</li>
<li>支持Excel导出对象的多个子列表</li>
<li>升级oshi到最新版本6.9.1</li>
<li>升级druid到最新版本1.2.27</li>
<li>升级fastjson到最新版2.0.60</li>
<li>升级spring-security到5.7.14</li>
<li>升级tomcat到最新版本9.0.112</li>
<li>升级commons.io到最新版本2.21.0</li>
<li>用户导入添加验证提示</li>
<li>显示列信息支持对象格式</li>
<li>忽略用户密码字段的JSON序列化</li>
<li>网页标题设置新增SET_TITLE方法</li>
<li>自动识别json对象白名单配置范围缩小</li>
<li>登录/注册页面底部版权信息修改为读取配置</li>
<li>修复用户归属部门无法修改为空问题</li>
<li>修复固定头部时出现的导航栏偏移问题</li>
<li>修复v3时间控件between选择后清空报错问题</li>
<li>修复comboReadDict属性下多个sheet出现的报错</li>
<li>修复表单构建移除所有控件后切换路由回来空白问题</li>
<li>优化布局设置显示</li>
<li>优化字典组件值宽松匹配</li>
<li>优化获取字典类型值的方法</li>
<li>优化生成代码下载的zip文件名</li>
<li>优化日志记录参数拼装提升效率</li>
<li>优化导入文件检查标题行不能为空</li>
<li>优化表单构建关闭页签销毁复制插件</li>
<li>优化Excel统计行数值的单元格样式显示</li>
<li>优化数据权限控制逻辑放开permission限制</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.9.0 - 2025-05-28"> <el-collapse-item title="v3.9.0 - 2025-05-28">
<ol> <ol>
<li>优化菜单搜索查询页</li> <li>优化菜单搜索查询页</li>
@ -1059,7 +1096,7 @@ export default {
data() { data() {
return { return {
// 版本号 // 版本号
version: "3.9.0" version: "3.9.1"
} }
}, },
methods: { methods: {

View File

@ -56,7 +56,7 @@
</el-form> </el-form>
<!-- 底部 --> <!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span> <span>{{ footerContent }}</span>
</div> </div>
</div> </div>
</template> </template>
@ -65,12 +65,14 @@
import { getCodeImg } from "@/api/login" import { getCodeImg } from "@/api/login"
import Cookies from "js-cookie" import Cookies from "js-cookie"
import { encrypt, decrypt } from '@/utils/jsencrypt' import { encrypt, decrypt } from '@/utils/jsencrypt'
import defaultSettings from '@/settings'
export default { export default {
name: "Login", name: "Login",
data() { data() {
return { return {
title: process.env.VUE_APP_TITLE, title: process.env.VUE_APP_TITLE,
footerContent: defaultSettings.footerContent,
codeUrl: "", codeUrl: "",
loginForm: { loginForm: {
username: "admin", username: "admin",
@ -156,7 +158,7 @@ export default {
} }
</script> </script>
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss" scoped>
.login { .login {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -477,13 +477,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.jobId != undefined) { if (this.form.jobId != undefined) {
updateJob(this.form).then(response => { updateJob(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addJob(this.form).then(response => { addJob(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -61,13 +61,14 @@
</el-form> </el-form>
<!-- 底部 --> <!-- 底部 -->
<div class="el-register-footer"> <div class="el-register-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span> <span>{{ footerContent }}</span>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { getCodeImg, register } from "@/api/login" import { getCodeImg, register } from "@/api/login"
import defaultSettings from '@/settings'
export default { export default {
name: "Register", name: "Register",
@ -81,6 +82,7 @@ export default {
} }
return { return {
title: process.env.VUE_APP_TITLE, title: process.env.VUE_APP_TITLE,
footerContent: defaultSettings.footerContent,
codeUrl: "", codeUrl: "",
registerForm: { registerForm: {
username: "", username: "",
@ -126,7 +128,7 @@ export default {
this.$refs.registerForm.validate(valid => { this.$refs.registerForm.validate(valid => {
if (valid) { if (valid) {
this.loading = true this.loading = true
register(this.registerForm).then(res => { register(this.registerForm).then(() => {
const username = this.registerForm.username const username = this.registerForm.username
this.$alert("<font color='red'>恭喜你,您的账号 " + username + " 注册成功!</font>", '系统提示', { this.$alert("<font color='red'>恭喜你,您的账号 " + username + " 注册成功!</font>", '系统提示', {
dangerouslyUseHTMLString: true, dangerouslyUseHTMLString: true,
@ -147,7 +149,7 @@ export default {
} }
</script> </script>
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss" scoped>
.register { .register {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -301,13 +301,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.configId != undefined) { if (this.form.configId != undefined) {
updateConfig(this.form).then(response => { updateConfig(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addConfig(this.form).then(response => { addConfig(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -311,13 +311,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.deptId != undefined) { if (this.form.deptId != undefined) {
updateDept(this.form).then(response => { updateDept(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addDept(this.form).then(response => { addDept(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -363,14 +363,14 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.dictCode != undefined) { if (this.form.dictCode != undefined) {
updateData(this.form).then(response => { updateData(this.form).then(() => {
this.$store.dispatch('dict/removeDict', this.queryParams.dictType) this.$store.dispatch('dict/removeDict', this.queryParams.dictType)
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addData(this.form).then(response => { addData(this.form).then(() => {
this.$store.dispatch('dict/removeDict', this.queryParams.dictType) this.$store.dispatch('dict/removeDict', this.queryParams.dictType)
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false

View File

@ -304,13 +304,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.dictId != undefined) { if (this.form.dictId != undefined) {
updateType(this.form).then(response => { updateType(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addType(this.form).then(response => { addType(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -448,13 +448,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.menuId != undefined) { if (this.form.menuId != undefined) {
updateMenu(this.form).then(response => { updateMenu(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addMenu(this.form).then(response => { addMenu(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -282,13 +282,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.noticeId != undefined) { if (this.form.noticeId != undefined) {
updateNotice(this.form).then(response => { updateNotice(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addNotice(this.form).then(response => { addNotice(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -273,13 +273,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.postId != undefined) { if (this.form.postId != undefined) {
updatePost(this.form).then(response => { updatePost(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addPost(this.form).then(response => { addPost(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -184,7 +184,7 @@ export default {
}).catch(() => {}) }).catch(() => {})
}, },
/** 批量取消授权按钮操作 */ /** 批量取消授权按钮操作 */
cancelAuthUserAll(row) { cancelAuthUserAll() {
const roleId = this.queryParams.roleId const roleId = this.queryParams.roleId
const userIds = this.userIds.join(",") const userIds = this.userIds.join(",")
this.$modal.confirm('是否取消选中用户授权数据项?').then(function() { this.$modal.confirm('是否取消选中用户授权数据项?').then(function() {

View File

@ -557,14 +557,14 @@ export default {
if (valid) { if (valid) {
if (this.form.roleId != undefined) { if (this.form.roleId != undefined) {
this.form.menuIds = this.getMenuAllCheckedKeys() this.form.menuIds = this.getMenuAllCheckedKeys()
updateRole(this.form).then(response => { updateRole(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
this.form.menuIds = this.getMenuAllCheckedKeys() this.form.menuIds = this.getMenuAllCheckedKeys()
addRole(this.form).then(response => { addRole(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()
@ -577,7 +577,7 @@ export default {
submitDataScope: function() { submitDataScope: function() {
if (this.form.roleId != undefined) { if (this.form.roleId != undefined) {
this.form.deptIds = this.getDeptAllCheckedKeys() this.form.deptIds = this.getDeptAllCheckedKeys()
dataScope(this.form).then(response => { dataScope(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.openDataScope = false this.openDataScope = false
this.getList() this.getList()

View File

@ -108,7 +108,7 @@ export default {
submitForm() { submitForm() {
const userId = this.form.userId const userId = this.form.userId
const roleIds = this.roleIds.join(",") const roleIds = this.roleIds.join(",")
updateAuthRole({ userId: userId, roleIds: roleIds }).then((response) => { updateAuthRole({ userId: userId, roleIds: roleIds }).then(() => {
this.$modal.msgSuccess("授权成功") this.$modal.msgSuccess("授权成功")
this.close() this.close()
}) })

View File

@ -476,7 +476,7 @@ export default {
} }
}, },
}).then(({ value }) => { }).then(({ value }) => {
resetUserPwd(row.userId, value).then(response => { resetUserPwd(row.userId, value).then(() => {
this.$modal.msgSuccess("修改成功,新密码是:" + value) this.$modal.msgSuccess("修改成功,新密码是:" + value)
}) })
}).catch(() => {}) }).catch(() => {})
@ -491,13 +491,13 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.userId != undefined) { if (this.form.userId != undefined) {
updateUser(this.form).then(response => { updateUser(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
this.getList() this.getList()
}) })
} else { } else {
addUser(this.form).then(response => { addUser(this.form).then(() => {
this.$modal.msgSuccess("新增成功") this.$modal.msgSuccess("新增成功")
this.open = false this.open = false
this.getList() this.getList()

View File

@ -55,7 +55,7 @@ export default {
submit() { submit() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => { updateUserPwd(this.user.oldPassword, this.user.newPassword).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
}) })
} }

View File

@ -72,7 +72,7 @@ export default {
submit() { submit() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
updateUserProfile(this.form).then(response => { updateUserProfile(this.form).then(() => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.user.phonenumber = this.form.phonenumber this.user.phonenumber = this.form.phonenumber
this.user.email = this.form.email this.user.email = this.form.email

View File

@ -146,13 +146,14 @@ import { beautifierConf, titleCase } from '@/utils/index'
import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/html' import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/html'
import { makeUpJs } from '@/utils/generator/js' import { makeUpJs } from '@/utils/generator/js'
import { makeUpCss } from '@/utils/generator/css' import { makeUpCss } from '@/utils/generator/css'
import drawingDefault from '@/utils/generator/drawingDefault' import { drawingDefaultValue, initDrawingDefaultValue, cleanDrawingDefaultValue } from '@/utils/generator/drawingDefault'
import logo from '@/assets/logo/logo.png' import logo from '@/assets/logo/logo.png'
import CodeTypeDialog from './CodeTypeDialog' import CodeTypeDialog from './CodeTypeDialog'
import DraggableItem from './DraggableItem' import DraggableItem from './DraggableItem'
let oldActiveId let oldActiveId
let tempActiveData let tempActiveData
let clipboard = null
export default { export default {
components: { components: {
@ -171,17 +172,20 @@ export default {
selectComponents, selectComponents,
layoutComponents, layoutComponents,
labelWidth: 100, labelWidth: 100,
drawingList: drawingDefault, drawingList: drawingDefaultValue,
drawingData: {}, drawingData: {},
activeId: drawingDefault[0].formId, activeId: drawingDefaultValue[0].formId,
drawerVisible: false, drawerVisible: false,
formData: {}, formData: {},
dialogVisible: false, dialogVisible: false,
generateConf: null, generateConf: null,
showFileName: false, showFileName: false,
activeData: drawingDefault[0] activeData: drawingDefaultValue[0]
} }
}, },
beforeCreate() {
initDrawingDefaultValue()
},
created() { created() {
// 防止 firefox 下 拖拽 会新打卡一个选项卡 // 防止 firefox 下 拖拽 会新打卡一个选项卡
document.body.ondrop = event => { document.body.ondrop = event => {
@ -208,7 +212,7 @@ export default {
} }
}, },
mounted() { mounted() {
const clipboard = new ClipboardJS('#copyNode', { clipboard = new ClipboardJS('#copyNode', {
text: trigger => { text: trigger => {
const codeStr = this.generateCode() const codeStr = this.generateCode()
this.$notify({ this.$notify({
@ -223,6 +227,9 @@ export default {
this.$message.error('代码复制失败') this.$message.error('代码复制失败')
}) })
}, },
beforeDestroy() {
clipboard.destroy()
},
methods: { methods: {
activeFormItem(element) { activeFormItem(element) {
this.activeData = element this.activeData = element
@ -284,6 +291,7 @@ export default {
this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then( this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
() => { () => {
this.drawingList = [] this.drawingList = []
cleanDrawingDefaultValue()
} }
) )
}, },

View File

@ -98,9 +98,9 @@
<span>{{(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1}}</span> <span>{{(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" width="120" /> <el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" width="140" />
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="120" /> <el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="140" />
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" width="120" /> <el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" width="140" />
<el-table-column label="创建时间" align="center" prop="createTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="160" /> <el-table-column label="创建时间" align="center" prop="createTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="160" />
<el-table-column label="更新时间" align="center" prop="updateTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="160" /> <el-table-column label="更新时间" align="center" prop="updateTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@ -262,11 +262,12 @@ export default {
return return
} }
if(row.genType === "1") { if(row.genType === "1") {
genCode(row.tableName).then(response => { genCode(row.tableName).then(() => {
this.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath) this.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath)
}) })
} else { } else {
this.$download.zip("/tool/gen/batchGenCode?tables=" + tableNames, "ruoyi.zip") const zipName = Array.isArray(tableNames) ? "ruoyi.zip" : tableNames + ".zip"
this.$download.zip("/tool/gen/batchGenCode?tables=" + tableNames, zipName)
} }
}, },
/** 同步数据库操作 */ /** 同步数据库操作 */