Compare commits

...

66 Commits

Author SHA1 Message Date
47c6a9ba6a 代码生成时判断del_flag存在自动生成逻辑删除代码 issues/4196 2022-11-16 22:39:58 +08:00
3e96c7e376 删除不需要的引用 2022-11-16 18:31:47 +08:00
1a902d94c9 优化vue3代码生成器模板子表高度样式 2022-11-16 18:18:22 +08:00
fb1cef7cd3 修复Apache Shiro 身份认证绕过漏洞 (CVE-2022-40664) issues/4222 2022-11-14 11:07:59 +08:00
83f909daca online报表参数无效 【jeecgboot-vue3/issues/225】 2022-11-11 14:07:47 +08:00
036b51cee6 无用的接口删掉 2022-11-08 22:12:53 +08:00
8da82d8269 vue3版代码生成popup字段未转为驼峰导致,选择值带不上 2022-11-08 22:11:35 +08:00
c981c45a14 生成的一对多代码,tab切换样式问题 2022-11-07 15:18:33 +08:00
91848b98b7 避免用户自定义表无默认字段创建时间,导致排序报错 2022-11-07 14:45:59 +08:00
ec262cff53 避免用户自定义表无默认字段创建时间,导致排序报错 2022-11-07 14:35:37 +08:00
9745b7e3ee 代码生成器模板优化,主要与内部版本同步 2022-11-07 13:41:28 +08:00
97736e4b5d [issues/4142] exlce模板导出如果模板中有多个合并单元格的循环表格,第二个表格读取错
[issues/3369] Excel导入 带公式的时候精度丢失
2022-11-07 10:48:33 +08:00
12991c83cb vue3版,配置聚合路由的地址时,报系统已存在该值的错误 2022-11-06 22:26:09 +08:00
2a99fa2ecb 针对字典sql,加入sql注入check和sql黑名单check 2022-11-06 17:51:47 +08:00
4a1ed660ca issues/4128 sql injection 2022-11-06 17:36:48 +08:00
8632a835c2 【#4127】sql漏洞写法修复 2022-11-06 17:03:57 +08:00
958cf01649 【#4127】sql漏洞写法修复 2022-11-06 17:03:48 +08:00
01602bd60a 单体升级微服务最新版本3.4.3,启动jeecg-demo-cloud-start失败 #4190 2022-11-06 16:46:56 +08:00
14c69fa533 企业微信官方通讯录同步接口调整 #4058 2022-11-03 09:36:49 +08:00
9f6c89a983 修改类说明 2022-11-02 23:35:56 +08:00
51e2227bfe /sys/user/putRecycleBin is affected by sql injection #4126
/sys/user/deleteRecycleBin is affected by sql injection #4125
2022-11-02 23:07:06 +08:00
ff77973a6c 上传接口对文件类型进行限制,避免恶意脚本上传攻击风险 2022-11-02 23:01:16 +08:00
f18ced524c sql注入检查更加严格,修复/sys/duplicate/check存在sql注入漏洞 #4129 2022-11-02 09:53:38 +08:00
d34614c422 UPDATE README-EN.md 2022-11-01 14:45:40 +08:00
c5e54f66a0 UPDATE README-EN.md 2022-11-01 14:43:02 +08:00
422dea8ddf README-EN.md 2022-11-01 14:34:19 +08:00
31ba9e8343 源码下载文档调整 2022-10-31 14:15:39 +08:00
5a303dfc4a 源码下载文档调整 2022-10-31 14:13:50 +08:00
d7269976e9 3.4.3-GA阶段性版本!又升级了,重要的事情说三遍,升级 Online 前端依赖,解决了几个很棒的功能! 2022-10-31 09:37:32 +08:00
3eab5ee9e6 online表单生成的预览之后放在菜单表单里面不可以用 (Issue #201)
【原因:vue3版online隐藏路由菜单与vue2未保持一致】
2022-10-30 17:51:40 +08:00
fb8afcda7d 解决两个问题
1 online表单开发代码生成选择Vue3风格,生成的代码却是vue2的 issues/4151
2 GUI代码生成器 issues/4150
2022-10-27 12:40:08 +08:00
feb7dddd65 关闭gitee的issue通道,问题反馈请在github发issue 2022-10-27 11:08:37 +08:00
ab81c8d3a7 [issues/4117]online代码生成Tab风格,新增数据中的字表tab页面错乱 2022-10-25 22:26:01 +08:00
7c1daee18c 文档更新 2022-10-25 17:25:29 +08:00
578940aa40 文档更新 2022-10-25 17:24:25 +08:00
92ea7282e6 升级积木到最新版本1.5.4 2022-10-25 14:01:02 +08:00
4667e9db40 online代码生成,支持选择那种前端代码生成(vue3\vue2\vue3原生) 2022-10-25 12:00:10 +08:00
5558c30223 博客地址更新 2022-10-19 15:01:48 +08:00
97842dc619 技术支持 2022-10-19 14:16:49 +08:00
a7c3e6e3dd 调整文档 2022-10-19 11:25:14 +08:00
3c489e3d98 文档更新 2022-10-18 10:50:59 +08:00
951c7ebc79 APP端开发框架,源码下载地址 2022-10-18 10:44:22 +08:00
c321002504 3.4.3 版本发布,低代码功能专项升级(新版数据库) 2022-10-17 14:40:58 +08:00
708579b36d 3.4.3 版本发布,低代码功能专项升级 2022-10-17 14:06:08 +08:00
949f1e8e33 3.4.3 版本发布,低代码功能专项升级 2022-10-17 10:48:49 +08:00
1725fc56f3 3.4.3 版本发布,低代码功能专项升级 2022-10-14 19:17:12 +08:00
ee493c2728 3.4.3 版本发布,低代码功能专项升级 2022-10-14 19:16:48 +08:00
af68147848 3.4.3 版本发布,低代码功能专项升级 2022-10-14 19:16:44 +08:00
5dab553e3b 3.4.3 版本发布,低代码功能专项升级 2022-10-14 17:47:14 +08:00
6aa9fff66d 3.4.3 版本发布,低代码功能专项升级 2022-10-14 17:14:12 +08:00
63af9e4ca9 项目介绍 2022-10-13 09:30:37 +08:00
3e146061dd 解决doMultiFieldsOrder() 多字段排序方法存在问题,没有读取 MybatisPlus 注解 @TableField 里 value 的值。 2022-10-10 11:18:34 +08:00
3580f50b07 【jeecg-boot/issues/I5U0OV】修复合并最新代码,Gateway服务起不来 2022-10-08 10:02:21 +08:00
99e5160436 修复bug 2022-09-29 13:52:24 +08:00
5c00114d7c Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot 2022-09-29 13:49:46 +08:00
394c0e2caf Merge pull request #4015 from wang1223440313/master
【修复+优化】系统字典数据应该包括自定义的java类-枚举,扫描全部类修改为只扫描枚举类
2022-09-29 11:56:23 +08:00
cb8adfb236 Merge pull request #4001 from xiaohundun/master
sentinel nacos 支持指定命名空间
2022-09-29 11:52:32 +08:00
d5ef6106db 127 【轻量级 PR】:修复使用带命名空间启动网关swagger看不到接口文档的问题 2022-09-29 10:00:37 +08:00
04486dca56 修复 Monitor 无法使用--spring.profiles.active 方式,使用配置文件。 2022-09-29 09:48:16 +08:00
c78c5dda4d 修复yml文件格式不规范导致的打包启动问题 2022-09-29 09:33:00 +08:00
06bea53726 star his 2022-09-28 14:22:27 +08:00
fbfe4962b3 nacos log4j 是2.13.3版本版本,存在漏洞 2022-09-27 21:01:16 +08:00
28f007c13c 问题 oss外链经过转编码后,部分无效,大概在三分一;无需转编码直接返回即可 #4023
https://github.com/jeecgboot/jeecg-boot/issues/4023
2022-09-27 20:54:01 +08:00
ae74abe727 在线报表不支持子查询,解析报错 #4040
https://github.com/jeecgboot/jeecg-boot/issues/4040
2022-09-27 20:30:53 +08:00
53b670befd 系统字典数据应该包括自定义的java类-枚举,修改为只扫描枚举类 2022-08-31 11:42:51 +08:00
7690f0798a sentinel nacos 支持指定命名空间 2022-08-26 15:49:09 +08:00
115 changed files with 13656 additions and 4671 deletions

489
README-EN.md Normal file
View File

@ -0,0 +1,489 @@
![JEECG](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/logov3.png "JeecgBoot低代码开发平台")
JEECG BOOT Low Code Development Platform
===============
The Latest Version 3.4.3-GARelease date2022-10-31
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-GuojuSoftWare-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/Blog-blog-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.4.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
Project introduction
-----------------------------------
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
JeecgBoot is a `low code development platform` based on code `generators`! Front and back end separation architecture SpringBoot2.x, SpringCloud, Ant Design&Vue, Mybatis plus, Shiro, JWT, support for microservices. The powerful code generator makes the front and back end of the code generation, low code development! JeecgBoot leads a new low-code development paradigm (OnlineCoding-> Code Generator -> Manual MERGE) that helps resolve 70% of the duplication in Java projects and makes development more business-focused. Not only can quickly improve efficiency, save research and development costs, but also do not lose flexibility!
JeecgBoot provides a series of low code modules to make Online development truly zero code: Online form development, online reports, report configuration capabilities, online chart design, large screen design, mobile configuration capabilities, form designer, online design flow, process automation configuration, plug-in capabilities (pluggable) and more!
The purpose of JEECG is: simple functions are implemented by OnlineCoding configuration, so that zero code development; Complex functions are generated by code generator and manually Merge to achieve low code development, which ensures both intelligence and flexibility. The implementation of low code development and support flexible coding at the same time, to solve the current low code products are generally not flexible drawbacks!
JEECG Business process: Using workflow to implement and extend the task interface for developing and writing business logic, forms provides a variety of solutions: form designer, online configuration form, and coding form. At the same time, the separation design of process and form (loose coupling) is realized, and the flexible configuration of task nodes is supported, which not only ensures the confidentiality of the company's process, but also reduces the workload of developers.
Technical support
-----------------------------------
Problems or bugs in use can be found in [Making on the Issues](https://github.com/jeecgboot/jeecg-boot/issues/new)
Official Support: http://jeecg.com/doc/help
Download the source code
-----------------------------------
- The background source https://github.com/jeecgboot/jeecg-boot
- Front-end source (Vue3 version)https://github.com/jeecgboot/jeecgboot-vue3
- Front-end source (Vue2 version)https://github.com/jeecgboot/ant-design-vue-jeecg
- APP Supporting frameworkhttps://github.com/jeecgboot/jeecg-uniapp
##### Project description
| Project | description |
|--------------------|------------------------|
| `jeecg-boot` | SpringBoot background source code (support microservices) |
| `jeecgboot-vue3` | Vue3+TS new front-end source code|
| `ant-design-vue-jeecg` |Vue2 version front-end source code |
| `jeecg-uniapp` | APP development framework, a code multi terminal adaptation, and support APP, small program, H5 |
| `jeecg-boot-starter` | [Stater relies on the project to be maintained separately. Click Download](https://gitee.com/jeecg/jeecg-boot-starter) |
| `More` | [Download more source code](https://github.com/jeecgboot) |
For the project
-----------------------------------
Jeecg-Boot low code development platform can be applied in the development of any J2EE project, especially for SAAS projects, enterprise information management system (MIS), internal office system (OA), enterprise resource planning system (ERP), customer relationship management system (CRM), etc. Its semi-intelligent manual Merge development method, Can significantly improve the development efficiency of more than 70%, greatly reduce the development cost.
Docker starts the project
-----------------------------------
- [Docker starts the monomer background](http://doc.jeecg.com/2043889)
- [Docker starts the Vue3 front-end](http://vue3.jeecg.com/3028878)
- [Docker starts the micro-service background](http://doc.jeecg.com/3043472)
- [Docker starts the Vue2 front-end](http://doc.jeecg.com/3043612)
Technical documentation
-----------------------------------
- Website [http://www.jeecg.com](http://www.jeecg.com)
- Demo [Vue3](http://boot3.jeecg.com) | [Vue2](http://boot.jeecg.com)
- Doc [Vue3](http://vue3.jeecg.com) | [Main](http://doc.jeecg.com)
- Newbie guide [Quick start](http://www.jeecg.com/doc/quickstart) | [video](https://space.bilibili.com/454617261/channel/series) | [Q&A ](http://www.jeecg.com/doc/qa) | [help](http://jeecg.com/doc/help) | [1 minute experience](https://my.oschina.net/jeecg/blog/3083313)
- Microservice Development [Monomer upgrade to microservice](http://doc.jeecg.com/3043471)
- QQ group ⑥730954414、683903138、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full)
##### Star charts
[![Star History Chart](https://api.star-history.com/svg?repos=jeecgboot/jeecg-boot&type=Date)](https://star-history.com/#jeecgboot/jeecg-boot)
Background directory Structure
-----------------------------------
```
project structure
├─jeecg-boot-parent
│ ├─jeecg-boot-base-core
│ ├─jeecg-module-demo
│ ├─jeecg-module-system
│ │ ├─jeecg-system-biz
│ │ ├─jeecg-system-start system (8080
│ │ ├─jeecg-system-api
│ │ │ ├─jeecg-system-cloud-api
│ │ │ ├─jeecg-system-local-api
│ ├─jeecg-server-cloud
├─jeecg-cloud-gateway (9999)
├─jeecg-cloud-nacos --Nacos(8848)
├─jeecg-system-cloud-start --System(7001)
├─jeecg-demo-cloud-start --Demo(7002)
├─jeecg-visual
├─jeecg-cloud-monitor -- (9111)
├─jeecg-cloud-xxljob -- (9080)
├─jeecg-cloud-sentinel --sentinel (9000)
├─jeecg-cloud-test
├─jeecg-cloud-test-more
├─jeecg-cloud-test-rabbitmq
├─jeecg-cloud-test-seata
├─jeecg-cloud-test-shardingsphere
```
Why JeecgBoot?
-----------------------------------
* Adopt the latest mainstream front and back separation framework (Springboot+Mybatis+antd), easy to use; Code generator has low dependency, flexible expansion ability, and can quickly realize secondary development;
* Support microservices SpringCloud Alibaba(Nacos, Gateway, Sentinel, Skywalking), and provide switching mechanism to support free switching between single and microservices
* High development efficiency, using code generator, single table, tree list, one-to-many, one-to-one and other data models, add, delete, change and search function one-key generation, menu configuration directly use;
* Code generator provides powerful template mechanism, support custom template, currently provide four sets of style template (single table two sets, tree model one set, one to many three sets)
* Code generator is very intelligent, online business modeling, online configuration, WYSIWYG support 23 kinds of controls, a key to generate front and back end code, greatly improve the development efficiency, no longer worry about repeated work.
* Low code ability: Online online form (without coding, through online configuration of the form, to achieve the addition, deletion, change and check of the form, support single table, tree, one-to-many, one-to-one model, to achieve everyone can code)
* Low code ability: Online online report (without coding, through online configuration, to achieve data report, can quickly extract data, reduce development pressure, to achieve everyone can code)
* Low code ability: Online online chart (without coding, through online configuration, to achieve graphs, bar graphs, data reports, etc., support custom layout, to achieve everyone can code)
* Complete encapsulation of user, role, menu, organization, data dictionary, online scheduled tasks and other basic functions, support access authorization, button permission, data permission and other functions
* Commonly used common package, various tools (scheduled task, SMS interface, email sending,Excel import and export, etc.), basically meeting 80% of project requirements
* Easy Excel import and export, support single table export and one-to-many table mode export, generated code with import and export function
* Integrated simple report tools, image report and data export is very convenient, can be extremely convenient to generate graphical reports, pdf, excel, word and other reports;
* Before and after the separation technology, the page UI style is exquisite, for the commonly used components to do the encapsulation: time, row table control, interception display control, report component, editor and so on
* Query filter: query function automatically generated, the background dynamic spell SQL additional query conditions; Supports multiple matching modes (full matching, fuzzy query, included query, and unmatched query).
* Data permission (fine data permission control, control to row level, list level, form field level, realize different people see different data, different people operate different fields on the same page
* Page verification automatically generated (must be input, digital verification, amount verification, time and space, etc.);
* Support SAAS service model and provide SaaS multi-tenant architecture solution.
* Distributed file service, integration of minio, Ali OSS and other excellent third parties, to provide convenient file upload and management, but also support local storage.
* Mainstream database compatibility, a set of code is fully compatible with Mysql, Postgresql, Oracle, Sqlserver, MariaDB, dream and other mainstream databases.
* Integrate workflow activiti and realize only the configuration of flow direction in the page, which can greatly simplify the development of bpm workflow; Using bpm's process designer to draw the flow direction, a workflow is basically complete with a small amount of java code;
* Low code ability: online process design, using open source Activiti process engine, to achieve online drawing process, custom form, form attachment, business flow
* Multi-data source: its simple way of use, online configuration of data source configuration, convenient to grab data from other data;
* Provide single sign-on CAS integration solution, and complete docking code has been provided in the project
* Low code ability: form designer, support user custom form layout, support single table, one to many forms, support select, radio, checkbox, textarea, date, popup, list, macro and other controls
* Professional interface docking mechanism, unified using restful interface, integrated swagger-ui online interface documentation, Jwt token security verification, convenient client docking
* Interface security mechanism, can be refined control interface authorization, very simple to realize different clients only see their own data control
* Advanced combination query function, online configuration support primary and sub-table associated query, can save the query history
* Provide a variety of system monitoring, real-time tracking system running conditions (monitoring Redis, Tomcat, jvm, server information, request tracking, SQL monitoring)
* Message center (support SMS, email, wechat push, etc.)
* Integrate Websocket message notification mechanism
* Excellent mobile adaptive effect, providing APP release scheme:
* Support multiple languages and provide internationalization solutions;
* Data change record log, can record each change of data content, through the version comparison function to view historical changes
* The platform UI is powerful and mobile adaptation is implemented
* Platform home page style, provide a variety of combination mode, support custom style
* Provide easy to use print plug-in, support Google, Firefox, IE11+ and other browsers
* Rich sample code, provide a lot of learning case reference
* Using maven module development method
* Support dynamic menu routing
* RBAC (Role-Based Access Control) is used for permission control.
* Provide new row edit table JVXETable, easily meet a variety of complex ERP layout, with higher performance, more flexible extension, more powerful functions
Technical Architecture:
-----------------------------------
#### Development Environment
- Language: Java 8+ (less than 17)
- IDE(JAVA) : IDEA (lombok plug-in must be installed)
- IDE(front-end) : Vscode, WebStorm, IDEA
- Dependency management: Maven
- Cache: Redis
- Database: MySQL5.7 + & Oracle 11 g & Sqlserver2017 [More Databases](https://my.oschina.net/jeecg/blog/4905722)
#### backend
- Basic framework: Spring Boot 2.6.6
- Microservice framework: Spring Cloud Alibaba 2021.0.1.0
- Persistence layer framework: MybatisPlus 3.5.1
- Report tool: JimuReport 1.5.2
- Security framework: Apache Shiro 1.8.0, Jwt 3.11.0
- Microservice technology stack: Spring Cloud Alibaba, Nacos, Gateway, Sentinel, Skywalking
- Database connection pool: Alibaba Druid 1.1.22
- Log printing: logback
- Others: autopoi, fastjson, poi, Swagger-ui, quartz, lombok (simplified code), etc.
#### The front end
- Vue2 version`Vue2.6+@vue/cli+AntDesignVue+Viser-vue+Vuex` [detail](https://github.com/jeecgboot/ant-design-vue-jeecg)
- Vue3 version`Vue3.0+TypeScript+Vite+AntDesignVue+pinia+echarts` [detail](https://github.com/jeecgboot/jeecgboot-vue3)
#### Support library
| database | support |
| --- | --- |
| MySQL | √ |
| Oracle11g | √ |
| Sqlserver2017 | √ |
| PostgreSQL | √ |
| MariaDB | √ |
| 达梦、人大金仓 | √ |
## Microservice solutions
1. Service registration and discovery Nacos √
2. Nacos √
3. Route gateway gateway(Three loading modes) √
4. Distributed http feign √
5. fuse degrade current limiting Sentinel √
6. Distributed files Minio and Alioss √
7. Unified permission control
8. Service monitoring SpringBootAdmin√
9. link tracking Skywalking [reference document](http://doc.jeecg.com/2350293)
10. Messaging middleware RabbitMQ √
11. Distributed task xxl-job √
12. Distributed Transaction Seata
13. Distributed log elk + kafka
14. Support docker-compose, k8s, jenkins
15. CAS SSO √
16. Route traffic limiting √
#### Microservice architecture diagram
![微服务架构图](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/jeecgboot_springcloud2022.png "在这里输入图片标题")
### Jeecg Boot product functionality blueprint
![功能蓝图](https://jeecgos.oss-cn-beijing.aliyuncs.com/upload/test/Jeecg-Boot-lantu202005_1590912449914.jpg "在这里输入图片标题")
### Function module
```
├─系统管理
│ ├─用户管理
│ ├─角色管理
│ ├─菜单管理
│ ├─权限设置(支持按钮权限、数据权限)
│ ├─表单权限(控制字段禁用、隐藏)
│ ├─部门管理
│ ├─我的部门(二级管理员)
│ └─字典管理
│ └─分类字典
│ └─系统公告
│ └─职务管理
│ └─通讯录
│ └─多租户管理
├─消息中心
│ ├─消息管理
│ ├─模板管理
├─代码生成器(低代码)
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
│ ├─代码生成器模板提供4套模板分别支持单表和一对多模型不同风格选择
│ ├─代码生成器模板生成代码自带excel导入导出
│ ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)
│ ├─高级查询器(弹窗自动组合查询条件)
│ ├─Excel导入导出工具集成支持单表一对多 导入导出)
│ ├─平台移动自适应支持
├─系统监控
│ ├─Gateway路由网关
│ ├─性能扫描监控
│ │ ├─监控 Redis
│ │ ├─Tomcat
│ │ ├─jvm
│ │ ├─服务器信息
│ │ ├─请求追踪
│ │ ├─磁盘监控
│ ├─定时任务
│ ├─系统日志
│ ├─消息中心(支持短信、邮件、微信推送等等)
│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
│ ├─系统通知
│ ├─SQL监控
│ ├─swagger-ui(在线接口文档)
│─报表示例
│ ├─曲线图
│ └─饼状图
│ └─柱状图
│ └─折线图
│ └─面积图
│ └─雷达图
│ └─仪表图
│ └─进度条
│ └─排名列表
│ └─等等
│─大屏模板
│ ├─作战指挥中心大屏
│ └─物流服务中心大屏
│─常用示例
│ ├─自定义组件
│ ├─对象存储(对接阿里云)
│ ├─JVXETable示例各种复杂ERP布局示例
│ ├─单表模型例子
│ └─一对多模型例子
│ └─打印例子
│ └─一对多TAB例子
│ └─内嵌table例子
│ └─常用选择组件
│ └─异步树table
│ └─接口模拟测试
│ └─表格合计示例
│ └─异步树列表示例
│ └─一对多JEditable
│ └─JEditable组件示例
│ └─图片拖拽排序
│ └─图片翻页
│ └─图片预览
│ └─PDF预览
│ └─分屏功能
│─封装通用组件
│ ├─行编辑表格JEditableTable
│ └─省略显示组件
│ └─时间控件
│ └─高级查询
│ └─用户选择组件
│ └─报表组件封装
│ └─字典组件
│ └─下拉多选组件
│ └─选人组件
│ └─选部门组件
│ └─通过部门选人组件
│ └─封装曲线、柱状图、饼状图、折线图等等报表的组件(经过封装,使用简单)
│ └─在线code编辑器
│ └─上传文件组件
│ └─验证码组件
│ └─树列表组件
│ └─表单禁用组件
│ └─等等
│─更多页面模板
│ ├─各种高级表单
│ ├─各种列表效果
│ └─结果页面
│ └─异常页面
│ └─个人页面
├─高级功能
│ ├─系统编码规则
│ ├─提供单点登录CAS集成方案
│ ├─提供APP发布方案
│ ├─集成Websocket消息通知机制
├─Online在线开发(低代码)
│ ├─Online在线表单 - 功能已开放
│ ├─Online代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放
│ ├─Online在线图表(未开源)
│ ├─Online图表模板配置(未开源)
│ ├─Online布局设计(未开源)
│ ├─多数据源管理 - 功能已开放
├─积木报表设计器(低代码)
│ ├─打印设计器
│ ├─数据报表设计
│ ├─图形报表设计支持echart
│ ├─大屏设计器(未开源)
│─流程模块功能 (未开源)
│ ├─流程设计器
│ ├─表单设计器
├─大屏设计器
├─门户设计/仪表盘设计器
│ └─我的任务
│ └─历史流程
│ └─历史流程
│ └─流程实例管理
│ └─流程监听管理
│ └─流程表达式
│ └─我发起的流程
│ └─我的抄送
│ └─流程委派、抄送、跳转
│ └─。。。
│─OA办公组件 (未开源)
│ ├─更多功能
│ └─。。。
└─其他模块
└─更多功能开发中。。
```
Effect of system
----
##### Screen template
![输入图片说明](https://static.oschina.net/uploads/img/201912/25133248_Ag1C.jpg "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201912/25133301_k9Kc.jpg "在这里输入图片标题")
##### PC
![输入图片说明](https://static.oschina.net/uploads/img/201904/14155402_AmlV.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160657_cHwb.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160813_KmXS.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160935_Nibs.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14161004_bxQ4.png "在这里输入图片标题")
##### Online interface documentation
![输入图片说明](https://static.oschina.net/uploads/img/201908/27095258_M2Xq.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160957_hN3X.png "在这里输入图片标题")
##### Report
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160828_pkFr.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160834_Lo23.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160842_QK7B.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160849_GBm5.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160858_6RAM.png "在这里输入图片标题")
##### Process
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160623_8fwk.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160917_9Ftz.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160633_u59G.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201907/05165142_yyQ7.png "在这里输入图片标题")
##### App
![](https://oscimg.oschina.net/oscnet/da543c5d0d57baab0cecaa4670c8b68c521.jpg)
![](https://oscimg.oschina.net/oscnet/fda4bd82cab9d682de1c1fbf2060bf14fa6.jpg)
##### PAD
![](https://oscimg.oschina.net/oscnet/e90fef970a8c33790ab03ffd6c4c7cec225.jpg)
![](https://oscimg.oschina.net/oscnet/d78218803a9e856a0aa82b45efc49849a0c.jpg)
![](https://oscimg.oschina.net/oscnet/0404054d9a12647ef6f82cf9cfb80a5ac02.jpg)
![](https://oscimg.oschina.net/oscnet/59c23b230f52384e588ee16309b44fa20de.jpg)
## donation
If so, buy the author a cup of coffee ☺
![](https://static.oschina.net/uploads/img/201903/08155608_0EFX.png)

View File

@ -7,13 +7,13 @@
JEECG BOOT 低代码开发平台
===============
当前最新版本: 3.4.2发布日期2022-09-22
当前最新版本: 3.4.3-GA发布日期2022-10-31
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://my.oschina.net/jeecg)
[![](https://img.shields.io/badge/version-3.4.2-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.4.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
@ -34,42 +34,61 @@ JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
项目源码
技术支持
-----------------------------------
| 仓库 |前端 Vue3版 | 前端 Vue2版 | 后端源码 |
|-|-|-|-|
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [ant-design-vue-jeecg](https://github.com/jeecgboot/ant-design-vue-jeecg) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
| 码云 | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [ant-design-vue-jeecg](https://gitee.com/jeecg/ant-design-vue-jeecg) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
关闭gitee的issue通道使用中遇到问题或者BUG可以在 [Github上提Issues](https://github.com/jeecgboot/jeecg-boot/issues/new)
官方支持: http://jeecg.com/doc/help
源码下载
-----------------------------------
- 后台源码 https://github.com/jeecgboot/jeecg-boot
- 前端源码(Vue3版)https://github.com/jeecgboot/jeecgboot-vue3
- 前端源码(Vue2版)https://github.com/jeecgboot/ant-design-vue-jeecg
- APP配套框架https://github.com/jeecgboot/jeecg-uniapp
##### 项目说明
| 项目名 | 说明 |
|--------------------|------------------------|
| `jeecg-boot` | SpringBoot后台源码支持微服务 |
| `ant-design-vue-jeecg` |Vue2版前端源码 |
| `jeecgboot-vue3` | Vue3+Ts版前端源码 |
| `jeecg-boot-starter` | stater依赖项目单独维护 [下载地址](https://gitee.com/jeecg/jeecg-boot-starter) |
| `jeecgboot-vue3` | Vue3+TS 新版前端源码 |
| `ant-design-vue-jeecg` |Vue2版 前端源码 |
| `jeecg-uniapp` | APP开发框架一份代码多终端适配同时支持APP、小程序、H5 |
| `jeecg-boot-starter` | [Stater依赖项目单独维护点击下载](https://gitee.com/jeecg/jeecg-boot-starter) |
| `更多开源插件` | [更多源码下载](https://github.com/jeecgboot) |
##### Star走势图
![](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/star20220907.png)
适用项目
-----------------------------------
Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中尤其适合SAAS项目、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRM其半智能手工Merge的开发方式可以显著提高开发效率70%以上,极大降低开发成本。
Docker启动项目
-----------------------------------
- [Docker启动单体后台](http://doc.jeecg.com/2043889)
- [Docker启动Vue3前端](http://vue3.jeecg.com/3028878)
- [Docker启动微服务后台](http://doc.jeecg.com/3043472)
- [Docker启动Vue2前端](http://doc.jeecg.com/3043612)
技术文档
-----------------------------------
- 技术官网: [http://www.jeecg.com](http://www.jeecg.com)
- 在线演示 [Vue2版本](http://boot.jeecg.com) | [Vue3版本](http://boot3.jeecg.com)
- 在线演示 [Vue3版本](http://boot3.jeecg.com) | [Vue2版本](http://boot.jeecg.com)
- 开发文档: [主项目文档](http://doc.jeecg.com) | [Vue3文档](http://vue3.jeecg.com)
- 开发文档: [Vue3文档](http://vue3.jeecg.com) | [主项目文档](http://doc.jeecg.com)
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [常见问题 ](http://www.jeecg.com/doc/qa) | [技术支持](http://jeecg.com/doc/help) | [1分钟体验低代码](https://my.oschina.net/jeecg/blog/3083313)
@ -81,6 +100,13 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
##### Star走势图
[![Star History Chart](https://api.star-history.com/svg?repos=jeecgboot/jeecg-boot&type=Date)](https://star-history.com/#jeecgboot/jeecg-boot)
后台目录结构
-----------------------------------
```
@ -110,12 +136,6 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
├─jeecg-cloud-test-shardingsphere -- 微服务测试示例(分库分表)
```
Docker启动项目
-----------------------------------
- [Docker启动单体后台](http://doc.jeecg.com/2043889)
- [Docker启动微服务后台](http://doc.jeecg.com/3043472)
- [Docker启动Vue3前端](http://vue3.jeecg.com/3028878)
- [Docker启动Vue2前端](http://doc.jeecg.com/3043612)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
oracle导出编码 export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
导出用户: jeecgboot
导入命令: imp scott/tiger@orcl file=jeecgboot-oracle11g.dmp

View File

@ -1,102 +0,0 @@
-- 系统通知消息支持标星
ALTER TABLE `sys_announcement_send`
ADD COLUMN `star_flag` varchar(10) NULL COMMENT '标星状态( 1为标星 空/0没有标星)' AFTER `update_time`;
ALTER TABLE `sys_comment`
MODIFY COLUMN `comment_content` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '回复内容' AFTER `comment_id`;
ALTER TABLE `sys_data_log`
ADD COLUMN `type` varchar(20) NULL DEFAULT 'json' COMMENT '类型' AFTER `data_version`;
update sys_data_log set type = 'json';
-- vue3单表原生组件菜单
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1534418199197323265', '1438108197958311537', '单表原生示例', '/one/OneNativeList', 'demo/jeecg/Native/one/OneNativeList', 1, NULL, NULL, 1, NULL, '0', 13.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-06-08 14:13:01', 'admin', '2022-06-08 14:13:12', 0, 0, NULL, 0);
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `type`) VALUES ('1242298510024429569', '提醒方式', 'remindMode', '', 0, 'admin', '2020-03-24 11:53:40', 'admin', '2020-03-24 12:03:22', 0);
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('1242300779390357505', '1242298510024429569', '短信提醒', '2', '', 2, 1, 'admin', '2020-03-24 12:02:41', 'admin', '2020-03-30 18:21:33');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('1242300814383435777', '1242298510024429569', '邮件提醒', '1', '', 1, 1, 'admin', '2020-03-24 12:02:49', 'admin', '2020-03-30 18:21:26');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('1242300887343353857', '1242298510024429569', '系统消息', '4', '', 4, 1, 'admin', '2020-03-24 12:03:07', 'admin', '2020-03-30 18:21:43');
-- 删除vue3作废菜单
DELETE from sys_permission where component = 'demo/comp/upload/index';
-- vue3示例菜单缓存配置修正
UPDATE `sys_permission` SET `parent_id` = '1438108187455774722', `name` = '多级菜单', `url` = '/level', `component` = 'layouts/default/index', `is_route` = 1, `component_name` = NULL, `redirect` = '/level/menu1/menu1-1/menu1-1-1', `menu_type` = 0, `perms` = NULL, `perms_type` = '1', `sort_no` = 9.00, `always_show` = 0, `icon` = 'ion:menu-outline', `is_leaf` = 0, `keep_alive` = 0, `hidden` = 0, `hide_tab` = NULL, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = NULL, `update_time` = NULL, `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220418809857';
UPDATE `sys_permission` SET `parent_id` = '1438108220418809857', `name` = 'Menu1', `url` = '/level/menu1', `component` = NULL, `is_route` = 1, `component_name` = NULL, `redirect` = '/level/menu1/menu1-1/menu1-1-1', `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 0.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 0, `keep_alive` = 0, `hidden` = 0, `hide_tab` = NULL, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = NULL, `update_time` = NULL, `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220523667458';
UPDATE `sys_permission` SET `parent_id` = '1438108220418809857', `name` = 'Menu2', `url` = '/level/menu2', `component` = 'demo/level/Menu2', `is_route` = 1, `component_name` = 'Menu2Demo', `redirect` = NULL, `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 1.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 1, `keep_alive` = 1, `hidden` = 0, `hide_tab` = 0, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = 'admin', `update_time` = '2022-09-20 15:24:13', `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220724994049';
UPDATE `sys_permission` SET `parent_id` = '1438108220523667458', `name` = 'Menu1-1', `url` = '/level/menu1/menu1-1', `component` = NULL, `is_route` = 1, `component_name` = NULL, `redirect` = '/level/menu1/menu1-1/menu1-1-1', `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 0.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 0, `keep_alive` = 0, `hidden` = 0, `hide_tab` = NULL, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = NULL, `update_time` = NULL, `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220896960513';
UPDATE `sys_permission` SET `parent_id` = '1438108220896960513', `name` = 'Menu1-1-1', `url` = '/level/menu1/menu1-1/menu1-1-1', `component` = 'demo/level/Menu111', `is_route` = 1, `component_name` = 'Menu111Demo', `redirect` = NULL, `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 0.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 1, `keep_alive` = 1, `hidden` = 0, `hide_tab` = 0, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = 'admin', `update_time` = '2022-09-20 15:24:03', `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108221127647234';
UPDATE `sys_permission` SET `parent_id` = '1438108220523667458', `name` = 'Menu1-2', `url` = '/level/menu1/menu1-2', `component` = 'demo/level/Menu12', `is_route` = 1, `component_name` = 'Menu12Demo', `redirect` = NULL, `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 1.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 1, `keep_alive` = 1, `hidden` = 0, `hide_tab` = 0, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = 'admin', `update_time` = '2022-09-20 15:24:08', `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108221270253570';
-- 新增评论表和文件表
DROP TABLE IF EXISTS `sys_comment`;
CREATE TABLE `sys_comment` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`table_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '表名',
`table_data_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '数据id',
`from_user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '来源用户id',
`to_user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发送给用户id(允许为空)',
`comment_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '评论id(允许为空,不为空时,则为回复)',
`comment_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '回复内容',
`create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新日期',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_table_data_id`(`table_name`, `table_data_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统评论回复表' ROW_FORMAT = DYNAMIC;
DROP TABLE IF EXISTS `sys_files`;
CREATE TABLE `sys_files` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键id',
`file_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称',
`url` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件地址',
`file_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文档类型folder:文件夹 excel:excel doc:word ppt:ppt image:图片 archive:其他文档 video:视频 pdf:pdf',
`store_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件上传类型(temp/本地上传(临时文件) manage/知识库)',
`parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '父级id',
`tenant_id` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '租户id',
`file_size` double(13, 2) NULL DEFAULT NULL COMMENT '文件大小kb',
`iz_folder` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否文件夹(1是 0否)',
`iz_root_folder` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否为1级文件夹允许为空 (1是 )',
`iz_star` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否标星(1是 0否)',
`down_count` int(11) NULL DEFAULT NULL COMMENT '下载次数',
`read_count` int(11) NULL DEFAULT NULL COMMENT '阅读次数',
`share_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分享链接',
`share_perms` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分享权限(1.关闭分享 2.允许所有联系人查看 3.允许任何人查看)',
`enable_down` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否允许下载(1是 0否)',
`enable_updat` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否允许修改(1是 0否)',
`del_flag` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除状态(0-正常,1-删除至回收站)',
`create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人登录名称',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人登录名称',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新日期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '知识库-文档管理' ROW_FORMAT = DYNAMIC;
DROP TABLE IF EXISTS `sys_form_file`;
CREATE TABLE `sys_form_file` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`table_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '表名',
`table_data_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '数据id',
`file_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '关联文件id',
`file_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型(text:文本, excel:excel doc:word ppt:ppt image:图片 archive:其他文档 video:视频 pdf:pdf)',
`create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人登录名称',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建日期',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_table_form`(`table_name`, `table_data_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
ALTER TABLE `sys_files`
MODIFY COLUMN `tenant_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '租户id' AFTER `parent_id`;
ALTER TABLE `sys_files`
ADD INDEX `index_tenant_id`(`tenant_id`),
ADD INDEX `index_del_flag`(`del_flag`);
ALTER TABLE `sys_form_file`
ADD INDEX `index_file_id`(`file_id`);

View File

@ -0,0 +1,6 @@
ALTER TABLE `onl_cgform_field`
MODIFY COLUMN `field_show_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '表单控件类型' AFTER `dict_text`;
ALTER TABLE `onl_cgform_field`
ADD COLUMN `db_is_persist` tinyint(1) NULL COMMENT '是否需要同步数据库字段 1是0否' AFTER `db_is_null`;
update onl_cgform_field set db_is_persist = 1;

View File

@ -4,7 +4,7 @@
<parent>
<artifactId>jeecg-boot-parent</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -14,32 +14,33 @@ public enum CgformEnum {
/**
* 单表
*/
ONE(1, "one", "/jeecg/code-template-online", "default.one", "经典风格"),
/**
* 多表
*/
MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格"),
/**
* 多表jvxe风格
* */
JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "JVXE风格"),
ONE(1, "one", "/jeecg/code-template-online", "default.one", "经典风格", new String[]{"vue3","vue","vue3Native"}),
/**
* 多表
*/
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"),
MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格" ,new String[]{"vue"}),
/**
* 多表jvxe风格
* */
JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "JVXE风格" ,new String[]{"vue3","vue","vue3Native"}),
/**
* 多表 (erp风格)
*/
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue"}),
/**
* 多表(内嵌子表风格)
*/
INNER_TABLE(2, "innerTable", "/jeecg/code-template-online", "inner-table.onetomany", "内嵌子表风格"),
INNER_TABLE(2, "innerTable", "/jeecg/code-template-online", "inner-table.onetomany", "内嵌子表风格" ,new String[]{"vue3","vue"}),
/**
* 多表tab风格
* */
TAB(2, "tab", "/jeecg/code-template-online", "tab.onetomany", "Tab风格"),
TAB(2, "tab", "/jeecg/code-template-online", "tab.onetomany", "Tab风格" ,new String[]{"vue3","vue"}),
/**
* 树形列表
*/
TREE(3, "tree", "/jeecg/code-template-online", "default.tree", "树形列表");
TREE(3, "tree", "/jeecg/code-template-online", "default.tree", "树形列表" ,new String[]{"vue3","vue","vue3Native"});
/**
* 类型 1/单表 2/一对多 3/树
@ -61,6 +62,10 @@ public enum CgformEnum {
* 模板风格名称
*/
String note;
/**
* 支持代码风格 vue3:vue3包装代码 vue3Native:vue3原生代码 vue:vue2代码
*/
String[] vueStyle;
/**
* 构造器
@ -70,13 +75,15 @@ public enum CgformEnum {
* @param templatePath 模板路径
* @param stylePath 模板子路径
* @param note
* @param vueStyle 支持代码风格
*/
CgformEnum(int type, String code, String templatePath, String stylePath, String note) {
CgformEnum(int type, String code, String templatePath, String stylePath, String note, String[] vueStyle) {
this.type = type;
this.code = code;
this.templatePath = templatePath;
this.stylePath = stylePath;
this.note = note;
this.vueStyle = vueStyle;
}
/**
@ -114,6 +121,14 @@ public enum CgformEnum {
this.stylePath = stylePath;
}
public String[] getVueStyle() {
return vueStyle;
}
public void setVueStyle(String[] vueStyle) {
this.vueStyle = vueStyle;
}
/**
* 根据code找枚举
*

View File

@ -193,7 +193,7 @@ public class QueryGenerator {
}
}
// 排序逻辑 处理
doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap.keySet());
doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap);
//高级查询
doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
@ -229,7 +229,8 @@ public class QueryGenerator {
}
}
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Set<String> allFields) {
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Map<String,String> fieldColumnMap) {
Set<String> allFields = fieldColumnMap.keySet();
String column=null,order=null;
if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) {
column = parameterMap.get(ORDER_COLUMN)[0];
@ -237,7 +238,16 @@ public class QueryGenerator {
if(parameterMap!=null&& parameterMap.containsKey(ORDER_TYPE)) {
order = parameterMap.get(ORDER_TYPE)[0];
}
log.debug("排序规则>>列:" + column + ",排序方式:" + order);
log.info("排序规则>>列:" + column + ",排序方式:" + order);
//update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错
if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){
column = "id";
log.warn("检测到实体里没有字段createTime改成采用ID排序");
}
//update-end-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
if (oConvertUtils.isNotEmpty(column) && oConvertUtils.isNotEmpty(order)) {
//字典字段,去掉字典翻译文本后缀
if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
@ -252,6 +262,19 @@ public class QueryGenerator {
}
//update-end-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时使用SQL注入生效
//update-begin-author:scott date:2022-10-10 for:【jeecg-boot/issues/I5FJU6】doMultiFieldsOrder() 多字段排序方法存在问题
//多字段排序方法没有读取 MybatisPlus 注解 @TableField 里 value 的值
if (column.contains(",")) {
List<String> columnList = Arrays.asList(column.split(","));
String columnStrNew = columnList.stream().map(c -> fieldColumnMap.get(c)).collect(Collectors.joining(","));
if (oConvertUtils.isNotEmpty(columnStrNew)) {
column = columnStrNew;
}
}else{
column = fieldColumnMap.get(column);
}
//update-end-author:scott date:2022-10-10 for:【jeecg-boot/issues/I5FJU6】doMultiFieldsOrder() 多字段排序方法存在问题
//SQL注入check
SqlInjectionUtil.filterContent(column);

View File

@ -36,6 +36,12 @@ public class ResourceUtil {
*/
private final static String CLASS_PATTERN="/**/*.class";
/**
* 所有枚举java类
*/
private final static String CLASS_ENMU_PATTERN="/**/*Enum.class";
/**
* 包路径 org.jeecg
*/
@ -55,7 +61,7 @@ public class ResourceUtil {
return enumDictData;
}
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_PATTERN;
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_ENMU_PATTERN;
try {
Resource[] resources = resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

View File

@ -6,6 +6,7 @@ import org.jeecg.common.exception.JeecgBootException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@ -20,7 +21,7 @@ public class SqlInjectionUtil {
* (上线修改值 20200501同步修改前端的盐值
*/
private final static String TABLE_DICT_SIGN_SALT = "20200501";
private final static String XSS_STR = "and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
private final static String XSS_STR = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
/**
* 正则 user() 匹配更严谨
@ -29,6 +30,11 @@ public class SqlInjectionUtil {
/**正则 show tables*/
private final static String SHOW_TABLES = "show\\s+tables";
/**
* sql注释的正则
*/
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*.*\\*/");
/**
* 针对表字典进行额外的sign签名校验增加安全机制
* @param dictCode:
@ -66,10 +72,12 @@ public class SqlInjectionUtil {
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
value = value.replaceAll("/\\*.*\\*/","");
//value = value.replaceAll("/\\*.*\\*/","");
String[] xssArr = XSS_STR.split("\\|");
for (int i = 0; i < xssArr.length; i++) {
@ -117,10 +125,12 @@ public class SqlInjectionUtil {
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
value = value.replaceAll("/\\*.*\\*/","");
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1) {
@ -157,15 +167,17 @@ public class SqlInjectionUtil {
*/
//@Deprecated
public static void specialFilterContentForDictSql(String value) {
String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
String specialXssStr = " exec |extractvalue|updatexml| insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
value = value.replaceAll("/\\*.*\\*/","");
//value = value.replaceAll("/\\*.*\\*/","");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
@ -189,15 +201,17 @@ public class SqlInjectionUtil {
*/
//@Deprecated
public static void specialFilterContentForOnlineReport(String value) {
String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
String specialXssStr = " exec |extractvalue|updatexml| insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
}
// 校验sql注释 不允许有sql注释
checkSqlAnnotation(value);
// 统一转为小写
value = value.toLowerCase();
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
value = value.replaceAll("/\\*.*\\*/","");
//value = value.replaceAll("/\\*.*\\*/"," ");
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
@ -256,4 +270,17 @@ public class SqlInjectionUtil {
}
return true;
}
/**
* 校验是否有sql注释
* @return
*/
public static void checkSqlAnnotation(String str){
Matcher matcher = SQL_ANNOTATION.matcher(str);
if(matcher.find()){
String error = "请注意值可能存在SQL注入风险---> \\*.*\\";
log.error(error);
throw new RuntimeException(error);
}
}
}

View File

@ -10,7 +10,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Description: TODO
* @Description: 校验上传文件敏感后缀
* @author: lsq
* @date: 2021年08月09日 15:29
*/

View File

@ -119,10 +119,10 @@ public class oConvertUtils {
}
public static Integer[] getInts(String[] s) {
Integer[] integer = new Integer[s.length];
if (s == null) {
return null;
}
Integer[] integer = new Integer[s.length];
for (int i = 0; i < s.length; i++) {
integer[i] = Integer.parseInt(s[i]);
}

View File

@ -20,7 +20,6 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Date;
import java.util.UUID;
@ -299,10 +298,13 @@ public class OssBootUtil {
//update-end---author:liusq Date:20220120 for替换objectName前缀防止key不一致导致获取不到文件----
if(ossClient.doesObjectExist(bucketName,objectName)){
URL url = ossClient.generatePresignedUrl(bucketName,objectName,expires);
return URLDecoder.decode(url.toString(),"UTF-8");
//log.info("原始url : {}", url.toString());
//log.info("decode url : {}", URLDecoder.decode(url.toString(), "UTF-8"));
//【issues/4023】问题 oss外链经过转编码后部分无效大概在三分一无需转编码直接返回即可 #4023
return url.toString();
}
}catch (Exception e){
log.info("文件路径获取失败" + e.getMessage());
log.info("文件路径获取失败" + e.getMessage());
}
return null;
}

View File

@ -41,7 +41,14 @@ public abstract class AbstractQueryBlackListHandler {
* @return
*/
public boolean isPass(String sql) {
List<QueryTable> list = this.getQueryTableInfo(sql.toLowerCase());
List<QueryTable> list = null;
//【jeecg-boot/issues/4040】在线报表不支持子查询解析报错 #4040
try {
list = this.getQueryTableInfo(sql.toLowerCase());
} catch (Exception e) {
log.warn("校验sql语句解析报错{}",e.getMessage());
}
if(list==null){
return true;
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-boot-parent</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-system-api</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-system-api</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-module-system</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-module-system</artifactId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,10 @@ import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.dto.DataLogDTO;
import org.jeecg.common.api.dto.OnlineAuthDTO;
import org.jeecg.common.api.dto.message.*;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.*;
import org.jeecg.common.util.SqlInjectionUtil;
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.system.service.impl.SysBaseApiImpl;
import org.springframework.beans.factory.annotation.Autowired;
@ -30,6 +33,9 @@ public class SystemApiController {
@Autowired
private ISysUserService sysUserService;
@Autowired
private DictQueryBlackListHandler dictQueryBlackListHandler;
/**
* 发送系统消息
@ -521,6 +527,10 @@ public class SystemApiController {
*/
@GetMapping("/loadDictItem")
public List<String> loadDictItem(@RequestParam("dictCode") String dictCode, @RequestParam("keys") String keys) {
if(!dictQueryBlackListHandler.isPass(dictCode)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseApi.loadDictItem(dictCode, keys);
}
@ -533,6 +543,10 @@ public class SystemApiController {
*/
@GetMapping("/getDictItems")
public List<DictModel> getDictItems(@RequestParam("dictCode") String dictCode) {
if(!dictQueryBlackListHandler.isPass(dictCode)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseApi.getDictItems(dictCode);
}
@ -557,6 +571,10 @@ public class SystemApiController {
*/
@GetMapping("/loadDictItemByKeyword")
public List<DictModel> loadDictItemByKeyword(@RequestParam("dictCode") String dictCode, @RequestParam("keyword") String keyword, @RequestParam(value = "pageSize", required = false) Integer pageSize) {
if(!dictQueryBlackListHandler.isPass(dictCode)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseApi.loadDictItemByKeyword(dictCode, keyword, pageSize);
}
@ -581,6 +599,11 @@ public class SystemApiController {
*/
@GetMapping("/queryTableDictItemsByCode")
List<DictModel> queryTableDictItemsByCode(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseApi.queryTableDictItemsByCode(table, text, code);
}
@ -594,6 +617,14 @@ public class SystemApiController {
*/
@GetMapping("/queryFilterTableDictInfo")
List<DictModel> queryFilterTableDictInfo(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("filterSql") String filterSql){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
String[] arr = new String[]{table, text, code};
SqlInjectionUtil.filterContent(arr);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
return sysBaseApi.queryFilterTableDictInfo(table, text, code, filterSql);
}
@ -609,6 +640,11 @@ public class SystemApiController {
@Deprecated
@GetMapping("/queryTableDictByKeys")
public List<String> queryTableDictByKeys(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("keyArray") String[] keyArray){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return sysBaseApi.queryTableDictByKeys(table, text, code, keyArray);
}
@ -623,6 +659,13 @@ public class SystemApiController {
*/
@GetMapping("/translateDictFromTable")
public String translateDictFromTable(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("key") String key){
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
String[] arr = new String[]{table, text, code, key};
SqlInjectionUtil.filterContent(arr);
return sysBaseApi.translateDictFromTable(table, text, code, key);
}
@ -639,6 +682,11 @@ public class SystemApiController {
*/
@GetMapping("/translateDictFromTableByKeys")
public List<DictModel> translateDictFromTableByKeys(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("keys") String keys) {
String str = table+","+text+","+code;
if(!dictQueryBlackListHandler.isPass(str)){
log.error(dictQueryBlackListHandler.getError());
return null;
}
return this.sysBaseApi.translateDictFromTableByKeys(table, text, code, keys);
}
@ -697,4 +745,23 @@ public class SystemApiController {
public void sendAppChatSocket(@RequestParam(name="userId") String userId){
this.sysBaseApi.sendAppChatSocket(userId);
}
/**
* VUEN-2584【issue】平台sql注入漏洞几个问题
* 部分特殊函数 可以将查询结果混夹在错误信息中,导致数据库的信息暴露
* @param e
* @return
*/
@ExceptionHandler(java.sql.SQLException.class)
public Result<?> handleSQLException(Exception e){
String msg = e.getMessage();
String extractvalue = "extractvalue";
String updatexml = "updatexml";
if(msg!=null && (msg.toLowerCase().indexOf(extractvalue)>=0 || msg.toLowerCase().indexOf(updatexml)>=0)){
return Result.error("校验失败sql解析异常");
}
return Result.error("校验失败sql解析异常" + msg);
}
}

View File

@ -1,24 +1,20 @@
package org.jeecg.modules.system.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.RestUtil;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.filter.FileTypeFilter;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerMapping;
@ -27,7 +23,7 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
/**
* <p>
* 用户表 前端控制器
@ -66,7 +62,7 @@ public class CommonController {
* @return
*/
@PostMapping(value = "/upload")
public Result<?> upload(HttpServletRequest request, HttpServletResponse response) {
public Result<?> upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
Result<?> result = new Result<>();
String savePath = "";
String bizPath = request.getParameter("biz");
@ -93,6 +89,9 @@ public class CommonController {
}
}
if(CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)){
//update-begin-author:liusq date:20221102 for: 过滤上传文件类型
FileTypeFilter.fileTypeFilter(file);
//update-end-author:liusq date:20221102 for: 过滤上传文件类型
//update-begin-author:lvdandan date:20200928 for:修改JEditor编辑器本地上传
savePath = this.uploadLocal(file,bizPath);
//update-begin-author:lvdandan date:20200928 for:修改JEditor编辑器本地上传
@ -344,53 +343,4 @@ public class CommonController {
return new AntPathMatcher().extractPathWithinPattern(bestMatchPattern, path);
}
/**
* 中转HTTP请求解决跨域问题
*
* @param url 必填:请求地址
* @return
*/
@RequestMapping("/transitRESTful")
public Result transitRestful(@RequestParam("url") String url, HttpServletRequest request) {
try {
ServletServerHttpRequest httpRequest = new ServletServerHttpRequest(request);
// 中转请求method、body
HttpMethod method = httpRequest.getMethod();
JSONObject params;
try {
params = JSON.parseObject(JSON.toJSONString(httpRequest.getBody()));
} catch (Exception e) {
params = new JSONObject();
}
// 中转请求问号参数
JSONObject variables = JSON.parseObject(JSON.toJSONString(request.getParameterMap()));
variables.remove("url");
// 在 headers 里传递Token
String token = TokenUtils.getTokenByRequest(request);
HttpHeaders headers = new HttpHeaders();
headers.set("X-Access-Token", token);
// 发送请求
String httpUrl = URLDecoder.decode(url, "UTF-8");
ResponseEntity<String> response = RestUtil.request(httpUrl, method, headers , variables, params, String.class);
// 封装返回结果
Result<Object> result = new Result<>();
int statusCode = response.getStatusCodeValue();
result.setCode(statusCode);
result.setSuccess(statusCode == 200);
String responseBody = response.getBody();
try {
// 尝试将返回结果转为JSON
Object json = JSON.parse(responseBody);
result.setResult(json);
} catch (Exception e) {
// 转成JSON失败直接返回原始数据
result.setResult(responseBody);
}
return result;
} catch (Exception e) {
log.debug("中转HTTP请求失败", e);
return Result.error(e.getMessage());
}
}
}

View File

@ -309,7 +309,7 @@ public class SysDictController {
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/loadTreeData", method = RequestMethod.GET)
public Result<List<TreeSelectModel>> loadTreeData(@RequestParam(name="pid") String pid,@RequestParam(name="pidField") String pidField,
public Result<List<TreeSelectModel>> loadTreeData(@RequestParam(name="pid",required = false) String pid,@RequestParam(name="pidField") String pidField,
@RequestParam(name="tableName") String tbname,
@RequestParam(name="text") String text,
@RequestParam(name="code") String code,
@ -347,7 +347,12 @@ public class SysDictController {
Result<List<DictModel>> res = new Result<List<DictModel>>();
// SQL注入漏洞 sign签名校验
String dictCode = query.getTable()+","+query.getText()+","+query.getCode();
SqlInjectionUtil.filterContent(dictCode);
SqlInjectionUtil.filterContent(dictCode);
//update-begin-author:taoyan date:2022-11-4 for: issues/4128 sql injection
if(!dictQueryBlackListHandler.isPass(dictCode)){
return res.error500(dictQueryBlackListHandler.getError());
}
//update-end-author:taoyan date:2022-11-4 for: issues/4128 sql injection
List<DictModel> ls = this.sysDictService.queryDictTablePageList(query,pageSize,pageNo);
res.setResult(ls);
res.setSuccess(true);
@ -617,4 +622,21 @@ public class SysDictController {
}
}
/**
* VUEN-2584【issue】平台sql注入漏洞几个问题
* 部分特殊函数 可以将查询结果混夹在错误信息中,导致数据库的信息暴露
* @param e
* @return
*/
@ExceptionHandler(java.sql.SQLException.class)
public Result<?> handleSQLException(Exception e){
String msg = e.getMessage();
String extractvalue = "extractvalue";
String updatexml = "updatexml";
if(msg!=null && (msg.toLowerCase().indexOf(extractvalue)>=0 || msg.toLowerCase().indexOf(updatexml)>=0)){
return Result.error("校验失败sql解析异常");
}
return Result.error("校验失败sql解析异常" + msg);
}
}

View File

@ -1,18 +1,16 @@
package org.jeecg.modules.system.controller;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.constant.enums.RoleIndexConfigEnum;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.Md5Util;
import org.jeecg.common.util.oConvertUtils;
@ -393,6 +391,28 @@ public class SysPermissionController {
return result;
}
/**
* 检测菜单路径是否存在
* @param id
* @param url
* @return
*/
@RequestMapping(value = "/checkPermDuplication", method = RequestMethod.GET)
public Result<String> checkPermDuplication(@RequestParam(name = "id", required = false) String id, @RequestParam(name = "url") String url, @RequestParam(name = "alwaysShow") Boolean alwaysShow) {
Result<String> result = new Result<>();
try {
boolean check=sysPermissionService.checkPermDuplication(id,url,alwaysShow);
if(check){
return Result.ok("该值可用!");
}
return Result.error("该值不可用,系统中已存在!");
} catch (Exception e) {
log.error(e.getMessage(), e);
result.error500("操作失败");
}
return result;
}
/**
* 删除菜单
* @param id
@ -425,13 +445,21 @@ public class SysPermissionController {
String[] arr = ids.split(",");
for (String id : arr) {
if (oConvertUtils.isNotEmpty(id)) {
sysPermissionService.deletePermission(id);
try {
sysPermissionService.deletePermission(id);
} catch (JeecgBootException e) {
if(e.getMessage()!=null && e.getMessage().contains("未找到菜单信息")){
log.warn(e.getMessage());
}else{
throw e;
}
}
}
}
result.success("删除成功!");
} catch (Exception e) {
log.error(e.getMessage(), e);
result.error500("删除成功!");
result.error500("删除失败!");
}
return result;
}

View File

@ -72,15 +72,17 @@ public class ThirdAppController {
*/
@GetMapping("/sync/wechatEnterprise/user/toLocal")
public Result syncWechatEnterpriseUserToLocal(@RequestParam(value = "ids", required = false) String ids) {
if (thirdAppConfig.isWechatEnterpriseEnabled()) {
SyncInfoVo syncInfo = wechatEnterpriseService.syncThirdAppUserToLocal();
if (syncInfo.getFailInfo().size() == 0) {
return Result.OK("同步成功", syncInfo);
} else {
return Result.error("同步失败", syncInfo);
}
}
return Result.error("企业微信同步功能已禁用");
return Result.error("由于企业微信接口调整,同步到本地功能已失效");
// if (thirdAppConfig.isWechatEnterpriseEnabled()) {
// SyncInfoVo syncInfo = wechatEnterpriseService.syncThirdAppUserToLocal();
// if (syncInfo.getFailInfo().size() == 0) {
// return Result.OK("同步成功", syncInfo);
// } else {
// return Result.error("同步失败", syncInfo);
// }
// }
// return Result.error("企业微信同步功能已禁用");
}
/**

View File

@ -261,5 +261,6 @@ public interface SysDictMapper extends BaseMapper<SysDict> {
* @param codeValues 存储字段值 作为查询条件in
* @return
*/
@Deprecated
List<DictModel> queryTableDictByKeysAndFilterSql(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql, @Param("codeValues") List<String> codeValues);
}

View File

@ -1,12 +1,12 @@
package org.jeecg.modules.system.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.system.entity.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.system.model.SysUserSysDepartModel;
import org.jeecg.modules.system.vo.SysUserDepVo;
@ -133,14 +133,14 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
* @param entity
* @return int
*/
int revertLogicDeleted(@Param("userIds") String userIds, @Param("entity") SysUser entity);
int revertLogicDeleted(@Param("userIds") List<String> userIds, @Param("entity") SysUser entity);
/**
* 彻底删除被逻辑删除的用户
* @param userIds 多个用户id
* @return int
*/
int deleteLogicDeleted(@Param("userIds") String userIds);
int deleteLogicDeleted(@Param("userIds") List<String> userIds);
/**
* 更新空字符串为null【此写法有sql注入风险禁止随便用】

View File

@ -52,6 +52,7 @@
where p.id = a.permission_id AND d.username = #{username,jdbcType=VARCHAR}
)
or (p.url like '%:code' and p.url like '/online%' and p.hidden = 1)
or (p.url like '%:id' and p.url like '/online%' and p.hidden = 1)
or p.url = '/online')
and p.del_flag = 0
<!--update begin Author:lvdandan Date:20200213 for加入部门权限 -->

View File

@ -92,6 +92,8 @@
sys_user.id AS id,
sys_user.realname AS realname,
sys_user.avatar AS avatar,
sys_user.sex AS sex,
sys_user.birthday AS birthday,
sys_user.work_no AS workNo,
sys_user.post AS post,
sys_user.telephone AS telephone,
@ -141,18 +143,30 @@
update_time = #{entity.updateTime}
WHERE
del_flag = 1
AND id IN (${userIds})
AND id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator="," >
#{userId}
</foreach>
</update>
<!-- 彻底删除被逻辑删除的用户 -->
<delete id="deleteLogicDeleted">
DELETE FROM sys_user WHERE del_flag = 1 AND id IN (${userIds})
DELETE FROM sys_user WHERE del_flag = 1 AND id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator="," >
#{userId}
</foreach>
</delete>
<!-- 更新空字符串为null -->
<update id="updateNullByEmptyString">
UPDATE sys_user SET ${fieldName} = NULL WHERE ${fieldName} = ''
</update>
<!-- 更新空字符串为null -->
<update id="updateNullByEmptyString">
UPDATE sys_user
<if test="fieldName == 'email'">
SET email = NULL WHERE email = ''
</if>
<if test="fieldName == 'phone'">
SET phone = NULL WHERE phone = ''
</if>
</update>
<!-- 通过多个部门IDS查询部门下的用户信息 -->
<select id="queryByDepIds" resultType="org.jeecg.modules.system.entity.SysUser">

View File

@ -1,13 +1,11 @@
package org.jeecg.modules.system.service;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.system.entity.SysPermission;
import org.jeecg.modules.system.model.TreeModel;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
@ -100,4 +98,13 @@ public interface ISysPermissionService extends IService<SysPermission> {
* @return
*/
List<SysPermission> queryDepartPermissionList(String departId);
/**
* 检测地址是否存在(聚合路由的情况下允许使用子菜单路径作为父菜单的路由地址)
* @param id
* @param url
* @param alwaysShow 是否是聚合路由
* @return
*/
boolean checkPermDuplication(String id, String url,Boolean alwaysShow);
}

View File

@ -23,11 +23,8 @@ import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.*;
import org.jeecg.common.util.HTMLUtils;
import org.jeecg.common.util.SysAnnmentTypeEnum;
import org.jeecg.common.util.YouBianCodeUtil;
import org.jeecg.common.util.*;
import org.jeecg.common.util.dynamic.db.FreemarkerParseFactory;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.message.entity.SysMessageTemplate;
import org.jeecg.modules.message.handle.impl.DdSendMsgHandle;
import org.jeecg.modules.message.handle.impl.EmailSendMsgHandle;
@ -315,6 +312,9 @@ public class SysBaseApiImpl implements ISysBaseAPI {
table = QueryGenerator.getSqlRuleValue(table);
}
//update-end-author:taoyan date:20200820 for:【Online+系统】字典表加权限控制机制逻辑,想法不错 LOWCOD-799
String[] arr = new String[]{text, code};
SqlInjectionUtil.filterContent(arr);
SqlInjectionUtil.specialFilterContentForDictSql(table);
return sysDictService.queryTableDictItemsByCode(table, text, code);
}

View File

@ -10,7 +10,6 @@ import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.util.ResourceUtil;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.DictModelMany;
@ -180,6 +179,9 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
table = arr[0];
filterSql = arr[1];
}
String[] tableAndFields = new String[]{table, text, code};
SqlInjectionUtil.filterContent(tableAndFields);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
return sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, keys);
//update-end-author:taoyan date:20220113 for: @dict注解支持 dicttable 设置where条件
}
@ -216,6 +218,9 @@ public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> impl
table = arr[0];
filterSql = arr[1];
}
String[] tableAndFields = new String[]{table, text, code};
SqlInjectionUtil.filterContent(tableAndFields);
SqlInjectionUtil.specialFilterContentForDictSql(filterSql);
List<DictModel> dicts = sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, Arrays.asList(keyArray));
//update-end-author:taoyan date:2022-4-24 for: 下拉搜索组件表单编辑页面回显下拉搜索的文本的时候因为表名后配置了条件导致sql执行失败
List<String> texts = new ArrayList<>(dicts.size());

View File

@ -1,12 +1,8 @@
package org.jeecg.modules.system.service.impl;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
@ -25,9 +21,11 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
@ -277,4 +275,11 @@ public class SysPermissionServiceImpl extends ServiceImpl<SysPermissionMapper, S
return sysPermissionMapper.queryDepartPermissionList(departId);
}
@Override
public boolean checkPermDuplication(String id, String url,Boolean alwaysShow) {
QueryWrapper<SysPermission> qw=new QueryWrapper();
qw.lambda().eq(true, SysPermission::getUrl,url).ne(oConvertUtils.isNotEmpty(id), SysPermission::getId,id).eq(true, SysPermission::isAlwaysShow,alwaysShow);
return count(qw)==0;
}
}

View File

@ -459,16 +459,14 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
@Override
@CacheEvict(value={CacheConstant.SYS_USERS_CACHE}, allEntries=true)
public boolean revertLogicDeleted(List<String> userIds, SysUser updateEntity) {
String ids = String.format("'%s'", String.join("','", userIds));
return userMapper.revertLogicDeleted(ids, updateEntity) > 0;
return userMapper.revertLogicDeleted(userIds, updateEntity) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeLogicDeleted(List<String> userIds) {
String ids = String.format("'%s'", String.join("','", userIds));
// 1. 删除用户
int line = userMapper.deleteLogicDeleted(ids);
int line = userMapper.deleteLogicDeleted(userIds);
// 2. 删除用户部门关系
line += sysUserDepartMapper.delete(new LambdaQueryWrapper<SysUserDepart>().in(SysUserDepart::getUserId, userIds));
//3. 删除用户角色关系

View File

@ -21,6 +21,7 @@ import org.jeecg.common.util.PasswordUtil;
import org.jeecg.common.util.RestUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.config.thirdapp.ThirdAppConfig;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.SysAnnouncementSendMapper;
@ -51,6 +52,8 @@ public class ThirdAppWechatEnterpriseServiceImpl implements IThirdAppService {
@Autowired
ThirdAppConfig thirdAppConfig;
@Autowired
JeecgBaseConfig jeecgBaseConfig;
@Autowired
private ISysDepartService sysDepartService;
@Autowired
private SysUserMapper userMapper;
@ -302,7 +305,10 @@ public class ThirdAppWechatEnterpriseServiceImpl implements IThirdAppService {
return syncInfo;
}
// 获取企业微信所有的用户
List<User> qwUsers = JwUserAPI.getDetailUsersByDepartid("1", null, null, accessToken);
// List<User> qwUsers = JwUserAPI.getDetailUsersByDepartid("1", null, null, accessToken);
// 获取企业微信所有的用户只能获取userid
List<User> qwUsers = JwUserAPI.getUserIdList(accessToken);
if (qwUsers == null) {
syncInfo.addFailInfo("企业微信用户列表查询失败!");
return syncInfo;
@ -336,15 +342,16 @@ public class ThirdAppWechatEnterpriseServiceImpl implements IThirdAppService {
for (User qwUserTemp : qwUsers) {
if (sysThirdAccount == null || oConvertUtils.isEmpty(sysThirdAccount.getThirdUserId()) || !sysThirdAccount.getThirdUserId().equals(qwUserTemp.getUserid())) {
// sys_third_account 表匹配失败,尝试用手机号匹配
String phone = sysUser.getPhone();
if (!(oConvertUtils.isEmpty(phone) || phone.equals(qwUserTemp.getMobile()))) {
// 新版企业微信调整了API现在只能通过userid来判断是否同步过了
// String phone = sysUser.getPhone();
// if (!(oConvertUtils.isEmpty(phone) || phone.equals(qwUserTemp.getMobile()))) {
// 手机号匹配失败再尝试用username匹配
String username = sysUser.getUsername();
if (!(oConvertUtils.isEmpty(username) || username.equals(qwUserTemp.getUserid()))) {
// username 匹配失败,直接跳到下一次循环继续
continue;
}
}
// }
}
// 循环到此说明用户匹配成功,进行更新操作
qwUser = this.sysUserToQwUser(sysUser, qwUserTemp);
@ -834,7 +841,18 @@ public class ThirdAppWechatEnterpriseServiceImpl implements IThirdAppService {
TextCardEntity entity = new TextCardEntity();
entity.setTitle(announcement.getTitile());
entity.setDescription(oConvertUtils.getString(announcement.getMsgAbstract(),""));
entity.setUrl(RestUtil.getBaseUrl() + "/sys/annountCement/show/" + announcement.getId());
String baseUrl = null;
//优先通过请求获取basepath获取不到读取 jeecg.domainUrl.pc
try {
baseUrl = RestUtil.getBaseUrl();
} catch (Exception e) {
log.warn(e.getMessage());
baseUrl = jeecgBaseConfig.getDomainUrl().getPc();
//e.printStackTrace();
}
entity.setUrl(baseUrl + "/sys/annountCement/show/" + announcement.getId());
textCard.setTextcard(entity);
return JwMessageAPI.sendTextCardMessage(textCard, accessToken);
}

View File

@ -28,5 +28,8 @@
}
<#else>
@ApiModelProperty(value = "${po.filedComment}")
<#if po.fieldDbName == 'del_flag'>
@TableLogic
</#if>
private ${po.fieldType} ${po.fieldName};
</#if>

View File

@ -25,7 +25,7 @@
code="${po.dictTable}"
:fieldConfig="[
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${targetFields[fieldName_index]}' },
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
]"
:multi="${po.extendParams.popupMulti?c}"

View File

@ -43,7 +43,7 @@
<#if query_field_no gt 1> </#if>code="${po.dictTable}"
<#if query_field_no gt 1> </#if>:fieldConfig="[
<#list sourceFields as fieldName>
<#if query_field_no gt 1> </#if>{ source: '${fieldName}', target: '${targetFields[fieldName_index]}' },
<#if query_field_no gt 1> </#if>{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
<#if query_field_no gt 1> </#if>]"
<#if query_field_no gt 1> </#if>:multi="${po.extendParams.popupMulti?c}"

View File

@ -4,7 +4,7 @@
popupCode:"${col.dictTable}",
fieldConfig: [
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${targetFields[fieldName_index]}' },
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
],
<#if col.readonly=='Y'>

View File

@ -8,7 +8,7 @@
code:"${po.dictTable}",
fieldConfig: [
<#list sourceFields as fieldName>
{ source: '${fieldName}', target: '${targetFields[fieldName_index]}' },
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
</#list>
],
multi:${po.extendParams.popupMulti?c}

View File

@ -4,7 +4,7 @@
<#assign id = '${.now?string["yyyyMMddhhmmSSsss"]}0'>
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
VALUES ('${id}', NULL, '${tableVo.ftlDescription}', '/${entityPackage}/${entityName?uncap_first}List', '${entityPackage}/${entityName}List', NULL, NULL, 0, NULL, '1', 1.00, 0, NULL, 1, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0);
VALUES ('${id}', NULL, '${tableVo.ftlDescription}', '/${entityPackage}/${entityName?uncap_first}List', '${entityPackage}/${entityName}List', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 1, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', '${.now?string["yyyy-MM-dd HH:mm:ss"]}', NULL, NULL, 0);
-- 权限控制sql
-- 新增

View File

@ -8,6 +8,7 @@ import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -0,0 +1,71 @@
<#include "/common/utils.ftl">
<template>
<div style="min-height: 400px">
<BasicForm @register="registerForm"></BasicForm>
<div style="width: 100%;text-align: center" v-if="!formDisabled">
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button>
</div>
</div>
</template>
<script lang="ts">
import {BasicForm, useForm} from '/@/components/Form/index';
import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../${entityName}.data';
import {saveOrUpdate} from '../${entityName}.api';
export default defineComponent({
name: "${entityName}Form",
components:{
BasicForm
},
props:{
formData: propTypes.object.def({}),
formBpm: propTypes.bool.def(true),
},
setup(props){
const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: {span: 24}
});
const formDisabled = computed(()=>{
if(props.formData.disabled === false){
return false;
}
return true;
});
let formData = {};
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
formData = {...data}
//设置表单的值
await setFieldsValue(formData);
//默认是禁用
await setProps({disabled: formDisabled.value})
}
async function submitForm() {
let data = getFieldsValue();
let params = Object.assign({}, formData, data);
console.log('表单数据', params)
await saveOrUpdate(params, true)
}
initFormData();
return {
registerForm,
formDisabled,
submitForm,
}
}
});
</script>

View File

@ -141,6 +141,9 @@
<#if need_pca>
import { getAreaTextByCode } from '/@/components/Form/src/utils/Area';
</#if>
<#if bpm_flag==true>
import { startProcess } from '/@/api/common/api';
</#if>
const queryParam = ref<any>({});
const toggleSearchStatus = ref<boolean>(false);
@ -241,19 +244,43 @@
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
<#if bpm_flag==true>
let dropDownAction = [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
}
}
];
if(record.bpmStatus == '1'){
dropDownAction.push({
label: '发起流程',
popConfirm: {
title: '确认提交流程吗?',
confirm: handleProcess.bind(null, record),
}
})
}
return dropDownAction;
<#else>
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
}
}
]
</#if>
}
/**
@ -295,6 +322,22 @@
}
</#if>
<#if bpm_flag==true>
/**
* 提交流程
*/
async function handleProcess(record) {
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackage}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
handleSuccess();
}
</#if>
<#if need_category>
/**
* form点击事件

View File

@ -38,16 +38,20 @@
</#if>
<#include "/common/form/native/vue3NativeForm.ftl">
</#list>
<#if bpm_flag>
<a-col v-if="showFlowSubmitButton" :span="24" style="width: 100%;text-align: center;">
<a-button preIcon="ant-design:check-outlined" style="width: 126px" type="primary" @click="submitForm">提 交</a-button>
</a-col>
</#if>
</a-row>
</a-form>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed } from 'vue';
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import moment from 'moment';
<#include "/common/form/native/vue3NativeImport.ftl">
import { getValueType } from '/@/utils';
import { saveOrUpdate } from '../${entityName}.api';
@ -57,7 +61,9 @@
</#if>
const props = defineProps({
disabled: { type: Boolean, default: false },
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
});
const formRef = ref();
const useForm = Form.useForm;
@ -85,6 +91,43 @@
<#include "/common/validatorRulesTemplate/native/vue3MainNative.ftl">
};
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
// 表单禁用
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
<#if bpm_flag>
onMounted(()=>{
initFormData();
});
//渲染流程表单数据
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
if(props.formBpm === true){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
//设置表单的值
edit({...data});
}
}
// 是否显示提交按钮
const showFlowSubmitButton = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return true
}
}
return false
});
</#if>
/**
* 新增
@ -179,7 +222,7 @@
<style lang="less" scoped>
.antd-modal-form {
height: 500px !important;
min-height: 500px !important;
overflow-y: auto;
padding: 24px 24px 24px 24px;
}

View File

@ -1,6 +1,6 @@
<template>
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<${entityName}Form ref="registerForm" @ok="submitCallback" :disabled="disableSubmit"></${entityName}Form>
<${entityName}Form ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></${entityName}Form>
</a-modal>
</template>

View File

@ -7,6 +7,7 @@ import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -7,6 +7,7 @@ import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -7,6 +7,7 @@ import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -1,3 +1,4 @@
<#include "/common/utils.ftl">
<#assign pidFieldName = "">
<#assign hasChildrenField = "">
<#assign bpm_flag=false>
@ -350,7 +351,7 @@
}
}
];
if(record.bpmStatus == '1'){
if(record.bpmStatus == '1' || !record.bpmStatus){
dropDownAction.push({
label: '发起流程',
popConfirm: {
@ -384,7 +385,7 @@
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackage}/modules/${entityName}Form',
formUrl: '${entityPackage}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);

View File

@ -403,3 +403,13 @@ export const formSchema: FormSchema[] = [
},
</#if>
];
/**
* 流程表单调用这个方法获取formSchema
* @param param
*/
export function getBpmFormSchema(_formData): FormSchema[]{
// 默认和原始表单保持一致 如果流程中配置了权限数据这里需要单独处理formSchema
return formSchema;
}

View File

@ -0,0 +1,71 @@
<#include "/common/utils.ftl">
<template>
<div style="min-height: 400px">
<BasicForm @register="registerForm"></BasicForm>
<div style="width: 100%;text-align: center" v-if="!formDisabled">
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button>
</div>
</div>
</template>
<script lang="ts">
import {BasicForm, useForm} from '/@/components/Form/index';
import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../${entityName}.data';
import {saveOrUpdateDict} from '../${entityName}.api';
export default defineComponent({
name: "${entityName}Form",
components:{
BasicForm
},
props:{
formData: propTypes.object.def({}),
formBpm: propTypes.bool.def(true),
},
setup(props){
const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: {span: 24}
});
const formDisabled = computed(()=>{
if(props.formData.disabled === false){
return false;
}
return true;
});
let formData = {};
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
formData = {...data}
//设置表单的值
await setFieldsValue(formData);
//默认是禁用
await setProps({disabled: formDisabled.value})
}
async function submitForm() {
let data = getFieldsValue();
let params = Object.assign({}, formData, data);
console.log('表单数据', params)
await saveOrUpdateDict(params, true)
}
initFormData();
return {
registerForm,
formDisabled,
submitForm,
}
}
});
</script>

View File

@ -1,3 +1,4 @@
<#include "/common/utils.ftl">
<template>
<div>
<#assign pidFieldName = "">
@ -150,6 +151,9 @@
<#if need_pca>
import { getAreaTextByCode } from '/@/components/Form/src/utils/Area';
</#if>
<#if bpm_flag==true>
import { startProcess } from '/@/api/common/api';
</#if>
const expandedRowKeys = ref([]);
const queryParam = ref<any>({});
@ -381,12 +385,14 @@
}
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
function getDropDownAction(record){
<#if bpm_flag==true>
let dropDownAction = [
{
label: '详情',
onClick: handleDetail.bind(null, record),
@ -394,7 +400,7 @@
{
label: '添加下级',
onClick: handleAddSub.bind(null, { pid: record.id }),
},
},
{
label: '删除',
popConfirm: {
@ -403,8 +409,54 @@
},
},
];
if(record.bpmStatus == '1' || !record.bpmStatus){
dropDownAction.push({
label: '发起流程',
popConfirm: {
title: '确认提交流程吗?',
confirm: handleProcess.bind(null, record),
}
})
}
return dropDownAction;
<#else>
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '添加下级',
onClick: handleAddSub.bind(null, { pid: record.id }),
},
{
label: '删除',
popConfirm: {
title: '确定删除吗?',
confirm: handleDelete.bind(null, record),
},
},
];
</#if>
}
<#if bpm_flag==true>
/**
* 提交流程
*/
async function handleProcess(record) {
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackage}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
await reload();
}
</#if>
/**
* 查询
*/

View File

@ -56,16 +56,20 @@
</#if>
<#include "/common/form/native/vue3NativeForm.ftl">
</#list>
<#if bpm_flag>
<a-col v-if="showFlowSubmitButton" :span="24" style="width: 100%;text-align: center;">
<a-button preIcon="ant-design:check-outlined" style="width: 126px" type="primary" @click="submitForm">提 交</a-button>
</a-col>
</#if>
</a-row>
</a-form>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, unref, defineProps, computed } from 'vue';
import { ref, reactive, defineExpose, nextTick, unref, defineProps, computed, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import moment from 'moment';
<#include "/common/form/native/vue3NativeImport.ftl">
import { getValueType } from '/@/utils';
import {loadTreeData, saveOrUpdateDict} from '../${entityName}.api';
@ -106,8 +110,47 @@
};
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
const props = defineProps({
disabled: { type: Boolean, default: false },
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
});
// 表单禁用
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
<#if bpm_flag>
onMounted(()=>{
initFormData();
});
//渲染流程表单数据
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
if(props.formBpm === true){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
//设置表单的值
edit({...data});
}
}
// 是否显示提交按钮
const showFlowSubmitButton = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return true
}
}
return false
});
</#if>
/**
* 新增
@ -230,7 +273,7 @@
<style lang="less" scoped>
.antd-modal-form {
height: 500px !important;
min-height: 500px !important;
overflow-y: auto;
padding: 24px 24px 24px 24px;
}

View File

@ -1,6 +1,6 @@
<template>
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<${entityName}Form ref="registerForm" @ok="submitCallback" :disabled="disableSubmit"></${entityName}Form>
<${entityName}Form ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></${entityName}Form>
</a-modal>
</template>

View File

@ -7,6 +7,7 @@ import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecgframework.poi.excel.annotation.Excel;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;

View File

@ -7,6 +7,7 @@ import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.aspect.annotation.Dict;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;

View File

@ -7,6 +7,7 @@ import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -7,6 +7,7 @@ import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -230,7 +230,7 @@
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackage}/modules/${entityName}Form',
formUrl: '${entityPackage}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
@ -255,7 +255,7 @@
}
}
];
if(record.bpmStatus == '1'){
if(record.bpmStatus == '1' || !record.bpmStatus){
dropDownAction.push({
label: '发起流程',
popConfirm: {

View File

@ -794,4 +794,13 @@ export const ${sub.entityName?uncap_first}JVxeColumns: JVxeColumn[] = [
<#-- 循环子表的列 结束 -->
]
</#if>
</#list>
</#list>
/**
* 流程表单调用这个方法获取formSchema
* @param param
*/
export function getBpmFormSchema(_formData): FormSchema[]{
// 默认和原始表单保持一致 如果流程中配置了权限数据这里需要单独处理formSchema
return formSchema;
}

View File

@ -0,0 +1,212 @@
<#include "/common/utils.ftl">
<template>
<div>
<BasicForm @register="registerForm" ref="formRef"/>
<!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<#list subTables as sub><#rt/>
<#assign refKey = sub.entityName?uncap_first/>
<#if sub.foreignRelationType =='1'>
<a-tab-pane tab="${sub.ftlDescription}" key="${refKey}" :forceRender="true">
<${sub.entityName}Form ref="${sub.entityName?uncap_first}Form" :disabled="formDisabled"></${sub.entityName}Form>
</a-tab-pane>
<#else>
<a-tab-pane tab="${sub.ftlDescription}" key="${refKey}" :forceRender="true">
<JVxeTable
v-if="${sub.entityName?uncap_first}Table.show"
keep-source
resizable
ref="${refKey}"
:loading="${sub.entityName?uncap_first}Table.loading"
:columns="${sub.entityName?uncap_first}Table.columns"
:dataSource="${sub.entityName?uncap_first}Table.dataSource"
:height="340"
:disabled="formDisabled"
:rowNumber="true"
:rowSelection="true"
:toolbar="true"
/>
</a-tab-pane>
</#if>
</#list>
</a-tabs>
<div style="width: 100%;text-align: center;margin-top: 10px;" v-if="showFlowSubmitButton">
<a-button preIcon="ant-design:check-outlined" style="width: 126px" type="primary" @click="handleSubmit">提 交</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { defHttp } from '/@/utils/http/axios';
import {ref, computed, unref,reactive, onMounted, defineProps } from 'vue';
import {BasicForm, useForm} from '/@/components/Form/index';
import { JVxeTable } from '/@/components/jeecg/JVxeTable'
import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods.ts'
<#list subTables as sub>
<#if sub.foreignRelationType =='1'>
import ${sub.entityName}Form from './${sub.entityName}Form.vue'
</#if>
</#list>
import {formSchema<#list subTables as sub><#if sub.foreignRelationType =='0'>,${sub.entityName?uncap_first}JVxeColumns</#if></#list>} from '../${entityName}.data';
import {saveOrUpdate<#list subTables as sub>,query${sub.entityName}</#list>} from '../${entityName}.api';
import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils'
const isUpdate = ref(true);
const refKeys = ref([<#list subTables as sub>'${sub.entityName?uncap_first}', </#list>]);
<#assign hasOne2Many = false>
<#assign hasOne2One = false>
const activeKey = ref('${subTables[0].entityName?uncap_first}');
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
<#assign hasOne2Many = true>
const ${sub.entityName?uncap_first} = ref();
</#if>
<#if sub.foreignRelationType =='1'>
<#assign hasOne2One = true>
const ${sub.entityName?uncap_first}Form = ref();
</#if>
</#list>
const tableRefs = {<#list subTables as sub><#if sub.foreignRelationType =='0'>${sub.entityName?uncap_first}, <#assign hasOne2Many = true></#if></#list>};
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
const ${sub.entityName?uncap_first}Table = reactive({
loading: false,
dataSource: [],
columns:${sub.entityName?uncap_first}JVxeColumns,
show: false
})
</#if>
</#list>
const props = defineProps({
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
});
const formDisabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}
}
return true;
});
// 是否显示提交按钮
const showFlowSubmitButton = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return true
}
}
return false
});
//表单配置
const [registerForm, {setProps,resetFields, setFieldsValue, validate}] = useForm({
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: {span: ${getFormSpan(tableVo.fieldRowNum?default(1))}}
});
onMounted(()=>{
initFormData();
});
//渲染流程表单数据
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
if(props.formBpm === true){
await reset();
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
//表单赋值
await setFieldsValue({
...data
});
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.initFormData(query${sub.entityName}, data.id)
</#if>
</#list>
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='0'>
requestSubTableData(query${sub.entityName}, {id: data.id}, ${sub.entityName?uncap_first}Table, ()=>{
${sub.entityName?uncap_first}Table.show = true;
});
</#if>
</#list>
// 隐藏底部时禁用整个表单
setProps({ disabled: formDisabled.value })
}
}
//方法配置
const [handleChangeTabs,handleSubmit,requestSubTableData,formRef] = useJvxeMethod(requestAddOrEdit,classifyIntoFormData,tableRefs,activeKey,refKeys<#if hasOne2One==true>,validateSubForm</#if>);
async function reset(){
await resetFields();
activeKey.value = '${subTables[0].entityName?uncap_first}';
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first}Table.dataSource = [];
</#if>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.resetFields();
</#if>
</#list>
}
function classifyIntoFormData(allValues) {
let main = Object.assign({}, allValues.formValue)
return {
...main, // 展开
<#assign subManyIndex = 0>
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first}List: allValues.tablesValue[${subManyIndex}].tableData,
<#assign subManyIndex = subManyIndex+1>
<#else>
${sub.entityName?uncap_first}List: ${sub.entityName?uncap_first}Form.value.getFormData(),
</#if>
</#list>
}
}
<#if hasOne2One==true>
//校验所有一对一子表表单
function validateSubForm(allValues){
return new Promise((resolve,reject)=>{
Promise.all([
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.validateForm(${sub_index}),
</#if>
</#list>
]).then(() => {
resolve(allValues)
}).catch(e => {
if (e.error === VALIDATE_FAILED) {
// 如果有未通过表单验证的子表就自动跳转到它所在的tab
activeKey.value = e.index == null ? unref(activeKey) : refKeys.value[e.index]
} else {
console.error(e)
}
})
})
}
</#if>
//表单提交事件
async function requestAddOrEdit(values) {
//提交表单
await saveOrUpdate(values, true);
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number){
width: 100%
}
:deep(.ant-calendar-picker){
width: 100%
}
</style>

View File

@ -3,7 +3,7 @@
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="${getModalWidth(tableVo.fieldRowNum?default(1))}" @ok="handleSubmit">
<BasicForm @register="registerForm" ref="formRef"/>
<!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" @change="handleChangeTabs">
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<#list subTables as sub><#rt/>
<#assign refKey = sub.entityName?uncap_first/>
<#if sub.foreignRelationType =='1'>

View File

@ -7,6 +7,7 @@ import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -7,6 +7,7 @@ import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -229,7 +229,7 @@
}
}
];
if(record.bpmStatus == '1'){
if(record.bpmStatus == '1' || !record.bpmStatus){
dropDownAction.push({
label: '发起流程',
popConfirm: {

View File

@ -0,0 +1,203 @@
<#include "/common/utils.ftl">
<template>
<div>
<BasicForm @register="registerForm" ref="formRef"/>
<!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<#list subTables as sub><#rt/>
<#assign refKey = sub.entityName?uncap_first/>
<#if sub.foreignRelationType =='1'>
<a-tab-pane tab="${sub.ftlDescription}" key="${refKey}" :forceRender="true">
<${sub.entityName}Form ref="${sub.entityName?uncap_first}Form" :disabled="formDisabled"></${sub.entityName}Form>
</a-tab-pane>
<#else>
<a-tab-pane tab="${sub.ftlDescription}" key="${refKey}" :forceRender="true">
<JVxeTable
keep-source
resizable
ref="${refKey}"
v-if="${sub.entityName?uncap_first}Table.show"
:loading="${sub.entityName?uncap_first}Table.loading"
:columns="${sub.entityName?uncap_first}Table.columns"
:dataSource="${sub.entityName?uncap_first}Table.dataSource"
:height="340"
:rowNumber="true"
:rowSelection="true"
:disabled="formDisabled"
:toolbar="true"
/>
</a-tab-pane>
</#if>
</#list>
</a-tabs>
<div style="width: 100%;text-align: center" v-if="!formDisabled">
<a-button @click="handleSubmit" pre-icon="ant-design:check" type="primary">提 交</a-button>
</div>
</div>
</template>
<script lang="ts">
import {BasicForm, useForm} from '/@/components/Form/index';
import { computed, defineComponent, reactive, ref, unref } from 'vue';
import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes';
import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods';
import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils';
<#list subTables as sub>
<#if sub.foreignRelationType =='1'>
import ${sub.entityName}Form from './${sub.entityName}Form.vue'
</#if>
</#list>
import {getBpmFormSchema<#list subTables as sub><#if sub.foreignRelationType =='0'>,${sub.entityName?uncap_first}Columns</#if></#list>} from '../${entityName}.data';
import {saveOrUpdate<#list subTables as sub>,${sub.entityName?uncap_first}List</#list>} from '../${entityName}.api';
export default defineComponent({
name: "${entityName}Form",
components:{
BasicForm,
<#list subTables as sub>
<#if sub.foreignRelationType =='1'>
${sub.entityName}Form,
</#if>
</#list>
},
props:{
formData: propTypes.object.def({}),
formBpm: propTypes.bool.def(true),
},
setup(props){
const [registerForm, { setFieldsValue, setProps }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: {span: 24}
});
const formDisabled = computed(()=>{
if(props.formData.disabled === false){
return false;
}
return true;
});
<#assign hasOne2Many = false>
<#assign hasOne2One = false>
const refKeys = ref([<#list subTables as sub>'${sub.entityName?uncap_first}', </#list>]);
const activeKey = ref('${subTables[0].entityName?uncap_first}');
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
<#assign hasOne2Many = true>
const ${sub.entityName?uncap_first} = ref();
</#if>
<#if sub.foreignRelationType =='1'>
<#assign hasOne2One = true>
const ${sub.entityName?uncap_first}Form = ref();
</#if>
</#list>
const tableRefs = {<#list subTables as sub><#if sub.foreignRelationType =='0'>${sub.entityName?uncap_first}, <#assign hasOne2Many = true></#if></#list>};
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
const ${sub.entityName?uncap_first}Table = reactive({
loading: false,
dataSource: [],
columns:${sub.entityName?uncap_first}Columns,
show: false
})
</#if>
</#list>
const [handleChangeTabs,handleSubmit,requestSubTableData,formRef] = useJvxeMethod(requestAddOrEdit,classifyIntoFormData,tableRefs,activeKey,refKeys,validateSubForm);
function classifyIntoFormData(allValues) {
let main = Object.assign({}, allValues.formValue)
return {
...main, // 展开
<#assign subManyIndex = 0>
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first}List: allValues.tablesValue[${subManyIndex}].tableData,
<#assign subManyIndex = subManyIndex+1>
<#else>
${sub.entityName?uncap_first}List: ${sub.entityName?uncap_first}Form.value.getFormData(),
</#if>
</#list>
}
}
<#if hasOne2One==true>
//校验所有一对一子表表单
function validateSubForm(allValues){
return new Promise((resolve, _reject)=>{
Promise.all([
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.validateForm(${sub_index}),
</#if>
</#list>
]).then(() => {
resolve(allValues)
}).catch(e => {
if (e.error === VALIDATE_FAILED) {
// 如果有未通过表单验证的子表就自动跳转到它所在的tab
activeKey.value = e.index == null ? unref(activeKey) : refKeys.value[e.index]
} else {
console.error(e)
}
})
})
}
</#if>
//表单提交事件
async function requestAddOrEdit(values) {
await saveOrUpdate(values, true);
}
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
//设置表单的值
await setFieldsValue({...data});
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
requestSubTableData(${sub.entityName?uncap_first}List, {id: data.id}, ${sub.entityName?uncap_first}Table, ()=>{
${sub.entityName?uncap_first}Table.show = true;
});
</#if>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.initFormData(${sub.entityName?uncap_first}List, data.id);
</#if>
</#list>
//默认是禁用
await setProps({disabled: formDisabled.value})
}
initFormData();
return {
registerForm,
formDisabled,
formRef,
handleSubmit,
activeKey,
handleChangeTabs,
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first},
</#if>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form,
</#if>
</#list>
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first}Table,
</#if>
</#list>
}
}
});
</script>

View File

@ -3,7 +3,7 @@
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="${getModalWidth(tableVo.fieldRowNum?default(1))}" @ok="handleSubmit">
<BasicForm @register="registerForm" ref="formRef"/>
<!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" @change="handleChangeTabs">
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<#list subTables as sub><#rt/>
<#assign refKey = sub.entityName?uncap_first/>
<#if sub.foreignRelationType =='1'>

View File

@ -1,3 +1,4 @@
<#include "/common/utils.ftl">
<template>
<div>
<#assign query_field_no=0>
@ -145,6 +146,10 @@
import { getAuthCache, setAuthCache } from '/@/utils/auth';
import { DB_DICT_DATA_KEY } from '/@/enums/cacheEnum';
</#if>
<#if bpm_flag==true>
import { startProcess } from '/@/api/common/api';
</#if>
const checkedKeys = ref<Array<string | number>>([]);
//注册model
const [registerModal, {openModal}] = useModal();
@ -238,19 +243,61 @@
* 下拉操作栏
*/
function getDropDownAction(record){
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
}
<#if bpm_flag==true>
let dropDownAction = [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
}
]
}
];
if(record.bpmStatus == '1' || !record.bpmStatus){
dropDownAction.push({
label: '发起流程',
popConfirm: {
title: '确认提交流程吗?',
confirm: handleProcess.bind(null, record),
}
})
}
return dropDownAction;
<#else>
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}, {
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
}
}
]
</#if>
}
<#if bpm_flag==true>
/**
* 提交流程
*/
async function handleProcess(record) {
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackage}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
handleSuccess();
}
</#if>
<#if need_category>
/**
* form点击事件

View File

@ -50,16 +50,17 @@
<#list subTables as sub><#rt/>
<a-tab-pane tab="${sub.ftlDescription}" key="${sub.entityName?uncap_first}" :forceRender="true">
<#if sub.foreignRelationType =='1'>
<${Format.humpToShortbar(sub.entityName)}-form ref="${sub.entityName?uncap_first}FormRef" :disabled="formDisabled"></${Format.humpToShortbar(sub.entityName)}-form>
<${Format.humpToShortbar(sub.entityName)}-form ref="${sub.entityName?uncap_first}FormRef" :disabled="disabled"></${Format.humpToShortbar(sub.entityName)}-form>
<#else>
<j-vxe-table
:keep-source="true"
ref="${sub.entityName?uncap_first}TableRef"
v-if="${sub.entityName?uncap_first}Table.show"
:loading="${sub.entityName?uncap_first}Table.loading"
:columns="${sub.entityName?uncap_first}Table.columns"
:dataSource="${sub.entityName?uncap_first}Table.dataSource"
:maxHeight="300"
:disabled="formDisabled"
:height="340"
:disabled="disabled"
:rowNumber="true"
:rowSelection="true"
:toolbar="true"/>
@ -67,11 +68,17 @@
</a-tab-pane>
</#list>
</a-tabs>
<#if bpm_flag>
<div v-if="showFlowSubmitButton" :span="24" style="width: 100%;text-align: center;margin-top: 10px">
<a-button preIcon="ant-design:check-outlined" style="width: 126px" type="primary" @click="submitForm">提 交</a-button>
</div>
</#if>
</a-spin>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, computed, toRaw } from 'vue';
import { defineComponent, ref, reactive, computed, toRaw, onMounted } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useValidateAntFormAndTable } from '/@/hooks/system/useJvxeMethods';
import { <#list subTables as sub><#if sub.foreignRelationType =='0'>query${sub.entityName}ListByMainId, </#if></#list>queryDataById, saveOrUpdate } from '../${entityName}.api';
<#list subTables as sub>
@ -112,10 +119,12 @@
</#list>
},
props:{
disabled:{
formDisabled:{
type: Boolean,
default: false
}
},
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
},
emits:['success'],
setup(props, {emit}) {
@ -131,7 +140,8 @@
const ${sub.entityName?uncap_first}Table = reactive<Record<string, any>>({
loading: false,
columns: ${sub.entityName?uncap_first}Columns,
dataSource: []
dataSource: [],
show: false
});
</#if>
</#list>
@ -160,10 +170,56 @@
wrapperCol: {xs: {span: 24}, sm: {span: 16}},
};
const formDisabled = computed(() => {
return props.disabled;
// 表单禁用
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
<#if bpm_flag>
onMounted(()=>{
initFormData();
});
//渲染流程表单数据
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
if(props.formBpm === true){
let params = {id: props.formData.dataId};
const row = await defHttp.get({url: queryByIdUrl, params});
//设置表单的值
Object.keys(row).map(k => {
formData[k] = row[k];
});
//子表数据
<#list subTables as sub>
<#if sub.foreignRelationType =='1'>
await ${sub.entityName?uncap_first}FormRef.value.initFormData(row['${subMainFieldMap[sub.entityName]}']);
<#else>
const ${sub.entityName?uncap_first}DataList = await query${sub.entityName}ListByMainId(row['${subMainFieldMap[sub.entityName]}']);
${sub.entityName?uncap_first}Table.dataSource = [...${sub.entityName?uncap_first}DataList];
${sub.entityName?uncap_first}Table.show = true;
</#if>
</#list>
}
}
// 是否显示提交按钮
const showFlowSubmitButton = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return true
}
}
return false
});
</#if>
function add() {
resetFields();
<#list subTables as sub>
@ -264,7 +320,10 @@
setFieldsValue,
handleFormChange,
formItemLayout,
formDisabled,
disabled,
<#if bpm_flag>
showFlowSubmitButton,
</#if>
getFormData,
submitForm,
add,

View File

@ -1,7 +1,7 @@
<#include "/common/utils.ftl">
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="${getModalWidth(tableVo.fieldRowNum?default(1))}" @ok="handleSubmit">
<${Format.humpToShortbar(entityName)}-form ref="formComponent" :disabled="formDisabled" @success="submitSuccess"></${Format.humpToShortbar(entityName)}-form>
<${Format.humpToShortbar(entityName)}-form ref="formComponent" :formDisabled="formDisabled" :formBpm="false" @success="submitSuccess"></${Format.humpToShortbar(entityName)}-form>
</BasicModal>
</template>

View File

@ -7,6 +7,7 @@ import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -7,6 +7,7 @@ import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;

View File

@ -204,7 +204,7 @@
let params = {
flowCode: 'dev_${tableName}_001',
id: record.id,
formUrl: '${entityPackage}/modules/${entityName}Form',
formUrl: '${entityPackage}/components/${entityName}Form',
formUrlMobile: ''
}
await startProcess(params);
@ -229,7 +229,7 @@
}
}
];
if(record.bpmStatus == '1'){
if(record.bpmStatus == '1' || !record.bpmStatus){
dropDownAction.push({
label: '发起流程',
popConfirm: {

View File

@ -0,0 +1,253 @@
<#include "/common/utils.ftl">
<template>
<div>
<!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<!--主表区域 -->
<a-tab-pane tab="${tableVo.ftlDescription}" :key="refKeys[0]" :forceRender="true" :style="tabsStyle">
<BasicForm @register="registerForm" ref="formRef"/>
</a-tab-pane>
<!--子表单区域 -->
<#list subTables as sub><#rt/>
<#assign refKey = sub.entityName?uncap_first/>
<#if sub.foreignRelationType =='1'>
<a-tab-pane tab="${sub.ftlDescription}" key="${refKey}" :forceRender="true" :style="tabsStyle">
<${sub.entityName}Form ref="${sub.entityName?uncap_first}Form" :disabled="formDisabled"></${sub.entityName}Form>
</a-tab-pane>
<#else>
<a-tab-pane tab="${sub.ftlDescription}" key="${refKey}" :forceRender="true" :style="tabsStyle">
<JVxeTable
keep-source
resizable
ref="${refKey}"
v-if="${sub.entityName?uncap_first}Table.show"
:loading="${sub.entityName?uncap_first}Table.loading"
:columns="${sub.entityName?uncap_first}Table.columns"
:dataSource="${sub.entityName?uncap_first}Table.dataSource"
:height="340"
:disabled="formDisabled"
:rowNumber="true"
:rowSelection="true"
:toolbar="true"
/>
</a-tab-pane>
</#if>
</#list>
</a-tabs>
<div style="width: 100%;text-align: center;margin-top: 10px;" v-if="showFlowSubmitButton">
<a-button preIcon="ant-design:check-outlined" style="width: 126px" type="primary" @click="handleSubmit">提 交</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { defHttp } from '/@/utils/http/axios';
import {ref, computed, unref,reactive, onMounted, defineProps } from 'vue';
import {BasicForm, useForm} from '/@/components/Form/index';
import { JVxeTable } from '/@/components/jeecg/JVxeTable'
import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods.ts'
<#list subTables as sub>
<#if sub.foreignRelationType =='1'>
import ${sub.entityName}Form from './${sub.entityName}Form.vue'
</#if>
</#list>
import {formSchema<#list subTables as sub><#if sub.foreignRelationType =='0'>,${sub.entityName?uncap_first}Columns</#if></#list>} from '../${entityName}.data';
import {saveOrUpdate<#list subTables as sub>,${sub.entityName?uncap_first}List</#list>} from '../${entityName}.api';
import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils'
const refKeys = ref(['${tableVo.entityName?uncap_first}',<#list subTables as sub>'${sub.entityName?uncap_first}', </#list>]);
<#assign hasOne2Many = false>
<#assign hasOne2One = false>
const activeKey = ref('${tableVo.entityName?uncap_first}');
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
<#assign hasOne2Many = true>
const ${sub.entityName?uncap_first} = ref();
</#if>
<#if sub.foreignRelationType =='1'>
<#assign hasOne2One = true>
const ${sub.entityName?uncap_first}Form = ref();
</#if>
</#list>
const tableRefs = {<#list subTables as sub><#if sub.foreignRelationType =='0'>${sub.entityName?uncap_first}, <#assign hasOne2Many = true></#if></#list>};
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
const ${sub.entityName?uncap_first}Table = reactive({
loading: false,
dataSource: [],
columns:${sub.entityName?uncap_first}Columns,
show: false
})
</#if>
</#list>
const props = defineProps({
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
});
const formDisabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}
}
return true;
});
// 是否显示提交按钮
const showFlowSubmitButton = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return true
}
}
return false
});
//表单配置
const [registerForm, {setProps,resetFields, setFieldsValue, validate}] = useForm({
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: {span: ${getFormSpan(tableVo.fieldRowNum?default(1))}}
});
onMounted(()=>{
initFormData();
});
//渲染流程表单数据
const queryByIdUrl = '/${entityPackage}/${entityName?uncap_first}/queryById';
async function initFormData(){
if(props.formBpm === true){
await reset();
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
//表单赋值
await setFieldsValue({
...data
});
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.initFormData(${sub.entityName?uncap_first}List, data.id)
</#if>
</#list>
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='0'>
requestSubTableData(${sub.entityName?uncap_first}List, {id: data.id}, ${sub.entityName?uncap_first}Table, ()=>{
${sub.entityName?uncap_first}Table.show = true;
})
</#if>
</#list>
// 隐藏底部时禁用整个表单
setProps({ disabled: formDisabled.value })
}
}
//方法配置
const [handleChangeTabs,handleSubmit,requestSubTableData,formRef] = useJvxeMethod(requestAddOrEdit,classifyIntoFormData,tableRefs,activeKey,refKeys<#if hasOne2One==true>,validateSubForm</#if>);
// 弹窗tabs滚动区域的高度
const tabsStyle = computed(() => {
let height: Nullable<string> = null
let minHeight = '100px'
let maxHeight: Nullable<string> = '500px'
// 弹窗wrapper
<#-- update-begin-author:taoyan date:2022-11-14 for:VUEN-2674 【代码生成】对接流程表单 附加单据显示问题 5.多tab生成代码后新增 没有滚动条,只能填写部分字段 -->
let overflow = 'auto';
return {height, minHeight, maxHeight, overflow};
<#-- update-end-author:taoyan date:2022-11-14 for:VUEN-2674 【代码生成】对接流程表单 附加单据显示问题 5.多tab生成代码后新增 没有滚动条,只能填写部分字段 -->
})
async function reset(){
await resetFields();
activeKey.value = '${tableVo.entityName?uncap_first}';
<#list subTables as sub>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first}Table.dataSource = [];
</#if>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.resetFields();
</#if>
</#list>
}
function classifyIntoFormData(allValues) {
let main = Object.assign({}, allValues.formValue)
return {
...main, // 展开
<#assign subManyIndex = 0>
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='0'>
${sub.entityName?uncap_first}List: allValues.tablesValue[${subManyIndex}].tableData,
<#assign subManyIndex = subManyIndex+1>
<#else>
${sub.entityName?uncap_first}List: ${sub.entityName?uncap_first}Form.value.getFormData(),
</#if>
</#list>
}
}
<#if hasOne2One==true>
//校验所有一对一子表表单
function validateSubForm(allValues){
return new Promise((resolve,reject)=>{
Promise.all([
<#list subTables as sub><#rt/>
<#if sub.foreignRelationType =='1'>
${sub.entityName?uncap_first}Form.value.validateForm(${sub_index+1}),
</#if>
</#list>
]).then(() => {
resolve(allValues)
}).catch(e => {
if (e.error === VALIDATE_FAILED) {
// 如果有未通过表单验证的子表就自动跳转到它所在的tab
activeKey.value = e.index == null ? unref(activeKey) : refKeys.value[e.index]
} else {
console.error(e)
}
})
})
}
</#if>
//表单提交事件
async function requestAddOrEdit(values) {
//提交表单
await saveOrUpdate(values, true);
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number){
width: 100%
}
:deep(.ant-calendar-picker){
width: 100%
}
</style>
<style lang="less">
// Online表单Tab风格专属样式
.j-cgform-tab-modal {
.ant-modal-header {
padding-top: 8px;
padding-bottom: 8px;
border-bottom: none !important;
}
.ant-modal .ant-modal-body > .scrollbar,
.ant-tabs-nav .ant-tabs-tab {
padding-top: 0;
}
.ant-tabs-top-bar {
width: calc(100% - 55px);
position: relative;
left: -14px;
}
.ant-tabs .ant-tabs-top-content > .ant-tabs-tabpane {
overflow: hidden auto;
}
}
</style>

View File

@ -2,7 +2,7 @@
<template>
<BasicModal ref="modalRef" destroyOnClose wrapClassName="j-cgform-tab-modal" v-bind="$attrs" @register="registerModal" :width="${getModalWidth(tableVo.fieldRowNum?default(1))}" @ok="handleSubmit">
<!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" @change="handleChangeTabs">
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<!--主表区域 -->
<a-tab-pane tab="${tableVo.ftlDescription}" :key="refKeys[0]" :forceRender="true" :style="tabsStyle">
<BasicForm @register="registerForm" ref="formRef"/>
@ -128,7 +128,10 @@
maxHeight = null
}
}
return {height, minHeight, maxHeight}
<#-- update-begin-author:taoyan date:2022-11-14 for:VUEN-2674 【代码生成】对接流程表单 附加单据显示问题 5.多tab生成代码后新增 没有滚动条,只能填写部分字段 -->
let overflow = 'auto';
return {height, minHeight, maxHeight, overflow};
<#-- update-end-author:taoyan date:2022-11-14 for:VUEN-2674 【代码生成】对接流程表单 附加单据显示问题 5.多tab生成代码后新增 没有滚动条,只能填写部分字段 -->
})
async function reset(){

View File

@ -11,6 +11,6 @@ WORKDIR /jeecg-boot
EXPOSE 8080
#ADD ./src/main/resources/jeecg ./config/jeecg
ADD ./target/jeecg-system-start-3.4.2.jar ./
ADD ./target/jeecg-system-start-3.4.3.jar ./
CMD sleep 60;java -Djava.security.egd=file:/dev/./urandom -jar jeecg-system-start-3.4.2.jar
CMD sleep 60;java -Djava.security.egd=file:/dev/./urandom -jar jeecg-system-start-3.4.3.jar

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-module-system</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -3,9 +3,7 @@ package org.jeecg;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ConfigurableApplicationContext;

View File

@ -65,8 +65,8 @@ spring:
threadsInheritContextClassLoaderOfInitializingThread: true
#json 时间戳统一转换
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
jpa:
open-in-view: false
aop:

View File

@ -45,8 +45,8 @@ spring:
overwrite-existing-jobs: true
#json 时间戳统一转换
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
jpa:
open-in-view: false
properties:

View File

@ -65,8 +65,8 @@ spring:
threadsInheritContextClassLoaderOfInitializingThread: true
#json 时间戳统一转换
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
jpa:
open-in-view: false
aop:

View File

@ -65,8 +65,8 @@ spring:
threadsInheritContextClassLoaderOfInitializingThread: true
#json 时间戳统一转换
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
aop:
proxy-target-class: true
jpa:

View File

@ -9,6 +9,6 @@ ${AnsiColor.BRIGHT_BLUE}
${AnsiColor.BRIGHT_GREEN}
Jeecg Boot Version: 3.4.2
Jeecg Boot Version: 3.4.3
Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version}
${AnsiColor.BLACK}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-boot-parent</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -10,6 +10,6 @@ WORKDIR /jeecg-cloud-gateway
EXPOSE 9999
ADD ./target/jeecg-cloud-gateway-3.4.2.jar ./
ADD ./target/jeecg-cloud-gateway-3.4.3.jar ./
CMD sleep 50;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-gateway-3.4.2.jar
CMD sleep 50;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-gateway-3.4.3.jar

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-server-cloud</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-cloud-gateway</artifactId>

View File

@ -15,10 +15,7 @@ import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* 聚合各个服务的swagger接口
@ -44,7 +41,12 @@ public class MySwaggerResourceProvider implements SwaggerResourcesProvider {
*/
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String serverAddr;
/**
* nacos namespace
*/
@Value("${spring.cloud.nacos.discovery.namespace:#{null}}")
private String namespace;
/**
* Swagger中需要排除的服务
*/
@ -107,7 +109,14 @@ public class MySwaggerResourceProvider implements SwaggerResourcesProvider {
private Boolean checkRoute(String routeId) {
Boolean hasRoute = false;
try {
NamingService naming = NamingFactory.createNamingService(serverAddr);
//修复使用带命名空间启动网关swagger看不到接口文档的问题
Properties properties=new Properties();
properties.setProperty("serverAddr",serverAddr);
if(namespace!=null && !"".equals(namespace)){
properties.setProperty("namespace",namespace);
}
NamingService naming = NamingFactory.createNamingService(properties);
List<Instance> list = naming.selectInstances(routeId, true);
if (ObjectUtil.isNotEmpty(list)) {
hasRoute = true;

View File

@ -10,6 +10,6 @@ WORKDIR /jeecg-cloud-nacos
EXPOSE 8848
ADD ./target/jeecg-cloud-nacos-3.4.2.jar ./
ADD ./target/jeecg-cloud-nacos-3.4.3.jar ./
CMD sleep 5;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-nacos-3.4.2.jar
CMD sleep 5;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-nacos-3.4.3.jar

View File

@ -5,7 +5,7 @@
<artifactId>jeecg-cloud-nacos</artifactId>
<name>jeecg-cloud-nacos</name>
<description>nacos启动模块</description>
<version>3.4.2</version>
<version>3.4.3</version>
<!-- Nacos2不支持springboot2.6.6 -->
<parent>
@ -34,6 +34,10 @@
</repository>
</repositories>
<properties>
<log4j2.version>2.17.0</log4j2.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>

View File

@ -10,6 +10,6 @@ WORKDIR /jeecg-demo-cloud
EXPOSE 7002
ADD ./target/jeecg-demo-cloud-start-3.4.2.jar ./
ADD ./target/jeecg-demo-cloud-start-3.4.3.jar ./
CMD sleep 10;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-demo-cloud-start-3.4.2.jar
CMD sleep 10;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-demo-cloud-start-3.4.3.jar

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-server-cloud</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -10,6 +10,6 @@ WORKDIR /jeecg-system-cloud
EXPOSE 7001
ADD ./target/jeecg-system-cloud-start-3.4.2.jar ./
ADD ./target/jeecg-system-cloud-start-3.4.3.jar ./
CMD sleep 10;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-system-cloud-start-3.4.2.jar
CMD sleep 10;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-system-cloud-start-3.4.3.jar

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-server-cloud</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-system-cloud-start</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-visual</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.4.2</version>
<version>3.4.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-cloud-monitor</artifactId>

View File

@ -13,6 +13,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableAdminServer
public class JeecgMonitorApplication {
public static void main(String[] args) {
SpringApplication.run(JeecgMonitorApplication.class);
SpringApplication.run(JeecgMonitorApplication.class, args);
}
}

View File

@ -10,6 +10,6 @@ WORKDIR /jeecg-cloud-sentinel
EXPOSE 8848
ADD ./target/jeecg-cloud-sentinel-3.4.2.jar ./
ADD ./target/jeecg-cloud-sentinel-3.4.3.jar ./
CMD sleep 5;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-sentinel-3.4.2.jar
CMD sleep 5;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-sentinel-3.4.3.jar

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