mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-08 17:12:28 +08:00
Compare commits
73 Commits
v3.8.0
...
v3.8.0last
| Author | SHA1 | Date | |
|---|---|---|---|
| c8e33d43cb | |||
| 6c6af870ae | |||
| 79107599a6 | |||
| b6e8b37aee | |||
| 195ddd0421 | |||
| 9ddad931ff | |||
| fc05fe1aff | |||
| 7e325f68ca | |||
| f74da23d06 | |||
| e279915ba2 | |||
| bb6f077a95 | |||
| 7a031e6135 | |||
| 157877f9a6 | |||
| 25a71fa66c | |||
| fdc02fa68a | |||
| ec3c34969a | |||
| 5a215525d5 | |||
| 450b93d916 | |||
| 2d62bad2a9 | |||
| 0b10096f1c | |||
| 431ddb8fcb | |||
| ddf0f61ae5 | |||
| 4042579167 | |||
| bd5fda5968 | |||
| fdbd9c30ac | |||
| b8b4d3f29d | |||
| 78212aa7c0 | |||
| 6dc3c6af2a | |||
| b7a6812140 | |||
| 7efc51e30e | |||
| bd83b994bc | |||
| 8fb81f331c | |||
| fb188a83a1 | |||
| 9aea5de668 | |||
| 8979dd7ae9 | |||
| a56bd05389 | |||
| 9cf3328ea4 | |||
| 37c593e1d4 | |||
| 49ba40e98a | |||
| 0e184eaa64 | |||
| 86a3ed9dae | |||
| 94bff11eb1 | |||
| 590d73dfe3 | |||
| fe9630d15c | |||
| 77ae25b86a | |||
| 3b34276cf8 | |||
| cffba084fc | |||
| d1589acc41 | |||
| 0d18e536f0 | |||
| 21392c44f8 | |||
| 2730d8e06f | |||
| 0002606d41 | |||
| 8bd19484ee | |||
| 5d2db92613 | |||
| c1b39d21dd | |||
| e83c9b8190 | |||
| 9bd03f467d | |||
| e032591366 | |||
| de767e07b4 | |||
| b77d3e36ab | |||
| 7885aaed3b | |||
| 6f4c2eb77c | |||
| 3f0597a0f6 | |||
| 04a3764f00 | |||
| 68464109de | |||
| 79866c5823 | |||
| d64b8ecaef | |||
| ec9f2b146a | |||
| 69fd2888a1 | |||
| cb1d8e3527 | |||
| a89b299a4b | |||
| 7a15bfc161 | |||
| 65f7eb9542 |
14
README-AI.md
14
README-AI.md
@ -25,7 +25,7 @@ JeecgBoot平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
|||||||
> - 对话回复格式美观:
|
> - 对话回复格式美观:
|
||||||
> 在对话过程中,JEECG AI能够保持回复内容的原格式,也不丢失图片,使得输出的文章更加美观,不会出现格式错乱的情况,还支持图片的渲染。
|
> 在对话过程中,JEECG AI能够保持回复内容的原格式,也不丢失图片,使得输出的文章更加美观,不会出现格式错乱的情况,还支持图片的渲染。
|
||||||
> - PDF文档导入与格式转换:
|
> - PDF文档导入与格式转换:
|
||||||
> JEECG AI在处理PDF文档时,能够更好地保持原始格式和图片,确保转换后的内容与原始文档一致。这哥功能在许多AI产品中表现不佳,而JEECG AI在这方面做出了显著的优化
|
> JEECG AI在处理PDF文档时,能够更好地保持原始格式和图片,确保转换后的内容与原始文档一致。这个功能在许多AI产品中表现不佳,而JEECG AI在这方面做出了显著的优化
|
||||||
|
|
||||||
|
|
||||||
| 功能 | Dify | Jeecg AI |
|
| 功能 | Dify | Jeecg AI |
|
||||||
@ -63,11 +63,17 @@ JeecgBoot平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 产品体验
|
|
||||||
|
|
||||||
- 使用手册:https://help.jeecg.com/aigc
|
#### 在线体验
|
||||||
- 演示地址:https://boot3.jeecg.com
|
|
||||||
|
|
||||||
|
- JeecgBoot演示: https://boot3.jeecg.com
|
||||||
|
- 敲敲云在线搭建AI知识库:https://app.qiaoqiaoyun.com
|
||||||
|
|
||||||
|
|
||||||
|
## 技术交流
|
||||||
|
|
||||||
|
- 开发文档:https://help.jeecg.com/aigc
|
||||||
|
- QQ群:716488839
|
||||||
|
|
||||||
|
|
||||||
## 功能列表
|
## 功能列表
|
||||||
|
|||||||
49
README.md
49
README.md
@ -41,10 +41,22 @@ JeecgBoot 提供了一系列 `低代码能力`,实现`真正的零代码`在
|
|||||||
|
|
||||||
适用项目
|
适用项目
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
JeecgBoot AI低代码平台,可以应用在任何J2EE项目的开发中,支持信创国产化(默认适配达梦和人大金仓)。尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
JeecgBoot AI低代码平台,可以应用在任何J2EE项目的开发中,支持信创国产化。尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||||
又是一个全栈式 AI 开发平台,快速帮助企业构建和部署个性化的 AI 应用。
|
又是一个全栈式 AI 开发平台,快速帮助企业构建和部署个性化的 AI 应用。
|
||||||
|
|
||||||
|
|
||||||
|
信创国产化
|
||||||
|
-----------------------------------
|
||||||
|
JeecgBoot 是一个开源低代码开发平台,支持全信创环境。它兼容多种国产操作系统和数据库,包括:
|
||||||
|
|
||||||
|
- 操作系统:国产麒麟、银河麒麟等国产系统几乎都是基于 Linux 内核,因此它们具有良好的兼容性。
|
||||||
|
- 数据库:达梦、人大金仓、TiDB , [转库文档](https://my.oschina.net/jeecg/blog/4905722)
|
||||||
|
- 中间件:东方通 TongWeb、TongRDS,宝兰德 AppServer、CacheDB, [信创配置文档](https://help.jeecg.com/java/tongweb-deploy/)
|
||||||
|
|
||||||
|
通过这些适配,JeecgBoot 为使用国产软件和硬件的用户提供了高效的开发解决方案。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
项目说明
|
项目说明
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
@ -56,14 +68,6 @@ JeecgBoot AI低代码平台,可以应用在任何J2EE项目的开发中,支
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
技术文档
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
|
||||||
- 在线演示 : [平台演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [体验低代码](https://jeecg.blog.csdn.net/article/details/106079007) | [体验零代码](https://app.qiaoqiaoyun.com/myapps/index)
|
|
||||||
- 开发文档: [文档中心](https://help.jeecg.com) | [AIGC大模块](https://help.jeecg.com/aigc)
|
|
||||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [入门视频](http://jeecg.com/doc/video) | [如何反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md)
|
|
||||||
- QQ交流群 : ⑩716488839、⑨808791225(满)、其他(满)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +79,29 @@ JeecgBoot AI低代码平台,可以应用在任何J2EE项目的开发中,支
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在线体验
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
> JeecgBoot vs 敲敲云
|
||||||
|
> - JeecgBoot是低代码产品拥有很多低代码能力,比如流程设计、表单设计、大屏设计,代码生成器,适合半开发模式(开发+低代码结合),也可以集成零代码的应用管理模块;
|
||||||
|
> - 敲敲云是零代码产品,完全不写代码,通过配置搭建业务系统,其在jeecgboot基础上研发而成,删除了online、代码生成、OA等很多需要编码的功能,只保留了应用管理和聊天、流程、日程、文件四个标准OA功能
|
||||||
|
|
||||||
|
|
||||||
|
- JeecgBoot低代码: https://boot3.jeecg.com
|
||||||
|
- 敲敲云零代码:https://app.qiaoqiaoyun.com
|
||||||
|
- APP演示: http://jeecg.com/appIndex
|
||||||
|
|
||||||
|
技术文档
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
|
||||||
|
- 开发文档: [文档中心](https://help.jeecg.com) | [AIGC大模块](https://help.jeecg.com/aigc) | [低代码初体验一分钟](https://jeecg.blog.csdn.net/article/details/106079007)
|
||||||
|
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [入门视频](http://jeecg.com/doc/video) | [反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md)
|
||||||
|
- QQ交流群 : ⑩716488839、⑨808791225(满)、其他(满)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
AIGC应用平台介绍
|
AIGC应用平台介绍
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
@ -93,6 +120,8 @@ JeecgBoot 平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
|||||||
[](https://www.bilibili.com/video/BV1zmd7YFE4w)
|
[](https://www.bilibili.com/video/BV1zmd7YFE4w)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### Dify `VS` JEECG AI
|
##### Dify `VS` JEECG AI
|
||||||
|
|
||||||
> JEECG AI与Dify相比,在多个方面展现出显著的优势,特别是在文档处理、格式和图片保持方面。以下是一些具体的优点:
|
> JEECG AI与Dify相比,在多个方面展现出显著的优势,特别是在文档处理、格式和图片保持方面。以下是一些具体的优点:
|
||||||
@ -101,7 +130,7 @@ JeecgBoot 平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
|||||||
> - 对话回复格式美观:
|
> - 对话回复格式美观:
|
||||||
> 在对话过程中,JEECG AI能够保持回复内容的原格式,也不丢失图片,使得输出的文章更加美观,不会出现格式错乱的情况,还支持图片的渲染。
|
> 在对话过程中,JEECG AI能够保持回复内容的原格式,也不丢失图片,使得输出的文章更加美观,不会出现格式错乱的情况,还支持图片的渲染。
|
||||||
> - PDF文档导入与格式转换:
|
> - PDF文档导入与格式转换:
|
||||||
> JEECG AI在处理PDF文档时,能够更好地保持原始格式和图片,确保转换后的内容与原始文档一致。这哥功能在许多AI产品中表现不佳,而JEECG AI在这方面做出了显著的优化
|
> JEECG AI在处理PDF文档时,能够更好地保持原始格式和图片,确保转换后的内容与原始文档一致。这个功能在许多AI产品中表现不佳,而JEECG AI在这方面做出了显著的优化
|
||||||
|
|
||||||
##### 功能大模块
|
##### 功能大模块
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
170
jeecg-boot/db/基于AK和SK安全鉴权的OpenAPI/升级SQL脚本.sql
Normal file
170
jeecg-boot/db/基于AK和SK安全鉴权的OpenAPI/升级SQL脚本.sql
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : mysql5.7
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 50738 (5.7.38)
|
||||||
|
Source Host : 127.0.0.1:3306
|
||||||
|
Source Schema : jeecg-boot
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 50738 (5.7.38)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 15/05/2025 10:18:36
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for open_api
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `open_api`;
|
||||||
|
CREATE TABLE `open_api` (
|
||||||
|
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接口名称',
|
||||||
|
`request_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求方法',
|
||||||
|
`request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接口地址',
|
||||||
|
`black_list` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'IP 黑名单',
|
||||||
|
`body` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求体内容',
|
||||||
|
`origin_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '原始地址',
|
||||||
|
`status` int(10) NULL DEFAULT NULL COMMENT '状态',
|
||||||
|
`del_flag` int(10) NULL DEFAULT NULL COMMENT '删除标识',
|
||||||
|
`create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '修改人',
|
||||||
|
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`headers_json` json NULL COMMENT '请求头json',
|
||||||
|
`params_json` json NULL COMMENT '请求参数json',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '接口表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of open_api
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `open_api` VALUES ('1922132683346649090', '根据部门查询用户', 'GET', 'TEwcXBlr', NULL, NULL, '/sys/user/queryUserByDepId', 1, 0, 'admin', '2025-05-13 11:31:58', 'admin', '2025-05-15 10:10:01', '[]', '[{\"id\": \"row_24\", \"note\": \"\", \"paramKey\": \"id\", \"required\": \"1\", \"defaultValue\": \"\"}]');
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for open_api_auth
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `open_api_auth`;
|
||||||
|
CREATE TABLE `open_api_auth` (
|
||||||
|
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '授权名称',
|
||||||
|
`ak` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'AK',
|
||||||
|
`sk` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'SK',
|
||||||
|
`create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '修改人',
|
||||||
|
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`system_user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联系统用户名',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of open_api_auth
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `open_api_auth` VALUES ('1922164194775056386', 'scott', 'ak-pFjyNHWRsJEFWlu6', '4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS', 'admin', '2025-05-13 13:37:11', NULL, NULL, 'e9ca23d68d884d4ebb19d07889727dae');
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for open_api_log
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `open_api_log`;
|
||||||
|
CREATE TABLE `open_api_log` (
|
||||||
|
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`api_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接口ID',
|
||||||
|
`call_auth_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '调用ID',
|
||||||
|
`call_time` datetime NULL DEFAULT NULL COMMENT '调用时间',
|
||||||
|
`used_time` bigint(20) NULL DEFAULT NULL COMMENT '耗时',
|
||||||
|
`response_time` datetime NULL DEFAULT NULL COMMENT '响应时间',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '调用记录表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of open_api_log
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922175238557913090', '1922132683346649090', '1922164194775056386', '2025-05-13 14:21:04', 94, '2025-05-13 14:21:04');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922175436256432130', '1922132683346649090', '1922164194775056386', '2025-05-13 14:21:51', 38, '2025-05-13 14:21:51');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922175487921868802', '1922132683346649090', '1922164194775056386', '2025-05-13 14:22:03', 31, '2025-05-13 14:22:03');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922176033789562883', '1922132683346649090', '1922164194775056386', '2025-05-13 14:24:13', 27, '2025-05-13 14:24:13');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922176583943835650', '1922132683346649090', '1922164194775056386', '2025-05-13 14:26:25', 39, '2025-05-13 14:26:25');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922177249969934337', '1922132683346649090', '1922164194775056386', '2025-05-13 14:28:08', 55250, '2025-05-13 14:29:03');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922180212645941249', '1922132683346649090', '1922164194775056386', '2025-05-13 14:40:46', 4162, '2025-05-13 14:40:50');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922180441692688385', '1922132683346649090', '1922164194775056386', '2025-05-13 14:41:11', 33346, '2025-05-13 14:41:44');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922180521686454273', '1922132683346649090', '1922164194775056386', '2025-05-13 14:42:00', 3570, '2025-05-13 14:42:03');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922180965825499138', '1922132683346649090', '1922164194775056386', '2025-05-13 14:42:10', 99211, '2025-05-13 14:43:49');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922181034515615746', '1922132683346649090', '1922164194775056386', '2025-05-13 14:43:52', 14005, '2025-05-13 14:44:06');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922183171307982850', '1922132683346649090', '1922164194775056386', '2025-05-13 14:52:15', 19834, '2025-05-13 14:52:35');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922184177068523521', '1922132683346649090', '1922164194775056386', '2025-05-13 14:56:34', 748, '2025-05-13 14:56:35');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922184729043107841', '1922132683346649090', '1922164194775056386', '2025-05-13 14:58:46', 1031, '2025-05-13 14:58:47');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922184806453182465', '1922132683346649090', '1922164194775056386', '2025-05-13 14:59:05', 68, '2025-05-13 14:59:05');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922184918382379009', '1922132683346649090', '1922164194775056386', '2025-05-13 14:59:10', 22155, '2025-05-13 14:59:32');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922185292635844610', '1922132683346649090', '1922164194775056386', '2025-05-13 15:00:55', 6267, '2025-05-13 15:01:01');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922186002672791554', '1922132683346649090', '1922164194775056386', '2025-05-13 15:03:23', 27554, '2025-05-13 15:03:50');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922187506582425601', '1922132683346649090', '1922164194775056386', '2025-05-13 15:09:45', 3464, '2025-05-13 15:09:49');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922187586597163011', '1922132683346649090', '1922164194775056386', '2025-05-13 15:10:08', 82, '2025-05-13 15:10:08');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922187924741951490', '1922132683346649090', '1922164194775056386', '2025-05-13 15:10:49', 39590, '2025-05-13 15:11:28');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922188138710261761', '1922132683346649090', '1922164194775056386', '2025-05-13 15:12:19', 758, '2025-05-13 15:12:19');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922188290661507073', '1922132683346649090', '1922164194775056386', '2025-05-13 15:12:29', 26527, '2025-05-13 15:12:56');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922189701755424769', '1922132683346649090', '1922164194775056386', '2025-05-13 15:18:28', 3619, '2025-05-13 15:18:32');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922190076784803841', '1922132683346649090', '1922164194775056386', '2025-05-13 15:20:01', 741, '2025-05-13 15:20:02');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922836671113101313', '1922132683346649090', '1922164194775056386', '2025-05-15 10:09:21', 186, '2025-05-15 10:09:22');
|
||||||
|
INSERT INTO `open_api_log` VALUES ('1922836856287428610', '1922132683346649090', '1922164194775056386', '2025-05-15 10:10:06', 145, '2025-05-15 10:10:06');
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for open_api_permission
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `open_api_permission`;
|
||||||
|
CREATE TABLE `open_api_permission` (
|
||||||
|
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`api_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接口ID',
|
||||||
|
`api_auth_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '认证ID',
|
||||||
|
`create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新人',
|
||||||
|
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'openapi授权' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of open_api_permission
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `open_api_permission` VALUES ('1922164225875820545', '1922132683346649090', '1922164194775056386', 'admin', '2025-05-13 13:37:18', NULL, NULL);
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|
||||||
|
|
||||||
|
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 ('1917957565728198657', '1922109301837606914', '接口文档', '/openapi/SwaggerUI', 'openapi/SwaggerUI', 1, '', null, 1, null, '0', 1, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 23:01:32', 'admin', '2025-05-13 09:59:46', 0, 0, null, 0);
|
||||||
|
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 ('1922109301837606914', '', 'OpenApi管理', '/openapi', 'layouts/RouteView', 1, '', null, 0, null, '0', 12.1, 0, 'ant-design:swap-outlined', 0, 0, 0, 0, null, 'admin', '2025-05-13 09:59:03', 'admin', '2025-05-13 10:02:43', 0, 0, null, 0);
|
||||||
|
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 ('2025050104193340030', '1922109301837606914', '接口管理', '/openapi/openApiList', 'openapi/OpenApiList', 1, null, null, 1, null, '1', 0, 0, null, 0, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', 'admin', '2025-05-13 09:59:24', 0, 0, '1', 0);
|
||||||
|
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 ('2025050104193350031', '2025050104193340030', '添加接口管理', null, null, 0, null, null, 2, 'openapi:open_api:add', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050104193350032', '2025050104193340030', '编辑接口管理', null, null, 0, null, null, 2, 'openapi:open_api:edit', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050104193350033', '2025050104193340030', '删除接口管理', null, null, 0, null, null, 2, 'openapi:open_api:delete', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050104193350034', '2025050104193340030', '批量删除接口管理', null, null, 0, null, null, 2, 'openapi:open_api:deleteBatch', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050104193350035', '2025050104193340030', '导出excel_接口管理', null, null, 0, null, null, 2, 'openapi:open_api:exportXls', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050104193350036', '2025050104193340030', '导入excel_接口管理', null, null, 0, null, null, 2, 'openapi:open_api:importExcel', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 16:19:03', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940200', '1922109301837606914', '授权管理', '/openapi/openApiAuthList', 'openapi/OpenApiAuthList', 1, null, null, 1, null, '1', 0, 0, null, 0, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', 'admin', '2025-05-13 09:59:35', 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940201', '2025050105554940200', '添加授权管理', null, null, 0, null, null, 2, 'openapi:open_api_auth:add', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940202', '2025050105554940200', '编辑授权管理', null, null, 0, null, null, 2, 'openapi:open_api_auth:edit', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940203', '2025050105554940200', '删除授权管理', null, null, 0, null, null, 2, 'openapi:open_api_auth:delete', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940204', '2025050105554940200', '批量删除授权管理', null, null, 0, null, null, 2, 'openapi:open_api_auth:deleteBatch', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940205', '2025050105554940200', '导出excel_授权管理', null, null, 0, null, null, 2, 'openapi:open_api_auth:exportXls', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', null, null, 0, 0, '1', 0);
|
||||||
|
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 ('2025050105554940206', '2025050105554940200', '导入excel_授权管理', null, null, 0, null, null, 2, 'openapi:open_api_auth:importExcel', '1', null, 0, null, 1, 0, 0, 0, null, 'admin', '2025-05-01 17:55:20', null, null, 0, 0, '1', 0);
|
||||||
|
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917957659860963330', 'f6817f48af4fb3af11b9e8bf182f618b', '1917957565728198657', null, '2025-05-01 23:01:55', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1922109760551858178', 'f6817f48af4fb3af11b9e8bf182f618b', '1922109301837606914', null, '2025-05-13 10:00:53', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071739539457', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193340030', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071806648321', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193350031', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071806648322', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193350032', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071806648323', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193350033', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071806648324', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193350034', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071806648325', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193350035', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917857071806648326', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050104193350036', null, '2025-05-01 16:22:13', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917881149426864129', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050105554940200', null, '2025-05-01 17:57:53', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917881149431058436', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050105554940203', null, '2025-05-01 17:57:53', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917881149431058437', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050105554940204', null, '2025-05-01 17:57:53', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917881149431058438', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050105554940205', null, '2025-05-01 17:57:53', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917881149431058439', 'f6817f48af4fb3af11b9e8bf182f618b', '2025050105554940206', null, '2025-05-01 17:57:53', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1917957659860963330', 'f6817f48af4fb3af11b9e8bf182f618b', '1917957565728198657', null, '2025-05-01 23:01:55', '0:0:0:0:0:0:0:1');
|
||||||
|
INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) VALUES ('1922109760551858178', 'f6817f48af4fb3af11b9e8bf182f618b', '1922109301837606914', null, '2025-05-13 10:00:53', '0:0:0:0:0:0:0:1');
|
||||||
@ -314,5 +314,10 @@
|
|||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- minidao -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework</groupId>
|
||||||
|
<artifactId>minidao-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@ -36,7 +36,6 @@ public class RestUtil {
|
|||||||
}
|
}
|
||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getPath() {
|
private static String getPath() {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
path = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("server.servlet.context-path");
|
path = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("server.servlet.context-path");
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -16,7 +17,13 @@ import java.util.regex.Pattern;
|
|||||||
* @author zhoujf
|
* @author zhoujf
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SqlInjectionUtil {
|
public class SqlInjectionUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql注入黑名单数据库名
|
||||||
|
*/
|
||||||
|
public final static String XSS_STR_TABLE = "peformance_schema|information_schema";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认—sql注入关键词
|
* 默认—sql注入关键词
|
||||||
*/
|
*/
|
||||||
@ -167,7 +174,28 @@ public class SqlInjectionUtil {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否存在SQL注入关键词字符串
|
||||||
|
*
|
||||||
|
* @param keyword
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("AlibabaUndefineMagicConstant")
|
||||||
|
private static boolean isExistSqlInjectTableKeyword(String sql, String keyword) {
|
||||||
|
// 需要匹配的,sql注入关键词
|
||||||
|
String[] matchingTexts = new String[]{"`" + keyword, "(" + keyword, "(`" + keyword};
|
||||||
|
for (String matchingText : matchingTexts) {
|
||||||
|
String[] checkTexts = new String[]{" " + matchingText, "from" + matchingText};
|
||||||
|
for (String checkText : checkTexts) {
|
||||||
|
if (sql.contains(checkText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sql注入过滤处理,遇到注入关键字抛异常
|
* sql注入过滤处理,遇到注入关键字抛异常
|
||||||
*
|
*
|
||||||
@ -208,6 +236,14 @@ public class SqlInjectionUtil {
|
|||||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String[] xssTableArr = XSS_STR_TABLE.split("\\|");
|
||||||
|
for (String xssTableStr : xssTableArr) {
|
||||||
|
if (isExistSqlInjectTableKeyword(value, xssTableStr)) {
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssTableStr);
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 三、SQL注入检测存在绕过风险 (正则校验)
|
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||||
@ -244,6 +280,14 @@ public class SqlInjectionUtil {
|
|||||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String[] xssTableArr = XSS_STR_TABLE.split("\\|");
|
||||||
|
for (String xssTableStr : xssTableArr) {
|
||||||
|
if (isExistSqlInjectTableKeyword(value, xssTableStr)) {
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssTableStr);
|
||||||
|
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||||
|
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 三、SQL注入检测存在绕过风险 (正则校验)
|
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package org.jeecg.common.util.security;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||||
|
import org.jeecg.common.util.SqlInjectionUtil;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -66,6 +68,8 @@ public abstract class AbstractQueryBlackListHandler {
|
|||||||
if(flag == false){
|
if(flag == false){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Set<String> xssTableSet = new HashSet<>(Arrays.asList(SqlInjectionUtil.XSS_STR_TABLE.split("\\|")));
|
||||||
|
|
||||||
for (QueryTable table : list) {
|
for (QueryTable table : list) {
|
||||||
String name = table.getName();
|
String name = table.getName();
|
||||||
String fieldRule = ruleMap.get(name);
|
String fieldRule = ruleMap.get(name);
|
||||||
@ -81,6 +85,16 @@ public abstract class AbstractQueryBlackListHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// 判断是否调用了黑名单数据库
|
||||||
|
String dbName = table.getDbName();
|
||||||
|
if (oConvertUtils.isNotEmpty(dbName)) {
|
||||||
|
dbName = dbName.toLowerCase().trim();
|
||||||
|
if (xssTableSet.contains(dbName)) {
|
||||||
|
flag = false;
|
||||||
|
log.warn("sql黑名单校验,数据库【" + dbName + "】禁止查询");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回黑名单校验结果(不合法直接抛出异常)
|
// 返回黑名单校验结果(不合法直接抛出异常)
|
||||||
@ -135,6 +149,8 @@ public abstract class AbstractQueryBlackListHandler {
|
|||||||
* 查询的表的信息
|
* 查询的表的信息
|
||||||
*/
|
*/
|
||||||
protected class QueryTable {
|
protected class QueryTable {
|
||||||
|
//数据库名
|
||||||
|
private String dbName;
|
||||||
//表名
|
//表名
|
||||||
private String name;
|
private String name;
|
||||||
//表的别名
|
//表的别名
|
||||||
@ -158,6 +174,14 @@ public abstract class AbstractQueryBlackListHandler {
|
|||||||
this.fields.add(field);
|
this.fields.add(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDbName() {
|
||||||
|
return dbName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDbName(String dbName) {
|
||||||
|
this.dbName = dbName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,255 +1,255 @@
|
|||||||
package org.jeecg.common.util.sqlparse;
|
//package org.jeecg.common.util.sqlparse;
|
||||||
|
//
|
||||||
import lombok.extern.slf4j.Slf4j;
|
//import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
//import net.sf.jsqlparser.JSQLParserException;
|
||||||
import net.sf.jsqlparser.expression.*;
|
//import net.sf.jsqlparser.expression.*;
|
||||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
//import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||||
import net.sf.jsqlparser.schema.Column;
|
//import net.sf.jsqlparser.schema.Column;
|
||||||
import net.sf.jsqlparser.schema.Table;
|
//import net.sf.jsqlparser.schema.Table;
|
||||||
import net.sf.jsqlparser.statement.Statement;
|
//import net.sf.jsqlparser.statement.Statement;
|
||||||
import net.sf.jsqlparser.statement.select.*;
|
//import net.sf.jsqlparser.statement.select.*;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
//import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
//import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||||
|
//
|
||||||
import java.io.StringReader;
|
//import java.io.StringReader;
|
||||||
import java.util.ArrayList;
|
//import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
//import java.util.HashMap;
|
||||||
import java.util.List;
|
//import java.util.List;
|
||||||
import java.util.Map;
|
//import java.util.Map;
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
* 解析所有表名和字段的类
|
// * 解析所有表名和字段的类
|
||||||
*/
|
// */
|
||||||
@Slf4j
|
//@Slf4j
|
||||||
public class JSqlParserAllTableManager {
|
//public class JSqlParserAllTableManager {
|
||||||
|
//
|
||||||
private final String sql;
|
// private final String sql;
|
||||||
private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
// private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
||||||
/**
|
// /**
|
||||||
* 别名对应实际表名
|
// * 别名对应实际表名
|
||||||
*/
|
// */
|
||||||
private final Map<String, String> tableAliasMap = new HashMap<>();
|
// private final Map<String, String> tableAliasMap = new HashMap<>();
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 解析后的sql
|
// * 解析后的sql
|
||||||
*/
|
// */
|
||||||
private String parsedSql = null;
|
// private String parsedSql = null;
|
||||||
|
//
|
||||||
JSqlParserAllTableManager(String selectSql) {
|
// JSqlParserAllTableManager(String selectSql) {
|
||||||
this.sql = selectSql;
|
// this.sql = selectSql;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 开始解析
|
// * 开始解析
|
||||||
*
|
// *
|
||||||
* @return
|
// * @return
|
||||||
* @throws JSQLParserException
|
// * @throws JSQLParserException
|
||||||
*/
|
// */
|
||||||
public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
// public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
||||||
// 1. 创建解析器
|
// // 1. 创建解析器
|
||||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
// CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||||
// 2. 使用解析器解析sql生成具有层次结构的java类
|
// // 2. 使用解析器解析sql生成具有层次结构的java类
|
||||||
Statement stmt = mgr.parse(new StringReader(this.sql));
|
// Statement stmt = mgr.parse(new StringReader(this.sql));
|
||||||
if (stmt instanceof Select) {
|
// if (stmt instanceof Select) {
|
||||||
Select selectStatement = (Select) stmt;
|
// Select selectStatement = (Select) stmt;
|
||||||
SelectBody selectBody = selectStatement.getSelectBody();
|
// SelectBody selectBody = selectStatement.getSelectBody();
|
||||||
this.parsedSql = selectBody.toString();
|
// this.parsedSql = selectBody.toString();
|
||||||
// 3. 解析select查询sql的信息
|
// // 3. 解析select查询sql的信息
|
||||||
if (selectBody instanceof PlainSelect) {
|
// if (selectBody instanceof PlainSelect) {
|
||||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
// PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||||
// 4. 合并 fromItems
|
// // 4. 合并 fromItems
|
||||||
List<FromItem> fromItems = new ArrayList<>();
|
// List<FromItem> fromItems = new ArrayList<>();
|
||||||
fromItems.add(plainSelect.getFromItem());
|
// fromItems.add(plainSelect.getFromItem());
|
||||||
// 4.1 处理join的表
|
// // 4.1 处理join的表
|
||||||
List<Join> joins = plainSelect.getJoins();
|
// List<Join> joins = plainSelect.getJoins();
|
||||||
if (joins != null) {
|
// if (joins != null) {
|
||||||
joins.forEach(join -> fromItems.add(join.getRightItem()));
|
// joins.forEach(join -> fromItems.add(join.getRightItem()));
|
||||||
}
|
// }
|
||||||
// 5. 处理 fromItems
|
// // 5. 处理 fromItems
|
||||||
for (FromItem fromItem : fromItems) {
|
// for (FromItem fromItem : fromItems) {
|
||||||
// 5.1 通过表名的方式from
|
// // 5.1 通过表名的方式from
|
||||||
if (fromItem instanceof Table) {
|
// if (fromItem instanceof Table) {
|
||||||
this.addSqlInfoByTable((Table) fromItem);
|
// this.addSqlInfoByTable((Table) fromItem);
|
||||||
}
|
// }
|
||||||
// 5.2 通过子查询的方式from
|
// // 5.2 通过子查询的方式from
|
||||||
else if (fromItem instanceof SubSelect) {
|
// else if (fromItem instanceof SubSelect) {
|
||||||
this.handleSubSelect((SubSelect) fromItem);
|
// this.handleSubSelect((SubSelect) fromItem);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// 6. 解析 selectFields
|
// // 6. 解析 selectFields
|
||||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
// List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||||
for (SelectItem selectItem : selectItems) {
|
// for (SelectItem selectItem : selectItems) {
|
||||||
// 6.1 查询的是全部字段
|
// // 6.1 查询的是全部字段
|
||||||
if (selectItem instanceof AllColumns) {
|
// if (selectItem instanceof AllColumns) {
|
||||||
// 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
// // 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
||||||
String tableName = plainSelect.getFromItem(Table.class).getName();
|
// String tableName = plainSelect.getFromItem(Table.class).getName();
|
||||||
// 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
// // 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
||||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
// SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||||
assert sqlInfo != null;
|
// assert sqlInfo != null;
|
||||||
// 设置为查询全部字段
|
// // 设置为查询全部字段
|
||||||
sqlInfo.setSelectAll(true);
|
// sqlInfo.setSelectAll(true);
|
||||||
sqlInfo.setSelectFields(null);
|
// sqlInfo.setSelectFields(null);
|
||||||
sqlInfo.setRealSelectFields(null);
|
// sqlInfo.setRealSelectFields(null);
|
||||||
}
|
// }
|
||||||
// 6.2 查询的是带表别名( u.* )的全部字段
|
// // 6.2 查询的是带表别名( u.* )的全部字段
|
||||||
else if (selectItem instanceof AllTableColumns) {
|
// else if (selectItem instanceof AllTableColumns) {
|
||||||
AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
// AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
||||||
String aliasName = allTableColumns.getTable().getName();
|
// String aliasName = allTableColumns.getTable().getName();
|
||||||
// 通过别名获取表名
|
// // 通过别名获取表名
|
||||||
String tableName = this.tableAliasMap.get(aliasName);
|
// String tableName = this.tableAliasMap.get(aliasName);
|
||||||
if (tableName == null) {
|
// if (tableName == null) {
|
||||||
tableName = aliasName;
|
// tableName = aliasName;
|
||||||
}
|
// }
|
||||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
// SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||||
// 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
// // 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
||||||
if (sqlInfo != null) {
|
// if (sqlInfo != null) {
|
||||||
// 设置为查询全部字段
|
// // 设置为查询全部字段
|
||||||
sqlInfo.setSelectAll(true);
|
// sqlInfo.setSelectAll(true);
|
||||||
sqlInfo.setSelectFields(null);
|
// sqlInfo.setSelectFields(null);
|
||||||
sqlInfo.setRealSelectFields(null);
|
// sqlInfo.setRealSelectFields(null);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// 6.3 各种字段表达式处理
|
// // 6.3 各种字段表达式处理
|
||||||
else if (selectItem instanceof SelectExpressionItem) {
|
// else if (selectItem instanceof SelectExpressionItem) {
|
||||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
// SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||||
Expression expression = selectExpressionItem.getExpression();
|
// Expression expression = selectExpressionItem.getExpression();
|
||||||
Alias alias = selectExpressionItem.getAlias();
|
// Alias alias = selectExpressionItem.getAlias();
|
||||||
this.handleExpression(expression, alias, plainSelect.getFromItem());
|
// this.handleExpression(expression, alias, plainSelect.getFromItem());
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
// log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
// throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// 非 select 查询sql,不做处理
|
// // 非 select 查询sql,不做处理
|
||||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
// throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||||
}
|
// }
|
||||||
return this.allTableMap;
|
// return this.allTableMap;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 处理子查询
|
// * 处理子查询
|
||||||
*
|
// *
|
||||||
* @param subSelect
|
// * @param subSelect
|
||||||
*/
|
// */
|
||||||
private void handleSubSelect(SubSelect subSelect) {
|
// private void handleSubSelect(SubSelect subSelect) {
|
||||||
try {
|
// try {
|
||||||
String subSelectSql = subSelect.getSelectBody().toString();
|
// String subSelectSql = subSelect.getSelectBody().toString();
|
||||||
// 递归调用解析
|
// // 递归调用解析
|
||||||
Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
// Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
||||||
if (map != null) {
|
// if (map != null) {
|
||||||
this.assignMap(map);
|
// this.assignMap(map);
|
||||||
}
|
// }
|
||||||
} catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
log.error("解析子查询出错", e);
|
// log.error("解析子查询出错", e);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 处理查询字段表达式
|
// * 处理查询字段表达式
|
||||||
*
|
// *
|
||||||
* @param expression
|
// * @param expression
|
||||||
*/
|
// */
|
||||||
private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
// private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
||||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
// // 处理函数式字段 CONCAT(name,'(',age,')')
|
||||||
if (expression instanceof Function) {
|
// if (expression instanceof Function) {
|
||||||
Function functionExp = (Function) expression;
|
// Function functionExp = (Function) expression;
|
||||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
// List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||||
for (Expression expItem : expressions) {
|
// for (Expression expItem : expressions) {
|
||||||
this.handleExpression(expItem, null, fromItem);
|
// this.handleExpression(expItem, null, fromItem);
|
||||||
}
|
// }
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// 处理字段上的子查询
|
// // 处理字段上的子查询
|
||||||
if (expression instanceof SubSelect) {
|
// if (expression instanceof SubSelect) {
|
||||||
this.handleSubSelect((SubSelect) expression);
|
// this.handleSubSelect((SubSelect) expression);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// 不处理字面量
|
// // 不处理字面量
|
||||||
if (expression instanceof StringValue ||
|
// if (expression instanceof StringValue ||
|
||||||
expression instanceof NullValue ||
|
// expression instanceof NullValue ||
|
||||||
expression instanceof LongValue ||
|
// expression instanceof LongValue ||
|
||||||
expression instanceof DoubleValue ||
|
// expression instanceof DoubleValue ||
|
||||||
expression instanceof HexValue ||
|
// expression instanceof HexValue ||
|
||||||
expression instanceof DateValue ||
|
// expression instanceof DateValue ||
|
||||||
expression instanceof TimestampValue ||
|
// expression instanceof TimestampValue ||
|
||||||
expression instanceof TimeValue
|
// expression instanceof TimeValue
|
||||||
) {
|
// ) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// 处理字段
|
// // 处理字段
|
||||||
if (expression instanceof Column) {
|
// if (expression instanceof Column) {
|
||||||
Column column = (Column) expression;
|
// Column column = (Column) expression;
|
||||||
// 查询字段名
|
// // 查询字段名
|
||||||
String fieldName = column.getColumnName();
|
// String fieldName = column.getColumnName();
|
||||||
String aliasName = fieldName;
|
// String aliasName = fieldName;
|
||||||
if (alias != null) {
|
// if (alias != null) {
|
||||||
aliasName = alias.getName();
|
// aliasName = alias.getName();
|
||||||
}
|
// }
|
||||||
String tableName;
|
// String tableName;
|
||||||
if (column.getTable() != null) {
|
// if (column.getTable() != null) {
|
||||||
// 通过列的表名获取 sqlInfo
|
// // 通过列的表名获取 sqlInfo
|
||||||
// 例如 user.name,这里的 tableName 就是 user
|
// // 例如 user.name,这里的 tableName 就是 user
|
||||||
tableName = column.getTable().getName();
|
// tableName = column.getTable().getName();
|
||||||
// 有可能是别名,需要转换为真实表名
|
// // 有可能是别名,需要转换为真实表名
|
||||||
if (this.tableAliasMap.get(tableName) != null) {
|
// if (this.tableAliasMap.get(tableName) != null) {
|
||||||
tableName = this.tableAliasMap.get(tableName);
|
// tableName = this.tableAliasMap.get(tableName);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// 当column的table为空时,说明是 fromItem 中的字段
|
// // 当column的table为空时,说明是 fromItem 中的字段
|
||||||
tableName = ((Table) fromItem).getName();
|
// tableName = ((Table) fromItem).getName();
|
||||||
}
|
// }
|
||||||
SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
// SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
||||||
if ($sqlInfo != null) {
|
// if ($sqlInfo != null) {
|
||||||
$sqlInfo.addSelectField(aliasName, fieldName);
|
// $sqlInfo.addSelectField(aliasName, fieldName);
|
||||||
} else {
|
// } else {
|
||||||
log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
// log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 根据表名添加sqlInfo
|
// * 根据表名添加sqlInfo
|
||||||
*
|
// *
|
||||||
* @param table
|
// * @param table
|
||||||
*/
|
// */
|
||||||
private void addSqlInfoByTable(Table table) {
|
// private void addSqlInfoByTable(Table table) {
|
||||||
String tableName = table.getName();
|
// String tableName = table.getName();
|
||||||
// 解析 aliasName
|
// // 解析 aliasName
|
||||||
if (table.getAlias() != null) {
|
// if (table.getAlias() != null) {
|
||||||
this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
// this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
||||||
}
|
// }
|
||||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
// SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
||||||
sqlInfo.setFromTableName(table.getName());
|
// sqlInfo.setFromTableName(table.getName());
|
||||||
this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
// this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 合并map
|
// * 合并map
|
||||||
*
|
// *
|
||||||
* @param source
|
// * @param source
|
||||||
*/
|
// */
|
||||||
private void assignMap(Map<String, SelectSqlInfo> source) {
|
// private void assignMap(Map<String, SelectSqlInfo> source) {
|
||||||
for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
// for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
||||||
SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
// SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
||||||
if (sqlInfo == null) {
|
// if (sqlInfo == null) {
|
||||||
this.allTableMap.put(entry.getKey(), entry.getValue());
|
// this.allTableMap.put(entry.getKey(), entry.getValue());
|
||||||
} else {
|
// } else {
|
||||||
// 合并
|
// // 合并
|
||||||
if (sqlInfo.getSelectFields() == null) {
|
// if (sqlInfo.getSelectFields() == null) {
|
||||||
sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
// sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
||||||
} else {
|
// } else {
|
||||||
sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
// sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
||||||
}
|
// }
|
||||||
if (sqlInfo.getRealSelectFields() == null) {
|
// if (sqlInfo.getRealSelectFields() == null) {
|
||||||
sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
// sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
||||||
} else {
|
// } else {
|
||||||
sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
// sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|||||||
@ -1,190 +1,190 @@
|
|||||||
package org.jeecg.common.util.sqlparse;
|
//package org.jeecg.common.util.sqlparse;
|
||||||
|
//
|
||||||
import lombok.extern.slf4j.Slf4j;
|
//import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
//import net.sf.jsqlparser.JSQLParserException;
|
||||||
import net.sf.jsqlparser.expression.*;
|
//import net.sf.jsqlparser.expression.*;
|
||||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
//import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||||
import net.sf.jsqlparser.schema.Column;
|
//import net.sf.jsqlparser.schema.Column;
|
||||||
import net.sf.jsqlparser.schema.Table;
|
//import net.sf.jsqlparser.schema.Table;
|
||||||
import net.sf.jsqlparser.statement.Statement;
|
//import net.sf.jsqlparser.statement.Statement;
|
||||||
import net.sf.jsqlparser.statement.select.*;
|
//import net.sf.jsqlparser.statement.select.*;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
//import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
//import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
//import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||||
|
//
|
||||||
import java.io.StringReader;
|
//import java.io.StringReader;
|
||||||
import java.util.List;
|
//import java.util.List;
|
||||||
import java.util.Map;
|
//import java.util.Map;
|
||||||
|
//
|
||||||
@Slf4j
|
//@Slf4j
|
||||||
public class JSqlParserUtils {
|
//public class JSqlParserUtils {
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 解析 查询(select)sql的信息,
|
// * 解析 查询(select)sql的信息,
|
||||||
* 此方法会展开所有子查询到一个map里,
|
// * 此方法会展开所有子查询到一个map里,
|
||||||
* key只存真实的表名,如果查询的没有真实的表名,则会被忽略。
|
// * key只存真实的表名,如果查询的没有真实的表名,则会被忽略。
|
||||||
* value只存真实的字段名,如果查询的没有真实的字段名,则会被忽略。
|
// * value只存真实的字段名,如果查询的没有真实的字段名,则会被忽略。
|
||||||
* <p>
|
// * <p>
|
||||||
* 例如:SELECT a.*,d.age,(SELECT count(1) FROM sys_depart) AS count FROM (SELECT username AS foo, realname FROM sys_user) a, demo d
|
// * 例如:SELECT a.*,d.age,(SELECT count(1) FROM sys_depart) AS count FROM (SELECT username AS foo, realname FROM sys_user) a, demo d
|
||||||
* 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
|
// * 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
|
||||||
*
|
// *
|
||||||
* @param selectSql
|
// * @param selectSql
|
||||||
* @return
|
// * @return
|
||||||
*/
|
// */
|
||||||
public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
|
// public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
|
||||||
if (oConvertUtils.isEmpty(selectSql)) {
|
// if (oConvertUtils.isEmpty(selectSql)) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
// log.info("解析查询Sql:{}", selectSql);
|
// // log.info("解析查询Sql:{}", selectSql);
|
||||||
JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
|
// JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
|
||||||
return allTableManager.parse();
|
// return allTableManager.parse();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 解析 查询(select)sql的信息,子查询嵌套
|
// * 解析 查询(select)sql的信息,子查询嵌套
|
||||||
*
|
// *
|
||||||
* @param selectSql
|
// * @param selectSql
|
||||||
* @return
|
// * @return
|
||||||
*/
|
// */
|
||||||
public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
|
// public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
|
||||||
if (oConvertUtils.isEmpty(selectSql)) {
|
// if (oConvertUtils.isEmpty(selectSql)) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
// log.info("解析查询Sql:{}", selectSql);
|
// // log.info("解析查询Sql:{}", selectSql);
|
||||||
// 使用 JSqlParer 解析sql
|
// // 使用 JSqlParer 解析sql
|
||||||
// 1、创建解析器
|
// // 1、创建解析器
|
||||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
// CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||||
// 2、使用解析器解析sql生成具有层次结构的java类
|
// // 2、使用解析器解析sql生成具有层次结构的java类
|
||||||
Statement stmt = mgr.parse(new StringReader(selectSql));
|
// Statement stmt = mgr.parse(new StringReader(selectSql));
|
||||||
if (stmt instanceof Select) {
|
// if (stmt instanceof Select) {
|
||||||
Select selectStatement = (Select) stmt;
|
// Select selectStatement = (Select) stmt;
|
||||||
// 3、解析select查询sql的信息
|
// // 3、解析select查询sql的信息
|
||||||
return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
|
// return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
|
||||||
} else {
|
// } else {
|
||||||
// 非 select 查询sql,不做处理
|
// // 非 select 查询sql,不做处理
|
||||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
// throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 解析 select 查询sql的信息
|
// * 解析 select 查询sql的信息
|
||||||
*
|
// *
|
||||||
* @param selectBody
|
// * @param selectBody
|
||||||
* @return
|
// * @return
|
||||||
*/
|
// */
|
||||||
private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
// private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
||||||
// 判断是否使用了union等操作
|
// // 判断是否使用了union等操作
|
||||||
if (selectBody instanceof SetOperationList) {
|
// if (selectBody instanceof SetOperationList) {
|
||||||
// 如果使用了union等操作,则只解析第一个查询
|
// // 如果使用了union等操作,则只解析第一个查询
|
||||||
List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
// List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
||||||
return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
// return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
||||||
}
|
// }
|
||||||
// 简单的select查询
|
// // 简单的select查询
|
||||||
if (selectBody instanceof PlainSelect) {
|
// if (selectBody instanceof PlainSelect) {
|
||||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
// SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
||||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
// PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||||
FromItem fromItem = plainSelect.getFromItem();
|
// FromItem fromItem = plainSelect.getFromItem();
|
||||||
// 解析 aliasName
|
// // 解析 aliasName
|
||||||
if (fromItem.getAlias() != null) {
|
// if (fromItem.getAlias() != null) {
|
||||||
sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
|
// sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
|
||||||
}
|
// }
|
||||||
// 解析 表名
|
// // 解析 表名
|
||||||
if (fromItem instanceof Table) {
|
// if (fromItem instanceof Table) {
|
||||||
// 通过表名的方式from
|
// // 通过表名的方式from
|
||||||
Table fromTable = (Table) fromItem;
|
// Table fromTable = (Table) fromItem;
|
||||||
sqlInfo.setFromTableName(fromTable.getName());
|
// sqlInfo.setFromTableName(fromTable.getName());
|
||||||
} else if (fromItem instanceof SubSelect) {
|
// } else if (fromItem instanceof SubSelect) {
|
||||||
// 通过子查询的方式from
|
// // 通过子查询的方式from
|
||||||
SubSelect fromSubSelect = (SubSelect) fromItem;
|
// SubSelect fromSubSelect = (SubSelect) fromItem;
|
||||||
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
|
// SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
|
||||||
sqlInfo.setFromSubSelect(subSqlInfo);
|
// sqlInfo.setFromSubSelect(subSqlInfo);
|
||||||
}
|
// }
|
||||||
// 解析 selectFields
|
// // 解析 selectFields
|
||||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
// List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||||
for (SelectItem selectItem : selectItems) {
|
// for (SelectItem selectItem : selectItems) {
|
||||||
if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
|
// if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
|
||||||
// 全部字段
|
// // 全部字段
|
||||||
sqlInfo.setSelectAll(true);
|
// sqlInfo.setSelectAll(true);
|
||||||
sqlInfo.setSelectFields(null);
|
// sqlInfo.setSelectFields(null);
|
||||||
sqlInfo.setRealSelectFields(null);
|
// sqlInfo.setRealSelectFields(null);
|
||||||
break;
|
// break;
|
||||||
} else if (selectItem instanceof SelectExpressionItem) {
|
// } else if (selectItem instanceof SelectExpressionItem) {
|
||||||
// 获取单个查询字段名
|
// // 获取单个查询字段名
|
||||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
// SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||||
Expression expression = selectExpressionItem.getExpression();
|
// Expression expression = selectExpressionItem.getExpression();
|
||||||
Alias alias = selectExpressionItem.getAlias();
|
// Alias alias = selectExpressionItem.getAlias();
|
||||||
JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
|
// JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return sqlInfo;
|
// return sqlInfo;
|
||||||
} else {
|
// } else {
|
||||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
// log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
// throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 处理查询字段表达式
|
// * 处理查询字段表达式
|
||||||
*
|
// *
|
||||||
* @param sqlInfo
|
// * @param sqlInfo
|
||||||
* @param expression
|
// * @param expression
|
||||||
* @param alias 是否有别名,无传null
|
// * @param alias 是否有别名,无传null
|
||||||
*/
|
// */
|
||||||
private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
|
// private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
|
||||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
// // 处理函数式字段 CONCAT(name,'(',age,')')
|
||||||
if (expression instanceof Function) {
|
// if (expression instanceof Function) {
|
||||||
JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
|
// JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// 处理字段上的子查询
|
// // 处理字段上的子查询
|
||||||
if (expression instanceof SubSelect) {
|
// if (expression instanceof SubSelect) {
|
||||||
SubSelect subSelect = (SubSelect) expression;
|
// SubSelect subSelect = (SubSelect) expression;
|
||||||
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
|
// SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
|
||||||
// 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
|
// // 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
|
||||||
sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
|
// sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
|
||||||
sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
|
// sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// 不处理字面量
|
// // 不处理字面量
|
||||||
if (expression instanceof StringValue ||
|
// if (expression instanceof StringValue ||
|
||||||
expression instanceof NullValue ||
|
// expression instanceof NullValue ||
|
||||||
expression instanceof LongValue ||
|
// expression instanceof LongValue ||
|
||||||
expression instanceof DoubleValue ||
|
// expression instanceof DoubleValue ||
|
||||||
expression instanceof HexValue ||
|
// expression instanceof HexValue ||
|
||||||
expression instanceof DateValue ||
|
// expression instanceof DateValue ||
|
||||||
expression instanceof TimestampValue ||
|
// expression instanceof TimestampValue ||
|
||||||
expression instanceof TimeValue
|
// expression instanceof TimeValue
|
||||||
) {
|
// ) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// 查询字段名
|
// // 查询字段名
|
||||||
String selectField = expression.toString();
|
// String selectField = expression.toString();
|
||||||
// 实际查询字段名
|
// // 实际查询字段名
|
||||||
String realSelectField = selectField;
|
// String realSelectField = selectField;
|
||||||
// 判断是否有别名
|
// // 判断是否有别名
|
||||||
if (alias != null) {
|
// if (alias != null) {
|
||||||
selectField = alias.getName();
|
// selectField = alias.getName();
|
||||||
}
|
// }
|
||||||
// 获取真实字段名
|
// // 获取真实字段名
|
||||||
if (expression instanceof Column) {
|
// if (expression instanceof Column) {
|
||||||
Column column = (Column) expression;
|
// Column column = (Column) expression;
|
||||||
realSelectField = column.getColumnName();
|
// realSelectField = column.getColumnName();
|
||||||
}
|
// }
|
||||||
sqlInfo.addSelectField(selectField, realSelectField);
|
// sqlInfo.addSelectField(selectField, realSelectField);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 处理函数式字段
|
// * 处理函数式字段
|
||||||
*
|
// *
|
||||||
* @param functionExp
|
// * @param functionExp
|
||||||
* @param sqlInfo
|
// * @param sqlInfo
|
||||||
*/
|
// */
|
||||||
private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
|
// private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
|
||||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
// List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||||
for (Expression expression : expressions) {
|
// for (Expression expression : expressions) {
|
||||||
JSqlParserUtils.handleExpression(sqlInfo, expression, null);
|
// JSqlParserUtils.handleExpression(sqlInfo, expression, null);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|||||||
@ -1,101 +1,101 @@
|
|||||||
package org.jeecg.common.util.sqlparse.vo;
|
//package org.jeecg.common.util.sqlparse.vo;
|
||||||
|
//
|
||||||
import lombok.Data;
|
//import lombok.Data;
|
||||||
import net.sf.jsqlparser.statement.select.SelectBody;
|
//import net.sf.jsqlparser.statement.select.SelectBody;
|
||||||
|
//
|
||||||
import java.util.HashSet;
|
//import java.util.HashSet;
|
||||||
import java.util.Set;
|
//import java.util.Set;
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
* select 查询 sql 的信息
|
// * select 查询 sql 的信息
|
||||||
*/
|
// */
|
||||||
@Data
|
//@Data
|
||||||
public class SelectSqlInfo {
|
//public class SelectSqlInfo {
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 查询的表名,如果是子查询,则此处为null
|
// * 查询的表名,如果是子查询,则此处为null
|
||||||
*/
|
// */
|
||||||
private String fromTableName;
|
// private String fromTableName;
|
||||||
/**
|
// /**
|
||||||
* 表别名
|
// * 表别名
|
||||||
*/
|
// */
|
||||||
private String fromTableAliasName;
|
// private String fromTableAliasName;
|
||||||
/**
|
// /**
|
||||||
* 通过子查询获取的表信息,例如:select name from (select * from user) u
|
// * 通过子查询获取的表信息,例如:select name from (select * from user) u
|
||||||
* 如果不是子查询,则为null
|
// * 如果不是子查询,则为null
|
||||||
*/
|
// */
|
||||||
private SelectSqlInfo fromSubSelect;
|
// private SelectSqlInfo fromSubSelect;
|
||||||
/**
|
// /**
|
||||||
* 查询的字段集合,如果是 * 则为null,如果设了别名则为别名
|
// * 查询的字段集合,如果是 * 则为null,如果设了别名则为别名
|
||||||
*/
|
// */
|
||||||
private Set<String> selectFields;
|
// private Set<String> selectFields;
|
||||||
/**
|
// /**
|
||||||
* 真实的查询字段集合,如果是 * 则为null,如果设了别名则为原始字段名
|
// * 真实的查询字段集合,如果是 * 则为null,如果设了别名则为原始字段名
|
||||||
*/
|
// */
|
||||||
private Set<String> realSelectFields;
|
// private Set<String> realSelectFields;
|
||||||
/**
|
// /**
|
||||||
* 是否是查询所有字段
|
// * 是否是查询所有字段
|
||||||
*/
|
// */
|
||||||
private boolean selectAll;
|
// private boolean selectAll;
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 解析之后的 SQL (关键字都是大写)
|
// * 解析之后的 SQL (关键字都是大写)
|
||||||
*/
|
// */
|
||||||
private final String parsedSql;
|
// private final String parsedSql;
|
||||||
|
//
|
||||||
public SelectSqlInfo(String parsedSql) {
|
// public SelectSqlInfo(String parsedSql) {
|
||||||
this.parsedSql = parsedSql;
|
// this.parsedSql = parsedSql;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public SelectSqlInfo(SelectBody selectBody) {
|
// public SelectSqlInfo(SelectBody selectBody) {
|
||||||
this.parsedSql = selectBody.toString();
|
// this.parsedSql = selectBody.toString();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public void addSelectField(String selectField, String realSelectField) {
|
// public void addSelectField(String selectField, String realSelectField) {
|
||||||
if (this.selectFields == null) {
|
// if (this.selectFields == null) {
|
||||||
this.selectFields = new HashSet<>();
|
// this.selectFields = new HashSet<>();
|
||||||
}
|
// }
|
||||||
if (this.realSelectFields == null) {
|
// if (this.realSelectFields == null) {
|
||||||
this.realSelectFields = new HashSet<>();
|
// this.realSelectFields = new HashSet<>();
|
||||||
}
|
// }
|
||||||
this.selectFields.add(selectField);
|
// this.selectFields.add(selectField);
|
||||||
this.realSelectFields.add(realSelectField);
|
// this.realSelectFields.add(realSelectField);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 获取所有字段,包括子查询里的。
|
// * 获取所有字段,包括子查询里的。
|
||||||
*
|
// *
|
||||||
* @return
|
// * @return
|
||||||
*/
|
// */
|
||||||
public Set<String> getAllRealSelectFields() {
|
// public Set<String> getAllRealSelectFields() {
|
||||||
Set<String> fields = new HashSet<>();
|
// Set<String> fields = new HashSet<>();
|
||||||
// 递归获取所有字段,起个直观的方法名为:
|
// // 递归获取所有字段,起个直观的方法名为:
|
||||||
this.recursiveGetAllFields(this, fields);
|
// this.recursiveGetAllFields(this, fields);
|
||||||
return fields;
|
// return fields;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 递归获取所有字段
|
// * 递归获取所有字段
|
||||||
*/
|
// */
|
||||||
private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
|
// private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
|
||||||
if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
|
// if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
|
||||||
fields.addAll(sqlInfo.getRealSelectFields());
|
// fields.addAll(sqlInfo.getRealSelectFields());
|
||||||
}
|
// }
|
||||||
if (sqlInfo.getFromSubSelect() != null) {
|
// if (sqlInfo.getFromSubSelect() != null) {
|
||||||
recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
|
// recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Override
|
// @Override
|
||||||
public String toString() {
|
// public String toString() {
|
||||||
return "SelectSqlInfo{" +
|
// return "SelectSqlInfo{" +
|
||||||
"fromTableName='" + fromTableName + '\'' +
|
// "fromTableName='" + fromTableName + '\'' +
|
||||||
", fromSubSelect=" + fromSubSelect +
|
// ", fromSubSelect=" + fromSubSelect +
|
||||||
", aliasName='" + fromTableAliasName + '\'' +
|
// ", aliasName='" + fromTableAliasName + '\'' +
|
||||||
", selectFields=" + selectFields +
|
// ", selectFields=" + selectFields +
|
||||||
", realSelectFields=" + realSelectFields +
|
// ", realSelectFields=" + realSelectFields +
|
||||||
", selectAll=" + selectAll +
|
// ", selectAll=" + selectAll +
|
||||||
"}";
|
// "}";
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|||||||
@ -11,17 +11,16 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
|||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||||
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.http.CacheControl;
|
import org.springframework.http.CacheControl;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
@ -59,6 +58,14 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private PrometheusMeterRegistry prometheusMeterRegistry;
|
private PrometheusMeterRegistry prometheusMeterRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* meterRegistryPostProcessor
|
||||||
|
* for [QQYUN-12558]【监控】系统监控的头两个tab不好使,接口404
|
||||||
|
*/
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("meterRegistryPostProcessor")
|
||||||
|
private BeanPostProcessor meterRegistryPostProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 静态资源的配置 - 使得可以从磁盘中读取 Html、图片、视频、音频等
|
* 静态资源的配置 - 使得可以从磁盘中读取 Html、图片、视频、音频等
|
||||||
*/
|
*/
|
||||||
@ -147,12 +154,17 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解决metrics端点不显示jvm信息的问题(zyf)
|
* 监听应用启动完成事件,确保 PrometheusMeterRegistry 已经初始化
|
||||||
|
* for [QQYUN-12558]【监控】系统监控的头两个tab不好使,接口404
|
||||||
|
* @param event
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2025/5/26 16:46
|
||||||
*/
|
*/
|
||||||
@Bean
|
@EventListener
|
||||||
@ConditionalOnBean(name = "meterRegistryPostProcessor")
|
public void onApplicationReady(ApplicationReadyEvent event) {
|
||||||
InitializingBean forcePrometheusPostProcessor(BeanPostProcessor meterRegistryPostProcessor) {
|
if(null != meterRegistryPostProcessor){
|
||||||
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "");
|
meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
|
|||||||
@ -6,15 +6,12 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.jeecg.common.api.CommonAPI;
|
import org.jeecg.common.api.CommonAPI;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.CommonUtils;
|
import org.jeecg.common.util.CommonUtils;
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -70,6 +67,9 @@ public class LowCodeModeInterceptor implements HandlerInterceptor {
|
|||||||
Set<String> hasRoles = null;
|
Set<String> hasRoles = null;
|
||||||
if (loginUser == null) {
|
if (loginUser == null) {
|
||||||
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginUser != null) {
|
||||||
//当前登录人拥有的角色
|
//当前登录人拥有的角色
|
||||||
hasRoles = commonAPI.queryUserRolesById(loginUser.getId());
|
hasRoles = commonAPI.queryUserRolesById(loginUser.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
springdoc.auto-tag-classes: false
|
springdoc.auto-tag-classes: false
|
||||||
springdoc.packages-to-scan: org.jeecg
|
springdoc.packages-to-scan: org.jeecg
|
||||||
|
springdoc.default-flat-param-object: true
|
||||||
@ -1,75 +0,0 @@
|
|||||||
package org.jeecg.test.sqlinjection;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
|
||||||
import org.jeecg.common.util.SqlInjectionUtil;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL注入攻击检查测试
|
|
||||||
* @author: liusq
|
|
||||||
* @date: 2023年09月08日
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class TestInjectWithSqlParser {
|
|
||||||
/**
|
|
||||||
* 注入测试
|
|
||||||
*
|
|
||||||
* @param sql
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isExistSqlInject(String sql) {
|
|
||||||
try {
|
|
||||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info("===================================================");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() throws JSQLParserException {
|
|
||||||
//不存在sql注入
|
|
||||||
assertFalse(isExistSqlInject("select * from fm_time where dept_id=:sqlparamsmap.id and time=:sqlparamsmap.time"));
|
|
||||||
assertFalse(isExistSqlInject("select * from test"));
|
|
||||||
assertFalse(isExistSqlInject("select load_file(\"C:\\\\benben.txt\")"));
|
|
||||||
assertFalse(isExistSqlInject("WITH SUB1 AS (SELECT user FROM t1) SELECT * FROM T2 WHERE id > 123 "));
|
|
||||||
|
|
||||||
//存在sql注入
|
|
||||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
|
||||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
|
||||||
assertTrue(isExistSqlInject("select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));"));
|
|
||||||
assertTrue(isExistSqlInject("select * from users;show databases;"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id=1 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13"));
|
|
||||||
assertTrue(isExistSqlInject("update user set name = '123'"));
|
|
||||||
assertTrue(isExistSqlInject("SELECT * FROM users WHERE username = 'admin' AND password = '123456' OR 1=1;--"));
|
|
||||||
assertTrue(isExistSqlInject("select * from users where id=1 and (select count(*) from information_schema.tables where table_schema='数据库名')>4 %23"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where sleep(5) %23"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where 2=2.0 or 2 != 4"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where 1!=2.0"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id=floor(2.0)"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where not true"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where 1 or id > 0"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where 'tom' or id > 0"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where '-2.3' "));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where 2 "));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where (3+2) "));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where -1 IS TRUE"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where 'hello' is null "));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where '2022-10-31' and id > 0"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id > 0 or 1!=2.0 "));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id > 0 or 1 in (1,3,4) "));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device UNION select name from other"));
|
|
||||||
assertTrue(isExistSqlInject("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
package org.jeecg.test.sqlinjection;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
|
||||||
import org.jeecg.common.util.SqlInjectionUtil;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL注入攻击检查测试
|
|
||||||
* @author: liusq
|
|
||||||
* @date: 2023年09月08日
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class TestSqlInjectForDict {
|
|
||||||
/**
|
|
||||||
* 注入测试
|
|
||||||
*
|
|
||||||
* @param sql
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isExistSqlInject(String sql) {
|
|
||||||
try {
|
|
||||||
SqlInjectionUtil.specialFilterContentForDictSql(sql);
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info("===================================================");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() throws JSQLParserException {
|
|
||||||
//不存在sql注入
|
|
||||||
assertFalse(isExistSqlInject("sys_user,realname,id"));
|
|
||||||
assertFalse(isExistSqlInject("oa_officialdoc_organcode,organ_name,id"));
|
|
||||||
assertFalse(isExistSqlInject("onl_cgform_head where table_type!=3 and copy_type=0,table_txt,table_name"));
|
|
||||||
assertFalse(isExistSqlInject("onl_cgform_head where copy_type = 0,table_txt,table_name"));
|
|
||||||
|
|
||||||
//存在sql注入
|
|
||||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
|
||||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
package org.jeecg.test.sqlinjection;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
|
||||||
import org.jeecg.common.util.SqlInjectionUtil;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL注入攻击检查测试
|
|
||||||
* @author: liusq
|
|
||||||
* @date: 2023年09月08日
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class TestSqlInjectForOnlineReport {
|
|
||||||
/**
|
|
||||||
* 注入测试
|
|
||||||
*
|
|
||||||
* @param sql
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isExistSqlInject(String sql) {
|
|
||||||
try {
|
|
||||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info("===================================================");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() throws JSQLParserException {
|
|
||||||
//不存在sql注入
|
|
||||||
assertFalse(isExistSqlInject("select * from fm_time where dept_id=:sqlparamsmap.id and time=:sqlparamsmap.time"));
|
|
||||||
assertFalse(isExistSqlInject("select * from test"));
|
|
||||||
assertFalse(isExistSqlInject("select load_file(\"C:\\\\benben.txt\")"));
|
|
||||||
assertFalse(isExistSqlInject("select * from dc_device where id in (select id from other)"));
|
|
||||||
assertFalse(isExistSqlInject("select * from dc_device UNION select name from other"));
|
|
||||||
|
|
||||||
//存在sql注入
|
|
||||||
assertTrue(isExistSqlInject("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)"));
|
|
||||||
assertTrue(isExistSqlInject("or 1= 1 --"));
|
|
||||||
assertTrue(isExistSqlInject("select * from test where sleep(%23)"));
|
|
||||||
assertTrue(isExistSqlInject("select * from test where SLEEP(3)"));
|
|
||||||
assertTrue(isExistSqlInject("select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));"));
|
|
||||||
assertTrue(isExistSqlInject("select * from users;show databases;"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where id=1 and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13"));
|
|
||||||
assertTrue(isExistSqlInject("update user set name = '123'"));
|
|
||||||
assertTrue(isExistSqlInject("SELECT * FROM users WHERE username = 'admin' AND password = '123456' OR 1=1;--"));
|
|
||||||
assertTrue(isExistSqlInject("select * from users where id=1 and (select count(*) from information_schema.tables where table_schema='数据库名')>4 %23"));
|
|
||||||
assertTrue(isExistSqlInject("select * from dc_device where sleep(5) %23"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
package org.jeecg.test.sqlinjection;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
|
|
||||||
import org.jeecg.common.util.SqlInjectionUtil;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Description: SQL注入测试类
|
|
||||||
* @author: scott
|
|
||||||
* @date: 2023年08月14日 9:55
|
|
||||||
*/
|
|
||||||
public class TestSqlInjection {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表名带别名,同时有html编码字符
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testSpecialSQL() {
|
|
||||||
String tableName = "sys_user t";
|
|
||||||
//解决使用参数tableName=sys_user t&复测,漏洞仍然存在
|
|
||||||
if (tableName.contains(" ")) {
|
|
||||||
tableName = tableName.substring(0, tableName.indexOf(" "));
|
|
||||||
}
|
|
||||||
//【issues/4393】 sys_user , (sys_user), sys_user%20, %60sys_user%60
|
|
||||||
String reg = "\\s+|\\(|\\)|`";
|
|
||||||
tableName = tableName.replaceAll(reg, "");
|
|
||||||
System.out.println(tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试sql是否含sql注入风险
|
|
||||||
* <p>
|
|
||||||
* mybatis plus的方法
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void sqlInjectionCheck() {
|
|
||||||
String sql = "select * from sys_user";
|
|
||||||
System.out.println(SqlInjectionUtils.check(sql));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试sql是否有SLEEP风险
|
|
||||||
* <p>
|
|
||||||
* mybatisPlus的方法
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void sqlSleepCheck() {
|
|
||||||
SqlInjectionUtil.checkSqlAnnotation("(SELECT 6240 FROM (SELECT(SLEEP(5))and 1=2)vidl)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试sql是否含sql注入风险
|
|
||||||
* <p>
|
|
||||||
* 自定义方法
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void sqlInjectionCheck2() {
|
|
||||||
String sql = "select * from sys_user";
|
|
||||||
SqlInjectionUtil.specialFilterContentForOnlineReport(sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
|
||||||
* <p>
|
|
||||||
* 判断字段名是否符合规范
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testFieldSpecification() {
|
|
||||||
List<String> list = new ArrayList();
|
|
||||||
list.add("Hello World!");
|
|
||||||
list.add("Hello%20World!");
|
|
||||||
list.add("HelloWorld!");
|
|
||||||
list.add("Hello World");
|
|
||||||
list.add("age");
|
|
||||||
list.add("user_name");
|
|
||||||
list.add("user_name%20");
|
|
||||||
list.add("user_name%20 ");
|
|
||||||
|
|
||||||
for (String input : list) {
|
|
||||||
boolean containsSpecialChars = isValidString(input);
|
|
||||||
System.out.println("input:" + input + " ,包含空格和特殊字符: " + containsSpecialChars);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
|
||||||
*
|
|
||||||
* @param input
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static boolean isValidString(String input) {
|
|
||||||
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]+$");
|
|
||||||
return pattern.matcher(input).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
package org.jeecg.test.sqlparse;
|
|
||||||
|
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.jeecg.common.util.sqlparse.JSqlParserUtils;
|
|
||||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 针对 JSqlParserUtils 的单元测试
|
|
||||||
*/
|
|
||||||
public class JSqlParserUtilsTest {
|
|
||||||
|
|
||||||
private static final String[] sqlList = new String[]{
|
|
||||||
"select * from sys_user",
|
|
||||||
"select u.* from sys_user u",
|
|
||||||
"select u.*, c.name from sys_user u, demo c",
|
|
||||||
"select u.age, c.name from sys_user u, demo c",
|
|
||||||
"select sex, age, c.name from sys_user, demo c",
|
|
||||||
// 别名测试
|
|
||||||
"select username as realname from sys_user",
|
|
||||||
"select username as realname, u.realname as aaa, u.id bbb from sys_user u",
|
|
||||||
// 不存在真实地查询字段
|
|
||||||
"select count(1) from sys_user",
|
|
||||||
// 函数式字段
|
|
||||||
"select max(sex), id from sys_user",
|
|
||||||
// 复杂嵌套函数式字段
|
|
||||||
"select CONCAT(CONCAT(' _ ', sex), ' - ' , birthday) as info, id from sys_user",
|
|
||||||
// 更复杂的嵌套函数式字段
|
|
||||||
"select CONCAT(CONCAT(101,'_',NULL, DATE(create_time),'_',sex),' - ',birthday) as info, id from sys_user",
|
|
||||||
// 子查询SQL
|
|
||||||
"select u.name1 as name2 from (select username as name1 from sys_user) u",
|
|
||||||
// 多层嵌套子查询SQL
|
|
||||||
"select u2.name2 as name3 from (select u1.name1 as name2 from (select username as name1 from sys_user) u1) u2",
|
|
||||||
// 字段子查询SQL
|
|
||||||
"select id, (select username as name1 from sys_user u2 where u1.id = u2.id) as name2 from sys_user u1",
|
|
||||||
// 带条件的SQL(不解析where条件里的字段,但不影响解析查询字段)
|
|
||||||
"select username as name1 from sys_user where realname LIKE '%张%'",
|
|
||||||
// 多重复杂关联表查询解析,包含的表为:sys_user, sys_depart, sys_dict_item, demo
|
|
||||||
"" +
|
|
||||||
"SELECT " +
|
|
||||||
" u.*, d.age, sd.item_text AS sex, (SELECT count(sd.id) FROM sys_depart sd) AS count " +
|
|
||||||
"FROM " +
|
|
||||||
" (SELECT sd.username AS foo, sd.realname FROM sys_user sd) u, " +
|
|
||||||
" demo d " +
|
|
||||||
"LEFT JOIN sys_dict_item AS sd ON d.sex = sd.item_value " +
|
|
||||||
"WHERE sd.dict_id = '3d9a351be3436fbefb1307d4cfb49bf2'",
|
|
||||||
};
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseSelectSql() {
|
|
||||||
System.out.println("-----------------------------------------");
|
|
||||||
for (String sql : sqlList) {
|
|
||||||
System.out.println("待测试的sql:" + sql);
|
|
||||||
try {
|
|
||||||
// 解析所有的表名,key=表名,value=解析后的sql信息
|
|
||||||
Map<String, SelectSqlInfo> parsedMap = JSqlParserUtils.parseAllSelectTable(sql);
|
|
||||||
assert parsedMap != null;
|
|
||||||
for (Map.Entry<String, SelectSqlInfo> entry : parsedMap.entrySet()) {
|
|
||||||
System.out.println("表名:" + entry.getKey());
|
|
||||||
this.printSqlInfo(entry.getValue(), 1);
|
|
||||||
}
|
|
||||||
} catch (JSQLParserException e) {
|
|
||||||
System.out.println("SQL解析出现异常:" + e.getMessage());
|
|
||||||
}
|
|
||||||
System.out.println("-----------------------------------------");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printSqlInfo(SelectSqlInfo sqlInfo, int level) {
|
|
||||||
String beforeStr = this.getBeforeStr(level);
|
|
||||||
if (sqlInfo.getFromTableName() == null) {
|
|
||||||
// 子查询
|
|
||||||
System.out.println(beforeStr + "子查询:" + sqlInfo.getFromSubSelect().getParsedSql());
|
|
||||||
this.printSqlInfo(sqlInfo.getFromSubSelect(), level + 1);
|
|
||||||
} else {
|
|
||||||
// 非子查询
|
|
||||||
System.out.println(beforeStr + "查询的表名:" + sqlInfo.getFromTableName());
|
|
||||||
}
|
|
||||||
if (oConvertUtils.isNotEmpty(sqlInfo.getFromTableAliasName())) {
|
|
||||||
System.out.println(beforeStr + "查询的表别名:" + sqlInfo.getFromTableAliasName());
|
|
||||||
}
|
|
||||||
if (sqlInfo.isSelectAll()) {
|
|
||||||
System.out.println(beforeStr + "查询的字段:*");
|
|
||||||
} else {
|
|
||||||
System.out.println(beforeStr + "查询的字段:" + sqlInfo.getSelectFields());
|
|
||||||
System.out.println(beforeStr + "真实的字段:" + sqlInfo.getRealSelectFields());
|
|
||||||
if (sqlInfo.getFromTableName() == null) {
|
|
||||||
System.out.println(beforeStr + "所有的字段(包括子查询):" + sqlInfo.getAllRealSelectFields());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印前缀,根据层级来打印
|
|
||||||
private String getBeforeStr(int level) {
|
|
||||||
if (level == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
StringBuilder beforeStr = new StringBuilder();
|
|
||||||
for (int i = 0; i < level; i++) {
|
|
||||||
beforeStr.append(" ");
|
|
||||||
}
|
|
||||||
beforeStr.append("- ");
|
|
||||||
return beforeStr.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -55,9 +55,48 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-aiflow-jdk8</artifactId>
|
<artifactId>jeecg-aiflow</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- aiflow 脚本依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-script-graaljs</artifactId>
|
||||||
|
<version>${liteflow.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-script-groovy</artifactId>
|
||||||
|
<version>${liteflow.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-script-kotlin</artifactId>
|
||||||
|
<version>${liteflow.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-scripting-jsr223</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-script-aviator</artifactId>
|
||||||
|
<version>${liteflow.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>aviator</artifactId>
|
||||||
|
<groupId>com.googlecode.aviator</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<!-- aiflow 脚本依赖 -->
|
||||||
|
|
||||||
<!-- langChain4j model support -->
|
<!-- langChain4j model support -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -24,6 +24,12 @@
|
|||||||
<artifactId>hibernate-re</artifactId>
|
<artifactId>hibernate-re</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AI大模型管理 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
|
<artifactId>jeecg-boot-module-airag</artifactId>
|
||||||
|
<version>${jeecgboot.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- 企业微信/钉钉 api -->
|
<!-- 企业微信/钉钉 api -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework</groupId>
|
<groupId>org.jeecgframework</groupId>
|
||||||
|
|||||||
@ -4,14 +4,14 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.common.util.sqlparse.JSqlParserUtils;
|
|
||||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
import org.jeecg.config.firewall.SqlInjection.IDictTableWhiteListHandler;
|
import org.jeecg.config.firewall.SqlInjection.IDictTableWhiteListHandler;
|
||||||
import org.jeecg.config.firewall.interceptor.LowCodeModeInterceptor;
|
import org.jeecg.config.firewall.interceptor.LowCodeModeInterceptor;
|
||||||
import org.jeecg.modules.system.entity.SysTableWhiteList;
|
import org.jeecg.modules.system.entity.SysTableWhiteList;
|
||||||
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
|
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
|
||||||
import org.jeecg.modules.system.service.ISysTableWhiteListService;
|
import org.jeecg.modules.system.service.ISysTableWhiteListService;
|
||||||
|
import org.jeecgframework.minidao.sqlparser.impl.vo.SelectSqlInfo;
|
||||||
|
import org.jeecgframework.minidao.util.MiniDaoUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler
|
|||||||
public boolean isPassBySql(String sql) {
|
public boolean isPassBySql(String sql) {
|
||||||
Map<String, SelectSqlInfo> parsedMap = null;
|
Map<String, SelectSqlInfo> parsedMap = null;
|
||||||
try {
|
try {
|
||||||
parsedMap = JSqlParserUtils.parseAllSelectTable(sql);
|
parsedMap = MiniDaoUtil.parseAllSelectTable(sql);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("校验sql语句,解析报错:{}", e.getMessage());
|
log.warn("校验sql语句,解析报错:{}", e.getMessage());
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler
|
|||||||
log.info("字典拼接的查询SQL:{}", sql);
|
log.info("字典拼接的查询SQL:{}", sql);
|
||||||
try {
|
try {
|
||||||
// 进行SQL解析
|
// 进行SQL解析
|
||||||
JSqlParserUtils.parseSelectSqlInfo(sql);
|
MiniDaoUtil.parseSelectSqlInfo(sql);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 如果SQL解析失败,则通过字段名和表名进行校验
|
// 如果SQL解析失败,则通过字段名和表名进行校验
|
||||||
return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields)));
|
return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields)));
|
||||||
|
|||||||
@ -19,6 +19,21 @@ public class CustomInMemoryHttpTraceRepository extends InMemoryHttpTraceReposito
|
|||||||
return super.findAll();
|
return super.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* for [issues/8309]系统监控>请求追踪,列表每刷新一下,总数据就减一#8309
|
||||||
|
* @param trace
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2025/6/4 19:38
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void add(HttpTrace trace) {
|
||||||
|
// 只有当请求不是OPTIONS方法,并且URI不包含httptrace时才记录数据
|
||||||
|
if (!"OPTIONS".equals(trace.getRequest().getMethod()) &&
|
||||||
|
!trace.getRequest().getUri().toString().contains("httptrace")) {
|
||||||
|
super.add(trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<HttpTrace> findAll(String query) {
|
public List<HttpTrace> findAll(String query) {
|
||||||
List<HttpTrace> allTrace = super.findAll();
|
List<HttpTrace> allTrace = super.findAll();
|
||||||
if (null != allTrace && !allTrace.isEmpty()) {
|
if (null != allTrace && !allTrace.isEmpty()) {
|
||||||
|
|||||||
@ -78,7 +78,9 @@ public class CustomUndertowMetricsHandler {
|
|||||||
// 获取当前 session,如果不存在则创建
|
// 获取当前 session,如果不存在则创建
|
||||||
Session session = sessionManager.getSession(exchange, sessionConfig);
|
Session session = sessionManager.getSession(exchange, sessionConfig);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
sessionManager.createSession(exchange, sessionConfig);
|
try {
|
||||||
|
sessionManager.createSession(exchange, sessionConfig);
|
||||||
|
} catch (Exception e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行下一个 Handler
|
// 执行下一个 Handler
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package org.jeecg.modules.openapi.controller;
|
package org.jeecg.modules.openapi.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@ -10,14 +12,14 @@ import org.jeecg.common.constant.CommonConstant;
|
|||||||
import org.jeecg.common.system.base.controller.JeecgController;
|
import org.jeecg.common.system.base.controller.JeecgController;
|
||||||
import org.jeecg.common.system.query.QueryGenerator;
|
import org.jeecg.common.system.query.QueryGenerator;
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
|
import org.jeecg.common.util.RedisUtil;
|
||||||
|
import org.jeecg.common.util.RestUtil;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
||||||
import org.jeecg.modules.openapi.generator.PathGenerator;
|
import org.jeecg.modules.openapi.generator.PathGenerator;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiHeaderService;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiParamService;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||||
import org.jeecg.modules.openapi.swagger.*;
|
import org.jeecg.modules.openapi.swagger.*;
|
||||||
import org.jeecg.modules.system.entity.SysUser;
|
import org.jeecg.modules.system.entity.SysUser;
|
||||||
@ -26,13 +28,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @date 2024/12/10 9:11
|
* @date 2024/12/10 9:11
|
||||||
@ -44,9 +48,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RestTemplate restTemplate;
|
private RestTemplate restTemplate;
|
||||||
@Autowired
|
@Autowired
|
||||||
private OpenApiParamService openApiParamService;
|
private RedisUtil redisUtil;
|
||||||
@Autowired
|
|
||||||
private OpenApiHeaderService openApiHeaderService;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISysUserService sysUserService;
|
private ISysUserService sysUserService;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -67,10 +69,6 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
QueryWrapper<OpenApi> queryWrapper = QueryGenerator.initQueryWrapper(openApi, req.getParameterMap());
|
QueryWrapper<OpenApi> queryWrapper = QueryGenerator.initQueryWrapper(openApi, req.getParameterMap());
|
||||||
Page<OpenApi> page = new Page<>(pageNo, pageSize);
|
Page<OpenApi> page = new Page<>(pageNo, pageSize);
|
||||||
IPage<OpenApi> pageList = service.page(page, queryWrapper);
|
IPage<OpenApi> pageList = service.page(page, queryWrapper);
|
||||||
for (OpenApi api : pageList.getRecords()) {
|
|
||||||
api.setParams(openApiParamService.findByApiId(api.getId()));
|
|
||||||
api.setHeaders(openApiHeaderService.findByApiId(api.getId()));
|
|
||||||
}
|
|
||||||
return Result.ok(pageList);
|
return Result.ok(pageList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,16 +80,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
*/
|
*/
|
||||||
@PostMapping(value = "/add")
|
@PostMapping(value = "/add")
|
||||||
public Result<?> add(@RequestBody OpenApi openApi) {
|
public Result<?> add(@RequestBody OpenApi openApi) {
|
||||||
if (service.save(openApi)) {
|
service.save(openApi);
|
||||||
if (!CollectionUtils.isEmpty(openApi.getHeaders())) {
|
|
||||||
openApiHeaderService.saveBatch(openApi.getHeaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(openApi.getParams())) {
|
|
||||||
openApiParamService.saveBatch(openApi.getParams());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok("添加成功!");
|
return Result.ok("添加成功!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,18 +92,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
*/
|
*/
|
||||||
@PutMapping(value = "/edit")
|
@PutMapping(value = "/edit")
|
||||||
public Result<?> edit(@RequestBody OpenApi openApi) {
|
public Result<?> edit(@RequestBody OpenApi openApi) {
|
||||||
if (service.updateById(openApi)) {
|
service.updateById(openApi);
|
||||||
openApiHeaderService.deleteByApiId(openApi.getId());
|
|
||||||
openApiParamService.deleteByApiId(openApi.getId());
|
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(openApi.getHeaders())) {
|
|
||||||
openApiHeaderService.saveBatch(openApi.getHeaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(openApi.getParams())) {
|
|
||||||
openApiParamService.saveBatch(openApi.getParams());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Result.ok("修改成功!");
|
return Result.ok("修改成功!");
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -170,27 +148,72 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
result.put("data", null);
|
result.put("data", null);
|
||||||
return Result.error("失败", result);
|
return Result.error("失败", result);
|
||||||
}
|
}
|
||||||
List<OpenApiHeader> headers = openApiHeaderService.findByApiId(openApi.getId());
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
if (StrUtil.isNotEmpty(openApi.getHeadersJson())) {
|
||||||
|
List<OpenApiHeader> headers = JSON.parseArray(openApi.getHeadersJson(),OpenApiHeader.class);
|
||||||
|
if (headers.size()>0) {
|
||||||
|
for (OpenApiHeader header : headers) {
|
||||||
|
httpHeaders.put(header.getHeaderKey(), Lists.newArrayList(request.getHeader(header.getHeaderKey())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String url = openApi.getOriginUrl();
|
String url = openApi.getOriginUrl();
|
||||||
String method = openApi.getRequestMethod();
|
String method = openApi.getRequestMethod();
|
||||||
|
|
||||||
HttpHeaders httpHeaders = new HttpHeaders();
|
|
||||||
for (OpenApiHeader header : headers) {
|
|
||||||
httpHeaders.put(header.getHeaderKey(), Lists.newArrayList(request.getHeader(header.getHeaderKey())));
|
|
||||||
}
|
|
||||||
|
|
||||||
String appkey = request.getHeader("appkey");
|
String appkey = request.getHeader("appkey");
|
||||||
OpenApiAuth openApiAuth = openApiAuthService.getByAppkey(appkey);
|
OpenApiAuth openApiAuth = openApiAuthService.getByAppkey(appkey);
|
||||||
SysUser systemUser = sysUserService.getById(openApiAuth.getSystemUserId());
|
SysUser systemUser = sysUserService.getUserByName(openApiAuth.getCreateBy());
|
||||||
String token = JwtUtil.sign(systemUser.getUsername(), systemUser.getPassword());
|
String token = this.getToken(systemUser.getUsername(), systemUser.getPassword());
|
||||||
httpHeaders.put("X-Access-Token", Lists.newArrayList(token));
|
httpHeaders.put("X-Access-Token", Lists.newArrayList(token));
|
||||||
|
httpHeaders.put("Content-Type",Lists.newArrayList("application/json"));
|
||||||
HttpEntity<String> httpEntity = new HttpEntity<>(json, httpHeaders);
|
HttpEntity<String> httpEntity = new HttpEntity<>(json, httpHeaders);
|
||||||
|
url = RestUtil.getBaseUrl() + url;
|
||||||
|
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
|
||||||
|
if (HttpMethod.GET.matches(method)
|
||||||
|
|| HttpMethod.DELETE.matches(method)
|
||||||
|
|| HttpMethod.OPTIONS.matches(method)
|
||||||
|
|| HttpMethod.TRACE.matches(method)) {
|
||||||
|
//拼接参数
|
||||||
|
if (!request.getParameterMap().isEmpty()) {
|
||||||
|
if (StrUtil.isNotEmpty(openApi.getParamsJson())) {
|
||||||
|
List<OpenApiParam> params = JSON.parseArray(openApi.getParamsJson(),OpenApiParam.class);
|
||||||
|
if (params.size()>0) {
|
||||||
|
Map<String, OpenApiParam> openApiParamMap = params.stream().collect(Collectors.toMap(p -> p.getParamKey(), p -> p, (e, r) -> e));
|
||||||
|
request.getParameterMap().forEach((k, v) -> {
|
||||||
|
OpenApiParam openApiParam = openApiParamMap.get(k);
|
||||||
|
if (Objects.nonNull(openApiParam)) {
|
||||||
|
if(v==null&&StrUtil.isNotEmpty(openApiParam.getDefaultValue())){
|
||||||
|
builder.queryParam(openApiParam.getParamKey(), openApiParam.getDefaultValue());
|
||||||
|
}
|
||||||
|
if (v!=null){
|
||||||
|
builder.queryParam(openApiParam.getParamKey(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return restTemplate.exchange(url, HttpMethod.resolve(method), httpEntity, Result.class, request.getParameterMap()).getBody();
|
}
|
||||||
|
}
|
||||||
|
URI targetUrl = builder.build().encode().toUri();
|
||||||
|
return restTemplate.exchange(targetUrl.toString(), Objects.requireNonNull(HttpMethod.resolve(method)), httpEntity, Result.class, request.getParameterMap()).getBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成接口访问令牌 Token
|
||||||
|
*
|
||||||
|
* @param USERNAME
|
||||||
|
* @param PASSWORD
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String getToken(String USERNAME, String PASSWORD) {
|
||||||
|
String token = JwtUtil.sign(USERNAME, PASSWORD);
|
||||||
|
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
|
||||||
|
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, 60);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/json")
|
@GetMapping("/json")
|
||||||
public SwaggerModel swaggerModel() {
|
public SwaggerModel swaggerModel() {
|
||||||
|
|
||||||
@ -230,13 +253,16 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
definition.setType("object");
|
definition.setType("object");
|
||||||
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
||||||
definition.setProperties(definitionProperties);
|
definition.setProperties(definitionProperties);
|
||||||
|
if (openApi.getBody()!=null){
|
||||||
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
||||||
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
if (jsonObject.size()>0){
|
||||||
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
||||||
swaggerDefinitionProperties.setType("string");
|
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
||||||
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
|
swaggerDefinitionProperties.setType("string");
|
||||||
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
|
||||||
|
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// body的definition构建完成
|
// body的definition构建完成
|
||||||
definitions.put(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body", definition);
|
definitions.put(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body", definition);
|
||||||
@ -327,25 +353,28 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
|
|
||||||
private void parameters(SwaggerOperation operation, OpenApi openApi) {
|
private void parameters(SwaggerOperation operation, OpenApi openApi) {
|
||||||
List<SwaggerOperationParameter> parameters = new ArrayList<>();
|
List<SwaggerOperationParameter> parameters = new ArrayList<>();
|
||||||
|
if (openApi.getParamsJson()!=null) {
|
||||||
for (OpenApiParam openApiParam : openApiParamService.findByApiId(openApi.getId())) {
|
List<OpenApiParam> openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
|
||||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
for (OpenApiParam openApiParam : openApiParams) {
|
||||||
parameter.setIn("path");
|
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||||
parameter.setName(openApiParam.getParamKey());
|
parameter.setIn("path");
|
||||||
parameter.setRequired(openApiParam.getRequired() == 1);
|
parameter.setName(openApiParam.getParamKey());
|
||||||
parameter.setDescription(openApiParam.getNote());
|
parameter.setRequired(openApiParam.getRequired() == 1);
|
||||||
parameters.add(parameter);
|
parameter.setDescription(openApiParam.getNote());
|
||||||
|
parameters.add(parameter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (openApi.getHeadersJson()!=null) {
|
||||||
for (OpenApiHeader openApiHeader : openApiHeaderService.findByApiId(openApi.getId())) {
|
List<OpenApiHeader> openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
|
||||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
for (OpenApiHeader openApiHeader : openApiHeaders) {
|
||||||
parameter.setIn("header");
|
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||||
parameter.setName(openApiHeader.getHeaderKey());
|
parameter.setIn("header");
|
||||||
parameter.setRequired(openApiHeader.getRequired() == 1);
|
parameter.setName(openApiHeader.getHeaderKey());
|
||||||
parameter.setDescription(openApiHeader.getNote());
|
parameter.setRequired(openApiHeader.getRequired() == 1);
|
||||||
parameters.add(parameter);
|
parameter.setDescription(openApiHeader.getNote());
|
||||||
|
parameters.add(parameter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.setParameters(parameters);
|
operation.setParameters(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,7 +382,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
|||||||
SwaggerInfo info = new SwaggerInfo();
|
SwaggerInfo info = new SwaggerInfo();
|
||||||
|
|
||||||
info.setDescription("OpenAPI 接口列表");
|
info.setDescription("OpenAPI 接口列表");
|
||||||
info.setVersion("3.7.1");
|
info.setVersion("3.8.0");
|
||||||
info.setTitle("OpenAPI 接口列表");
|
info.setTitle("OpenAPI 接口列表");
|
||||||
info.setTermsOfService("https://jeecg.com");
|
info.setTermsOfService("https://jeecg.com");
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.system.base.controller.JeecgController;
|
import org.jeecg.common.system.base.controller.JeecgController;
|
||||||
import org.jeecg.common.system.query.QueryGenerator;
|
import org.jeecg.common.system.query.QueryGenerator;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiRecord;
|
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiRecordService;
|
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -18,47 +18,47 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/openapi/record")
|
@RequestMapping("/openapi/record")
|
||||||
public class OpenApiRecordController extends JeecgController<OpenApiRecord, OpenApiRecordService> {
|
public class OpenApiLogController extends JeecgController<OpenApiLog, OpenApiLogService> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页列表查询
|
* 分页列表查询
|
||||||
*
|
*
|
||||||
* @param openApiRecord
|
* @param OpenApiLog
|
||||||
* @param pageNo
|
* @param pageNo
|
||||||
* @param pageSize
|
* @param pageSize
|
||||||
* @param req
|
* @param req
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/list")
|
@GetMapping(value = "/list")
|
||||||
public Result<?> queryPageList(OpenApiRecord openApiRecord, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
public Result<?> queryPageList(OpenApiLog OpenApiLog, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||||
QueryWrapper<OpenApiRecord> queryWrapper = QueryGenerator.initQueryWrapper(openApiRecord, req.getParameterMap());
|
QueryWrapper<OpenApiLog> queryWrapper = QueryGenerator.initQueryWrapper(OpenApiLog, req.getParameterMap());
|
||||||
Page<OpenApiRecord> page = new Page<>(pageNo, pageSize);
|
Page<OpenApiLog> page = new Page<>(pageNo, pageSize);
|
||||||
IPage<OpenApiRecord> pageList = service.page(page, queryWrapper);
|
IPage<OpenApiLog> pageList = service.page(page, queryWrapper);
|
||||||
return Result.ok(pageList);
|
return Result.ok(pageList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加
|
* 添加
|
||||||
*
|
*
|
||||||
* @param openApiRecord
|
* @param OpenApiLog
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@PostMapping(value = "/add")
|
@PostMapping(value = "/add")
|
||||||
public Result<?> add(@RequestBody OpenApiRecord openApiRecord) {
|
public Result<?> add(@RequestBody OpenApiLog OpenApiLog) {
|
||||||
service.save(openApiRecord);
|
service.save(OpenApiLog);
|
||||||
return Result.ok("添加成功!");
|
return Result.ok("添加成功!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑
|
* 编辑
|
||||||
*
|
*
|
||||||
* @param openApiRecord
|
* @param OpenApiLog
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@PutMapping(value = "/edit")
|
@PutMapping(value = "/edit")
|
||||||
public Result<?> edit(@RequestBody OpenApiRecord openApiRecord) {
|
public Result<?> edit(@RequestBody OpenApiLog OpenApiLog) {
|
||||||
service.updateById(openApiRecord);
|
service.updateById(OpenApiLog);
|
||||||
return Result.ok("修改成功!");
|
return Result.ok("修改成功!");
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ public class OpenApiRecordController extends JeecgController<OpenApiRecord, Open
|
|||||||
*/
|
*/
|
||||||
@GetMapping(value = "/queryById")
|
@GetMapping(value = "/queryById")
|
||||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||||
OpenApiRecord openApiRecord = service.getById(id);
|
OpenApiLog OpenApiLog = service.getById(id);
|
||||||
return Result.ok(openApiRecord);
|
return Result.ok(OpenApiLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,10 +4,7 @@ import org.jeecg.common.api.vo.Result;
|
|||||||
import org.jeecg.common.system.base.controller.JeecgController;
|
import org.jeecg.common.system.base.controller.JeecgController;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/openapi/permission")
|
@RequestMapping("/openapi/permission")
|
||||||
@ -15,6 +12,11 @@ public class OpenApiPermissionController extends JeecgController<OpenApiPermissi
|
|||||||
|
|
||||||
@PostMapping("add")
|
@PostMapping("add")
|
||||||
public Result add(@RequestBody OpenApiPermission openApiPermission) {
|
public Result add(@RequestBody OpenApiPermission openApiPermission) {
|
||||||
return Result.ok(service.save(openApiPermission));
|
service.add(openApiPermission);
|
||||||
|
return Result.ok("保存成功");
|
||||||
|
}
|
||||||
|
@GetMapping("/getOpenApi")
|
||||||
|
public Result<?> getOpenApi( String apiAuthId) {
|
||||||
|
return service.getOpenApi(apiAuthId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,18 +47,15 @@ public class OpenApi implements Serializable {
|
|||||||
* IP 黑名单
|
* IP 黑名单
|
||||||
*/
|
*/
|
||||||
private String blackList;
|
private String blackList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求头列表
|
* 请求头json
|
||||||
*/
|
*/
|
||||||
@TableField(exist = false)
|
private String headersJson;
|
||||||
private List<OpenApiHeader> headers;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求参数列表
|
* 请求参数json
|
||||||
*/
|
*/
|
||||||
@TableField(exist = false)
|
private String paramsJson;
|
||||||
private List<OpenApiParam> params;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 目前仅支持json
|
* 目前仅支持json
|
||||||
@ -100,4 +97,9 @@ public class OpenApi implements Serializable {
|
|||||||
* 更新时间
|
* 更新时间
|
||||||
*/
|
*/
|
||||||
private Date updateTime;
|
private Date updateTime;
|
||||||
|
/**
|
||||||
|
* 历史已选接口
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String ifCheckBox = "0";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
import org.jeecg.common.aspect.annotation.Dict;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -44,6 +45,7 @@ public class OpenApiAuth implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 系统用户ID
|
* 系统用户ID
|
||||||
*/
|
*/
|
||||||
|
@Dict(dictTable = "sys_user",dicCode = "id",dicText = "username")
|
||||||
private String systemUserId;
|
private String systemUserId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -18,16 +18,6 @@ import java.io.Serializable;
|
|||||||
public class OpenApiHeader implements Serializable {
|
public class OpenApiHeader implements Serializable {
|
||||||
private static final long serialVersionUID = 5032708503120184683L;
|
private static final long serialVersionUID = 5032708503120184683L;
|
||||||
|
|
||||||
/**
|
|
||||||
* id
|
|
||||||
*/
|
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接口ID
|
|
||||||
*/
|
|
||||||
private String apiId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* key
|
* key
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import java.util.Date;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class OpenApiRecord implements Serializable {
|
public class OpenApiLog implements Serializable {
|
||||||
private static final long serialVersionUID = -5870384488947863579L;
|
private static final long serialVersionUID = -5870384488947863579L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,16 +17,6 @@ import java.io.Serializable;
|
|||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class OpenApiParam implements Serializable {
|
public class OpenApiParam implements Serializable {
|
||||||
private static final long serialVersionUID = -6174831468578022357L;
|
private static final long serialVersionUID = -6174831468578022357L;
|
||||||
/**
|
|
||||||
* id
|
|
||||||
*/
|
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接口ID
|
|
||||||
*/
|
|
||||||
private String apiId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* key
|
* key
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.jeecg.common.exception.JeecgBootException;
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||||
|
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiRecord;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
||||||
|
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiRecordService;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
@ -27,7 +27,7 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ApiAuthFilter implements Filter {
|
public class ApiAuthFilter implements Filter {
|
||||||
|
|
||||||
private OpenApiRecordService openApiRecordService;
|
private OpenApiLogService openApiLogService;
|
||||||
private OpenApiAuthService openApiAuthService;
|
private OpenApiAuthService openApiAuthService;
|
||||||
private OpenApiPermissionService openApiPermissionService;
|
private OpenApiPermissionService openApiPermissionService;
|
||||||
private OpenApiService openApiService;
|
private OpenApiService openApiService;
|
||||||
@ -61,13 +61,13 @@ public class ApiAuthFilter implements Filter {
|
|||||||
filterChain.doFilter(servletRequest, servletResponse);
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
long endTime = System.currentTimeMillis();
|
long endTime = System.currentTimeMillis();
|
||||||
|
|
||||||
OpenApiRecord record = new OpenApiRecord();
|
OpenApiLog openApiLog = new OpenApiLog();
|
||||||
record.setApiId(openApi.getId());
|
openApiLog.setApiId(openApi.getId());
|
||||||
record.setCallAuthId(openApiAuth.getId());
|
openApiLog.setCallAuthId(openApiAuth.getId());
|
||||||
record.setCallTime(callTime);
|
openApiLog.setCallTime(callTime);
|
||||||
record.setUsedTime(endTime - startTime);
|
openApiLog.setUsedTime(endTime - startTime);
|
||||||
record.setResponseTime(new Date());
|
openApiLog.setResponseTime(new Date());
|
||||||
openApiRecordService.save(record);
|
openApiLogService.save(openApiLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,7 +75,7 @@ public class ApiAuthFilter implements Filter {
|
|||||||
ServletContext servletContext = filterConfig.getServletContext();
|
ServletContext servletContext = filterConfig.getServletContext();
|
||||||
WebApplicationContext applicationContext = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
|
WebApplicationContext applicationContext = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
|
||||||
this.openApiService = applicationContext.getBean(OpenApiService.class);
|
this.openApiService = applicationContext.getBean(OpenApiService.class);
|
||||||
this.openApiRecordService = applicationContext.getBean(OpenApiRecordService.class);
|
this.openApiLogService = applicationContext.getBean(OpenApiLogService.class);
|
||||||
this.openApiAuthService = applicationContext.getBean(OpenApiAuthService.class);
|
this.openApiAuthService = applicationContext.getBean(OpenApiAuthService.class);
|
||||||
this.openApiPermissionService = applicationContext.getBean(OpenApiPermissionService.class);
|
this.openApiPermissionService = applicationContext.getBean(OpenApiPermissionService.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.mapper;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 14:47
|
|
||||||
*/
|
|
||||||
@Mapper
|
|
||||||
public interface OpenApiHeaderMapper extends BaseMapper<OpenApiHeader> {
|
|
||||||
}
|
|
||||||
@ -2,11 +2,11 @@ package org.jeecg.modules.openapi.mapper;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiRecord;
|
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @date 2024/12/10 9:50
|
* @date 2024/12/10 9:50
|
||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface OpenApiRecordMapper extends BaseMapper<OpenApiRecord> {
|
public interface OpenApiLogMapper extends BaseMapper<OpenApiLog> {
|
||||||
}
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.mapper;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 14:48
|
|
||||||
*/
|
|
||||||
@Mapper
|
|
||||||
public interface OpenApiParamMapper extends BaseMapper<OpenApiParam> {
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.service;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 14:48
|
|
||||||
*/
|
|
||||||
public interface OpenApiHeaderService extends IService<OpenApiHeader> {
|
|
||||||
|
|
||||||
boolean deleteByApiId(String apiId);
|
|
||||||
|
|
||||||
List<OpenApiHeader> findByApiId(String apiId);
|
|
||||||
}
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
package org.jeecg.modules.openapi.service;
|
package org.jeecg.modules.openapi.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiRecord;
|
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @date 2024/12/10 9:51
|
* @date 2024/12/10 9:51
|
||||||
*/
|
*/
|
||||||
public interface OpenApiRecordService extends IService<OpenApiRecord> {
|
public interface OpenApiLogService extends IService<OpenApiLog> {
|
||||||
}
|
}
|
||||||
@ -1,15 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.service;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 14:49
|
|
||||||
*/
|
|
||||||
public interface OpenApiParamService extends IService<OpenApiParam> {
|
|
||||||
boolean deleteByApiId(String apiId);
|
|
||||||
|
|
||||||
List<OpenApiParam> findByApiId(String apiId);
|
|
||||||
}
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package org.jeecg.modules.openapi.service;
|
package org.jeecg.modules.openapi.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -10,4 +12,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface OpenApiPermissionService extends IService<OpenApiPermission> {
|
public interface OpenApiPermissionService extends IService<OpenApiPermission> {
|
||||||
List<OpenApiPermission> findByAuthId(String authId);
|
List<OpenApiPermission> findByAuthId(String authId);
|
||||||
|
|
||||||
|
Result<?> getOpenApi(String apiAuthId);
|
||||||
|
|
||||||
|
void add(OpenApiPermission openApiPermission);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.service.impl;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
|
||||||
import org.jeecg.modules.openapi.mapper.OpenApiHeaderMapper;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiHeaderService;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 14:49
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class OpenApiHeaderServiceImpl extends ServiceImpl<OpenApiHeaderMapper, OpenApiHeader> implements OpenApiHeaderService {
|
|
||||||
@Override
|
|
||||||
public boolean deleteByApiId(String apiId) {
|
|
||||||
return baseMapper.delete(Wrappers.lambdaUpdate(OpenApiHeader.class).eq(OpenApiHeader::getApiId, apiId)) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<OpenApiHeader> findByApiId(String apiId) {
|
|
||||||
return baseMapper.selectList(Wrappers.lambdaQuery(OpenApiHeader.class).eq(OpenApiHeader::getApiId, apiId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package org.jeecg.modules.openapi.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||||
|
import org.jeecg.modules.openapi.mapper.OpenApiLogMapper;
|
||||||
|
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @date 2024/12/10 9:53
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class OpenApiLogServiceImpl extends ServiceImpl<OpenApiLogMapper, OpenApiLog> implements OpenApiLogService {
|
||||||
|
}
|
||||||
@ -1,27 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.service.impl;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
|
||||||
import org.jeecg.modules.openapi.mapper.OpenApiParamMapper;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiParamService;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 14:50
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class OpenApiParamServiceImpl extends ServiceImpl<OpenApiParamMapper, OpenApiParam> implements OpenApiParamService {
|
|
||||||
@Override
|
|
||||||
public boolean deleteByApiId(String apiId) {
|
|
||||||
return baseMapper.delete(Wrappers.lambdaUpdate(OpenApiParam.class).eq(OpenApiParam::getApiId, apiId)) > 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<OpenApiParam> findByApiId(String apiId) {
|
|
||||||
return baseMapper.selectList(Wrappers.lambdaQuery(OpenApiParam.class).eq(OpenApiParam::getApiId, apiId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +1,67 @@
|
|||||||
package org.jeecg.modules.openapi.service.impl;
|
package org.jeecg.modules.openapi.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||||
import org.jeecg.modules.openapi.mapper.OpenApiPermissionMapper;
|
import org.jeecg.modules.openapi.mapper.OpenApiPermissionMapper;
|
||||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||||
|
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Collections;
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @date 2024/12/19 17:44
|
* @date 2024/12/19 17:44
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class OpenApiPermissionServiceImpl extends ServiceImpl<OpenApiPermissionMapper, OpenApiPermission> implements OpenApiPermissionService {
|
public class OpenApiPermissionServiceImpl extends ServiceImpl<OpenApiPermissionMapper, OpenApiPermission> implements OpenApiPermissionService {
|
||||||
|
@Resource
|
||||||
|
private OpenApiService openApiService;
|
||||||
@Override
|
@Override
|
||||||
public List<OpenApiPermission> findByAuthId(String authId) {
|
public List<OpenApiPermission> findByAuthId(String authId) {
|
||||||
return baseMapper.selectList(Wrappers.lambdaQuery(OpenApiPermission.class).eq(OpenApiPermission::getApiAuthId, authId));
|
return baseMapper.selectList(Wrappers.lambdaQuery(OpenApiPermission.class).eq(OpenApiPermission::getApiAuthId, authId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<?> getOpenApi(String apiAuthId) {
|
||||||
|
List<OpenApi> openApis = openApiService.list();
|
||||||
|
if (CollectionUtil.isEmpty(openApis)) {
|
||||||
|
return Result.error("接口不存在");
|
||||||
|
}
|
||||||
|
List<OpenApiPermission> openApiPermissions = baseMapper.selectList(Wrappers.<OpenApiPermission>lambdaQuery().eq(OpenApiPermission::getApiAuthId, apiAuthId));
|
||||||
|
if (CollectionUtil.isNotEmpty(openApiPermissions)) {
|
||||||
|
Map<String, OpenApi> openApiMap = openApis.stream().collect(Collectors.toMap(OpenApi::getId, o -> o));
|
||||||
|
for (OpenApiPermission openApiPermission : openApiPermissions) {
|
||||||
|
OpenApi openApi = openApiMap.get(openApiPermission.getApiId());
|
||||||
|
if (openApi!=null) {
|
||||||
|
openApi.setIfCheckBox("1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.ok(openApis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(OpenApiPermission openApiPermission) {
|
||||||
|
this.remove(Wrappers.<OpenApiPermission>lambdaQuery().eq(OpenApiPermission::getApiAuthId, openApiPermission.getApiAuthId()));
|
||||||
|
List<String> list = Arrays.asList(openApiPermission.getApiId().split(","));
|
||||||
|
if (CollectionUtil.isNotEmpty(list)) {
|
||||||
|
list.forEach(l->{
|
||||||
|
if (StrUtil.isNotEmpty(l)){
|
||||||
|
OpenApiPermission saveApiPermission = new OpenApiPermission();
|
||||||
|
saveApiPermission.setApiId(l);
|
||||||
|
saveApiPermission.setApiAuthId(openApiPermission.getApiAuthId());
|
||||||
|
this.save(saveApiPermission);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
package org.jeecg.modules.openapi.service.impl;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
||||||
import org.jeecg.modules.openapi.entity.OpenApiRecord;
|
|
||||||
import org.jeecg.modules.openapi.mapper.OpenApiRecordMapper;
|
|
||||||
import org.jeecg.modules.openapi.service.OpenApiRecordService;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @date 2024/12/10 9:53
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class OpenApiRecordServiceImpl extends ServiceImpl<OpenApiRecordMapper, OpenApiRecord> implements OpenApiRecordService {
|
|
||||||
}
|
|
||||||
@ -32,7 +32,10 @@ import org.jeecg.common.constant.enums.MessageTypeEnum;
|
|||||||
import org.jeecg.common.exception.JeecgBootBizTipException;
|
import org.jeecg.common.exception.JeecgBootBizTipException;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
import org.jeecg.common.util.*;
|
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.JeecgBaseConfig;
|
||||||
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
|
||||||
import org.jeecg.modules.system.entity.*;
|
import org.jeecg.modules.system.entity.*;
|
||||||
@ -741,6 +744,7 @@ public class ThirdAppDingtalkServiceImpl implements IThirdAppService {
|
|||||||
}
|
}
|
||||||
sysDepart.setDepartName(department.getName());
|
sysDepart.setDepartName(department.getName());
|
||||||
sysDepart.setDepartOrder(department.getOrder());
|
sysDepart.setDepartOrder(department.getOrder());
|
||||||
|
sysDepart.setDingIdentifier(department.getSource_identifier());
|
||||||
return sysDepart;
|
return sysDepart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { render } from '@/common/renderUtils';
|
||||||
|
//列表数据
|
||||||
|
export const columns = [
|
||||||
|
<#list columns as po>
|
||||||
|
<#if po.isShowList =='Y' && po.fieldName !='id' && po.fieldName !='delFlag'>
|
||||||
|
{
|
||||||
|
title: '${po.filedComment}',
|
||||||
|
align:"center",
|
||||||
|
<#if po.sort=='Y'>
|
||||||
|
sorter: true,
|
||||||
|
</#if>
|
||||||
|
<#if po.classType=='date'>
|
||||||
|
dataIndex: '${po.fieldName}',
|
||||||
|
<#elseif po.fieldDbType=='Blob'>
|
||||||
|
dataIndex: '${po.fieldName}String'
|
||||||
|
<#elseif po.classType=='umeditor'>
|
||||||
|
dataIndex: '${po.fieldName}',
|
||||||
|
<#elseif po.classType=='pca'>
|
||||||
|
dataIndex: '${po.fieldName}',
|
||||||
|
<#elseif po.classType=='file'>
|
||||||
|
dataIndex: '${po.fieldName}',
|
||||||
|
<#elseif po.classType=='image'>
|
||||||
|
dataIndex: '${po.fieldName}',
|
||||||
|
customRender:render.renderImage,
|
||||||
|
<#elseif po.classType=='switch'>
|
||||||
|
dataIndex: '${po.fieldName}',
|
||||||
|
<#assign switch_extend_arr=['Y','N']>
|
||||||
|
<#if po.dictField?default("")?contains("[")>
|
||||||
|
<#assign switch_extend_arr=po.dictField?eval>
|
||||||
|
</#if>
|
||||||
|
<#list switch_extend_arr as a>
|
||||||
|
<#if a_index == 0>
|
||||||
|
<#assign switch_extend_arr1=a>
|
||||||
|
<#else>
|
||||||
|
<#assign switch_extend_arr2=a>
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
customRender:({text}) => {
|
||||||
|
return render.renderSwitch(text, [{text:'是',value:'${switch_extend_arr1}'},{text:'否',value:'${switch_extend_arr2}'}])
|
||||||
|
},
|
||||||
|
<#elseif po.classType == 'sel_tree' || po.classType=='list' || po.classType=='list_multi' || po.classType=='sel_search' || po.classType=='radio' || po.classType=='checkbox' || po.classType=='sel_depart' || po.classType=='sel_user' || po.classType=='popup_dict'>
|
||||||
|
dataIndex: '${po.fieldName}_dictText'
|
||||||
|
<#else>
|
||||||
|
dataIndex: '${po.fieldName}'
|
||||||
|
</#if>
|
||||||
|
},
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
];
|
||||||
@ -0,0 +1,512 @@
|
|||||||
|
<#include "/common/utils.ftl">
|
||||||
|
<route lang="json5" type="page">
|
||||||
|
{
|
||||||
|
layout: 'default',
|
||||||
|
style: {
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
navigationBarTitleText: '${tableVo.ftlDescription}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageLayout :navTitle="navTitle" :backRouteName="backRouteName">
|
||||||
|
<scroll-view class="scrollArea" scroll-y>
|
||||||
|
<view class="form-container">
|
||||||
|
<wd-form ref="form" :model="myFormData">
|
||||||
|
<wd-cell-group border>
|
||||||
|
<#list columns as po><#rt/>
|
||||||
|
<#assign form_field_dictCode="">
|
||||||
|
<#if po.dictTable?default("")?trim?length gt 1 && po.dictText?default("")?trim?length gt 1 && po.dictField?default("")?trim?length gt 1>
|
||||||
|
<#assign form_field_dictCode="${po.dictTable},${po.dictText},${po.dictField}">
|
||||||
|
<#elseif po.dictField?default("")?trim?length gt 1>
|
||||||
|
<#assign form_field_dictCode="${po.dictField}">
|
||||||
|
</#if>
|
||||||
|
<view class="{ 'mt-14px': ${po_index % 2} == 0 }">
|
||||||
|
<#if po.fieldName !='id' && po.isShow =='Y' && po.fieldName !='delFlag'><#rt/>
|
||||||
|
<#if po.classType =='image'>
|
||||||
|
<!-- 图片 -->
|
||||||
|
<wd-cell
|
||||||
|
:title="get4Label('${po.filedComment}')"
|
||||||
|
title-width="100px"
|
||||||
|
<#if po.nullable == 'N'>
|
||||||
|
:required="true"
|
||||||
|
</#if>
|
||||||
|
>
|
||||||
|
<online-image
|
||||||
|
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
<#if po.uploadnum??>
|
||||||
|
:maxNum="${po.uploadnum}"
|
||||||
|
</#if>
|
||||||
|
/>
|
||||||
|
</wd-cell>
|
||||||
|
<#elseif po.classType =='file'>
|
||||||
|
<wd-cell
|
||||||
|
:title="get4Label('${po.filedComment}')"
|
||||||
|
title-width="100px"
|
||||||
|
<#if po.nullable == 'N'>
|
||||||
|
:required="true"
|
||||||
|
</#if>
|
||||||
|
>
|
||||||
|
<!-- #ifndef APP-PLUS -->
|
||||||
|
<online-file
|
||||||
|
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
></online-file>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- #ifdef APP-PLUS -->
|
||||||
|
<online-file-custom
|
||||||
|
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
></online-file-custom>
|
||||||
|
<!-- #endif -->
|
||||||
|
</wd-cell>
|
||||||
|
<#elseif po.classType =='datetime' || po.classType =='time'>
|
||||||
|
<DateTime
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
type="${po.classType}"
|
||||||
|
<#if po.classType =='datetime'>
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
<#else>
|
||||||
|
format="HH:mm:ss"
|
||||||
|
</#if>
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></DateTime>
|
||||||
|
<#elseif po.classType =='date'>
|
||||||
|
<online-date
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
<#if po.extendParams?exists && po.extendParams.picker?exists>
|
||||||
|
:type="getDateExtendType('${po.extendParams.picker}')"
|
||||||
|
<#else>
|
||||||
|
type="${po.classType}"
|
||||||
|
</#if>
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></online-date>
|
||||||
|
<#elseif po.classType =='switch'>
|
||||||
|
<#assign switch_extend_arr=['Y','N']>
|
||||||
|
<#if po.dictField?default("")?contains("[")>
|
||||||
|
<#assign switch_extend_arr=po.dictField?eval>
|
||||||
|
</#if>
|
||||||
|
<!-- 开关 -->
|
||||||
|
<wd-cell
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
title-width="100px"
|
||||||
|
center
|
||||||
|
>
|
||||||
|
<wd-switch
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
size="18px"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
active-value="${switch_extend_arr[0]}"
|
||||||
|
inactive-value="${switch_extend_arr[1]}"
|
||||||
|
/>
|
||||||
|
</wd-cell>
|
||||||
|
<#elseif po.classType =='list' || po.classType =='sel_search'>
|
||||||
|
<online-select
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
type="${po.classType}"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
dict="${form_field_dictCode}"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></online-select>
|
||||||
|
<#elseif po.classType =='checkbox'>
|
||||||
|
<online-checkbox
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
type="${po.classType}"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
dict="${form_field_dictCode}"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></online-checkbox>
|
||||||
|
<#elseif po.classType =='radio'>
|
||||||
|
<online-radio
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
type="${po.classType}"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
dict="${form_field_dictCode}"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></online-radio>
|
||||||
|
<#elseif po.classType =='list_multi'>
|
||||||
|
<online-multi
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
type="${po.classType}"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
dict="${form_field_dictCode}"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></online-multi>
|
||||||
|
<#elseif po.classType =='textarea' || po.classType =='markdown' || po.classType =='umeditor'>
|
||||||
|
<wd-textarea
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
type="${po.classType}"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
prop=${autoStringSuffix(po)}
|
||||||
|
clearable
|
||||||
|
:maxlength="300"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></wd-textarea>
|
||||||
|
<#elseif po.classType =='password'>
|
||||||
|
<wd-input
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
show-password
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
prop=${autoStringSuffix(po)}
|
||||||
|
clearable
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></wd-input>
|
||||||
|
<#elseif po.classType =='popup_dict'>
|
||||||
|
<PopupDict
|
||||||
|
labelWidth="100px"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
:multi="${po.extendParams.popupMulti?c}"
|
||||||
|
dictCode="${po.dictTable},${po.dictText},${po.dictField}"
|
||||||
|
></PopupDict>
|
||||||
|
<#elseif po.classType =='popup'>
|
||||||
|
<#assign sourceFields = po.dictField?default("")?trim?split(",")/>
|
||||||
|
<#assign targetFields = po.dictText?default("")?trim?split(",")/>
|
||||||
|
<Popup
|
||||||
|
labelWidth="100px"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
:multi="${po.extendParams.popupMulti?c}"
|
||||||
|
code="${po.dictTable}"
|
||||||
|
:setFieldsValue="setFieldsValue"
|
||||||
|
:fieldConfig="[
|
||||||
|
<#list sourceFields as fieldName>
|
||||||
|
{ source: '${fieldName}', target: '${dashedToCamel(targetFields[fieldName_index])}' },
|
||||||
|
</#list>
|
||||||
|
]"
|
||||||
|
></Popup>
|
||||||
|
<#elseif po.classType =='link_table'>
|
||||||
|
<online-popup-link-record
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
labelWidth="100px"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
:formSchema="getFormSchema('${po.dictTable}','${po.dictField}','${po.dictText}')"
|
||||||
|
v-model:value="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
></online-popup-link-record>
|
||||||
|
<#elseif po.classType =='sel_user'>
|
||||||
|
<select-user
|
||||||
|
labelWidth="100px"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
<#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if>
|
||||||
|
<#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if>
|
||||||
|
></select-user>
|
||||||
|
<#elseif po.classType =='sel_depart'>
|
||||||
|
<select-dept
|
||||||
|
labelWidth="100px"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
<#if po.extendParams?exists && po.extendParams.text?exists>labelKey="${po.extendParams.text}"</#if>
|
||||||
|
<#if po.extendParams?exists && po.extendParams.store?exists>rowKey="${po.extendParams.store}"</#if>
|
||||||
|
:multiple="${po.extendParams.multi?default('true')}"
|
||||||
|
></select-dept>
|
||||||
|
<#elseif po.classType =='cat_tree'>
|
||||||
|
<CategorySelect
|
||||||
|
labelWidth="100px"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
pcode="${po.dictField?default("")}"
|
||||||
|
></CategorySelect>
|
||||||
|
<#elseif po.classType =='sel_tree'>
|
||||||
|
<TreeSelect
|
||||||
|
labelWidth="100px"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
<#if po.dictText??>
|
||||||
|
<#if po.dictText?split(',')[2]?? && po.dictText?split(',')[0]??>
|
||||||
|
dict="${po.dictTable},${po.dictText?split(',')[2]},${po.dictText?split(',')[0]}"
|
||||||
|
<#elseif po.dictText?split(',')[1]??>
|
||||||
|
pidField="${po.dictText?split(',')[1]}"
|
||||||
|
<#elseif po.dictText?split(',')[3]??>
|
||||||
|
hasChildField="${po.dictText?split(',')[3]}"
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
:pidValue="`${po.dictField}`"
|
||||||
|
></TreeSelect>
|
||||||
|
<#elseif po.fieldDbType=='int' || po.fieldDbType=='double' || po.fieldDbType=='BigDecimal'>
|
||||||
|
<wd-input
|
||||||
|
label-width="100px"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
prop=${autoStringSuffix(po)}
|
||||||
|
placeholder="请选择${po.filedComment}"
|
||||||
|
inputMode="numeric"
|
||||||
|
:rules="[
|
||||||
|
<#if po.fieldName != 'id'>
|
||||||
|
<#assign fieldValidType = po.fieldValidType!''>
|
||||||
|
<#-- 非空校验 -->
|
||||||
|
<#if po.nullable == 'N' || fieldValidType == '*'>
|
||||||
|
{ required: true, message: '请输入${po.filedComment}!'},
|
||||||
|
<#elseif fieldValidType!=''>
|
||||||
|
{ required: false},
|
||||||
|
</#if>
|
||||||
|
<#-- 6到16位数字 -->
|
||||||
|
<#if fieldValidType == 'n6-16'>
|
||||||
|
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
|
||||||
|
<#-- 6到16位任意字符 -->
|
||||||
|
<#elseif fieldValidType == '*6-16'>
|
||||||
|
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
|
||||||
|
<#-- 6到18位字母 -->
|
||||||
|
<#elseif fieldValidType == 's6-18'>
|
||||||
|
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
|
||||||
|
<#-- 网址 -->
|
||||||
|
<#elseif fieldValidType == 'url'>
|
||||||
|
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
|
||||||
|
<#-- 电子邮件 -->
|
||||||
|
<#elseif fieldValidType == 'e'>
|
||||||
|
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
|
||||||
|
<#-- 手机号码 -->
|
||||||
|
<#elseif fieldValidType == 'm'>
|
||||||
|
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
|
||||||
|
<#-- 邮政编码 -->
|
||||||
|
<#elseif fieldValidType == 'p'>
|
||||||
|
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
|
||||||
|
<#-- 字母 -->
|
||||||
|
<#elseif fieldValidType == 's'>
|
||||||
|
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
|
||||||
|
<#-- 数字 -->
|
||||||
|
<#elseif fieldValidType == 'n'>
|
||||||
|
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
|
||||||
|
<#-- 整数 -->
|
||||||
|
<#elseif fieldValidType == 'z'>
|
||||||
|
{ pattern: /^-?\d+$/, message: '请输入整数!'},
|
||||||
|
<#-- 金额 -->
|
||||||
|
<#elseif fieldValidType == 'money'>
|
||||||
|
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
|
||||||
|
<#-- 正则校验 -->
|
||||||
|
<#elseif fieldValidType != '' && fieldValidType != '*'>
|
||||||
|
{ pattern: '${fieldValidType}', message: '不符合校验规则!'},
|
||||||
|
<#-- 无校验 -->
|
||||||
|
<#else>
|
||||||
|
<#t>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
]"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<#else>
|
||||||
|
<wd-input
|
||||||
|
label-width="100px"
|
||||||
|
v-model="myFormData[${autoStringSuffix(po)}]"
|
||||||
|
:label="get4Label('${po.filedComment}')"
|
||||||
|
name=${autoStringSuffix(po)}
|
||||||
|
prop=${autoStringSuffix(po)}
|
||||||
|
placeholder="请选择${po.filedComment}"
|
||||||
|
:rules="[
|
||||||
|
<#if po.fieldName != 'id'>
|
||||||
|
<#assign fieldValidType = po.fieldValidType!''>
|
||||||
|
<#-- 非空校验 -->
|
||||||
|
<#if po.nullable == 'N' || fieldValidType == '*'>
|
||||||
|
{ required: true, message: '请输入${po.filedComment}!'},
|
||||||
|
<#elseif fieldValidType!=''>
|
||||||
|
{ required: false},
|
||||||
|
</#if>
|
||||||
|
<#-- 6到16位数字 -->
|
||||||
|
<#if fieldValidType == 'n6-16'>
|
||||||
|
{ pattern: /^\d{6,16}$|^(?=\d+\.\d+)[\d.]{7,17}$/, message: '请输入6到16位数字!'},
|
||||||
|
<#-- 6到16位任意字符 -->
|
||||||
|
<#elseif fieldValidType == '*6-16'>
|
||||||
|
{ pattern: /^.{6,16}$/, message: '请输入6到16位任意字符!'},
|
||||||
|
<#-- 6到18位字母 -->
|
||||||
|
<#elseif fieldValidType == 's6-18'>
|
||||||
|
{ pattern: /^[a-z|A-Z]{6,18}$/, message: '请输入6到18位字母!'},
|
||||||
|
<#-- 网址 -->
|
||||||
|
<#elseif fieldValidType == 'url'>
|
||||||
|
{ pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, message: '请输入正确的网址!'},
|
||||||
|
<#-- 电子邮件 -->
|
||||||
|
<#elseif fieldValidType == 'e'>
|
||||||
|
{ pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/, message: '请输入正确的电子邮件!'},
|
||||||
|
<#-- 手机号码 -->
|
||||||
|
<#elseif fieldValidType == 'm'>
|
||||||
|
{ pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码!'},
|
||||||
|
<#-- 邮政编码 -->
|
||||||
|
<#elseif fieldValidType == 'p'>
|
||||||
|
{ pattern: /^[0-9]\d{5}$/, message: '请输入正确的邮政编码!'},
|
||||||
|
<#-- 字母 -->
|
||||||
|
<#elseif fieldValidType == 's'>
|
||||||
|
{ pattern: /^[A-Z|a-z]+$/, message: '请输入字母!'},
|
||||||
|
<#-- 数字 -->
|
||||||
|
<#elseif fieldValidType == 'n'>
|
||||||
|
{ pattern: /^-?\d+\.?\d*$/, message: '请输入数字!'},
|
||||||
|
<#-- 整数 -->
|
||||||
|
<#elseif fieldValidType == 'z'>
|
||||||
|
{ pattern: /^-?\d+$/, message: '请输入整数!'},
|
||||||
|
<#-- 金额 -->
|
||||||
|
<#elseif fieldValidType == 'money'>
|
||||||
|
{ pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/, message: '请输入正确的金额!'},
|
||||||
|
<#-- 正则校验 -->
|
||||||
|
<#elseif fieldValidType != '' && fieldValidType != '*'>
|
||||||
|
{ pattern: '${fieldValidType}', message: '不符合校验规则!'},
|
||||||
|
<#-- 无校验 -->
|
||||||
|
<#else>
|
||||||
|
<#t>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
]"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</view>
|
||||||
|
</#list>
|
||||||
|
</wd-cell-group>
|
||||||
|
</wd-form>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="footer">
|
||||||
|
<wd-button :disabled="loading" block :loading="loading" @click="handleSubmit">提交</wd-button>
|
||||||
|
</view>
|
||||||
|
</PageLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { http } from '@/utils/http'
|
||||||
|
import { useToast } from 'wot-design-uni'
|
||||||
|
import { useRouter } from '@/plugin/uni-mini-router'
|
||||||
|
import { ref, onMounted, computed,reactive } from 'vue'
|
||||||
|
import OnlineImage from '@/components/online/view/online-image.vue'
|
||||||
|
import OnlineFile from '@/components/online/view/online-file.vue'
|
||||||
|
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
|
||||||
|
import OnlineSelect from '@/components/online/view/online-select.vue'
|
||||||
|
import OnlineTime from '@/components/online/view/online-time.vue'
|
||||||
|
import OnlineDate from '@/components/online/view/online-date.vue'
|
||||||
|
import OnlineRadio from '@/components/online/view/online-radio.vue'
|
||||||
|
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
|
||||||
|
import OnlineMulti from '@/components/online/view/online-multi.vue'
|
||||||
|
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
|
||||||
|
import SelectDept from '@/components/SelectDept/SelectDept.vue'
|
||||||
|
import SelectUser from '@/components/SelectUser/SelectUser.vue'
|
||||||
|
defineOptions({
|
||||||
|
name: '${entityName}Form',
|
||||||
|
options: {
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const toast = useToast()
|
||||||
|
const router = useRouter()
|
||||||
|
const form = ref(null)
|
||||||
|
// 定义响应式数据
|
||||||
|
const myFormData = reactive({})
|
||||||
|
const loading = ref(false)
|
||||||
|
const navTitle = ref('新增')
|
||||||
|
const dataId = ref('')
|
||||||
|
const backRouteName = ref('${entityName}List')
|
||||||
|
// 定义 initForm 方法
|
||||||
|
const initForm = (item) => {
|
||||||
|
console.log('initForm item', item)
|
||||||
|
if(item?.dataId){
|
||||||
|
dataId.value = item.dataId;
|
||||||
|
navTitle.value = item.dataId?'编辑':'新增';
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 初始化数据
|
||||||
|
const initData = () => {
|
||||||
|
http.get("/${entityPackagePath}/${entityName?uncap_first}/queryById",{id:dataId.value}).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
let obj = res.result
|
||||||
|
Object.assign(myFormData, { ...obj })
|
||||||
|
}else{
|
||||||
|
toast.error(res?.message || '表单加载失败!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleSuccess = () => {
|
||||||
|
uni.$emit('refreshList');
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = () => {
|
||||||
|
let url = dataId.value?'/${entityPackagePath}/${entityName?uncap_first}/edit':'/${entityPackagePath}/${entityName?uncap_first}/add';
|
||||||
|
form.value
|
||||||
|
.validate()
|
||||||
|
.then(({ valid, errors }) => {
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true;
|
||||||
|
http.post(url,myFormData).then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
if (res.success) {
|
||||||
|
toast.success('保存成功');
|
||||||
|
handleSuccess()
|
||||||
|
}else{
|
||||||
|
toast.error(res?.message || '表单保存失败!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error, 'error')
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 标题
|
||||||
|
const get4Label = computed(() => {
|
||||||
|
return (label) => {
|
||||||
|
return label && label.length > 4 ? label.substring(0, 4) : label;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const getFormSchema = computed(() => {
|
||||||
|
return (dictTable,dictCode,dictText) => {
|
||||||
|
return {
|
||||||
|
dictCode,
|
||||||
|
dictTable,
|
||||||
|
dictText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 获取日期控件的扩展类型
|
||||||
|
* @param picker
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const getDateExtendType = (picker: string) => {
|
||||||
|
let mapField = {
|
||||||
|
month: 'year-month',
|
||||||
|
year: 'year',
|
||||||
|
quarter: 'quarter',
|
||||||
|
week: 'week',
|
||||||
|
day: 'date',
|
||||||
|
}
|
||||||
|
return picker && mapField[picker]
|
||||||
|
? mapField[picker]
|
||||||
|
: 'date'
|
||||||
|
}
|
||||||
|
//设置pop返回值
|
||||||
|
const setFieldsValue = (data) => {
|
||||||
|
Object.assign(myFormData, {...data })
|
||||||
|
}
|
||||||
|
// onLoad 生命周期钩子
|
||||||
|
onLoad((option) => {
|
||||||
|
initForm(option)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 20px;
|
||||||
|
padding-bottom: calc(constant(safe-area-inset-bottom) + 10px);
|
||||||
|
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
<route lang="json5" type="page">
|
||||||
|
{
|
||||||
|
layout: 'default',
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '${tableVo.ftlDescription}',
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
<template>
|
||||||
|
<PageLayout navTitle="${tableVo.ftlDescription}" backRouteName="index" routeMethod="pushTab">
|
||||||
|
<view class="wrap">
|
||||||
|
<z-paging
|
||||||
|
ref="paging"
|
||||||
|
:fixed="false"
|
||||||
|
v-model="dataList"
|
||||||
|
@query="queryList"
|
||||||
|
:default-page-size="15"
|
||||||
|
>
|
||||||
|
<template v-for="item in dataList" :key="item.id">
|
||||||
|
<wd-swipe-action>
|
||||||
|
<view class="list" @click="handleEdit(item)">
|
||||||
|
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
|
||||||
|
<view v-if="cIndex < 3" class="box" :style="getBoxStyle">
|
||||||
|
<view class="field ellipsis">{{ cItem.title }}</view>
|
||||||
|
<view class="value cu-text-grey">{{ item[cItem.dataIndex] }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<template #right>
|
||||||
|
<view class="action">
|
||||||
|
<view class="button" @click="handleAction('del', item)">删除</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</wd-swipe-action>
|
||||||
|
</template>
|
||||||
|
</z-paging>
|
||||||
|
<view class="add u-iconfont u-icon-add" @click="handleAdd"></view>
|
||||||
|
</view>
|
||||||
|
</PageLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { http } from '@/utils/http'
|
||||||
|
import usePageList from '@/hooks/usePageList'
|
||||||
|
import {columns} from './${entityName}Data';
|
||||||
|
defineOptions({
|
||||||
|
name: '${entityName}List',
|
||||||
|
options: {
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//分页加载配置
|
||||||
|
let { toast, router, paging, dataList, queryList } = usePageList('/${entityPackagePath}/${entityName?uncap_first}/list');
|
||||||
|
|
||||||
|
//样式
|
||||||
|
const getBoxStyle = computed(() => {
|
||||||
|
return { width: "calc(33% - 5px)" }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 其他操作
|
||||||
|
const handleAction = (val, item) => {
|
||||||
|
if (val == 'del') {
|
||||||
|
http.delete("/${entityPackagePath}/${entityName?uncap_first}/delete?id="+item.id,{id:item.id}).then((res) => {
|
||||||
|
toast.success('删除成功~')
|
||||||
|
paging.value.reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// go 新增页
|
||||||
|
const handleAdd = () => {
|
||||||
|
router.push({
|
||||||
|
name: '${entityName}Form'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go 编辑页
|
||||||
|
const handleEdit = (record) => {
|
||||||
|
router.push({
|
||||||
|
name: '${entityName}Form',
|
||||||
|
params: {dataId: record.id},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 监听刷新列表事件
|
||||||
|
uni.$on('refreshList', () => {
|
||||||
|
queryList(1,10)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wrap {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.wd-swipe-action) {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
padding: 10px 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.box {
|
||||||
|
width: 33%;
|
||||||
|
.field {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
width: 60px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
color: #fff;
|
||||||
|
&:first-child {
|
||||||
|
background-color: #fa4350;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.add {
|
||||||
|
height: 70upx;
|
||||||
|
width: 70upx;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 70upx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 80upx;
|
||||||
|
right: 30upx;
|
||||||
|
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -25,13 +25,6 @@
|
|||||||
<version>${jeecgboot.version}</version>
|
<version>${jeecgboot.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- AI大模型管理 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
|
||||||
<artifactId>jeecg-boot-module-airag</artifactId>
|
|
||||||
<version>${jeecgboot.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- flyway 数据库自动升级 -->
|
<!-- flyway 数据库自动升级 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.flywaydb</groupId>
|
<groupId>org.flywaydb</groupId>
|
||||||
|
|||||||
@ -40,8 +40,8 @@ public class JeecgSystemApplication extends SpringBootServletInitializer {
|
|||||||
String path = oConvertUtils.getString(env.getProperty("server.servlet.context-path"));
|
String path = oConvertUtils.getString(env.getProperty("server.servlet.context-path"));
|
||||||
log.info("\n----------------------------------------------------------\n\t" +
|
log.info("\n----------------------------------------------------------\n\t" +
|
||||||
"Application Jeecg-Boot is running! Access URLs:\n\t" +
|
"Application Jeecg-Boot is running! Access URLs:\n\t" +
|
||||||
"Local: \t\thttp://localhost:" + port + path + "/\n\t" +
|
"Local: \t\thttp://localhost:" + port + path + "/doc.html\n\t" +
|
||||||
"External: \thttp://" + ip + ":" + port + path + "/\n\t" +
|
"External: \thttp://" + ip + ":" + port + path + "/doc.html\n\t" +
|
||||||
"Swagger文档: \thttp://" + ip + ":" + port + path + "/doc.html\n" +
|
"Swagger文档: \thttp://" + ip + ":" + port + path + "/doc.html\n" +
|
||||||
"----------------------------------------------------------");
|
"----------------------------------------------------------");
|
||||||
|
|
||||||
|
|||||||
@ -40,12 +40,12 @@ spring:
|
|||||||
username: jeecgos@163.com
|
username: jeecgos@163.com
|
||||||
password: ??
|
password: ??
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail.smtp.timeout: 10000 # 连接超时(毫秒)
|
||||||
smtp:
|
mail.smtp.connectiontimeout: 10000 # 连接超时(毫秒)
|
||||||
auth: true
|
mail.smtp.writetimeout: 10000 # 写入超时(毫秒)
|
||||||
starttls:
|
mail.smtp.auth: true
|
||||||
enable: true
|
smtp.ssl.enable: true
|
||||||
required: true
|
mail.debug: true # 启用调试模式(查看详细日志)
|
||||||
## quartz定时任务,采用数据库方式
|
## quartz定时任务,采用数据库方式
|
||||||
quartz:
|
quartz:
|
||||||
job-store-type: jdbc
|
job-store-type: jdbc
|
||||||
|
|||||||
@ -38,12 +38,8 @@ spring:
|
|||||||
username: jeecgos@163.com
|
username: jeecgos@163.com
|
||||||
password: ??
|
password: ??
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail.smtp.auth: true
|
||||||
smtp:
|
smtp.ssl.enable: true
|
||||||
auth: true
|
|
||||||
starttls:
|
|
||||||
enable: true
|
|
||||||
required: true
|
|
||||||
## quartz定时任务,采用数据库方式
|
## quartz定时任务,采用数据库方式
|
||||||
quartz:
|
quartz:
|
||||||
job-store-type: jdbc
|
job-store-type: jdbc
|
||||||
|
|||||||
@ -38,12 +38,8 @@ spring:
|
|||||||
username: jeecgos@163.com
|
username: jeecgos@163.com
|
||||||
password: ??
|
password: ??
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail.smtp.auth: true
|
||||||
smtp:
|
smtp.ssl.enable: true
|
||||||
auth: true
|
|
||||||
starttls:
|
|
||||||
enable: true
|
|
||||||
required: true
|
|
||||||
## quartz定时任务,采用数据库方式
|
## quartz定时任务,采用数据库方式
|
||||||
quartz:
|
quartz:
|
||||||
job-store-type: jdbc
|
job-store-type: jdbc
|
||||||
|
|||||||
@ -36,12 +36,8 @@ spring:
|
|||||||
username: jeecgos@163.com
|
username: jeecgos@163.com
|
||||||
password: ??
|
password: ??
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail.smtp.auth: true
|
||||||
smtp:
|
smtp.ssl.enable: true
|
||||||
auth: true
|
|
||||||
starttls:
|
|
||||||
enable: true
|
|
||||||
required: true
|
|
||||||
## quartz定时任务,采用数据库方式
|
## quartz定时任务,采用数据库方式
|
||||||
quartz:
|
quartz:
|
||||||
job-store-type: jdbc
|
job-store-type: jdbc
|
||||||
|
|||||||
@ -38,12 +38,9 @@ spring:
|
|||||||
username: jeecgos@163.com
|
username: jeecgos@163.com
|
||||||
password: ??
|
password: ??
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail.smtp.auth: true
|
||||||
smtp:
|
smtp.ssl.enable: true
|
||||||
auth: true
|
mail.debug: true # 启用调试模式(查看详细日志)
|
||||||
starttls:
|
|
||||||
enable: true
|
|
||||||
required: true
|
|
||||||
## quartz定时任务,采用数据库方式
|
## quartz定时任务,采用数据库方式
|
||||||
quartz:
|
quartz:
|
||||||
job-store-type: jdbc
|
job-store-type: jdbc
|
||||||
|
|||||||
@ -0,0 +1,95 @@
|
|||||||
|
package org.jeecg.modules.openapi.test;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.client.methods.*;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class SampleOpenApiTest {
|
||||||
|
private final String base_url = "http://localhost:8080/jeecg-boot";
|
||||||
|
private final String appKey = "ak-pFjyNHWRsJEFWlu6";
|
||||||
|
private final String searchKey = "4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS";
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
// 根据部门ID查询用户
|
||||||
|
String url = base_url+"/openapi/call/TEwcXBlr?id=6d35e179cd814e3299bd588ea7daed3f";
|
||||||
|
JSONObject header = genTimestampAndSignature();
|
||||||
|
HttpGet httpGet = new HttpGet(url);
|
||||||
|
// 设置请求头
|
||||||
|
httpGet.setHeader("Content-Type", "application/json");
|
||||||
|
httpGet.setHeader("appkey",appKey);
|
||||||
|
httpGet.setHeader("signature",header.get("signature").toString());
|
||||||
|
httpGet.setHeader("timestamp",header.get("timestamp").toString());
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||||
|
CloseableHttpResponse response = httpClient.execute(httpGet);) {
|
||||||
|
// 获取响应状态码
|
||||||
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
|
System.out.println("[debug] 响应状态码: " + statusCode);
|
||||||
|
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
System.out.println(entity);
|
||||||
|
// 获取响应内容
|
||||||
|
String responseBody = EntityUtils.toString(response.getEntity());
|
||||||
|
System.out.println("[debug] 响应内容: " + responseBody);
|
||||||
|
|
||||||
|
// 解析JSON响应
|
||||||
|
JSONObject res = JSON.parseObject(responseBody);
|
||||||
|
//错误日志判断
|
||||||
|
if(res.containsKey("success")){
|
||||||
|
Boolean success = res.getBoolean("success");
|
||||||
|
if(success){
|
||||||
|
System.out.println("[info] 调用成功: " + res.toJSONString());
|
||||||
|
}else{
|
||||||
|
System.out.println("[error] 调用失败: " + res.getString("message"));
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
System.out.println("[error] 调用失败: " + res.getString("message"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private JSONObject genTimestampAndSignature(){
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
jsonObject.put("timestamp",timestamp);
|
||||||
|
jsonObject.put("signature", md5(appKey + searchKey + timestamp));
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成md5
|
||||||
|
* @param sourceStr
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected String md5(String sourceStr) {
|
||||||
|
String result = "";
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(sourceStr.getBytes("utf-8"));
|
||||||
|
byte[] hash = md.digest();
|
||||||
|
int i;
|
||||||
|
StringBuffer buf = new StringBuffer(32);
|
||||||
|
for (int offset = 0; offset < hash.length; offset++) {
|
||||||
|
i = hash[offset];
|
||||||
|
if (i < 0) {
|
||||||
|
i += 256;
|
||||||
|
}
|
||||||
|
if (i < 16) {
|
||||||
|
buf.append("0");
|
||||||
|
}
|
||||||
|
buf.append(Integer.toHexString(i));
|
||||||
|
}
|
||||||
|
result = buf.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("sign签名错误", e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,14 @@
|
|||||||
package org.jeecg.handler.swagger;
|
package org.jeecg.handler.swagger;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import springfox.documentation.swagger.web.*;
|
import springfox.documentation.swagger.web.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** 已使用knife4j-gateway支持该功能
|
/** 已使用knife4j-gateway支持该功能
|
||||||
@ -19,6 +21,9 @@ import java.util.List;
|
|||||||
public class SwaggerResourceController {
|
public class SwaggerResourceController {
|
||||||
private MySwaggerResourceProvider swaggerResourceProvider;
|
private MySwaggerResourceProvider swaggerResourceProvider;
|
||||||
|
|
||||||
|
@Value("${knife4j.gateway.enabled:true}")
|
||||||
|
private Boolean enableSwagger;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public SwaggerResourceController(MySwaggerResourceProvider swaggerResourceProvider) {
|
public SwaggerResourceController(MySwaggerResourceProvider swaggerResourceProvider) {
|
||||||
this.swaggerResourceProvider = swaggerResourceProvider;
|
this.swaggerResourceProvider = swaggerResourceProvider;
|
||||||
@ -36,6 +41,9 @@ public class SwaggerResourceController {
|
|||||||
|
|
||||||
@RequestMapping
|
@RequestMapping
|
||||||
public ResponseEntity<List<SwaggerResource>> swaggerResources() {
|
public ResponseEntity<List<SwaggerResource>> swaggerResources() {
|
||||||
|
if (!enableSwagger) {
|
||||||
|
return new ResponseEntity<>(new ArrayList<>(), HttpStatus.OK);
|
||||||
|
}
|
||||||
return new ResponseEntity<>(swaggerResourceProvider.get(), HttpStatus.OK);
|
return new ResponseEntity<>(swaggerResourceProvider.get(), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,11 +4,6 @@ server:
|
|||||||
knife4j:
|
knife4j:
|
||||||
gateway:
|
gateway:
|
||||||
enabled: true
|
enabled: true
|
||||||
strategy: discover
|
|
||||||
discover:
|
|
||||||
excluded-services: ${spring.application.name}
|
|
||||||
enabled: true
|
|
||||||
version: OpenAPI3
|
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<jeecgboot.version>3.8.0</jeecgboot.version>
|
<jeecgboot.version>3.8.0</jeecgboot.version>
|
||||||
<!-- JDK版本支持17和1.8 -->
|
<!-- JDK版本支持17和1.8 -->
|
||||||
<java.version>1.8</java.version>
|
<java.version>17</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<!-- 微服务 -->
|
<!-- 微服务 -->
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<seata.version>1.5.2</seata.version>
|
<seata.version>1.5.2</seata.version>
|
||||||
|
|
||||||
<xxl-job-core.version>2.4.1</xxl-job-core.version>
|
<xxl-job-core.version>2.4.1</xxl-job-core.version>
|
||||||
<fastjson.version>2.0.56</fastjson.version>
|
<fastjson.version>2.0.57</fastjson.version>
|
||||||
<aviator.version>5.2.6</aviator.version>
|
<aviator.version>5.2.6</aviator.version>
|
||||||
<pegdown.version>1.6.0</pegdown.version>
|
<pegdown.version>1.6.0</pegdown.version>
|
||||||
<commonmark.version>0.17.0</commonmark.version>
|
<commonmark.version>0.17.0</commonmark.version>
|
||||||
@ -56,7 +56,8 @@
|
|||||||
<dm8.version>8.1.1.49</dm8.version>
|
<dm8.version>8.1.1.49</dm8.version>
|
||||||
|
|
||||||
<!-- 积木报表-->
|
<!-- 积木报表-->
|
||||||
<jimureport-spring-boot-starter.version>1.9.5</jimureport-spring-boot-starter.version>
|
<jimureport-spring-boot-starter.version>2.0.0</jimureport-spring-boot-starter.version>
|
||||||
|
<minidao.version>1.10.10</minidao.version>
|
||||||
<!-- 持久层 -->
|
<!-- 持久层 -->
|
||||||
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
|
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
|
||||||
<dynamic-datasource-spring-boot-starter.version>4.1.3</dynamic-datasource-spring-boot-starter.version>
|
<dynamic-datasource-spring-boot-starter.version>4.1.3</dynamic-datasource-spring-boot-starter.version>
|
||||||
@ -68,8 +69,8 @@
|
|||||||
<aliyun.oss.version>3.11.2</aliyun.oss.version>
|
<aliyun.oss.version>3.11.2</aliyun.oss.version>
|
||||||
<!-- shiro -->
|
<!-- shiro -->
|
||||||
<shiro.version>1.13.0</shiro.version>
|
<shiro.version>1.13.0</shiro.version>
|
||||||
<java-jwt.version>4.5.0</java-jwt.version>
|
|
||||||
<shiro-redis.version>3.2.3</shiro-redis.version>
|
<shiro-redis.version>3.2.3</shiro-redis.version>
|
||||||
|
<java-jwt.version>4.5.0</java-jwt.version>
|
||||||
<codegenerate.version>1.4.9</codegenerate.version>
|
<codegenerate.version>1.4.9</codegenerate.version>
|
||||||
<autopoi-web.version>1.4.11</autopoi-web.version>
|
<autopoi-web.version>1.4.11</autopoi-web.version>
|
||||||
<minio.version>8.0.3</minio.version>
|
<minio.version>8.0.3</minio.version>
|
||||||
@ -254,7 +255,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>hibernate-re</artifactId>
|
<artifactId>hibernate-re</artifactId>
|
||||||
<version>3.8.0-GA</version>
|
<version>3.8.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--mongon db-->
|
<!--mongon db-->
|
||||||
@ -359,7 +360,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework</groupId>
|
<groupId>org.jeecgframework</groupId>
|
||||||
<artifactId>weixin4j</artifactId>
|
<artifactId>weixin4j</artifactId>
|
||||||
<version>2.0.2</version>
|
<version>2.0.4</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<artifactId>commons-beanutils</artifactId>
|
<artifactId>commons-beanutils</artifactId>
|
||||||
@ -383,6 +384,22 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- minidao -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework</groupId>
|
||||||
|
<artifactId>minidao-spring-boot-starter</artifactId>
|
||||||
|
<version>${minidao.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>jsqlparser</artifactId>
|
||||||
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
<!-- 积木报表-->
|
<!-- 积木报表-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.jimureport</groupId>
|
<groupId>org.jeecgframework.jimureport</groupId>
|
||||||
@ -418,7 +435,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.jimureport</groupId>
|
<groupId>org.jeecgframework.jimureport</groupId>
|
||||||
<artifactId>jimureport-nosql-starter</artifactId>
|
<artifactId>jimureport-nosql-starter</artifactId>
|
||||||
<version>${jimureport-spring-boot-starter.version}</version>
|
<version>1.9.5.2</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.apache.calcite</groupId>
|
<groupId>org.apache.calcite</groupId>
|
||||||
@ -430,13 +447,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.jimureport</groupId>
|
<groupId>org.jeecgframework.jimureport</groupId>
|
||||||
<artifactId>jimubi-spring-boot-starter</artifactId>
|
<artifactId>jimubi-spring-boot-starter</artifactId>
|
||||||
<version>1.9.4</version>
|
<version>1.9.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- AI集成 -->
|
<!-- AI集成 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
|
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
|
||||||
<!--mysql5.6,需要把版本号改成5.2.1-->
|
<!--mysql5.6,需要把版本号改成5.2.1-->
|
||||||
|
|||||||
1
jeecgboot-vue3/.gitignore
vendored
1
jeecgboot-vue3/.gitignore
vendored
@ -2,7 +2,6 @@ node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.github
|
.github
|
||||||
dist
|
dist
|
||||||
.npmrc
|
|
||||||
.cache
|
.cache
|
||||||
|
|
||||||
tests/server/static
|
tests/server/static
|
||||||
|
|||||||
2
jeecgboot-vue3/.npmrc
Normal file
2
jeecgboot-vue3/.npmrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
shamefully-hoist=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
@ -66,6 +66,7 @@
|
|||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
|
"swagger-ui-dist": "^5.21.0",
|
||||||
"tinymce": "6.6.2",
|
"tinymce": "6.6.2",
|
||||||
"vditor": "^3.10.8",
|
"vditor": "^3.10.8",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
@ -77,8 +78,9 @@
|
|||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vue-types": "^5.1.3",
|
"vue-types": "^5.1.3",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"vxe-table": "4.6.17",
|
"vxe-table": "4.13.31",
|
||||||
"vxe-table-plugin-antd": "4.0.7",
|
"vxe-table-plugin-antd": "4.0.8",
|
||||||
|
"vxe-pc-ui": "4.6.12",
|
||||||
"xe-utils": "3.5.26",
|
"xe-utils": "3.5.26",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-upload name="file" :showUploadList="false" :customRequest="(file) => onClick(file)">
|
<a-upload name="file" :showUploadList="false" :customRequest="(file) => onClick(file)">
|
||||||
<Button :type="type" :class="getButtonClass">
|
<Button :type="type" :class="getButtonClass" :disabled="props.disabled">
|
||||||
<template #default="data">
|
<template #default="data">
|
||||||
<Icon :icon="preIcon" v-if="preIcon" :size="iconSize" />
|
<Icon :icon="preIcon" v-if="preIcon" :size="iconSize" />
|
||||||
<slot v-bind="data || {}"></slot>
|
<slot v-bind="data || {}"></slot>
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export { default as JDictSelectTag } from './src/jeecg/components/JDictSelectTag
|
|||||||
export { default as JTreeSelect } from './src/jeecg/components/JTreeSelect.vue';
|
export { default as JTreeSelect } from './src/jeecg/components/JTreeSelect.vue';
|
||||||
export { default as JSearchSelect } from './src/jeecg/components/JSearchSelect.vue';
|
export { default as JSearchSelect } from './src/jeecg/components/JSearchSelect.vue';
|
||||||
export { default as JSelectUserByDept } from './src/jeecg/components/JSelectUserByDept.vue';
|
export { default as JSelectUserByDept } from './src/jeecg/components/JSelectUserByDept.vue';
|
||||||
|
export { default as JSelectUserByDepartment } from './src/jeecg/components/JSelectUserByDepartment.vue';
|
||||||
export { default as JEditor } from './src/jeecg/components/JEditor.vue';
|
export { default as JEditor } from './src/jeecg/components/JEditor.vue';
|
||||||
export { default as JImageUpload } from './src/jeecg/components/JImageUpload.vue';
|
export { default as JImageUpload } from './src/jeecg/components/JImageUpload.vue';
|
||||||
// Jeecg自定义校验
|
// Jeecg自定义校验
|
||||||
|
|||||||
@ -64,6 +64,7 @@ import JInput from './jeecg/components/JInput.vue';
|
|||||||
import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
||||||
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
||||||
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
||||||
|
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
|
||||||
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
||||||
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
||||||
import JAddInput from './jeecg/components/JAddInput.vue';
|
import JAddInput from './jeecg/components/JAddInput.vue';
|
||||||
@ -159,6 +160,7 @@ componentMap.set('JInput', JInput);
|
|||||||
componentMap.set('JTreeSelect', JTreeSelect);
|
componentMap.set('JTreeSelect', JTreeSelect);
|
||||||
componentMap.set('JEllipsis', JEllipsis);
|
componentMap.set('JEllipsis', JEllipsis);
|
||||||
componentMap.set('JSelectUserByDept', JSelectUserByDept);
|
componentMap.set('JSelectUserByDept', JSelectUserByDept);
|
||||||
|
componentMap.set('JSelectUserByDepartment', JSelectUserByDepartment);
|
||||||
componentMap.set('JUpload', JUpload);
|
componentMap.set('JUpload', JUpload);
|
||||||
componentMap.set('JSearchSelect', JSearchSelect);
|
componentMap.set('JSearchSelect', JSearchSelect);
|
||||||
componentMap.set('JAddInput', JAddInput);
|
componentMap.set('JAddInput', JAddInput);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!--职务选择组件-->
|
<!--职务选择组件-->
|
||||||
<template>
|
<template>
|
||||||
<div class="JSelectPosition">
|
<div class="JSelectPosition">
|
||||||
<JSelectBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JSelectBiz>
|
<JSelectBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" @change="(changeValue) => $emit('update:value', changeValue)"></JSelectBiz>
|
||||||
<!-- update-begin--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
|
<!-- update-begin--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<PositionSelectModal @register="regModal" @getSelectResult="setValue" v-bind="getBindValue"></PositionSelectModal>
|
<PositionSelectModal @register="regModal" @getSelectResult="setValue" v-bind="getBindValue"></PositionSelectModal>
|
||||||
@ -51,7 +51,7 @@
|
|||||||
//下拉框选项值
|
//下拉框选项值
|
||||||
const selectOptions = ref<SelectValue>([]);
|
const selectOptions = ref<SelectValue>([]);
|
||||||
//下拉框选中值
|
//下拉框选中值
|
||||||
let selectValues = reactive<object>({
|
let selectValues = reactive<any>({
|
||||||
value: [],
|
value: [],
|
||||||
change: false,
|
change: false,
|
||||||
});
|
});
|
||||||
@ -70,9 +70,23 @@
|
|||||||
/**
|
/**
|
||||||
* 监听组件值
|
* 监听组件值
|
||||||
*/
|
*/
|
||||||
watchEffect(() => {
|
// update-begin--author:liaozhiyang---date:20250423---for:【pull/8014】插槽方式弹窗中取消该数据checkbox的选中状态,需要点击第二次才生效。
|
||||||
props.value && initValue();
|
watch(
|
||||||
});
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
if (props.value) {
|
||||||
|
initValue();
|
||||||
|
} else {
|
||||||
|
// update-begin--author:liaozhiyang---date:20250604---for:【issues/8233】resetFields时无法重置
|
||||||
|
if (selectValues.value?.length) {
|
||||||
|
selectValues.value = [];
|
||||||
|
}
|
||||||
|
// update-end--author:liaozhiyang---date:20250604---for:【issues/8233】resetFields时无法重置
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【pull/8014】插槽方式弹窗中取消该数据checkbox的选中状态,需要点击第二次才生效。
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听selectValues变化
|
* 监听selectValues变化
|
||||||
|
|||||||
@ -0,0 +1,176 @@
|
|||||||
|
<!--用户选择组件-->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<JSelectBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" @change="handleSelectChange"></JSelectBiz>
|
||||||
|
<JSelectUserByDepartmentModal
|
||||||
|
v-if="modalShow"
|
||||||
|
:selectedUser="selectOptions"
|
||||||
|
:modalTitle="modalTitle"
|
||||||
|
:rowKey="rowKey"
|
||||||
|
:labelKey="labelKey"
|
||||||
|
:isRadioSelection="isRadioSelection"
|
||||||
|
:params="params"
|
||||||
|
@register="regModal"
|
||||||
|
@change="setValue"
|
||||||
|
@close="() => (modalShow = false)"
|
||||||
|
v-bind="attrs"
|
||||||
|
></JSelectUserByDepartmentModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, watch, provide } from 'vue';
|
||||||
|
import JSelectUserByDepartmentModal from './modal/JSelectUserByDepartmentModal.vue';
|
||||||
|
import JSelectBiz from './base/JSelectBiz.vue';
|
||||||
|
import { useModal } from '/@/components/Modal';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||||
|
import { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
import { isArray, isString, isObject } from '/@/utils/is';
|
||||||
|
import { getTableList as getTableListOrigin } from '/@/api/common/api';
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
|
|
||||||
|
defineOptions({ name: 'JSelectUserByDepartment' });
|
||||||
|
const props = defineProps({
|
||||||
|
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||||
|
modalTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '部门用户选择',
|
||||||
|
},
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'username',
|
||||||
|
},
|
||||||
|
labelKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'realname',
|
||||||
|
},
|
||||||
|
//查询参数
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
isRadioSelection: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['options-change', 'change', 'update:value']);
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
//注册model
|
||||||
|
const [regModal, { openModal }] = useModal();
|
||||||
|
// 是否显示弹窗
|
||||||
|
const modalShow = ref(false);
|
||||||
|
//下拉框选项值
|
||||||
|
const selectOptions: any = ref<SelectValue>([]);
|
||||||
|
//下拉框选中值
|
||||||
|
let selectValues: any = reactive<object>({
|
||||||
|
value: [],
|
||||||
|
change: false,
|
||||||
|
});
|
||||||
|
// 是否正在加载回显数据
|
||||||
|
const loadingEcho = ref<boolean>(false);
|
||||||
|
//下发 selectOptions,xxxBiz组件接收
|
||||||
|
provide('selectOptions', selectOptions);
|
||||||
|
//下发 selectValues,xxxBiz组件接收
|
||||||
|
provide('selectValues', selectValues);
|
||||||
|
//下发 loadingEcho,xxxBiz组件接收
|
||||||
|
provide('loadingEcho', loadingEcho);
|
||||||
|
|
||||||
|
const attrs: any = useAttrs();
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
function handleOpen() {
|
||||||
|
modalShow.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
openModal(true, {
|
||||||
|
isUpdate: false,
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
const handleSelectChange = (data) => {
|
||||||
|
selectOptions.value = selectOptions.value.filter((item) => data.includes(item[props.rowKey]));
|
||||||
|
setValue(selectOptions.value);
|
||||||
|
};
|
||||||
|
// 设置下拉框的值
|
||||||
|
const setValue = (data) => {
|
||||||
|
selectOptions.value = data.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: item[props.labelKey],
|
||||||
|
value: item[props.rowKey],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
selectValues.value = data.map((item) => item[props.rowKey]);
|
||||||
|
// 更新value
|
||||||
|
emit('update:value', selectValues.value);
|
||||||
|
// 触发change事件(不转是因为basicForm提交时会自动将字符串转化为数组)
|
||||||
|
emit('change', selectValues.value);
|
||||||
|
// 触发options-change事件
|
||||||
|
emit('options-change', selectOptions.value);
|
||||||
|
};
|
||||||
|
// 翻译
|
||||||
|
const transform = () => {
|
||||||
|
let value = props.value;
|
||||||
|
let len;
|
||||||
|
if (isArray(value) || isString(value)) {
|
||||||
|
if (isArray(value)) {
|
||||||
|
len = value.length;
|
||||||
|
value = value.join(',');
|
||||||
|
} else {
|
||||||
|
len = value.split(',').length;
|
||||||
|
}
|
||||||
|
value = value.trim();
|
||||||
|
if (value) {
|
||||||
|
// 如果value的值在selectedUser中存在,则不请求翻译
|
||||||
|
let isNotRequestTransform = false;
|
||||||
|
isNotRequestTransform = value.split(',').every((value) => !!selectOptions.value.find((item) => item[props.rowKey] === value));
|
||||||
|
if (isNotRequestTransform) {
|
||||||
|
selectValues.value = value.split(',')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = { isMultiTranslate: true, pageSize: len, [props.rowKey]: value };
|
||||||
|
if (isObject(attrs.params)) {
|
||||||
|
Object.assign(params, attrs.params);
|
||||||
|
}
|
||||||
|
getTableListOrigin(params).then((result: any) => {
|
||||||
|
const records = result.records ?? [];
|
||||||
|
selectValues.value = records.map((item) => item[props.rowKey]);
|
||||||
|
selectOptions.value = records.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: item[props.labelKey],
|
||||||
|
value: item[props.rowKey],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectValues.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 监听value变化
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
transform();
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.j-select-row {
|
||||||
|
@width: 82px;
|
||||||
|
.left {
|
||||||
|
width: calc(100% - @width - 8px);
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
width: @width;
|
||||||
|
}
|
||||||
|
.full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
:deep(.ant-select-search__field) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||||
import { TreeSelect } from 'ant-design-vue';
|
import { TreeSelect } from 'ant-design-vue';
|
||||||
import { useMessage } from '/@/hooks/web/useMessage';
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
import { isObject } from '/@/utils/is';
|
import { isObject, isArray } from '/@/utils/is';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
enum Api {
|
enum Api {
|
||||||
url = '/sys/dict/loadTreeData',
|
url = '/sys/dict/loadTreeData',
|
||||||
@ -143,6 +143,23 @@
|
|||||||
if(props.url){
|
if(props.url){
|
||||||
getItemFromTreeData();
|
getItemFromTreeData();
|
||||||
}else{
|
}else{
|
||||||
|
// update-begin--author:liaozhiyang---date:20250423---for:【issues/8093】选择节点后会先变成编码再显示label文字
|
||||||
|
if (props.value) {
|
||||||
|
if (isArray(treeValue.value)) {
|
||||||
|
let isNotRequestTransform = false;
|
||||||
|
const value = isArray(props.value) ? props.value : props.value.split(',');
|
||||||
|
isNotRequestTransform = value.every((value) => !!treeValue.value.find((item) => item.value === value));
|
||||||
|
if (isNotRequestTransform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (isObject(treeValue.value) && unref(treeValue).label != null) {
|
||||||
|
if (props.value == unref(treeValue).value) {
|
||||||
|
// 不需要再去请求翻译
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【issues/8093】选择节点后会先变成编码再显示label文字
|
||||||
let params = { key: props.value };
|
let params = { key: props.value };
|
||||||
let result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false });
|
let result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false });
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@ -291,7 +308,22 @@
|
|||||||
} else {
|
} else {
|
||||||
emitValue(value.value);
|
emitValue(value.value);
|
||||||
}
|
}
|
||||||
treeValue.value = value;
|
// update-begin--author:liaozhiyang---date:20250423---for:【issues/8093】删除后会先变成编码再显示label文字
|
||||||
|
if (isArray(value)) {
|
||||||
|
// 编辑删除时有选中的值是异步(第二级以上的)会不显示label
|
||||||
|
value.forEach((item) => {
|
||||||
|
if (item.label === undefined && item.value != null) {
|
||||||
|
const findItem = treeValue.value.find((o) => o.value === item.value);
|
||||||
|
if (findItem) {
|
||||||
|
item.label = findItem.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
treeValue.value = value;
|
||||||
|
} else {
|
||||||
|
treeValue.value = value;
|
||||||
|
}
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【issues/8093】删除后会先变成编码再显示label文字
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitValue(value) {
|
function emitValue(value) {
|
||||||
|
|||||||
@ -0,0 +1,833 @@
|
|||||||
|
<template>
|
||||||
|
<BasicModal
|
||||||
|
wrapClassName="JSelectUserByDepartmentModal"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@register="register"
|
||||||
|
:title="modalTitle"
|
||||||
|
width="800px"
|
||||||
|
@ok="handleOk"
|
||||||
|
destroyOnClose
|
||||||
|
@visible-change="visibleChange"
|
||||||
|
>
|
||||||
|
<div class="j-select-user-by-dept">
|
||||||
|
<div class="modal-content">
|
||||||
|
<!-- 左侧搜索和组织列表 -->
|
||||||
|
<div class="left-content">
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="search-box">
|
||||||
|
<a-input v-model:value.trim="searchText" placeholder="搜索" @change="handleSearch" @pressEnter="handleSearch" allowClear />
|
||||||
|
</div>
|
||||||
|
<!-- 组织架构 -->
|
||||||
|
<div class="tree-box">
|
||||||
|
<template v-if="searchText.length">
|
||||||
|
<template v-if="searchResult.depart.length || searchResult.user.length">
|
||||||
|
<div class="search-result">
|
||||||
|
<template v-if="searchResult.user.length">
|
||||||
|
<div class="search-user">
|
||||||
|
<p class="search-user-title">人员</p>
|
||||||
|
<template v-for="item in searchResult.user" :key="item.id">
|
||||||
|
<div class="search-user-item" @click="handleSearchUserCheck(item)">
|
||||||
|
<a-checkbox v-model:checked="item.checked" />
|
||||||
|
<div class="right">
|
||||||
|
<div class="search-user-item-circle">
|
||||||
|
<img v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" alt="avatar" />
|
||||||
|
</div>
|
||||||
|
<div class="search-user-item-info">
|
||||||
|
<div class="search-user-item-name">{{ item.realname }}</div>
|
||||||
|
<div class="search-user-item-org">{{ item.orgCodeTxt }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="searchResult.depart.length">
|
||||||
|
<div class="search-depart">
|
||||||
|
<p class="search-depart-title">部门</p>
|
||||||
|
<template v-for="item in searchResult.depart" :key="item.id">
|
||||||
|
<div class="search-depart-item" @click="handleSearchDepartClick(item)">
|
||||||
|
<a-checkbox v-model:checked="item.checked" @click.stop @change="($event) => handleSearchDepartCheck($event, item)" />
|
||||||
|
<div class="search-depart-item-name">{{ item.departName }}</div>
|
||||||
|
<RightOutlined />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="no-data">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-breadcrumb v-if="breadcrumb.length">
|
||||||
|
<a-breadcrumb-item @click="handleBreadcrumbClick()">
|
||||||
|
<HomeOutlined />
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
<template v-for="item in breadcrumb" :key="item?.id">
|
||||||
|
<a-breadcrumb-item @click="handleBreadcrumbClick(item)">
|
||||||
|
<span>{{ item.departName }}</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</template>
|
||||||
|
</a-breadcrumb>
|
||||||
|
<div v-if="currentDepartUsers.length">
|
||||||
|
<!-- 当前部门用户树 -->
|
||||||
|
<div class="depart-users-tree">
|
||||||
|
<div v-if="!currentDepartTree.length" class="allChecked">
|
||||||
|
<a-checkbox v-model:checked="currentDepartAllUsers" @change="handleAllUsers">全选</a-checkbox>
|
||||||
|
</div>
|
||||||
|
<template v-for="item in currentDepartUsers" :key="item.id">
|
||||||
|
<div class="depart-users-tree-item" @click="handleDepartUsersTreeCheck(item)">
|
||||||
|
<a-checkbox v-model:checked="item.checked" />
|
||||||
|
<div class="right">
|
||||||
|
<div class="depart-users-tree-item-circle">
|
||||||
|
<img v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" alt="avatar" />
|
||||||
|
</div>
|
||||||
|
<div class="depart-users-tree-item-name">{{ item.realname }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 部门树 -->
|
||||||
|
<div v-if="currentDepartTree.length" class="depart-tree">
|
||||||
|
<template v-for="item in currentDepartTree" :key="item.id">
|
||||||
|
<div class="depart-tree-item" @click="handleDepartTreeClick(item)">
|
||||||
|
<a-checkbox v-model:checked="item.checked" @click.stop @change="($event) => handleDepartTreeCheck($event, item)" />
|
||||||
|
<div class="depart-tree-item-name">{{ item.departName }}</div>
|
||||||
|
<RightOutlined />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-if="currentDepartTree.length === 0 && currentDepartUsers.length === 0" class="no-data">
|
||||||
|
<a-empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧已选人员展示 -->
|
||||||
|
<div class="right-content">
|
||||||
|
<div class="selected-header"> 已选人员:{{ selectedUsers.length }}人 </div>
|
||||||
|
<div class="selected-users">
|
||||||
|
<div class="content">
|
||||||
|
<div v-for="user in selectedUsers" :key="user.id" class="user-avatar" @click="handleDelUser(user)">
|
||||||
|
<div class="avatar-circle">
|
||||||
|
<img v-if="user.avatar" :src="getFileAccessHttpUrl(user.avatar)" alt="avatar" />
|
||||||
|
<div class="mask">
|
||||||
|
<CloseOutlined></CloseOutlined>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-name">{{ user.realname }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { RightOutlined, HomeOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||||
|
import { queryTreeList, getTableList as getTableListOrigin } from '/@/api/common/api';
|
||||||
|
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||||
|
import { isArray } from '/@/utils/is';
|
||||||
|
import { defHttp } from '/@/utils/http/axios';
|
||||||
|
|
||||||
|
defineOptions({ name: 'JSelectUserByDepartmentModal' });
|
||||||
|
const props = defineProps({
|
||||||
|
// ...selectProps,
|
||||||
|
//回传value字段名
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'id',
|
||||||
|
},
|
||||||
|
//回传文本字段名
|
||||||
|
labelKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'name',
|
||||||
|
},
|
||||||
|
modalTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '部门用户选择',
|
||||||
|
},
|
||||||
|
selectedUser: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
//查询参数
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
//最大选择数量
|
||||||
|
maxSelectCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
// 是否单选
|
||||||
|
isRadioSelection: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['close', 'register', 'change']);
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
// 搜索文本
|
||||||
|
const searchText = ref('');
|
||||||
|
const breadcrumb = ref<any[]>([]);
|
||||||
|
// 部门树(整颗树)
|
||||||
|
const departTree = ref([]);
|
||||||
|
// 当前部门树
|
||||||
|
const currentDepartTree = ref<any[]>([]);
|
||||||
|
// 选中的部门节点
|
||||||
|
const checkedDepartIds = ref<string[]>([]);
|
||||||
|
// 当前部门用户
|
||||||
|
const currentDepartUsers = ref([]);
|
||||||
|
// 已选用户
|
||||||
|
const selectedUsers = ref<any[]>([]);
|
||||||
|
// 全选
|
||||||
|
const currentDepartAllUsers = ref(false);
|
||||||
|
// 搜索结构
|
||||||
|
const searchResult: any = reactive({
|
||||||
|
depart: [],
|
||||||
|
user: [],
|
||||||
|
});
|
||||||
|
// 映射部门和人员的关系
|
||||||
|
const cacheDepartUser = {};
|
||||||
|
//注册弹框
|
||||||
|
const [register, { closeModal }] = useModalInner(async (data) => {
|
||||||
|
// 初始化
|
||||||
|
if (props.selectedUser.length) {
|
||||||
|
// 编辑时,传进来已选中的数据
|
||||||
|
selectedUsers.value = props.selectedUser;
|
||||||
|
}
|
||||||
|
getQueryTreeList();
|
||||||
|
});
|
||||||
|
const visibleChange = (visible) => {
|
||||||
|
if (visible === false) {
|
||||||
|
setTimeout(() => {
|
||||||
|
emit('close');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleOk = () => {
|
||||||
|
if (selectedUsers.value.length == 0) {
|
||||||
|
createMessage.warning('请选择人员');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.isRadioSelection && selectedUsers.value.length > 1) {
|
||||||
|
createMessage.warning('只允许选择一个用户');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.maxSelectCount && selectedUsers.value.length > props.maxSelectCount) {
|
||||||
|
createMessage.warning(`最多只能选择${props.maxSelectCount}个用户`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit('change', selectedUsers.value);
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
// 搜索人员/部门
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (searchText.value) {
|
||||||
|
defHttp
|
||||||
|
.get({
|
||||||
|
url: `/sys/user/listAll`,
|
||||||
|
params: {
|
||||||
|
column: 'createTime',
|
||||||
|
order: 'desc',
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
realname: `*${searchText.value}*`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
result.records?.forEach((item) => {
|
||||||
|
const findItem = selectedUsers.value.find((user) => user.id == item.id);
|
||||||
|
if (findItem) {
|
||||||
|
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||||
|
item.checked = true;
|
||||||
|
} else {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
searchResult.user = result.records ?? [];
|
||||||
|
});
|
||||||
|
searchResult.depart = getDepartByName(searchText.value) ?? [];
|
||||||
|
} else {
|
||||||
|
searchResult.user = [];
|
||||||
|
searchResult.depart = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 面包屑
|
||||||
|
const handleBreadcrumbClick = (item?) => {
|
||||||
|
// 先清空
|
||||||
|
currentDepartUsers.value = [];
|
||||||
|
if (item) {
|
||||||
|
const findIndex = breadcrumb.value.findIndex((o) => o.id === item.id);
|
||||||
|
if (findIndex != -1) {
|
||||||
|
breadcrumb.value = breadcrumb.value.filter((item, index) => {
|
||||||
|
console.log(item);
|
||||||
|
return index <= findIndex;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const data = getDepartTreeNodeById(item.id, departTree.value);
|
||||||
|
currentDepartTree.value = data.children;
|
||||||
|
} else {
|
||||||
|
// 根节点
|
||||||
|
currentDepartTree.value = departTree.value;
|
||||||
|
breadcrumb.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 点击部门树复选框触发
|
||||||
|
const handleDepartTreeCheck = (e, item) => {
|
||||||
|
const { target } = e;
|
||||||
|
if (target.checked) {
|
||||||
|
// 选中
|
||||||
|
getUsersByDeptId(item['id']).then((users) => {
|
||||||
|
addUsers(users);
|
||||||
|
});
|
||||||
|
checkedDepartIds.value.push((item as any).id);
|
||||||
|
// 检查父节点下所有子节点是否选中
|
||||||
|
const parentItem = getDepartTreeParentById(item.id);
|
||||||
|
if (parentItem?.children) {
|
||||||
|
const isChildAllChecked = parentItem.children.every((item) => item.checked);
|
||||||
|
if (isChildAllChecked) {
|
||||||
|
parentItem.checked = true;
|
||||||
|
} else {
|
||||||
|
parentItem.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 取消选中
|
||||||
|
const findIndex = checkedDepartIds.value.findIndex((o: any) => o.id === item.id);
|
||||||
|
if (findIndex != -1) {
|
||||||
|
checkedDepartIds.value.splice(findIndex, 1);
|
||||||
|
}
|
||||||
|
// 如果父节点是选中,则需要取消
|
||||||
|
const parentItem = getDepartTreeParentById(item.id);
|
||||||
|
if (parentItem) {
|
||||||
|
parentItem.checked = false;
|
||||||
|
}
|
||||||
|
getUsersByDeptId(item['id']).then((users) => {
|
||||||
|
users.forEach((item) => {
|
||||||
|
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id);
|
||||||
|
if (findIndex != -1) {
|
||||||
|
selectedUsers.value.splice(findIndex, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 点击部门树节点触发
|
||||||
|
const handleDepartTreeClick = (item) => {
|
||||||
|
breadcrumb.value = [...breadcrumb.value, item];
|
||||||
|
if (item.children) {
|
||||||
|
// 有子节点,则显示部门
|
||||||
|
if (item.checked) {
|
||||||
|
// 父节点勾选,则子节点全部勾选
|
||||||
|
item.children.forEach((item) => {
|
||||||
|
item.checked = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentDepartTree.value = item.children;
|
||||||
|
defHttp
|
||||||
|
.get({
|
||||||
|
url: '/sys/sysDepart/getUsersByDepartId',
|
||||||
|
params: {
|
||||||
|
id: item['id'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res: any) => {
|
||||||
|
const result = res ?? [];
|
||||||
|
if (item.checked) {
|
||||||
|
// 父节点勾选,则默认勾选
|
||||||
|
result.forEach((item) => {
|
||||||
|
item.checked = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 右侧勾选了,则默认勾选(用户存在多部门,在别的部门被选中了)
|
||||||
|
if (selectedUsers.value.length) {
|
||||||
|
result.forEach((item) => {
|
||||||
|
const findItem = selectedUsers.value.find((user) => user.id === item.id);
|
||||||
|
if (findItem) {
|
||||||
|
// 说明在selectedUsers中被找到
|
||||||
|
item.checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentDepartUsers.value = result;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 没有子节点,则显示用户
|
||||||
|
currentDepartTree.value = [];
|
||||||
|
getTableList({
|
||||||
|
departId: item['id'],
|
||||||
|
}).then((res: any) => {
|
||||||
|
if (res?.records) {
|
||||||
|
let checked = true;
|
||||||
|
res.records.forEach((item) => {
|
||||||
|
const findItem = selectedUsers.value.find((user) => user.id == item.id);
|
||||||
|
if (findItem) {
|
||||||
|
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||||
|
item.checked = true;
|
||||||
|
} else {
|
||||||
|
item.checked = false;
|
||||||
|
checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentDepartAllUsers.value = checked;
|
||||||
|
currentDepartUsers.value = res.records;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 点击部门用户树复选框触发
|
||||||
|
const handleDepartUsersTreeCheck = (item) => {
|
||||||
|
item.checked = !item.checked;
|
||||||
|
if (item.checked) {
|
||||||
|
addUsers(item);
|
||||||
|
} else {
|
||||||
|
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id);
|
||||||
|
}
|
||||||
|
if (item.checked == false) {
|
||||||
|
// 有一个是false,则全选false
|
||||||
|
currentDepartAllUsers.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 全选
|
||||||
|
const handleAllUsers = ({ target }) => {
|
||||||
|
const { checked } = target;
|
||||||
|
if (checked) {
|
||||||
|
currentDepartUsers.value.forEach((item: any) => (item.checked = true));
|
||||||
|
addUsers(currentDepartUsers.value);
|
||||||
|
} else {
|
||||||
|
currentDepartUsers.value.forEach((item: any) => (item.checked = false));
|
||||||
|
selectedUsers.value = selectedUsers.value.filter((user) => {
|
||||||
|
const userId = user.id;
|
||||||
|
const findItem = currentDepartUsers.value.find((item: any) => item.id === userId);
|
||||||
|
if (findItem) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 删除人员
|
||||||
|
const handleDelUser = (item) => {
|
||||||
|
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id);
|
||||||
|
if (findIndex != -1) {
|
||||||
|
selectedUsers.value.splice(findIndex, 1);
|
||||||
|
}
|
||||||
|
const findItem: any = currentDepartUsers.value.find((user: any) => user.id === item.id);
|
||||||
|
if (findItem) {
|
||||||
|
findItem.checked = false;
|
||||||
|
currentDepartAllUsers.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 点击搜索用户复选框
|
||||||
|
const handleSearchUserCheck = (item) => {
|
||||||
|
item.checked = !item.checked;
|
||||||
|
if (item.checked) {
|
||||||
|
addUsers(item);
|
||||||
|
} else {
|
||||||
|
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 点击搜索部门复选框
|
||||||
|
const handleSearchDepartCheck = (e, item) => {
|
||||||
|
handleDepartTreeCheck(e, item);
|
||||||
|
};
|
||||||
|
// 点击搜索部门
|
||||||
|
const handleSearchDepartClick = (item) => {
|
||||||
|
searchResult.depart = [];
|
||||||
|
searchResult.user = [];
|
||||||
|
breadcrumb.value = getPathToNodeById(item.id);
|
||||||
|
handleDepartTreeClick(item);
|
||||||
|
};
|
||||||
|
// 添加人员到右侧
|
||||||
|
const addUsers = (users) => {
|
||||||
|
let newUsers: any = [];
|
||||||
|
if (isArray(users)) {
|
||||||
|
// selectedUsers里面没有才添加(防止重复)
|
||||||
|
newUsers = users.filter((user: any) => !selectedUsers.value.find((item) => item.id === user.id));
|
||||||
|
} else {
|
||||||
|
if (!selectedUsers.value.find((user) => user.id === users.id)) {
|
||||||
|
// selectedUsers里面没有才添加(防止重复)
|
||||||
|
newUsers = [users];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedUsers.value = [...selectedUsers.value, ...newUsers];
|
||||||
|
const result = currentDepartUsers.value.every((item: any) => !!item.checked);
|
||||||
|
currentDepartAllUsers.value = result;
|
||||||
|
};
|
||||||
|
// 解析参数
|
||||||
|
const parseParams = (params) => {
|
||||||
|
if (props?.params) {
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
...props.params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
const getQueryTreeList = (params?) => {
|
||||||
|
params = parseParams(params);
|
||||||
|
queryTreeList({ ...params }).then((res) => {
|
||||||
|
if (res) {
|
||||||
|
departTree.value = res;
|
||||||
|
currentDepartTree.value = res;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 根据部门id获取用户
|
||||||
|
const getTableList = (params) => {
|
||||||
|
params = parseParams(params);
|
||||||
|
return getTableListOrigin({ ...params });
|
||||||
|
};
|
||||||
|
const getUsersByDeptId = (id) => {
|
||||||
|
return new Promise<any[]>((resolve) => {
|
||||||
|
if (cacheDepartUser[id]) {
|
||||||
|
resolve(cacheDepartUser[id]);
|
||||||
|
} else {
|
||||||
|
getTableList({
|
||||||
|
departId: id,
|
||||||
|
}).then((res: any) => {
|
||||||
|
cacheDepartUser[id] = res.records ?? [];
|
||||||
|
if (res?.records?.length) {
|
||||||
|
resolve(res.records ?? []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 根据id获取根节点到当前节点路径
|
||||||
|
const getPathToNodeById = (id: string, tree = departTree.value, path = []): any[] => {
|
||||||
|
for (const node of tree) {
|
||||||
|
if ((node as any).id === id) {
|
||||||
|
return [...path];
|
||||||
|
}
|
||||||
|
if ((node as any).children) {
|
||||||
|
const foundPath = getPathToNodeById(id, (node as any).children, [...path, node]);
|
||||||
|
if (foundPath.length) {
|
||||||
|
return foundPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
// 根据id获取部门树父节点数据
|
||||||
|
const getDepartTreeParentById = (id: string, tree = departTree.value, parent = null): any => {
|
||||||
|
for (const node of tree) {
|
||||||
|
if ((node as any).id === id) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
if ((node as any).children) {
|
||||||
|
const found = getDepartTreeParentById(id, (node as any).children, node);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
// 通过名称搜索部门支持模糊
|
||||||
|
const getDepartByName = (name: string, tree = departTree.value): any[] => {
|
||||||
|
const result: any[] = [];
|
||||||
|
const search = (nodes: any[]) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.departName?.toLowerCase().includes(name.toLowerCase())) {
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
if (node.children?.length) {
|
||||||
|
search(node.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
search(tree);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
// 根据id获取部门树当前节点数据
|
||||||
|
const getDepartTreeNodeById = (id: string, tree = departTree.value): any => {
|
||||||
|
for (const node of tree) {
|
||||||
|
if ((node as any).id === id) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if ((node as any).children) {
|
||||||
|
const found = getDepartTreeNodeById(id, (node as any).children);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.JSelectUserByDepartmentModal {
|
||||||
|
.scroll-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.j-select-user-by-dept {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
display: flex;
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
height: 400px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.left-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
border-right: 1px solid #e8e8e8;
|
||||||
|
.search-box {
|
||||||
|
margin: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
|
:deep(.ant-breadcrumb) {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 16px;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
li {
|
||||||
|
.ant-breadcrumb-link {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
.ant-breadcrumb-link {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tree-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
.no-data {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.depart-tree {
|
||||||
|
.depart-tree-item {
|
||||||
|
padding: 0 16px;
|
||||||
|
line-height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f4f6fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.depart-tree-item-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.depart-users-tree {
|
||||||
|
.allChecked {
|
||||||
|
padding: 0 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-top: 12px;
|
||||||
|
:deep(.ant-checkbox-wrapper) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.depart-users-tree-item {
|
||||||
|
line-height: 50px;
|
||||||
|
padding: 0 16px;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f4f6fa;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
.depart-users-tree-item-circle {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #aaa;
|
||||||
|
overflow: hidden;
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.depart-users-tree-item-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search-depart {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
.search-depart-title {
|
||||||
|
padding-left: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.search-depart-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f4f6fa;
|
||||||
|
}
|
||||||
|
.search-depart-item-name {
|
||||||
|
margin-left: 8px;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-user {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
.search-user-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
.search-user-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f4f6fa;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
.search-user-item-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.search-user-item-circle {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #aaa;
|
||||||
|
}
|
||||||
|
.search-user-item-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.search-user-item-org {
|
||||||
|
color: #999;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-content {
|
||||||
|
width: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 16px;
|
||||||
|
.selected-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.selected-users {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.user-avatar {
|
||||||
|
text-align: center;
|
||||||
|
width: 70px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.avatar-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.mask {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mask {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.user-name {
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -45,7 +45,9 @@ export function useSelectBiz(getList, props, emit?) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
//设置列表默认选中
|
//设置列表默认选中
|
||||||
checkedKeys['value'] = selectValues['value'];
|
// update-begin--author:liaozhiyang---date:20250423---for:【QQYUN-12155】弹窗中勾选,再点取消,值被选中了
|
||||||
|
checkedKeys['value'] = [...selectValues['value']];
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【QQYUN-12155】弹窗中勾选,再点取消,值被选中了
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
@ -109,7 +111,9 @@ export function useSelectBiz(getList, props, emit?) {
|
|||||||
code: selectValues['value'].join(','),
|
code: selectValues['value'].join(','),
|
||||||
pageSize: selectValues['value'].length,
|
pageSize: selectValues['value'].length,
|
||||||
});
|
});
|
||||||
checkedKeys['value'] = selectValues['value'];
|
// update-begin--author:liaozhiyang---date:20250423---for:【QQYUN-12155】弹窗中勾选,再点取消,值被选中了
|
||||||
|
checkedKeys['value'] = [...selectValues['value']];
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【QQYUN-12155】弹窗中勾选,再点取消,值被选中了
|
||||||
selectRows['value'] = records;
|
selectRows['value'] = records;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +122,9 @@ export function useSelectBiz(getList, props, emit?) {
|
|||||||
*/
|
*/
|
||||||
async function visibleChange(visible) {
|
async function visibleChange(visible) {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
// update-begin--author:liaozhiyang---date:20250423---for:【QQYUN-12179】弹窗勾选了值,点击取消再次打开弹窗遗留了上次的勾选的值
|
||||||
|
checkedKeys['value'] = [...selectValues['value']];
|
||||||
|
// update-begin--author:liaozhiyang---date:20250423---for:【QQYUN-12179】弹窗勾选了值,点击取消再次打开弹窗遗留了上次的勾选的值
|
||||||
//设置列表默认选中
|
//设置列表默认选中
|
||||||
props.showSelected && initSelectRows();
|
props.showSelected && initSelectRows();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { Ref } from 'vue';
|
|||||||
import { inject, reactive, ref, computed, unref, watch, nextTick } from 'vue';
|
import { inject, reactive, ref, computed, unref, watch, nextTick } from 'vue';
|
||||||
import { TreeActionType } from '/@/components/Tree';
|
import { TreeActionType } from '/@/components/Tree';
|
||||||
import { listToTree } from '/@/utils/common/compUtils';
|
import { listToTree } from '/@/utils/common/compUtils';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||||
//接收下拉框选项
|
//接收下拉框选项
|
||||||
@ -22,7 +23,7 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
|||||||
const getCheckStrictly = computed(() => (realProps.multiple ? props.checkStrictly : true));
|
const getCheckStrictly = computed(() => (realProps.multiple ? props.checkStrictly : true));
|
||||||
// 是否是首次加载回显,只有首次加载,才会显示 loading
|
// 是否是首次加载回显,只有首次加载,才会显示 loading
|
||||||
let isFirstLoadEcho = true;
|
let isFirstLoadEcho = true;
|
||||||
|
let prevSelectValues = [];
|
||||||
/**
|
/**
|
||||||
* 监听selectValues变化
|
* 监听selectValues变化
|
||||||
*/
|
*/
|
||||||
@ -32,12 +33,17 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
|||||||
if(!values){
|
if(!values){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (openModal.value == false && values.length > 0) {
|
// update-begin--author:liaozhiyang---date:20250604---for:【issues/8232】代码设置JSelectDept组件值没翻译
|
||||||
|
if (values.length > 0) {
|
||||||
|
// 防止多次请求
|
||||||
|
if (isEqual(values, prevSelectValues)) return;
|
||||||
|
prevSelectValues = values;
|
||||||
loadingEcho.value = isFirstLoadEcho;
|
loadingEcho.value = isFirstLoadEcho;
|
||||||
isFirstLoadEcho = false;
|
isFirstLoadEcho = false;
|
||||||
onLoadData(null, values.join(',')).finally(() => {
|
onLoadData(null, values.join(',')).finally(() => {
|
||||||
loadingEcho.value = false;
|
loadingEcho.value = false;
|
||||||
});
|
});
|
||||||
|
// update-end--author:liaozhiyang---date:20250604---for:【issues/8232】代码设置JSelectDept组件值没翻译
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
|
|||||||
@ -139,6 +139,7 @@ export type ComponentType =
|
|||||||
| 'JTreeSelect'
|
| 'JTreeSelect'
|
||||||
| 'JEllipsis'
|
| 'JEllipsis'
|
||||||
| 'JSelectUserByDept'
|
| 'JSelectUserByDept'
|
||||||
|
| 'JSelectUserByDepartment'
|
||||||
| 'JUpload'
|
| 'JUpload'
|
||||||
| 'JSearchSelect'
|
| 'JSearchSelect'
|
||||||
| 'JAddInput'
|
| 'JAddInput'
|
||||||
|
|||||||
@ -222,7 +222,7 @@
|
|||||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef, getColumnsRef, getRowSelectionRef, getDataSourceRef, slots);
|
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef, getColumnsRef, getRowSelectionRef, getDataSourceRef, slots, getPaginationInfo);
|
||||||
|
|
||||||
const { customRow } = useCustomRow(getProps, {
|
const { customRow } = useCustomRow(getProps, {
|
||||||
setSelectedRowKeys,
|
setSelectedRowKeys,
|
||||||
|
|||||||
@ -371,9 +371,7 @@
|
|||||||
|
|
||||||
if (props.column.dataIndex) {
|
if (props.column.dataIndex) {
|
||||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||||
// update-begin--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
|
||||||
props.record.editValueRefs[props.column.dataIndex] = unref(currentValueRef);
|
|
||||||
// update-end--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
|
||||||
}
|
}
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
props.record.onCancelEdit = () => {
|
props.record.onCancelEdit = () => {
|
||||||
|
|||||||
@ -73,8 +73,6 @@ export type EditRecordRow<T = Recordable> = Partial<
|
|||||||
submitCbs: Cbs[];
|
submitCbs: Cbs[];
|
||||||
cancelCbs: Cbs[];
|
cancelCbs: Cbs[];
|
||||||
validCbs: Cbs[];
|
validCbs: Cbs[];
|
||||||
// update-begin--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
editValueRefs: Recordable<Ref>;
|
||||||
editValueRefs: Recordable;
|
|
||||||
// update-end--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
|
||||||
} & T
|
} & T
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -15,7 +15,8 @@ export function useTableScroll(
|
|||||||
columnsRef: ComputedRef<BasicColumn[]>,
|
columnsRef: ComputedRef<BasicColumn[]>,
|
||||||
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
|
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
|
||||||
getDataSourceRef: ComputedRef<Recordable[]>,
|
getDataSourceRef: ComputedRef<Recordable[]>,
|
||||||
slots: Slots
|
slots: Slots,
|
||||||
|
getPaginationInfo: ComputedRef<any>
|
||||||
) {
|
) {
|
||||||
const tableHeightRef: Ref<Nullable<number>> = ref(null);
|
const tableHeightRef: Ref<Nullable<number>> = ref(null);
|
||||||
|
|
||||||
@ -138,12 +139,33 @@ export function useTableScroll(
|
|||||||
// 10+6(外层边距padding:10 + 内层padding-bottom:6)
|
// 10+6(外层边距padding:10 + 内层padding-bottom:6)
|
||||||
height -= 16;
|
height -= 16;
|
||||||
// update-end--author:liaozhiyang---date:20240603---for:【TV360X-861】列表查询区域不可往上滚动
|
// update-end--author:liaozhiyang---date:20240603---for:【TV360X-861】列表查询区域不可往上滚动
|
||||||
|
|
||||||
height = (height < minHeight! ? (minHeight as number) : height) ?? height;
|
height = (height < minHeight! ? (minHeight as number) : height) ?? height;
|
||||||
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
||||||
setHeight(height);
|
setHeight(height);
|
||||||
|
|
||||||
bodyEl!.style.height = `${height}px`;
|
bodyEl!.style.height = `${height}px`;
|
||||||
|
// update-begin--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||||||
|
if (maxHeight === undefined) {
|
||||||
|
if (unref(getPaginationInfo) && unref(getDataSourceRef).length) {
|
||||||
|
const pageSize = unref(getPaginationInfo)?.pageSize;
|
||||||
|
const current = unref(getPaginationInfo)?.current;
|
||||||
|
const total = unref(getPaginationInfo)?.total;
|
||||||
|
const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement;
|
||||||
|
const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? [];
|
||||||
|
const lastrEl = tr[tr.length - 1] as HTMLElement;
|
||||||
|
const trHeight = lastrEl.offsetHeight;
|
||||||
|
const dataHeight = trHeight * pageSize;
|
||||||
|
if (tableBody && lastrEl) {
|
||||||
|
if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) {
|
||||||
|
tableBody.style.height = `${height}px`;
|
||||||
|
} else {
|
||||||
|
tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update-end--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||||||
}
|
}
|
||||||
useWindowSizeFn(calcTableHeight, 280);
|
useWindowSizeFn(calcTableHeight, 280);
|
||||||
onMountedOrActivated(() => {
|
onMountedOrActivated(() => {
|
||||||
|
|||||||
@ -7,6 +7,8 @@ interface PaginationRenderProps {
|
|||||||
originalElement: any;
|
originalElement: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Position = 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
|
||||||
|
|
||||||
export declare class PaginationConfig extends Pagination {
|
export declare class PaginationConfig extends Pagination {
|
||||||
position?: 'top' | 'bottom' | 'both';
|
position?: 'top' | 'bottom' | 'both';
|
||||||
}
|
}
|
||||||
@ -96,4 +98,11 @@ export interface PaginationProps {
|
|||||||
* @type Function
|
* @type Function
|
||||||
*/
|
*/
|
||||||
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
|
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
|
||||||
|
// update-begin--author:liaozhiyang---date:20250423---for:【pull/8013】修复 BasicTable position 属性类型配置
|
||||||
|
/**
|
||||||
|
* specify the position of Pagination
|
||||||
|
* @type Position[]
|
||||||
|
*/
|
||||||
|
position?: Position[];
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【pull/8013】修复 BasicTable position 属性类型配置
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,9 @@ export function useColumns(props: JVxeTableProps, data: JVxeDataProps, methods:
|
|||||||
// update-begin--author:liaozhiyang---date:20250403---for:【issues/7812】linkageConfig改变了,vxetable没更新
|
// update-begin--author:liaozhiyang---date:20250403---for:【issues/7812】linkageConfig改变了,vxetable没更新
|
||||||
// linkageConfig变化时也需要执行
|
// linkageConfig变化时也需要执行
|
||||||
const linkageConfig = toRaw(props.linkageConfig);
|
const linkageConfig = toRaw(props.linkageConfig);
|
||||||
console.log(linkageConfig);
|
if (linkageConfig) {
|
||||||
|
// console.log(linkageConfig);
|
||||||
|
}
|
||||||
// update-end--author:liaozhiyang---date:20250403---for:【issues/7812】linkageConfig改变了,vxetable没更新
|
// update-end--author:liaozhiyang---date:20250403---for:【issues/7812】linkageConfig改变了,vxetable没更新
|
||||||
let columns: JVxeColumn[] = [];
|
let columns: JVxeColumn[] = [];
|
||||||
if (isArray(props.columns)) {
|
if (isArray(props.columns)) {
|
||||||
|
|||||||
@ -18,10 +18,10 @@ export function useData(props: JVxeTableProps): JVxeDataProps {
|
|||||||
// rowId: props.rowKey,
|
// rowId: props.rowKey,
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: props.rowKey,
|
keyField: props.rowKey,
|
||||||
|
// 高亮hover的行
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-327】vxetable警告
|
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-327】vxetable警告
|
||||||
// 高亮hover的行
|
|
||||||
highlightHoverRow: true,
|
|
||||||
|
|
||||||
// --- 【issues/209】自带的tooltip会错位,所以替换成原生的title ---
|
// --- 【issues/209】自带的tooltip会错位,所以替换成原生的title ---
|
||||||
// 溢出隐藏并显示tooltip
|
// 溢出隐藏并显示tooltip
|
||||||
@ -41,8 +41,9 @@ export function useData(props: JVxeTableProps): JVxeDataProps {
|
|||||||
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||||
},
|
},
|
||||||
expandConfig: {
|
expandConfig: {
|
||||||
iconClose: 'ant-table-row-expand-icon ant-table-row-expand-icon-collapsed',
|
iconClose: 'vxe-icon-arrow-right',
|
||||||
iconOpen: 'ant-table-row-expand-icon ant-table-row-expand-icon-expanded',
|
iconOpen: 'vxe-icon-arrow-down',
|
||||||
|
...props.expandConfig,
|
||||||
},
|
},
|
||||||
// 虚拟滚动配置,y轴大于xx条数据时启用虚拟滚动
|
// 虚拟滚动配置,y轴大于xx条数据时启用虚拟滚动
|
||||||
scrollY: {
|
scrollY: {
|
||||||
|
|||||||
@ -23,7 +23,12 @@ export function useDragSort(props: JVxeTableProps, methods: JVxeTableMethods) {
|
|||||||
function createSortable() {
|
function createSortable() {
|
||||||
let xTable = methods.getXTable();
|
let xTable = methods.getXTable();
|
||||||
// let dom = xTable.$el.querySelector('.vxe-table--fixed-wrapper .vxe-table--body tbody')
|
// let dom = xTable.$el.querySelector('.vxe-table--fixed-wrapper .vxe-table--body tbody')
|
||||||
let dom = xTable.$el.querySelector('.body--wrapper>.vxe-table--body tbody');
|
// let dom = xTable.$el.querySelector('.body--wrapper>.vxe-table--body tbody');
|
||||||
|
let dom = xTable.$el.querySelector('.vxe-table--body-inner-wrapper > .vxe-table--body tbody');
|
||||||
|
if (!dom) {
|
||||||
|
console.warn('[JVxeTable] 拖拽排序初始化失败,可能是vxe-table升级导致的版本不兼容。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
let startChildren = [];
|
let startChildren = [];
|
||||||
sortable2 = Sortable.create(dom as HTMLElement, {
|
sortable2 = Sortable.create(dom as HTMLElement, {
|
||||||
handle: '.drag-btn',
|
handle: '.drag-btn',
|
||||||
|
|||||||
@ -424,12 +424,13 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
|
|||||||
let xTable = getXTable();
|
let xTable = getXTable();
|
||||||
let { setActive, index } = options;
|
let { setActive, index } = options;
|
||||||
index = index === -1 ? index : xTable.internalData.tableFullData[index];
|
index = index === -1 ? index : xTable.internalData.tableFullData[index];
|
||||||
|
index = index == null ? -1 : index;
|
||||||
// 插入行
|
// 插入行
|
||||||
let result = await xTable.insertAt(rows, index);
|
let result = await xTable.insertAt(rows, index);
|
||||||
if (setActive) {
|
if (setActive) {
|
||||||
// -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
|
// -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
|
||||||
// 激活最后一行的编辑模式
|
// 激活最后一行的编辑模式
|
||||||
xTable.setEditRow(result.rows[result.rows.length - 1]);
|
xTable.setEditRow(result.rows[result.rows.length - 1], true);
|
||||||
// -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
|
// -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告
|
||||||
}
|
}
|
||||||
await recalcSortNumber();
|
await recalcSortNumber();
|
||||||
@ -763,7 +764,7 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
|
|||||||
// 4.1.0
|
// 4.1.0
|
||||||
//await xTable.updateCache();
|
//await xTable.updateCache();
|
||||||
// 4.1.1
|
// 4.1.1
|
||||||
await xTable.cacheRowMap()
|
await xTable.cacheRowMap(true)
|
||||||
// update-end--author:liaozhiyang---date:20231011---for:【QQYUN-5133】JVxeTable 行编辑升级
|
// update-end--author:liaozhiyang---date:20231011---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||||
return await xTable.updateData();
|
return await xTable.updateData();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,9 @@ export function usePagination(props: JVxeTableProps, methods: JVxeTableMethods)
|
|||||||
[
|
[
|
||||||
h(Pagination, {
|
h(Pagination, {
|
||||||
...bindProps.value,
|
...bindProps.value,
|
||||||
disabled: props.disabled,
|
// update-begin--author:liaozhiyang---date:20250423---for:【issues/8137】vxetable表格禁用后分页隐藏了
|
||||||
|
disabled: false,
|
||||||
|
// update-end--author:liaozhiyang---date:20250423---for:【issues/8137】vxetable表格禁用后分页隐藏了
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
onShowSizeChange: handleShowSizeChange,
|
onShowSizeChange: handleShowSizeChange,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
// 引入 vxe-table
|
// 引入 vxe-table
|
||||||
import 'xe-utils';
|
import 'xe-utils';
|
||||||
|
import VxeUIAll from 'vxe-pc-ui';
|
||||||
import VXETable /*Grid*/ from 'vxe-table';
|
import VXETable /*Grid*/ from 'vxe-table';
|
||||||
import VXETablePluginAntd from 'vxe-table-plugin-antd';
|
import VXETablePluginAntd from 'vxe-table-plugin-antd';
|
||||||
|
import 'vxe-pc-ui/lib/style.css';
|
||||||
import 'vxe-table/lib/style.css';
|
import 'vxe-table/lib/style.css';
|
||||||
|
|
||||||
import JVxeTable from './JVxeTable';
|
import JVxeTable from './JVxeTable';
|
||||||
@ -27,6 +29,7 @@ export function registerJVxeTable(app: App) {
|
|||||||
// 注册自定义组件
|
// 注册自定义组件
|
||||||
registerAllComponent();
|
registerAllComponent();
|
||||||
// 执行注册方法
|
// 执行注册方法
|
||||||
|
app.use(VxeUIAll);
|
||||||
app.use(VXETable, VXETableSettings);
|
app.use(VXETable, VXETableSettings);
|
||||||
app.component('JVxeTable', JVxeTable);
|
app.component('JVxeTable', JVxeTable);
|
||||||
}
|
}
|
||||||
@ -39,6 +42,12 @@ export function registerJVxeTable(app: App) {
|
|||||||
function preventClosingPopUp(this: any, params) {
|
function preventClosingPopUp(this: any, params) {
|
||||||
// 获取组件增强
|
// 获取组件增强
|
||||||
let col = params.column.params;
|
let col = params.column.params;
|
||||||
|
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8178】使用原生vxe-table组件编辑模式下失去焦点报错
|
||||||
|
if (col === undefined) {
|
||||||
|
// 说明使用的是纯原生的vxe-table
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// update-end--author:liaozhiyang---date:20250429---for:【issues/8178】使用原生vxe-table组件编辑模式下失去焦点报错
|
||||||
let { $event } = params;
|
let { $event } = params;
|
||||||
const interceptor = getEnhanced(col.type).interceptor;
|
const interceptor = getEnhanced(col.type).interceptor;
|
||||||
// 执行增强
|
// 执行增强
|
||||||
|
|||||||
@ -258,6 +258,20 @@ export const schemas: FormSchema[] = [
|
|||||||
label: '选中用户',
|
label: '选中用户',
|
||||||
colProps: { span: 12 },
|
colProps: { span: 12 },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'user4',
|
||||||
|
component: 'JSelectUserByDepartment',
|
||||||
|
label: '部门选择用户',
|
||||||
|
helpMessage: ['component模式'],
|
||||||
|
defaultValue: '',
|
||||||
|
componentProps: {
|
||||||
|
labelKey: 'realname',
|
||||||
|
rowKey: 'username',
|
||||||
|
},
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'role2',
|
field: 'role2',
|
||||||
component: 'JSelectRole',
|
component: 'JSelectRole',
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
const { createMessage } = useMessage();
|
const { createMessage } = useMessage();
|
||||||
const checkedKeys = ref<Array<string | number>>([]);
|
const checkedKeys = ref<Array<string | number>>([]);
|
||||||
|
|
||||||
const logColumns = ref<any>(columns);
|
const logColumns = ref<any>(exceptionColumns);
|
||||||
const searchSchema = ref<any>(searchFormSchema);
|
const searchSchema = ref<any>(searchFormSchema);
|
||||||
const searchInfo = { logType: '4' };
|
const searchInfo = { logType: '4' };
|
||||||
// 列表页面公共参数、方法
|
// 列表页面公共参数、方法
|
||||||
|
|||||||
118
jeecgboot-vue3/src/views/openapi/OpenApi.api.ts
Normal file
118
jeecgboot-vue3/src/views/openapi/OpenApi.api.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import {defHttp} from '/@/utils/http/axios';
|
||||||
|
import { useMessage } from "/@/hooks/web/useMessage";
|
||||||
|
|
||||||
|
const { createConfirm } = useMessage();
|
||||||
|
|
||||||
|
enum Api {
|
||||||
|
list = '/openapi/list',
|
||||||
|
save='/openapi/add',
|
||||||
|
edit='/openapi/edit',
|
||||||
|
deleteOne = '/openapi/delete',
|
||||||
|
deleteBatch = '/openapi/deleteBatch',
|
||||||
|
genPath = '/openapi/genPath',
|
||||||
|
importExcel = '/openapi/importExcel',
|
||||||
|
exportXls = '/openapi/exportXls',
|
||||||
|
openApiHeaderList = '/openapi/list',
|
||||||
|
openApiParamList = '/openapi/list',
|
||||||
|
openApiJson = '/openapi/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子表单查询接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const genPath = Api.genPath
|
||||||
|
/**
|
||||||
|
* swagger文档json
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const openApiJson = Api.openApiJson
|
||||||
|
/**
|
||||||
|
* 导出api
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getExportUrl = Api.exportXls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入api
|
||||||
|
*/
|
||||||
|
export const getImportUrl = Api.importExcel;
|
||||||
|
/**
|
||||||
|
* 子表单查询接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const queryOpenApiHeader = Api.openApiHeaderList
|
||||||
|
/**
|
||||||
|
* 子表单查询接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const queryOpenApiParam = Api.openApiParamList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const list = (params) =>
|
||||||
|
defHttp.get({url: Api.list, params});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单个
|
||||||
|
*/
|
||||||
|
export const deleteOne = (params,handleSuccess) => {
|
||||||
|
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
|
||||||
|
handleSuccess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const batchDelete = (params, handleSuccess) => {
|
||||||
|
createConfirm({
|
||||||
|
iconType: 'warning',
|
||||||
|
title: '确认删除',
|
||||||
|
content: '是否删除选中数据',
|
||||||
|
okText: '确认',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: () => {
|
||||||
|
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
|
||||||
|
handleSuccess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 保存或者更新
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const saveOrUpdate = (params, isUpdate) => {
|
||||||
|
if (isUpdate) {
|
||||||
|
return defHttp.put({url: Api.edit, params});
|
||||||
|
} else {
|
||||||
|
return defHttp.post({url: Api.save, params});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取接口地址
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getGenPath = (params) =>
|
||||||
|
defHttp.get({url: Api.genPath, params},{isTransformResponse:false});
|
||||||
|
/**
|
||||||
|
* 子表列表接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const openApiHeaderList = (params) =>
|
||||||
|
defHttp.get({url: Api.openApiHeaderList, params},{isTransformResponse:false});
|
||||||
|
/**
|
||||||
|
* 子表列表接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const openApiParamList = (params) =>
|
||||||
|
defHttp.get({url: Api.openApiParamList, params},{isTransformResponse:false});
|
||||||
|
/**
|
||||||
|
* swagger文档json
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getOpenApiJson = (params) =>
|
||||||
|
defHttp.get({url: Api.openApiJson, params},{isTransformResponse:false});
|
||||||
345
jeecgboot-vue3/src/views/openapi/OpenApi.data.ts
Normal file
345
jeecgboot-vue3/src/views/openapi/OpenApi.data.ts
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
import {BasicColumn} from '/@/components/Table';
|
||||||
|
import {FormSchema} from '/@/components/Table';
|
||||||
|
import { rules} from '/@/utils/helper/validator';
|
||||||
|
import { render } from '/@/utils/common/renderUtils';
|
||||||
|
import {JVxeTypes,JVxeColumn} from '/@/components/jeecg/JVxeTable/types'
|
||||||
|
import { getWeekMonthQuarterYear } from '/@/utils';
|
||||||
|
//列表数据
|
||||||
|
export const columns: BasicColumn[] = [
|
||||||
|
{
|
||||||
|
title: '接口名称',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '请求方法',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'requestMethod'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '接口地址',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'requestUrl'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IP 黑名单',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'blackList'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: '状态',
|
||||||
|
// align:"center",
|
||||||
|
// dataIndex: 'status'
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '创建人',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'createBy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'createTime'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
//查询数据
|
||||||
|
export const searchFormSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
label: "接口名称",
|
||||||
|
field: "name",
|
||||||
|
component: 'JInput',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "创建人",
|
||||||
|
field: "createBy",
|
||||||
|
component: 'JInput',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
//表单数据
|
||||||
|
export const formSchema: FormSchema[] = [
|
||||||
|
{
|
||||||
|
label: '接口名称',
|
||||||
|
field: 'name',
|
||||||
|
component: 'Input',
|
||||||
|
dynamicRules: ({model,schema}) => {
|
||||||
|
return [
|
||||||
|
{ required: true, message: '请输入接口名称!'},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '原始地址',
|
||||||
|
field: 'originUrl',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '请求方法',
|
||||||
|
field: 'requestMethod',
|
||||||
|
component: 'JSearchSelect',
|
||||||
|
componentProps:{
|
||||||
|
dictOptions: [
|
||||||
|
{
|
||||||
|
text: 'POST',
|
||||||
|
value: 'POST',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'GET',
|
||||||
|
value: 'GET',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'HEAD',
|
||||||
|
value: 'HEAD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'PUT',
|
||||||
|
value: 'PUT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'PATCH',
|
||||||
|
value: 'PATCH',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'DELETE',
|
||||||
|
value: 'DELETE',
|
||||||
|
},{
|
||||||
|
text: 'OPTIONS',
|
||||||
|
value: 'OPTIONS',
|
||||||
|
},{
|
||||||
|
text: 'TRACE',
|
||||||
|
value: 'TRACE',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dynamicRules: ({model,schema}) => {
|
||||||
|
return [
|
||||||
|
{ required: true, message: '请输入请求方法!'},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '接口地址',
|
||||||
|
field: 'requestUrl',
|
||||||
|
component: 'Input',
|
||||||
|
dynamicDisabled:true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'IP 黑名单',
|
||||||
|
field: 'blackList',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '请求体内容',
|
||||||
|
component:"Input",
|
||||||
|
field: 'body'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除标识',
|
||||||
|
field: 'delFlag',
|
||||||
|
component: 'Input',
|
||||||
|
defaultValue:0,
|
||||||
|
show:false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
field: 'status',
|
||||||
|
component: 'Input',
|
||||||
|
defaultValue:"1",
|
||||||
|
show:false
|
||||||
|
},
|
||||||
|
// TODO 主键隐藏字段,目前写死为ID
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
field: 'id',
|
||||||
|
component: 'Input',
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
];
|
||||||
|
//子表单数据
|
||||||
|
//子表列表数据
|
||||||
|
export const openApiHeaderColumns: BasicColumn[] = [
|
||||||
|
// {
|
||||||
|
// title: 'apiId',
|
||||||
|
// align:"center",
|
||||||
|
// dataIndex: 'apiId'
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '请求头Key',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'headerKey'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否必填',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'required_dictText'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '默认值',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'defaultValue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'note'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
//子表列表数据
|
||||||
|
export const openApiParamColumns: BasicColumn[] = [
|
||||||
|
// {
|
||||||
|
// title: 'apiId',
|
||||||
|
// align:"center",
|
||||||
|
// dataIndex: 'apiId'
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '参数Key',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'paramKey'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否必填',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'required_dictText'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '默认值',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'defaultValue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
align:"center",
|
||||||
|
dataIndex: 'note'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
//子表表格配置
|
||||||
|
export const openApiHeaderJVxeColumns: JVxeColumn[] = [
|
||||||
|
// {
|
||||||
|
// title: 'apiId',
|
||||||
|
// key: 'apiId',
|
||||||
|
// type: JVxeTypes.input,
|
||||||
|
// width:"200px",
|
||||||
|
// placeholder: '请输入${title}',
|
||||||
|
// defaultValue:'',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '请求头Key',
|
||||||
|
key: 'headerKey',
|
||||||
|
type: JVxeTypes.input,
|
||||||
|
width:"200px",
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否必填',
|
||||||
|
key: 'required',
|
||||||
|
type: JVxeTypes.checkbox,
|
||||||
|
options:[],
|
||||||
|
// dictCode:"yn",
|
||||||
|
width:"100px",
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
customValue: ['1','0']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '默认值',
|
||||||
|
key: 'defaultValue',
|
||||||
|
type: JVxeTypes.input,
|
||||||
|
width:"200px",
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
key: 'note',
|
||||||
|
type: JVxeTypes.input,
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
export const openApiParamJVxeColumns: JVxeColumn[] = [
|
||||||
|
// {
|
||||||
|
// title: 'apiId',
|
||||||
|
// key: 'apiId',
|
||||||
|
// type: JVxeTypes.input,
|
||||||
|
// width:"200px",
|
||||||
|
// placeholder: '请输入${title}',
|
||||||
|
// defaultValue:'',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '参数Key',
|
||||||
|
key: 'paramKey',
|
||||||
|
type: JVxeTypes.input,
|
||||||
|
width:"200px",
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否必填',
|
||||||
|
key: 'required',
|
||||||
|
type: JVxeTypes.checkbox,
|
||||||
|
options:[],
|
||||||
|
// dictCode:"yn",
|
||||||
|
width:"100px",
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
customValue: ['1','0']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '默认值',
|
||||||
|
key: 'defaultValue',
|
||||||
|
type: JVxeTypes.input,
|
||||||
|
width:"200px",
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
key: 'note',
|
||||||
|
type: JVxeTypes.input,
|
||||||
|
placeholder: '请输入${title}',
|
||||||
|
defaultValue:'',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 高级查询数据
|
||||||
|
export const superQuerySchema = {
|
||||||
|
name: {title: '接口名称',order: 0,view: 'text', type: 'string',},
|
||||||
|
requestMethod: {title: '请求方法',order: 1,view: 'list', type: 'string',dictCode: '',},
|
||||||
|
requestUrl: {title: '接口地址',order: 2,view: 'text', type: 'string',},
|
||||||
|
blackList: {title: 'IP 黑名单',order: 3,view: 'text', type: 'string',},
|
||||||
|
status: {title: '状态',order: 5,view: 'number', type: 'number',},
|
||||||
|
createBy: {title: '创建人',order: 6,view: 'text', type: 'string',},
|
||||||
|
createTime: {title: '创建时间',order: 7,view: 'datetime', type: 'string',},
|
||||||
|
//子表高级查询
|
||||||
|
openApiHeader: {
|
||||||
|
title: '请求头表',
|
||||||
|
view: 'table',
|
||||||
|
fields: {
|
||||||
|
// apiId: {title: 'apiId',order: 0,view: 'text', type: 'string',},
|
||||||
|
headerKey: {title: '请求头Key',order: 1,view: 'text', type: 'string',},
|
||||||
|
required: {title: '是否必填',order: 2,view: 'number', type: 'number',dictCode: 'yn',},
|
||||||
|
defaultValue: {title: '默认值',order: 3,view: 'text', type: 'string',},
|
||||||
|
note: {title: '备注',order: 4,view: 'text', type: 'string',},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openApiParam: {
|
||||||
|
title: '请求参数部分',
|
||||||
|
view: 'table',
|
||||||
|
fields: {
|
||||||
|
// apiId: {title: 'apiId',order: 0,view: 'text', type: 'string',},
|
||||||
|
paramKey: {title: '参数Key',order: 1,view: 'text', type: 'string',},
|
||||||
|
required: {title: '是否必填',order: 2,view: 'number', type: 'number',dictCode: 'yn',},
|
||||||
|
defaultValue: {title: '默认值',order: 3,view: 'text', type: 'string',},
|
||||||
|
note: {title: '备注',order: 4,view: 'text', type: 'string',},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程表单调用这个方法获取formSchema
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
export function getBpmFormSchema(_formData): FormSchema[]{
|
||||||
|
// 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
|
||||||
|
return formSchema;
|
||||||
|
}
|
||||||
122
jeecgboot-vue3/src/views/openapi/OpenApiAuth.api.ts
Normal file
122
jeecgboot-vue3/src/views/openapi/OpenApiAuth.api.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { defHttp } from '/@/utils/http/axios';
|
||||||
|
import { useMessage } from "/@/hooks/web/useMessage";
|
||||||
|
|
||||||
|
const { createConfirm } = useMessage();
|
||||||
|
|
||||||
|
enum Api {
|
||||||
|
list = '/openapi/auth/list',
|
||||||
|
save='/openapi/auth/add',
|
||||||
|
edit='/openapi/auth/edit',
|
||||||
|
apiList= '/openapi/list',
|
||||||
|
genAKSK = '/openapi/auth/genAKSK',
|
||||||
|
permissionList='/openapi/permission/getOpenApi',
|
||||||
|
permissionAdd='/openapi/permission/add',
|
||||||
|
deleteOne = '/openapi/auth/delete',
|
||||||
|
deleteBatch = '/openapi/auth/deleteBatch',
|
||||||
|
importExcel = '/openapi/auth/importExcel',
|
||||||
|
exportXls = '/openapi/auth/exportXls',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取API
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const apiList = Api.apiList;
|
||||||
|
/**
|
||||||
|
* 权限添加
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const permissionAdd = Api.permissionAdd;
|
||||||
|
/**
|
||||||
|
* 生成AKSK
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const genAKSK = Api.genAKSK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出api
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getExportUrl = Api.exportXls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入api
|
||||||
|
*/
|
||||||
|
export const getImportUrl = Api.importExcel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单个
|
||||||
|
* @param params
|
||||||
|
* @param handleSuccess
|
||||||
|
*/
|
||||||
|
export const deleteOne = (params,handleSuccess) => {
|
||||||
|
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
|
||||||
|
handleSuccess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
* @param params
|
||||||
|
* @param handleSuccess
|
||||||
|
*/
|
||||||
|
export const batchDelete = (params, handleSuccess) => {
|
||||||
|
createConfirm({
|
||||||
|
iconType: 'warning',
|
||||||
|
title: '确认删除',
|
||||||
|
content: '是否删除选中数据',
|
||||||
|
okText: '确认',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: () => {
|
||||||
|
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
|
||||||
|
handleSuccess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存或者更新
|
||||||
|
* @param params
|
||||||
|
* @param isUpdate
|
||||||
|
*/
|
||||||
|
export const saveOrUpdate = (params, isUpdate) => {
|
||||||
|
if (isUpdate) {
|
||||||
|
return defHttp.put({ url: Api.edit, params }, { isTransformResponse: false });
|
||||||
|
}
|
||||||
|
return defHttp.post({ url: Api.save, params }, { isTransformResponse: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部权限列表接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getApiList = (params) => defHttp.get({ url: Api.apiList, params }, { isTransformResponse: false });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已授权项目的接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getPermissionList = (params) => defHttp.get({ url: Api.permissionList, params });
|
||||||
|
/**
|
||||||
|
* 授权保存方法
|
||||||
|
* @param params
|
||||||
|
* @param isUpdate
|
||||||
|
*/
|
||||||
|
export const permissionAddFunction = (params) => {
|
||||||
|
return defHttp.post({ url: Api.permissionAdd, params }, { isTransformResponse: false });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 授权保存方法
|
||||||
|
* @param params
|
||||||
|
* @param isUpdate
|
||||||
|
*/
|
||||||
|
export const getGenAKSK = (params) => {
|
||||||
|
return defHttp.get({ url: Api.genAKSK, params });
|
||||||
|
}
|
||||||
48
jeecgboot-vue3/src/views/openapi/OpenApiAuth.data.ts
Normal file
48
jeecgboot-vue3/src/views/openapi/OpenApiAuth.data.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import {BasicColumn} from '/@/components/Table';
|
||||||
|
import {FormSchema} from '/@/components/Table';
|
||||||
|
import { rules} from '/@/utils/helper/validator';
|
||||||
|
import { render } from '/@/utils/common/renderUtils';
|
||||||
|
import { getWeekMonthQuarterYear } from '/@/utils';
|
||||||
|
//列表数据
|
||||||
|
export const columns: BasicColumn[] = [
|
||||||
|
{
|
||||||
|
title: '授权名称',
|
||||||
|
align: "center",
|
||||||
|
dataIndex: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AK',
|
||||||
|
align: "center",
|
||||||
|
dataIndex: 'ak'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'SK',
|
||||||
|
align: "center",
|
||||||
|
dataIndex: 'sk'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建人',
|
||||||
|
align: "center",
|
||||||
|
dataIndex: 'createBy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
align: "center",
|
||||||
|
dataIndex: 'createTime'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: '关联系统用户名',
|
||||||
|
// align: "center",
|
||||||
|
// dataIndex: 'createBy',
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 高级查询数据
|
||||||
|
export const superQuerySchema = {
|
||||||
|
name: {title: '授权名称',order: 0,view: 'text', type: 'string',},
|
||||||
|
ak: {title: 'AK',order: 1,view: 'text', type: 'string',},
|
||||||
|
sk: {title: 'SK',order: 2,view: 'text', type: 'string',},
|
||||||
|
createBy: {title: '关联系统用户名',order: 3,view: 'text', type: 'string',},
|
||||||
|
createTime: {title: '创建时间',order: 4,view: 'datetime', type: 'string',},
|
||||||
|
// systemUserId: {title: '关联系统用户名',order: 5,view: 'text', type: 'string',},
|
||||||
|
};
|
||||||
303
jeecgboot-vue3/src/views/openapi/OpenApiAuthList.vue
Normal file
303
jeecgboot-vue3/src/views/openapi/OpenApiAuthList.vue
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-2">
|
||||||
|
<!--查询区域-->
|
||||||
|
<div class="jeecg-basic-table-form-container">
|
||||||
|
<a-form ref="formRef" @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :lg="6">
|
||||||
|
<a-form-item name="name">
|
||||||
|
<template #label><span title="授权名称">授权名称</span></template>
|
||||||
|
<a-input placeholder="请输入授权名称" v-model:value="queryParam.name" allow-clear ></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6">
|
||||||
|
<a-form-item name="createBy">
|
||||||
|
<template #label><span title="关联系统用户名">关联系统用户名</span></template>
|
||||||
|
<JSearchSelect dict="sys_user,username,username" v-model:value="queryParam.createBy" placeholder="请输入关联系统用户名" allow-clear ></JSearchSelect>
|
||||||
|
<!-- <a-input placeholder="请输入关联系统用户名" v-model:value="queryParam.systemUserId" allow-clear ></a-input>-->
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xl="6" :lg="7" :md="8" :sm="24">
|
||||||
|
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
|
||||||
|
<a-col :lg="6">
|
||||||
|
<a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
|
||||||
|
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button>
|
||||||
|
<a @click="toggleSearchStatus = !toggleSearchStatus" style="margin-left: 8px">
|
||||||
|
{{ toggleSearchStatus ? '收起' : '展开' }}
|
||||||
|
<Icon :icon="toggleSearchStatus ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
|
||||||
|
</a>
|
||||||
|
</a-col>
|
||||||
|
</span>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
<!--引用表格-->
|
||||||
|
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||||
|
<!--插槽:table标题-->
|
||||||
|
<template #tableTitle>
|
||||||
|
<a-button type="primary" v-auth="'openapi:open_api_auth:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||||
|
<a-button type="primary" v-auth="'openapi:open_api_auth:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||||
|
<j-upload-button type="primary" v-auth="'openapi:open_api_auth:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||||
|
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-menu-item key="1" @click="batchHandleDelete">
|
||||||
|
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||||
|
删除
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
<a-button v-auth="'openapi:open_api_auth:deleteBatch'">批量操作
|
||||||
|
<Icon icon="mdi:chevron-down"></Icon>
|
||||||
|
</a-button>
|
||||||
|
</a-dropdown>
|
||||||
|
<!-- 高级查询 -->
|
||||||
|
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
|
||||||
|
</template>
|
||||||
|
<!--操作栏-->
|
||||||
|
<template #action="{ record }">
|
||||||
|
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:bodyCell="{ column, record, index, text }">
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 表单区域 -->
|
||||||
|
<OpenApiAuthModal ref="registerModal" @success="handleSuccess"></OpenApiAuthModal>
|
||||||
|
<AuthModal ref="authModal" @success="handleSuccess"></AuthModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" name="openapi-openApiAuth" setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { BasicTable, TableAction } from '/@/components/Table';
|
||||||
|
import { useListPage } from '/@/hooks/system/useListPage';
|
||||||
|
import { columns, superQuerySchema } from './OpenApiAuth.data';
|
||||||
|
import {
|
||||||
|
list,
|
||||||
|
deleteOne,
|
||||||
|
batchDelete,
|
||||||
|
getImportUrl,
|
||||||
|
getExportUrl,
|
||||||
|
getGenAKSK, saveOrUpdate
|
||||||
|
} from "./OpenApiAuth.api";
|
||||||
|
import OpenApiAuthModal from './components/OpenApiAuthModal.vue'
|
||||||
|
import AuthModal from './components/AuthModal.vue'
|
||||||
|
import { useUserStore } from '/@/store/modules/user';
|
||||||
|
import JSearchSelect from "../../components/Form/src/jeecg/components/JSearchSelect.vue";
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const queryParam = reactive<any>({});
|
||||||
|
const toggleSearchStatus = ref<boolean>(false);
|
||||||
|
const registerModal = ref();
|
||||||
|
const authModal = ref();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
//注册table数据
|
||||||
|
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||||
|
tableProps: {
|
||||||
|
title: '授权管理',
|
||||||
|
api: list,
|
||||||
|
columns,
|
||||||
|
canResize:false,
|
||||||
|
useSearchForm: false,
|
||||||
|
actionColumn: {
|
||||||
|
width: 200,
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
beforeFetch: async (params) => {
|
||||||
|
return Object.assign(params, queryParam);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exportConfig: {
|
||||||
|
name: "授权管理",
|
||||||
|
url: getExportUrl,
|
||||||
|
params: queryParam,
|
||||||
|
},
|
||||||
|
importConfig: {
|
||||||
|
url: getImportUrl,
|
||||||
|
success: handleSuccess
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [registerTable, { reload, updateTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||||
|
const labelCol = reactive({
|
||||||
|
xs:24,
|
||||||
|
sm:10,
|
||||||
|
xl:6,
|
||||||
|
xxl:10
|
||||||
|
});
|
||||||
|
const wrapperCol = reactive({
|
||||||
|
xs: 24,
|
||||||
|
sm: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 高级查询配置
|
||||||
|
const superQueryConfig = reactive(superQuerySchema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高级查询事件
|
||||||
|
*/
|
||||||
|
function handleSuperQuery(params) {
|
||||||
|
Object.keys(params).map((k) => {
|
||||||
|
queryParam[k] = params[k];
|
||||||
|
});
|
||||||
|
searchQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增事件
|
||||||
|
*/
|
||||||
|
function handleAdd() {
|
||||||
|
registerModal.value.disableSubmit = false;
|
||||||
|
registerModal.value.add();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑事件
|
||||||
|
*/
|
||||||
|
function handleAuth(record: Recordable) {
|
||||||
|
authModal.value.disableSubmit = false;
|
||||||
|
authModal.value.edit(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑事件
|
||||||
|
*/
|
||||||
|
function handleEdit(record: Recordable) {
|
||||||
|
registerModal.value.disableSubmit = false;
|
||||||
|
registerModal.value.authDrawerOpen = true;
|
||||||
|
registerModal.value.edit(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置事件
|
||||||
|
* @param record
|
||||||
|
*/
|
||||||
|
async function handleReset(record: Recordable) {
|
||||||
|
const AKSKObj = await getGenAKSK({});
|
||||||
|
record.ak = AKSKObj[0];
|
||||||
|
record.sk = AKSKObj[1];
|
||||||
|
saveOrUpdate(record,true);
|
||||||
|
// handleSuccess;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
function handleDetail(record: Recordable) {
|
||||||
|
registerModal.value.disableSubmit = true;
|
||||||
|
registerModal.value.edit(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除事件
|
||||||
|
*/
|
||||||
|
async function handleDelete(record) {
|
||||||
|
await deleteOne({ id: record.id }, handleSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除事件
|
||||||
|
*/
|
||||||
|
async function batchHandleDelete() {
|
||||||
|
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功回调
|
||||||
|
*/
|
||||||
|
function handleSuccess() {
|
||||||
|
(selectedRowKeys.value = []) && reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作栏
|
||||||
|
*/
|
||||||
|
function getTableAction(record) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '授权',
|
||||||
|
onClick: handleAuth.bind(null, record),
|
||||||
|
auth: 'openapi:open_api_auth:edit'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '重置',
|
||||||
|
popConfirm: {
|
||||||
|
title: '是否重置AK,SK',
|
||||||
|
confirm: handleReset.bind(null, record),
|
||||||
|
placement: 'topLeft',
|
||||||
|
},
|
||||||
|
auth: 'openapi:open_api_auth:edit'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下拉操作栏
|
||||||
|
*/
|
||||||
|
function getDropDownAction(record) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '详情',
|
||||||
|
onClick: handleDetail.bind(null, record),
|
||||||
|
}, {
|
||||||
|
label: '删除',
|
||||||
|
popConfirm: {
|
||||||
|
title: '是否确认删除',
|
||||||
|
confirm: handleDelete.bind(null, record),
|
||||||
|
placement: 'topLeft',
|
||||||
|
},
|
||||||
|
auth: 'openapi:open_api_auth:delete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询
|
||||||
|
*/
|
||||||
|
function searchQuery() {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置
|
||||||
|
*/
|
||||||
|
function searchReset() {
|
||||||
|
formRef.value.resetFields();
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
//刷新数据
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.jeecg-basic-table-form-container {
|
||||||
|
padding: 0;
|
||||||
|
.table-page-search-submitButtons {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.query-group-cust{
|
||||||
|
min-width: 100px !important;
|
||||||
|
}
|
||||||
|
.query-group-split-cust{
|
||||||
|
width: 30px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
.ant-form-item:not(.ant-form-item-with-help){
|
||||||
|
margin-bottom: 16px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
:deep(.ant-picker),:deep(.ant-input-number){
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
217
jeecgboot-vue3/src/views/openapi/OpenApiList.vue
Normal file
217
jeecgboot-vue3/src/views/openapi/OpenApiList.vue
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!--引用表格-->
|
||||||
|
<BasicTable @register="registerTable" :rowSelection="rowSelection" @expand="handleExpand">
|
||||||
|
<!-- 内嵌table区域 begin -->
|
||||||
|
<!-- <template #expandedRowRender="{record}">-->
|
||||||
|
<!-- <a-tabs tabPosition="top">-->
|
||||||
|
<!-- <a-tab-pane tab="请求头表" key="openApiHeader" forceRender>-->
|
||||||
|
<!-- <openApiHeaderSubTable v-if="expandedRowKeys.includes(record.id)" :id="record.id" />-->
|
||||||
|
<!-- </a-tab-pane>-->
|
||||||
|
<!-- <a-tab-pane tab="请求参数部分" key="openApiParam" forceRender>-->
|
||||||
|
<!-- <openApiParamSubTable v-if="expandedRowKeys.includes(record.id)" :id="record.id" />-->
|
||||||
|
<!-- </a-tab-pane>-->
|
||||||
|
<!-- </a-tabs>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- 内嵌table区域 end -->
|
||||||
|
<!--插槽:table标题-->
|
||||||
|
<template #tableTitle>
|
||||||
|
<a-button type="primary" v-auth="'openapi:open_api:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||||
|
<a-button type="primary" v-auth="'openapi:open_api:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||||
|
<j-upload-button type="primary" v-auth="'openapi:open_api:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||||
|
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-menu-item key="1" @click="batchHandleDelete">
|
||||||
|
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||||
|
删除
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
<a-button v-auth="'openapi:open_api:deleteBatch'">批量操作
|
||||||
|
<Icon icon="mdi:chevron-down"></Icon>
|
||||||
|
</a-button>
|
||||||
|
</a-dropdown>
|
||||||
|
<!-- 高级查询 -->
|
||||||
|
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
|
||||||
|
</template>
|
||||||
|
<!--操作栏-->
|
||||||
|
<template #action="{ record }">
|
||||||
|
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
|
||||||
|
</template>
|
||||||
|
<!--字段回显插槽-->
|
||||||
|
<template v-slot:bodyCell="{ column, record, index, text }">
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
<!-- 表单区域 -->
|
||||||
|
<OpenApiModal @register="registerModal" @success="handleSuccess"></OpenApiModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" name="openapi-openApi" setup>
|
||||||
|
import {ref, reactive, computed, unref} from 'vue';
|
||||||
|
import {BasicTable, useTable, TableAction} from '/@/components/Table';
|
||||||
|
import { useListPage } from '/@/hooks/system/useListPage'
|
||||||
|
import {useModal} from '/@/components/Modal';
|
||||||
|
import OpenApiModal from './components/OpenApiModal.vue'
|
||||||
|
import OpenApiHeaderSubTable from './subTables/OpenApiHeaderSubTable.vue'
|
||||||
|
import OpenApiParamSubTable from './subTables/OpenApiParamSubTable.vue'
|
||||||
|
import {columns, searchFormSchema, superQuerySchema} from './OpenApi.data';
|
||||||
|
import {list, deleteOne, batchDelete, getImportUrl,getExportUrl} from './OpenApi.api';
|
||||||
|
import {downloadFile} from '/@/utils/common/renderUtils';
|
||||||
|
import { useUserStore } from '/@/store/modules/user';
|
||||||
|
const queryParam = reactive<any>({});
|
||||||
|
// 展开key
|
||||||
|
const expandedRowKeys = ref<any[]>([]);
|
||||||
|
//注册model
|
||||||
|
const [registerModal, {openModal}] = useModal();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
//注册table数据
|
||||||
|
const { prefixCls,tableContext,onExportXls,onImportXls } = useListPage({
|
||||||
|
tableProps:{
|
||||||
|
title: '接口管理',
|
||||||
|
api: list,
|
||||||
|
columns,
|
||||||
|
canResize:false,
|
||||||
|
formConfig: {
|
||||||
|
//labelWidth: 120,
|
||||||
|
schemas: searchFormSchema,
|
||||||
|
autoSubmitOnEnter:true,
|
||||||
|
showAdvancedButton:true,
|
||||||
|
fieldMapToNumber: [
|
||||||
|
],
|
||||||
|
fieldMapToTime: [
|
||||||
|
],
|
||||||
|
},
|
||||||
|
actionColumn: {
|
||||||
|
width: 120,
|
||||||
|
fixed:'right'
|
||||||
|
},
|
||||||
|
beforeFetch: (params) => {
|
||||||
|
return Object.assign(params, queryParam);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exportConfig: {
|
||||||
|
name:"接口管理",
|
||||||
|
url: getExportUrl,
|
||||||
|
params: queryParam,
|
||||||
|
},
|
||||||
|
importConfig: {
|
||||||
|
url: getImportUrl,
|
||||||
|
success: handleSuccess
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const [registerTable, {reload},{ rowSelection, selectedRowKeys }] = tableContext
|
||||||
|
|
||||||
|
// 高级查询配置
|
||||||
|
const superQueryConfig = reactive(superQuerySchema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高级查询事件
|
||||||
|
*/
|
||||||
|
function handleSuperQuery(params) {
|
||||||
|
Object.keys(params).map((k) => {
|
||||||
|
queryParam[k] = params[k];
|
||||||
|
});
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展开事件
|
||||||
|
* */
|
||||||
|
function handleExpand(expanded, record){
|
||||||
|
expandedRowKeys.value=[];
|
||||||
|
if (expanded === true) {
|
||||||
|
expandedRowKeys.value.push(record.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 新增事件
|
||||||
|
*/
|
||||||
|
function handleAdd() {
|
||||||
|
openModal(true, {
|
||||||
|
isUpdate: false,
|
||||||
|
showFooter: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 编辑事件
|
||||||
|
*/
|
||||||
|
function handleEdit(record: Recordable) {
|
||||||
|
openModal(true, {
|
||||||
|
record,
|
||||||
|
isUpdate: true,
|
||||||
|
showFooter: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
function handleDetail(record: Recordable) {
|
||||||
|
openModal(true, {
|
||||||
|
record,
|
||||||
|
isUpdate: true,
|
||||||
|
showFooter: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 删除事件
|
||||||
|
*/
|
||||||
|
async function handleDelete(record) {
|
||||||
|
await deleteOne({id: record.id}, handleSuccess);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 批量删除事件
|
||||||
|
*/
|
||||||
|
async function batchHandleDelete() {
|
||||||
|
await batchDelete({ids: selectedRowKeys.value},handleSuccess);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 成功回调
|
||||||
|
*/
|
||||||
|
function handleSuccess() {
|
||||||
|
(selectedRowKeys.value = []) && reload();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 操作栏
|
||||||
|
*/
|
||||||
|
function getTableAction(record){
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '编辑',
|
||||||
|
onClick: handleEdit.bind(null, record),
|
||||||
|
auth: 'openapi:open_api:edit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下拉操作栏
|
||||||
|
*/
|
||||||
|
function getDropDownAction(record){
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '详情',
|
||||||
|
onClick: handleDetail.bind(null, record),
|
||||||
|
}, {
|
||||||
|
label: '删除',
|
||||||
|
popConfirm: {
|
||||||
|
title: '是否确认删除',
|
||||||
|
confirm: handleDelete.bind(null, record),
|
||||||
|
placement: 'topLeft'
|
||||||
|
},
|
||||||
|
auth: 'openapi:open_api:delete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.ant-picker),:deep(.ant-input-number){
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
36
jeecgboot-vue3/src/views/openapi/SwaggerUI.vue
Normal file
36
jeecgboot-vue3/src/views/openapi/SwaggerUI.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="swaggerUiRef" style="height: 100%;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
// 尝试直接导入 SwaggerUI 而不是使用 * as
|
||||||
|
import SwaggerUI from 'swagger-ui-dist/swagger-ui-bundle'; // 确保使用 ESM 版本
|
||||||
|
import 'swagger-ui-dist/swagger-ui.css';
|
||||||
|
|
||||||
|
import { getOpenApiJson } from './OpenApi.api';
|
||||||
|
|
||||||
|
const swaggerUiRef = ref<HTMLElement | null>(null);
|
||||||
|
const API_DOMAIN = import.meta.env.VITE_GLOB_DOMAIN_URL
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const response = await getOpenApiJson();
|
||||||
|
const openApiJson = response;
|
||||||
|
if (swaggerUiRef.value) {
|
||||||
|
SwaggerUI({
|
||||||
|
domNode: swaggerUiRef.value,
|
||||||
|
spec: openApiJson,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch OpenAPI JSON:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 确保容器有高度 */
|
||||||
|
.swagger-ui-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
271
jeecgboot-vue3/src/views/openapi/components/AuthForm.vue
Normal file
271
jeecgboot-vue3/src/views/openapi/components/AuthForm.vue
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<a-spin :spinning="confirmLoading">
|
||||||
|
<a-row :span="24" style="margin-bottom: 10px">
|
||||||
|
<a-col :span="12" v-for="item in apiList" @click="handleSelect(item)">
|
||||||
|
<a-card :style="item.checked ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%', padding: '10px' }">
|
||||||
|
<div class="checkbox-name" style="display: flex; width: 100%; justify-content: space-between">
|
||||||
|
<span>接口名称: {{ item.name }}</span>
|
||||||
|
<a-checkbox v-model:checked="item.checked" @click.stop class="quantum-checker" @change="(e) => handleChange(e, item)"> </a-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox-name" style="margin-top: 4px">
|
||||||
|
请求方式: <span>{{item.requestMethod}}</span>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<Pagination
|
||||||
|
v-if="apiList.length > 0"
|
||||||
|
:current="pageNo"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:total="total"
|
||||||
|
:showQuickJumper="true"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
@change="handlePageChange"
|
||||||
|
class="list-footer"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, defineExpose, defineProps, nextTick, reactive, ref } from "vue";
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
|
import { getApiList, getPermissionList, permissionAddFunction } from '../OpenApiAuth.api';
|
||||||
|
import { Form, Pagination } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
formDisabled: { type: Boolean, default: false },
|
||||||
|
formData: { type: Object, default: () => ({}) },
|
||||||
|
formBpm: { type: Boolean, default: true },
|
||||||
|
});
|
||||||
|
const useForm = Form.useForm;
|
||||||
|
const emit = defineEmits(['register', 'ok']);
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
//认证ID
|
||||||
|
const apiAuthId = ref<string>('');
|
||||||
|
//表单验证
|
||||||
|
const validatorRules = reactive({});
|
||||||
|
//api列表
|
||||||
|
const apiList = ref<any>([]);
|
||||||
|
//选中的值
|
||||||
|
const selectedRowKeys = ref<any>([]);
|
||||||
|
//选中的数据
|
||||||
|
const selectedRows = ref<any>([]);
|
||||||
|
//当前页数
|
||||||
|
const pageNo = ref<number>(1);
|
||||||
|
//每页条数
|
||||||
|
const pageSize = ref<number>(10);
|
||||||
|
//总条数
|
||||||
|
const total = ref<number>(0);
|
||||||
|
//可选择的页数
|
||||||
|
const pageSizeOptions = ref<any>(['10', '20', '30']);
|
||||||
|
|
||||||
|
// 表单禁用
|
||||||
|
const disabled = computed(() => {
|
||||||
|
if (props.formBpm === true) {
|
||||||
|
if (props.formData.disabled === false) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props.formDisabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载数据
|
||||||
|
*/
|
||||||
|
function reload() {
|
||||||
|
getApiList({ pageNo: pageNo.value, pageSize: pageSize.value, column: 'createTime', order: 'desc'}).then((res)=>{
|
||||||
|
if (res.success) {
|
||||||
|
for (const item of res.result.records) {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
apiList.value = res.result.records;
|
||||||
|
total.value = res.result.total;
|
||||||
|
setChecked();
|
||||||
|
} else {
|
||||||
|
apiList.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
function add() {
|
||||||
|
edit({});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑
|
||||||
|
*/
|
||||||
|
async function edit(record) {
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
pageNo.value = 1;
|
||||||
|
pageSize.value = 10;
|
||||||
|
apiAuthId.value = record.id;
|
||||||
|
await nextTick(() => {
|
||||||
|
// 获取当前已授权的项目
|
||||||
|
getPermissionList({ apiAuthId: record.id }).then((res) => {
|
||||||
|
if (res.length > 0) {
|
||||||
|
res.forEach((item) => {
|
||||||
|
if(item.ifCheckBox == "1"){
|
||||||
|
selectedRowKeys.value.push(item.id);
|
||||||
|
selectedRows.value.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//设置选中
|
||||||
|
setChecked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交数据
|
||||||
|
*/
|
||||||
|
async function submitForm() {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
//时间格式化
|
||||||
|
let model = {};
|
||||||
|
let apiId = ""
|
||||||
|
selectedRowKeys.value.forEach((item) => {
|
||||||
|
apiId += item +",";
|
||||||
|
})
|
||||||
|
model['apiId'] = apiId;
|
||||||
|
model['apiAuthId'] = apiAuthId.value;
|
||||||
|
await permissionAddFunction(model)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
createMessage.success(res.message);
|
||||||
|
emit('ok');
|
||||||
|
cleanData()
|
||||||
|
} else {
|
||||||
|
createMessage.warning(res.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const cleanData = () => {
|
||||||
|
selectedRows.value = []
|
||||||
|
selectedRowKeys.value = []
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复选框选中事件
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
function handleSelect(item) {
|
||||||
|
let id = item.id;
|
||||||
|
const target = apiList.value.find((item) => item.id === id);
|
||||||
|
if (target) {
|
||||||
|
target.checked = !target.checked;
|
||||||
|
}
|
||||||
|
//存放选中的知识库的id
|
||||||
|
if (!selectedRowKeys.value || selectedRowKeys.value.length == 0) {
|
||||||
|
selectedRowKeys.value.push(id);
|
||||||
|
selectedRows.value.push(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let findIndex = selectedRowKeys.value.findIndex((item) => item === id);
|
||||||
|
if (findIndex === -1) {
|
||||||
|
selectedRowKeys.value.push(id);
|
||||||
|
selectedRows.value.push(item);
|
||||||
|
} else {
|
||||||
|
selectedRowKeys.value.splice(findIndex, 1);
|
||||||
|
selectedRows.value.splice(findIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复选框选中事件
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
function handleChange(e, item: any) {
|
||||||
|
if (e.target.checked) {
|
||||||
|
selectedRowKeys.value.push(item.id);
|
||||||
|
selectedRows.value.push(item);
|
||||||
|
} else {
|
||||||
|
let findIndex = selectedRowKeys.value.findIndex((val) => val === item.id);
|
||||||
|
if (findIndex != -1) {
|
||||||
|
selectedRowKeys.value.splice(findIndex, 1);
|
||||||
|
selectedRows.value.splice(findIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页改变事件
|
||||||
|
* @param page
|
||||||
|
* @param current
|
||||||
|
*/
|
||||||
|
function handlePageChange(page, current) {
|
||||||
|
pageNo.value = page;
|
||||||
|
pageSize.value = current;
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置选装状态
|
||||||
|
*/
|
||||||
|
function setChecked() {
|
||||||
|
if (apiList.value && apiList.value.length > 0){
|
||||||
|
let value = selectedRowKeys.value.join(',');
|
||||||
|
apiList.value = apiList.value.map((item) => {
|
||||||
|
if (value.indexOf(item.id) !== -1) {
|
||||||
|
item.checked = true;
|
||||||
|
} else {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
add,
|
||||||
|
edit,
|
||||||
|
submitForm,
|
||||||
|
cleanData
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.antd-modal-form {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
.list-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -22px;
|
||||||
|
right: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.checkbox-card {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.checkbox-img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.checkbox-name {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.use-select {
|
||||||
|
color: #646a73;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
100
jeecgboot-vue3/src/views/openapi/components/AuthModal.vue
Normal file
100
jeecgboot-vue3/src/views/openapi/components/AuthModal.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <j-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">-->
|
||||||
|
<div style="position: relative;">
|
||||||
|
<a-modal
|
||||||
|
v-model:open="authDrawerOpen"
|
||||||
|
class="custom-class"
|
||||||
|
root-class-name="root-class-name"
|
||||||
|
:root-style="{ color: 'blue' }"
|
||||||
|
:body-style="{ padding: '20px' }"
|
||||||
|
style="color: red"
|
||||||
|
:title="title"
|
||||||
|
:width="600"
|
||||||
|
@after-open-change="authDrawerOpenChange"
|
||||||
|
@ok="handleOk"
|
||||||
|
>
|
||||||
|
<AuthForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></AuthForm>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
<!-- </j-modal>-->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, nextTick, defineExpose } from 'vue';
|
||||||
|
import AuthForm from './AuthForm.vue';
|
||||||
|
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
|
||||||
|
|
||||||
|
const title = ref<string>('');
|
||||||
|
const width = ref<number>(800);
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
const disableSubmit = ref<boolean>(false);
|
||||||
|
const registerForm = ref();
|
||||||
|
const emit = defineEmits(['register', 'success']);
|
||||||
|
|
||||||
|
const authDrawerOpen = ref(false);
|
||||||
|
const authDrawerOpenChange = (val: any) => {
|
||||||
|
if(!val)
|
||||||
|
registerForm.value.cleanData()
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
function add() {
|
||||||
|
title.value = '新增';
|
||||||
|
visible.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
registerForm.value.add();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权
|
||||||
|
* @param record
|
||||||
|
*/
|
||||||
|
function edit(record) {
|
||||||
|
title.value = disableSubmit.value ? '详情' : '授权';
|
||||||
|
visible.value = true;
|
||||||
|
authDrawerOpen.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
registerForm.value.edit(record);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确定按钮点击事件
|
||||||
|
*/
|
||||||
|
function handleOk() {
|
||||||
|
registerForm.value.submitForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* form保存回调事件
|
||||||
|
*/
|
||||||
|
function submitCallback() {
|
||||||
|
handleCancel();
|
||||||
|
emit('success');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消按钮回调事件
|
||||||
|
*/
|
||||||
|
function handleCancel() {
|
||||||
|
visible.value = false;
|
||||||
|
authDrawerOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
add,
|
||||||
|
edit,
|
||||||
|
disableSubmit,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
/**隐藏样式-modal确定按钮 */
|
||||||
|
.jee-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less" scoped></style>
|
||||||
175
jeecgboot-vue3/src/views/openapi/components/OpenApiAuthForm.vue
Normal file
175
jeecgboot-vue3/src/views/openapi/components/OpenApiAuthForm.vue
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<a-spin :spinning="confirmLoading">
|
||||||
|
<JFormContainer :disabled="disabled">
|
||||||
|
<template #detail>
|
||||||
|
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol" name="OpenApiAuthForm">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="授权名称" v-bind="validateInfos.name" id="OpenApiAuthForm-name" name="name">
|
||||||
|
<a-input v-model:value="formData.name" placeholder="请输入授权名称" allow-clear ></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="AK" v-bind="validateInfos.ak" id="OpenApiAuthForm-ak" name="ak">
|
||||||
|
<a-input v-model:value="formData.ak" placeholder="请输入AK" disabled allow-clear ></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="SK" v-bind="validateInfos.sk" id="OpenApiAuthForm-sk" name="sk">
|
||||||
|
<a-input v-model:value="formData.sk" placeholder="请输入SK" disabled allow-clear ></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<!-- <a-col :span="24">-->
|
||||||
|
<!-- <a-form-item label="关联系统用户名" v-bind="validateInfos.systemUserId" id="OpenApiAuthForm-systemUserId" name="systemUserId">-->
|
||||||
|
<!-- <JSearchSelect dict="sys_user,username,id" v-model:value="formData.systemUserId" placeholder="请输入关联系统用户名" allow-clear ></JSearchSelect>-->
|
||||||
|
<!-- </a-form-item>-->
|
||||||
|
<!-- </a-col>-->
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
</JFormContainer>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, defineExpose, nextTick, defineProps, computed, } from 'vue';
|
||||||
|
import { USER_INFO_KEY} from '/@/enums/cacheEnum';
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
|
import { getValueType } from '/@/utils';
|
||||||
|
import { saveOrUpdate,getGenAKSK } from '../OpenApiAuth.api';
|
||||||
|
import { Form } from 'ant-design-vue';
|
||||||
|
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
|
||||||
|
import JSearchSelect from '/@/components/Form/src/jeecg/components/JSearchSelect.vue';
|
||||||
|
import { getAuthCache } from "@/utils/auth";
|
||||||
|
const props = defineProps({
|
||||||
|
formDisabled: { type: Boolean, default: false },
|
||||||
|
formData: { type: Object, default: () => ({})},
|
||||||
|
formBpm: { type: Boolean, default: true },
|
||||||
|
title: { type: String, default: "" },
|
||||||
|
});
|
||||||
|
const formRef = ref();
|
||||||
|
const useForm = Form.useForm;
|
||||||
|
const emit = defineEmits(['register', 'ok']);
|
||||||
|
const formData = reactive<Record<string, any>>({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
ak: '',
|
||||||
|
sk: '',
|
||||||
|
systemUserId: '',
|
||||||
|
});
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
|
||||||
|
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
//表单验证
|
||||||
|
const validatorRules = reactive({
|
||||||
|
name:[{ required: true, message: '请输入授权名称!'},],
|
||||||
|
systemUserId:[{ required: true, message: '请输入关联系统用户名!'},],
|
||||||
|
});
|
||||||
|
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
|
||||||
|
|
||||||
|
// 表单禁用
|
||||||
|
const disabled = computed(()=>{
|
||||||
|
if(props.formBpm === true){
|
||||||
|
if(props.formData.disabled === false){
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props.formDisabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
async function add() {
|
||||||
|
edit({});
|
||||||
|
const AKSKObj = await getGenAKSK({});
|
||||||
|
formData.ak = AKSKObj[0];
|
||||||
|
formData.sk = AKSKObj[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑
|
||||||
|
*/
|
||||||
|
function edit(record) {
|
||||||
|
const userData = getAuthCache(USER_INFO_KEY)
|
||||||
|
if(props.title == "新增"){
|
||||||
|
record.systemUserId = userData.id
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
resetFields();
|
||||||
|
const tmpData = {};
|
||||||
|
Object.keys(formData).forEach((key) => {
|
||||||
|
if(record.hasOwnProperty(key)){
|
||||||
|
tmpData[key] = record[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//赋值
|
||||||
|
Object.assign(formData, tmpData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交数据
|
||||||
|
*/
|
||||||
|
async function submitForm() {
|
||||||
|
try {
|
||||||
|
// 触发表单验证
|
||||||
|
await validate();
|
||||||
|
} catch ({ errorFields }) {
|
||||||
|
if (errorFields) {
|
||||||
|
const firstField = errorFields[0];
|
||||||
|
if (firstField) {
|
||||||
|
formRef.value.scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(errorFields);
|
||||||
|
}
|
||||||
|
confirmLoading.value = true;
|
||||||
|
const isUpdate = ref<boolean>(false);
|
||||||
|
//时间格式化
|
||||||
|
let model = formData;
|
||||||
|
if (model.id) {
|
||||||
|
isUpdate.value = true;
|
||||||
|
}
|
||||||
|
//循环数据
|
||||||
|
for (let data in model) {
|
||||||
|
//如果该数据是数组并且是字符串类型
|
||||||
|
if (model[data] instanceof Array) {
|
||||||
|
let valueType = getValueType(formRef.value.getProps, data);
|
||||||
|
//如果是字符串类型的需要变成以逗号分割的字符串
|
||||||
|
if (valueType === 'string') {
|
||||||
|
model[data] = model[data].join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await saveOrUpdate(model, isUpdate.value)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
createMessage.success(res.message);
|
||||||
|
emit('ok');
|
||||||
|
} else {
|
||||||
|
createMessage.warning(res.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
add,
|
||||||
|
edit,
|
||||||
|
submitForm,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.antd-modal-form {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<j-modal :title="title" :width="width" :maxHeight="200" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
|
||||||
|
<OpenApiAuthForm ref="registerForm" @ok="submitCallback" :title="title" :formDisabled="disableSubmit" :formBpm="false"></OpenApiAuthForm>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, nextTick, defineExpose } from 'vue';
|
||||||
|
import OpenApiAuthForm from './OpenApiAuthForm.vue'
|
||||||
|
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
|
||||||
|
|
||||||
|
const title = ref<string>('');
|
||||||
|
const width = ref<number>(800);
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
const disableSubmit = ref<boolean>(false);
|
||||||
|
const registerForm = ref();
|
||||||
|
const emit = defineEmits(['register', 'success']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
function add() {
|
||||||
|
title.value = '新增';
|
||||||
|
visible.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
registerForm.value.add();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑
|
||||||
|
* @param record
|
||||||
|
*/
|
||||||
|
function edit(record) {
|
||||||
|
title.value = disableSubmit.value ? '详情' : '编辑';
|
||||||
|
visible.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
registerForm.value.edit(record);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确定按钮点击事件
|
||||||
|
*/
|
||||||
|
function handleOk() {
|
||||||
|
registerForm.value.submitForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* form保存回调事件
|
||||||
|
*/
|
||||||
|
function submitCallback() {
|
||||||
|
handleCancel();
|
||||||
|
emit('success');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消按钮回调事件
|
||||||
|
*/
|
||||||
|
function handleCancel() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
add,
|
||||||
|
edit,
|
||||||
|
disableSubmit,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
/**隐藏样式-modal确定按钮 */
|
||||||
|
.jee-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less" scoped></style>
|
||||||
178
jeecgboot-vue3/src/views/openapi/components/OpenApiModal.vue
Normal file
178
jeecgboot-vue3/src/views/openapi/components/OpenApiModal.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<BasicModal :bodyStyle="{ padding: '20px' }" v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" width="80%" @ok="handleSubmit">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="10">
|
||||||
|
<BasicForm @register="registerForm" ref="formRef" name="OpenApiForm" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="14">
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="24" style="margin-top: -0.6em">
|
||||||
|
<JVxeTable
|
||||||
|
keep-source
|
||||||
|
ref="openApiHeader"
|
||||||
|
:loading="openApiHeaderTable.loading"
|
||||||
|
:columns="openApiHeaderTable.columns"
|
||||||
|
:dataSource="openApiHeaderTable.dataSource"
|
||||||
|
:height="240"
|
||||||
|
:disabled="formDisabled"
|
||||||
|
:rowNumber="true"
|
||||||
|
:rowSelection="true"
|
||||||
|
:toolbar="true"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<JVxeTable
|
||||||
|
keep-source
|
||||||
|
ref="openApiParam"
|
||||||
|
:loading="openApiParamTable.loading"
|
||||||
|
:columns="openApiParamTable.columns"
|
||||||
|
:dataSource="openApiParamTable.dataSource"
|
||||||
|
:height="240"
|
||||||
|
:disabled="formDisabled"
|
||||||
|
:rowNumber="true"
|
||||||
|
:rowSelection="true"
|
||||||
|
:toolbar="true"
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, unref, reactive } from 'vue';
|
||||||
|
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||||
|
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||||
|
import { JVxeTable } from '/@/components/jeecg/JVxeTable';
|
||||||
|
import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods.ts';
|
||||||
|
import { formSchema, openApiHeaderJVxeColumns, openApiParamJVxeColumns } from '../OpenApi.data';
|
||||||
|
import { saveOrUpdate, queryOpenApiHeader, queryOpenApiParam, getGenPath } from '../OpenApi.api';
|
||||||
|
import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils';
|
||||||
|
import { useMessage } from "@/hooks/web/useMessage";
|
||||||
|
|
||||||
|
// Emits声明
|
||||||
|
const $message = useMessage();
|
||||||
|
const emit = defineEmits(['register', 'success']);
|
||||||
|
const isUpdate = ref(true);
|
||||||
|
const formDisabled = ref(false);
|
||||||
|
const refKeys = ref(['openApiHeader', 'openApiParam']);
|
||||||
|
const activeKey = ref('openApiHeader');
|
||||||
|
const openApiHeader = ref();
|
||||||
|
const openApiParam = ref();
|
||||||
|
const tableRefs = { openApiHeader, openApiParam };
|
||||||
|
const openApiHeaderTable = reactive({
|
||||||
|
loading: false,
|
||||||
|
dataSource: [],
|
||||||
|
columns: openApiHeaderJVxeColumns,
|
||||||
|
});
|
||||||
|
const openApiParamTable = reactive({
|
||||||
|
loading: false,
|
||||||
|
dataSource: [],
|
||||||
|
columns: openApiParamJVxeColumns,
|
||||||
|
});
|
||||||
|
//表单配置
|
||||||
|
const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
|
||||||
|
labelWidth: 100,
|
||||||
|
schemas: formSchema,
|
||||||
|
showActionButtonGroup: false,
|
||||||
|
baseColProps: { span: 24 },
|
||||||
|
wrapperCol: { span: 24 },
|
||||||
|
});
|
||||||
|
//表单赋值
|
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||||
|
//重置表单
|
||||||
|
await reset();
|
||||||
|
setModalProps({ confirmLoading: false, showCancelBtn: data?.showFooter, showOkBtn: data?.showFooter });
|
||||||
|
isUpdate.value = !!data?.isUpdate;
|
||||||
|
formDisabled.value = !data?.showFooter;
|
||||||
|
if (unref(isUpdate)) {
|
||||||
|
//表单赋值
|
||||||
|
await setFieldsValue({
|
||||||
|
...data.record,
|
||||||
|
});
|
||||||
|
// 请求后端接口获取数据
|
||||||
|
// requestSubTableData(queryOpenApiHeader, {id:data?.record?.id}, openApiHeaderTable)
|
||||||
|
// requestSubTableData(queryOpenApiParam, {id:data?.record?.id}, openApiParamTable)
|
||||||
|
openApiHeaderTable.dataSource = !!data.record.headersJson?JSON.parse(data.record.headersJson):[];
|
||||||
|
openApiParamTable.dataSource = !!data.record.paramsJson?JSON.parse(data.record.paramsJson):[];
|
||||||
|
} else {
|
||||||
|
// /openapi/genpath
|
||||||
|
const requestUrlObj = await getGenPath({});
|
||||||
|
await setFieldsValue({
|
||||||
|
requestUrl: requestUrlObj.result
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 隐藏底部时禁用整个表单
|
||||||
|
setProps({ disabled: !data?.showFooter });
|
||||||
|
});
|
||||||
|
//方法配置
|
||||||
|
const [handleChangeTabs, handleSubmit, requestSubTableData, formRef] = useJvxeMethod(
|
||||||
|
requestAddOrEdit,
|
||||||
|
classifyIntoFormData,
|
||||||
|
tableRefs,
|
||||||
|
activeKey,
|
||||||
|
refKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
//设置标题
|
||||||
|
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(formDisabled) ? '编辑' : '详情'));
|
||||||
|
|
||||||
|
async function reset() {
|
||||||
|
await resetFields();
|
||||||
|
activeKey.value = 'openApiHeader';
|
||||||
|
openApiHeaderTable.dataSource = [];
|
||||||
|
openApiParamTable.dataSource = [];
|
||||||
|
}
|
||||||
|
function classifyIntoFormData(allValues) {
|
||||||
|
let main = Object.assign({}, allValues.formValue);
|
||||||
|
return {
|
||||||
|
...main, // 展开
|
||||||
|
headersJson: allValues.tablesValue[0].tableData,
|
||||||
|
paramsJson: allValues.tablesValue[1].tableData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//表单提交事件
|
||||||
|
async function requestAddOrEdit(values) {
|
||||||
|
let headersJson = !!values.headersJson?JSON.stringify(values.headersJson):null;
|
||||||
|
let paramsJson = !!values.headersJson?JSON.stringify(values.paramsJson):null;
|
||||||
|
try {
|
||||||
|
if (!!values.body){
|
||||||
|
try {
|
||||||
|
if (typeof JSON.parse(values.body)!='object'){
|
||||||
|
$message.createMessage.error("JSON格式化错误,请检查输入数据");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.createMessage.error("JSON格式化错误,请检查输入数据");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setModalProps({ confirmLoading: true });
|
||||||
|
values.headersJson = headersJson
|
||||||
|
values.paramsJson = paramsJson
|
||||||
|
//提交表单
|
||||||
|
await saveOrUpdate(values, isUpdate.value);
|
||||||
|
//关闭弹窗
|
||||||
|
closeModal();
|
||||||
|
//刷新列表
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
setModalProps({ confirmLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
/** 时间和数字输入框样式 */
|
||||||
|
:deep(.ant-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-calendar-picker) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!--引用表格-->
|
||||||
|
<BasicTable bordered size="middle" :loading="loading" rowKey="id" :canResize="false" :columns="openApiHeaderColumns" :dataSource="dataSource" :pagination="false">
|
||||||
|
<!--字段回显插槽-->
|
||||||
|
<template v-slot:bodyCell="{ column, record, index, text }">
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {ref,watchEffect} from 'vue';
|
||||||
|
import {BasicTable} from '/@/components/Table';
|
||||||
|
import {openApiHeaderColumns} from '../OpenApi.data';
|
||||||
|
import {openApiHeaderList} from '../OpenApi.api';
|
||||||
|
import { downloadFile } from '/@/utils/common/renderUtils';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const dataSource = ref([]);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
props.id && loadData(props.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadData(id) {
|
||||||
|
dataSource.value = []
|
||||||
|
loading.value = true
|
||||||
|
openApiHeaderList({id}).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
dataSource.value = res.result.records
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user