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能够保持回复内容的原格式,也不丢失图片,使得输出的文章更加美观,不会出现格式错乱的情况,还支持图片的渲染。
|
||||
> - PDF文档导入与格式转换:
|
||||
> JEECG AI在处理PDF文档时,能够更好地保持原始格式和图片,确保转换后的内容与原始文档一致。这哥功能在许多AI产品中表现不佳,而JEECG AI在这方面做出了显著的优化
|
||||
> JEECG AI在处理PDF文档时,能够更好地保持原始格式和图片,确保转换后的内容与原始文档一致。这个功能在许多AI产品中表现不佳,而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 应用。
|
||||
|
||||
|
||||
信创国产化
|
||||
-----------------------------------
|
||||
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应用平台介绍
|
||||
-----------------------------------
|
||||
|
||||
@ -93,6 +120,8 @@ JeecgBoot 平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
||||
[](https://www.bilibili.com/video/BV1zmd7YFE4w)
|
||||
|
||||
|
||||
|
||||
|
||||
##### Dify `VS` JEECG AI
|
||||
|
||||
> JEECG AI与Dify相比,在多个方面展现出显著的优势,特别是在文档处理、格式和图片保持方面。以下是一些具体的优点:
|
||||
@ -101,7 +130,7 @@ JeecgBoot 平台的AIGC功能模块,是一套类似`Dify`的`AIGC应用开发
|
||||
> - 对话回复格式美观:
|
||||
> 在对话过程中,JEECG AI能够保持回复内容的原格式,也不丢失图片,使得输出的文章更加美观,不会出现格式错乱的情况,还支持图片的渲染。
|
||||
> - 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>
|
||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||
</dependency>
|
||||
<!-- minidao -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>minidao-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -36,7 +36,6 @@ public class RestUtil {
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
private static String getPath() {
|
||||
if (path == null) {
|
||||
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.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
@ -16,7 +17,13 @@ import java.util.regex.Pattern;
|
||||
* @author zhoujf
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlInjectionUtil {
|
||||
public class SqlInjectionUtil {
|
||||
|
||||
/**
|
||||
* sql注入黑名单数据库名
|
||||
*/
|
||||
public final static String XSS_STR_TABLE = "peformance_schema|information_schema";
|
||||
|
||||
/**
|
||||
* 默认—sql注入关键词
|
||||
*/
|
||||
@ -167,7 +174,28 @@ public class SqlInjectionUtil {
|
||||
}
|
||||
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注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
@ -208,6 +236,14 @@ public class SqlInjectionUtil {
|
||||
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注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
@ -244,6 +280,14 @@ public class SqlInjectionUtil {
|
||||
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注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
|
||||
@ -3,6 +3,8 @@ package org.jeecg.common.util.security;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
@ -66,6 +68,8 @@ public abstract class AbstractQueryBlackListHandler {
|
||||
if(flag == false){
|
||||
return false;
|
||||
}
|
||||
Set<String> xssTableSet = new HashSet<>(Arrays.asList(SqlInjectionUtil.XSS_STR_TABLE.split("\\|")));
|
||||
|
||||
for (QueryTable table : list) {
|
||||
String name = table.getName();
|
||||
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 {
|
||||
//数据库名
|
||||
private String dbName;
|
||||
//表名
|
||||
private String name;
|
||||
//表的别名
|
||||
@ -158,6 +174,14 @@ public abstract class AbstractQueryBlackListHandler {
|
||||
this.fields.add(field);
|
||||
}
|
||||
|
||||
public String getDbName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
public void setDbName(String dbName) {
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -1,255 +1,255 @@
|
||||
package org.jeecg.common.util.sqlparse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 解析所有表名和字段的类
|
||||
*/
|
||||
@Slf4j
|
||||
public class JSqlParserAllTableManager {
|
||||
|
||||
private final String sql;
|
||||
private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
||||
/**
|
||||
* 别名对应实际表名
|
||||
*/
|
||||
private final Map<String, String> tableAliasMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 解析后的sql
|
||||
*/
|
||||
private String parsedSql = null;
|
||||
|
||||
JSqlParserAllTableManager(String selectSql) {
|
||||
this.sql = selectSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始解析
|
||||
*
|
||||
* @return
|
||||
* @throws JSQLParserException
|
||||
*/
|
||||
public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
||||
// 1. 创建解析器
|
||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// 2. 使用解析器解析sql生成具有层次结构的java类
|
||||
Statement stmt = mgr.parse(new StringReader(this.sql));
|
||||
if (stmt instanceof Select) {
|
||||
Select selectStatement = (Select) stmt;
|
||||
SelectBody selectBody = selectStatement.getSelectBody();
|
||||
this.parsedSql = selectBody.toString();
|
||||
// 3. 解析select查询sql的信息
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// 4. 合并 fromItems
|
||||
List<FromItem> fromItems = new ArrayList<>();
|
||||
fromItems.add(plainSelect.getFromItem());
|
||||
// 4.1 处理join的表
|
||||
List<Join> joins = plainSelect.getJoins();
|
||||
if (joins != null) {
|
||||
joins.forEach(join -> fromItems.add(join.getRightItem()));
|
||||
}
|
||||
// 5. 处理 fromItems
|
||||
for (FromItem fromItem : fromItems) {
|
||||
// 5.1 通过表名的方式from
|
||||
if (fromItem instanceof Table) {
|
||||
this.addSqlInfoByTable((Table) fromItem);
|
||||
}
|
||||
// 5.2 通过子查询的方式from
|
||||
else if (fromItem instanceof SubSelect) {
|
||||
this.handleSubSelect((SubSelect) fromItem);
|
||||
}
|
||||
}
|
||||
// 6. 解析 selectFields
|
||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
for (SelectItem selectItem : selectItems) {
|
||||
// 6.1 查询的是全部字段
|
||||
if (selectItem instanceof AllColumns) {
|
||||
// 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
||||
String tableName = plainSelect.getFromItem(Table.class).getName();
|
||||
// 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
assert sqlInfo != null;
|
||||
// 设置为查询全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
}
|
||||
// 6.2 查询的是带表别名( u.* )的全部字段
|
||||
else if (selectItem instanceof AllTableColumns) {
|
||||
AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
||||
String aliasName = allTableColumns.getTable().getName();
|
||||
// 通过别名获取表名
|
||||
String tableName = this.tableAliasMap.get(aliasName);
|
||||
if (tableName == null) {
|
||||
tableName = aliasName;
|
||||
}
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
||||
if (sqlInfo != null) {
|
||||
// 设置为查询全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
}
|
||||
}
|
||||
// 6.3 各种字段表达式处理
|
||||
else if (selectItem instanceof SelectExpressionItem) {
|
||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
Expression expression = selectExpressionItem.getExpression();
|
||||
Alias alias = selectExpressionItem.getAlias();
|
||||
this.handleExpression(expression, alias, plainSelect.getFromItem());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
}
|
||||
} else {
|
||||
// 非 select 查询sql,不做处理
|
||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
}
|
||||
return this.allTableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理子查询
|
||||
*
|
||||
* @param subSelect
|
||||
*/
|
||||
private void handleSubSelect(SubSelect subSelect) {
|
||||
try {
|
||||
String subSelectSql = subSelect.getSelectBody().toString();
|
||||
// 递归调用解析
|
||||
Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
||||
if (map != null) {
|
||||
this.assignMap(map);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析子查询出错", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查询字段表达式
|
||||
*
|
||||
* @param expression
|
||||
*/
|
||||
private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
if (expression instanceof Function) {
|
||||
Function functionExp = (Function) expression;
|
||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
for (Expression expItem : expressions) {
|
||||
this.handleExpression(expItem, null, fromItem);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 处理字段上的子查询
|
||||
if (expression instanceof SubSelect) {
|
||||
this.handleSubSelect((SubSelect) expression);
|
||||
return;
|
||||
}
|
||||
// 不处理字面量
|
||||
if (expression instanceof StringValue ||
|
||||
expression instanceof NullValue ||
|
||||
expression instanceof LongValue ||
|
||||
expression instanceof DoubleValue ||
|
||||
expression instanceof HexValue ||
|
||||
expression instanceof DateValue ||
|
||||
expression instanceof TimestampValue ||
|
||||
expression instanceof TimeValue
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理字段
|
||||
if (expression instanceof Column) {
|
||||
Column column = (Column) expression;
|
||||
// 查询字段名
|
||||
String fieldName = column.getColumnName();
|
||||
String aliasName = fieldName;
|
||||
if (alias != null) {
|
||||
aliasName = alias.getName();
|
||||
}
|
||||
String tableName;
|
||||
if (column.getTable() != null) {
|
||||
// 通过列的表名获取 sqlInfo
|
||||
// 例如 user.name,这里的 tableName 就是 user
|
||||
tableName = column.getTable().getName();
|
||||
// 有可能是别名,需要转换为真实表名
|
||||
if (this.tableAliasMap.get(tableName) != null) {
|
||||
tableName = this.tableAliasMap.get(tableName);
|
||||
}
|
||||
} else {
|
||||
// 当column的table为空时,说明是 fromItem 中的字段
|
||||
tableName = ((Table) fromItem).getName();
|
||||
}
|
||||
SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
||||
if ($sqlInfo != null) {
|
||||
$sqlInfo.addSelectField(aliasName, fieldName);
|
||||
} else {
|
||||
log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名添加sqlInfo
|
||||
*
|
||||
* @param table
|
||||
*/
|
||||
private void addSqlInfoByTable(Table table) {
|
||||
String tableName = table.getName();
|
||||
// 解析 aliasName
|
||||
if (table.getAlias() != null) {
|
||||
this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
||||
}
|
||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
||||
sqlInfo.setFromTableName(table.getName());
|
||||
this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并map
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
private void assignMap(Map<String, SelectSqlInfo> source) {
|
||||
for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
||||
if (sqlInfo == null) {
|
||||
this.allTableMap.put(entry.getKey(), entry.getValue());
|
||||
} else {
|
||||
// 合并
|
||||
if (sqlInfo.getSelectFields() == null) {
|
||||
sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
||||
} else {
|
||||
sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
||||
}
|
||||
if (sqlInfo.getRealSelectFields() == null) {
|
||||
sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
||||
} else {
|
||||
sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//package org.jeecg.common.util.sqlparse;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import net.sf.jsqlparser.JSQLParserException;
|
||||
//import net.sf.jsqlparser.expression.*;
|
||||
//import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
//import net.sf.jsqlparser.schema.Column;
|
||||
//import net.sf.jsqlparser.schema.Table;
|
||||
//import net.sf.jsqlparser.statement.Statement;
|
||||
//import net.sf.jsqlparser.statement.select.*;
|
||||
//import org.jeecg.common.exception.JeecgBootException;
|
||||
//import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
//
|
||||
//import java.io.StringReader;
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.HashMap;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * 解析所有表名和字段的类
|
||||
// */
|
||||
//@Slf4j
|
||||
//public class JSqlParserAllTableManager {
|
||||
//
|
||||
// private final String sql;
|
||||
// private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
||||
// /**
|
||||
// * 别名对应实际表名
|
||||
// */
|
||||
// private final Map<String, String> tableAliasMap = new HashMap<>();
|
||||
//
|
||||
// /**
|
||||
// * 解析后的sql
|
||||
// */
|
||||
// private String parsedSql = null;
|
||||
//
|
||||
// JSqlParserAllTableManager(String selectSql) {
|
||||
// this.sql = selectSql;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 开始解析
|
||||
// *
|
||||
// * @return
|
||||
// * @throws JSQLParserException
|
||||
// */
|
||||
// public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
||||
// // 1. 创建解析器
|
||||
// CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// // 2. 使用解析器解析sql生成具有层次结构的java类
|
||||
// Statement stmt = mgr.parse(new StringReader(this.sql));
|
||||
// if (stmt instanceof Select) {
|
||||
// Select selectStatement = (Select) stmt;
|
||||
// SelectBody selectBody = selectStatement.getSelectBody();
|
||||
// this.parsedSql = selectBody.toString();
|
||||
// // 3. 解析select查询sql的信息
|
||||
// if (selectBody instanceof PlainSelect) {
|
||||
// PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// // 4. 合并 fromItems
|
||||
// List<FromItem> fromItems = new ArrayList<>();
|
||||
// fromItems.add(plainSelect.getFromItem());
|
||||
// // 4.1 处理join的表
|
||||
// List<Join> joins = plainSelect.getJoins();
|
||||
// if (joins != null) {
|
||||
// joins.forEach(join -> fromItems.add(join.getRightItem()));
|
||||
// }
|
||||
// // 5. 处理 fromItems
|
||||
// for (FromItem fromItem : fromItems) {
|
||||
// // 5.1 通过表名的方式from
|
||||
// if (fromItem instanceof Table) {
|
||||
// this.addSqlInfoByTable((Table) fromItem);
|
||||
// }
|
||||
// // 5.2 通过子查询的方式from
|
||||
// else if (fromItem instanceof SubSelect) {
|
||||
// this.handleSubSelect((SubSelect) fromItem);
|
||||
// }
|
||||
// }
|
||||
// // 6. 解析 selectFields
|
||||
// List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
// for (SelectItem selectItem : selectItems) {
|
||||
// // 6.1 查询的是全部字段
|
||||
// if (selectItem instanceof AllColumns) {
|
||||
// // 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
||||
// String tableName = plainSelect.getFromItem(Table.class).getName();
|
||||
// // 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
||||
// SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// assert sqlInfo != null;
|
||||
// // 设置为查询全部字段
|
||||
// sqlInfo.setSelectAll(true);
|
||||
// sqlInfo.setSelectFields(null);
|
||||
// sqlInfo.setRealSelectFields(null);
|
||||
// }
|
||||
// // 6.2 查询的是带表别名( u.* )的全部字段
|
||||
// else if (selectItem instanceof AllTableColumns) {
|
||||
// AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
||||
// String aliasName = allTableColumns.getTable().getName();
|
||||
// // 通过别名获取表名
|
||||
// String tableName = this.tableAliasMap.get(aliasName);
|
||||
// if (tableName == null) {
|
||||
// tableName = aliasName;
|
||||
// }
|
||||
// SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// // 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
||||
// if (sqlInfo != null) {
|
||||
// // 设置为查询全部字段
|
||||
// sqlInfo.setSelectAll(true);
|
||||
// sqlInfo.setSelectFields(null);
|
||||
// sqlInfo.setRealSelectFields(null);
|
||||
// }
|
||||
// }
|
||||
// // 6.3 各种字段表达式处理
|
||||
// else if (selectItem instanceof SelectExpressionItem) {
|
||||
// SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
// Expression expression = selectExpressionItem.getExpression();
|
||||
// Alias alias = selectExpressionItem.getAlias();
|
||||
// this.handleExpression(expression, alias, plainSelect.getFromItem());
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
// throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
// }
|
||||
// } else {
|
||||
// // 非 select 查询sql,不做处理
|
||||
// throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
// }
|
||||
// return this.allTableMap;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理子查询
|
||||
// *
|
||||
// * @param subSelect
|
||||
// */
|
||||
// private void handleSubSelect(SubSelect subSelect) {
|
||||
// try {
|
||||
// String subSelectSql = subSelect.getSelectBody().toString();
|
||||
// // 递归调用解析
|
||||
// Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
||||
// if (map != null) {
|
||||
// this.assignMap(map);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("解析子查询出错", e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理查询字段表达式
|
||||
// *
|
||||
// * @param expression
|
||||
// */
|
||||
// private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
||||
// // 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
// if (expression instanceof Function) {
|
||||
// Function functionExp = (Function) expression;
|
||||
// List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
// for (Expression expItem : expressions) {
|
||||
// this.handleExpression(expItem, null, fromItem);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// // 处理字段上的子查询
|
||||
// if (expression instanceof SubSelect) {
|
||||
// this.handleSubSelect((SubSelect) expression);
|
||||
// return;
|
||||
// }
|
||||
// // 不处理字面量
|
||||
// if (expression instanceof StringValue ||
|
||||
// expression instanceof NullValue ||
|
||||
// expression instanceof LongValue ||
|
||||
// expression instanceof DoubleValue ||
|
||||
// expression instanceof HexValue ||
|
||||
// expression instanceof DateValue ||
|
||||
// expression instanceof TimestampValue ||
|
||||
// expression instanceof TimeValue
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 处理字段
|
||||
// if (expression instanceof Column) {
|
||||
// Column column = (Column) expression;
|
||||
// // 查询字段名
|
||||
// String fieldName = column.getColumnName();
|
||||
// String aliasName = fieldName;
|
||||
// if (alias != null) {
|
||||
// aliasName = alias.getName();
|
||||
// }
|
||||
// String tableName;
|
||||
// if (column.getTable() != null) {
|
||||
// // 通过列的表名获取 sqlInfo
|
||||
// // 例如 user.name,这里的 tableName 就是 user
|
||||
// tableName = column.getTable().getName();
|
||||
// // 有可能是别名,需要转换为真实表名
|
||||
// if (this.tableAliasMap.get(tableName) != null) {
|
||||
// tableName = this.tableAliasMap.get(tableName);
|
||||
// }
|
||||
// } else {
|
||||
// // 当column的table为空时,说明是 fromItem 中的字段
|
||||
// tableName = ((Table) fromItem).getName();
|
||||
// }
|
||||
// SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
||||
// if ($sqlInfo != null) {
|
||||
// $sqlInfo.addSelectField(aliasName, fieldName);
|
||||
// } else {
|
||||
// log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 根据表名添加sqlInfo
|
||||
// *
|
||||
// * @param table
|
||||
// */
|
||||
// private void addSqlInfoByTable(Table table) {
|
||||
// String tableName = table.getName();
|
||||
// // 解析 aliasName
|
||||
// if (table.getAlias() != null) {
|
||||
// this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
||||
// }
|
||||
// SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
||||
// sqlInfo.setFromTableName(table.getName());
|
||||
// this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 合并map
|
||||
// *
|
||||
// * @param source
|
||||
// */
|
||||
// private void assignMap(Map<String, SelectSqlInfo> source) {
|
||||
// for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
||||
// SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
||||
// if (sqlInfo == null) {
|
||||
// this.allTableMap.put(entry.getKey(), entry.getValue());
|
||||
// } else {
|
||||
// // 合并
|
||||
// if (sqlInfo.getSelectFields() == null) {
|
||||
// sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
||||
// } else {
|
||||
// sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
||||
// }
|
||||
// if (sqlInfo.getRealSelectFields() == null) {
|
||||
// sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
||||
// } else {
|
||||
// sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
@ -1,190 +1,190 @@
|
||||
package org.jeecg.common.util.sqlparse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class JSqlParserUtils {
|
||||
|
||||
/**
|
||||
* 解析 查询(select)sql的信息,
|
||||
* 此方法会展开所有子查询到一个map里,
|
||||
* key只存真实的表名,如果查询的没有真实的表名,则会被忽略。
|
||||
* value只存真实的字段名,如果查询的没有真实的字段名,则会被忽略。
|
||||
* <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
|
||||
* 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
|
||||
*
|
||||
* @param selectSql
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
|
||||
if (oConvertUtils.isEmpty(selectSql)) {
|
||||
return null;
|
||||
}
|
||||
// log.info("解析查询Sql:{}", selectSql);
|
||||
JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
|
||||
return allTableManager.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 查询(select)sql的信息,子查询嵌套
|
||||
*
|
||||
* @param selectSql
|
||||
* @return
|
||||
*/
|
||||
public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
|
||||
if (oConvertUtils.isEmpty(selectSql)) {
|
||||
return null;
|
||||
}
|
||||
// log.info("解析查询Sql:{}", selectSql);
|
||||
// 使用 JSqlParer 解析sql
|
||||
// 1、创建解析器
|
||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// 2、使用解析器解析sql生成具有层次结构的java类
|
||||
Statement stmt = mgr.parse(new StringReader(selectSql));
|
||||
if (stmt instanceof Select) {
|
||||
Select selectStatement = (Select) stmt;
|
||||
// 3、解析select查询sql的信息
|
||||
return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
|
||||
} else {
|
||||
// 非 select 查询sql,不做处理
|
||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 select 查询sql的信息
|
||||
*
|
||||
* @param selectBody
|
||||
* @return
|
||||
*/
|
||||
private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
||||
// 判断是否使用了union等操作
|
||||
if (selectBody instanceof SetOperationList) {
|
||||
// 如果使用了union等操作,则只解析第一个查询
|
||||
List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
||||
return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
||||
}
|
||||
// 简单的select查询
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
// 解析 aliasName
|
||||
if (fromItem.getAlias() != null) {
|
||||
sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
|
||||
}
|
||||
// 解析 表名
|
||||
if (fromItem instanceof Table) {
|
||||
// 通过表名的方式from
|
||||
Table fromTable = (Table) fromItem;
|
||||
sqlInfo.setFromTableName(fromTable.getName());
|
||||
} else if (fromItem instanceof SubSelect) {
|
||||
// 通过子查询的方式from
|
||||
SubSelect fromSubSelect = (SubSelect) fromItem;
|
||||
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
|
||||
sqlInfo.setFromSubSelect(subSqlInfo);
|
||||
}
|
||||
// 解析 selectFields
|
||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
for (SelectItem selectItem : selectItems) {
|
||||
if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
|
||||
// 全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
break;
|
||||
} else if (selectItem instanceof SelectExpressionItem) {
|
||||
// 获取单个查询字段名
|
||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
Expression expression = selectExpressionItem.getExpression();
|
||||
Alias alias = selectExpressionItem.getAlias();
|
||||
JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
|
||||
}
|
||||
}
|
||||
return sqlInfo;
|
||||
} else {
|
||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查询字段表达式
|
||||
*
|
||||
* @param sqlInfo
|
||||
* @param expression
|
||||
* @param alias 是否有别名,无传null
|
||||
*/
|
||||
private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
|
||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
if (expression instanceof Function) {
|
||||
JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
|
||||
return;
|
||||
}
|
||||
// 处理字段上的子查询
|
||||
if (expression instanceof SubSelect) {
|
||||
SubSelect subSelect = (SubSelect) expression;
|
||||
SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
|
||||
// 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
|
||||
sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
|
||||
sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
|
||||
return;
|
||||
}
|
||||
// 不处理字面量
|
||||
if (expression instanceof StringValue ||
|
||||
expression instanceof NullValue ||
|
||||
expression instanceof LongValue ||
|
||||
expression instanceof DoubleValue ||
|
||||
expression instanceof HexValue ||
|
||||
expression instanceof DateValue ||
|
||||
expression instanceof TimestampValue ||
|
||||
expression instanceof TimeValue
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询字段名
|
||||
String selectField = expression.toString();
|
||||
// 实际查询字段名
|
||||
String realSelectField = selectField;
|
||||
// 判断是否有别名
|
||||
if (alias != null) {
|
||||
selectField = alias.getName();
|
||||
}
|
||||
// 获取真实字段名
|
||||
if (expression instanceof Column) {
|
||||
Column column = (Column) expression;
|
||||
realSelectField = column.getColumnName();
|
||||
}
|
||||
sqlInfo.addSelectField(selectField, realSelectField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理函数式字段
|
||||
*
|
||||
* @param functionExp
|
||||
* @param sqlInfo
|
||||
*/
|
||||
private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
|
||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
for (Expression expression : expressions) {
|
||||
JSqlParserUtils.handleExpression(sqlInfo, expression, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//package org.jeecg.common.util.sqlparse;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import net.sf.jsqlparser.JSQLParserException;
|
||||
//import net.sf.jsqlparser.expression.*;
|
||||
//import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
//import net.sf.jsqlparser.schema.Column;
|
||||
//import net.sf.jsqlparser.schema.Table;
|
||||
//import net.sf.jsqlparser.statement.Statement;
|
||||
//import net.sf.jsqlparser.statement.select.*;
|
||||
//import org.jeecg.common.exception.JeecgBootException;
|
||||
//import org.jeecg.common.util.oConvertUtils;
|
||||
//import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
//
|
||||
//import java.io.StringReader;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
//@Slf4j
|
||||
//public class JSqlParserUtils {
|
||||
//
|
||||
// /**
|
||||
// * 解析 查询(select)sql的信息,
|
||||
// * 此方法会展开所有子查询到一个map里,
|
||||
// * key只存真实的表名,如果查询的没有真实的表名,则会被忽略。
|
||||
// * value只存真实的字段名,如果查询的没有真实的字段名,则会被忽略。
|
||||
// * <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
|
||||
// * 解析后的结果为:{sys_user=[username, realname], demo=[age], sys_depart=[]}
|
||||
// *
|
||||
// * @param selectSql
|
||||
// * @return
|
||||
// */
|
||||
// public static Map<String, SelectSqlInfo> parseAllSelectTable(String selectSql) throws JSQLParserException {
|
||||
// if (oConvertUtils.isEmpty(selectSql)) {
|
||||
// return null;
|
||||
// }
|
||||
// // log.info("解析查询Sql:{}", selectSql);
|
||||
// JSqlParserAllTableManager allTableManager = new JSqlParserAllTableManager(selectSql);
|
||||
// return allTableManager.parse();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 解析 查询(select)sql的信息,子查询嵌套
|
||||
// *
|
||||
// * @param selectSql
|
||||
// * @return
|
||||
// */
|
||||
// public static SelectSqlInfo parseSelectSqlInfo(String selectSql) throws JSQLParserException {
|
||||
// if (oConvertUtils.isEmpty(selectSql)) {
|
||||
// return null;
|
||||
// }
|
||||
// // log.info("解析查询Sql:{}", selectSql);
|
||||
// // 使用 JSqlParer 解析sql
|
||||
// // 1、创建解析器
|
||||
// CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// // 2、使用解析器解析sql生成具有层次结构的java类
|
||||
// Statement stmt = mgr.parse(new StringReader(selectSql));
|
||||
// if (stmt instanceof Select) {
|
||||
// Select selectStatement = (Select) stmt;
|
||||
// // 3、解析select查询sql的信息
|
||||
// return JSqlParserUtils.parseBySelectBody(selectStatement.getSelectBody());
|
||||
// } else {
|
||||
// // 非 select 查询sql,不做处理
|
||||
// throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 解析 select 查询sql的信息
|
||||
// *
|
||||
// * @param selectBody
|
||||
// * @return
|
||||
// */
|
||||
// private static SelectSqlInfo parseBySelectBody(SelectBody selectBody) {
|
||||
// // 判断是否使用了union等操作
|
||||
// if (selectBody instanceof SetOperationList) {
|
||||
// // 如果使用了union等操作,则只解析第一个查询
|
||||
// List<SelectBody> selectBodyList = ((SetOperationList) selectBody).getSelects();
|
||||
// return JSqlParserUtils.parseBySelectBody(selectBodyList.get(0));
|
||||
// }
|
||||
// // 简单的select查询
|
||||
// if (selectBody instanceof PlainSelect) {
|
||||
// SelectSqlInfo sqlInfo = new SelectSqlInfo(selectBody);
|
||||
// PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// FromItem fromItem = plainSelect.getFromItem();
|
||||
// // 解析 aliasName
|
||||
// if (fromItem.getAlias() != null) {
|
||||
// sqlInfo.setFromTableAliasName(fromItem.getAlias().getName());
|
||||
// }
|
||||
// // 解析 表名
|
||||
// if (fromItem instanceof Table) {
|
||||
// // 通过表名的方式from
|
||||
// Table fromTable = (Table) fromItem;
|
||||
// sqlInfo.setFromTableName(fromTable.getName());
|
||||
// } else if (fromItem instanceof SubSelect) {
|
||||
// // 通过子查询的方式from
|
||||
// SubSelect fromSubSelect = (SubSelect) fromItem;
|
||||
// SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(fromSubSelect.getSelectBody());
|
||||
// sqlInfo.setFromSubSelect(subSqlInfo);
|
||||
// }
|
||||
// // 解析 selectFields
|
||||
// List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
// for (SelectItem selectItem : selectItems) {
|
||||
// if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
|
||||
// // 全部字段
|
||||
// sqlInfo.setSelectAll(true);
|
||||
// sqlInfo.setSelectFields(null);
|
||||
// sqlInfo.setRealSelectFields(null);
|
||||
// break;
|
||||
// } else if (selectItem instanceof SelectExpressionItem) {
|
||||
// // 获取单个查询字段名
|
||||
// SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
// Expression expression = selectExpressionItem.getExpression();
|
||||
// Alias alias = selectExpressionItem.getAlias();
|
||||
// JSqlParserUtils.handleExpression(sqlInfo, expression, alias);
|
||||
// }
|
||||
// }
|
||||
// return sqlInfo;
|
||||
// } else {
|
||||
// log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
// throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理查询字段表达式
|
||||
// *
|
||||
// * @param sqlInfo
|
||||
// * @param expression
|
||||
// * @param alias 是否有别名,无传null
|
||||
// */
|
||||
// private static void handleExpression(SelectSqlInfo sqlInfo, Expression expression, Alias alias) {
|
||||
// // 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
// if (expression instanceof Function) {
|
||||
// JSqlParserUtils.handleFunctionExpression((Function) expression, sqlInfo);
|
||||
// return;
|
||||
// }
|
||||
// // 处理字段上的子查询
|
||||
// if (expression instanceof SubSelect) {
|
||||
// SubSelect subSelect = (SubSelect) expression;
|
||||
// SelectSqlInfo subSqlInfo = JSqlParserUtils.parseBySelectBody(subSelect.getSelectBody());
|
||||
// // 注:字段上的子查询,必须只查询一个字段,否则会报错,所以可以放心合并
|
||||
// sqlInfo.getSelectFields().addAll(subSqlInfo.getSelectFields());
|
||||
// sqlInfo.getRealSelectFields().addAll(subSqlInfo.getAllRealSelectFields());
|
||||
// return;
|
||||
// }
|
||||
// // 不处理字面量
|
||||
// if (expression instanceof StringValue ||
|
||||
// expression instanceof NullValue ||
|
||||
// expression instanceof LongValue ||
|
||||
// expression instanceof DoubleValue ||
|
||||
// expression instanceof HexValue ||
|
||||
// expression instanceof DateValue ||
|
||||
// expression instanceof TimestampValue ||
|
||||
// expression instanceof TimeValue
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 查询字段名
|
||||
// String selectField = expression.toString();
|
||||
// // 实际查询字段名
|
||||
// String realSelectField = selectField;
|
||||
// // 判断是否有别名
|
||||
// if (alias != null) {
|
||||
// selectField = alias.getName();
|
||||
// }
|
||||
// // 获取真实字段名
|
||||
// if (expression instanceof Column) {
|
||||
// Column column = (Column) expression;
|
||||
// realSelectField = column.getColumnName();
|
||||
// }
|
||||
// sqlInfo.addSelectField(selectField, realSelectField);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 处理函数式字段
|
||||
// *
|
||||
// * @param functionExp
|
||||
// * @param sqlInfo
|
||||
// */
|
||||
// private static void handleFunctionExpression(Function functionExp, SelectSqlInfo sqlInfo) {
|
||||
// List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
// for (Expression expression : expressions) {
|
||||
// JSqlParserUtils.handleExpression(sqlInfo, expression, null);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
@ -1,101 +1,101 @@
|
||||
package org.jeecg.common.util.sqlparse.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import net.sf.jsqlparser.statement.select.SelectBody;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* select 查询 sql 的信息
|
||||
*/
|
||||
@Data
|
||||
public class SelectSqlInfo {
|
||||
|
||||
/**
|
||||
* 查询的表名,如果是子查询,则此处为null
|
||||
*/
|
||||
private String fromTableName;
|
||||
/**
|
||||
* 表别名
|
||||
*/
|
||||
private String fromTableAliasName;
|
||||
/**
|
||||
* 通过子查询获取的表信息,例如:select name from (select * from user) u
|
||||
* 如果不是子查询,则为null
|
||||
*/
|
||||
private SelectSqlInfo fromSubSelect;
|
||||
/**
|
||||
* 查询的字段集合,如果是 * 则为null,如果设了别名则为别名
|
||||
*/
|
||||
private Set<String> selectFields;
|
||||
/**
|
||||
* 真实的查询字段集合,如果是 * 则为null,如果设了别名则为原始字段名
|
||||
*/
|
||||
private Set<String> realSelectFields;
|
||||
/**
|
||||
* 是否是查询所有字段
|
||||
*/
|
||||
private boolean selectAll;
|
||||
|
||||
/**
|
||||
* 解析之后的 SQL (关键字都是大写)
|
||||
*/
|
||||
private final String parsedSql;
|
||||
|
||||
public SelectSqlInfo(String parsedSql) {
|
||||
this.parsedSql = parsedSql;
|
||||
}
|
||||
|
||||
public SelectSqlInfo(SelectBody selectBody) {
|
||||
this.parsedSql = selectBody.toString();
|
||||
}
|
||||
|
||||
public void addSelectField(String selectField, String realSelectField) {
|
||||
if (this.selectFields == null) {
|
||||
this.selectFields = new HashSet<>();
|
||||
}
|
||||
if (this.realSelectFields == null) {
|
||||
this.realSelectFields = new HashSet<>();
|
||||
}
|
||||
this.selectFields.add(selectField);
|
||||
this.realSelectFields.add(realSelectField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有字段,包括子查询里的。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<String> getAllRealSelectFields() {
|
||||
Set<String> fields = new HashSet<>();
|
||||
// 递归获取所有字段,起个直观的方法名为:
|
||||
this.recursiveGetAllFields(this, fields);
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有字段
|
||||
*/
|
||||
private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
|
||||
if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
|
||||
fields.addAll(sqlInfo.getRealSelectFields());
|
||||
}
|
||||
if (sqlInfo.getFromSubSelect() != null) {
|
||||
recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SelectSqlInfo{" +
|
||||
"fromTableName='" + fromTableName + '\'' +
|
||||
", fromSubSelect=" + fromSubSelect +
|
||||
", aliasName='" + fromTableAliasName + '\'' +
|
||||
", selectFields=" + selectFields +
|
||||
", realSelectFields=" + realSelectFields +
|
||||
", selectAll=" + selectAll +
|
||||
"}";
|
||||
}
|
||||
|
||||
}
|
||||
//package org.jeecg.common.util.sqlparse.vo;
|
||||
//
|
||||
//import lombok.Data;
|
||||
//import net.sf.jsqlparser.statement.select.SelectBody;
|
||||
//
|
||||
//import java.util.HashSet;
|
||||
//import java.util.Set;
|
||||
//
|
||||
///**
|
||||
// * select 查询 sql 的信息
|
||||
// */
|
||||
//@Data
|
||||
//public class SelectSqlInfo {
|
||||
//
|
||||
// /**
|
||||
// * 查询的表名,如果是子查询,则此处为null
|
||||
// */
|
||||
// private String fromTableName;
|
||||
// /**
|
||||
// * 表别名
|
||||
// */
|
||||
// private String fromTableAliasName;
|
||||
// /**
|
||||
// * 通过子查询获取的表信息,例如:select name from (select * from user) u
|
||||
// * 如果不是子查询,则为null
|
||||
// */
|
||||
// private SelectSqlInfo fromSubSelect;
|
||||
// /**
|
||||
// * 查询的字段集合,如果是 * 则为null,如果设了别名则为别名
|
||||
// */
|
||||
// private Set<String> selectFields;
|
||||
// /**
|
||||
// * 真实的查询字段集合,如果是 * 则为null,如果设了别名则为原始字段名
|
||||
// */
|
||||
// private Set<String> realSelectFields;
|
||||
// /**
|
||||
// * 是否是查询所有字段
|
||||
// */
|
||||
// private boolean selectAll;
|
||||
//
|
||||
// /**
|
||||
// * 解析之后的 SQL (关键字都是大写)
|
||||
// */
|
||||
// private final String parsedSql;
|
||||
//
|
||||
// public SelectSqlInfo(String parsedSql) {
|
||||
// this.parsedSql = parsedSql;
|
||||
// }
|
||||
//
|
||||
// public SelectSqlInfo(SelectBody selectBody) {
|
||||
// this.parsedSql = selectBody.toString();
|
||||
// }
|
||||
//
|
||||
// public void addSelectField(String selectField, String realSelectField) {
|
||||
// if (this.selectFields == null) {
|
||||
// this.selectFields = new HashSet<>();
|
||||
// }
|
||||
// if (this.realSelectFields == null) {
|
||||
// this.realSelectFields = new HashSet<>();
|
||||
// }
|
||||
// this.selectFields.add(selectField);
|
||||
// this.realSelectFields.add(realSelectField);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取所有字段,包括子查询里的。
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public Set<String> getAllRealSelectFields() {
|
||||
// Set<String> fields = new HashSet<>();
|
||||
// // 递归获取所有字段,起个直观的方法名为:
|
||||
// this.recursiveGetAllFields(this, fields);
|
||||
// return fields;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 递归获取所有字段
|
||||
// */
|
||||
// private void recursiveGetAllFields(SelectSqlInfo sqlInfo, Set<String> fields) {
|
||||
// if (!sqlInfo.isSelectAll() && sqlInfo.getRealSelectFields() != null) {
|
||||
// fields.addAll(sqlInfo.getRealSelectFields());
|
||||
// }
|
||||
// if (sqlInfo.getFromSubSelect() != null) {
|
||||
// recursiveGetAllFields(sqlInfo.getFromSubSelect(), fields);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String toString() {
|
||||
// return "SelectSqlInfo{" +
|
||||
// "fromTableName='" + fromTableName + '\'' +
|
||||
// ", fromSubSelect=" + fromSubSelect +
|
||||
// ", aliasName='" + fromTableAliasName + '\'' +
|
||||
// ", selectFields=" + selectFields +
|
||||
// ", realSelectFields=" + realSelectFields +
|
||||
// ", 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.LocalTimeSerializer;
|
||||
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
@ -59,6 +58,14 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
||||
@Autowired(required = false)
|
||||
private PrometheusMeterRegistry prometheusMeterRegistry;
|
||||
|
||||
/**
|
||||
* meterRegistryPostProcessor
|
||||
* for [QQYUN-12558]【监控】系统监控的头两个tab不好使,接口404
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
@Qualifier("meterRegistryPostProcessor")
|
||||
private BeanPostProcessor meterRegistryPostProcessor;
|
||||
|
||||
/**
|
||||
* 静态资源的配置 - 使得可以从磁盘中读取 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
|
||||
@ConditionalOnBean(name = "meterRegistryPostProcessor")
|
||||
InitializingBean forcePrometheusPostProcessor(BeanPostProcessor meterRegistryPostProcessor) {
|
||||
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "");
|
||||
@EventListener
|
||||
public void onApplicationReady(ApplicationReadyEvent event) {
|
||||
if(null != meterRegistryPostProcessor){
|
||||
meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "");
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
|
||||
@ -6,15 +6,12 @@ import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@ -70,6 +67,9 @@ public class LowCodeModeInterceptor implements HandlerInterceptor {
|
||||
Set<String> hasRoles = null;
|
||||
if (loginUser == null) {
|
||||
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
||||
}
|
||||
|
||||
if (loginUser != null) {
|
||||
//当前登录人拥有的角色
|
||||
hasRoles = commonAPI.queryUserRolesById(loginUser.getId());
|
||||
}
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
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>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-aiflow-jdk8</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<artifactId>jeecg-aiflow</artifactId>
|
||||
<version>1.0.4</version>
|
||||
</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 -->
|
||||
<dependency>
|
||||
|
||||
@ -24,6 +24,12 @@
|
||||
<artifactId>hibernate-re</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AI大模型管理 -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-module-airag</artifactId>
|
||||
<version>${jeecgboot.version}</version>
|
||||
</dependency>
|
||||
<!-- 企业微信/钉钉 api -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
|
||||
@ -4,14 +4,14 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
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.firewall.SqlInjection.IDictTableWhiteListHandler;
|
||||
import org.jeecg.config.firewall.interceptor.LowCodeModeInterceptor;
|
||||
import org.jeecg.modules.system.entity.SysTableWhiteList;
|
||||
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
|
||||
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.stereotype.Component;
|
||||
|
||||
@ -65,7 +65,7 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler
|
||||
public boolean isPassBySql(String sql) {
|
||||
Map<String, SelectSqlInfo> parsedMap = null;
|
||||
try {
|
||||
parsedMap = JSqlParserUtils.parseAllSelectTable(sql);
|
||||
parsedMap = MiniDaoUtil.parseAllSelectTable(sql);
|
||||
} catch (Exception e) {
|
||||
log.warn("校验sql语句,解析报错:{}", e.getMessage());
|
||||
}
|
||||
@ -127,7 +127,7 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler
|
||||
log.info("字典拼接的查询SQL:{}", sql);
|
||||
try {
|
||||
// 进行SQL解析
|
||||
JSqlParserUtils.parseSelectSqlInfo(sql);
|
||||
MiniDaoUtil.parseSelectSqlInfo(sql);
|
||||
} catch (Exception e) {
|
||||
// 如果SQL解析失败,则通过字段名和表名进行校验
|
||||
return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields)));
|
||||
|
||||
@ -19,6 +19,21 @@ public class CustomInMemoryHttpTraceRepository extends InMemoryHttpTraceReposito
|
||||
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) {
|
||||
List<HttpTrace> allTrace = super.findAll();
|
||||
if (null != allTrace && !allTrace.isEmpty()) {
|
||||
|
||||
@ -78,7 +78,9 @@ public class CustomUndertowMetricsHandler {
|
||||
// 获取当前 session,如果不存在则创建
|
||||
Session session = sessionManager.getSession(exchange, sessionConfig);
|
||||
if (session == null) {
|
||||
sessionManager.createSession(exchange, sessionConfig);
|
||||
try {
|
||||
sessionManager.createSession(exchange, sessionConfig);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
// 执行下一个 Handler
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package org.jeecg.modules.openapi.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.query.QueryGenerator;
|
||||
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.OpenApiAuth;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
||||
import org.jeecg.modules.openapi.generator.PathGenerator;
|
||||
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.swagger.*;
|
||||
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.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:11
|
||||
@ -44,9 +48,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private OpenApiParamService openApiParamService;
|
||||
@Autowired
|
||||
private OpenApiHeaderService openApiHeaderService;
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
@Autowired
|
||||
@ -67,10 +69,6 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
QueryWrapper<OpenApi> queryWrapper = QueryGenerator.initQueryWrapper(openApi, req.getParameterMap());
|
||||
Page<OpenApi> page = new Page<>(pageNo, pageSize);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -82,16 +80,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody OpenApi openApi) {
|
||||
if (service.save(openApi)) {
|
||||
if (!CollectionUtils.isEmpty(openApi.getHeaders())) {
|
||||
openApiHeaderService.saveBatch(openApi.getHeaders());
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(openApi.getParams())) {
|
||||
openApiParamService.saveBatch(openApi.getParams());
|
||||
}
|
||||
}
|
||||
|
||||
service.save(openApi);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
@ -103,18 +92,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody OpenApi openApi) {
|
||||
if (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());
|
||||
}
|
||||
}
|
||||
service.updateById(openApi);
|
||||
return Result.ok("修改成功!");
|
||||
|
||||
}
|
||||
@ -170,27 +148,72 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
result.put("data", null);
|
||||
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 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");
|
||||
OpenApiAuth openApiAuth = openApiAuthService.getByAppkey(appkey);
|
||||
SysUser systemUser = sysUserService.getById(openApiAuth.getSystemUserId());
|
||||
String token = JwtUtil.sign(systemUser.getUsername(), systemUser.getPassword());
|
||||
SysUser systemUser = sysUserService.getUserByName(openApiAuth.getCreateBy());
|
||||
String token = this.getToken(systemUser.getUsername(), systemUser.getPassword());
|
||||
httpHeaders.put("X-Access-Token", Lists.newArrayList(token));
|
||||
|
||||
httpHeaders.put("Content-Type",Lists.newArrayList("application/json"));
|
||||
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")
|
||||
public SwaggerModel swaggerModel() {
|
||||
|
||||
@ -230,13 +253,16 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
definition.setType("object");
|
||||
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
||||
definition.setProperties(definitionProperties);
|
||||
|
||||
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
||||
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
||||
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
||||
swaggerDefinitionProperties.setType("string");
|
||||
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
|
||||
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
||||
if (openApi.getBody()!=null){
|
||||
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
||||
if (jsonObject.size()>0){
|
||||
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
||||
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
||||
swaggerDefinitionProperties.setType("string");
|
||||
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
|
||||
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
List<SwaggerOperationParameter> parameters = new ArrayList<>();
|
||||
|
||||
for (OpenApiParam openApiParam : openApiParamService.findByApiId(openApi.getId())) {
|
||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||
parameter.setIn("path");
|
||||
parameter.setName(openApiParam.getParamKey());
|
||||
parameter.setRequired(openApiParam.getRequired() == 1);
|
||||
parameter.setDescription(openApiParam.getNote());
|
||||
parameters.add(parameter);
|
||||
if (openApi.getParamsJson()!=null) {
|
||||
List<OpenApiParam> openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
|
||||
for (OpenApiParam openApiParam : openApiParams) {
|
||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||
parameter.setIn("path");
|
||||
parameter.setName(openApiParam.getParamKey());
|
||||
parameter.setRequired(openApiParam.getRequired() == 1);
|
||||
parameter.setDescription(openApiParam.getNote());
|
||||
parameters.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
for (OpenApiHeader openApiHeader : openApiHeaderService.findByApiId(openApi.getId())) {
|
||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||
parameter.setIn("header");
|
||||
parameter.setName(openApiHeader.getHeaderKey());
|
||||
parameter.setRequired(openApiHeader.getRequired() == 1);
|
||||
parameter.setDescription(openApiHeader.getNote());
|
||||
parameters.add(parameter);
|
||||
if (openApi.getHeadersJson()!=null) {
|
||||
List<OpenApiHeader> openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
|
||||
for (OpenApiHeader openApiHeader : openApiHeaders) {
|
||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||
parameter.setIn("header");
|
||||
parameter.setName(openApiHeader.getHeaderKey());
|
||||
parameter.setRequired(openApiHeader.getRequired() == 1);
|
||||
parameter.setDescription(openApiHeader.getNote());
|
||||
parameters.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
operation.setParameters(parameters);
|
||||
}
|
||||
|
||||
@ -353,7 +382,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||
SwaggerInfo info = new SwaggerInfo();
|
||||
|
||||
info.setDescription("OpenAPI 接口列表");
|
||||
info.setVersion("3.7.1");
|
||||
info.setVersion("3.8.0");
|
||||
info.setTitle("OpenAPI 接口列表");
|
||||
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.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiRecord;
|
||||
import org.jeecg.modules.openapi.service.OpenApiRecordService;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -18,47 +18,47 @@ import java.util.Arrays;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/openapi/record")
|
||||
public class OpenApiRecordController extends JeecgController<OpenApiRecord, OpenApiRecordService> {
|
||||
public class OpenApiLogController extends JeecgController<OpenApiLog, OpenApiLogService> {
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param openApiRecord
|
||||
* @param OpenApiLog
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@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) {
|
||||
QueryWrapper<OpenApiRecord> queryWrapper = QueryGenerator.initQueryWrapper(openApiRecord, req.getParameterMap());
|
||||
Page<OpenApiRecord> page = new Page<>(pageNo, pageSize);
|
||||
IPage<OpenApiRecord> pageList = service.page(page, queryWrapper);
|
||||
QueryWrapper<OpenApiLog> queryWrapper = QueryGenerator.initQueryWrapper(OpenApiLog, req.getParameterMap());
|
||||
Page<OpenApiLog> page = new Page<>(pageNo, pageSize);
|
||||
IPage<OpenApiLog> pageList = service.page(page, queryWrapper);
|
||||
return Result.ok(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param openApiRecord
|
||||
* @param OpenApiLog
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody OpenApiRecord openApiRecord) {
|
||||
service.save(openApiRecord);
|
||||
public Result<?> add(@RequestBody OpenApiLog OpenApiLog) {
|
||||
service.save(OpenApiLog);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param openApiRecord
|
||||
* @param OpenApiLog
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody OpenApiRecord openApiRecord) {
|
||||
service.updateById(openApiRecord);
|
||||
public Result<?> edit(@RequestBody OpenApiLog OpenApiLog) {
|
||||
service.updateById(OpenApiLog);
|
||||
return Result.ok("修改成功!");
|
||||
|
||||
}
|
||||
@ -96,7 +96,7 @@ public class OpenApiRecordController extends JeecgController<OpenApiRecord, Open
|
||||
*/
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
OpenApiRecord openApiRecord = service.getById(id);
|
||||
return Result.ok(openApiRecord);
|
||||
OpenApiLog OpenApiLog = service.getById(id);
|
||||
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.modules.openapi.entity.OpenApiPermission;
|
||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/openapi/permission")
|
||||
@ -15,6 +12,11 @@ public class OpenApiPermissionController extends JeecgController<OpenApiPermissi
|
||||
|
||||
@PostMapping("add")
|
||||
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 黑名单
|
||||
*/
|
||||
private String blackList;
|
||||
|
||||
/**
|
||||
* 请求头列表
|
||||
* 请求头json
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<OpenApiHeader> headers;
|
||||
|
||||
private String headersJson;
|
||||
/**
|
||||
* 请求参数列表
|
||||
* 请求参数json
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<OpenApiParam> params;
|
||||
private String paramsJson;
|
||||
|
||||
|
||||
/**
|
||||
* 目前仅支持json
|
||||
@ -100,4 +97,9 @@ public class OpenApi implements Serializable {
|
||||
* 更新时间
|
||||
*/
|
||||
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.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
@ -44,6 +45,7 @@ public class OpenApiAuth implements Serializable {
|
||||
/**
|
||||
* 系统用户ID
|
||||
*/
|
||||
@Dict(dictTable = "sys_user",dicCode = "id",dicText = "username")
|
||||
private String systemUserId;
|
||||
|
||||
/**
|
||||
|
||||
@ -18,16 +18,6 @@ import java.io.Serializable;
|
||||
public class OpenApiHeader implements Serializable {
|
||||
private static final long serialVersionUID = 5032708503120184683L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 接口ID
|
||||
*/
|
||||
private String apiId;
|
||||
|
||||
/**
|
||||
* key
|
||||
|
||||
@ -16,7 +16,7 @@ import java.util.Date;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiRecord implements Serializable {
|
||||
public class OpenApiLog implements Serializable {
|
||||
private static final long serialVersionUID = -5870384488947863579L;
|
||||
|
||||
/**
|
||||
@ -17,16 +17,6 @@ import java.io.Serializable;
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiParam implements Serializable {
|
||||
private static final long serialVersionUID = -6174831468578022357L;
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 接口ID
|
||||
*/
|
||||
private String apiId;
|
||||
|
||||
/**
|
||||
* key
|
||||
|
||||
@ -4,11 +4,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
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.OpenApiRecord;
|
||||
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.OpenApiRecordService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
@ -27,7 +27,7 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class ApiAuthFilter implements Filter {
|
||||
|
||||
private OpenApiRecordService openApiRecordService;
|
||||
private OpenApiLogService openApiLogService;
|
||||
private OpenApiAuthService openApiAuthService;
|
||||
private OpenApiPermissionService openApiPermissionService;
|
||||
private OpenApiService openApiService;
|
||||
@ -61,13 +61,13 @@ public class ApiAuthFilter implements Filter {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
OpenApiRecord record = new OpenApiRecord();
|
||||
record.setApiId(openApi.getId());
|
||||
record.setCallAuthId(openApiAuth.getId());
|
||||
record.setCallTime(callTime);
|
||||
record.setUsedTime(endTime - startTime);
|
||||
record.setResponseTime(new Date());
|
||||
openApiRecordService.save(record);
|
||||
OpenApiLog openApiLog = new OpenApiLog();
|
||||
openApiLog.setApiId(openApi.getId());
|
||||
openApiLog.setCallAuthId(openApiAuth.getId());
|
||||
openApiLog.setCallTime(callTime);
|
||||
openApiLog.setUsedTime(endTime - startTime);
|
||||
openApiLog.setResponseTime(new Date());
|
||||
openApiLogService.save(openApiLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -75,7 +75,7 @@ public class ApiAuthFilter implements Filter {
|
||||
ServletContext servletContext = filterConfig.getServletContext();
|
||||
WebApplicationContext applicationContext = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
|
||||
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.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 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
|
||||
*/
|
||||
@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;
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
|
||||
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 java.util.List;
|
||||
@ -10,4 +12,8 @@ import java.util.List;
|
||||
*/
|
||||
public interface OpenApiPermissionService extends IService<OpenApiPermission> {
|
||||
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;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
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.mapper.OpenApiPermissionMapper;
|
||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @date 2024/12/19 17:44
|
||||
*/
|
||||
@Service
|
||||
public class OpenApiPermissionServiceImpl extends ServiceImpl<OpenApiPermissionMapper, OpenApiPermission> implements OpenApiPermissionService {
|
||||
@Resource
|
||||
private OpenApiService openApiService;
|
||||
@Override
|
||||
public List<OpenApiPermission> findByAuthId(String 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.JeecgBootException;
|
||||
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.mybatis.MybatisPlusSaasConfig;
|
||||
import org.jeecg.modules.system.entity.*;
|
||||
@ -741,6 +744,7 @@ public class ThirdAppDingtalkServiceImpl implements IThirdAppService {
|
||||
}
|
||||
sysDepart.setDepartName(department.getName());
|
||||
sysDepart.setDepartOrder(department.getOrder());
|
||||
sysDepart.setDingIdentifier(department.getSource_identifier());
|
||||
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>
|
||||
</dependency>
|
||||
|
||||
<!-- AI大模型管理 -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-module-airag</artifactId>
|
||||
<version>${jeecgboot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- flyway 数据库自动升级 -->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
|
||||
@ -40,8 +40,8 @@ public class JeecgSystemApplication extends SpringBootServletInitializer {
|
||||
String path = oConvertUtils.getString(env.getProperty("server.servlet.context-path"));
|
||||
log.info("\n----------------------------------------------------------\n\t" +
|
||||
"Application Jeecg-Boot is running! Access URLs:\n\t" +
|
||||
"Local: \t\thttp://localhost:" + port + path + "/\n\t" +
|
||||
"External: \thttp://" + ip + ":" + port + path + "/\n\t" +
|
||||
"Local: \t\thttp://localhost:" + port + path + "/doc.html\n\t" +
|
||||
"External: \thttp://" + ip + ":" + port + path + "/doc.html\n\t" +
|
||||
"Swagger文档: \thttp://" + ip + ":" + port + path + "/doc.html\n" +
|
||||
"----------------------------------------------------------");
|
||||
|
||||
|
||||
@ -40,12 +40,12 @@ spring:
|
||||
username: jeecgos@163.com
|
||||
password: ??
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
mail.smtp.timeout: 10000 # 连接超时(毫秒)
|
||||
mail.smtp.connectiontimeout: 10000 # 连接超时(毫秒)
|
||||
mail.smtp.writetimeout: 10000 # 写入超时(毫秒)
|
||||
mail.smtp.auth: true
|
||||
smtp.ssl.enable: true
|
||||
mail.debug: true # 启用调试模式(查看详细日志)
|
||||
## quartz定时任务,采用数据库方式
|
||||
quartz:
|
||||
job-store-type: jdbc
|
||||
|
||||
@ -38,12 +38,8 @@ spring:
|
||||
username: jeecgos@163.com
|
||||
password: ??
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
mail.smtp.auth: true
|
||||
smtp.ssl.enable: true
|
||||
## quartz定时任务,采用数据库方式
|
||||
quartz:
|
||||
job-store-type: jdbc
|
||||
|
||||
@ -38,12 +38,8 @@ spring:
|
||||
username: jeecgos@163.com
|
||||
password: ??
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
mail.smtp.auth: true
|
||||
smtp.ssl.enable: true
|
||||
## quartz定时任务,采用数据库方式
|
||||
quartz:
|
||||
job-store-type: jdbc
|
||||
|
||||
@ -36,12 +36,8 @@ spring:
|
||||
username: jeecgos@163.com
|
||||
password: ??
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
mail.smtp.auth: true
|
||||
smtp.ssl.enable: true
|
||||
## quartz定时任务,采用数据库方式
|
||||
quartz:
|
||||
job-store-type: jdbc
|
||||
|
||||
@ -38,12 +38,9 @@ spring:
|
||||
username: jeecgos@163.com
|
||||
password: ??
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
mail.smtp.auth: true
|
||||
smtp.ssl.enable: true
|
||||
mail.debug: true # 启用调试模式(查看详细日志)
|
||||
## quartz定时任务,采用数据库方式
|
||||
quartz:
|
||||
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;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import springfox.documentation.swagger.web.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** 已使用knife4j-gateway支持该功能
|
||||
@ -19,6 +21,9 @@ import java.util.List;
|
||||
public class SwaggerResourceController {
|
||||
private MySwaggerResourceProvider swaggerResourceProvider;
|
||||
|
||||
@Value("${knife4j.gateway.enabled:true}")
|
||||
private Boolean enableSwagger;
|
||||
|
||||
@Autowired
|
||||
public SwaggerResourceController(MySwaggerResourceProvider swaggerResourceProvider) {
|
||||
this.swaggerResourceProvider = swaggerResourceProvider;
|
||||
@ -36,6 +41,9 @@ public class SwaggerResourceController {
|
||||
|
||||
@RequestMapping
|
||||
public ResponseEntity<List<SwaggerResource>> swaggerResources() {
|
||||
if (!enableSwagger) {
|
||||
return new ResponseEntity<>(new ArrayList<>(), HttpStatus.OK);
|
||||
}
|
||||
return new ResponseEntity<>(swaggerResourceProvider.get(), HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@ -4,11 +4,6 @@ server:
|
||||
knife4j:
|
||||
gateway:
|
||||
enabled: true
|
||||
strategy: discover
|
||||
discover:
|
||||
excluded-services: ${spring.application.name}
|
||||
enabled: true
|
||||
version: OpenAPI3
|
||||
|
||||
spring:
|
||||
application:
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<properties>
|
||||
<jeecgboot.version>3.8.0</jeecgboot.version>
|
||||
<!-- JDK版本支持17和1.8 -->
|
||||
<java.version>1.8</java.version>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- 微服务 -->
|
||||
@ -40,7 +40,7 @@
|
||||
<seata.version>1.5.2</seata.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>
|
||||
<pegdown.version>1.6.0</pegdown.version>
|
||||
<commonmark.version>0.17.0</commonmark.version>
|
||||
@ -56,7 +56,8 @@
|
||||
<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>
|
||||
<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>
|
||||
<!-- shiro -->
|
||||
<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>
|
||||
<java-jwt.version>4.5.0</java-jwt.version>
|
||||
<codegenerate.version>1.4.9</codegenerate.version>
|
||||
<autopoi-web.version>1.4.11</autopoi-web.version>
|
||||
<minio.version>8.0.3</minio.version>
|
||||
@ -254,7 +255,7 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>hibernate-re</artifactId>
|
||||
<version>3.8.0-GA</version>
|
||||
<version>3.8.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--mongon db-->
|
||||
@ -359,7 +360,7 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>weixin4j</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<version>2.0.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
@ -383,6 +384,22 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</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>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
@ -418,7 +435,7 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-nosql-starter</artifactId>
|
||||
<version>${jimureport-spring-boot-starter.version}</version>
|
||||
<version>1.9.5.2</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.calcite</groupId>
|
||||
@ -430,13 +447,13 @@
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimubi-spring-boot-starter</artifactId>
|
||||
<version>1.9.4</version>
|
||||
<version>1.9.5</version>
|
||||
</dependency>
|
||||
<!-- AI集成 -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<version>3.8.0.2</version>
|
||||
</dependency>
|
||||
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
|
||||
<!--mysql5.6,需要把版本号改成5.2.1-->
|
||||
|
||||
1
jeecgboot-vue3/.gitignore
vendored
1
jeecgboot-vue3/.gitignore
vendored
@ -2,7 +2,6 @@ node_modules
|
||||
.DS_Store
|
||||
.github
|
||||
dist
|
||||
.npmrc
|
||||
.cache
|
||||
|
||||
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",
|
||||
"showdown": "^2.1.0",
|
||||
"sortablejs": "^1.15.6",
|
||||
"swagger-ui-dist": "^5.21.0",
|
||||
"tinymce": "6.6.2",
|
||||
"vditor": "^3.10.8",
|
||||
"vue": "^3.5.13",
|
||||
@ -77,8 +78,9 @@
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-types": "^5.1.3",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-table": "4.6.17",
|
||||
"vxe-table-plugin-antd": "4.0.7",
|
||||
"vxe-table": "4.13.31",
|
||||
"vxe-table-plugin-antd": "4.0.8",
|
||||
"vxe-pc-ui": "4.6.12",
|
||||
"xe-utils": "3.5.26",
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<Icon :icon="preIcon" v-if="preIcon" :size="iconSize" />
|
||||
<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 JSearchSelect } from './src/jeecg/components/JSearchSelect.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 JImageUpload } from './src/jeecg/components/JImageUpload.vue';
|
||||
// Jeecg自定义校验
|
||||
|
||||
@ -64,6 +64,7 @@ import JInput from './jeecg/components/JInput.vue';
|
||||
import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
||||
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
||||
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
||||
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
|
||||
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
||||
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
||||
import JAddInput from './jeecg/components/JAddInput.vue';
|
||||
@ -159,6 +160,7 @@ componentMap.set('JInput', JInput);
|
||||
componentMap.set('JTreeSelect', JTreeSelect);
|
||||
componentMap.set('JEllipsis', JEllipsis);
|
||||
componentMap.set('JSelectUserByDept', JSelectUserByDept);
|
||||
componentMap.set('JSelectUserByDepartment', JSelectUserByDepartment);
|
||||
componentMap.set('JUpload', JUpload);
|
||||
componentMap.set('JSearchSelect', JSearchSelect);
|
||||
componentMap.set('JAddInput', JAddInput);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!--职务选择组件-->
|
||||
<template>
|
||||
<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组件的样式 -->
|
||||
<a-form-item>
|
||||
<PositionSelectModal @register="regModal" @getSelectResult="setValue" v-bind="getBindValue"></PositionSelectModal>
|
||||
@ -51,7 +51,7 @@
|
||||
//下拉框选项值
|
||||
const selectOptions = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues = reactive<object>({
|
||||
let selectValues = reactive<any>({
|
||||
value: [],
|
||||
change: false,
|
||||
});
|
||||
@ -70,9 +70,23 @@
|
||||
/**
|
||||
* 监听组件值
|
||||
*/
|
||||
watchEffect(() => {
|
||||
props.value && initValue();
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20250423---for:【pull/8014】插槽方式弹窗中取消该数据checkbox的选中状态,需要点击第二次才生效。
|
||||
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变化
|
||||
|
||||
@ -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 { TreeSelect } from 'ant-design-vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { isObject } from '/@/utils/is';
|
||||
import { isObject, isArray } from '/@/utils/is';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
enum Api {
|
||||
url = '/sys/dict/loadTreeData',
|
||||
@ -143,6 +143,23 @@
|
||||
if(props.url){
|
||||
getItemFromTreeData();
|
||||
}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 result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false });
|
||||
if (result.success) {
|
||||
@ -291,7 +308,22 @@
|
||||
} else {
|
||||
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) {
|
||||
|
||||
@ -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 }
|
||||
);
|
||||
@ -109,7 +111,9 @@ export function useSelectBiz(getList, props, emit?) {
|
||||
code: selectValues['value'].join(','),
|
||||
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;
|
||||
}
|
||||
|
||||
@ -118,6 +122,9 @@ export function useSelectBiz(getList, props, emit?) {
|
||||
*/
|
||||
async function visibleChange(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();
|
||||
} else {
|
||||
|
||||
@ -2,6 +2,7 @@ import type { Ref } from 'vue';
|
||||
import { inject, reactive, ref, computed, unref, watch, nextTick } from 'vue';
|
||||
import { TreeActionType } from '/@/components/Tree';
|
||||
import { listToTree } from '/@/utils/common/compUtils';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
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));
|
||||
// 是否是首次加载回显,只有首次加载,才会显示 loading
|
||||
let isFirstLoadEcho = true;
|
||||
|
||||
let prevSelectValues = [];
|
||||
/**
|
||||
* 监听selectValues变化
|
||||
*/
|
||||
@ -32,12 +33,17 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
if(!values){
|
||||
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;
|
||||
isFirstLoadEcho = false;
|
||||
onLoadData(null, values.join(',')).finally(() => {
|
||||
loadingEcho.value = false;
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20250604---for:【issues/8232】代码设置JSelectDept组件值没翻译
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@ -139,6 +139,7 @@ export type ComponentType =
|
||||
| 'JTreeSelect'
|
||||
| 'JEllipsis'
|
||||
| 'JSelectUserByDept'
|
||||
| 'JSelectUserByDepartment'
|
||||
| 'JUpload'
|
||||
| 'JSearchSelect'
|
||||
| 'JAddInput'
|
||||
|
||||
@ -222,7 +222,7 @@
|
||||
// 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, {
|
||||
setSelectedRowKeys,
|
||||
|
||||
@ -371,9 +371,7 @@
|
||||
|
||||
if (props.column.dataIndex) {
|
||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||
// update-begin--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
||||
props.record.editValueRefs[props.column.dataIndex] = unref(currentValueRef);
|
||||
// update-end--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
||||
props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
|
||||
}
|
||||
/* eslint-disable */
|
||||
props.record.onCancelEdit = () => {
|
||||
|
||||
@ -73,8 +73,6 @@ export type EditRecordRow<T = Recordable> = Partial<
|
||||
submitCbs: Cbs[];
|
||||
cancelCbs: Cbs[];
|
||||
validCbs: Cbs[];
|
||||
// update-begin--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
||||
editValueRefs: Recordable;
|
||||
// update-end--author:liaozhiyang---date:20250206---for:【issues/7709】当dataSource是响应式时,单元格编辑输入会自动关闭
|
||||
editValueRefs: Recordable<Ref>;
|
||||
} & T
|
||||
>;
|
||||
|
||||
@ -15,7 +15,8 @@ export function useTableScroll(
|
||||
columnsRef: ComputedRef<BasicColumn[]>,
|
||||
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
|
||||
getDataSourceRef: ComputedRef<Recordable[]>,
|
||||
slots: Slots
|
||||
slots: Slots,
|
||||
getPaginationInfo: ComputedRef<any>
|
||||
) {
|
||||
const tableHeightRef: Ref<Nullable<number>> = ref(null);
|
||||
|
||||
@ -138,12 +139,33 @@ export function useTableScroll(
|
||||
// 10+6(外层边距padding:10 + 内层padding-bottom:6)
|
||||
height -= 16;
|
||||
// update-end--author:liaozhiyang---date:20240603---for:【TV360X-861】列表查询区域不可往上滚动
|
||||
|
||||
|
||||
height = (height < minHeight! ? (minHeight as number) : height) ?? height;
|
||||
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
||||
setHeight(height);
|
||||
|
||||
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);
|
||||
onMountedOrActivated(() => {
|
||||
|
||||
@ -7,6 +7,8 @@ interface PaginationRenderProps {
|
||||
originalElement: any;
|
||||
}
|
||||
|
||||
type Position = 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
|
||||
|
||||
export declare class PaginationConfig extends Pagination {
|
||||
position?: 'top' | 'bottom' | 'both';
|
||||
}
|
||||
@ -96,4 +98,11 @@ export interface PaginationProps {
|
||||
* @type Function
|
||||
*/
|
||||
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没更新
|
||||
// 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没更新
|
||||
let columns: JVxeColumn[] = [];
|
||||
if (isArray(props.columns)) {
|
||||
|
||||
@ -18,10 +18,10 @@ export function useData(props: JVxeTableProps): JVxeDataProps {
|
||||
// rowId: props.rowKey,
|
||||
rowConfig: {
|
||||
keyField: props.rowKey,
|
||||
// 高亮hover的行
|
||||
isHover: true,
|
||||
},
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-327】vxetable警告
|
||||
// 高亮hover的行
|
||||
highlightHoverRow: true,
|
||||
|
||||
// --- 【issues/209】自带的tooltip会错位,所以替换成原生的title ---
|
||||
// 溢出隐藏并显示tooltip
|
||||
@ -41,8 +41,9 @@ export function useData(props: JVxeTableProps): JVxeDataProps {
|
||||
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
},
|
||||
expandConfig: {
|
||||
iconClose: 'ant-table-row-expand-icon ant-table-row-expand-icon-collapsed',
|
||||
iconOpen: 'ant-table-row-expand-icon ant-table-row-expand-icon-expanded',
|
||||
iconClose: 'vxe-icon-arrow-right',
|
||||
iconOpen: 'vxe-icon-arrow-down',
|
||||
...props.expandConfig,
|
||||
},
|
||||
// 虚拟滚动配置,y轴大于xx条数据时启用虚拟滚动
|
||||
scrollY: {
|
||||
|
||||
@ -23,7 +23,12 @@ export function useDragSort(props: JVxeTableProps, methods: JVxeTableMethods) {
|
||||
function createSortable() {
|
||||
let xTable = methods.getXTable();
|
||||
// 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 = [];
|
||||
sortable2 = Sortable.create(dom as HTMLElement, {
|
||||
handle: '.drag-btn',
|
||||
|
||||
@ -424,12 +424,13 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
|
||||
let xTable = getXTable();
|
||||
let { setActive, index } = options;
|
||||
index = index === -1 ? index : xTable.internalData.tableFullData[index];
|
||||
index = index == null ? -1 : index;
|
||||
// 插入行
|
||||
let result = await xTable.insertAt(rows, index);
|
||||
if (setActive) {
|
||||
// -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警告
|
||||
}
|
||||
await recalcSortNumber();
|
||||
@ -763,7 +764,7 @@ export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps,
|
||||
// 4.1.0
|
||||
//await xTable.updateCache();
|
||||
// 4.1.1
|
||||
await xTable.cacheRowMap()
|
||||
await xTable.cacheRowMap(true)
|
||||
// update-end--author:liaozhiyang---date:20231011---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
return await xTable.updateData();
|
||||
}
|
||||
|
||||
@ -52,7 +52,9 @@ export function usePagination(props: JVxeTableProps, methods: JVxeTableMethods)
|
||||
[
|
||||
h(Pagination, {
|
||||
...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,
|
||||
onShowSizeChange: handleShowSizeChange,
|
||||
}),
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import type { App } from 'vue';
|
||||
// 引入 vxe-table
|
||||
import 'xe-utils';
|
||||
import VxeUIAll from 'vxe-pc-ui';
|
||||
import VXETable /*Grid*/ from 'vxe-table';
|
||||
import VXETablePluginAntd from 'vxe-table-plugin-antd';
|
||||
import 'vxe-pc-ui/lib/style.css';
|
||||
import 'vxe-table/lib/style.css';
|
||||
|
||||
import JVxeTable from './JVxeTable';
|
||||
@ -27,6 +29,7 @@ export function registerJVxeTable(app: App) {
|
||||
// 注册自定义组件
|
||||
registerAllComponent();
|
||||
// 执行注册方法
|
||||
app.use(VxeUIAll);
|
||||
app.use(VXETable, VXETableSettings);
|
||||
app.component('JVxeTable', JVxeTable);
|
||||
}
|
||||
@ -39,6 +42,12 @@ export function registerJVxeTable(app: App) {
|
||||
function preventClosingPopUp(this: any, 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;
|
||||
const interceptor = getEnhanced(col.type).interceptor;
|
||||
// 执行增强
|
||||
|
||||
@ -258,6 +258,20 @@ export const schemas: FormSchema[] = [
|
||||
label: '选中用户',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'user4',
|
||||
component: 'JSelectUserByDepartment',
|
||||
label: '部门选择用户',
|
||||
helpMessage: ['component模式'],
|
||||
defaultValue: '',
|
||||
componentProps: {
|
||||
labelKey: 'realname',
|
||||
rowKey: 'username',
|
||||
},
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'role2',
|
||||
component: 'JSelectRole',
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
const { createMessage } = useMessage();
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
|
||||
const logColumns = ref<any>(columns);
|
||||
const logColumns = ref<any>(exceptionColumns);
|
||||
const searchSchema = ref<any>(searchFormSchema);
|
||||
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