91 Commits

Author SHA1 Message Date
RuoYi
8c49d541ba 用户密码支持自定义配置规则 2026-04-17 14:48:04 +08:00
RuoYi
91185005ce 优化代码生成同步操作column_type没更新问题 2026-04-16 14:25:47 +08:00
RuoYi
8dd8bc784a 优化白名单支持对通配符路径匹配 2026-04-16 13:44:10 +08:00
RuoYi
bcdb182129 通知公告新增阅读用户&详细 2026-04-14 16:44:04 +08:00
RuoYi
2bffc42d19 新增标签页样式chrome风格 2026-04-13 10:11:12 +08:00
RuoYi
a1e7704286 新增代码生成详情页功能 2026-04-12 10:42:38 +08:00
RuoYi
ad242d9327 自动导入配置 2026-04-10 16:37:54 +08:00
RuoYi
19748abe4b 代码生成修改拖拽时显示手指样式 2026-04-10 16:36:42 +08:00
RuoYi
cb7f4b2877 用户列表新增抽屉效果详细信息 2026-04-09 14:14:01 +08:00
RuoYi
c9e3c072d2 优化样式 2026-04-03 22:34:34 +08:00
RuoYi
314a36bcfd 数据权限注解支持自定义字段名 2026-04-03 15:41:12 +08:00
RuoYi
fba922ccb7 新增Excel导入组件ExcelImportDialog 2026-04-03 08:56:51 +08:00
RuoYi
566cbc8c5d 新增树分割组件TreePanel 2026-04-02 11:37:05 +08:00
RuoYi
3b0982515e 代码生成支持表单布局选项 2026-04-02 11:11:26 +08:00
RuoYi
d9a6263626 支持表格列显隐状态记忆 2026-03-31 19:50:01 +08:00
RuoYi
b2685bf8e8 支持多sheet导出 2026-03-31 18:48:52 +08:00
RuoYi
b0b4d2e446 若依 3.6.8 2026-03-30 08:01:38 +08:00
RuoYi
af768553bb 优化页签全屏显示 2026-03-26 00:53:45 +08:00
RuoYi
74bd4562bd 优化快速点击页签刷新出现404问题 2026-03-25 21:22:56 +08:00
RuoYi
219848fdb7 修复typescript版多表代码生成报错问题 2026-03-25 13:03:24 +08:00
RuoYi
b232e394c6 菜单管理列表新增类型显示 2026-03-24 15:34:39 +08:00
RuoYi
9df5446d5f 优化菜单主题风格 2026-03-24 10:55:47 +08:00
RuoYi
997dc3968e 升级axios到最新版本0.30.3 2026-03-23 11:45:19 +08:00
RuoYi
751844f441 优化页签显示 2026-03-23 11:43:46 +08:00
RuoYi
74207e2593 添加持久化标签页开关功能 2026-03-22 23:30:05 +08:00
RuoYi
943be56511 添加持久化标签页开关功能 2026-03-22 21:17:08 +08:00
RuoYi
e30ecdbf2d 优化tag全屏为页签内区域 2026-03-22 21:16:36 +08:00
RuoYi
4c952fbe36 菜单搜索支持文本高亮&数量提示 2026-03-22 16:56:12 +08:00
RuoYi
00ac3e3df6 首页新增通知公告消息提醒 2026-03-21 23:57:52 +08:00
RuoYi
1eaf3a6ec7 新增锁定屏幕功能 2026-03-21 23:30:07 +08:00
RuoYi
59392c7b35 菜单管理支持批量保存排序 2026-03-21 22:52:41 +08:00
RuoYi
29d4ea24e0 保存排序添加编辑权限 2026-03-21 22:40:02 +08:00
RuoYi
ca80568837 优化定时任务详情页展示&补充执行时间字段 2026-03-21 22:20:52 +08:00
RuoYi
254e7522db 字典类型列表新增抽屉效果详细信息 2026-03-21 22:07:25 +08:00
RuoYi
466f0f559d 操作日志详细页面优化 2026-03-21 21:12:17 +08:00
RuoYi
970f718351 优化RightToolbar搜索栏切换动画 2026-03-21 20:51:40 +08:00
RuoYi
97759ec0ed TypeScript前端代码生成模板同步到最新 2026-03-18 13:07:43 +08:00
RuoYi
114e806326 update README.md 2026-03-14 13:44:46 +08:00
RuoYi
09e7ccdc61 update README.md 2026-03-14 13:42:31 +08:00
若依
7a829c44cb !452 固定nacos版本
Merge pull request !452 from magic/fix/docker-sp2
2026-03-12 14:22:06 +00:00
gowshwah
2844201b6f 固定nacos版本 2026-03-12 17:22:12 +08:00
RuoYi
18079f70d0 TypeScript前端代码生成模板同步到最新 2026-03-12 14:13:31 +08:00
RuoYi
1a3f3ebaea update README.md 2026-03-11 14:42:39 +08:00
RuoYi
8c122a9609 升级fastjson到最新版2.0.61 2026-03-09 17:41:30 +08:00
RuoYi
de1dbe61a5 update userid default value 2026-03-09 17:38:37 +08:00
RuoYi
156471d638 优化Excel自定义格式样式重复创建问题 2026-03-09 15:12:11 +08:00
RuoYi
661e8cf7b4 优化字典类型属性提醒说明 2026-02-11 15:38:13 +08:00
RuoYi
28be96930c README.md 2026-01-28 21:21:10 +08:00
RuoYi
6c6a2c0623 update README.md 2026-01-28 21:16:49 +08:00
RuoYi
7d821ed043 优化代码 2026-01-28 21:16:05 +08:00
RuoYi
5d54693b27 添加菜单路由地址和名称的校验规则 2026-01-09 11:19:02 +08:00
RuoYi
cfd619fa4f 优化防重提交间隔时间可自定义 2026-01-08 13:42:00 +08:00
RuoYi
e942847b89 修复Excel自定义格式样式污染问题 2026-01-06 13:54:27 +08:00
RuoYi
0b09d7c1da copyright 2026 2026-01-05 14:50:53 +08:00
RuoYi
1c0562ba18 将isAdmin方法统一到SecurityUtils 2026-01-05 14:50:37 +08:00
RuoYi
da65996e69 若依 3.6.7 2025-12-22 09:17:03 +08:00
RuoYi
873444dd0b 优化topbar顶部菜单样式 2025-12-18 14:45:57 +08:00
RuoYi
2301280c4d 默认固定头部 2025-12-18 14:42:34 +08:00
RuoYi
4eebb27956 优化字典组件值宽松匹配 2025-12-16 16:42:15 +08:00
RuoYi
c2ff461ff0 升级fastjson到最新版2.0.60 2025-12-16 13:44:40 +08:00
RuoYi
eb489bda83 升级commons.io到最新版本2.21.0 2025-12-16 13:44:16 +08:00
RuoYi
1db2086163 升级druid到最新版本1.2.27 2025-12-16 13:43:20 +08:00
RuoYi
839e4f35f5 菜单导航设置支持纯顶部 2025-12-16 11:42:07 +08:00
RuoYi
f53b783049 优化数据权限控制逻辑,放开permission限制 2025-12-04 17:35:47 +08:00
RuoYi
97f30a5415 支持Excel导出对象的多个子列表 2025-12-04 16:48:19 +08:00
RuoYi
ad1d009165 升级tomcat到最新版本9.0.112 2025-12-04 16:43:11 +08:00
RuoYi
90cbabb7a7 优化代码 2025-12-04 16:42:55 +08:00
RuoYi
1c4dbb1e46 优化表单构建关闭页签销毁复制插件 2025-12-04 13:15:50 +08:00
RuoYi
a3eefb6bad 优化生成代码下载的zip文件名 2025-12-03 10:27:23 +08:00
RuoYi
09e8e9995a 网页标题设置新增SET_TITLE方法 2025-12-02 19:31:23 +08:00
RuoYi
381151bc50 支持Excel导出对象的多个子列表 2025-12-02 19:14:04 +08:00
RuoYi
66e502727a 登录/注册页面底部版权信息修改为读取配置 2025-12-02 15:34:27 +08:00
RuoYi
4265f8ecb7 修复v3时间控件between选择后清空报错问题 2025-12-02 15:00:36 +08:00
RuoYi
2c82079d04 修复表单构建移除所有控件后切换路由回来空白问题 2025-12-02 13:12:55 +08:00
RuoYi
6aecd35a4f 修复combo属性过多sheet出现的异常问题 2025-11-13 11:58:03 +08:00
RuoYi
ac92ae3ae6 修复固定头部时出现的导航栏偏移问题 2025-09-04 20:06:26 +08:00
RuoYi
1d2c8378f7 文件支持防盗链配置 2025-09-02 13:26:33 +08:00
RuoYi
50a9337ee8 优化代码 2025-08-28 13:51:15 +08:00
RuoYi
e6a3415a71 优化代码 2025-08-27 16:01:28 +08:00
RuoYi
7919af54da 用户导入添加验证提示 2025-08-23 11:46:49 +08:00
RuoYi
3503005f9d 优化布局设置显示 2025-08-23 11:46:39 +08:00
RuoYi
b304a41194 修复用户归属部门无法修改为空问题 2025-08-21 14:59:10 +08:00
RuoYi
a39ae33c82 columns default value 2025-08-09 16:13:49 +08:00
RuoYi
b9a27657c5 显示列信息支持对象格式 2025-08-09 15:19:13 +08:00
RuoYi
2e009841ca 自动识别json对象白名单配置范围缩小 2025-08-09 15:18:29 +08:00
RuoYi
2cbe4a8234 升级tomcat到最新版本9.0.108 2025-08-09 15:17:10 +08:00
RuoYi
4b37049713 添加新群号:112869560 2025-07-19 19:28:44 +08:00
RuoYi
e549210ad6 优化定时任务包名白名单匹配方式 2025-06-20 11:56:03 +08:00
RuoYi
ad988d54bb 优化Excel统计行数值的单元格样式显示 2025-06-19 14:54:00 +08:00
RuoYi
51a6fce0a5 升级tomcat到最新版本9.0.106 2025-06-18 13:41:37 +08:00
RuoYi
c86bfa9243 用户头像更换后移除旧头像文件 2025-06-06 19:36:36 +08:00
178 changed files with 8944 additions and 1480 deletions

View File

@@ -1,11 +1,11 @@
<p align="center"> <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png"> <img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png">
</p> </p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.6.6</h1> <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.6.8</h1>
<h4 align="center">基于 Vue/Element UI 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4> <h4 align="center">基于 Vue/Element UI 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4>
<p align="center"> <p align="center">
<a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a> <a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.6-brightgreen.svg"></a> <a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.8-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a> <a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p> </p>
@@ -13,14 +13,35 @@
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* 采用前后端分离的模式,微服务版本前端(基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)) * 本仓库为RuoYi-Cloud的Spring Boot 2 的版本,保持同步更新
* 后端采用Spring Boot、Spring Cloud & Alibaba。 * 后端采用Spring Boot2、Spring Cloud & Alibaba。
* 注册中心、配置中心选型Nacos权限认证使用Redis。 * 注册中心、配置中心选型Nacos权限认证使用Redis。
* 流量控制框架选型Sentinel分布式事务选型Seata。 * 流量控制框架选型Sentinel分布式事务选型Seata。
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Cloud-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Cloud-Vue3),保持同步更新。
* 如需不分离应用,请移步 [RuoYi](https://gitee.com/y_project/RuoYi),如需分离应用,请移步 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)
* 阿里云优惠券:[点我进入](http://aly.ruoyi.vip),腾讯云优惠券:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp; * 阿里云优惠券:[点我进入](http://aly.ruoyi.vip),腾讯云优惠券:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
# 版本分支
RuoYi-Cloud 后端项目提供 Spring Boot 2.x / 3.x / 4.x 多版本分支的并行维护。
| 名称 | 说明 | 地址 |
| :---------------- | :----------------------------------- | :-------------------------------------------------------- |
| master 默认分支 | Spring Boot 4.x (JDK 17+、Nacos 3.x) | https://gitee.com/y_project/RuoYi-Cloud |
| springboot3 分支 | Spring Boot 3.x (JDK 17+、Nacos 3.x) | https://gitee.com/y_project/RuoYi-Cloud/tree/springboot3 |
| springboot2 分支 | Spring Boot 2.x (JDK 8+、 Nacos 2.x) | https://gitee.com/y_project/RuoYi-Cloud/tree/springboot2 |
RuoYi-Cloud 前端项目提供 Vue 2.x / 3.x / JavaScript TypeScript 版本均可混用搭配
| 项目名称 | **RuoYi-Cloud** | **RuoYi-Cloud-Vue3** | **RuoYi-Cloud-Vue3-TypeScript** |
| :--- | :--- | :--- | :--- |
| **前端框架** | Vue 2 | Vue 3 | Vue 3 |
| **脚本语言** | JavaScript | JavaScript | TypeScript |
| **构建工具** | Vue CLI | Vite | Vite |
| **UI 组件库** | Element UI | Element Plus | Element Plus |
| **状态管理** | Vuex | Pinia | Pinia |
| **路由管理** | Vue Router 3 | Vue Router 4 | Vue Router 4 |
| **核心特点** | 1. 技术栈经典稳定<br>2. 社区资料丰富<br>3. 当前维护重心已转移 | 1. 现代前端技术栈<br>2. 开发体验与性能更优<br>3. 官方主推的活跃版本 | 1. 类型加持,减少沟通成本<br>2. 开发时有提示,效率更高<br>3. 多人协作企业级开发项目 |
| **仓库地址** | [RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) | [RuoYi-Cloud-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Cloud-Vue3) | [RuoYi-Cloud-Vue3-TypeScript](https://gitcode.com/yangzongzhuan/RuoYi-Cloud-Vue3/tree/typescript) |
## 系统模块 ## 系统模块
~~~ ~~~
@@ -126,4 +147,4 @@ com.ruoyi
## 若依微服务交流群 ## 若依微服务交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/已满-225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [![加入QQ群](https://img.shields.io/badge/已满-201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/已满-118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [![加入QQ群](https://img.shields.io/badge/已满-101038945-blue.svg)](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [![加入QQ群](https://img.shields.io/badge/已满-128355254-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G4jZ4EtdT50PhnMBudTnEwgonxkXOscJ&authKey=FkGHYfoTKlGE6wHdKdjH9bVoOgQjtLP9WM%2Fj7pqGY1msoqw9uxDiBo39E2mLgzYg&noverify=0&group_code=128355254) [![加入QQ群](https://img.shields.io/badge/已满-179219821-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=irnwcXhbLOQEv1g-TwGifjNTA_f4wZiA&authKey=4bpzEwhcUY%2FvsPDHvzYn6xfoS%2FtOArvZ%2BGXzfr7O0%2FEqLfkKA%2BuCDXlzHIFg8t93&noverify=0&group_code=179219821) [![加入QQ群](https://img.shields.io/badge/158753145-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=lx1uEdEDuxeM7rUvF3qmlFdqKqdJ5Z-R&authKey=rgyPW9yhhh4IIURKVFa6NgP3qiqH04WAzrJ0trsgkr3pjzm6sKIOGyA58oOjoj%2FJ&noverify=0&group_code=158753145) 点击按钮入群。 QQ群 [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/已满-225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [![加入QQ群](https://img.shields.io/badge/已满-201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/已满-118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [![加入QQ群](https://img.shields.io/badge/已满-101038945-blue.svg)](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [![加入QQ群](https://img.shields.io/badge/已满-128355254-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G4jZ4EtdT50PhnMBudTnEwgonxkXOscJ&authKey=FkGHYfoTKlGE6wHdKdjH9bVoOgQjtLP9WM%2Fj7pqGY1msoqw9uxDiBo39E2mLgzYg&noverify=0&group_code=128355254) [![加入QQ群](https://img.shields.io/badge/已满-179219821-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=irnwcXhbLOQEv1g-TwGifjNTA_f4wZiA&authKey=4bpzEwhcUY%2FvsPDHvzYn6xfoS%2FtOArvZ%2BGXzfr7O0%2FEqLfkKA%2BuCDXlzHIFg8t93&noverify=0&group_code=179219821) [![加入QQ群](https://img.shields.io/badge/已满-158753145-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=lx1uEdEDuxeM7rUvF3qmlFdqKqdJ5Z-R&authKey=rgyPW9yhhh4IIURKVFa6NgP3qiqH04WAzrJ0trsgkr3pjzm6sKIOGyA58oOjoj%2FJ&noverify=0&group_code=158753145) [![加入QQ群](https://img.shields.io/badge/112869560-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Kuaw0Xdlw2Nlgn6s8h9elzuquHGxGObD&authKey=cSrQcWQ%2BzQZAFFrwxaR%2BbzcumX4WRduZnd1O6JO1dlclQMiu%2BKwxAy8t2JfNp67V&noverify=0&group_code=112869560) 点击按钮入群。

View File

@@ -9,8 +9,8 @@ usage() {
# copy sql # copy sql
echo "begin copy sql " echo "begin copy sql "
cp ../sql/ry_20250523.sql ./mysql/db cp ../sql/ry_20260402.sql ./mysql/db
cp ../sql/ry_config_20250224.sql ./mysql/db cp ../sql/ry_config_20260311.sql ./mysql/db
# copy html # copy html
echo "begin copy html " echo "begin copy html "

View File

@@ -2,7 +2,7 @@ version : '3.8'
services: services:
ruoyi-nacos: ruoyi-nacos:
container_name: ruoyi-nacos container_name: ruoyi-nacos
image: nacos/nacos-server image: nacos/nacos-server:v2.4.2
build: build:
context: ./nacos context: ./nacos
environment: environment:

View File

@@ -1,5 +1,5 @@
# 基础镜像 # 基础镜像
FROM nacos/nacos-server FROM nacos/nacos-server:v2.4.2
# author # author
MAINTAINER ruoyi MAINTAINER ruoyi

12
pom.xml
View File

@@ -6,14 +6,14 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.6</version> <version>3.6.8</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.6.6</ruoyi.version> <ruoyi.version>3.6.8</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>1.8</java.version> <java.version>1.8</java.version>
@@ -24,18 +24,18 @@
<tobato.version>1.27.2</tobato.version> <tobato.version>1.27.2</tobato.version>
<kaptcha.version>2.3.3</kaptcha.version> <kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>2.0.0</pagehelper.boot.version> <pagehelper.boot.version>2.0.0</pagehelper.boot.version>
<druid.version>1.2.23</druid.version> <druid.version>1.2.27</druid.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version> <dynamic-ds.version>4.3.1</dynamic-ds.version>
<commons.io.version>2.19.0</commons.io.version> <commons.io.version>2.21.0</commons.io.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<fastjson.version>2.0.57</fastjson.version> <fastjson.version>2.0.61</fastjson.version>
<jjwt.version>0.9.1</jjwt.version> <jjwt.version>0.9.1</jjwt.version>
<minio.version>8.2.2</minio.version> <minio.version>8.2.2</minio.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<springdoc.version>1.6.9</springdoc.version> <springdoc.version>1.6.9</springdoc.version>
<transmittable-thread-local.version>2.14.4</transmittable-thread-local.version> <transmittable-thread-local.version>2.14.4</transmittable-thread-local.version>
<!-- override dependency version --> <!-- override dependency version -->
<tomcat.version>9.0.105</tomcat.version> <tomcat.version>9.0.112</tomcat.version>
<logback.version>1.2.13</logback.version> <logback.version>1.2.13</logback.version>
<spring-framework.version>5.3.39</spring-framework.version> <spring-framework.version>5.3.39</spring-framework.version>
</properties> </properties>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api</artifactId> <artifactId>ruoyi-api</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -2,7 +2,9 @@ package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.constant.ServiceNameConstants;
@@ -26,4 +28,13 @@ public interface RemoteFileService
*/ */
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file); public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file);
/**
* 删除文件
*
* @param fileUrl 文件地址
* @return 结果
*/
@DeleteMapping(value = "/delete", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public R<Boolean> delete(@RequestParam("fileUrl") String fileUrl);
} }

View File

@@ -3,6 +3,7 @@ package com.ruoyi.system.api.domain;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.validation.constraints.*; import javax.validation.constraints.*;
import com.fasterxml.jackson.annotation.JsonFormat;
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.ruoyi.common.core.annotation.Excel; import com.ruoyi.common.core.annotation.Excel;
@@ -68,6 +69,7 @@ public class SysUser extends BaseEntity
private String loginIp; private String loginIp;
/** 最后登录时间 */ /** 最后登录时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate; private Date loginDate;
@@ -114,11 +116,6 @@ public class SysUser extends BaseEntity
} }
public boolean isAdmin() public boolean isAdmin()
{
return isAdmin(this.userId);
}
public static boolean isAdmin(Long userId)
{ {
return UserConstants.isAdmin(userId); return UserConstants.isAdmin(userId);
} }

View File

@@ -30,6 +30,12 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
{ {
return R.fail("上传文件失败:" + throwable.getMessage()); return R.fail("上传文件失败:" + throwable.getMessage());
} }
@Override
public R<Boolean> delete(String fileUrl)
{
return R.fail("删除文件失败:" + throwable.getMessage());
}
}; };
} }
} }

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.auth.form.LoginBody; import com.ruoyi.auth.form.LoginBody;
import com.ruoyi.auth.form.RegisterBody; import com.ruoyi.auth.form.RegisterBody;
import com.ruoyi.auth.form.UnLockBody;
import com.ruoyi.auth.service.SysLoginService; import com.ruoyi.auth.service.SysLoginService;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.JwtUtils; import com.ruoyi.common.core.utils.JwtUtils;
@@ -75,4 +76,14 @@ public class TokenController
sysLoginService.register(registerBody.getUsername(), registerBody.getPassword()); sysLoginService.register(registerBody.getUsername(), registerBody.getPassword());
return R.ok(); return R.ok();
} }
/**
* 解锁屏幕
*/
@PostMapping("/unlockscreen")
public R<?> unlockScreen(@RequestBody UnLockBody unLockBody)
{
sysLoginService.unlock(unLockBody.getPassword());
return R.ok();
}
} }

