40 Commits

Author SHA1 Message Date
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
e2badb7c68 升级oshi到最新版本6.8.3 2025-08-28 13:38:19 +08:00
40f54f9700 优化代码 2025-08-28 13:37:57 +08:00
534bd7b8cd 优化代码 2025-08-27 15:34:47 +08:00
6c811c88c2 用户导入添加验证提示 2025-08-23 11:14:45 +08:00
eed3ae8f80 优化布局设置显示 2025-08-23 11:14:36 +08:00
54 changed files with 1107 additions and 332 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>
@ -92,4 +92,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) 点击按钮入群。

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.2</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

@ -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

@ -3,7 +3,7 @@ ruoyi:
# 名称 # 名称
name: RuoYi name: RuoYi
# 版本 # 版本
version: 3.9.0 version: 3.9.1
# 版权年份 # 版权年份
copyrightYear: 2025 copyrightYear: 2025
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath # 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
@ -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,6 +5,7 @@ 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;
@ -200,6 +201,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

@ -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

@ -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)
{ {
@ -1239,18 +1244,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 +1561,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 +1610,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 +1683,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 +1867,7 @@ public class ExcelUtil<T>
*/ */
public boolean isSubList() public boolean isSubList()
{ {
return StringUtils.isNotNull(subFields) && subFields.size() > 0; return !StringUtils.isEmpty(subFieldsMap);
} }
/** /**
@ -1848,24 +1875,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

@ -10,7 +10,6 @@ import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants; 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.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; 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.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
@ -172,10 +171,6 @@ public class SysLoginService
*/ */
public void recordLoginInfo(Long userId) public void recordLoginInfo(Long userId)
{ {
SysUser sysUser = new SysUser(); userService.updateLoginInfo(userId, IpUtils.getIpAddr(), DateUtils.getNowDate());
sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr());
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
} }
} }

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.getOperatingSystem(userAgent));
loginUser.setOs(userAgent.getOperatingSystem().getName()); loginUser.setOs(UserAgentUtils.getBrowser(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

@ -1,5 +1,6 @@
package com.ruoyi.system.mapper; package com.ruoyi.system.mapper;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
@ -76,6 +77,25 @@ public interface SysUserMapper
*/ */
public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar); public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar);
/**
* 修改用户状态
*
* @param userId 用户ID
* @param status 状态
* @return 结果
*/
public int updateUserStatus(@Param("userId") Long userId, @Param("status") String status);
/**
* 更新用户登录信息IP和登录时间
*
* @param userId 用户ID
* @param loginIp 登录IP地址
* @param loginDate 登录时间
* @return 结果
*/
public int updateLoginInfo(@Param("userId") Long userId, @Param("loginIp") String loginIp, @Param("loginDate") Date loginDate);
/** /**
* 重置用户密码 * 重置用户密码
* *

View File

@ -1,5 +1,6 @@
package com.ruoyi.system.service; package com.ruoyi.system.service;
import java.util.Date;
import java.util.List; import java.util.List;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
@ -161,6 +162,16 @@ public interface ISysUserService
*/ */
public boolean updateUserAvatar(Long userId, String avatar); public boolean updateUserAvatar(Long userId, String avatar);
/**
* 更新用户登录信息IP和登录时间
*
* @param userId 用户ID
* @param loginIp 登录IP地址
* @param loginDate 登录时间
* @return 结果
*/
public void updateLoginInfo(Long userId, String loginIp, Date loginDate);
/** /**
* 重置用户密码 * 重置用户密码
* *

View File

@ -1,6 +1,7 @@
package com.ruoyi.system.service.impl; package com.ruoyi.system.service.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.validation.Validator; import jakarta.validation.Validator;
@ -326,7 +327,7 @@ public class SysUserServiceImpl implements ISysUserService
@Override @Override
public int updateUserStatus(SysUser user) public int updateUserStatus(SysUser user)
{ {
return userMapper.updateUser(user); return userMapper.updateUserStatus(user.getUserId(), user.getStatus());
} }
/** /**
@ -354,6 +355,19 @@ public class SysUserServiceImpl implements ISysUserService
return userMapper.updateUserAvatar(userId, avatar) > 0; return userMapper.updateUserAvatar(userId, avatar) > 0;
} }
/**
* 更新用户登录信息IP和登录时间
*
* @param userId 用户ID
* @param loginIp 登录IP地址
* @param loginDate 登录时间
* @return 结果
*/
public void updateLoginInfo(Long userId, String loginIp, Date loginDate)
{
userMapper.updateLoginInfo(userId, loginIp, loginDate);
}
/** /**
* 重置用户密码 * 重置用户密码
* *
@ -363,7 +377,7 @@ public class SysUserServiceImpl implements ISysUserService
@Override @Override
public int resetPwd(SysUser user) public int resetPwd(SysUser user)
{ {
return userMapper.updateUser(user); return userMapper.resetUserPwd(user.getUserId(), user.getPassword());
} }
/** /**
@ -517,6 +531,7 @@ public class SysUserServiceImpl implements ISysUserService
checkUserDataScope(u.getUserId()); checkUserDataScope(u.getUserId());
deptService.checkDeptDataScope(user.getDeptId()); deptService.checkDeptDataScope(user.getDeptId());
user.setUserId(u.getUserId()); user.setUserId(u.getUserId());
user.setDeptId(u.getDeptId());
user.setUpdateBy(operName); user.setUpdateBy(operName);
userMapper.updateUser(user); userMapper.updateUser(user);
successNum++; successNum++;

View File

@ -198,15 +198,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update> </update>
<update id="updateUserStatus" parameterType="SysUser"> <update id="updateUserStatus" parameterType="SysUser">
update sys_user set status = #{status} where user_id = #{userId} update sys_user set status = #{status}, update_time = sysdate() where user_id = #{userId}
</update> </update>
<update id="updateUserAvatar" parameterType="SysUser"> <update id="updateUserAvatar" parameterType="SysUser">
update sys_user set avatar = #{avatar} where user_id = #{userId} update sys_user set avatar = #{avatar}, update_time = sysdate() where user_id = #{userId}
</update>
<update id="updateLoginInfo" parameterType="SysUser">
update sys_user set login_ip = #{loginIp}, login_date = #{loginDate} where user_id = #{userId}
</update> </update>
<update id="resetUserPwd" parameterType="SysUser"> <update id="resetUserPwd" parameterType="SysUser">
update sys_user set pwd_update_date = sysdate(), password = #{password} where user_id = #{userId} update sys_user set pwd_update_date = sysdate(), password = #{password}, update_time = sysdate() where user_id = #{userId}
</update> </update>
<delete id="deleteUserById" parameterType="Long"> <delete id="deleteUserById" parameterType="Long">

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

@ -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,18 +116,20 @@
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;
}
} }
} }
} }
.el-submenu { .el-submenu {
overflow: hidden; overflow: hidden;

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

@ -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" />
@ -34,15 +37,14 @@
<router-link to="/user/profile"> <router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item>个人中心</el-dropdown-item>
</router-link> </router-link>
<el-dropdown-item @click.native="setLayout" v-if="setting">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided @click.native="logout"> <el-dropdown-item divided @click.native="logout">
<span>退出登录</span> <span>退出登录</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<div class="right-menu-item hover-effect setting" @click="setLayout" v-if="setting">
<svg-icon icon-class="more-up" />
</div>
</div> </div>
</div> </div>
</template> </template>
@ -51,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'
@ -62,7 +66,9 @@ export default {
emits: ['setLayout'], emits: ['setLayout'],
components: { components: {
Breadcrumb, Breadcrumb,
Logo,
TopNav, TopNav,
TopBar,
Hamburger, Hamburger,
Screenfull, Screenfull,
SizeSelect, SizeSelect,
@ -82,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
} }
} }
}, },
@ -111,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)
@ -132,7 +156,7 @@ export default {
} }
.breadcrumb-container { .breadcrumb-container {
float: left; flex-shrink: 0;
} }
.topmenu-container { .topmenu-container {
@ -140,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;
@ -178,6 +213,7 @@ export default {
.avatar-wrapper { .avatar-wrapper {
margin-top: 10px; margin-top: 10px;
right: 8px;
position: relative; position: relative;
.user-avatar { .user-avatar {
@ -190,6 +226,7 @@ export default {
.user-nickname{ .user-nickname{
position: relative; position: relative;
bottom: 10px; bottom: 10px;
left: 2px;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
} }

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

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

@ -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>
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=EeCBXu51I1zPWRia2uskpjDRx6VrbnFN&authKey=Xm8yDxk0%2FyYGI11oxhXaQnTn4K7UwCk7Kn2MZTh3P1JxLctollAkyeySjnaILDkb&noverify=0&group_code=174569686" target="_blank">174569686</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

@ -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: "",
@ -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

@ -546,6 +546,11 @@ export default {
}, },
// 提交上传文件 // 提交上传文件
submitFileForm() { submitFileForm() {
const file = this.$refs.upload.uploadFiles
if (!file || file.length === 0 || !file[0].name.toLowerCase().endsWith('.xls') && !file[0].name.toLowerCase().endsWith('.xlsx')) {
this.$modal.msgError("请选择后缀为 “xls”或“xlsx”的文件。")
return
}
this.$refs.upload.submit() this.$refs.upload.submit()
} }
} }

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

@ -266,7 +266,8 @@ export default {
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)
} }
}, },
/** 同步数据库操作 */ /** 同步数据库操作 */