View File

@@ -0,0 +1,24 @@
package com.ruoyi.auth.form;
/**
* 系统解锁对象
*
* @author ruoyi
*/
public class UnLockBody
{
/**
* 用户密码
*/
private String password;
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}

View File

@@ -113,11 +113,40 @@ public class SysLoginService
remoteUserService.recordUserLogin(sysUser, SecurityConstants.INNER); remoteUserService.recordUserLogin(sysUser, SecurityConstants.INNER);
} }
/**
* 退出
*/
public void logout(String loginName) public void logout(String loginName)
{ {
recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功"); recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
} }
/**
* 解锁
*/
public void unlock(String password)
{
String username = SecurityUtils.getUsername();
// 或密码为空 错误
if (StringUtils.isEmpty(password))
{
throw new ServiceException("密码不能为空");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode())
{
throw new ServiceException(userResult.getMsg());
}
SysUser user = userResult.getData().getSysUser();
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{
throw new ServiceException("密码错误,请重新输入");
}
}
/** /**
* 注册 * 注册
*/ */

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -87,6 +87,16 @@ public class Constants
*/ */
public static final String LOGIN_FAIL = "Error"; public static final String LOGIN_FAIL = "Error";
/**
* 所有权限标识
*/
public static final String ALL_PERMISSION = "*:*:*";
/**
* 管理员角色权限标识
*/
public static final String SUPER_ADMIN = "admin";
/** /**
* 当前记录起始索引 * 当前记录起始索引
*/ */
@@ -120,7 +130,7 @@ public class Constants
/** /**
* 自动识别json对象白名单配置仅允许解析的包名范围越小越安全 * 自动识别json对象白名单配置仅允许解析的包名范围越小越安全
*/ */
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" }; public static final String[] JSON_WHITELIST_STR = { "com.ruoyi" };
/** /**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
@@ -132,4 +142,35 @@ public class Constants
*/ */
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.core.utils.file" }; "org.springframework", "org.apache", "com.ruoyi.common.core.utils.file" };
/**
* 部门相关常量
*/
public static class Dept
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
}
} }

View File

@@ -31,6 +31,9 @@ public class GenConstants
/** 上级菜单名称字段 */ /** 上级菜单名称字段 */
public static final String PARENT_MENU_NAME = "parentMenuName"; public static final String PARENT_MENU_NAME = "parentMenuName";
/** 生成详情页开关 */
public static final String GEN_VIEW = "genView";
/** 数据库字符串类型 */ /** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };

View File

@@ -53,7 +53,7 @@ public class SecurityContextHolder
public static Long getUserId() public static Long getUserId()
{ {
return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), 0L); return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), null);
} }
public static void setUserId(String account) public static void setUserId(String account)

View File

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

View File

@@ -114,20 +114,20 @@ public class FileUtils
} }
/** /**
* 检查文件是否可下载 * 校验文件路径合法性(安全性与扩展名)
* *
* @param resource 需要下载的文件 * @param fileUrl 待校验的文件地址
* @return true 正常 false 非法 * @return true 正常 false 非法
*/ */
public static boolean checkAllowDownload(String resource) public static boolean validateFilePath(String fileUrl)
{ {
// 禁止目录上跳级别 // 禁止目录上跳级别
if (StringUtils.contains(resource, "..")) if (StringUtils.contains(fileUrl, ".."))
{ {
return false; return false;
} }
// 判断是否在允许下载的文件规则内 // 判断是否在允许下载的文件规则内
return ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)); return ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(fileUrl));
} }
/** /**

View File

@@ -0,0 +1,85 @@
package com.ruoyi.common.core.utils.poi;
import java.util.ArrayList;
import java.util.List;
/**
* 多 Sheet 导出时的数据信息
*
* 使用示例:
* <pre>
* List<ExcelSheet<?>> sheets = new ArrayList<>();
* sheets.add(new ExcelSheet<>("参数数据", configList, Config.class, "参数信息"));
* sheets.add(new ExcelSheet<>("岗位数据", postList, Post.class, "岗位信息"));
* return ExcelUtil.exportMultiSheet(sheets);
* </pre>
*
* @author ruoyi
*/
public class ExcelSheet<T>
{
/** Sheet 名称 */
private String sheetName;
/** 导出数据集合 */
private List<T> list;
/** 数据对应的实体 Class */
private Class<T> clazz;
/** Sheet 顶部大标题(可为空) */
private String title;
public ExcelSheet(String sheetName, List<T> list, Class<T> clazz)
{
this(sheetName, list, clazz, "");
}
public ExcelSheet(String sheetName, List<T> list, Class<T> clazz, String title)
{
this.sheetName = sheetName;
this.list = list != null ? list : new ArrayList<>();
this.clazz = clazz;
this.title = title != null ? title : "";
}
public String getSheetName()
{
return sheetName;
}
public List<T> getList()
{
return list;
}
public Class<T> getClazz()
{
return clazz;
}
public String getTitle()
{
return title;
}
public void setSheetName(String sheetName)
{
this.sheetName = sheetName;
}
public void setList(List<T> list)
{
this.list = list;
}
public void setClazz(Class<T> clazz)
{
this.clazz = clazz;
}
public void setTitle(String title)
{
this.title = title;
}
}

View File

@@ -73,10 +73,17 @@ public class ExcelUtil<T>
{ {
private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
public static final String SEPARATOR = ",";
public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; public static final String FORMULA_REGEX_STR = "=|-|\\+|@";
public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; public static final String[] FORMULA_STR = { "=", "-", "+", "@" };
/**
* 单元格样式缓存
*/
private Map<String, CellStyle> cellStyleCache = new HashMap<String, CellStyle>();
/** /**
* Excel sheet最大行数默认65536 * Excel sheet最大行数默认65536
*/ */
@@ -145,23 +152,18 @@ public class ExcelUtil<T>
/** /**
* 对象的子列表方法 * 对象的子列表方法
*/ */
private Method subMethod; private Map<String, Method> subMethods;
/** /**
* 对象的子列表属性 * 对象的子列表属性
*/ */
private List<Field> subFields; private Map<String, List<Field>> subFieldsMap;
/** /**
* 统计列表 * 统计列表
*/ */
private Map<Integer, Double> statistics = new HashMap<Integer, Double>(); private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
/**
* 数字格式
*/
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
/** /**
* 实体对象 * 实体对象
*/ */
@@ -228,7 +230,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);
@@ -248,16 +253,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);
@@ -269,7 +275,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++;
@@ -341,7 +347,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))
@@ -349,10 +359,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();
@@ -496,6 +502,82 @@ public class ExcelUtil<T>
exportExcel(response); exportExcel(response);
} }
/**
* 多 Sheet 导出 —— 将多个不同类型的数据集合写入同一 Excel直接输出到 HttpServletResponse
*
* @param response HTTP 响应
* @param sheets Sheet 描述列表
*/
public static void exportMultiSheet(HttpServletResponse response, List<ExcelSheet<?>> sheets)
{
if (sheets == null || sheets.isEmpty())
{
return;
}
SXSSFWorkbook wb = buildWorkbook(sheets);
try
{
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
wb.write(response.getOutputStream());
}
catch (Exception e)
{
log.error("多Sheet导出Excel异常{}", e.getMessage());
}
finally
{
IOUtils.closeQuietly(wb);
}
}
/**
* 构建多 Sheet Workbook —— 创建 SXSSFWorkbook 并将所有 Sheet 数据写入
*
* @param sheets Sheet 描述列表
* @return 已写入所有 Sheet 数据的 SXSSFWorkbook
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private static SXSSFWorkbook buildWorkbook(List<ExcelSheet<?>> sheets)
{
SXSSFWorkbook wb = new SXSSFWorkbook(500);
for (ExcelSheet<?> excelSheet : sheets)
{
ExcelUtil util = new ExcelUtil(excelSheet.getClazz());
util.initWithWorkbook(wb, excelSheet.getList(), excelSheet.getSheetName(), excelSheet.getTitle());
util.writeSheet();
}
return wb;
}
/**
* 使用外部传入的 Workbook 初始化(多 Sheet 导出专用)
* 与 init() 的区别:不新建 Workbook而是在已有 wb 上追加新 Sheet
*
* @param wb 已有工作簿
* @param list 数据集合
* @param sheetName Sheet 名称
* @param title 大标题(可为空)
*/
public void initWithWorkbook(SXSSFWorkbook wb, List<T> list, String sheetName, String title)
{
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
this.sheetName = sheetName;
this.title = title != null ? title : "";
this.type = Type.EXPORT;
this.rownum = 0;
this.wb = wb;
this.sheet = wb.createSheet(sheetName);
createExcelField();
this.styles = createStyles(wb);
createTitle();
createSubHead();
}
/** /**
* 对list数据源将其里面的数据导入到excel表单 * 对list数据源将其里面的数据导入到excel表单
* *
@@ -565,7 +647,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++);
@@ -578,7 +661,7 @@ public class ExcelUtil<T>
} }
if (Type.EXPORT.equals(type)) if (Type.EXPORT.equals(type))
{ {
fillExcelData(index, row); fillExcelData(index);
addStatisticsRow(); addStatisticsRow();
} }
} }
@@ -588,10 +671,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());
@@ -599,7 +681,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);
@@ -612,6 +694,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;
@@ -624,15 +707,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)
{ {
@@ -724,6 +807,7 @@ public class ExcelUtil<T>
style = wb.createCellStyle(); style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER); style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setDataFormat(dataFormat.getFormat("######0.00"));
Font totalFont = wb.createFont(); Font totalFont = wb.createFont();
totalFont.setFontName("Arial"); totalFont.setFontName("Arial");
totalFont.setFontHeightInPoints((short) 10); totalFont.setFontHeightInPoints((short) 10);
@@ -984,7 +1068,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)
{ {
@@ -1000,7 +1084,7 @@ public class ExcelUtil<T>
String separator = attr.separator(); String separator = attr.separator();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{ {
cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); cell.setCellStyle(createCellStyle(cell.getCellStyle(), dateFormat));
cell.setCellValue(parseDateToStr(dateFormat, value)); cell.setCellValue(parseDateToStr(dateFormat, value));
} }
else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
@@ -1030,6 +1114,28 @@ public class ExcelUtil<T>
return cell; return cell;
} }
/**
* 使用自定义格式,同时避免样式污染
*
* @param cellStyle 从此样式复制
* @param format 格式匹配的字符串
* @return 格式化后CellStyle对象
*/
private CellStyle createCellStyle(CellStyle cellStyle, String format)
{
String key = cellStyle.getIndex() + "|" + format;
CellStyle cached = cellStyleCache.get(key);
if (cached != null)
{
return cached;
}
CellStyle style = wb.createCellStyle();
style.cloneStyleFrom(cellStyle);
style.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(format));
cellStyleCache.put(key, style);
return style;
}
/** /**
* 设置 POI XSSFSheet 单元格提示或选择框 * 设置 POI XSSFSheet 单元格提示或选择框
* *
@@ -1081,18 +1187,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);
// 数据有效性对象 // 数据有效性对象
@@ -1130,7 +1254,7 @@ public class ExcelUtil<T>
public static String convertByExp(String propertyValue, String converterExp, String separator) public static String convertByExp(String propertyValue, String converterExp, String separator)
{ {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(","); String[] convertSource = converterExp.split(SEPARATOR);
for (String item : convertSource) for (String item : convertSource)
{ {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
@@ -1167,7 +1291,7 @@ public class ExcelUtil<T>
public static String reverseByExp(String propertyValue, String converterExp, String separator) public static String reverseByExp(String propertyValue, String converterExp, String separator)
{ {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(","); String[] convertSource = converterExp.split(SEPARATOR);
for (String item : convertSource) for (String item : convertSource)
{ {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
@@ -1255,7 +1379,7 @@ public class ExcelUtil<T>
{ {
cell = row.createCell(key); cell = row.createCell(key);
cell.setCellStyle(styles.get("total")); cell.setCellStyle(styles.get("total"));
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); cell.setCellValue(statistics.get(key));
} }
statistics.clear(); statistics.clear();
} }
@@ -1330,6 +1454,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))
@@ -1377,10 +1503,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));
} }
} }
@@ -1449,7 +1576,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);
} }
} }
@@ -1574,7 +1702,7 @@ public class ExcelUtil<T>
*/ */
public boolean isSubList() public boolean isSubList()
{ {
return StringUtils.isNotNull(subFields) && subFields.size() > 0; return !StringUtils.isEmpty(subFieldsMap);
} }
/** /**
@@ -1582,24 +1710,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

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -16,15 +16,25 @@ import java.lang.annotation.Target;
@Documented @Documented
public @interface DataScope public @interface DataScope
{ {
/**
* 用户表的别名
*/
public String userAlias() default "";
/** /**
* 部门表的别名 * 部门表的别名
*/ */
public String deptAlias() default ""; public String deptAlias() default "";
/** /**
* 用户表的别 * 用户字段
*/ */
public String userAlias() default ""; public String userField() default "user_id";
/**
* 部门字段名
*/
public String deptField() default "dept_id";
/** /**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@RequiresPermissions获取多个权限用逗号分隔开来 * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@RequiresPermissions获取多个权限用逗号分隔开来

View File

@@ -6,6 +6,7 @@ import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.context.SecurityContextHolder; import com.ruoyi.common.core.context.SecurityContextHolder;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
@@ -26,31 +27,6 @@ import com.ruoyi.system.api.model.LoginUser;
@Component @Component
public class DataScopeAspect public class DataScopeAspect
{ {
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/** /**
* 数据权限过滤关键字 * 数据权限过滤关键字
*/ */
@@ -74,7 +50,7 @@ public class DataScopeAspect
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{ {
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission()); String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission); dataScopeFilter(joinPoint, currentUser, controllerDataScope.userAlias(), controllerDataScope.deptAlias(), controllerDataScope.userField(), controllerDataScope.deptField(), permission);
} }
} }
} }
@@ -88,13 +64,13 @@ public class DataScopeAspect
* @param userAlias 用户别名 * @param userAlias 用户别名
* @param permission 权限字符 * @param permission 权限字符
*/ */
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String userAlias, String deptAlias, String userField, String deptField, String permission)
{ {
StringBuilder sqlString = new StringBuilder(); StringBuilder sqlString = new StringBuilder();
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 (Constants.Dept.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,46 +83,46 @@ 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;
} }
if (DATA_SCOPE_ALL.equals(dataScope)) if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope))
{ {
sqlString = new StringBuilder(); sqlString = new StringBuilder();
conditions.add(dataScope); conditions.add(dataScope);
break; break;
} }
else if (DATA_SCOPE_CUSTOM.equals(dataScope)) else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope))
{ {
if (scopeCustomIds.size() > 1) if (scopeCustomIds.size() > 1)
{ {
// 多个自定数据权限使用in查询避免多次拼接。 // 多个自定数据权限使用in查询避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds))); sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, deptField, String.join(",", scopeCustomIds)));
} }
else else
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, deptField, role.getRoleId()));
} }
} }
else if (DATA_SCOPE_DEPT.equals(dataScope)) else if (Constants.Dept.DATA_SCOPE_DEPT.equals(dataScope))
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); sqlString.append(StringUtils.format(" OR {}.{} = {} ", deptAlias, deptField, user.getDeptId()));
} }
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) else if (Constants.Dept.DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId())); sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, deptField, user.getDeptId(), user.getDeptId()));
} }
else if (DATA_SCOPE_SELF.equals(dataScope)) else if (Constants.Dept.DATA_SCOPE_SELF.equals(dataScope))
{ {
if (StringUtils.isNotBlank(userAlias)) if (StringUtils.isNotBlank(userAlias))
{ {
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); sqlString.append(StringUtils.format(" OR {}.{} = {} ", userAlias, userField, user.getUserId()));
} }
else else
{ {
// 数据权限为仅本人且没有userAlias别名不查询任何数据 // 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
} }
} }
conditions.add(dataScope); conditions.add(dataScope);
@@ -155,7 +131,7 @@ public class DataScopeAspect
// 角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据 // 角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions)) if (StringUtils.isEmpty(conditions))
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField));
} }
if (StringUtils.isNotBlank(sqlString.toString())) if (StringUtils.isNotBlank(sqlString.toString()))

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -48,6 +48,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;
@Autowired @Autowired
private AsyncLogService asyncLogService; private AsyncLogService asyncLogService;
@@ -166,16 +169,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));
} }
} }
@@ -184,7 +187,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)
@@ -194,15 +197,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

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -4,6 +4,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.TokenConstants; import com.ruoyi.common.core.constant.TokenConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.context.SecurityContextHolder; import com.ruoyi.common.core.context.SecurityContextHolder;
import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
@@ -79,6 +80,16 @@ public class SecurityUtils
return token; return token;
} }
/**
* 是否为管理员
*
* @return 结果
*/
public static boolean isAdmin()
{
return isAdmin(getUserId());
}
/** /**
* 是否为管理员 * 是否为管理员
* *
@@ -87,7 +98,7 @@ public class SecurityUtils
*/ */
public static boolean isAdmin(Long userId) public static boolean isAdmin(Long userId)
{ {
return userId != null && 1L == userId; return UserConstants.isAdmin(userId);
} }
/** /**

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId> <artifactId>ruoyi</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,46 @@
package com.ruoyi.file.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.file.filter.RefererFilter;
/**
* Filter配置
*
* @author ruoyi
*/
@Configuration
public class FilterConfig
{
/**
* 资源映射路径 前缀
*/
@Value("${file.prefix}")
public String localFilePrefix;
@Value("${referer.allowed-domains}")
private String allowedDomains;
@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(localFilePrefix + "/*");
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;
}
}

View File

@@ -3,10 +3,12 @@ package com.ruoyi.file.controller;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.file.FileUtils; import com.ruoyi.common.core.utils.file.FileUtils;
import com.ruoyi.file.service.ISysFileService; import com.ruoyi.file.service.ISysFileService;
import com.ruoyi.system.api.domain.SysFile; import com.ruoyi.system.api.domain.SysFile;
@@ -45,4 +47,26 @@ public class SysFileController
return R.fail(e.getMessage()); return R.fail(e.getMessage());
} }
} }
/**
* 文件删除请求
*/
@DeleteMapping("delete")
public R<Boolean> delete(String fileUrl)
{
try
{
if (!FileUtils.validateFilePath(fileUrl))
{
throw new Exception(StringUtils.format("资源文件({})非法,不允许删除。 ", fileUrl));
}
sysFileService.deleteFile(fileUrl);
return R.ok();
}
catch (Exception e)
{
log.error("删除文件失败", e);
return R.fail(e.getMessage());
}
}
} }

View File

@@ -0,0 +1,77 @@
package com.ruoyi.file.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.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,11 +1,11 @@
package com.ruoyi.file.service; package com.ruoyi.file.service;
import java.io.InputStream; import java.io.InputStream;
import com.alibaba.nacos.common.utils.IoUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.alibaba.nacos.common.utils.IoUtils;
import com.github.tobato.fastdfs.domain.fdfs.StorePath; import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient; import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.ruoyi.common.core.utils.file.FileTypeUtils; import com.ruoyi.common.core.utils.file.FileTypeUtils;
@@ -53,4 +53,24 @@ public class FastDfsSysFileServiceImpl implements ISysFileService
IoUtils.closeQuietly(inputStream); IoUtils.closeQuietly(inputStream);
} }
} }
/**
* FastDFS文件删除接口
*
* @param fileUrl 文件访问URL
* @throws Exception
*/
@Override
public void deleteFile(String fileUrl) throws Exception
{
try
{
StorePath storePath = StorePath.parseFromUrl(fileUrl);
storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
}
catch (Exception e)
{
throw new RuntimeException("FastDfs Failed to delete file: ", e);
}
}
} }

View File

@@ -17,4 +17,12 @@ public interface ISysFileService
* @throws Exception * @throws Exception
*/ */
public String uploadFile(MultipartFile file) throws Exception; public String uploadFile(MultipartFile file) throws Exception;
/**
* 文件删除接口
*
* @param fileUrl 文件访问URL
* @throws Exception
*/
public void deleteFile(String fileUrl) throws Exception;
} }

View File

@@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.file.FileUtils;
import com.ruoyi.file.utils.FileUploadUtils; import com.ruoyi.file.utils.FileUploadUtils;
/** /**
@@ -47,4 +49,17 @@ public class LocalSysFileServiceImpl implements ISysFileService
String url = domain + localFilePrefix + name; String url = domain + localFilePrefix + name;
return url; return url;
} }
/**
* 本地文件删除接口
*
* @param fileUrl 文件访问URL
* @throws Exception
*/
@Override
public void deleteFile(String fileUrl) throws Exception
{
String localFile = StringUtils.substringAfter(fileUrl, localFilePrefix);
FileUtils.deleteFile(localFilePath + localFile);
}
} }

View File

@@ -5,10 +5,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.alibaba.nacos.common.utils.IoUtils; import com.alibaba.nacos.common.utils.IoUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.file.config.MinioConfig; import com.ruoyi.file.config.MinioConfig;
import com.ruoyi.file.utils.FileUploadUtils; import com.ruoyi.file.utils.FileUploadUtils;
import io.minio.MinioClient; import io.minio.MinioClient;
import io.minio.PutObjectArgs; import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
/** /**
* Minio 文件存储 * Minio 文件存储
@@ -57,4 +59,24 @@ public class MinioSysFileServiceImpl implements ISysFileService
IoUtils.closeQuietly(inputStream); IoUtils.closeQuietly(inputStream);
} }
} }
/**
* Minio文件删除接口
*
* @param fileUrl 文件访问URL
* @throws Exception
*/
@Override
public void deleteFile(String fileUrl) throws Exception
{
try
{
String minioFile = StringUtils.substringAfter(fileUrl, minioConfig.getBucketName());
client.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(minioFile).build());
}
catch (Exception e)
{
throw new RuntimeException("Minio Failed to delete file", e);
}
}
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.controller.BaseController;
@@ -104,12 +105,12 @@ public class GenController extends BaseController
@RequiresPermissions("tool:gen:import") @RequiresPermissions("tool:gen:import")
@Log(title = "代码生成", businessType = BusinessType.IMPORT) @Log(title = "代码生成", businessType = BusinessType.IMPORT)
@PostMapping("/importTable") @PostMapping("/importTable")
public AjaxResult importTableSave(String tables) public AjaxResult importTableSave(@RequestParam("tables") String tables, @RequestParam("tplWebType") String tplWebType)
{ {
String[] tableNames = Convert.toStrArray(tables); String[] tableNames = Convert.toStrArray(tables);
// 查询表信息 // 查询表信息
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames); List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
genTableService.importGenTable(tableList); genTableService.importGenTable(tableList, tplWebType);
return success(); return success();
} }

View File

@@ -41,7 +41,7 @@ public class GenTable extends BaseEntity
/** 使用的模板crud单表操作 tree树表操作 sub主子表操作 */ /** 使用的模板crud单表操作 tree树表操作 sub主子表操作 */
private String tplCategory; private String tplCategory;
/** 前端类型element-ui模版 element-plus模版 */ /** 前端类型element-ui模版 element-plus模版 element-plus-typescript模版 */
private String tplWebType; private String tplWebType;
/** 生成包路径 */ /** 生成包路径 */
@@ -64,6 +64,9 @@ public class GenTable extends BaseEntity
@NotBlank(message = "作者不能为空") @NotBlank(message = "作者不能为空")
private String functionAuthor; private String functionAuthor;
/** 表单布局(单列 双列 三列) */
private Integer formColNum;
/** 生成代码方式0zip压缩包 1自定义路径 */ /** 生成代码方式0zip压缩包 1自定义路径 */
private String genType; private String genType;
@@ -98,6 +101,9 @@ public class GenTable extends BaseEntity
/** 上级菜单名称字段 */ /** 上级菜单名称字段 */
private String parentMenuName; private String parentMenuName;
/** 是否生成详情页 */
private boolean isView;
public Long getTableId() public Long getTableId()
{ {
return tableId; return tableId;
@@ -228,6 +234,16 @@ public class GenTable extends BaseEntity
this.functionAuthor = functionAuthor; this.functionAuthor = functionAuthor;
} }
public Integer getFormColNum()
{
return formColNum;
}
public void setFormColNum(Integer formColNum)
{
this.formColNum = formColNum;
}
public String getGenType() public String getGenType()
{ {
return genType; return genType;
@@ -267,6 +283,7 @@ public class GenTable extends BaseEntity
{ {
this.subTable = subTable; this.subTable = subTable;
} }
public List<GenTableColumn> getColumns() public List<GenTableColumn> getColumns()
{ {
return columns; return columns;
@@ -337,6 +354,16 @@ public class GenTable extends BaseEntity
this.parentMenuName = parentMenuName; this.parentMenuName = parentMenuName;
} }
public boolean isView()
{
return isView;
}
public void setView(boolean isView)
{
this.isView = isView;
}
public boolean isSub() public boolean isSub()
{ {
return isSub(this.tplCategory); return isSub(this.tplCategory);
@@ -346,6 +373,7 @@ public class GenTable extends BaseEntity
{ {
return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
} }
public boolean isTree() public boolean isTree()
{ {
return isTree(this.tplCategory); return isTree(this.tplCategory);

View File

@@ -4,6 +4,8 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -157,7 +159,7 @@ public class GenTableServiceImpl implements IGenTableService
*/ */
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void importGenTable(List<GenTable> tableList) public void importGenTable(List<GenTable> tableList, String tplWebType)
{ {
String operName = SecurityUtils.getUsername(); String operName = SecurityUtils.getUsername();
try try
@@ -165,6 +167,7 @@ public class GenTableServiceImpl implements IGenTableService
for (GenTable table : tableList) for (GenTable table : tableList)
{ {
String tableName = table.getTableName(); String tableName = table.getTableName();
table.setTplWebType(tplWebType);
GenUtils.initTable(table, operName); GenUtils.initTable(table, operName);
int row = genTableMapper.insertGenTable(table); int row = genTableMapper.insertGenTable(table);
if (row > 0) if (row > 0)
@@ -206,7 +209,7 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates) for (String template : templates)
{ {
// 渲染模板 // 渲染模板
@@ -227,11 +230,7 @@ public class GenTableServiceImpl implements IGenTableService
@Override @Override
public byte[] downloadCode(String tableName) public byte[] downloadCode(String tableName)
{ {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return downloadCode(new String[] { tableName });
ZipOutputStream zip = new ZipOutputStream(outputStream);
generatorCode(tableName, zip);
IOUtils.closeQuietly(zip);
return outputStream.toByteArray();
} }
/** /**
@@ -254,10 +253,10 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates) for (String template : templates)
{ {
if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "api.ts.vm", "type.ts.vm", "index.ts.vm", "index.vue.vm", "index-tree.vue.vm", "view.vue.vm"))
{ {
// 渲染模板 // 渲染模板
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
@@ -342,9 +341,14 @@ public class GenTableServiceImpl implements IGenTableService
{ {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream); ZipOutputStream zip = new ZipOutputStream(outputStream);
Map<String, StringBuffer> typeFiles = new HashMap<>();
for (String tableName : tableNames) for (String tableName : tableNames)
{ {
generatorCode(tableName, zip); generatorCode(tableName, zip, typeFiles);
}
for (Map.Entry<String, StringBuffer> entry : typeFiles.entrySet())
{
writeToZip(zip, entry.getKey(), entry.getValue().toString());
} }
IOUtils.closeQuietly(zip); IOUtils.closeQuietly(zip);
return outputStream.toByteArray(); return outputStream.toByteArray();
@@ -353,7 +357,7 @@ public class GenTableServiceImpl implements IGenTableService
/** /**
* 查询表信息并生成代码 * 查询表信息并生成代码
*/ */
private void generatorCode(String tableName, ZipOutputStream zip) private void generatorCode(String tableName, ZipOutputStream zip, Map<String, StringBuffer> typeFiles)
{ {
// 查询表信息 // 查询表信息
GenTable table = genTableMapper.selectGenTableByName(tableName); GenTable table = genTableMapper.selectGenTableByName(tableName);
@@ -367,29 +371,56 @@ public class GenTableServiceImpl implements IGenTableService
VelocityContext context = VelocityUtils.prepareContext(table); VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 // 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); List<String> templates = VelocityUtils.getTemplateList(table);
for (String template : templates) for (String template : templates)
{ {
// 渲染模板 // 渲染模板
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8); Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw); tpl.merge(context, sw);
try String fileName = VelocityUtils.getFileName(template, table);
// index-bak.ts 模版,追加内容
if (fileName.contains("index-bak.ts"))
{ {
// 添加到zip if (!typeFiles.containsKey(fileName))
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); {
IOUtils.write(sw.toString(), zip, Constants.UTF8); typeFiles.put(fileName, new StringBuffer(sw.toString()));
IOUtils.closeQuietly(sw); }
zip.flush(); else
zip.closeEntry(); {
Arrays.stream(sw.toString().split("\n")).filter(line -> line.startsWith("export * from")).forEach(line -> typeFiles.get(fileName).append("\n").append(line));
}
} }
catch (IOException e) else
{ {
log.error("渲染模板失败,表名:" + table.getTableName(), e); // 其他文件正常添加
writeToZip(zip, fileName, sw.toString());
} }
} }
} }
/**
* 将字符串内容写入ZIP输出流
*
* @param zip ZIP输出流
* @param fileName ZIP条目名称即文件名
* @param content 要写入的内容
*/
private void writeToZip(ZipOutputStream zip, String fileName, String content)
{
try
{
zip.putNextEntry(new ZipEntry(fileName));
IOUtils.write(content, zip, Constants.UTF8);
zip.flush();
zip.closeEntry();
}
catch (IOException e)
{
log.error("写入ZIP文件失败文件名: " + fileName, e);
}
}
/** /**
* 修改保存参数校验 * 修改保存参数校验
* *
@@ -493,12 +524,14 @@ public class GenTableServiceImpl implements IGenTableService
String treeName = paramsObj.getString(GenConstants.TREE_NAME); String treeName = paramsObj.getString(GenConstants.TREE_NAME);
Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID); Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID);
String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
boolean isView = paramsObj.getBooleanValue(GenConstants.GEN_VIEW);
genTable.setTreeCode(treeCode); genTable.setTreeCode(treeCode);
genTable.setTreeParentCode(treeParentCode); genTable.setTreeParentCode(treeParentCode);
genTable.setTreeName(treeName); genTable.setTreeName(treeName);
genTable.setParentMenuId(parentMenuId); genTable.setParentMenuId(parentMenuId);
genTable.setParentMenuName(parentMenuName); genTable.setParentMenuName(parentMenuName);
genTable.setView(isView);
} }
} }

View File

@@ -70,8 +70,9 @@ public interface IGenTableService
* 导入表结构 * 导入表结构
* *
* @param tableList 导入表列表 * @param tableList 导入表列表
* @param tplWebType 前端类型
*/ */
public void importGenTable(List<GenTable> tableList); public void importGenTable(List<GenTable> tableList, String tplWebType);
/** /**
* 预览代码 * 预览代码

View File

@@ -14,7 +14,7 @@ import com.ruoyi.gen.domain.GenTable;
import com.ruoyi.gen.domain.GenTableColumn; import com.ruoyi.gen.domain.GenTableColumn;
/** /**
* 模板工具类 * 模板处理工具类
* *
* @author ruoyi * @author ruoyi
*/ */
@@ -29,6 +29,12 @@ public class VelocityUtils
/** 默认上级菜单,系统工具 */ /** 默认上级菜单,系统工具 */
private static final String DEFAULT_PARENT_MENU_ID = "3"; private static final String DEFAULT_PARENT_MENU_ID = "3";
/** Vue3 Element Plus 模版 */
private static final String ELEMENT_PLUS = "element-plus";
/** Vue3 Element Plus TypeScript 模版 */
private static final String ELEMENT_PLUS_TYPESSRIPT = "element-plus-typescript";
/** /**
* 设置模板变量信息 * 设置模板变量信息
* *
@@ -54,6 +60,7 @@ public class VelocityUtils
velocityContext.put("basePackage", getPackagePrefix(packageName)); velocityContext.put("basePackage", getPackagePrefix(packageName));
velocityContext.put("packageName", packageName); velocityContext.put("packageName", packageName);
velocityContext.put("author", genTable.getFunctionAuthor()); velocityContext.put("author", genTable.getFunctionAuthor());
velocityContext.put("colSpan", getColSpan(genTable.getFormColNum()));
velocityContext.put("datetime", DateUtils.getDate()); velocityContext.put("datetime", DateUtils.getDate());
velocityContext.put("pkColumn", genTable.getPkColumn()); velocityContext.put("pkColumn", genTable.getPkColumn());
velocityContext.put("importList", getImportList(genTable)); velocityContext.put("importList", getImportList(genTable));
@@ -61,6 +68,7 @@ public class VelocityUtils
velocityContext.put("columns", genTable.getColumns()); velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable); velocityContext.put("table", genTable);
velocityContext.put("dicts", getDicts(genTable)); velocityContext.put("dicts", getDicts(genTable));
setExtensionsContext(velocityContext, genTable.getOptions());
setMenuVelocityContext(velocityContext, genTable); setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory)) if (GenConstants.TPL_TREE.equals(tplCategory))
{ {
@@ -73,6 +81,13 @@ public class VelocityUtils
return velocityContext; return velocityContext;
} }
public static void setExtensionsContext(VelocityContext context, String options)
{
JSONObject paramsObj = JSONObject.parseObject(options);
boolean genView = genView(paramsObj);
context.put("genView", genView);
}
public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) public static void setMenuVelocityContext(VelocityContext context, GenTable genTable)
{ {
String options = genTable.getOptions(); String options = genTable.getOptions();
@@ -127,13 +142,23 @@ public class VelocityUtils
* @param tplWebType 前端类型 * @param tplWebType 前端类型
* @return 模板列表 * @return 模板列表
*/ */
public static List<String> getTemplateList(String tplCategory, String tplWebType) public static List<String> getTemplateList(GenTable table)
{ {
String tplWebType = table.getTplWebType();
String tplCategory = table.getTplCategory();
JSONObject paramsObj = JSONObject.parseObject(table.getOptions());
boolean isView = genView(paramsObj);
String useWebType = "vm/vue"; String useWebType = "vm/vue";
if ("element-plus".equals(tplWebType)) String apiTemplate = "vm/js/api.js.vm";
if (StringUtils.equals(ELEMENT_PLUS, tplWebType))
{ {
useWebType = "vm/vue/v3"; useWebType = "vm/vue/v3";
} }
else if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType))
{
useWebType = "vm/vue/v3ts";
apiTemplate = "vm/ts/api.ts.vm";
}
List<String> templates = new ArrayList<String>(); List<String> templates = new ArrayList<String>();
templates.add("vm/java/domain.java.vm"); templates.add("vm/java/domain.java.vm");
templates.add("vm/java/mapper.java.vm"); templates.add("vm/java/mapper.java.vm");
@@ -142,7 +167,12 @@ public class VelocityUtils
templates.add("vm/java/controller.java.vm"); templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm"); templates.add("vm/xml/mapper.xml.vm");
templates.add("vm/sql/sql.vm"); templates.add("vm/sql/sql.vm");
templates.add("vm/js/api.js.vm"); templates.add(apiTemplate);
if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType))
{
templates.add("vm/ts/type.ts.vm");
templates.add("vm/ts/index.ts.vm");
}
if (GenConstants.TPL_CRUD.equals(tplCategory)) if (GenConstants.TPL_CRUD.equals(tplCategory))
{ {
templates.add(useWebType + "/index.vue.vm"); templates.add(useWebType + "/index.vue.vm");
@@ -156,6 +186,10 @@ public class VelocityUtils
templates.add(useWebType + "/index.vue.vm"); templates.add(useWebType + "/index.vue.vm");
templates.add("vm/java/sub-domain.java.vm"); templates.add("vm/java/sub-domain.java.vm");
} }
if (isView)
{
templates.add(useWebType + "/view.vue.vm");
}
return templates; return templates;
} }
@@ -215,6 +249,18 @@ public class VelocityUtils
{ {
fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
} }
else if (template.contains("api.ts.vm"))
{
fileName = StringUtils.format("{}/api/{}/{}.ts", vuePath, moduleName, businessName);
}
else if (template.contains("type.ts.vm"))
{
fileName = StringUtils.format("{}/types/api/{}/{}.ts", vuePath, moduleName, businessName);
}
else if (template.contains("index.ts.vm"))
{
fileName = StringUtils.format("{}/types/api/index-bak.ts", vuePath);
}
else if (template.contains("index.vue.vm")) else if (template.contains("index.vue.vm"))
{ {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
@@ -223,6 +269,10 @@ public class VelocityUtils
{ {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
} }
else if (template.contains("view.vue.vm"))
{
fileName = StringUtils.format("{}/views/{}/{}/view.vue", vuePath, moduleName, businessName);
}
return fileName; return fileName;
} }
@@ -364,6 +414,21 @@ public class VelocityUtils
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
/**
* 扩展功能/生成详情页
*
* @param paramsObj 生成其他选项
* @return 是否生成详细页
*/
public static boolean genView(JSONObject paramsObj)
{
if (StringUtils.isNotNull(paramsObj) && paramsObj.containsKey(GenConstants.GEN_VIEW))
{
return paramsObj.getBoolean(GenConstants.GEN_VIEW);
}
return false;
}
/** /**
* 获取树名称 * 获取树名称
* *
@@ -405,4 +470,23 @@ public class VelocityUtils
} }
return num; return num;
} }
/**
* 获取表单 el-col span
*
* @param formColNum 表单布局方式1单列 2双列 3三列
* @return span 数值字符串
*/
public static String getColSpan(int formColNum)
{
if (formColNum == 2)
{
return "12";
}
else if (formColNum == 3)
{
return "8";
}
return "24";
}
} }

View File

@@ -94,6 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<set> <set>
<if test="columnComment != null">column_comment = #{columnComment},</if> <if test="columnComment != null">column_comment = #{columnComment},</if>
<if test="javaType != null">java_type = #{javaType},</if> <if test="javaType != null">java_type = #{javaType},</if>
<if test="columnType != null">column_type = #{columnType},</if>
<if test="javaField != null">java_field = #{javaField},</if> <if test="javaField != null">java_field = #{javaField},</if>
<if test="isInsert != null">is_insert = #{isInsert},</if> <if test="isInsert != null">is_insert = #{isInsert},</if>
<if test="isEdit != null">is_edit = #{isEdit},</if> <if test="isEdit != null">is_edit = #{isEdit},</if>

View File

@@ -18,6 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="businessName" column="business_name" /> <result property="businessName" column="business_name" />
<result property="functionName" column="function_name" /> <result property="functionName" column="function_name" />
<result property="functionAuthor" column="function_author" /> <result property="functionAuthor" column="function_author" />
<result property="formColNum" column="form_col_num" />
<result property="genType" column="gen_type" /> <result property="genType" column="gen_type" />
<result property="genPath" column="gen_path" /> <result property="genPath" column="gen_path" />
<result property="options" column="options" /> <result property="options" column="options" />
@@ -55,7 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectGenTableVo"> <sql id="selectGenTableVo">
select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, form_col_num, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table
</sql> </sql>
<select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult"> <select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult">
@@ -112,7 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult"> <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.form_col_num, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -120,7 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult"> <select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.form_col_num, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -128,7 +129,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult"> <select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark, SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.form_col_num, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id LEFT JOIN gen_table_column c ON t.table_id = c.table_id
@@ -147,6 +148,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="businessName != null and businessName != ''">business_name,</if> <if test="businessName != null and businessName != ''">business_name,</if>
<if test="functionName != null and functionName != ''">function_name,</if> <if test="functionName != null and functionName != ''">function_name,</if>
<if test="functionAuthor != null and functionAuthor != ''">function_author,</if> <if test="functionAuthor != null and functionAuthor != ''">function_author,</if>
<if test="formColNum != null">form_col_num,</if>
<if test="genType != null and genType != ''">gen_type,</if> <if test="genType != null and genType != ''">gen_type,</if>
<if test="genPath != null and genPath != ''">gen_path,</if> <if test="genPath != null and genPath != ''">gen_path,</if>
<if test="remark != null and remark != ''">remark,</if> <if test="remark != null and remark != ''">remark,</if>
@@ -163,6 +165,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="businessName != null and businessName != ''">#{businessName},</if> <if test="businessName != null and businessName != ''">#{businessName},</if>
<if test="functionName != null and functionName != ''">#{functionName},</if> <if test="functionName != null and functionName != ''">#{functionName},</if>
<if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if> <if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
<if test="formColNum != null">#{formColNum},</if>
<if test="genType != null and genType != ''">#{genType},</if> <if test="genType != null and genType != ''">#{genType},</if>
<if test="genPath != null and genPath != ''">#{genPath},</if> <if test="genPath != null and genPath != ''">#{genPath},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
@@ -180,6 +183,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if> <if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if>
<if test="className != null and className != ''">class_name = #{className},</if> <if test="className != null and className != ''">class_name = #{className},</if>
<if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if> <if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if>
<if test="formColNum != null">form_col_num = #{formColNum},</if>
<if test="genType != null and genType != ''">gen_type = #{genType},</if> <if test="genType != null and genType != ''">gen_type = #{genType},</if>
<if test="genPath != null and genPath != ''">gen_path = #{genPath},</if> <if test="genPath != null and genPath != ''">gen_path = #{genPath},</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if> <if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>

View File

@@ -1,7 +1,6 @@
package ${packageName}.controller; package ${packageName}.controller;
import java.util.List; import java.util.List;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;

View File

@@ -0,0 +1,51 @@
import request from '@/utils/request'
import type { AjaxResult, TableDataInfo, ${BusinessName}QueryParams, ${ClassName} } from '@/types'
// 查询${functionName}列表
#if($table.tree)
export function list${BusinessName}(query?: ${BusinessName}QueryParams): Promise<AjaxResult<${ClassName}[]>> {
#else
export function list${BusinessName}(query: ${BusinessName}QueryParams): Promise<TableDataInfo<${ClassName}[]>> {
#end
return request({
url: '/${moduleName}/${businessName}/list',
method: 'get',
params: query
})
}
// 查询${functionName}详细
export function get${BusinessName}(${pkColumn.javaField}: number): Promise<AjaxResult<${ClassName}>> {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
})
}
// 新增${functionName}
export function add${BusinessName}(data: ${ClassName}): Promise<AjaxResult> {
return request({
url: '/${moduleName}/${businessName}',
method: 'post',
data: data
})
}
// 修改${functionName}
export function update${BusinessName}(data: ${ClassName}): Promise<AjaxResult> {
return request({
url: '/${moduleName}/${businessName}',
method: 'put',
data: data
})
}
// 删除${functionName}
export function del${BusinessName}(${pkColumn.javaField}: number | number[]): Promise<AjaxResult> {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
})
}

View File

@@ -0,0 +1,9 @@
/**
* API 类型统一导出
*/
....
// 防止覆盖需手动追加下面代码到index.ts文件中追加好后此文件可删除
// ${moduleName} 模块
export * from "./${moduleName}/${businessName}";

View File

@@ -0,0 +1,51 @@
import type { PageDomain, BaseEntity } from "../common";
/** ${functionName}配置分页查询参数 */
export interface ${BusinessName}QueryParams extends PageDomain {
#foreach($column in $columns)
#if($column.query)
#set($type = "string")
#if($column.javaType == "Long" || $column.javaType == "Integer")
#set($type = "number")
#elseif($column.javaType == "Boolean")
#set($type = "boolean")
#end
/** ${column.columnComment} */
${column.javaField}?: ${type};
#end
#end
}
/** ${functionName}配置信息 */
export interface ${ClassName} extends BaseEntity {
#foreach($column in $columns)
#set($type = "string")
#if($column.javaType == "Long" || $column.javaType == "Integer")
#set($type = "number")
#elseif($column.javaType == "Boolean")
#set($type = "boolean")
#end
/** ${column.columnComment} */
${column.javaField}?: ${type};
#end
#if($table.sub)
/** $table.subTable.functionName信息 */
${subclassName}List?: ${subClassName}[];
#end
}
#if($table.sub)
/** ${subTable.functionName}配置信息 */
export interface ${subClassName} extends BaseEntity {
#foreach ($column in $subTable.columns)
#set($type = "string")
#if($column.javaType == "Long" || $column.javaType == "Integer")
#set($type = "number")
#elseif($column.javaType == "Boolean")
#set($type = "boolean")
#end
/** ${column.columnComment} */
${column.javaField}?: ${type};
#end
}
#end

View File

@@ -139,6 +139,15 @@
#end #end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
#if($genView)
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleViewData(scope.row)"
v-hasPermi="['${permissionPrefix}:query']"
>详情</el-button>
#end
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@@ -164,9 +173,21 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 --> <!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> #if($table.formColNum == 2)
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> #set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" :visible.sync="open" width="${dialogWidth}" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns) #foreach($column in $columns)
#set($field=$column.javaField) #set($field=$column.javaField)
#if($column.insert && !$column.pk) #if($column.insert && !$column.pk)
@@ -179,100 +200,127 @@
#end #end
#set($dictType=$column.dictType) #set($dictType=$column.dictType)
#if("" != $treeParentCode && $column.javaField == $treeParentCode) #if("" != $treeParentCode && $column.javaField == $treeParentCode)
<el-form-item label="${comment}" prop="${treeParentCode}"> <el-col :span="${colSpan}">
<treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" /> <el-form-item label="${comment}" prop="${treeParentCode}">
</el-form-item> <treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "input") #elseif($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload") #elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<image-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload") #elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<file-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor") #elseif($column.htmlType == "editor")
<el-form-item label="${comment}"> <el-col :span="24">
<editor v-model="form.${field}" :min-height="192"/> <el-form-item label="${comment}">
</el-form-item> <editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType) #elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option <el-select v-model="form.${field}" placeholder="请选择${comment}">
v-for="dict in dict.type.${dictType}" <el-option
:key="dict.value" v-for="dict in dict.type.${dictType}"
:label="dict.label" :key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)" :value="parseInt(dict.value)"
#else #else
:value="dict.value" :value="dict.value"
#end #end
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType) #elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option label="请选择字典生成" value="" /> <el-select v-model="form.${field}" placeholder="请选择${comment}">
</el-select> <el-option label="请选择字典生成" value="" />
</el-form-item> </el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType) #elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox <el-checkbox-group v-model="form.${field}">
v-for="dict in dict.type.${dictType}" <el-checkbox
:key="dict.value" v-for="dict in dict.type.${dictType}"
:label="dict.value"> :key="dict.value"
{{dict.label}} :label="dict.value">
</el-checkbox> {{dict.label}}
</el-checkbox-group> </el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType) #elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox>请选择字典生成</el-checkbox> <el-checkbox-group v-model="form.${field}">
</el-checkbox-group> <el-checkbox>请选择字典生成</el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType) #elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio <el-radio-group v-model="form.${field}">
v-for="dict in dict.type.${dictType}" <el-radio
:key="dict.value" v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)" :label="parseInt(dict.value)"
#else #else
:label="dict.value" :label="dict.value"
#end #end
>{{dict.label}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio label="1">请选择字典生成</el-radio> <el-radio-group v-model="form.${field}">
</el-radio-group> <el-radio label="1">请选择字典生成</el-radio>
</el-form-item> </el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime") #elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-date-picker clearable <el-form-item label="${comment}" prop="${field}">
v-model="form.${field}" <el-date-picker clearable
type="date" v-model="form.${field}"
value-format="yyyy-MM-dd" type="date"
placeholder="选择${comment}"> value-format="yyyy-MM-dd"
</el-date-picker> placeholder="选择${comment}">
</el-form-item> </el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea") #elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="24">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end #end
#end #end
#end #end
#end #end
</el-row>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button> <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -284,6 +332,9 @@
<script> <script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}" import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
import Treeselect from "@riophae/vue-treeselect" import Treeselect from "@riophae/vue-treeselect"
import "@riophae/vue-treeselect/dist/vue-treeselect.css" import "@riophae/vue-treeselect/dist/vue-treeselect.css"
@@ -293,6 +344,9 @@ export default {
dicts: [${dicts}], dicts: [${dicts}],
#end #end
components: { components: {
#if($genView)
${BusinessName}ViewDrawer,
#end
Treeselect Treeselect
}, },
data() { data() {
@@ -448,6 +502,12 @@ export default {
this.refreshTable = true this.refreshTable = true
}) })
}, },
#if($genView)
/** 详情按钮操作 */
handleViewData(row) {
this.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
},
#end
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {
this.reset() this.reset()

View File

@@ -153,6 +153,15 @@
#end #end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
#if($genView)
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleViewData(scope.row)"
v-hasPermi="['${permissionPrefix}:query']"
>详情</el-button>
#end
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@@ -179,9 +188,21 @@
@pagination="getList" @pagination="getList"
/> />
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 --> <!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> #if($table.formColNum == 2)
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> #set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" :visible.sync="open" width="${dialogWidth}" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns) #foreach($column in $columns)
#set($field=$column.javaField) #set($field=$column.javaField)
#if($column.insert && !$column.pk) #if($column.insert && !$column.pk)
@@ -194,96 +215,121 @@
#end #end
#set($dictType=$column.dictType) #set($dictType=$column.dictType)
#if($column.htmlType == "input") #if($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload") #elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<image-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload") #elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<file-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor") #elseif($column.htmlType == "editor")
<el-form-item label="${comment}"> <el-col :span="24">
<editor v-model="form.${field}" :min-height="192"/> <el-form-item label="${comment}">
</el-form-item> <editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType) #elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option <el-select v-model="form.${field}" placeholder="请选择${comment}">
v-for="dict in dict.type.${dictType}" <el-option
:key="dict.value" v-for="dict in dict.type.${dictType}"
:label="dict.label" :key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)" :value="parseInt(dict.value)"
#else #else
:value="dict.value" :value="dict.value"
#end #end
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType) #elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option label="请选择字典生成" value="" /> <el-select v-model="form.${field}" placeholder="请选择${comment}">
</el-select> <el-option label="请选择字典生成" value="" />
</el-form-item> </el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType) #elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox <el-checkbox-group v-model="form.${field}">
v-for="dict in dict.type.${dictType}" <el-checkbox
:key="dict.value" v-for="dict in dict.type.${dictType}"
:label="dict.value"> :key="dict.value"
{{dict.label}} :label="dict.value">
</el-checkbox> {{dict.label}}
</el-checkbox-group> </el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType) #elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox>请选择字典生成</el-checkbox> <el-checkbox-group v-model="form.${field}">
</el-checkbox-group> <el-checkbox>请选择字典生成</el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType) #elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio <el-radio-group v-model="form.${field}">
v-for="dict in dict.type.${dictType}" <el-radio
:key="dict.value" v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)" :label="parseInt(dict.value)"
#else #else
:label="dict.value" :label="dict.value"
#end #end
>{{dict.label}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio label="1">请选择字典生成</el-radio> <el-radio-group v-model="form.${field}">
</el-radio-group> <el-radio label="1">请选择字典生成</el-radio>
</el-form-item> </el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime") #elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-date-picker clearable <el-form-item label="${comment}" prop="${field}">
v-model="form.${field}" <el-date-picker clearable
type="date" v-model="form.${field}"
value-format="yyyy-MM-dd" type="date"
placeholder="请选择${comment}"> value-format="yyyy-MM-dd"
</el-date-picker> placeholder="请选择${comment}">
</el-form-item> </el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea") #elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="24">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end #end
#end #end
#end #end
#end #end
</el-row>
#if($table.sub) #if($table.sub)
<el-divider content-position="center">${subTable.functionName}信息</el-divider> <el-divider content-position="center">${subTable.functionName}信息</el-divider>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
@@ -354,9 +400,15 @@
<script> <script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}" import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
export default { export default {
name: "${BusinessName}", name: "${BusinessName}",
#if($genView)
components: { ${BusinessName}ViewDrawer },
#end
#if(${dicts} != '') #if(${dicts} != '')
dicts: [${dicts}], dicts: [${dicts}],
#end #end
@@ -493,7 +545,7 @@ export default {
// 多选框选中数据 // 多选框选中数据
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.${pkColumn.javaField}) this.ids = selection.map(item => item.${pkColumn.javaField})
this.single = selection.length!==1 this.single = selection.length !== 1
this.multiple = !selection.length this.multiple = !selection.length
}, },
/** 新增按钮操作 */ /** 新增按钮操作 */
@@ -590,6 +642,12 @@ export default {
handle${subClassName}SelectionChange(selection) { handle${subClassName}SelectionChange(selection) {
this.checked${subClassName} = selection.map(item => item.index) this.checked${subClassName} = selection.map(item => item.index)
}, },
#end
#if($genView)
/** 详情按钮操作 */
handleViewData(row) {
this.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
},
#end #end
/** 导出按钮操作 */ /** 导出按钮操作 */
handleExport() { handleExport() {

View File

@@ -136,6 +136,9 @@
#end #end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
#if($genView)
<el-button link type="primary" icon="View" @click="handleViewData(scope.row)" v-hasPermi="['${permissionPrefix}:query']">详情</el-button>
#end
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button>
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${permissionPrefix}:add']">新增</el-button> <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${permissionPrefix}:add']">新增</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button>
@@ -143,9 +146,21 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 --> <!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body> #if($table.formColNum == 2)
<el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px"> #set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" v-model="open" width="${dialogWidth}" append-to-body>
<el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns) #foreach($column in $columns)
#set($field=$column.javaField) #set($field=$column.javaField)
#if($column.insert && !$column.pk) #if($column.insert && !$column.pk)
@@ -158,107 +173,134 @@
#end #end
#set($dictType=$column.dictType) #set($dictType=$column.dictType)
#if("" != $treeParentCode && $column.javaField == $treeParentCode) #if("" != $treeParentCode && $column.javaField == $treeParentCode)
<el-form-item label="${comment}" prop="${treeParentCode}"> <el-col :span="${colSpan}">
<el-tree-select <el-form-item label="${comment}" prop="${treeParentCode}">
v-model="form.${treeParentCode}" <el-tree-select
:data="${businessName}Options" v-model="form.${treeParentCode}"
:props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }" :data="${businessName}Options"
value-key="${treeCode}" :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
placeholder="请选择${comment}" value-key="${treeCode}"
check-strictly placeholder="请选择${comment}"
/> check-strictly
</el-form-item> />
</el-form-item>
</el-col>
#elseif($column.htmlType == "input") #elseif($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload") #elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<image-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload") #elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<file-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor") #elseif($column.htmlType == "editor")
<el-form-item label="${comment}"> <el-col :span="24">
<editor v-model="form.${field}" :min-height="192"/> <el-form-item label="${comment}">
</el-form-item> <editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType) #elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option <el-select v-model="form.${field}" placeholder="请选择${comment}">
v-for="dict in ${dictType}" <el-option
:key="dict.value" v-for="dict in ${dictType}"
:label="dict.label" :key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)" :value="parseInt(dict.value)"
#else #else
:value="dict.value" :value="dict.value"
#end #end
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType) #elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option label="请选择字典生成" value="" /> <el-select v-model="form.${field}" placeholder="请选择${comment}">
</el-select> <el-option label="请选择字典生成" value="" />
</el-form-item> </el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType) #elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox <el-checkbox-group v-model="form.${field}">
v-for="dict in ${dictType}" <el-checkbox
:key="dict.value" v-for="dict in ${dictType}"
:label="dict.value"> :key="dict.value"
{{dict.label}} :label="dict.value">
</el-checkbox> {{dict.label}}
</el-checkbox-group> </el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType) #elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox>请选择字典生成</el-checkbox> <el-checkbox-group v-model="form.${field}">
</el-checkbox-group> <el-checkbox>请选择字典生成</el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType) #elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio <el-radio-group v-model="form.${field}">
v-for="dict in ${dictType}" <el-radio
:key="dict.value" v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)" :label="parseInt(dict.value)"
#else #else
:label="dict.value" :label="dict.value"
#end #end
>{{dict.label}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio label="1">请选择字典生成</el-radio> <el-radio-group v-model="form.${field}">
</el-radio-group> <el-radio label="1">请选择字典生成</el-radio>
</el-form-item> </el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime") #elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-date-picker clearable <el-form-item label="${comment}" prop="${field}">
v-model="form.${field}" <el-date-picker clearable
type="date" v-model="form.${field}"
value-format="YYYY-MM-DD" type="date"
placeholder="选择${comment}"> value-format="YYYY-MM-DD"
</el-date-picker> placeholder="选择${comment}">
</el-form-item> </el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea") #elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="24">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end #end
#end #end
#end #end
#end #end
</el-row>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
@@ -272,11 +314,14 @@
<script setup name="${BusinessName}"> <script setup name="${BusinessName}">
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}" import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
#if(${dicts} != '') #if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", "")) #set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = proxy.useDict(${dicts}) const { ${dictsNoSymbol} } = useDict(${dicts})
#end #end
const ${businessName}List = ref([]) const ${businessName}List = ref([])
@@ -299,7 +344,7 @@ const data = reactive({
queryParams: { queryParams: {
#foreach ($column in $columns) #foreach ($column in $columns)
#if($column.query) #if($column.query)
$column.javaField: null#if($foreach.count != $columns.size()),#end $column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end #end
#end #end
}, },
@@ -334,7 +379,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]
} }
@@ -356,13 +401,13 @@ function getTreeselect() {
}) })
} }
// 取消按钮 /** 取消按钮 */
function cancel() { function cancel() {
open.value = false open.value = false
reset() reset()
} }
// 表单重置 /** 表单重置 */
function reset() { function reset() {
form.value = { form.value = {
#foreach ($column in $columns) #foreach ($column in $columns)
@@ -414,6 +459,13 @@ function toggleExpandAll() {
refreshTable.value = true refreshTable.value = true
}) })
} }
#if($genView)
/** 详情按钮操作 */
function handleViewData(row) {
proxy.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
}
#end
/** 修改按钮操作 */ /** 修改按钮操作 */
async function handleUpdate(row) { async function handleUpdate(row) {
@@ -444,13 +496,13 @@ function submitForm() {
#end #end
#end #end
if (form.value.${pkColumn.javaField} != null) { if (form.value.${pkColumn.javaField} != null) {
update${BusinessName}(form.value).then(response => { update${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("修改成功") proxy.#[[$modal]]#.msgSuccess("修改成功")
open.value = false open.value = false
getList() getList()
}) })
} else { } else {
add${BusinessName}(form.value).then(response => { add${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("新增成功") proxy.#[[$modal]]#.msgSuccess("新增成功")
open.value = false open.value = false
getList() getList()

View File

@@ -148,6 +148,9 @@
#end #end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
#if($genView)
<el-button link type="primary" icon="View" @click="handleViewData(scope.row)" v-hasPermi="['${permissionPrefix}:query']">详情</el-button>
#end
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button>
</template> </template>
@@ -162,9 +165,21 @@
@pagination="getList" @pagination="getList"
/> />
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 --> <!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body> #if($table.formColNum == 2)
<el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px"> #set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" v-model="open" width="${dialogWidth}" append-to-body>
<el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns) #foreach($column in $columns)
#set($field=$column.javaField) #set($field=$column.javaField)
#if($column.insert && !$column.pk) #if($column.insert && !$column.pk)
@@ -177,96 +192,121 @@
#end #end
#set($dictType=$column.dictType) #set($dictType=$column.dictType)
#if($column.htmlType == "input") #if($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload") #elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<image-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload") #elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<file-upload v-model="form.${field}"/> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor") #elseif($column.htmlType == "editor")
<el-form-item label="${comment}"> <el-col :span="24">
<editor v-model="form.${field}" :min-height="192"/> <el-form-item label="${comment}">
</el-form-item> <editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType) #elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option <el-select v-model="form.${field}" placeholder="请选择${comment}">
v-for="dict in ${dictType}" <el-option
:key="dict.value" v-for="dict in ${dictType}"
:label="dict.label" :key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)" :value="parseInt(dict.value)"
#else #else
:value="dict.value" :value="dict.value"
#end #end
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType) #elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-select v-model="form.${field}" placeholder="请选择${comment}"> <el-form-item label="${comment}" prop="${field}">
<el-option label="请选择字典生成" value="" /> <el-select v-model="form.${field}" placeholder="请选择${comment}">
</el-select> <el-option label="请选择字典生成" value="" />
</el-form-item> </el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType) #elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox <el-checkbox-group v-model="form.${field}">
v-for="dict in ${dictType}" <el-checkbox
:key="dict.value" v-for="dict in ${dictType}"
:label="dict.value"> :key="dict.value"
{{dict.label}} :label="dict.value">
</el-checkbox> {{dict.label}}
</el-checkbox-group> </el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType) #elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-checkbox-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-checkbox>请选择字典生成</el-checkbox> <el-checkbox-group v-model="form.${field}">
</el-checkbox-group> <el-checkbox>请选择字典生成</el-checkbox>
</el-form-item> </el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType) #elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio <el-radio-group v-model="form.${field}">
v-for="dict in ${dictType}" <el-radio
:key="dict.value" v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long") #if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)" :label="parseInt(dict.value)"
#else #else
:label="dict.value" :label="dict.value"
#end #end
>{{dict.label}}</el-radio> >{{dict.label}}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType) #elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-radio-group v-model="form.${field}"> <el-form-item label="${comment}" prop="${field}">
<el-radio label="1">请选择字典生成</el-radio> <el-radio-group v-model="form.${field}">
</el-radio-group> <el-radio label="1">请选择字典生成</el-radio>
</el-form-item> </el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime") #elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="${colSpan}">
<el-date-picker clearable <el-form-item label="${comment}" prop="${field}">
v-model="form.${field}" <el-date-picker clearable
type="date" v-model="form.${field}"
value-format="YYYY-MM-DD" type="date"
placeholder="请选择${comment}"> value-format="YYYY-MM-DD"
</el-date-picker> placeholder="请选择${comment}">
</el-form-item> </el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea") #elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}"> <el-col :span="24">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> <el-form-item label="${comment}" prop="${field}">
</el-form-item> <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end #end
#end #end
#end #end
#end #end
</el-row>
#if($table.sub) #if($table.sub)
<el-divider content-position="center">${subTable.functionName}信息</el-divider> <el-divider content-position="center">${subTable.functionName}信息</el-divider>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
@@ -277,9 +317,13 @@
<el-button type="danger" icon="Delete" @click="handleDelete${subClassName}">删除</el-button> <el-button type="danger" icon="Delete" @click="handleDelete${subClassName}">删除</el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}"> <el-table :data="${subclassName}List" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" align="center" prop="index" width="50"/> <el-table-column label="序号" width="60">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
#foreach($column in $subTable.columns) #foreach($column in $subTable.columns)
#set($javaField=$column.javaField) #set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf("")) #set($parentheseIndex=$column.columnComment.indexOf(""))
@@ -344,11 +388,14 @@
<script setup name="${BusinessName}"> <script setup name="${BusinessName}">
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}" import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
#if(${dicts} != '') #if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", "")) #set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = proxy.useDict(${dicts}) const { ${dictsNoSymbol} } = useDict(${dicts})
#end #end
const ${businessName}List = ref([]) const ${businessName}List = ref([])
@@ -380,7 +427,7 @@ const data = reactive({
pageSize: 10, pageSize: 10,
#foreach ($column in $columns) #foreach ($column in $columns)
#if($column.query) #if($column.query)
$column.javaField: null#if($foreach.count != $columns.size()),#end $column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end #end
#end #end
}, },
@@ -415,7 +462,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]
} }
@@ -428,13 +475,13 @@ function getList() {
}) })
} }
// 取消按钮 /** 取消按钮 */
function cancel() { function cancel() {
open.value = false open.value = false
reset() reset()
} }
// 表单重置 /** 表单重置 */
function reset() { function reset() {
form.value = { form.value = {
#foreach ($column in $columns) #foreach ($column in $columns)
@@ -469,7 +516,7 @@ function resetQuery() {
handleQuery() handleQuery()
} }
// 多选框选中数据 /** 多选框选中数据 */
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
ids.value = selection.map(item => item.${pkColumn.javaField}) ids.value = selection.map(item => item.${pkColumn.javaField})
single.value = selection.length != 1 single.value = selection.length != 1
@@ -515,13 +562,13 @@ function submitForm() {
form.value.${subclassName}List = ${subclassName}List.value form.value.${subclassName}List = ${subclassName}List.value
#end #end
if (form.value.${pkColumn.javaField} != null) { if (form.value.${pkColumn.javaField} != null) {
update${BusinessName}(form.value).then(response => { update${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("修改成功") proxy.#[[$modal]]#.msgSuccess("修改成功")
open.value = false open.value = false
getList() getList()
}) })
} else { } else {
add${BusinessName}(form.value).then(response => { add${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("新增成功") proxy.#[[$modal]]#.msgSuccess("新增成功")
open.value = false open.value = false
getList() getList()
@@ -543,18 +590,13 @@ function handleDelete(row) {
} }
#if($table.sub) #if($table.sub)
/** ${subTable.functionName}序号 */
function row${subClassName}Index({ row, rowIndex }) {
row.index = rowIndex + 1
}
/** ${subTable.functionName}添加按钮操作 */ /** ${subTable.functionName}添加按钮操作 */
function handleAdd${subClassName}() { function handleAdd${subClassName}() {
let obj = {} let obj = {}
#foreach($column in $subTable.columns) #foreach($column in $subTable.columns)
#if($column.pk || $column.javaField == ${subTableFkclassName}) #if($column.pk || $column.javaField == ${subTableFkclassName})
#elseif($column.list && "" != $javaField) #elseif($column.list && "" != $javaField)
obj.$column.javaField = "" obj.$column.javaField = undefined
#end #end
#end #end
${subclassName}List.value.push(obj) ${subclassName}List.value.push(obj)
@@ -578,6 +620,13 @@ function handle${subClassName}SelectionChange(selection) {
checked${subClassName}.value = selection.map(item => item.index) checked${subClassName}.value = selection.map(item => item.index)
} }
#end
#if($genView)
/** 详情按钮操作 */
function handleViewData(row) {
proxy.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
}
#end #end
/** 导出按钮操作 */ /** 导出按钮操作 */
function handleExport() { function handleExport() {

View File

@@ -0,0 +1,83 @@
<template>
<el-drawer title="${functionName}详情" v-model="visible" direction="rtl" size="60%" append-to-body :before-close="handleClose" class="detail-drawer">
<div v-loading="loading" class="drawer-content">
<h4 class="section-header">基本信息</h4>
#set($i = 0)
#foreach($column in $columns)
#if(!$column.pk && $column.list)
#set($dictType=$column.dictType)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($i % 2 == 0)
<el-row :gutter="20" class="mb8">
#end
<el-col :span="12">
<div class="info-item">
<label class="info-label">${comment}</label>
<span class="info-value plaintext">
#if("" != $dictType)
#if($column.htmlType == "checkbox")
<dict-tag :options="${dictType}" :value="info.${javaField} ? info.${javaField}.split(',') : []" />
#else
<dict-tag :options="${dictType}" :value="info.${javaField}" />
#end
#elseif($column.htmlType == "datetime")
{{ parseTime(info.${javaField}, '{y}-{m}-{d}') }}
#elseif($column.htmlType == "imageUpload")
<image-preview :src="info.${javaField}" :width="60" :height="60" />
#else
{{ info.${javaField} }}
#end
</span>
</div>
</el-col>
#set($i = $i + 1)
#if($i % 2 == 0)
</el-row>
#end
#end
#end
#if($i % 2 != 0)
</el-row>
#end
</div>
</el-drawer>
</template>
<script setup name="${BusinessName}ViewDrawer">
import { get${BusinessName} } from '@/api/${moduleName}/${businessName}'
#if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = useDict(${dicts})
#end
const visible = ref(false)
const loading = ref(false)
const info = reactive({})
const open = async (${pkColumn.javaField}) => {
visible.value = true
loading.value = true
try {
const res = await get${BusinessName}(${pkColumn.javaField})
Object.assign(info, res.data || {})
} catch (error) {
console.error('获取${functionName}信息失败:', error)
} finally {
loading.value = false
}
}
function handleClose() {
visible.value = false
Object.keys(info).forEach(key => delete info[key])
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,528 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-input
v-model="queryParams.${column.javaField}"
placeholder="请输入${comment}"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
<el-option
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-date-picker clearable
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
<el-form-item label="${comment}" style="width: 308px">
<el-date-picker
v-model="daterange${AttrName}"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
#end
#end
#end
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['${permissionPrefix}:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="${businessName}List"
row-key="${treeCode}"
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk)
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
<template #default="scope">
<image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
</template>
</el-table-column>
#elseif($column.list && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
#if($column.htmlType == "checkbox")
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
#else
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
#end
</template>
</el-table-column>
#elseif($column.list && "" != $javaField)
#if(${foreach.index} == 1)
<el-table-column label="${comment}" prop="${javaField}" />
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
#if($genView)
<el-button link type="primary" icon="View" @click="handleViewData(scope.row)" v-hasPermi="['${permissionPrefix}:query']">详情</el-button>
#end
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button>
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${permissionPrefix}:add']">新增</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 -->
#if($table.formColNum == 2)
#set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" v-model="open" width="${dialogWidth}" append-to-body>
<el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns)
#set($field=$column.javaField)
#if($column.insert && !$column.pk)
#if(($column.usableColumn) || (!$column.superColumn))
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#set($dictType=$column.dictType)
#if("" != $treeParentCode && $column.javaField == $treeParentCode)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${treeParentCode}">
<el-tree-select
v-model="form.${treeParentCode}"
:data="${businessName}Options"
:props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
value-key="${treeCode}"
placeholder="请选择${comment}"
check-strictly
/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "input")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor")
<el-col :span="24">
<el-form-item label="${comment}">
<editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)"
#else
:label="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-date-picker clearable
v-model="form.${field}"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择${comment}">
</el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea")
<el-col :span="24">
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end
#end
#end
#end
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="${BusinessName}">
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
import type { ${ClassName}, ${BusinessName}QueryParams } from "@/types/api/${moduleName}/${businessName}"
import type { TreeSelect } from '@/types/api/common'
const { proxy } = getCurrentInstance()
#if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = useDict(${dicts})
#end
const ${businessName}List = ref<any[]>([])
const ${businessName}Options = ref<TreeSelect[]>([])
const open = ref<boolean>(false)
const loading = ref<boolean>(true)
const showSearch = ref<boolean>(true)
const title = ref<string>("")
const isExpandAll = ref<boolean>(true)
const refreshTable = ref<boolean>(true)
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
const daterange${AttrName} = ref([])
#end
#end
const data = reactive({
form: {} as ${ClassName},
queryParams: {
#foreach ($column in $columns)
#if($column.query)
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
} as ${BusinessName}QueryParams,
rules: {
#foreach ($column in $columns)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
}
})
const { queryParams, form, rules } = toRefs(data)
/** 查询${functionName}列表 */
function getList() {
loading.value = true
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
queryParams.value.params = {}
#break
#end
#end
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
if (null != daterange${AttrName}.value && '' != daterange${AttrName}.value) {
queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0]
queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1]
}
#end
#end
list${BusinessName}(queryParams.value).then(response => {
${businessName}List.value = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}")
loading.value = false
})
}
/** 查询${functionName}下拉树结构 */
function getTreeselect() {
list${BusinessName}().then(response => {
${businessName}Options.value = []
const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] }
data.children = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}")
${businessName}Options.value.push(data)
})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
/** 表单重置 */
function reset() {
form.value = {
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
}
proxy.resetForm("${businessName}Ref")
}
/** 搜索按钮操作 */
function handleQuery() {
getList()
}
/** 重置按钮操作 */
function resetQuery() {
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
daterange${AttrName}.value = []
#end
#end
proxy.resetForm("queryRef")
handleQuery()
}
/** 新增按钮操作 */
function handleAdd(row: ${ClassName}) {
reset()
getTreeselect()
if (row != null && row.${treeCode}) {
form.value.${treeParentCode} = row.${treeCode}
} else {
form.value.${treeParentCode} = 0
}
open.value = true
title.value = "添加${functionName}"
}
/** 展开/折叠操作 */
function toggleExpandAll() {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
nextTick(() => {
refreshTable.value = true
})
}
#if($genView)
/** 详情按钮操作 */
function handleViewData(row: ${ClassName}) {
proxy.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
}
#end
/** 修改按钮操作 */
async function handleUpdate(row: ${ClassName}) {
reset()
await getTreeselect()
if (row != null) {
form.value.${treeParentCode} = row.${treeParentCode}
}
get${BusinessName}(row.${pkColumn.javaField}!).then(response => {
form.value = response.data
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.split(",")
#end
#end
open.value = true
title.value = "修改${functionName}"
})
}
/** 提交按钮 */
function submitForm() {
proxy.#[[$]]#refs["${businessName}Ref"].validate((valid: boolean) => {
if (valid) {
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.join(",")
#end
#end
if (form.value.${pkColumn.javaField} != null) {
update${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
add${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row: ${ClassName}) {
proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() {
return del${BusinessName}(row.${pkColumn.javaField}!)
}).then(() => {
getList()
proxy.#[[$modal]]#.msgSuccess("删除成功")
}).catch(() => {})
}
getList()
</script>

View File

@@ -0,0 +1,644 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-input
v-model="queryParams.${column.javaField}"
placeholder="请输入${comment}"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
<el-option
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-date-picker clearable
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
<el-form-item label="${comment}" style="width: 308px">
<el-date-picker
v-model="daterange${AttrName}"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
#end
#end
#end
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['${permissionPrefix}:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['${permissionPrefix}:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['${permissionPrefix}:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['${permissionPrefix}:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk)
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
<template #default="scope">
<image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
</template>
</el-table-column>
#elseif($column.list && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
#if($column.htmlType == "checkbox")
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
#else
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
#end
</template>
</el-table-column>
#elseif($column.list && "" != $javaField)
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
#if($genView)
<el-button link type="primary" icon="View" @click="handleViewData(scope.row)" v-hasPermi="['${permissionPrefix}:query']">详情</el-button>
#end
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
#if($genView)
<!-- ${functionName}详情抽屉 -->
<${businessName}-view-drawer ref="${businessName}ViewRef" />
#end
<!-- 添加或修改${functionName}对话框 -->
#if($table.formColNum == 2)
#set($dialogWidth = "800px")
#elseif($table.formColNum == 3)
#set($dialogWidth = "1100px")
#else
#set($dialogWidth = "500px")
#end
<el-dialog :title="title" v-model="open" width="${dialogWidth}" append-to-body>
<el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="100px">
<el-row>
#foreach($column in $columns)
#set($field=$column.javaField)
#if($column.insert && !$column.pk)
#if(($column.usableColumn) || (!$column.superColumn))
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#set($dictType=$column.dictType)
#if($column.htmlType == "input")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
</el-col>
#elseif($column.htmlType == "imageUpload")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "fileUpload")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "editor")
<el-col :span="24">
<el-form-item label="${comment}">
<editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && "" != $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "select" && $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)"
#else
:label="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "radio" && $dictType)
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
#elseif($column.htmlType == "datetime")
<el-col :span="${colSpan}">
<el-form-item label="${comment}" prop="${field}">
<el-date-picker clearable
v-model="form.${field}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择${comment}">
</el-date-picker>
</el-form-item>
</el-col>
#elseif($column.htmlType == "textarea")
<el-col :span="24">
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
#end
#end
#end
#end
</el-row>
#if($table.sub)
<el-divider content-position="center">${subTable.functionName}信息</el-divider>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAdd${subClassName}">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="Delete" @click="handleDelete${subClassName}">删除</el-button>
</el-col>
</el-row>
<el-table :data="${subclassName}List" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" width="60">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
#foreach($column in $subTable.columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk || $javaField == ${subTableFkclassName})
#elseif($column.list && $column.htmlType == "input")
<el-table-column label="$comment" prop="${javaField}" width="150">
<template #default="scope">
<el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="$comment" prop="${javaField}" width="240">
<template #default="scope">
<el-date-picker clearable
v-model="scope.row.$javaField"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择$comment">
</el-date-picker>
</template>
</el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
<el-table-column label="$comment" prop="${javaField}" width="150">
<template #default="scope">
<el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
<el-option
v-for="dict in $column.dictType"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</template>
</el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
<el-table-column label="$comment" prop="${javaField}" width="150">
<template #default="scope">
<el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
<el-option label="请选择字典生成" value="" />
</el-select>
</template>
</el-table-column>
#end
#end
</el-table>
#end
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="${BusinessName}">
#if($table.sub)
import type { ${ClassName}, ${subClassName}, ${BusinessName}QueryParams } from "@/types/api/${moduleName}/${businessName}"
#else
import type { ${ClassName}, ${BusinessName}QueryParams } from "@/types/api/${moduleName}/${businessName}"
#end
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"
#if($genView)
import ${BusinessName}ViewDrawer from "./view"
#end
const { proxy } = getCurrentInstance()
#if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = useDict(${dicts})
#end
const ${businessName}List = ref<${ClassName}[]>([])
#if($table.sub)
const ${subclassName}List = ref([])
#end
const open = ref<boolean>(false)
const loading = ref<boolean>(true)
const showSearch = ref<boolean>(true)
const ids = ref<number[]>([])
#if($table.sub)
const checked${subClassName} = ref([])
#end
const single = ref<boolean>(true)
const multiple = ref<boolean>(true)
const total = ref<number>(0)
const title = ref<string>("")
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
const daterange${AttrName} = ref<string[]>([])
#end
#end
const data = reactive({
form: {} as ${ClassName},
queryParams: {
pageNum: 1,
pageSize: 10,
#foreach ($column in $columns)
#if($column.query)
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
} as ${BusinessName}QueryParams,
rules: {
#foreach ($column in $columns)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
}
})
const { queryParams, form, rules } = toRefs(data)
/** 查询${functionName}列表 */
function getList() {
loading.value = true
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
queryParams.value.params = {}
#break
#end
#end
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
if (null != daterange${AttrName}.value && '' != daterange${AttrName}.value) {
queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0]
queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1]
}
#end
#end
list${BusinessName}(queryParams.value).then(response => {
${businessName}List.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
/** 表单重置 */
function reset() {
form.value = {
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
}
#if($table.sub)
${subclassName}List.value = []
#end
proxy.resetForm("${businessName}Ref")
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
daterange${AttrName}.value = []
#end
#end
proxy.resetForm("queryRef")
handleQuery()
}
/** 多选框选中数据 */
function handleSelectionChange(selection: ${ClassName}[]) {
ids.value = selection.map(item => item.${pkColumn.javaField})
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 新增按钮操作 */
function handleAdd() {
reset()
open.value = true
title.value = "添加${functionName}"
}
/** 修改按钮操作 */
function handleUpdate(row: ${ClassName}) {
reset()
const _${pkColumn.javaField} = row.${pkColumn.javaField} || ids.value[0]
get${BusinessName}(_${pkColumn.javaField}).then(response => {
form.value = response.data
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.split(",")
#end
#end
#if($table.sub)
${subclassName}List.value = response.data?.${subclassName}List ?? []
#end
open.value = true
title.value = "修改${functionName}"
})
}
/** 提交按钮 */
function submitForm() {
proxy.#[[$]]#refs["${businessName}Ref"].validate((valid: boolean) => {
if (valid) {
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.join(",")
#end
#end
#if($table.sub)
form.value.${subclassName}List = ${subclassName}List.value
#end
if (form.value.${pkColumn.javaField} != null) {
update${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
add${BusinessName}(form.value).then(() => {
proxy.#[[$modal]]#.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row: ${ClassName}) {
const _${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value
proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').then(function() {
return del${BusinessName}(_${pkColumn.javaField}s)
}).then(() => {
getList()
proxy.#[[$modal]]#.msgSuccess("删除成功")
}).catch(() => {})
}
#if($table.sub)
/** ${subTable.functionName}添加按钮操作 */
function handleAdd${subClassName}() {
let obj: ${subClassName} = {}
#foreach($column in $subTable.columns)
#if($column.pk || $column.javaField == ${subTableFkclassName})
#elseif($column.list && "" != $javaField)
obj.$column.javaField = undefined
#end
#end
${subclassName}List.value.push(obj)
}
/** ${subTable.functionName}删除按钮操作 */
function handleDelete${subClassName}() {
if (checked${subClassName}.value.length == 0) {
proxy.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据")
} else {
const ${subclassName}s = ${subclassName}List.value
const checked${subClassName}s = checked${subClassName}.value
${subclassName}List.value = ${subclassName}s.filter(function(item: any) {
return checked${subClassName}s.indexOf(item.index) == -1
})
}
}
/** 复选框选中数据 */
function handle${subClassName}SelectionChange(selection: any[]) {
checked${subClassName}.value = selection.map(item => item.index)
}
#end
#if($genView)
/** 详情按钮操作 */
function handleViewData(row: ${ClassName}) {
proxy.#[[$]]#refs["${businessName}ViewRef"].open(row.${pkColumn.javaField})
}
#end
/** 导出按钮操作 */
function handleExport() {
proxy.download('${moduleName}/${businessName}/export', {
...queryParams.value
}, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
}
getList()
</script>

View File

@@ -0,0 +1,84 @@
<template>
<el-drawer title="${functionName}详情" v-model="visible" direction="rtl" size="60%" append-to-body :before-close="handleClose" class="detail-drawer">
<div v-loading="loading" class="drawer-content">
<h4 class="section-header">基本信息</h4>
#set($i = 0)
#foreach($column in $columns)
#if(!$column.pk && $column.list)
#set($dictType=$column.dictType)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($i % 2 == 0)
<el-row :gutter="20" class="mb8">
#end
<el-col :span="12">
<div class="info-item">
<label class="info-label">${comment}</label>
<span class="info-value plaintext">
#if("" != $dictType)
#if($column.htmlType == "checkbox")
<dict-tag :options="${dictType}" :value="info.${javaField} ? info.${javaField}.split(',') : []" />
#else
<dict-tag :options="${dictType}" :value="info.${javaField}" />
#end
#elseif($column.htmlType == "datetime")
{{ parseTime(info.${javaField}, '{y}-{m}-{d}') }}
#elseif($column.htmlType == "imageUpload")
<image-preview :src="info.${javaField}" :width="60" :height="60" />
#else
{{ info.${javaField} }}
#end
</span>
</div>
</el-col>
#set($i = $i + 1)
#if($i % 2 == 0)
</el-row>
#end
#end
#end
#if($i % 2 != 0)
</el-row>
#end
</div>
</el-drawer>
</template>
<script setup lang="ts" name="${BusinessName}ViewDrawer">
import type { ${ClassName} } from "@/types/api/${moduleName}/${businessName}"
import { get${BusinessName} } from '@/api/${moduleName}/${businessName}'
#if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = useDict(${dicts})
#end
const visible = ref<boolean>(false)
const loading = ref<boolean>(false)
const info = reactive<Partial<${ClassName}>>({})
const open = async (#if($pkColumn.javaType == "Long" || $pkColumn.javaType == "Integer")${pkColumn.javaField}: number#else${pkColumn.javaField}: string#end): Promise<void> => {
visible.value = true
loading.value = true
try {
const res = await get${BusinessName}(${pkColumn.javaField})
Object.assign(info, res.data ?? {})
} catch (error) {
console.error('获取${functionName}信息失败:', error)
} finally {
loading.value = false
}
}
const handleClose = (): void => {
visible.value = false
Object.keys(info).forEach(key => delete (info as any)[key])
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,86 @@
<template>
<el-drawer title="${functionName}详情" :visible.sync="visible" direction="rtl" size="60%" append-to-body :before-close="handleClose" custom-class="detail-drawer">
<div v-loading="loading" class="drawer-content">
<h4 class="section-header">基本信息</h4>
#set($i = 0)
#foreach($column in $columns)
#if(!$column.pk && $column.list)
#set($dictType=$column.dictType)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($i % 2 == 0)
<el-row :gutter="20" class="mb8">
#end
<el-col :span="12">
<div class="info-item">
<label class="info-label">${comment}</label>
<span class="info-value plaintext">
#if("" != $dictType)
#if($column.htmlType == "checkbox")
<dict-tag :options="dict.type.${dictType}" :value="info.${javaField} ? info.${javaField}.split(',') : []" />
#else
<dict-tag :options="dict.type.${dictType}" :value="info.${javaField}" />
#end
#elseif($column.htmlType == "datetime")
{{ parseTime(info.${javaField}, '{y}-{m}-{d}') }}
#else
{{ info.${javaField} }}
#end
</span>
</div>
</el-col>
#set($i = $i + 1)
#if($i % 2 == 0)
</el-row>
#end
#end
#end
#if($i % 2 != 0)
</el-row>
#end
</div>
</el-drawer>
</template>
<script>
import { get${BusinessName} } from '@/api/${moduleName}/${businessName}'
export default {
name: '${BusinessName}ViewDrawer',
#foreach($column in $columns)
#if("" != $column.dictType)
#set($hasDicts = true)
#break
#end
#end
#if($hasDicts)
dicts: [#foreach($column in $columns)#if("" != $column.dictType)'${column.dictType}'#if($foreach.hasNext), #end#end#end],
#end
data() {
return {
visible: false,
loading: false,
info: {}
}
},
methods: {
open(${pkColumn.javaField}) {
this.visible = true
this.loading = true
get${BusinessName}(${pkColumn.javaField}).then(res => {
this.info = res.data || {}
}).finally(() => {
this.loading = false
})
},
handleClose() {
this.visible = false
}
}
}
</script>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -3,6 +3,7 @@ package com.ruoyi.job.domain;
import java.util.Date; import java.util.Date;
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.JsonFormat;
import com.ruoyi.common.core.annotation.Excel; import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.common.core.web.domain.BaseEntity; import com.ruoyi.common.core.web.domain.BaseEntity;
@@ -44,10 +45,12 @@ public class SysJobLog extends BaseEntity
private String exceptionInfo; private String exceptionInfo;
/** 开始时间 */ /** 开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime; private Date startTime;
/** 停止时间 */ /** 结束时间 */
private Date stopTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
public Long getJobLogId() public Long getJobLogId()
{ {
@@ -129,14 +132,14 @@ public class SysJobLog extends BaseEntity
this.startTime = startTime; this.startTime = startTime;
} }
public Date getStopTime() public Date getEndTime()
{ {
return stopTime; return endTime;
} }
public void setStopTime(Date stopTime) public void setEndTime(Date endTime)
{ {
this.stopTime = stopTime; this.endTime = endTime;
} }
@Override @Override
@@ -149,7 +152,7 @@ public class SysJobLog extends BaseEntity
.append("status", getStatus()) .append("status", getStatus())
.append("exceptionInfo", getExceptionInfo()) .append("exceptionInfo", getExceptionInfo())
.append("startTime", getStartTime()) .append("startTime", getStartTime())
.append("stopTime", getStopTime()) .append("stopTime", getEndTime())
.toString(); .toString();
} }
} }

View File

@@ -76,8 +76,8 @@ public abstract class AbstractQuartzJob implements Job
sysJobLog.setJobGroup(sysJob.getJobGroup()); sysJobLog.setJobGroup(sysJob.getJobGroup());
sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
sysJobLog.setStartTime(startTime); sysJobLog.setStartTime(startTime);
sysJobLog.setStopTime(new Date()); sysJobLog.setEndTime(new Date());
long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime();
sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null) if (e != null)
{ {

View File

@@ -131,11 +131,11 @@ public class ScheduleUtils
int count = StringUtils.countMatches(packageName, "."); int count = StringUtils.countMatches(packageName, ".");
if (count > 1) if (count > 1)
{ {
return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR);
} }
Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
String beanPackageName = obj.getClass().getPackage().getName(); String beanPackageName = obj.getClass().getPackage().getName();
return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR)
&& !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); && !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR);
} }
} }

View File

@@ -12,11 +12,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="jobMessage" column="job_message" /> <result property="jobMessage" column="job_message" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="exceptionInfo" column="exception_info" /> <result property="exceptionInfo" column="exception_info" />
<result property="startTime" column="start_time" />
<result property="endTime" column="end_time" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
</resultMap> </resultMap>
<sql id="selectJobLogVo"> <sql id="selectJobLogVo">
select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, start_time, end_time, create_time
from sys_job_log from sys_job_log
</sql> </sql>
@@ -78,6 +80,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="jobMessage != null and jobMessage != ''">job_message,</if> <if test="jobMessage != null and jobMessage != ''">job_message,</if>
<if test="status != null and status != ''">status,</if> <if test="status != null and status != ''">status,</if>
<if test="exceptionInfo != null and exceptionInfo != ''">exception_info,</if> <if test="exceptionInfo != null and exceptionInfo != ''">exception_info,</if>
<if test="startTime != null">start_time,</if>
<if test="endTime != null">end_time,</if>
create_time create_time
)values( )values(
<if test="jobLogId != null and jobLogId != 0">#{jobLogId},</if> <if test="jobLogId != null and jobLogId != 0">#{jobLogId},</if>
@@ -87,6 +91,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="jobMessage != null and jobMessage != ''">#{jobMessage},</if> <if test="jobMessage != null and jobMessage != ''">#{jobMessage},</if>
<if test="status != null and status != ''">#{status},</if> <if test="status != null and status != ''">#{status},</if>
<if test="exceptionInfo != null and exceptionInfo != ''">#{exceptionInfo},</if> <if test="exceptionInfo != null and exceptionInfo != ''">#{exceptionInfo},</if>
<if test="startTime != null">#{startTime},</if>
<if test="endTime != null">#{endTime},</if>
sysdate() sysdate()
) )
</insert> </insert>

View File

@@ -21,7 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectJobVo"> <sql id="selectJobVo">
select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark
from sys_job from sys_job
</sql> </sql>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId> <artifactId>ruoyi-modules</artifactId>
<version>3.6.6</version> <version>3.6.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -1,6 +1,7 @@
package com.ruoyi.system.controller; package com.ruoyi.system.controller;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -111,6 +112,20 @@ public class SysDeptController extends BaseController
return toAjax(deptService.updateDept(dept)); return toAjax(deptService.updateDept(dept));
} }
/**
* 保存部门排序
*/
@RequiresPermissions("system:dept:edit")
@Log(title = "保存部门排序", businessType = BusinessType.UPDATE)
@PutMapping("/updateSort")
public AjaxResult updateSort(@RequestBody Map<String, String> params)
{
String[] deptIds = params.get("deptIds").split(",");
String[] orderNums = params.get("orderNums").split(",");
deptService.updateDeptSort(deptIds, orderNums);
return success();
}
/** /**
* 删除部门 * 删除部门
*/ */

View File

@@ -1,6 +1,7 @@
package com.ruoyi.system.controller; package com.ruoyi.system.controller;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -97,6 +98,10 @@ public class SysMenuController extends BaseController
{ {
return error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头"); return error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} }
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setCreateBy(SecurityUtils.getUsername()); menu.setCreateBy(SecurityUtils.getUsername());
return toAjax(menuService.insertMenu(menu)); return toAjax(menuService.insertMenu(menu));
} }
@@ -121,10 +126,28 @@ public class SysMenuController extends BaseController
{ {
return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
} }
else if (!menuService.checkRouteConfigUnique(menu))
{
return error("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
}
menu.setUpdateBy(SecurityUtils.getUsername()); menu.setUpdateBy(SecurityUtils.getUsername());
return toAjax(menuService.updateMenu(menu)); return toAjax(menuService.updateMenu(menu));
} }
/**
* 保存菜单排序
*/
@RequiresPermissions("system:menu:edit")
@Log(title = "保存菜单排序", businessType = BusinessType.UPDATE)
@PutMapping("/updateSort")
public AjaxResult updateSort(@RequestBody Map<String, String> params)
{
String[] menuIds = params.get("menuIds").split(",");
String[] orderNums = params.get("orderNums").split(",");
menuService.updateMenuSort(menuIds, orderNums);
return success();
}
/** /**
* 删除菜单 * 删除菜单
*/ */

View File

@@ -10,7 +10,9 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo; import com.ruoyi.common.core.web.page.TableDataInfo;
@@ -19,6 +21,7 @@ import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions; import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.domain.SysNotice; import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.service.ISysNoticeReadService;
import com.ruoyi.system.service.ISysNoticeService; import com.ruoyi.system.service.ISysNoticeService;
/** /**
@@ -33,6 +36,9 @@ public class SysNoticeController extends BaseController
@Autowired @Autowired
private ISysNoticeService noticeService; private ISysNoticeService noticeService;
@Autowired
private ISysNoticeReadService noticeReadService;
/** /**
* 获取通知公告列表 * 获取通知公告列表
*/ */
@@ -48,7 +54,6 @@ public class SysNoticeController extends BaseController
/** /**
* 根据通知公告编号获取详细信息 * 根据通知公告编号获取详细信息
*/ */
@RequiresPermissions("system:notice:query")
@GetMapping(value = "/{noticeId}") @GetMapping(value = "/{noticeId}")
public AjaxResult getInfo(@PathVariable Long noticeId) public AjaxResult getInfo(@PathVariable Long noticeId)
{ {
@@ -79,6 +84,59 @@ public class SysNoticeController extends BaseController
return toAjax(noticeService.updateNotice(notice)); return toAjax(noticeService.updateNotice(notice));
} }
/**
* 首页顶部公告列表返回全部正常公告带当前用户已读标记最多5条
*/
@GetMapping("/listTop")
@ResponseBody
public AjaxResult listTop()
{
Long userId = SecurityUtils.getUserId();
List<SysNotice> list = noticeReadService.selectNoticeListWithReadStatus(userId, 5);
long unreadCount = list.stream().filter(n -> !n.getIsRead()).count();
AjaxResult result = AjaxResult.success(list);
result.put("unreadCount", unreadCount);
return result;
}
/**
* 标记公告已读
*/
@PostMapping("/markRead")
@ResponseBody
public AjaxResult markRead(Long noticeId)
{
Long userId = SecurityUtils.getUserId();
noticeReadService.markRead(noticeId, userId);
return success();
}
/**
* 批量标记已读
*/
@PostMapping("/markReadAll")
@ResponseBody
public AjaxResult markReadAll(String ids)
{
Long userId = SecurityUtils.getUserId();
Long[] noticeIds = Convert.toLongArray(ids);
noticeReadService.markReadBatch(userId, noticeIds);
return success();
}
/**
* 已读用户列表数据
*/
@RequiresPermissions("system:notice:list")
@GetMapping("/readUsers/list")
@ResponseBody
public TableDataInfo readUsersList(Long noticeId, String searchValue)
{
startPage();
List<?> list = noticeReadService.selectReadUsersByNoticeId(noticeId, searchValue);
return getDataTable(list);
}
/** /**
* 删除通知公告 * 删除通知公告
*/ */
@@ -87,6 +145,7 @@ public class SysNoticeController extends BaseController
@DeleteMapping("/{noticeIds}") @DeleteMapping("/{noticeIds}")
public AjaxResult remove(@PathVariable Long[] noticeIds) public AjaxResult remove(@PathVariable Long[] noticeIds)
{ {
noticeReadService.deleteByNoticeIds(noticeIds);
return toAjax(noticeService.deleteNoticeByIds(noticeIds)); return toAjax(noticeService.deleteNoticeByIds(noticeIds));
} }
} }

View File

@@ -100,7 +100,7 @@ public class SysProfileController extends BaseController
String oldPassword = params.get("oldPassword"); String oldPassword = params.get("oldPassword");
String newPassword = params.get("newPassword"); String newPassword = params.get("newPassword");
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
String userName = loginUser.getUsername(); Long userId = loginUser.getUserid();
String password = loginUser.getSysUser().getPassword(); String password = loginUser.getSysUser().getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password)) if (!SecurityUtils.matchesPassword(oldPassword, password))
{ {
@@ -111,7 +111,7 @@ public class SysProfileController extends BaseController
return error("新密码不能与旧密码相同"); return error("新密码不能与旧密码相同");
} }
newPassword = SecurityUtils.encryptPassword(newPassword); newPassword = SecurityUtils.encryptPassword(newPassword);
if (userService.resetUserPwd(userName, newPassword) > 0) if (userService.resetUserPwd(userId, newPassword) > 0)
{ {
// 更新缓存用户密码&密码最后更新时间 // 更新缓存用户密码&密码最后更新时间
loginUser.getSysUser().setPwdUpdateDate(DateUtils.getNowDate()); loginUser.getSysUser().setPwdUpdateDate(DateUtils.getNowDate());
@@ -143,8 +143,13 @@ public class SysProfileController extends BaseController
return error("文件服务异常,请联系管理员"); return error("文件服务异常,请联系管理员");
} }
String url = fileResult.getData().getUrl(); String url = fileResult.getData().getUrl();
if (userService.updateUserAvatar(loginUser.getUsername(), url)) if (userService.updateUserAvatar(loginUser.getUserid(), url))
{ {
String oldAvatarUrl = loginUser.getSysUser().getAvatar();
if (StringUtils.isNotEmpty(oldAvatarUrl))
{
remoteFileService.delete(oldAvatarUrl);
}
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", url); ajax.put("imgUrl", url);
// 更新缓存用户头像 // 更新缓存用户头像

View File

@@ -163,7 +163,7 @@ public class SysUserController extends BaseController
@PutMapping("/recordlogin") @PutMapping("/recordlogin")
public R<Boolean> recordlogin(@RequestBody SysUser sysUser) public R<Boolean> recordlogin(@RequestBody SysUser sysUser)
{ {
return R.ok(userService.updateUserProfile(sysUser)); return R.ok(userService.updateLoginInfo(sysUser));
} }
/** /**
@@ -189,11 +189,18 @@ public class SysUserController extends BaseController
ajax.put("user", user); ajax.put("user", user);
ajax.put("roles", roles); ajax.put("roles", roles);
ajax.put("permissions", permissions); ajax.put("permissions", permissions);
ajax.put("pwdChrtype", getSysAccountChrtype());
ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
return ajax; return ajax;
} }
// 获取用户密码自定义配置规则
public String getSysAccountChrtype()
{
return Convert.toStr(configService.selectConfigByKey("sys.account.chrtype"), "0");
}
// 检查初始密码是否提醒修改 // 检查初始密码是否提醒修改
public boolean initPasswordIsModify(Date pwdUpdateDate) public boolean initPasswordIsModify(Date pwdUpdateDate)
{ {
@@ -235,7 +242,7 @@ public class SysUserController extends BaseController
ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
} }
List<SysRole> roles = roleService.selectRoleAll(); List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll()); ajax.put("posts", postService.selectPostAll());
return ajax; return ajax;
} }
@@ -350,7 +357,7 @@ public class SysUserController extends BaseController
SysUser user = userService.selectUserById(userId); SysUser user = userService.selectUserById(userId);
List<SysRole> roles = roleService.selectRolesByUserId(userId); List<SysRole> roles = roleService.selectRolesByUserId(userId);
ajax.put("user", user); ajax.put("user", user);
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return ajax; return ajax;
} }

View File

@@ -4,6 +4,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
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.core.web.domain.BaseEntity; import com.ruoyi.common.core.web.domain.BaseEntity;
import com.ruoyi.common.core.xss.Xss; import com.ruoyi.common.core.xss.Xss;
@@ -31,6 +32,10 @@ public class SysNotice extends BaseEntity
/** 公告状态0正常 1关闭 */ /** 公告状态0正常 1关闭 */
private String status; private String status;
/** 是否已读 */
@JsonProperty("isRead")
private boolean isRead;
public Long getNoticeId() public Long getNoticeId()
{ {
return noticeId; return noticeId;
@@ -84,6 +89,16 @@ public class SysNotice extends BaseEntity
return status; return status;
} }
public boolean getIsRead()
{
return isRead;
}
public void setIsRead(boolean isRead)
{
this.isRead = isRead;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@@ -0,0 +1,76 @@
package com.ruoyi.system.domain;
import java.util.Date;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* 公告已读记录表 sys_notice_read
*
* @author ruoyi
*/
public class SysNoticeRead
{
/** 主键 */
private Long readId;
/** 公告ID */
private Long noticeId;
/** 用户ID */
private Long userId;
/** 阅读时间 */
private Date readTime;
public Long getReadId()
{
return readId;
}
public void setReadId(Long readId)
{
this.readId = readId;
}
public Long getNoticeId()
{
return noticeId;
}
public void setNoticeId(Long noticeId)
{
this.noticeId = noticeId;
}
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public Date getReadTime()
{
return readTime;
}
public void setReadTime(Date readTime)
{
this.readTime = readTime;
}
@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("readId", getReadId())
.append("noticeId", getNoticeId())
.append("userId", getUserId())
.append("readTime", getReadTime())
.toString();
}
}

View File

@@ -108,6 +108,13 @@ public interface SysDeptMapper
*/ */
public int updateDeptChildren(@Param("depts") List<SysDept> depts); public int updateDeptChildren(@Param("depts") List<SysDept> depts);
/**
* 保存部门排序
*
* @param dept 部门信息
*/
public void updateDeptSort(SysDept dept);
/** /**
* 删除部门管理信息 * 删除部门管理信息
* *

View File

@@ -106,6 +106,13 @@ public interface SysMenuMapper
*/ */
public int updateMenu(SysMenu menu); public int updateMenu(SysMenu menu);
/**
* 保存菜单排序
*
* @param menu 菜单信息
*/
public void updateMenuSort(SysMenu menu);
/** /**
* 删除菜单管理信息 * 删除菜单管理信息
* *
@@ -122,4 +129,13 @@ public interface SysMenuMapper
* @return 结果 * @return 结果
*/ */
public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId);
/**
* 根据路由路径或名称查询菜单信息(用于唯一性校验)
*
* @param path 路由地址
* @param routeName 路由名称
* @return 匹配的菜单列表
*/
public List<SysMenu> selectMenusByPathOrRouteName(@Param("path") String path, @Param("routeName") String routeName);
} }

View File

@@ -0,0 +1,75 @@
package com.ruoyi.system.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.domain.SysNoticeRead;
/**
* 公告已读记录 数据层
*
* @author ruoyi
*/
public interface SysNoticeReadMapper
{
/**
* 新增已读记录(忽略重复)
*
* @param noticeRead 已读记录
* @return 结果
*/
public int insertNoticeRead(SysNoticeRead noticeRead);
/**
* 查询某用户未读公告数量
*
* @param userId 用户ID
* @return 未读数量
*/
public int selectUnreadCount(@Param("userId") Long userId);
/**
* 查询某用户是否已读某公告
*
* @param noticeId 公告ID
* @param userId 用户ID
* @return 已读记录数0未读 1已读
*/
public int selectIsRead(@Param("noticeId") Long noticeId, @Param("userId") Long userId);
/**
* 批量标记已读
*
* @param userId 用户ID
* @param noticeIds 公告ID数组
* @return 结果
*/
public int insertNoticeReadBatch(@Param("userId") Long userId, @Param("noticeIds") Long[] noticeIds);
/**
* 查询带已读状态的公告列表SQL层限制条数一次查询完成
*
* @param userId 用户ID
* @param limit 最多返回条数
* @return 带 isRead 标记的公告列表
*/
public List<SysNotice> selectNoticeListWithReadStatus(@Param("userId") Long userId, @Param("limit") int limit);
/**
* 查询已阅读某公告的用户列表
*
* @param noticeId 公告ID
* @param searchValue 搜索值
* @return 已读用户列表
*/
public List<Map<String, Object>> selectReadUsersByNoticeId(@Param("noticeId") Long noticeId, @Param("searchValue") String searchValue);
/**
* 公告删除时清理对应已读记录
*
* @param noticeIds 公告ID数组
* @return 结果
*/
public int deleteByNoticeIds(@Param("noticeIds") Long[] noticeIds);
}

View File

@@ -70,20 +70,37 @@ public interface SysUserMapper
/** /**
* 修改用户头像 * 修改用户头像
* *
* @param userName 用户 * @param userId 用户ID
* @param avatar 头像地址 * @param avatar 头像地址
* @return 结果 * @return 结果
*/ */
public int updateUserAvatar(@Param("userName") String userName, @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 user 用户信息
* @return 结果
*/
public int updateLoginInfo(SysUser user);
/** /**
* 重置用户密码 * 重置用户密码
* *
* @param userName 用户 * @param userId 用户ID
* @param password 密码 * @param password 密码
* @return 结果 * @return 结果
*/ */
public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); public int resetUserPwd(@Param("userId") Long userId, @Param("password") String password);
/** /**
* 通过用户ID删除用户 * 通过用户ID删除用户

View File

@@ -114,6 +114,14 @@ public interface ISysDeptService
*/ */
public int updateDept(SysDept dept); public int updateDept(SysDept dept);
/**
* 保存部门排序
*
* @param deptIds 部门ID数组
* @param orderNums 排序数组
*/
public void updateDeptSort(String[] deptIds, String[] orderNums);
/** /**
* 删除部门管理信息 * 删除部门管理信息
* *

View File

@@ -126,6 +126,14 @@ public interface ISysMenuService
*/ */
public int updateMenu(SysMenu menu); public int updateMenu(SysMenu menu);
/**
* 保存菜单排序
*
* @param menuIds 菜单ID
* @param orderNums 排序ID
*/
public void updateMenuSort(String[] menuIds, String[] orderNums);
/** /**
* 删除菜单管理信息 * 删除菜单管理信息
* *
@@ -141,4 +149,12 @@ public interface ISysMenuService
* @return 结果 * @return 结果
*/ */
public boolean checkMenuNameUnique(SysMenu menu); public boolean checkMenuNameUnique(SysMenu menu);
/**
* 校验路由组合是否唯一
*
* @param menu 菜单信息
* @return 结果
*/
public boolean checkRouteConfigUnique(SysMenu menu);
} }

View File

@@ -0,0 +1,62 @@
package com.ruoyi.system.service;
import java.util.List;
import java.util.Map;
import com.ruoyi.system.domain.SysNotice;
/**
* 公告已读记录 服务层
*
* @author ruoyi
*/
public interface ISysNoticeReadService
{
/**
* 标记已读(幂等,重复调用不报错)
*
* @param noticeId 公告ID
* @param userId 用户ID
*/
public void markRead(Long noticeId, Long userId);
/**
* 查询某用户未读公告数量
*
* @param userId 用户ID
* @return 未读数量
*/
public int selectUnreadCount(Long userId);
/**
* 查询公告列表并标记当前用户已读状态(用于首页展示)
*
* @param userId 用户ID
* @param limit 最多返回条数
* @return 带 isRead 标记的公告列表
*/
public List<SysNotice> selectNoticeListWithReadStatus(Long userId, int limit);
/**
* 批量标记已读
*
* @param userId 用户ID
* @param noticeIds 公告ID数组
*/
public void markReadBatch(Long userId, Long[] noticeIds);
/**
* 查询已阅读某公告的用户列表
*
* @param noticeId 公告ID
* @param searchValue 搜索值
* @return 已读用户列表
*/
public List<Map<String, Object>> selectReadUsersByNoticeId(Long noticeId, String searchValue);
/**
* 删除公告时清理对应已读记录
*
* @param noticeIds 公告ID数组
*/
public void deleteByNoticeIds(Long[] noticeIds);
}

View File

@@ -155,11 +155,19 @@ public interface ISysUserService
/** /**
* 修改用户头像 * 修改用户头像
* *
* @param userName 用户 * @param userId 用户ID
* @param avatar 头像地址 * @param avatar 头像地址
* @return 结果 * @return 结果
*/ */
public boolean updateUserAvatar(String userName, String avatar); public boolean updateUserAvatar(Long userId, String avatar);
/**
* 更新用户登录信息IP和登录时间
*
* @param user 用户信息
* @return 结果
*/
public boolean updateLoginInfo(SysUser user);
/** /**
* 重置用户密码 * 重置用户密码
@@ -172,11 +180,11 @@ public interface ISysUserService
/** /**
* 重置用户密码 * 重置用户密码
* *
* @param userName 用户 * @param userId 用户ID
* @param password 密码 * @param password 密码
* @return 结果 * @return 结果
*/ */
public int resetUserPwd(String userName, String password); public int resetUserPwd(Long userId, String password);
/** /**
* 通过用户ID删除用户 * 通过用户ID删除用户

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
@@ -15,7 +16,6 @@ import com.ruoyi.common.datascope.annotation.DataScope;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.domain.SysDept; import com.ruoyi.system.api.domain.SysDept;
import com.ruoyi.system.api.domain.SysRole; import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.domain.vo.TreeSelect; import com.ruoyi.system.domain.vo.TreeSelect;
import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.mapper.SysRoleMapper; import com.ruoyi.system.mapper.SysRoleMapper;
@@ -190,7 +190,7 @@ public class SysDeptServiceImpl implements ISysDeptService
@Override @Override
public void checkDeptDataScope(Long deptId) public void checkDeptDataScope(Long deptId)
{ {
if (!SysUser.isAdmin(SecurityUtils.getUserId()) && StringUtils.isNotNull(deptId)) if (!SecurityUtils.isAdmin() && StringUtils.isNotNull(deptId))
{ {
SysDept dept = new SysDept(); SysDept dept = new SysDept();
dept.setDeptId(deptId); dept.setDeptId(deptId);
@@ -281,6 +281,32 @@ public class SysDeptServiceImpl implements ISysDeptService
} }
} }
/**
* 保存部门排序
*
* @param deptIds 部门ID数组
* @param orderNums 排序数组
*/
@Override
@Transactional
public void updateDeptSort(String[] deptIds, String[] orderNums)
{
try
{
for (int i = 0; i < deptIds.length; i++)
{
SysDept dept = new SysDept();
dept.setDeptId(Convert.toLong(deptIds[i]));
dept.setOrderNum(Convert.toInt(orderNums[i]));
deptMapper.updateDeptSort(dept);
}
}
catch (Exception e)
{
throw new ServiceException("保存排序异常,请联系管理员");
}
}
/** /**
* 删除部门管理信息 * 删除部门管理信息
* *

View File

@@ -8,14 +8,18 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.domain.SysRole; import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.domain.SysMenu; import com.ruoyi.system.domain.SysMenu;
import com.ruoyi.system.domain.vo.MetaVo; import com.ruoyi.system.domain.vo.MetaVo;
import com.ruoyi.system.domain.vo.RouterVo; import com.ruoyi.system.domain.vo.RouterVo;
@@ -33,8 +37,12 @@ import com.ruoyi.system.service.ISysMenuService;
@Service @Service
public class SysMenuServiceImpl implements ISysMenuService public class SysMenuServiceImpl implements ISysMenuService
{ {
private static final Logger log = LoggerFactory.getLogger(SysMenuServiceImpl.class);
public static final String PREMISSION_STRING = "perms[\"{0}\"]"; public static final String PREMISSION_STRING = "perms[\"{0}\"]";
public static final Long MENU_ROOT_ID = 0L;
@Autowired @Autowired
private SysMenuMapper menuMapper; private SysMenuMapper menuMapper;
@@ -67,7 +75,7 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
List<SysMenu> menuList = null; List<SysMenu> menuList = null;
// 管理员显示所有菜单信息 // 管理员显示所有菜单信息
if (SysUser.isAdmin(userId)) if (SecurityUtils.isAdmin(userId))
{ {
menuList = menuMapper.selectMenuList(menu); menuList = menuMapper.selectMenuList(menu);
} }
@@ -139,7 +147,7 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
menus = menuMapper.selectMenuTreeByUserId(userId); menus = menuMapper.selectMenuTreeByUserId(userId);
} }
return getChildPerms(menus, 0); return getChildPerms(menus, MENU_ROOT_ID);
} }
/** /**
@@ -194,7 +202,7 @@ public class SysMenuServiceImpl implements ISysMenuService
childrenList.add(children); childrenList.add(children);
router.setChildren(childrenList); router.setChildren(childrenList);
} }
else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) else if (menu.getParentId().intValue() == MENU_ROOT_ID && isInnerLink(menu))
{ {
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
router.setPath("/"); router.setPath("/");
@@ -316,6 +324,32 @@ public class SysMenuServiceImpl implements ISysMenuService
return menuMapper.updateMenu(menu); return menuMapper.updateMenu(menu);
} }
/**
* 保存菜单排序
*
* @param menuIds 菜单ID
* @param orderNums 排序ID
*/
@Override
@Transactional
public void updateMenuSort(String[] menuIds, String[] orderNums)
{
try
{
for (int i = 0; i < menuIds.length; i++)
{
SysMenu menu = new SysMenu();
menu.setMenuId(Convert.toLong(menuIds[i]));
menu.setOrderNum(Convert.toInt(orderNums[i]));
menuMapper.updateMenuSort(menu);
}
}
catch (Exception e)
{
throw new ServiceException("保存排序异常,请联系管理员");
}
}
/** /**
* 删除菜单管理信息 * 删除菜单管理信息
* *
@@ -346,6 +380,47 @@ public class SysMenuServiceImpl implements ISysMenuService
return UserConstants.UNIQUE; return UserConstants.UNIQUE;
} }
/**
* 校验路由名称是否唯一
*
* @param menu 菜单信息
* @return 结果
*/
@Override
public boolean checkRouteConfigUnique(SysMenu menu)
{
Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
Long parentId = menu.getParentId();
String path = menu.getPath();
String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName();
List<SysMenu> sysMenuList = menuMapper.selectMenusByPathOrRouteName(path, routeName);
for (SysMenu sysMenu : sysMenuList)
{
if (sysMenu.getMenuId().longValue() != menuId.longValue())
{
Long dbParentId = sysMenu.getParentId();
String dbPath = sysMenu.getPath();
String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName();
if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == dbParentId.longValue())
{
log.warn("[同级路由冲突] 同级下已存在相同路由路径 '{}',冲突菜单:{}", dbPath, sysMenu.getMenuName());
return UserConstants.NOT_UNIQUE;
}
else if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == MENU_ROOT_ID)
{
log.warn("[根目录路由冲突] 根目录下路由 '{}' 必须唯一,已被菜单 '{}' 占用", path, sysMenu.getMenuName());
return UserConstants.NOT_UNIQUE;
}
else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName))
{
log.warn("[路由名称冲突] 路由名称 '{}' 需全局唯一,已被菜单 '{}' 使用", routeName, sysMenu.getMenuName());
return UserConstants.NOT_UNIQUE;
}
}
}
return UserConstants.UNIQUE;
}
/** /**
* 获取路由名称 * 获取路由名称
* *
@@ -385,12 +460,12 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
String routerPath = menu.getPath(); String routerPath = menu.getPath();
// 内链打开外网方式 // 内链打开外网方式
if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) if (menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu))
{ {
routerPath = innerLinkReplaceEach(routerPath); routerPath = innerLinkReplaceEach(routerPath);
} }
// 非外链并且是一级目录(类型为目录) // 非外链并且是一级目录(类型为目录)
if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) if (MENU_ROOT_ID == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
&& UserConstants.NO_FRAME.equals(menu.getIsFrame())) && UserConstants.NO_FRAME.equals(menu.getIsFrame()))
{ {
routerPath = "/" + menu.getPath(); routerPath = "/" + menu.getPath();
@@ -416,7 +491,7 @@ public class SysMenuServiceImpl implements ISysMenuService
{ {
component = menu.getComponent(); component = menu.getComponent();
} }
else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu))
{ {
component = UserConstants.INNER_LINK; component = UserConstants.INNER_LINK;
} }
@@ -435,10 +510,21 @@ public class SysMenuServiceImpl implements ISysMenuService
*/ */
public boolean isMenuFrame(SysMenu menu) public boolean isMenuFrame(SysMenu menu)
{ {
return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) return menu.getParentId().intValue() == MENU_ROOT_ID && UserConstants.TYPE_MENU.equals(menu.getMenuType())
&& menu.getIsFrame().equals(UserConstants.NO_FRAME); && menu.getIsFrame().equals(UserConstants.NO_FRAME);
} }
/**
* 是否为parent_view组件
*
* @param menu 菜单信息
* @return 结果
*/
public boolean isParentView(SysMenu menu)
{
return menu.getParentId().intValue() != MENU_ROOT_ID && UserConstants.TYPE_DIR.equals(menu.getMenuType());
}
/** /**
* 是否为内链组件 * 是否为内链组件
* *
@@ -450,17 +536,6 @@ public class SysMenuServiceImpl implements ISysMenuService
return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());
} }
/**
* 是否为parent_view组件
*
* @param menu 菜单信息
* @return 结果
*/
public boolean isParentView(SysMenu menu)
{
return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());
}
/** /**
* 根据父节点的ID获取所有子节点 * 根据父节点的ID获取所有子节点
* *
@@ -468,7 +543,7 @@ public class SysMenuServiceImpl implements ISysMenuService
* @param parentId 传入的父节点ID * @param parentId 传入的父节点ID
* @return String * @return String
*/ */
public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) public List<SysMenu> getChildPerms(List<SysMenu> list, long parentId)
{ {
List<SysMenu> returnList = new ArrayList<SysMenu>(); List<SysMenu> returnList = new ArrayList<SysMenu>();
for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();) for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)

View File

@@ -0,0 +1,83 @@
package com.ruoyi.system.service.impl;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.domain.SysNoticeRead;
import com.ruoyi.system.mapper.SysNoticeReadMapper;
import com.ruoyi.system.service.ISysNoticeReadService;
/**
* 公告已读记录 服务层实现
*
* @author ruoyi
*/
@Service
public class SysNoticeReadServiceImpl implements ISysNoticeReadService
{
@Autowired
private SysNoticeReadMapper noticeReadMapper;
/**
* 标记已读
*/
@Override
public void markRead(Long noticeId, Long userId)
{
SysNoticeRead record = new SysNoticeRead();
record.setNoticeId(noticeId);
record.setUserId(userId);
noticeReadMapper.insertNoticeRead(record);
}
/**
* 查询某用户未读公告数量
*/
@Override
public int selectUnreadCount(Long userId)
{
return noticeReadMapper.selectUnreadCount(userId);
}
/**
* 查询公告列表并标记当前用户已读状态
*/
@Override
public List<SysNotice> selectNoticeListWithReadStatus(Long userId, int limit)
{
return noticeReadMapper.selectNoticeListWithReadStatus(userId, limit);
}
/**
* 批量标记已读
*/
@Override
public void markReadBatch(Long userId, Long[] noticeIds)
{
if (noticeIds == null || noticeIds.length == 0)
{
return;
}
noticeReadMapper.insertNoticeReadBatch(userId, noticeIds);
}
/**
* 查询已阅读某公告的用户列表
*/
@Override
public List<Map<String, Object>> selectReadUsersByNoticeId(Long noticeId, String searchValue)
{
return noticeReadMapper.selectReadUsersByNoticeId(noticeId, searchValue);
}
/**
* 删除公告时清理对应已读记录
*/
@Override
public void deleteByNoticeIds(Long[] noticeIds)
{
noticeReadMapper.deleteByNoticeIds(noticeIds);
}
}

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.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.api.domain.SysRole; import com.ruoyi.system.api.domain.SysRole;
@@ -41,7 +42,7 @@ public class SysPermissionServiceImpl implements ISysPermissionService
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin())
{ {
roles.add("admin"); roles.add(Constants.SUPER_ADMIN);
} }
else else
{ {
@@ -63,7 +64,7 @@ public class SysPermissionServiceImpl implements ISysPermissionService
// 管理员拥有所有权限 // 管理员拥有所有权限
if (user.isAdmin()) if (user.isAdmin())
{ {
perms.add("*:*:*"); perms.add(Constants.ALL_PERMISSION);
} }
else else
{ {

View File

@@ -15,7 +15,6 @@ import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.datascope.annotation.DataScope; import com.ruoyi.common.datascope.annotation.DataScope;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.domain.SysRole; import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.domain.SysRoleDept; import com.ruoyi.system.domain.SysRoleDept;
import com.ruoyi.system.domain.SysRoleMenu; import com.ruoyi.system.domain.SysRoleMenu;
import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.domain.SysUserRole;
@@ -197,7 +196,7 @@ public class SysRoleServiceImpl implements ISysRoleService
@Override @Override
public void checkRoleDataScope(Long... roleIds) public void checkRoleDataScope(Long... roleIds)
{ {
if (!SysUser.isAdmin(SecurityUtils.getUserId())) if (!SecurityUtils.isAdmin())
{ {
for (Long roleId : roleIds) for (Long roleId : roleIds)
{ {

View File

@@ -238,7 +238,7 @@ public class SysUserServiceImpl implements ISysUserService
@Override @Override
public void checkUserDataScope(Long userId) public void checkUserDataScope(Long userId)
{ {
if (!SysUser.isAdmin(SecurityUtils.getUserId())) if (!SecurityUtils.isAdmin())
{ {
SysUser user = new SysUser(); SysUser user = new SysUser();
user.setUserId(userId); user.setUserId(userId);
@@ -326,7 +326,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());
} }
/** /**
@@ -344,14 +344,25 @@ public class SysUserServiceImpl implements ISysUserService
/** /**
* 修改用户头像 * 修改用户头像
* *
* @param userName 用户 * @param userId 用户ID
* @param avatar 头像地址 * @param avatar 头像地址
* @return 结果 * @return 结果
*/ */
@Override @Override
public boolean updateUserAvatar(String userName, String avatar) public boolean updateUserAvatar(Long userId, String avatar)
{ {
return userMapper.updateUserAvatar(userName, avatar) > 0; return userMapper.updateUserAvatar(userId, avatar) > 0;
}
/**
* 更新用户登录信息IP和登录时间
*
* @param user 用户信息
* @return 结果
*/
public boolean updateLoginInfo(SysUser user)
{
return userMapper.updateLoginInfo(user) > 0;
} }
/** /**
@@ -363,20 +374,20 @@ 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());
} }
/** /**
* 重置用户密码 * 重置用户密码
* *
* @param userName 用户 * @param userId 用户ID
* @param password 密码 * @param password 密码
* @return 结果 * @return 结果
*/ */
@Override @Override
public int resetUserPwd(String userName, String password) public int resetUserPwd(Long userId, String password)
{ {
return userMapper.resetUserPwd(userName, password); return userMapper.resetUserPwd(userId, password);
} }
/** /**
@@ -517,6 +528,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

@@ -150,6 +150,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</update> </update>
<update id="updateDeptSort" parameterType="SysDept">
update sys_dept set order_num = #{orderNum} where dept_id = #{deptId}
</update>
<delete id="deleteDeptById" parameterType="Long"> <delete id="deleteDeptById" parameterType="Long">
update sys_dept set del_flag = '2' where dept_id = #{deptId} update sys_dept set del_flag = '2' where dept_id = #{deptId}
</delete> </delete>

View File

@@ -130,7 +130,12 @@
<select id="checkMenuNameUnique" parameterType="SysMenu" resultMap="SysMenuResult"> <select id="checkMenuNameUnique" parameterType="SysMenu" resultMap="SysMenuResult">
<include refid="selectMenuVo"/> <include refid="selectMenuVo"/>
where menu_name=#{menuName} and parent_id = #{parentId} limit 1 where menu_name= #{menuName} and parent_id = #{parentId} limit 1
</select>
<select id="selectMenusByPathOrRouteName" parameterType="SysMenu" resultMap="SysMenuResult">
<include refid="selectMenuVo"/>
where menu_type in ('M', 'C') and (path = #{path} or path = #{routeName} or route_name = #{path} or route_name = #{routeName})
</select> </select>
<update id="updateMenu" parameterType="SysMenu"> <update id="updateMenu" parameterType="SysMenu">
@@ -199,6 +204,10 @@
) )
</insert> </insert>
<update id="updateMenuSort" parameterType="SysMenu">
update sys_menu set order_num = #{orderNum} where menu_id = #{menuId}
</update>
<delete id="deleteMenuById" parameterType="Long"> <delete id="deleteMenuById" parameterType="Long">
delete from sys_menu where menu_id = #{menuId} delete from sys_menu where menu_id = #{menuId}
</delete> </delete>

View File

@@ -40,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND create_by like concat('%', #{createBy}, '%') AND create_by like concat('%', #{createBy}, '%')
</if> </if>
</where> </where>
order by notice_id desc
</select> </select>
<insert id="insertNotice" parameterType="SysNotice"> <insert id="insertNotice" parameterType="SysNotice">

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysNoticeReadMapper">
<resultMap type="SysNoticeRead" id="SysNoticeReadResult">
<id property="readId" column="read_id" />
<result property="noticeId" column="notice_id" />
<result property="userId" column="user_id" />
<result property="readTime" column="read_time" />
</resultMap>
<!-- 新增已读记录 -->
<insert id="insertNoticeRead" parameterType="SysNoticeRead">
insert ignore into sys_notice_read (notice_id, user_id, read_time)
values (#{noticeId}, #{userId}, sysdate())
</insert>
<!-- 查询未读数量:正常状态公告 减去 当前用户已读数 -->
<select id="selectUnreadCount" resultType="int">
select count(*) from sys_notice n
where n.status = '0' and not exists (select 1 from sys_notice_read r where r.notice_id = n.notice_id and r.user_id = #{userId})
</select>
<!-- 查询是否已读 -->
<select id="selectIsRead" resultType="int">
select count(*) from sys_notice_read where notice_id = #{noticeId} and user_id = #{userId}
</select>
<!-- 查询带已读状态的公告列表直接在SQL中限制条数 -->
<select id="selectNoticeListWithReadStatus" resultType="SysNotice">
select
n.notice_id as noticeId,
n.notice_title as noticeTitle,
n.notice_type as noticeType,
n.status,
n.create_by as createBy,
n.create_time as createTime,
case when r.notice_id is not null then true else false end as isRead
from sys_notice n
left join sys_notice_read r
on r.notice_id = n.notice_id and r.user_id = #{userId}
where n.status = '0'
order by n.notice_id desc
limit #{limit}
</select>
<!-- 批量标记已读 -->
<insert id="insertNoticeReadBatch">
insert ignore into sys_notice_read (notice_id, user_id, read_time)
values
<foreach collection="noticeIds" item="noticeId" separator=",">
(#{noticeId}, #{userId}, sysdate())
</foreach>
</insert>
<!-- 删除公告时清理已读记录 -->
<delete id="deleteByNoticeIds">
delete from sys_notice_read where notice_id in
<foreach collection="noticeIds" item="noticeId" open="(" separator="," close=")">
#{noticeId}
</foreach>
</delete>
<!-- 查询已阅读某公告的用户列表,支持按登录名/用户名模糊筛选 -->
<select id="selectReadUsersByNoticeId" resultType="java.util.Map">
select
u.user_id as userId,
u.user_name as userName,
u.nick_name as nickName,
d.dept_name as deptName,
u.phonenumber as phonenumber,
r.read_time as readTime
from sys_notice_read r
inner join sys_user u on u.user_id = r.user_id and u.del_flag = '0'
left join sys_dept d on d.dept_id = u.dept_id
where r.notice_id = #{noticeId}
<if test="searchValue != null and searchValue != ''">
and (
u.user_name like concat('%', #{searchValue}, '%')
or u.nick_name like concat('%', #{searchValue}, '%')
)
</if>
order by r.read_time desc
</select>
</mapper>

View File

@@ -19,13 +19,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="loginIp" column="login_ip" /> <result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" /> <result property="loginDate" column="login_date" />
<result property="pwdUpdateDate" column="pwd_update_date" /> <result property="pwdUpdateDate" column="pwd_update_date" />
<result property="createBy" column="create_by" /> <result property="createBy" column="create_by" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<association property="dept" javaType="SysDept" resultMap="deptResult" /> <association property="dept" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" /> <collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
</resultMap> </resultMap>
<resultMap id="deptResult" type="SysDept"> <resultMap id="deptResult" type="SysDept">
@@ -48,7 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectUserVo"> <sql id="selectUserVo">
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark, select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.update_by, u.update_time, u.remark,
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
from sys_user u from sys_user u
@@ -180,7 +180,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateUser" parameterType="SysUser"> <update id="updateUser" parameterType="SysUser">
update sys_user update sys_user
<set> <set>
<if test="deptId != null and deptId != 0">dept_id = #{deptId},</if> <if test="deptId != 0">dept_id = #{deptId},</if>
<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if> <if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
<if test="email != null ">email = #{email},</if> <if test="email != null ">email = #{email},</if>
<if test="phonenumber != null ">phonenumber = #{phonenumber},</if> <if test="phonenumber != null ">phonenumber = #{phonenumber},</if>
@@ -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_name = #{userName} 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_name = #{userName} 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.6.6", "version": "3.6.8",
"description": "若依管理系统", "description": "若依管理系统",
"author": "若依", "author": "若依",
"license": "MIT", "license": "MIT",
@@ -25,7 +25,7 @@
}, },
"dependencies": { "dependencies": {
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",
"axios": "0.28.1", "axios": "0.30.3",
"clipboard": "2.0.8", "clipboard": "2.0.8",
"core-js": "3.37.1", "core-js": "3.37.1",
"echarts": "5.4.0", "echarts": "5.4.0",
@@ -40,7 +40,6 @@
"quill": "2.0.2", "quill": "2.0.2",
"screenfull": "5.0.2", "screenfull": "5.0.2",
"sortablejs": "1.10.2", "sortablejs": "1.10.2",
"splitpanes": "2.4.1",
"vue": "2.6.12", "vue": "2.6.12",
"vue-count-to": "1.0.13", "vue-count-to": "1.0.13",
"vue-cropper": "0.5.5", "vue-cropper": "0.5.5",

Some files were not shown because too many files have changed in this diff Show More