Compare commits

...

129 Commits
v2.4.2 ... v3.0

Author SHA1 Message Date
812cbce06b 在线演示(VUE3beta版) 2021-10-29 09:40:25 +08:00
3b92086047 JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3到来!! 2021-10-27 14:33:50 +08:00
0fa24b8518 JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3版本到来!! 2021-10-27 11:48:19 +08:00
7e7ea37857 JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3版本到来!! 2021-10-27 11:33:09 +08:00
e550843fd1 JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3版本到来!! 2021-10-27 10:55:52 +08:00
3cb20a6a43 JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3版本到来!! 2021-10-27 10:28:39 +08:00
0acea1abff JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3版本到来!! 2021-10-27 10:28:15 +08:00
9760185bf6 JeecgBoot低代码平台 3.0版本发布—新里程牌开始,迎接VUE3版本到来!! 2021-10-27 10:26:33 +08:00
75be8dd5b1 online下拉搜索组件,配置字典sql带'=' 提示sign失败问题处理 2021-10-21 16:31:10 +08:00
a7893c4941 qq群满了,新增⑤860162132 2021-10-19 13:23:47 +08:00
ef97f700ab 授权首页菜单后,自定义首页功能不生效 #3069 2021-10-16 19:41:55 +08:00
6fb01abbc5 online学习视频修改 2021-10-14 16:13:02 +08:00
fe1b58ade2 还原long精度改造,导致了两个严重问题
1表单的日期字段,不能显示日期格式
2前端分页插件报错
2021-09-26 17:35:30 +08:00
1998867ca6 Long转json精度丢失的配置 未生效 2021-09-14 18:12:16 +08:00
a351204865 暂时还原代码,改的有问题 [Long转json精度丢失的配置 未生效] 2021-09-14 17:48:53 +08:00
4da1948cb0 路由网关禁用Demo配置后,系统仍可以通过网关路由到Demo服务。issues/I49457 2021-09-14 17:24:45 +08:00
55ebea88af 消息队列中报微服务异常 issues/I4977W 2021-09-14 17:08:24 +08:00
4b830b37c9 消息队列中报微服务异常 issues/I4977W 2021-09-14 16:43:14 +08:00
537cc05601 Long转json精度丢失的配置 未生效issues/I49DNI 2021-09-14 16:32:31 +08:00
8f9f27c550 同步钉钉人员到本地错误 #2990 2021-09-14 10:09:56 +08:00
57f72dd4d0 issues/I48I0E 省市三级联动列表无法显示 2021-09-11 18:45:23 +08:00
e2e19fa456 内容更新 2021-09-03 12:09:01 +08:00
98a5148e52 【issues/I471XE】 钉钉人员同步时手机号未能正确同步 2021-08-31 22:29:01 +08:00
c2ae049ad7 issues/2959 微服务版集成企业微信单点登录 2021-08-31 22:28:28 +08:00
b44acde9b5 【issues/2939】JEditable 下子表 addBefore()方法,在其中自定义调用其他方法不生效 2021-08-31 22:28:04 +08:00
db467f22a7 docker文档调整 2021-08-27 18:08:08 +08:00
53c91fc349 README.MD 里面前端目录写错了 issues/I477T1 2021-08-27 09:37:07 +08:00
92028a7e44 JVXETable提供更便捷的三级联动机制,解决联动展示与选择BUG #2867 2021-08-20 11:13:42 +08:00
8ce40fa3d4 升级2.4.6后Online表单开发无法使用“一对多”的“ERP主题” issues/I468JY 2021-08-18 20:23:53 +08:00
05c7f76484 列表点击详情,出现id,好难看 #2922 2021-08-18 16:25:10 +08:00
71a1e9a63b oline在线内嵌子表主表与附表,设置扩展参数限制宽度不起作用 #2881
online java 增强当设置的增强过多时,显示异常 #2880
文本太长时,会遮挡页面issues/I44F0R
2021-08-18 15:59:44 +08:00
0606aa560a mybatisPlus升级,拦截器排除写法修改 2021-08-17 13:48:26 +08:00
1bbca48ba8 sqlserver同步数据库报错,SQL to parse以后与sqlserver不兼容 #2915
【升级mybatisPlus导致,多租户插件SqlServer下给添加字段sql多加了一个column】
2021-08-17 13:35:30 +08:00
d0c15f2302 解决新版IE11兼容问题 2021-08-17 11:50:00 +08:00
c9ff5d51b4 解决2.4.6新版问题:js增强点击无效 #2912 2021-08-16 16:38:00 +08:00
cce5d785e4 解决 升级到2.4.6后button type='danger'时的样式 #2909 2021-08-15 10:14:25 +08:00
234022d905 修复低级bug #2906 2021-08-14 19:39:42 +08:00
ab529aaf6c 2.4.6版本发布 2021-08-13 20:36:27 +08:00
07c6d1a23d 2.4.6 版本发布 2021-08-13 20:35:59 +08:00
8f780e180e JeecgBoot 2.4.6版本发布 2021-08-13 16:55:43 +08:00
87f17b9fc5 JeecgBoot 2.4.6版本发布 2021-08-13 16:18:52 +08:00
5a93d001b4 JeecgBoot 2.4.6版本发布(gateway默认采用database中的路由配置) 2021-08-13 16:18:31 +08:00
d822552e0c JeecgBoot 2.4.6版本发布(调整nacos配置) 2021-08-13 16:18:03 +08:00
832fa30cc9 JeecgBoot 2.4.6版本发布 2021-08-13 15:28:22 +08:00
3af1b390f1 JeecgBoot 2.4.6版本发布 2021-08-13 15:27:53 +08:00
a3695151dc JeecgBoot 2.4.6版本发布 2021-08-13 15:27:32 +08:00
c269d7637f JeecgBoot 2.4.6版本发布 2021-08-13 15:26:35 +08:00
664413e5d7 JeecgBoot 2.4.6版本发布 2021-08-13 15:26:20 +08:00
986f909628 issue格式要求 2021-08-09 16:25:21 +08:00
221940cc5f issue格式要求,发issue请详细 2021-08-09 16:13:32 +08:00
2a392fb738 issue格式要求 2021-08-02 18:06:14 +08:00
2b47cd0c34 升级下说明 2021-07-28 17:28:51 +08:00
1900f3fe77 Sign 签名校验失败 #2728 2021-07-01 12:45:17 +08:00
37fe6fea69 表字典接口存在SQL注入漏洞,增加签名拦截器 自定义组件验签失败 issues/I3XNK1 2021-06-25 15:30:47 +08:00
3fbb5ee4ad 登录账号提示修改 2021-06-25 13:41:33 +08:00
b1958fd295 HW21-0499 表字典接口存在SQL注入漏洞,增加签名拦截器 2021-06-25 11:50:34 +08:00
8b3d83ae0b HW21-0499 表字典接口存在SQL注入漏洞,增加签名拦截器 2021-06-21 20:37:23 +08:00
081c2615be HW21-0499 表字典接口存在SQL注入漏洞,增加签名拦截器 2021-06-21 20:37:09 +08:00
f97c675771 online表单数据源配置,数据库类型识别错误 #2671 2021-06-21 19:29:37 +08:00
1beddbf8e8 online表单数据源配置,不支持数据库密码加密 #2672 2021-06-21 19:29:21 +08:00
8921e84303 群满,加新群 ④774126647 2021-06-17 17:44:43 +08:00
43329545d8 【gitee/I3HTFI】自定义树控件的表单里的外键直接显示id不显示name
新增监控在线用户
阿里监控去掉广告
2021-06-17 10:39:59 +08:00
50333488a5 解决issue#2639 2.4.5升级后出现后端排序报错 2021-06-07 18:21:13 +08:00
624444e3b9 用户添加页面无法修改手机号 2021-06-04 16:52:07 +08:00
1cd0f2627d JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-04 11:14:53 +08:00
18007b0524 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-03 14:04:31 +08:00
d92861ad77 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-03 13:59:37 +08:00
8a6181b108 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 16:25:47 +08:00
6840772959 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:51:21 +08:00
1bc7ee3345 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:38:47 +08:00
5f25f726c2 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:38:28 +08:00
9b14d2d6a5 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:38:14 +08:00
130f2bc4be JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:37:49 +08:00
832bc376be JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:32:35 +08:00
432385fc14 新增一种 Cron表达式 选择组件 2021-06-02 15:28:23 +08:00
4d5ac2b518 JeecgBoot低代码平台 2.4.5 版本发布,钉钉与企业微信集成版本 2021-06-02 15:26:58 +08:00
b83c26b8fd 熔断默认时间改成3秒 2021-06-02 15:25:58 +08:00
b59fc67696 新增图表API接口 2021-06-02 15:22:45 +08:00
af3afc7263 删除不需要的底层工具包 2021-06-02 15:21:16 +08:00
63c3dd26f5 即将发布新版 2021-06-02 09:37:32 +08:00
892196fe9a 外部URL菜单加载为空为空 2021-04-14 22:01:47 +08:00
bbda918cde 升级jimureport,修复已知bug 2021-04-13 16:15:10 +08:00
003ec82f48 阿里oos桶名字修改 2021-04-13 16:14:47 +08:00
4992faf66c minidao db类型说明、阿里oos桶名字修改 2021-04-13 16:14:35 +08:00
34c612d9e4 兼容数据库说明 2021-04-13 16:11:58 +08:00
844f1e228c 外部url菜单,内部打开是,也支持通过 ${token}方式传递当前登录TOKEN 2021-04-07 14:07:41 +08:00
cf5fde80d4 nacos建表语句优化 2021-04-06 14:06:55 +08:00
49ea36c50f 升级脚本到积木报表的最新版本 2021-04-06 14:06:35 +08:00
f6e2b67c61 升级SQL:积木报表的示例 2021-04-05 19:01:12 +08:00
69a4a7df6d 重构登录注册页面为a-form-model模式 2021-04-05 18:58:36 +08:00
649f99664e 积木报表升级到最新版本1.3.1-beta2 2021-04-05 18:53:14 +08:00
ef762ff21f 日志里把具体的文件加上吧 issues/I3BJDQ 2021-04-01 09:44:47 +08:00
02abd5c803 优化生成器模板:让代码更简洁,流程相关代码默认不生成 2021-04-01 09:42:47 +08:00
5e7342b27d 优化代码生成器模板:一对多代码生成(ERP模板)生成的子表实体ApiModel注释中value为附表名称 #2365 2021-04-01 09:40:43 +08:00
ca95d7090c minio上传文件,文件名包含点的时候拼接文件名有问题 issues/I3CLFL 2021-04-01 09:38:45 +08:00
cdec71999a spring-boot-starter-jimureport降低版本,公共仓库可以下载 2021-03-31 17:46:04 +08:00
7990eff7a4 JImageUpload组件单张无法预览和删除 #2382 2021-03-31 12:01:18 +08:00
280f8c26ac online报表配置支持多租户系统变量 issues/I3CL75 2021-03-31 12:00:51 +08:00
4e05eaa4c5 JEditableTable组件popup字段设置必填校验有问题 2021-03-31 11:59:47 +08:00
c6645ed800 描述文字写错修改 2021-03-28 19:51:53 +08:00
a674340c5e 微服务模式部署下,nacos的账户密码如果不使用默认提供的nacos/nacos,会导致gateway读取路由信息失败 #2375 2021-03-28 19:45:50 +08:00
cbd8890cb3 代码生成器,树列表新增,会默认加上上次选的父节点问题 2021-03-24 15:56:31 +08:00
b25fb55d10 代码生成器,树列表固定操作列,解决列表分类未翻译问题 2021-03-24 15:55:59 +08:00
88646bea1b 让不对接流程的表单,生成的页面代码更简洁 2021-03-23 22:26:19 +08:00
9915b84808 升级2.4.3后,微服务网关路由更新bug 2021-03-23 15:30:04 +08:00
35c1214de3 开发和测试环境取消swagger文档密码 2021-03-23 14:23:32 +08:00
434b1a7e63 BindingResult无法使用 #2219 2021-03-23 14:23:06 +08:00
5c7d84117c nacos分组配置问题 #2355 2021-03-23 14:22:37 +08:00
4b9890205e JeecgBoot2.4.3版本发布——企业级低代码平台 2021-03-18 13:43:13 +08:00
d5898139b5 2.3版本oracle、SqlServer数据库脚本 2021-03-18 12:57:39 +08:00
bf438069e2 2.4.3版本发布——企业级低代码平台 2021-03-17 18:46:12 +08:00
c5965b10d8 JeecgBoot2.4.3版本发布——企业级低代码平台 2021-03-17 18:43:44 +08:00
da5ace3397 JeecgBoot2.4.3版本发布——企业级低代码平台 2021-03-17 18:40:08 +08:00
4674097078 单体镜像制作脚本 2021-03-10 14:39:14 +08:00
fea1607f1b 单体镜像制作脚本 2021-03-10 14:35:50 +08:00
d2336bc5e1 依赖jar上传到maven官仓,简化使用 2021-03-09 22:20:12 +08:00
cf0d29557a 重写xxljob避免默认 9999端口冲突 2021-03-09 18:27:34 +08:00
bb4c3c86b0 官仓未同步成功,暂时用jeecg私服 2021-03-09 17:26:15 +08:00
84a6e28677 nacos server本地化采用jar方式启动,简化开发 2021-03-09 11:34:11 +08:00
5b70c1d8b8 nacos server本地化采用jar方式启动,简化开发 2021-03-09 11:33:03 +08:00
f942abe09b 微服务模式相关服务的链接改成host模式 2021-03-09 11:19:51 +08:00
ad4eb300f0 nacos server本地化采用jar方式启动,简化开发 2021-03-09 11:10:01 +08:00
064a5c4a0e nacos server本地化采用jar方式启动,简化开发 2021-03-09 10:33:28 +08:00
6d688a157b nacos server本地化采用jar方式启动,简化开发 2021-03-08 20:30:17 +08:00
d612af5e3b nacos server本地化采用jar方式启动,简化开发 2021-03-08 20:29:36 +08:00
e1c0b0fa38 system服务和demo服务有办法同时使用xxl-job吗 #2313 2021-03-05 17:13:44 +08:00
ac0979c824 system服务和demo服务有办法同时使用xxl-job吗 #2313 2021-03-05 17:08:56 +08:00
78ba7ccba8 system服务和demo服务有办法同时使用xxl-job吗 #2313 2021-03-05 17:02:49 +08:00
b8ff5ebd43 微服务部署下代码生成失效,单体模式下代码生成可用 #2324 2021-03-04 17:39:54 +08:00
497 changed files with 245321 additions and 29115 deletions

1
.gitattributes vendored
View File

@ -2,3 +2,4 @@
*.css linguist-language=Java
*.html linguist-language=Java
*.vue linguist-language=Java
*.sql linguist-language=Java

View File

@ -9,4 +9,9 @@
友情提示: 未按格式要求发帖,会直接删掉。
#### 友情提示为了提高issue处理效率
- 未按格式要求发帖,会被直接删掉;
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
- 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能;
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;

View File

@ -1,18 +1,19 @@
![JEECG](https://static.oschina.net/uploads/img/201905/24164523_XDhg.png "JeecgBoot低代码开发平台")
![JEECG](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/site/jeecgfengm.png "JeecgBoot低代码开发平台")
JEECG BOOT 低代码开发平台(前后端分离版本)
===============
当前最新版本: 2.4.2发布日期2021-01-26
当前最新版本: 3.0发布日期2021-11-01
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-2.4.2-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
@ -23,7 +24,7 @@ JEECG BOOT 低代码开发平台(前后端分离版本)
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
JeecgBoot 是一款基于代码生成器的`低代码`开发平台!前后端分离架构 SpringBoot2.xSpringCloudAnt Design&VueMybatis-plusShiroJWT支持微服务。强大的代码生成器让前后端代码一键生成实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE) 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
JeecgBoot 是一款基于代码生成器的`低代码平台`!前后端分离架构 SpringBoot2.xSpringCloudAnt Design&VueMybatis-plusShiroJWT支持微服务。强大的代码生成器让前后端代码一键生成实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE) 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零代码`Online表单开发、Online报表、报表配置能力、在线图表设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力可插拔等等
@ -44,14 +45,16 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
- 技术官网: [http://www.jeecg.com](http://www.jeecg.com)
- 开发文档: [http://doc.jeecg.com](http://doc.jeecg.com)
- 微服务启动: [单体升级为微服务启动文档2.4+](http://doc.jeecg.com/2043906)
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 在线演示(VUE3beta版)[http://boot3.jeecg.com](http://boot3.jeecg.com)
- 开发文档: [http://doc.jeecg.com](http://doc.jeecg.com)
- 视频教程 [JeecgBoot入门视频](http://www.jeecg.com/doc/video)
- 微服务启动: [单体升级为微服务启动文档2.4+](http://doc.jeecg.com/2043906)
- 常见问题: [入门常见问题Q&A](http://jeecg.com/doc/qa)
- 更新日志: [版本日志](http://www.jeecg.com/doc/log)
@ -61,19 +64,19 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
交流互动
-----------------------------------
- QQ交流群 ③816531124、②769925425(满)、①284271917(满)
- QQ交流群 ⑤860162132、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
- 反馈问题: [反馈问题请按格式发Issues](https://github.com/zhangdaiscott/jeecg-boot/issues/new)
- 参与开源: [欢迎加入JEECG开源团队共同进步](http://www.jeecg.com/doc/join)
- Online一分钟 [1分钟快速学习](https://jeecg.blog.csdn.net/article/details/106078912)
- Online一分钟 [1分钟快速学习](https://my.oschina.net/jeecg/blog/3083313)
为什么选择JEECG-BOOT?
-----------------------------------
* 1.采用最新主流前后分离框架Springboot+Mybatis+antd容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
* 2.支持微服务SpringCloud Alibaba(Nacos、Gateway、Sentinel、Skywarking),提供切换机制支持单体和微服务自由切换
* 2.支持微服务SpringCloud Alibaba(Nacos、Gateway、Sentinel、Skywalking),提供切换机制支持单体和微服务自由切换
* 3.开发效率高,采用代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用;
* 4.代码生成器提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)
* 5.代码生成器非常智能在线业务建模、在线配置、所见即所得支持23种类控件一键生成前后端代码大幅度提升开发效率不再为重复工作发愁。
@ -90,7 +93,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
* 16.页面校验自动生成(必须输入、数字校验、金额校验、时间空间等);
* 17.支持SAAS服务模式提供SaaS多租户架构方案。
* 18.分布式文件服务集成minio、阿里OSS等优秀的第三方提供便捷的文件上传与管理同时也支持本地存储。
* 19.主流数据库兼容一套代码完全兼容Mysql、Postgresql、Oracle三大主流数据库。
* 19.主流数据库兼容一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦等主流数据库。
* 20.集成工作流activiti并实现了只需在页面配置流程转向可极大的简化bpm工作流的开发用bpm的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的java代码
* 21.低代码能力在线流程设计采用开源Activiti流程引擎实现在线画流程,自定义表单,表单挂靠,业务流转
* 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
@ -129,21 +132,37 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
- 依赖管理Maven
- 数据库MySQL5.7+ & Oracle 11g & Sqlserver2017
- 缓存Redis
- 数据库脚本MySQL5.7+ & Oracle 11g & Sqlserver2017默认只提供这三个库脚本其他库需要自己转
| 数据库 | 支持 |
| --- | --- |
| MySQL | √ |
| Oracle11g | √ |
| Sqlserver2017 | √ |
| PostgreSQL | √ |
| DB2、Informix | √ |
| MariaDB | √ |
| SQLite、Hsqldb、Derby、H2 | √ |
| 达梦、人大金仓、神通 | √ |
| 华为高斯、虚谷、瀚高数据库 | √ |
| 阿里云PolarDB、PPAS、HerdDB | √ |
| Hive、HBase、CouchBase | √ |
#### 后端
- 基础框架Spring Boot 2.3.5.RELEASE
- 微服务框架: Spring Cloud Alibaba 2.2.3.RELEASE
- 持久层框架Mybatis-plus 3.4.1
- 持久层框架Mybatis-plus 3.4.3.1、Minidao
- 报表工具: jimureport 1.3.78
- 安全框架Apache Shiro 1.7.0Jwt 3.11.0
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
- 数据库连接池阿里巴巴Druid 1.1.22
@ -328,7 +347,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
8、服务监控 SpringBootAdmin√
9、链路跟踪 Skywarking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
9、链路跟踪 Skywalking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
10、消息中间件 RabbitMQ √
@ -336,7 +355,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
12、分布式事务 Seata
13、分布式日志 elk + kafa
13、分布式日志 elk + kafka
14、支持 docker-compose、k8s、jenkins
@ -382,11 +401,11 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
- 拉取项目代码
```bash
git clone https://github.com/zhangdaiscott/jeecg-boot.git
cd jeecg-boot/ant-design-jeecg-vue
cd jeecg-boot/ant-design-vue-jeecg
```
1. 安装node.js
2. 切换到ant-design-jeecg-vue文件夹下
2. 切换到ant-design-vue-jeecg文件夹下
```
# 安装yarn
npm install -g yarn
@ -487,9 +506,9 @@ yarn run lint
- [Vue](https://cn.vuejs.org/v2/guide)
- [路由/菜单说明](https://gitee.com/jeecg/jeecg-boot/tree/v1.1/ant-design-jeecg-vue/src/router/README.md)
- [路由/菜单说明](https://gitee.com/jeecg/jeecg-boot/tree/v1.1/ant-design-vue-jeecg/src/router/README.md)
- [ANTD 默认配置项](https://gitee.com/jeecg/jeecg-boot/blob/v1.1/ant-design-jeecg-vue/src/defaultSettings.js)
- [ANTD 默认配置项](https://gitee.com/jeecg/jeecg-boot/blob/v1.1/ant-design-vue-jeecg/src/defaultSettings.js)
- 其他待补充...

View File

@ -1,3 +1,3 @@
NODE_ENV=production
VUE_APP_PLATFORM_NAME=JeecgBoot 企业级快速开发平台
VUE_APP_PLATFORM_NAME=JeecgBoot 企业级低代码平台
VUE_APP_SSO=false

View File

@ -1,4 +1,4 @@
NODE_ENV=production
VUE_APP_API_BASE_URL=https://bootapi.jeecg.com
VUE_APP_API_BASE_URL=http://localhost:8080/jeecg-boot
VUE_APP_CAS_BASE_URL=http://localhost:8888/cas
VUE_APP_ONLINE_BASE_URL=http://fileview.jeecg.com/onlinePreview

View File

@ -1,13 +1,13 @@
Ant Design Jeecg Vue
====
当前最新版本: 2.4.2发布日期20210126
当前最新版本: 3.0.0发布日期2021-11-01
Overview
----
基于 [Ant Design of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 实现的 Ant Design Pro Vue 版
Jeecg-boot 的前UI框架采用前后端分离方案提供强大代码生成器的快速开发平台。
Jeecg-boot 的前UI框架采用前后端分离方案提供强大代码生成器的低代码平台。
前端页面代码和后端功能代码一键生成不需要写任何代码保持jeecg一贯的强大
@ -33,7 +33,7 @@ Jeecg-boot 的前段UI框架采用前后端分离方案提供强大代码
- 拉取项目代码
```bash
git clone https://github.com/zhangdaiscott/jeecg-boot.git
cd jeecg-boot/ant-design-jeecg-vue
cd jeecg-boot/ant-design-vue-jeecg
```
- 安装依赖
@ -93,9 +93,9 @@ yarn run lint
- [Vue](https://cn.vuejs.org/v2/guide)
- [路由/菜单说明](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/router/README.md)
- [路由/菜单说明](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-vue-jeecg/src/router/README.md)
- [ANTD 默认配置项](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/defaultSettings.js)
- [ANTD 默认配置项](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-vue-jeecg/src/defaultSettings.js)
- 其他待补充...

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "vue-antd-jeecg",
"version": "2.4.2",
"version": "3.0.0",
"private": true,
"scripts": {
"pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
@ -10,8 +10,8 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@jeecg/antd-online-mini": "2.4.22-beta",
"ant-design-vue": "^1.7.2",
"@jeecg/antd-online-mini": "3.0.0-beta",
"@antv/data-set": "^0.11.4",
"viser-vue": "^2.4.8",
"axios": "^0.18.0",
@ -39,11 +39,12 @@
"tinymce": "^5.3.2",
"@toast-ui/editor": "^2.1.2",
"vue-area-linkage": "^5.1.0",
"area-data": "^5.0.6",
"china-area-data": "^5.0.1",
"dom-align": "1.12.0",
"xe-utils": "2.4.8",
"vxe-table": "2.9.13",
"vxe-table-plugin-antd": "1.8.10"
"vxe-table-plugin-antd": "1.8.10",
"cron-parser": "^2.10.0"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",
@ -96,10 +97,7 @@
"vue/html-closing-bracket-newline": 0,
"vue/no-parsing-error": 0,
"no-tabs": 0,
"indent": [
"off",
2
],
"indent": ["off", 2],
"no-console": 0,
"space-before-function-paren": 0
}

View File

@ -5,7 +5,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>JeecgBoot 企业级快速开发平台</title>
<title>JeecgBoot 企业级低代码平台</title>
<link rel="icon" href="<%= BASE_URL %>logo.png">
<script src="<%= BASE_URL %>cdn/babel-polyfill/polyfill_7_2_5.js"></script>
<style>
@ -251,7 +251,7 @@
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载 JeecgBoot 快速开发平台,请耐心等待
<div class="load_title">正在加载 JeecgBoot 低代码平台,请耐心等待
</div>
</div>

View File

@ -38,6 +38,7 @@ const queryPermissionRule = (params)=>getAction("/sys/permission/queryPermission
// 部门管理
const queryDepartTreeList = (params)=>getAction("/sys/sysDepart/queryTreeList",params);
const queryDepartTreeSync = (params)=>getAction("/sys/sysDepart/queryDepartTreeSync",params);
const queryIdTree = (params)=>getAction("/sys/sysDepart/queryIdTree",params);
const queryParentName = (params)=>getAction("/sys/sysDepart/queryParentName",params);
const searchByKeywords = (params)=>getAction("/sys/sysDepart/searchBy",params);
@ -52,7 +53,6 @@ const saveDeptRolePermission = (params)=>postAction("/sys/sysDepartPermission/sa
const queryMyDepartTreeList = (params)=>getAction("/sys/sysDepart/queryMyDeptTreeList",params);
//日志管理
//const getLogList = (params)=>getAction("/sys/log/list",params);
const deleteLog = (params)=>deleteAction("/sys/log/delete",params);
const deleteLogList = (params)=>deleteAction("/sys/log/deleteBatch",params);
@ -69,7 +69,7 @@ export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItem
function getDictItemsFromCache(dictCode) {
if (Vue.ls.get(UI_CACHE_DB_DICT_DATA) && Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode]) {
let dictItems = Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode];
console.log("-----------getDictItemsFromCache----------dictCode="+dictCode+"---- dictItems=",dictItems)
//console.log("-----------getDictItemsFromCache----------dictCode="+dictCode+"---- dictItems=",dictItems)
return dictItems;
}
}
@ -101,6 +101,8 @@ export const transitRESTful = {
}
export {
// imgView,
// doMian,
addRole,
editRole,
checkRoleCode,
@ -124,6 +126,7 @@ export {
getPermissionRuleList,
queryPermissionRule,
queryDepartTreeList,
queryDepartTreeSync,
queryIdTree,
queryParentName,
searchByKeywords,

View File

@ -71,4 +71,17 @@ export function thirdLogin(token,thirdType) {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
/**
* 强退其他账号
* @param token
* @returns {*}
*/
export function forceLogout(parameter) {
return axios({
url: '/sys/online/forceLogout',
method: 'post',
data: parameter
})
}

View File

@ -1,5 +1,6 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
import signMd5Utils from '@/utils/encryption/signMd5Utils'
const api = {
user: '/mock/api/user',
@ -13,19 +14,29 @@ export default api
//post
export function postAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method:'post' ,
data: parameter
data: parameter,
headers: signHeader
})
}
//post method= {post | put}
export function httpAction(url,parameter,method) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method:method ,
data: parameter
data: parameter,
headers: signHeader
})
}
@ -40,10 +51,15 @@ export function putAction(url,parameter) {
//get
export function getAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method: 'get',
params: parameter
params: parameter,
headers: signHeader
})
}

View File

@ -12,9 +12,9 @@
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
</a-tooltip>
<a-tooltip v-else :title="file.name">
<a-icon type="paper-clip" style="color:red;"/>
<span style="color:red;margin-left:5px">{{ ellipsisFileName }}</span>
<a-tooltip v-else :title="file.message||'上传失败'">
<a-icon type="exclamation-circle" style="color:red;"/>
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
</a-tooltip>
<template style="width: 30px">
@ -179,8 +179,19 @@
value['responseName'] = file.response[this.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
if (typeof file.response.success === 'boolean') {
if (file.response.success) {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
} else {
value['status'] = 'error'
value['message'] = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台可能不会返回 success 属性的情况,就默认为成功
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
}
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}

View File

@ -10,20 +10,9 @@
<template v-else-if="file['path']">
<img class="j-editable-image" :src="imgSrc" alt="无图片" @click="handleMoreOperation"/>
</template>
<template v-else>
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError"/>
</template>
<template slot="addonBefore" style="width: 30px">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
<a-tooltip v-else :title="file.message||'上传失败'" @click="handleClickShowImageError">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
@ -196,8 +185,19 @@
value['responseName'] = file.response[this.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
if (typeof file.response.success === 'boolean') {
if (file.response.success) {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
} else {
value['status'] = 'error'
value['message'] = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台可能不会返回 success 属性的情况,就默认为成功
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
}
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}

View File

@ -6,7 +6,7 @@
</template>
<script>
import JVxeCellMixins, { vModel, dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import JVxeCellMixins, { dispatchEvent, vModel } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxePopupCell',
@ -22,6 +22,8 @@
orgFields: col.orgFields,
destFields: col.destFields,
groupId: caseId,
param: col.param,
sorter: col.sorter,
}
},
},
@ -48,7 +50,9 @@
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-input'),
editActived(event) {
dispatchEvent.call(this, event, 'ant-input')
},
},
},
}

View File

@ -254,7 +254,9 @@ export const DictSearchInputCell = {
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-select'),
editActived(event) {
dispatchEvent.call(this, event, 'ant-select')
},
},
}
}

View File

@ -39,5 +39,5 @@ UserMenu.vue:首页右上侧的内容
![输入图片说明](https://static.oschina.net/uploads/img/201904/12201226_laQK.png "在这里输入图片标题")
####16.trend包 趋势显示组件(如下图)
![输入图片说明](https://static.oschina.net/uploads/img/201904/12201600_Wo8K.png "在这里输入图片标题")
![corn表达式](https://oscimg.oschina.net/oscnet/661f9ac09016395f9f49286143af3241623.jpg)
![corn控件添加清除按钮](https://oscimg.oschina.net/oscnet/15096e49f2e29bd829e304d56770025d03c.jpg)
![cron表达式](https://oscimg.oschina.net/oscnet/661f9ac09016395f9f49286143af3241623.jpg)
![cron控件添加清除按钮](https://oscimg.oschina.net/oscnet/15096e49f2e29bd829e304d56770025d03c.jpg)

View File

@ -1,5 +1,4 @@
import { pcaa } from 'area-data'
import Vue from 'vue'
/**
* 省市区
*/
@ -8,18 +7,23 @@ export default class Area {
* 构造器
* @param express
*/
constructor() {
constructor(pcaa) {
if(!pcaa){
pcaa = Vue.prototype.$Jpcaa;
}
let arr = []
const province = pcaa['86']
Object.keys(province).map(key=>{
arr.push({id:key, text:province[key], pid:'86'});
arr.push({id:key, text:province[key], pid:'86', index:1});
const city = pcaa[key];
Object.keys(city).map(key2=>{
arr.push({id:key2, text:city[key2], pid:key});
arr.push({id:key2, text:city[key2], pid:key, index:2});
const qu = pcaa[key2];
Object.keys(qu).map(key3=>{
arr.push({id:key3, text:qu[key3], pid:key2});
})
if(qu){
Object.keys(qu).map(key3=>{
arr.push({id:key3, text:qu[key3], pid:key2, index:3});
})
}
})
})
this.all = arr;
@ -45,33 +49,35 @@ export default class Area {
return ''
}
let arr = []
this.getAreaBycode(code,arr);
this.getAreaBycode(code, arr, 3);
return arr.join('/')
}
getRealCode(code){
let arr = []
this.getPcode(code, arr)
this.getPcode(code, arr, 3)
return arr;
}
getPcode(id, arr){
getPcode(id, arr, index){
for(let item of this.all){
if(item.id === id){
if(item.id === id && item.index == index){
arr.unshift(id)
if(item.pid != '86'){
this.getPcode(item.pid,arr)
this.getPcode(item.pid, arr, --index)
}
}
}
}
getAreaBycode(code,arr){
//console.log("this.all.length",this.all)
getAreaBycode(code, arr, index){
for(let item of this.all){
if(item.id === code){
if(item.id === code && item.index == index){
arr.unshift(item.text);
this.getAreaBycode(item.pid,arr)
if(item.pid != '86'){
this.getAreaBycode(item.pid, arr, --index)
}
}
}
}

View File

@ -32,4 +32,15 @@ export const cutStrByFullLength = (str = '', maxLength) => {
}
return pre
}, '')
}
// 下划线转换驼峰
export function underLinetoHump(name) {
return name.replace(/\_(\w)/g, function(all, letter){
return letter.toUpperCase();
});
}
// 驼峰转换下划线
export function humptoUnderLine(name) {
return name.replace(/([A-Z])/g,"_$1").toLowerCase();
}

View File

@ -25,7 +25,6 @@
props: {
dictCode: String,
placeholder: String,
triggerChange: Boolean,
disabled: Boolean,
value: [String, Number],
type: String,
@ -82,19 +81,15 @@
}
})
},
handleInput(e) {
handleInput(e='') {
let val;
if(this.tagType=="radio"){
if(Object.keys(e).includes('target')){
val = e.target.value
}else{
val = e
}
console.log(val);
if(this.triggerChange){
this.$emit('change', val);
}else{
this.$emit('input', val);
}
this.$emit('change', val);
},
setCurrentDictOptions(dictOptions){
this.dictOptions = dictOptions
@ -102,6 +97,10 @@
getCurrentDictOptions(){
return this.dictOptions
}
},
model:{
prop: 'value',
event: 'change'
}
}
</script>

View File

@ -48,7 +48,6 @@
props:{
disabled: Boolean,
value: [String, Number],
dict: String,
dictOptions: Array,
async: Boolean,
placeholder:{
@ -56,6 +55,11 @@
default:"请选择",
required:false
},
dict:{
type: String,
default: '',
required: false
},
popContainer:{
type:String,
default:'',
@ -65,7 +69,11 @@
type: Number,
default: 10,
required: false
}
},
getPopupContainer: {
type:Function,
default: null
},
},
data(){
this.loadData = debounce(this.loadData, 800);//消抖
@ -182,16 +190,20 @@
}
}
}else{
//异步一开始也加载一点数据
this.loading=true
getAction(`/sys/dict/loadDict/${this.dict}`,{pageSize: this.pageSize, keyword:''}).then(res=>{
this.loading=false
if(res.success){
this.options = res.result
}else{
this.$message.warning(res.message)
}
})
if(!this.dict){
console.error('搜索组件未配置字典项')
}else{
//异步一开始也加载一点数据
this.loading=true
getAction(`/sys/dict/loadDict/${this.dict}`,{pageSize: this.pageSize, keyword:''}).then(res=>{
this.loading=false
if(res.success){
this.options = res.result
}else{
this.$message.warning(res.message)
}
})
}
}
},
filterOption(input, option) {
@ -226,7 +238,9 @@
return this.options
},
getParentContainer(node){
if(!this.popContainer){
if(typeof this.getPopupContainer === 'function'){
return this.getPopupContainer(node)
} else if(!this.popContainer){
return node.parentNode
}else{
return document.querySelector(this.popContainer)

View File

@ -29,7 +29,6 @@
</template>
<script>
import { pcaa } from 'area-data'
import Area from '@/components/_util/Area'
export default {
@ -53,7 +52,7 @@
},
data() {
return {
pcaa,
pcaa: this.$Jpcaa,
innerValue: [],
usedListeners: ['change'],
enums: {
@ -114,7 +113,7 @@
/** 通过地区code获取子级 */
loadDataByCode(value) {
let options = []
let data = pcaa[value]
let data = this.pcaa[value]
if (data) {
for (let key in data) {
if (data.hasOwnProperty(key)) {
@ -139,7 +138,7 @@
},
initAreaData(){
if(!this.areaData){
this.areaData = new Area();
this.areaData = new Area(this.$Jpcaa);
}
},

View File

@ -400,6 +400,10 @@
.null-tip-hidden{
display: none;
}
/**选中样式偶然出现高度不够的情况*/
.CodeMirror-selected{
min-height: 19px !important;
}
}
/* 全屏样式 */

View File

@ -1,7 +1,7 @@
<template>
<div class="components-input-demo-presuffix">
<a-input @click="openModal" placeholder="corn表达式" v-model="cron" @change="handleOK">
<a-icon slot="prefix" type="schedule" title="corn控件"/>
<a-input @click="openModal" placeholder="cron表达式" v-model="cron" @change="(e)=>handleOK(e.target.value)">
<a-icon slot="prefix" type="schedule" title="cron控件"/>
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>

View File

@ -8,7 +8,7 @@
:showTime="showTime"
:format="dateFormat"
:getCalendarContainer="getCalendarContainer"
/>
v-bind="$attrs"/>
</template>
<script>
import moment from 'moment'

View File

@ -0,0 +1,281 @@
<template>
<div class="j-easy-cron">
<div class="content">
<div>
<a-tabs size="small" v-model="curtab">
<a-tab-pane tab="" key="second" v-if="!hideSecond">
<second-ui v-model="second" :disabled="disabled"></second-ui>
</a-tab-pane>
<a-tab-pane tab="" key="minute">
<minute-ui v-model="minute" :disabled="disabled"></minute-ui>
</a-tab-pane>
<a-tab-pane tab="" key="hour">
<hour-ui v-model="hour" :disabled="disabled"></hour-ui>
</a-tab-pane>
<a-tab-pane tab="" key="day">
<day-ui v-model="day" :week="week" :disabled="disabled"></day-ui>
</a-tab-pane>
<a-tab-pane tab="" key="month">
<month-ui v-model="month" :disabled="disabled"></month-ui>
</a-tab-pane>
<a-tab-pane tab="" key="week">
<week-ui v-model="week" :day="day" :disabled="disabled"></week-ui>
</a-tab-pane>
<a-tab-pane tab="" key="year" v-if="!hideYear && !hideSecond">
<year-ui v-model="year" :disabled="disabled"></year-ui>
</a-tab-pane>
</a-tabs>
</div>
<a-divider/>
<!-- 执行时间预览 -->
<a-row :gutter="8">
<a-col :span="18" style="margin-top: 22px;">
<a-row :gutter="8">
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.second" @blur="onInputBlur"/>
</a-col>
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.minute" @blur="onInputBlur"/>
</a-col>
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.hour" @blur="onInputBlur"/>
</a-col>
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.day" @blur="onInputBlur"/>
</a-col>
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.month" @blur="onInputBlur"/>
</a-col>
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.week" @blur="onInputBlur"/>
</a-col>
<a-col :span="8" style="margin-bottom: 8px;">
<a-input addon-before="" v-model="inputValues.year" @blur="onInputBlur"/>
</a-col>
<a-col :span="16" style="margin-bottom: 8px;">
<a-input addon-before="Cron" v-model="inputValues.cron" @blur="onInputCronBlur"/>
</a-col>
</a-row>
</a-col>
<a-col :span="6">
<div>近十次执行时间(不含年)</div>
<a-textarea type="textarea" :value="preTimeList" :rows="5"/>
</a-col>
</a-row>
</div>
</div>
</template>
<script>
import SecondUi from './tabs/second'
import MinuteUi from './tabs/minute'
import HourUi from './tabs/hour'
import DayUi from './tabs/day'
import WeekUi from './tabs/week'
import MonthUi from './tabs/month'
import YearUi from './tabs/year'
import CronParser from 'cron-parser'
import dateFormat from './format-date'
import { simpleDebounce } from '@/utils/util'
import ACol from 'ant-design-vue/es/grid/Col'
export default {
name: 'easy-cron',
components: {
ACol,
SecondUi,
MinuteUi,
HourUi,
DayUi,
WeekUi,
MonthUi,
YearUi
},
props: {
cronValue: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
hideSecond: {
type: Boolean,
default: false
},
hideYear: {
type: Boolean,
default: false
},
remote: {
type: Function,
default: null
}
},
data() {
return {
curtab: this.hideSecond ? 'minute' : 'second',
second: '*',
minute: '*',
hour: '*',
day: '*',
month: '*',
week: '?',
year: '*',
inputValues: {second: '', minute: '', hour: '', day: '', month: '', week: '', year: '', cron: ''},
preTimeList: '执行预览,会忽略年份参数',
}
},
computed: {
cronValue_c() {
let result = []
if (!this.hideSecond) result.push(this.second ? this.second : '*')
result.push(this.minute ? this.minute : '*')
result.push(this.hour ? this.hour : '*')
result.push(this.day ? this.day : '*')
result.push(this.month ? this.month : '*')
result.push(this.week ? this.week : '?')
if (!this.hideYear && !this.hideSecond) result.push(this.year ? this.year : '*')
return result.join(' ')
},
cronValue_c2() {
const v = this.cronValue_c
if (this.hideYear || this.hideSecond) return v
const vs = v.split(' ')
return vs.slice(0, vs.length - 1).join(' ')
}
},
watch: {
cronValue(newVal, oldVal) {
if (newVal === this.cronValue_c) {
// console.info('same cron value: ' + newVal)
return
}
this.formatValue()
},
cronValue_c(newVal, oldVal) {
this.calTriggerList()
this.$emit('change', newVal)
this.assignInput()
},
minute() {
if (this.second === '*') {
this.second = '0'
}
},
hour() {
if (this.minute === '*') {
this.minute = '0'
}
},
day(day) {
if (day !== '?' && this.hour === '*') {
this.hour = '0'
}
},
week(week) {
if (week !== '?' && this.hour === '*') {
this.hour = '0'
}
},
month() {
if (this.day === '?' && this.week === '*') {
this.week = '1'
} else if (this.week === '?' && this.day === '*') {
this.day = '1'
}
},
year() {
if (this.month === '*') {
this.month = '1'
}
},
},
created() {
this.formatValue()
this.$nextTick(() => {
this.calTriggerListInner()
})
},
methods: {
assignInput() {
Object.assign(this.inputValues, {
second: this.second,
minute: this.minute,
hour: this.hour,
day: this.day,
month: this.month,
week: this.week,
year: this.year,
cron: this.cronValue_c,
})
},
formatValue() {
if (!this.cronValue) return
const values = this.cronValue.split(' ').filter(item => !!item)
if (!values || values.length <= 0) return
let i = 0
if (!this.hideSecond) this.second = values[i++]
if (values.length > i) this.minute = values[i++]
if (values.length > i) this.hour = values[i++]
if (values.length > i) this.day = values[i++]
if (values.length > i) this.month = values[i++]
if (values.length > i) this.week = values[i++]
if (values.length > i) this.year = values[i]
this.assignInput()
},
calTriggerList: simpleDebounce(function () {
this.calTriggerListInner()
}, 500),
calTriggerListInner() {
// 设置了回调函数
if (this.remote) {
this.remote(this.cronValue_c, +new Date(), v => {
this.preTimeList = v
})
return
}
const format = 'yyyy-MM-dd hh:mm:ss'
const options = {
currentDate: dateFormat(new Date(), format)
}
const iter = CronParser.parseExpression(this.cronValue_c2, options)
const result = []
for (let i = 1; i <= 10; i++) {
result.push(dateFormat(new Date(iter.next()), format))
}
this.preTimeList = result.length > 0 ? result.join('\n') : '无执行时间'
},
onInputBlur(){
this.second = this.inputValues.second
this.minute = this.inputValues.minute
this.hour = this.inputValues.hour
this.day = this.inputValues.day
this.month = this.inputValues.month
this.week = this.inputValues.week
this.year = this.inputValues.year
},
onInputCronBlur(event){
this.$emit('change', event.target.value)
},
},
model: {
prop: 'cronValue',
event: 'change'
},
}
</script>
<style scoped lang="less">
.j-easy-cron {
/deep/ .content {
.ant-checkbox-wrapper + .ant-checkbox-wrapper {
margin-left: 0;
}
}
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<div class="input-cron">
<a-input :placeholder="placeholder" v-model="editCronValue" :disabled="disabled">
<a slot="addonAfter" @click="showConfigDlg" class="config-btn" :disabled="disabled">
<a-icon type="setting"></a-icon>
选择
</a>
</a-input>
<j-modal :visible.sync="show" title="Cron表达式" width="800px">
<easy-cron
v-model="editCronValue"
:exeStartTime="exeStartTime"
:hideYear="hideYear"
:remote="remote"
:hideSecond="hideSecond"
style="width: 100%"
></easy-cron>
</j-modal>
</div>
</template>
<script>
import EasyCron from './EasyCron.vue'
export default {
name: 'input-cron',
components: {EasyCron},
model: {
prop: 'cronValue',
event: 'change'
},
props: {
cronValue: {
type: String,
default: ''
},
width: {
type: String,
default: '800px'
},
placeholder: {
type: String,
default: '请输入cron表达式'
},
disabled: {
type: Boolean,
default: false
},
exeStartTime: {
type: [Number, String, Object],
default: 0
},
hideSecond: {
type: Boolean,
default: false
},
hideYear: {
type: Boolean,
default: false
},
remote: {
type: Function,
default: null
}
},
data() {
return {
editCronValue: this.cronValue,
show: false,
}
},
watch: {
cronValue(newVal, oldVal) {
if (newVal === this.editCronValue) {
return
}
this.editCronValue = newVal
},
editCronValue(newVal, oldVal) {
this.$emit('change', newVal)
}
},
methods: {
showConfigDlg() {
if (!this.disabled) {
this.show = true
}
}
}
}
</script>
<style scoped>
.config-btn {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,37 @@
const dateFormat = (date, block) => {
if (!date) {
return ''
}
let format = block || 'yyyy-MM-dd'
date = new Date(date)
const map = {
M: date.getMonth() + 1, // 月份
d: date.getDate(), // 日
h: date.getHours(), // 小时
m: date.getMinutes(), // 分
s: date.getSeconds(), // 秒
q: Math.floor((date.getMonth() + 3) / 3), // 季度
S: date.getMilliseconds() // 毫秒
}
format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
let v = map[t]
if (v !== undefined) {
if (all.length > 1) {
v = `0${v}`
v = v.substr(v.length - 2)
}
return v
} else if (t === 'y') {
return (date.getFullYear().toString()).substr(4 - all.length)
}
return all
})
return format
}
export default dateFormat

View File

@ -0,0 +1,6 @@
// 原开源项目地址https://gitee.com/toktok/easy-cron
import InputCron from './InputCron.vue'
InputCron.name = 'JEasyCron'
export default InputCron

View File

@ -0,0 +1,21 @@
export const WEEK_MAP_EN = {
'SUN': '0',
'MON': '1',
'TUE': '2',
'WED': '3',
'THU': '4',
'FRI': '5',
'SAT': '6'
}
export const replaceWeekName = (c) => {
// console.info('after: ' + c)
if (c) {
Object.keys(WEEK_MAP_EN).forEach(k => {
c = c.replace(new RegExp(k, 'g'), WEEK_MAP_EN[k])
})
c = c.replace(new RegExp('7', 'g'), '0')
}
// console.info('after: ' + c)
return c
}

View File

@ -0,0 +1,101 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
<span class="tip-info">日和周只能设置其中之一</span>
</div>
<div class="item">
<a-radio value="TYPE_EVERY" class="choice" :disabled="disableChoice">每日</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
日开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
<div class="item">
<a-radio value="TYPE_WORK" class="choice" :disabled="disableChoice">工作日</a-radio>
本月
<a-input-number :disabled="type!==TYPE_WORK || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueWork"/>
日,最近的工作日
</div>
<div class="item">
<a-radio value="TYPE_LAST" class="choice" :disabled="disableChoice">最后一日</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
<div class="list">
<a-checkbox-group v-model="valueList">
<template v-for="i of specifyRange">
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
</template>
</a-checkbox-group>
</div>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
export default {
name: 'day',
mixins: [mixin],
props: {
week: {
type: String,
default: '?'
}
},
data() {
return {}
},
computed: {
disableChoice() {
return (this.week && this.week !== '?') || this.disabled
}
},
watch: {
value_c(newVal, oldVal) {
// 数值变化
this.updateValue()
},
week(newVal, oldVal) {
// console.info('new week: ' + newVal)
this.updateValue()
}
},
methods: {
updateValue() {
this.$emit('change', this.disableChoice ? '?' : this.value_c)
}
},
created() {
this.DEFAULT_VALUE = '*'
this.minValue = 1
this.maxValue = 31
this.valueRange.start = 1
this.valueRange.end = 31
this.valueLoop.start = 1
this.valueLoop.interval = 1
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,67 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每时</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
时开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
<div class="item">
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
<div class="list">
<a-checkbox-group v-model="valueList">
<template v-for="i in specifyRange">
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
</template>
</a-checkbox-group>
</div>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
export default {
name: 'minute',
mixins: [mixin],
data() {
return {}
},
watch: {
value_c(newVal, oldVal) {
this.$emit('change', newVal)
}
},
created() {
this.DEFAULT_VALUE = '*'
this.minValue = 0
this.maxValue = 23
this.valueRange.start = 0
this.valueRange.end = 23
this.valueLoop.start = 0
this.valueLoop.interval = 1
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,67 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每分</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
分开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
<div class="item">
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
<div class="list">
<a-checkbox-group v-model="valueList">
<template v-for="i in specifyRange">
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
</template>
</a-checkbox-group>
</div>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
export default {
name: 'minute',
mixins: [mixin],
data() {
return {}
},
watch: {
value_c(newVal, oldVal) {
this.$emit('change', newVal)
}
},
created() {
this.DEFAULT_VALUE = '*'
this.minValue = 0
this.maxValue = 59
this.valueRange.start = 0
this.valueRange.end = 59
this.valueLoop.start = 0
this.valueLoop.interval = 1
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,162 @@
// 主要用于日和星期的互斥使用
const TYPE_NOT_SET = 'TYPE_NOT_SET'
const TYPE_EVERY = 'TYPE_EVERY'
const TYPE_RANGE = 'TYPE_RANGE'
const TYPE_LOOP = 'TYPE_LOOP'
const TYPE_WORK = 'TYPE_WORK'
const TYPE_LAST = 'TYPE_LAST'
const TYPE_SPECIFY = 'TYPE_SPECIFY'
const DEFAULT_VALUE = '?'
export default {
model: {
prop: 'prop',
event: 'change'
},
props: {
prop: {
type: String,
default: DEFAULT_VALUE
},
disabled: {
type: Boolean,
default: false
}
},
data () {
const type = TYPE_EVERY
return {
DEFAULT_VALUE,
// 类型
type,
// 启用日或者星期互斥用
TYPE_NOT_SET,
TYPE_EVERY,
TYPE_RANGE,
TYPE_LOOP,
TYPE_WORK,
TYPE_LAST,
TYPE_SPECIFY,
// 对于不同的类型,所定义的值也有所不同
valueRange: {
start: 0,
end: 0
},
valueLoop: {
start: 0,
interval: 1
},
valueWeek: {
start: 0,
end: 0
},
valueList: [],
valueWork: 1,
maxValue: 0,
minValue: 0
}
},
watch: {
prop (newVal, oldVal) {
if (newVal === this.value_c) {
// console.info('skip ' + newVal)
return
}
this.parseProp(newVal)
}
},
computed: {
value_c () {
let result = []
switch (this.type) {
case TYPE_NOT_SET:
result.push('?')
break
case TYPE_EVERY:
result.push('*')
break
case TYPE_RANGE:
result.push(`${this.valueRange.start}-${this.valueRange.end}`)
break
case TYPE_LOOP:
result.push(`${this.valueLoop.start}/${this.valueLoop.interval}`)
break
case TYPE_WORK:
result.push(`${this.valueWork}W`)
break
case TYPE_LAST:
result.push('L')
break
case TYPE_SPECIFY:
if (this.valueList.length === 0) {
this.valueList.push(this.minValue)
}
result.push(this.valueList.join(','))
break
default:
result.push(this.DEFAULT_VALUE)
break
}
return result.length > 0 ? result.join('') : this.DEFAULT_VALUE
},
// 指定值范围区间,介于最小值和最大值之间
specifyRange() {
let range = []
for (let i = this.minValue; i <= this.maxValue; i++) {
range.push(i)
}
return range
},
},
methods: {
parseProp (value) {
if (value === this.value_c) {
// console.info('same ' + value)
return
}
if (typeof (this.preProcessProp) === 'function') {
value = this.preProcessProp(value)
}
try {
if (!value || value === this.DEFAULT_VALUE) {
this.type = TYPE_EVERY
} else if (value.indexOf('?') >= 0) {
this.type = TYPE_NOT_SET
} else if (value.indexOf('-') >= 0) {
this.type = TYPE_RANGE
const values = value.split('-')
if (values.length >= 2) {
this.valueRange.start = parseInt(values[0])
this.valueRange.end = parseInt(values[1])
}
} else if (value.indexOf('/') >= 0) {
this.type = TYPE_LOOP
const values = value.split('/')
if (values.length >= 2) {
this.valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0])
this.valueLoop.interval = parseInt(values[1])
}
} else if (value.indexOf('W') >= 0) {
this.type = TYPE_WORK
const values = value.split('W')
if (!values[0] && !isNaN(values[0])) {
this.valueWork = parseInt(values[0])
}
} else if (value.indexOf('L') >= 0) {
this.type = TYPE_LAST
const values = value.split('L')
this.valueLast = parseInt(values[0])
} else if (value.indexOf(',') >= 0 || !isNaN(value)) {
this.type = TYPE_SPECIFY
this.valueList = value.split(',').map(item => parseInt(item))
} else {
this.type = TYPE_EVERY
}
} catch (e) {
// console.info(e)
this.type = TYPE_EVERY
}
}
}
}

View File

@ -0,0 +1,35 @@
.config-list {
text-align: left;
margin: 0 10px 10px 10px;
}
.item {
margin-top: 5px;
}
.choice {
padding: 5px 8px;
}
.w60 {
width: 60px;
}
.w80 {
width: 80px;
}
.list {
margin: 0 20px;
}
.list-check-item {
padding: 1px 3px;
width: 4em;
}
.tip-info {
color: #999
}

View File

@ -0,0 +1,67 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每月</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
月开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
<div class="item">
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
<div class="list">
<a-checkbox-group v-model="valueList">
<template v-for="i of specifyRange">
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
</template>
</a-checkbox-group>
</div>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
export default {
name: 'month',
mixins: [mixin],
data() {
return {}
},
watch: {
value_c(newVal, oldVal) {
this.$emit('change', newVal)
}
},
created() {
this.DEFAULT_VALUE = '*'
this.minValue = 1
this.maxValue = 12
this.valueRange.start = 1
this.valueRange.end = 12
this.valueLoop.start = 1
this.valueLoop.interval = 1
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,68 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每秒</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
秒开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
<div class="item">
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
<div class="list">
<a-checkbox-group v-model="valueList">
<template v-for="i in specifyRange">
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
</template>
</a-checkbox-group>
</div>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
export default {
name: 'second',
mixins: [mixin],
data() {
return {}
},
watch: {
value_c(newVal, oldVal) {
this.$emit('change', newVal)
}
},
created() {
this.DEFAULT_VALUE = '*'
this.minValue = 0
this.maxValue = 59
this.valueRange.start = 0
this.valueRange.end = 59
this.valueLoop.start = 0
this.valueLoop.interval = 1
// console.info('created')
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,118 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
<span class="tip-info">日和周只能设置其中之一</span>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
<a-select v-model="valueRange.start" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
<template v-for="(v, k) of WEEK_MAP">
<a-select-option :value="v">{{k}}</a-select-option>
</template>
</a-select>
<a-select v-model="valueRange.end" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
<template v-for="(v, k) of WEEK_MAP">
<a-select-option :value="v">{{k}}</a-select-option>
</template>
</a-select>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
<a-select v-model="valueLoop.start" class="w80" :disabled="type!==TYPE_LOOP || disableChoice">
<template v-for="(v, k) of WEEK_MAP">
<a-select-option :value="v">{{k}}</a-select-option>
</template>
</a-select>
开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
<div class="item">
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
<div class="list">
<a-checkbox-group v-model="valueList">
<template v-for="i in specifyRange">
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
</template>
</a-checkbox-group>
</div>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
import { replaceWeekName, WEEK_MAP_EN } from './const.js'
const WEEK_MAP = {
'周一': 1,
'周二': 2,
'周三': 3,
'周四': 4,
'周五': 5,
'周六': 6,
// 按照国人习惯,将周日放到每周的最后一天
'周日': 7,
}
export default {
name: 'week',
mixins: [mixin],
props: {
day: {
type: String,
default: '*'
}
},
data() {
return {
WEEK_MAP,
WEEK_MAP_EN
}
},
computed: {
disableChoice() {
return (this.day && this.day !== '?') || this.disabled
}
},
watch: {
value_c(newVal, oldVal) {
// 如果设置日,那么星期就直接不设置
this.updateValue()
},
day(newVal) {
// console.info('new day: ' + newVal)
this.updateValue()
}
},
methods: {
updateValue() {
this.$emit('change', this.disableChoice ? '?' : this.value_c)
},
preProcessProp(c) {
return replaceWeekName(c)
}
},
created() {
this.DEFAULT_VALUE = '*'
// 0,7表示周日 1表示周一
this.minValue = 1
this.maxValue = 7
this.valueRange.start = 1
this.valueRange.end = 7
this.valueLoop.start = 2
this.valueLoop.interval = 1
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,60 @@
<template>
<div class="config-list">
<a-radio-group v-model="type">
<div class="item">
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每年</a-radio>
</div>
<div class="item">
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="0" :precision="0" class="w60" v-model="valueRange.start"/>
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="1" :precision="0" class="w60" v-model="valueRange.end"/>
</div>
<div class="item">
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="0" :precision="0" class="w60" v-model="valueLoop.start"/>
年开始,间隔
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="1" :precision="0" class="w60" v-model="valueLoop.interval"/>
</div>
</a-radio-group>
</div>
</template>
<script>
import mixin from './mixin'
export default {
name: 'year',
mixins: [mixin],
data() {
return {}
},
watch: {
value_c(newVal, oldVal) {
// console.info('change:' + newVal)
this.$emit('change', newVal)
}
},
created() {
const nowYear = (new Date()).getFullYear()
this.DEFAULT_VALUE = '*'
this.minValue = 0
this.maxValue = 0
this.valueRange.start = nowYear
this.valueRange.end = nowYear + 100
this.valueLoop.start = nowYear
this.valueLoop.interval = 1
// console.info('created')
this.parseProp(this.prop)
}
}
</script>
<style lang="less" scoped>
@import "mixin.less";
</style>

View File

@ -0,0 +1,51 @@
import CronParser from 'cron-parser'
import { replaceWeekName } from './tabs/const'
export default (rule, value, callback) => {
// 没填写就不校验
if (!value) {
callback()
return true
}
const values = value.split(' ').filter(item => !!item)
if (values.length > 7) {
callback(new Error('Cron表达式最多7项'))
return false
}
// 检查第7项
let e = value
if (values.length === 7) {
const year = replaceWeekName(values[6])
if (year !== '*' && year !== '?') {
let yearValues = []
if (year.indexOf('-') >= 0) {
yearValues = year.split('-')
} else if (year.indexOf('/')) {
yearValues = year.split('/')
} else {
yearValues = [year]
}
// console.info(yearValues)
// 判断是否都是数字
const checkYear = yearValues.some(item => isNaN(item))
if (checkYear) {
callback(new Error('Cron表达式参数[]错误' + year))
return false
}
}
// 取其中的前六项
e = values.slice(0, 6).join(' ')
}
// 6位 没有年
// 5位没有秒、年
let result = true
try {
const iter = CronParser.parseExpression(e)
iter.next()
callback()
} catch (e) {
callback(new Error('Cron表达式错误' + e))
result = false
}
return result
}

View File

@ -1,5 +1,5 @@
<!-- JEditableTable -->
<!-- @version 1.6.1 -->
<!-- @version 1.6.2 -->
<!-- @author sjlei -->
<template>
<a-spin :spinning="loading">
@ -11,7 +11,33 @@
<a-col>
<!-- 操作按钮 -->
<div v-if="actionButton" class="action-button">
<a-button v-if="buttonPermission('add')" type="primary" icon="plus" @click="handleClickAdd" :disabled="disabled">新增</a-button>
<a-button-group v-if="buttonPermission('add')">
<a-button type="primary" icon="plus" @click="handleClickAdd" :disabled="disabled">新增</a-button>
<a-popover v-if="addButtonSettings" placement="right" overlayClassName="j-add-btn-settings">
<a-row slot="title">
<a-col :span="12">选项</a-col>
<a-col :span="12" style="text-align: right;">
<a-tooltip title="保存为默认值">
<a-button type="link" icon="save" size="small" style="position: relative;left:4px;" @click="onAddButtonSettingsSave"/>
</a-tooltip>
</a-col>
</a-row>
<template slot="content">
<a-form-model layout="horizontal" :labelCol="{span:8}" :wrapperCol="{span:16}">
<a-form-model-item label="添加行数">
<a-input-number v-model="settings.addRowNum" :min="1"/>
</a-form-model-item>
<a-form-model-item label="添加位置">
<a-input-number v-model="settings.addIndex" :min="0" :max="rows.length"/>
<p style="font-size: 12px;color:#aaa;line-height: 14px;text-align: right;margin: 0;">0 = 最底部</p>
</a-form-model-item>
<a-divider style="margin: 8px 0;"/>
<a-checkbox v-model="settings.addScrollToBottom">添加后滚动到底部</a-checkbox>
</a-form-model>
</template>
<a-button icon="setting" type="primary"></a-button>
</a-popover>
</a-button-group>
<span class="gap"></span>
<template v-if="selectedRowIds.length>0">
<a-popconfirm
@ -215,7 +241,7 @@
:value="departCompValues[id]"
:placeholder="replaceProps(col, col.placeholder)"
:trigger-change="true"
:multi="true"
:multi="isMultipleSelect(col)"
@change="(v)=>handleChangeDepartCommon(v,id,row,col)"
/>
<span
@ -239,7 +265,7 @@
:value="userCompValues[id]"
:placeholder="replaceProps(col, col.placeholder)"
:trigger-change="true"
:multi="true"
:multi="isMultipleSelect(col)"
@change="(v)=>handleChangeUserCommon(v,id,row,col)"
/>
<span
@ -277,8 +303,33 @@
>{{ jdateValues[id] }}</span>
</a-tooltip>
</template>
<!-- time -->
<template v-else-if="col.type === formTypes.time">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<j-time
v-if="isEditRow(row, col)"
:id="id"
:key="i"
v-bind="buildProps(row,col)"
style="width: 100%;"
:value="jdateValues[id]"
:getCalendarContainer="getParentContainer"
:placeholder="replaceProps(col, col.placeholder)"
allowClear
@change="(v)=>handleChangeJDateCommon(v,id,row,col)"
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ jdateValues[id] }}</span>
</a-tooltip>
</template>
<!-- input_pop -->
<template v-else-if="col.type === formTypes.input_pop">
<template v-else-if="col.type === formTypes.input_pop||col.type === 'textarea'">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<j-input-pop
v-if="isEditRow(row, col)"
@ -318,7 +369,7 @@
<a-tooltip v-else-if="file.status==='done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-tooltip v-else :title="file.message||'上传失败'">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
@ -350,7 +401,7 @@
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-upload
name="file"
:data="{'isup':1}"
:data="{'isup':1, ...(col.data||{})}"
:multiple="false"
:action="col.action"
:headers="uploadGetHeaders(row,col)"
@ -381,6 +432,8 @@
:dest-fields="col.destFields"
:code="col.popupCode"
:groupId="caseId"
:param="col.param"
:sorter="col.sorter"
@input="(value,others)=>popupCallback(value,others,id,row,col,rowIndex)"
/>
<span
@ -395,7 +448,7 @@
<!-- update-beign-author:taoyan date:0827 for文件/图片逻辑新增 -->
<div v-else-if="col.type === formTypes.file" :key="i">
<template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<template v-if="hasUploadValue(id)" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<div :key="fileKey" style="position: relative;">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading" style="color:red;"/>
@ -407,9 +460,9 @@
<span style="margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
</a-tooltip>
<a-tooltip v-else :title="file.name">
<a-icon type="paper-clip" style="color:red;"/>
<span style="color:red;margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
<a-tooltip v-else :title="file.message||'上传失败'">
<a-icon type="exclamation-circle" style="color:red;"/>
<span style="margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
</a-tooltip>
<template style="width: 30px">
@ -434,7 +487,7 @@
</div>
</template>
<div :hidden="uploadValues[id] != null">
<div :hidden="hasUploadValue(id)">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-upload
name="file"
@ -454,7 +507,7 @@
</div>
<div v-else-if="col.type === formTypes.image" :key="i">
<template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<template v-if="hasUploadValue(id)" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<div :key="fileKey" style="position: relative;">
<template v-if="!uploadValues[id] || !(uploadValues[id]['url'] || uploadValues[id]['path'] || uploadValues[id]['message'])">
<a-icon type="loading"/>
@ -462,20 +515,9 @@
<template v-else-if="uploadValues[id]['path']">
<img class="j-editable-image" :src="getCellImageView(id)" alt="无图片" @click="handleMoreOperation(id,'img',col)"/>
</template>
<template v-else>
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError(id)"/>
</template>
<template slot="addonBefore" style="width: 30px">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
<a-tooltip v-else :title="file.message||'上传失败'" @click="handleClickShowImageError(id)">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
@ -503,7 +545,7 @@
</div>
</template>
<div :hidden="uploadValues[id] != null">
<div :hidden="hasUploadValue(id)">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-upload
name="file"
@ -595,6 +637,33 @@
</template>
<!-- select搜索 -end -->
<!-- select异步搜索 -begin -->
<template v-else-if="col.type === formTypes.sel_search_async">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<j-search-select-tag
v-if="isEditRow(row, col)"
:id="id"
:key="i"
:value="searchSelectAsyncValues[id]"
:placeholder="replaceProps(col, col.placeholder)"
:dict="col.dict"
:async="true"
:getPopupContainer="getParentContainer"
v-bind="buildProps(row,col)"
style="width: 100%;"
@change="(v)=>handleSearchSelectAsyncChange(v,id,row,col)"
>
</j-search-select-tag>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ searchSelectAsyncValues[id] }}</span>
</a-tooltip>
</template>
<!-- select异步搜索 -end -->
<div v-else-if="col.type === formTypes.slot" :key="i">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<slot
@ -615,7 +684,7 @@
</div>
<!-- else (normal) -->
<span v-else :key="i" v-bind="buildProps(row,col)">{{ inputValues[rowIndex][col.key] }}</span>
<span class="comp-normal" v-else :key="i" :title="inputValues[rowIndex][col.key]" v-bind="buildProps(row,col)">{{ inputValues[rowIndex][col.key] }}</span>
</template>
</div>
</div>
@ -672,13 +741,13 @@
import Draggable from 'vuedraggable'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { FormTypes, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
import { cloneObject, randomString, randomNumber, getEventPath } from '@/utils/util'
import { cloneObject, getEventPath, randomNumber, randomString } from '@/utils/util'
import JDate from '@/components/jeecg/JDate'
import { filterDictText, initDictOptions } from '@/components/dict/JDictSelectUtil'
import { getFileAccessHttpUrl } from '@/api/manage';
import { getFileAccessHttpUrl } from '@/api/manage'
import JInputPop from '@/components/jeecg/minipop/JInputPop'
import JFilePop from '@/components/jeecg/minipop/JFilePop'
import { getNoAuthCols } from "@/utils/authFilter"
import { getNoAuthCols } from '@/utils/authFilter'
// 行高,需要在实例加载完成前用到
let rowHeight = 61
@ -709,6 +778,11 @@
type: Boolean,
default: false
},
// 是否显示添加按钮选项
addButtonSettings: {
type: Boolean,
default: false
},
// 是否显示行号
rowNumber: {
type: Boolean,
@ -814,6 +888,7 @@
metaCheckboxValues: {},
multiSelectValues: {},
searchSelectValues: {},
searchSelectAsyncValues: {},
// 绑定左侧选择框已选择的id
selectedRowIds: [],
// 存储被删除行的id
@ -836,7 +911,16 @@
lastPushTimeMap: new Map(),
number:0,
//不显示的按钮编码
excludeCode:[]
excludeCode:[],
// 选项配置
settings: {
// 添加行数
addRowNum: 1,
// 添加位置下标0 = 最底部
addIndex: 0,
// 添加后滚动到底部
addScrollToBottom: false,
},
}
},
created() {
@ -851,6 +935,7 @@
event.stopPropagation()
}
}
this.getSavedAddButtonSettings()
},
// 计算属性
computed: {
@ -1003,7 +1088,11 @@
},
methods: {
// 判断文件/图片是否存在
hasUploadValue(id){
let flag = this.uploadValues[id] != null && this.uploadValues[id].toString().length>0
return flag;
},
getElement(id, noCaseId = false) {
if (!this.el[id]) {
this.el[id] = document.getElementById((noCaseId ? '' : this.caseId) + id)
@ -1048,6 +1137,10 @@
this.inputValues = []
this.rows = []
this.deleteIds = []
this.selectedRowIds = []
this.tooltips = {}
this.notPassedIds = []
// 重置values
this.selectValues = {}
this.checkboxValues = {}
this.jdateValues = {}
@ -1055,14 +1148,16 @@
this.departCompValues = {}
this.userCompValues = {}
this.slotValues = {}
this.selectedRowIds = []
this.tooltips = {}
this.notPassedIds = []
this.uploadValues = []
this.popupValues = []
this.radioValues = []
this.multiSelectValues = []
this.searchSelectValues = []
//update-begin-author:shunjlei date:20210415 for:类型赋值错误
this.uploadValues = {}
this.popupValues = {}
this.radioValues = {}
this.multiSelectValues = {}
this.searchSelectValues = {}
this.searchSelectAsyncValues = {}
//update-end-author:shunjlei date:20210415 for:类型赋值错误
// 重置滚动条
this.scrollTop = 0
this.$nextTick(() => {
this.getElement('tbody').scrollTop = 0
@ -1136,6 +1231,7 @@
let radioValues = { ...this.radioValues }
let multiSelectValues = { ...this.multiSelectValues }
let searchSelectValues = { ...this.searchSelectValues }
let searchSelectAsyncValues = { ...this.searchSelectAsyncValues }
// 禁用行的id
let disabledRowIds = (this.disabledRowIds || [])
dataSource.forEach((data, newValueIndex) => {
@ -1207,7 +1303,7 @@
selectValues[inputId] = undefined
}
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime || column.type === FormTypes.time) {
jdateValues[inputId] = sourceValue
} else if (column.type === FormTypes.slot) {
@ -1219,12 +1315,14 @@
departCompValues[inputId] = sourceValue
} else if (column.type === FormTypes.sel_user) {
userCompValues[inputId] = sourceValue
} else if (column.type === FormTypes.input_pop) {
} else if (column.type === FormTypes.input_pop || column.type === 'textarea') {
jInputPopValues[inputId] = sourceValue
} else if (column.type === FormTypes.radio) {
radioValues[inputId] = sourceValue
} else if (column.type === FormTypes.sel_search) {
searchSelectValues[inputId] = sourceValue
} else if (column.type === FormTypes.sel_search_async) {
searchSelectAsyncValues[inputId] = sourceValue
} else if (column.type === FormTypes.list_multi) {
if (typeof sourceValue === 'string' && sourceValue.length > 0) {
multiSelectValues[inputId] = sourceValue.split(',')
@ -1245,6 +1343,8 @@
status: 'done',
path: sourceValue
}
} else {
uploadValues[inputId] = null
}
} else {
value[column.key] = sourceValue
@ -1309,6 +1409,7 @@
this.radioValues = radioValues
this.multiSelectValues = multiSelectValues
this.searchSelectValues = searchSelectValues
this.searchSelectAsyncValues = searchSelectAsyncValues
// 重新计算所有统计列
this.recalcAllStatisticsColumns()
// 更新到 dom
@ -1370,22 +1471,18 @@
let tbody = this.getElement('tbody')
let offsetHeight = tbody.offsetHeight
let realScrollTop = tbody.scrollTop + offsetHeight
if (forceScrollToBottom === false) {
// 只有滚动条在底部的时候才自动滚动
if (!((tbody.scrollHeight - realScrollTop) <= 10)) {
return
}
if (forceScrollToBottom) {
this.$nextTick(() => {
this.resetScrollTop(this.$refs.scrollView.scrollHeight)
})
}
this.$nextTick(() => {
tbody.scrollTop = tbody.scrollHeight
})
},
/**
* 在指定位置添加一行
* @param insertIndex 添加位置下标
* @param num 添加的行数默认1
*/
insert(insertIndex, num = 1) {
insert(insertIndex, num = 1, forceScrollToBottom = false) {
if (this.checkTooFastClick('insert', 1500)) {
return
}
@ -1413,6 +1510,12 @@
num, insertIndex,
target: this
})
// 设置滚动条位置
if (forceScrollToBottom) {
this.$nextTick(() => {
this.resetScrollTop(this.$refs.scrollView.scrollHeight)
})
}
},
/** 删除被选中的行 */
removeSelectedRows() {
@ -1514,7 +1617,7 @@
value[column.key] = selected
}
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime || column.type === FormTypes.time) {
value[column.key] = this.jdateValues[inputId]
} else if (column.type === FormTypes.sel_depart) {
@ -1523,7 +1626,7 @@
} else if (column.type === FormTypes.sel_user) {
value[column.key] = this.userCompValues[inputId]
} else if (column.type === FormTypes.input_pop) {
} else if (column.type === FormTypes.input_pop || column.type === 'textarea') {
value[column.key] = this.jInputPopValues[inputId]
} else if (column.type === FormTypes.upload) {
@ -1543,6 +1646,8 @@
value[column.key] = this.radioValues[inputId]
} else if (column.type === FormTypes.sel_search) {
value[column.key] = this.searchSelectValues[inputId]
} else if (column.type === FormTypes.sel_search_async) {
value[column.key] = this.searchSelectAsyncValues[inputId]
} else if (column.type === FormTypes.list_multi) {
if (!this.multiSelectValues[inputId] || this.multiSelectValues[inputId].length === 0) {
value[column.key] = ''
@ -1671,6 +1776,7 @@
radioValues: this.radioValues,
multiSelectValues: this.multiSelectValues,
searchSelectValues: this.searchSelectValues,
searchSelectAsyncValues: this.searchSelectAsyncValues,
})
},
/** 设置某行某列的值 */
@ -1716,13 +1822,13 @@
}
this.$set(this.checkboxValues, key, sourceValue)
edited = true
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime || column.type === FormTypes.time) {
edited = this.setOneValue(this.jdateValues, modelKey, newValue)
} else if (column.type === FormTypes.sel_depart) {
edited = this.setOneValue(this.departCompValues, modelKey, newValue)
} else if (column.type === FormTypes.sel_user) {
edited = this.setOneValue(this.userCompValues, modelKey, newValue)
} else if (column.type === FormTypes.input_pop) {
} else if (column.type === FormTypes.input_pop || column.type === 'textarea') {
edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
} else if (column.type === FormTypes.slot) {
edited = this.setOneValue(this.slotValues, modelKey, newValue)
@ -1736,6 +1842,8 @@
edited = this.setOneValue(this.multiSelectValues, modelKey, newValue, true)
} else if (column.type === FormTypes.sel_search) {
edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
} else if (column.type === FormTypes.sel_search_async) {
edited = this.setOneValue(this.searchSelectAsyncValues, modelKey, newValue)
} else {
edited = false
}
@ -2048,7 +2156,12 @@
},
handleClickAdd() {
this.add()
let {addRowNum, addIndex, addScrollToBottom} = this.settings
if (addIndex <= 0) {
this.add(addRowNum, addScrollToBottom)
} else {
this.insert(addIndex, addRowNum, addScrollToBottom)
}
},
handleConfirmDelete() {
this.removeSelectedRows()
@ -2059,6 +2172,29 @@
clearSelection() {
this.selectedRowIds = []
},
// 获取当前选中的行
getSelection() {
return this.selectedRowIds.map(id => this.getCleanId(id))
},
// 设置当前选中的行
async setSelection(selectedRowIds) {
if (Array.isArray(selectedRowIds) && selectedRowIds.length > 0) {
// 兼容IE
await this.getElementPromise('tbody')
await this.$nextTick()
this.selectedRowIds = selectedRowIds.map(id => {
let temp = id
if (!this.hasCaseId(id)) {
temp = this.caseId + id
}
return temp
})
}
},
// 切换全选状态
toggleSelectionAll() {
this.handleChangeCheckedAll()
},
/** 用于搜索下拉框中的内容 */
handleSelectFilterOption(input, option, column) {
if (column.allowSearch === true || column.allowInput === true) {
@ -2266,11 +2402,7 @@
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
if (showTime) {
this.elemValueChange(FormTypes.datetime, row, column, value)
} else {
this.elemValueChange(FormTypes.date, row, column, value)
}
this.elemValueChange(column.type, row, column, value)
},
//部门组件值改变
handleChangeDepartCommon(value, id, row, column){
@ -2306,7 +2438,21 @@
value['responseName'] = file.response[column.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[column.responseName]
if (typeof file.response.success === 'boolean') {
// 如果文件上传被拦截器拦下还会返回最外层的status = done
// 但是内部的success会返回false并携带异常信息
// 整个上传操作还是失败的
// https://github.com/zhangdaiscott/jeecg-boot/issues/2691
if (file.response.success) {
value['path'] = file.response[column.responseName]
} else {
value['status'] = 'error'
value['message'] = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台可能不会返回 success 属性的情况,就默认为成功
value['path'] = file.response[column.responseName]
}
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
@ -2368,6 +2514,25 @@
})
}
},
/** 添加按钮设置保存为默认值 */
onAddButtonSettingsSave() {
let obj = {
addRowNum: this.settings.addRowNum,
addIndex: this.settings.addIndex,
addScrollToBottom: this.settings.addScrollToBottom,
}
this.$ls.set('jet-add-btn-settings', obj)
this.$message.success('保存成功')
},
/** 获取保存的添加按钮默认值 */
getSavedAddButtonSettings() {
let obj= this.$ls.get('jet-add-btn-settings')
if (obj) {
Object.assign(this.settings, obj)
}
},
/** 记录用到数据绑定的组件的值 */
bindValuesChange(value, id, key) {
this.$set(this[key], id, value)
@ -2619,6 +2784,11 @@
if (col.type === FormTypes.select && (col.allowInput === true || col.allowSearch === true)) {
props['showSearch'] = true
}
if (col.type === FormTypes.sel_depart || col.type === FormTypes.sel_user) {
let { storeField, textField } = this.getStoreAndTextField(col)
props['store'] = storeField
props['text'] = textField
}
// 判断是否是禁用的列
props['disabled'] = (typeof col['disabled'] === 'boolean' ? col['disabled'] : props['disabled'])
@ -2636,6 +2806,42 @@
return props
},
/**获取部门选择 、用户选择的存储字段、展示字段*/
getStoreAndTextField(col){
let storeField = '', textField = ''
if(col.type === FormTypes.sel_depart){
storeField = 'id'
textField = 'departName'
}else if(col.type === FormTypes.sel_user){
storeField = 'username'
textField = 'realname'
}
if(col.fieldExtendJson){
// online逻辑
let tempJson = JSON.parse(col.fieldExtendJson)
if(tempJson){
if(tempJson.store){
storeField = tempJson.store
}
if(tempJson.text){
textField = tempJson.text
}
}
}else{
// 实际开发逻辑
if(col.store){
storeField = col.store
}
if(col.text){
textField = col.text
}
}
return {
storeField,
textField
}
},
/** 辅助方法:防止过快点击,如果点击过快的话就返回 true */
checkTooFastClick(key = 'default', ms = 300) {
let nowTime = Date.now()
@ -2704,7 +2910,7 @@
}
this.setOneValue(this.popupValues, id, popupValue)
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
this.validateOneInput(popupValue, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange('input', row, column, value)
},
@ -2731,6 +2937,11 @@
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
this.elemValueChange(FormTypes.sel_search, row, column, value)
},
handleSearchSelectAsyncChange(value, id, row, column) {
this.searchSelectAsyncValues = this.bindValuesChange(value, id, 'searchSelectAsyncValues')
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
this.elemValueChange(FormTypes.sel_search_async, row, column, value)
},
filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
},
@ -2844,6 +3055,21 @@
}else{
return this.excludeCode.indexOf(code)<0
}
},
// 判断用户、部门组件是否多选
isMultipleSelect(column){
let jsonStr = column.fieldExtendJson
if(jsonStr){
// online
let config = JSON.parse(jsonStr)
if(config && config['multiSelect']==false){
return false
}
}else if(column.multi==false){
// 实际开发
return false
}
return true;
}
},
@ -2992,6 +3218,8 @@
border-bottom: @border;
transition: background-color 300ms;
width: 100%;
height: 61px;
overflow: hidden;
position: absolute;
left: 0;
z-index: 10;
@ -3101,6 +3329,12 @@
}
}
.comp-normal {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.j-td-span {
position: relative;
padding: 4px 11px;
@ -3220,3 +3454,19 @@
}
</style>
<style lang="less">
// 新增按钮配置气泡的样式
.j-add-btn-settings {
width: 240px;
.ant-form {
.ant-form-item {
margin-bottom: 0;
.ant-input-number {
width: 100%;
}
}
}
}
</style>

View File

@ -128,12 +128,23 @@
this.reload()
}
})
//update--begin--autor:liusq-----date:20210316------for富文本编辑器tab父组件可能导致的赋值问题------
this.reload()
//update--end--autor:liusq-----date:20210316------for富文本编辑器tab父组件可能导致的赋值问题------
}else{
//update--begin--autor:wangshuai-----date:20200724------for富文本编辑器切换tab无法修改------
let tabLayout = getVmParentByName(this, 'TabLayout')
tabLayout.excuteCallback(()=>{
this.reload()
})
//update--begin--autor:liusq-----date:20210713------for处理特殊情况excuteCallback不能使用------
try {
tabLayout.excuteCallback(() => {
this.reload()
})
} catch (error) {
if (tabLayout) {
this.reload()
}
}
//update--end--autor:liusq-----date:20210713------for处理特殊情况excuteCallback不能使用------
//update--begin--autor:wangshuai-----date:20200724------for文本编辑器切换tab无法修改------
}
},

View File

@ -11,13 +11,13 @@
:beforeUpload="beforeUpload"
:disabled="disabled"
:isMultiple="isMultiple"
:showUploadList="isMultiple"
@change="handleChange"
@preview="handlePreview"
:class="!isMultiple?'imgupload':''">
<div :style="{'width':(!isMultiple?'104px':'auto'),'height':(!isMultiple?'104px':'auto')}">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>
<div v-else class="iconp">
:class="[!isMultiple?'imgupload':'', (!isMultiple && picUrl)?'image-upload-single-over':'' ]">
<div>
<!--<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>-->
<div class="iconp">
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
<div class="ant-upload-text">{{ text }}</div>
</div>
@ -189,7 +189,7 @@
path = ''
}
let arr = [];
if(!this.isMultiple){
if(!this.isMultiple && uploadFiles.length>0){
arr.push(uploadFiles[uploadFiles.length-1].response.message)
}else{
for(let a=0;a<uploadFiles.length;a++){
@ -231,8 +231,9 @@
* https://github.com/zhangdaiscott/jeecg-boot/issues/1810
* https://github.com/zhangdaiscott/jeecg-boot/issues/1779
*/
/deep/ .imgupload .ant-upload-select{display:block}
/deep/ .imgupload .ant-upload.ant-upload-select-picture-card{ width:120px;height: 120px;}
/deep/ .imgupload .iconp{padding:32px;}
/deep/ .imgupload .iconp{padding:20px;}
/* update--end--autor:lvdandan-----date:20201016------forj-image-upload图片组件单张图片详情回显空白*/
/deep/ .image-upload-single-over .ant-upload-select{display: none}
</style>

View File

@ -6,6 +6,13 @@
:confirmLoading="uploading"
@cancel="handleClose">
<div style="margin: 0px 0px 5px 1px" v-if="online">
<span style="display: inline-block;height: 32px;line-height: 32px;vertical-align: middle;">是否开启校验:</span>
<span style="display: inline-block;height: 32px;margin-left: 6px">
<a-switch :checked="validateStatus==1" @change="handleChangeValidateStatus" checked-children="" un-checked-children="" size="small"/>
</span>
</div>
<a-upload
name="file"
:multiple="true"
@ -47,6 +54,12 @@
type: String,
default: '',
required: false
},
//是否online导入
online:{
type: Boolean,
default: false,
required: false
}
},
data(){
@ -55,7 +68,8 @@
uploading:false,
fileList:[],
uploadAction:'',
foreignKeys:''
foreignKeys:'',
validateStatus: 0
}
},
watch: {
@ -78,6 +92,7 @@
this.uploading = false
this.visible = true
this.foreignKeys = arg;
this.validateStatus = 0
},
handleRemove(file) {
const index = this.fileList.indexOf(file);
@ -98,6 +113,9 @@
if(this.foreignKeys && this.foreignKeys.length>0){
formData.append('foreignKeys',this.foreignKeys);
}
if(this.online==true){
formData.append('validateStatus',this.validateStatus);
}
fileList.forEach((file) => {
formData.append('files[]', file);
});
@ -105,14 +123,41 @@
postAction(this.uploadAction, formData).then((res) => {
this.uploading = false
if(res.success){
this.$message.success(res.message)
if(res.code == 201){
this.errorTip(res.message, res.result)
}else{
this.$message.success(res.message)
}
this.visible=false
this.$emit('ok')
}else{
this.$message.warning(res.message)
}
})
}
},
// 是否开启校验 开关改变事件
handleChangeValidateStatus(checked){
this.validateStatus = checked==true?1:0
},
// 错误信息提示
errorTip(tipMessage, fileUrl) {
const h = this.$createElement;
let href = window._CONFIG['domianURL'] + fileUrl
this.$warning({
title: '导入成功,但是有错误数据!',
content: h('div', {}, [
h('div', tipMessage),
h('span', '具体详情请 '),
h('a', {
attrs: {
href: href,
target: '_blank'
},
},'点击下载'),
]),
onOk() {},
});
},
}
}

View File

@ -8,10 +8,11 @@
v-on="$listeners"
@ok="handleOk"
@cancel="handleCancel"
destroyOnClose
>
<slot></slot>
<!--有设置标题-->
<template v-if="!isNoTitle" slot="title">
<a-row class="j-modal-title-row" type="flex">
<a-col class="left">
@ -22,6 +23,14 @@
</a-col>
</a-row>
</template>
<!--没有设置标题-->
<template v-else slot="title">
<a-row class="j-modal-title-row" type="flex">
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
</a-col>
</a-row>
</template>
<!-- 处理 scopedSlots -->
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
@ -38,10 +47,10 @@
<script>
import { getClass, getStyle } from '@/utils/props-util'
import { triggerWindowResizeEvent } from '@/utils/util'
import { getClass, getStyle } from '@/utils/props-util'
import { triggerWindowResizeEvent } from '@/utils/util'
export default {
export default {
name: 'JModal',
props: {
title: String,
@ -160,8 +169,8 @@
</script>
<style lang="less">
.j-modal-box {
&.fullscreen {
top: 0;
left: 0;
@ -190,7 +199,6 @@
height: calc(100% - 55px);
}
}
&.no-title.no-footer {
.ant-modal-body {
height: 100%;
@ -217,6 +225,12 @@
}
}
}
&.no-title{
.ant-modal-header {
padding: 0px 24px;
border-bottom: 0px !important;
}
}
}
@media (max-width: 767px) {

View File

@ -0,0 +1,124 @@
<template>
<j-modal :visible="visible" :confirmLoading="loading" :after-close="afterClose" v-bind="modalProps" @ok="onOk" @cancel="onCancel">
<a-spin :spinning="loading">
<div v-html="content"></div>
<a-form-model ref="form" :model="model" :rules="rules">
<a-form-model-item prop="input">
<a-input ref="input" v-model="model.input" v-bind="inputProps" @pressEnter="onInputPressEnter"/>
</a-form-model-item>
</a-form-model>
</a-spin>
</j-modal>
</template>
<script>
import pick from 'lodash.pick'
export default {
name: 'JPrompt',
data() {
return {
visible: false,
loading: false,
content: '',
// 弹窗参数
modalProps: {
title: '',
},
inputProps: {
placeholder: '',
},
// form model
model: {
input: '',
},
// 校验
rule: [],
// 回调函数
callback: {},
}
},
computed: {
rules() {
return {
input: this.rule
}
},
},
methods: {
show(options) {
this.content = options.content
if (Array.isArray(options.rule)) {
this.rule = options.rule
}
if (options.defaultValue != null) {
this.model.input = options.defaultValue
}
// 取出常用的弹窗参数
let pickModalProps = pick(options, 'title', 'centered', 'cancelText', 'closable', 'mask', 'maskClosable', 'okText', 'okType', 'okButtonProps', 'cancelButtonProps', 'width', 'wrapClassName', 'zIndex', 'dialogStyle', 'dialogClass')
this.modalProps = Object.assign({}, pickModalProps, options.modalProps)
// 取出常用的input参数
let pickInputProps = pick(options, 'placeholder', 'allowClear')
this.inputProps = Object.assign({}, pickInputProps, options.inputProps)
// 回调函数
this.callback = pick(options, 'onOk', 'onOkAsync', 'onCancel')
this.visible = true
this.$nextTick(() => this.$refs.input.focus())
},
onOk() {
this.$refs.form.validate((ok, err) => {
if (ok) {
let event = {value: this.model.input, target: this}
// 异步方法优先级高于同步方法
if (typeof this.callback.onOkAsync === 'function') {
this.callback.onOkAsync(event)
} else if (typeof this.callback.onOk === 'function') {
this.callback.onOk(event)
this.close()
} else {
this.close()
}
}
})
},
onCancel() {
if (typeof this.callback.onCancel === 'function') {
this.callback.onCancel(this.model.input)
}
this.close()
},
onInputPressEnter() {
this.onOk()
},
close() {
this.visible = this.loading ? this.visible : false
},
forceClose() {
this.visible = false
},
showLoading() {
this.loading = true
},
hideLoading() {
this.loading = false
},
afterClose(e) {
if (typeof this.modalProps.afterClose === 'function') {
this.modalProps.afterClose(e)
}
this.$emit('after-close', e)
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,18 @@
import JModal from './JModal'
import JPrompt from './JPrompt'
export default {
install(Vue) {
Vue.component(JModal.name, JModal)
const JPromptExtend = Vue.extend(JPrompt)
Vue.prototype.$JPrompt = function (options = {}) {
// 创建prompt实例
const vm = new JPromptExtend().$mount()
vm.show(options)
// 关闭后销毁
vm.$on('after-close', () => vm.$destroy())
return vm
}
},
}

View File

@ -10,6 +10,7 @@
ref="jPopupOnlReport"
:code="code"
:multi="multi"
:sorter="sorter"
:groupId="uniqGroupId"
:param="param"
@ok="callBack"
@ -47,6 +48,11 @@
default: '',
required: false
},
/** 排序列,指定要排序的列,使用方式:列名=desc|asc */
sorter: {
type: String,
default: ''
},
width: {
type: Number,
default: 1200,
@ -167,9 +173,11 @@
let tempDestArr = []
for(let rw of rows){
let val = rw[orgFieldsArr[i]]
if(!val){
// update--begin--autor:liusq-----date:20210713------for处理val等于0的情况issues/I3ZL4T------
if(typeof val=='undefined'|| val==null || val.toString()==""){
val = ""
}
// update--end--autor:liusq-----date:20210713------for处理val等于0的情况issues/I3ZL4T------
tempDestArr.push(val)
}
res[destFieldsArr[i]] = tempDestArr.join(",")

View File

@ -1,7 +1,7 @@
<template>
<a-select :value="arrayValue" @change="onChange" mode="multiple" :placeholder="placeholder">
<a-select :value="arrayValue" @change="onChange" mode="multiple" :placeholder="placeholder" allowClear>
<a-select-option
v-for="(item,index) in options"
v-for="(item,index) in selectOptions"
:key="index"
:getPopupContainer="getParentContainer"
:value="item.value">
@ -12,6 +12,8 @@
<script>
//option {label:,value:}
import { getAction } from '@api/manage'
export default {
name: 'JSelectMultiple',
props: {
@ -31,7 +33,8 @@
},
options:{
type: Array,
required: true
default:()=>[],
required: false
},
triggerChange:{
type: Boolean,
@ -48,12 +51,22 @@
default:'',
required:false
},
dictCode:{
type:String,
required:false
},
},
data(){
return {
arrayValue:!this.value?[]:this.value.split(this.spliter)
arrayValue:!this.value?[]:this.value.split(this.spliter),
dictOptions: [],
}
},
computed:{
selectOptions(){
return this.dictOptions.length > 0 ? this.dictOptions : this.options
},
},
watch:{
value (val) {
if(!val){
@ -63,6 +76,11 @@
}
}
},
mounted(){
if (this.dictCode) {
this.loadDictOptions()
}
},
methods:{
onChange (selectedValue) {
if(this.triggerChange){
@ -77,7 +95,18 @@
}else{
return document.querySelector(this.popContainer)
}
}
},
// 根据字典code查询字典项
loadDictOptions(){
getAction(`/sys/dict/getDictItems/${this.dictCode}`,{}).then(res=>{
if (res.success) {
this.dictOptions = res.result.map(item => ({value: item.value, label: item.text}))
} else {
console.error('getDictItems error: : ', res)
this.dictOptions = []
}
})
},
},
}

View File

@ -379,7 +379,7 @@
this.$message.warn("不能查询空条件")
}
},
emitCallback(event = {}) {
emitCallback(event = {}, loadStatus = true) {
let { params = [], matchType = this.matchType } = event
this.superQueryFlag = (params && params.length > 0)
for (let param of params) {
@ -388,7 +388,7 @@
}
}
console.debug('---高级查询参数--->', { params, matchType })
this.$emit(this.callback, params, matchType)
this.$emit(this.callback, params, matchType, loadStatus)
},
handleCancel() {
this.close()
@ -412,8 +412,9 @@
this.queryParamsModel.splice(index, 1)
},
handleSelected(node, item) {
let { type, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
let { type, dbType, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
item['type'] = type
item['dbType'] = dbType
item['options'] = options
item['dictCode'] = dictCode
item['dictTable'] = dictTable
@ -427,9 +428,13 @@
handleOpen() {
this.show()
},
handleOutReset(loadStaus=true) {
this.resetLine()
this.emitCallback({}, loadStaus)
},
handleReset() {
this.resetLine()
this.emitCallback()
this.emitCallback({}, true)
},
handleSave() {
let queryParams = this.removeEmptyObject(this.queryParamsModel)

View File

@ -12,12 +12,12 @@
<a-upload
name="file"
:multiple="true"
:multiple="multiple"
:action="uploadAction"
:headers="headers"
:data="{'biz':bizPath}"
:fileList="fileList"
:beforeUpload="beforeUpload"
:beforeUpload="doBeforeUpload"
@change="handleChange"
:disabled="disabled"
:returnUrl="returnUrl"
@ -135,6 +135,13 @@
required:false,
default: true
},
multiple: {
type: Boolean,
default: true
},
beforeUpload: {
type: Function
},
},
watch:{
value:{
@ -238,7 +245,7 @@
}
this.$emit('change', path);
},
beforeUpload(file){
doBeforeUpload(file){
this.uploadGoOn=true
var fileType = file.type;
if(this.fileType===FILE_TYPE_IMG){
@ -248,7 +255,10 @@
return false;
}
}
//TODO 扩展功能验证文件大小
// 扩展 beforeUpload 验证
if (typeof this.beforeUpload === 'function') {
return this.beforeUpload(file)
}
return true
},
handleChange(info) {

View File

@ -13,7 +13,7 @@ import JVxeDetailsModal from './JVxeDetailsModal'
import JVxePagination from './JVxePagination'
import { cloneObject, getVmParentByName, pushIfNotExist, randomString, simpleDebounce } from '@/utils/util'
import { UtilTools } from 'vxe-table/packages/tools/src/utils'
import { getNoAuthCols } from "@/utils/authFilter"
import { getNoAuthCols } from '@/utils/authFilter'
export default {
name: 'JVxeTable',
@ -95,6 +95,11 @@ export default {
// 是否异步删除行,如果你要实现异步删除,那么需要把这个选项开启,
// 在remove事件里调用confirmRemove方法才会真正删除除非删除的全是新增的行
asyncRemove: PropTypes.bool.def(false),
// 是否一直显示组件如果为false则只有点击的时候才出现组件
// 注:该参数不能动态修改;如果行、列字段多的情况下,会根据机器性能造成不同程度的卡顿。
alwaysEdit: PropTypes.bool.def(false),
// 联动配置,数组,详情配置见文档
linkageConfig: PropTypes.array.def(() => []),
},
data() {
return {
@ -148,7 +153,10 @@ export default {
// 允许执行刷新特效的行ID
reloadEffectRowKeysMap: {},
//配置了但是没有授权的按钮和列 集合
excludeCode:[]
excludeCode:[],
// 联动下拉选项(用于隔离不同的下拉选项)
// 内部联动配置map
_innerLinkageConfig: null,
}
},
computed: {
@ -175,6 +183,18 @@ export default {
renderOptions.target = this
}
}
// 处理联动列,联动列只能作用于 select 组件
if (column.$type === JVXETypes.select && this._innerLinkageConfig != null) {
// 判断当前列是否是联动列
if (this._innerLinkageConfig.has(column.key)) {
renderOptions.linkage = {
config: this._innerLinkageConfig.get(column.key),
getLinkageOptionsSibling: this.getLinkageOptionsSibling,
getLinkageOptionsAsync: this.getLinkageOptionsAsync,
linkageSelectChange: this.linkageSelectChange,
}
}
}
if (column.editRender) {
Object.assign(column.editRender, renderOptions)
}
@ -275,15 +295,21 @@ export default {
immediate: true,
async handler() {
let vxe = await getRefPromise(this, 'vxe')
// 阻断vue监听大数据提高性能
// 开启了排序就自动计算排序值
if (this.dragSort) {
this.dataSource.forEach((data, idx) => {
this.dataSource.forEach((data, idx) => {
// 开启了排序就自动计算排序值
if (this.dragSort) {
this.$set(data, this.dragSortKey, idx + 1)
})
}
}
// 处理联动回显数据
if (this._innerLinkageConfig != null) {
for (let configItem of this._innerLinkageConfig.values()) {
this.autoSetLinkageOptionsByData(data, '', configItem, 0)
}
}
})
// 阻断vue监听大数据提高性能
vxe.loadData(this.dataSource)
// TODO 解析disabledRows
@ -354,7 +380,7 @@ export default {
col.visible = false
} else if (enhanced.switches.editRender) {
renderName = 'editRender'
renderOptions.type = enhanced.switches.visible ? 'visible' : 'default'
renderOptions.type = (enhanced.switches.visible || this.alwaysEdit) ? 'visible' : 'default'
}
} else {
renderOptions.name = JVXETypes._prefix + JVXETypes.normal
@ -466,7 +492,40 @@ export default {
this._innerColumns = _innerColumns
this._innerEditRules = _innerEditRules
}
}
},
// watch linkageConfig
// 整理多级联动配置
linkageConfig: {
immediate: true,
handler() {
if (Array.isArray(this.linkageConfig) && this.linkageConfig.length > 0) {
// 获取联动的key顺序
let getLcKeys = (key, arr) => {
let col = this._innerColumns.find(col => col.key === key)
if (col) {
arr.push(col.key)
if (col.linkageKey) {
return getLcKeys(col.linkageKey, arr)
}
}
return arr
}
let configMap = new Map()
this.linkageConfig.forEach(lc => {
let keys = getLcKeys(lc.key, [])
// 多个key共享一个引用地址
let configItem = {
...lc, keys,
optionsMap: new Map()
}
keys.forEach(k => configMap.set(k, configItem))
})
this._innerLinkageConfig = configMap
} else {
this._innerLinkageConfig = null
}
}
},
},
created() {
},
@ -665,7 +724,24 @@ export default {
async loadNewData(dataSource) {
if (Array.isArray(dataSource)) {
let {xTable} = this.$refs.vxe.$refs
return await xTable.loadData(dataSource)
// issues/2784
// 先清空所有数据
xTable.loadData([])
dataSource.forEach((data, idx) => {
// 开启了排序就自动计算排序值
if (this.dragSort) {
this.$set(data, this.dragSortKey, idx + 1)
}
// 处理联动回显数据
if (this._innerLinkageConfig != null) {
for (let configItem of this._innerLinkageConfig.values()) {
this.autoSetLinkageOptionsByData(data, '', configItem, 0)
}
}
})
// 再新增
return xTable.insertAt(dataSource)
}
return []
},
@ -790,6 +866,7 @@ export default {
* 添加一行或多行
*
* @param rows
* @param isOnlJs 是否是onlineJS增强触发的
* @return
*/
async addRows(rows = {}, isOnlJs) {
@ -889,6 +966,89 @@ export default {
this.$emit(name, event)
},
/** 【多级联动】获取同级联动下拉选项 */
getLinkageOptionsSibling(row, col, config, request) {
// 如果当前列不是顶级列
let key = ''
if (col.key !== config.key) {
// 就找出联动上级列
let idx = config.keys.findIndex(k => col.key === k)
let parentKey = config.keys[idx - 1]
key = row[parentKey]
// 如果联动上级列没有选择数据,就直接返回空数组
if (key === '' || key == null) {
return []
}
} else {
key = 'root'
}
let options = config.optionsMap.get(key)
if (!Array.isArray(options)) {
if (request) {
let parent = key === 'root' ? '' : key
return this.getLinkageOptionsAsync(config, parent)
} else {
options = []
}
}
return options
},
/** 【多级联动】获取联动下拉选项(异步) */
getLinkageOptionsAsync(config, parent) {
return new Promise(resolve => {
let key = parent ? parent : 'root'
let options
if (config.optionsMap.has(key)) {
options = config.optionsMap.get(key)
if (options instanceof Promise) {
options.then(opt => {
config.optionsMap.set(key, opt)
resolve(opt)
})
} else {
resolve(options)
}
} else if (typeof config.requestData === 'function') {
// 调用requestData方法通过传入parent来获取子级
let promise = config.requestData(parent)
config.optionsMap.set(key, promise)
promise.then(opt => {
config.optionsMap.set(key, opt)
resolve(opt)
})
} else {
resolve([])
}
})
},
// 【多级联动】 用于回显数据,自动填充 optionsMap
autoSetLinkageOptionsByData(data, parent, config, level) {
if (level === 0) {
this.getLinkageOptionsAsync(config, '')
} else {
this.getLinkageOptionsAsync(config, parent)
}
if (config.keys.length - 1 > level) {
let value = data[config.keys[level]]
if (value) {
this.autoSetLinkageOptionsByData(data, value, config, level + 1)
}
}
},
// 【多级联动】联动组件change时清空下级组件
linkageSelectChange(row, col, config, value) {
if (col.linkageKey) {
this.getLinkageOptionsAsync(config, value)
let idx = config.keys.findIndex(k => k === col.key)
let values = {}
for (let i = idx; i < config.keys.length; i++) {
values[config.keys[i]] = ''
}
// 清空后几列的数据
this.setValues([{rowKey: row.id, values}])
}
},
/** 加载数据字典并合并到 options */
_loadDictConcatToOptions(column) {
initDictOptions(column.dictCode).then((res) => {
@ -1076,11 +1236,20 @@ export default {
// 添加默认值
xTable.tableFullColumn.forEach(column => {
let col = column.own
if (record[col.key] == null || record[col.key] === '') {
if (col.key && (record[col.key] == null || record[col.key] === '')) {
// 设置默认值
let createValue = getEnhancedMixins(col.$type || col.type, 'createValue')
record[col.key] = createValue({row: record, column, $table: xTable})
}
// update-begin--author:sunjianlei---date:20210819------for: 处理联动列,联动列只能作用于 select 组件
if (col.$type === JVXETypes.select && this._innerLinkageConfig != null) {
// 判断当前列是否是联动列
if (this._innerLinkageConfig.has(col.key)) {
let configItem = this._innerLinkageConfig.get(col.key)
this.getLinkageOptionsAsync(configItem, '')
}
}
// update-end--author:sunjianlei---date:20210819------for: 处理联动列,联动列只能作用于 select 组件
})
return record
},

View File

@ -55,7 +55,9 @@
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event)),
editActived(event) {
dispatchEvent.call(this, event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event))
},
},
}
}

View File

@ -16,6 +16,8 @@
:multi="multi"
:rootOpened="rootOpened"
:depart-id="departIds"
:store="storeField()"
:text="textField()"
@ok="handleOK"
@initComp="initComp"/>
<span style="display: inline-block;height:100%;padding-left:14px" v-if="departIds" >
@ -68,7 +70,7 @@
}
},
multi(){
if(this.cellProps.multi==false){
if(this.cellProps.multi==false || this.originColumn.multi===false){
return false
}else{
return true
@ -101,15 +103,17 @@
handleEmpty(){
this.handleOK('')
},
handleOK(rows, idstr) {
handleOK(rows) {
let value = ''
if (!rows && rows.length <= 0) {
this.departNames = ''
this.departIds = ''
} else {
value = rows.map(row => row[this.customReturnField]).join(',')
this.departNames = rows.map(row => row['departName']).join(',')
this.departIds = idstr
let storeField = this.storeField();
let textField = this.textField();
value = rows.map(row => row[storeField]).join(',')
this.departNames = rows.map(row => row[textField]).join(',')
this.departIds = value
}
this.handleChangeCommon(this.departIds)
},
@ -118,6 +122,34 @@
},
handleChange(value) {
this.handleChangeCommon(value)
},
storeField(){
if(this.originColumn){
const str = this.originColumn.fieldExtendJson
if(str){
let json = JSON.parse(str)
if(json && json.store){
return json.store
}
}else if(this.originColumn.store){
return this.originColumn.store
}
}
return 'id'
},
textField(){
if(this.originColumn){
const str = this.originColumn.fieldExtendJson
if(str){
let json = JSON.parse(str)
if(json && json.text){
return json.text
}
}else if(this.originColumn.text){
return this.originColumn.text
}
}
return 'departName'
}
},
enhanced: {

View File

@ -34,7 +34,7 @@
return this.rowIndex === 0
},
disabledMoveDown() {
return this.rowIndex === (this.rows.length - 1)
return this.rowIndex === (this.fullDataLength - 1)
},
},
methods: {

View File

@ -7,11 +7,16 @@
v-bind="selectProps"
style="width: 100%;"
@blur="handleBlur"
@change="handleChangeCommon"
@change="handleChange"
@search="handleSearchSelect"
>
<template v-for="option of originColumn.options">
<div v-if="loading" slot="notFoundContent">
<a-icon type="loading" />
<span>&nbsp;加载中</span>
</div>
<template v-for="option of selectOptions">
<a-select-option :key="option.value" :value="option.value" :disabled="option.disabled">
<span>{{option.text || option.label || option.title|| option.value}}</span>
</a-select-option>
@ -23,10 +28,18 @@
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { JVXETypes } from '@comp/jeecg/JVxeTable/index'
import { filterDictText } from '@comp/dict/JDictSelectUtil'
export default {
name: 'JVxeSelectCell',
mixins: [JVxeCellMixins],
data(){
return {
loading: false,
// 异步加载的options用于多级联动
asyncOptions: null,
}
},
computed: {
selectProps() {
let props = {...this.cellProps}
@ -37,6 +50,32 @@
}
return props
},
// 下拉选项
selectOptions() {
if (this.asyncOptions) {
return this.asyncOptions
}
let {linkage} = this.renderOptions
if (linkage) {
let {getLinkageOptionsSibling, config} = linkage
let res = getLinkageOptionsSibling(this.row, this.originColumn, config, true)
// 当返回Promise时说明是多级联动
if (res instanceof Promise) {
this.loading = true
res.then(opt => {
this.asyncOptions = opt
this.loading = false
}).catch(e => {
console.error(e)
this.loading = false
})
} else {
this.asyncOptions = null
return res
}
}
return this.originColumn.options
},
},
created() {
let multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
@ -54,6 +93,15 @@
},
methods: {
handleChange(value) {
// 处理下级联动
let linkage = this.renderOptions.linkage
if (linkage) {
linkage.linkageSelectChange(this.row, this.originColumn, linkage.config, value)
}
this.handleChangeCommon(value)
},
/** 处理blur失去焦点事件 */
handleBlur(value) {
let {allowInput, options} = this.originColumn
@ -116,9 +164,32 @@
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-select'),
editActived(event) {
dispatchEvent.call(this, event, 'ant-select')
},
},
translate: {
enabled: true,
async handler(value,) {
let options
let {linkage} = this.renderOptions
// 判断是否是多级联动,如果是就通过接口异步翻译
if (linkage) {
let {getLinkageOptionsSibling, config} = linkage
options = getLinkageOptionsSibling(this.row, this.originColumn, config, true)
if (options instanceof Promise) {
return new Promise(resolve => {
options.then(opt => {
resolve(filterDictText(opt, value))
})
})
}
} else {
options = this.column.own.options
}
return filterDictText(options, value)
},
},
translate: {enabled: true},
getValue(value) {
if (Array.isArray(value)) {
return value.join(',')

View File

@ -23,7 +23,9 @@
autofocus: '.ant-input',
},
aopEvents: {
editActived: event => dispatchEvent(event, 'anticon-fullscreen'),
editActived(event) {
dispatchEvent.call(this, event, 'anticon-fullscreen')
},
},
},
}

View File

@ -0,0 +1,57 @@
<template>
<a-time-picker
ref="timePicker"
:value="innerDateValue"
allowClear
dropdownClassName="j-vxe-date-picker"
style="min-width: 0;"
v-bind="cellProps"
@change="handleChange"
/>
</template>
<script>
import moment from 'moment'
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeTimeCell',
mixins: [JVxeCellMixins],
props: {},
data() {
return {
innerDateValue: null,
dateFormat: 'HH:mm:ss'
}
},
watch: {
innerValue: {
immediate: true,
handler(val) {
if (val == null || val === '') {
this.innerDateValue = null
} else {
this.innerDateValue = moment(val, this.dateFormat)
}
}
}
},
methods: {
handleChange(mom, dateStr) {
this.handleChangeCommon(dateStr)
}
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived(event) {
dispatchEvent.call(this, event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event))
},
},
}
}
</script>
<style scoped>
</style>

View File

@ -14,7 +14,7 @@
<a-tooltip v-else-if="file.status === 'done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-tooltip v-else :title="file.message||'上传失败'">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
@ -118,8 +118,17 @@
value['responseName'] = file.response[col.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[col.responseName]
this.handleChangeCommon(value)
if (typeof file.response.success === 'boolean') {
if (file.response.success) {
value['path'] = file.response[col.responseName]
} else {
value['status'] = 'error'
value['message'] = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台可能不会返回 success 属性的情况,就默认为成功
value['path'] = file.response[col.responseName]
}
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}

View File

@ -16,6 +16,8 @@
:multi="multi"
:user-ids="userIds"
@ok="selectOK"
:store="storeField"
:text="textField"
@initComp="initComp"/>
<span style="display: inline-block;height:100%;padding-left:14px" v-if="userIds" >
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ userNames }}</span>
@ -77,6 +79,30 @@
}else{
return true
}
},
storeField(){
if(this.originColumn){
const str = this.originColumn.fieldExtendJson
if(str){
let json = JSON.parse(str)
if(json && json.store){
return json.store
}
}
}
return 'username'
},
textField(){
if(this.originColumn){
const str = this.originColumn.fieldExtendJson
if(str){
let json = JSON.parse(str)
if(json && json.text){
return json.text
}
}
}
return 'realname'
}
},
watch: {
@ -102,12 +128,8 @@
this.userNames = ''
this.userIds = ''
} else {
let temp = ''
for (let item of rows) {
temp += ',' + item.realname
}
this.userNames = temp.substring(1)
this.userIds = idstr
this.userIds = rows.map(row => row[this.storeField]).join(',')
this.userNames = rows.map(row => row[this.textField]).join(',')
}
this.handleChangeCommon(this.userIds)
},

View File

@ -6,6 +6,7 @@ import JVxeSlotCell from './components/cells/JVxeSlotCell'
import JVxeNormalCell from './components/cells/JVxeNormalCell'
import JVxeInputCell from './components/cells/JVxeInputCell'
import JVxeDateCell from './components/cells/JVxeDateCell'
import JVxeTimeCell from './components/cells/JVxeTimeCell'
import JVxeSelectCell from './components/cells/JVxeSelectCell'
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell'
import JVxeUploadCell from './components/cells/JVxeUploadCell'
@ -32,6 +33,7 @@ export const AllCells = {
...mapCell(JVXETypes.selectMultiple, JVxeSelectCell), // 下拉多选
...mapCell(JVXETypes.date, JVxeDateCell),
...mapCell(JVXETypes.datetime, JVxeDateCell),
...mapCell(JVXETypes.time, JVxeTimeCell),
...mapCell(JVXETypes.upload, JVxeUploadCell),
...mapCell(JVXETypes.textarea, JVxeTextareaCell),

View File

@ -22,6 +22,7 @@ export const JVXETypes = {
select: 'select',
date: 'date',
datetime: 'datetime',
time: 'time',
checkbox: 'checkbox',
upload: 'upload',
// 下拉搜索

View File

@ -36,6 +36,9 @@ export default {
rows() {
return this.params.data
},
fullDataLength() {
return this.params.$table.tableFullData.length
},
rowIndex() {
return this.params.rowIndex
},
@ -70,6 +73,16 @@ export default {
props['disabled'] = true
}
// update-begin-author:taoyan date:20211011 for: online表单附表用户选择器{"multiSelect":false}不生效,单表可以生效 #3036
let jsonStr = col['fieldExtendJson']
if(jsonStr){
let fieldExtendJson = JSON.parse(jsonStr)
if(fieldExtendJson && fieldExtendJson['multiSelect']==false){
props['multi'] = false
}
}
// update-end-author:taoyan date:20211011 for: online表单附表用户选择器{"multiSelect":false}不生效,单表可以生效 #3036
return props
},
},
@ -99,7 +112,13 @@ export default {
// 判断是否启用翻译
if (this.renderType === JVXERenderType.spaner && this.enhanced.translate.enabled) {
this.innerValue = this.enhanced.translate.handler.call(this, value)
let res = this.enhanced.translate.handler.call(this, value)
// 异步翻译,目前仅【多级联动】使用
if (res instanceof Promise) {
res.then(value => this.innerValue = value)
} else {
this.innerValue = res
}
}
},
},
@ -291,6 +310,10 @@ export function vModel(value, row, property) {
/** 模拟触发事件 */
export function dispatchEvent({cell, $event}, className, handler) {
// alwaysEdit 下不模拟触发事件,否者会导致触发两次
if (this && this.alwaysEdit) {
return
}
window.setTimeout(() => {
let element = cell.getElementsByClassName(className)
if (element && element.length > 0) {
@ -298,7 +321,9 @@ export function dispatchEvent({cell, $event}, className, handler) {
handler(element[0])
} else {
// 模拟触发点击事件
element[0].dispatchEvent($event)
if($event){
element[0].dispatchEvent($event)
}
}
}
}, 10)

View File

@ -141,6 +141,33 @@ export async function validateFormAndTables(form, cases, autoJumpTab) {
return dataMap
}
/**
* 一次性验证主表单和所有的次表单
* @param form 主表单 form 对象
* @param cases 接收一个数组每项都是一个JVxeTable实例
* @param autoJumpTab
* @returns {Promise<any>}
* @author sunjianlei
*/
export async function validateFormModelAndTables(form,formData, cases, autoJumpTab) {
if (!(form && typeof form.validate === 'function')) {
throw `form 参数需要的是一个form对象而传入的却是${typeof form}`
}
let dataMap = {}
let values = await new Promise((resolve, reject) => {
// 验证主表表单
form.validate((valid,obj) => {
valid ?resolve(formData): reject({error: VALIDATE_FAILED, originError: valid})
})
})
Object.assign(dataMap, {formValue: values})
// 验证所有子表的表单
let subData = await validateTables(cases, autoJumpTab)
// 合并最终数据
dataMap = Object.assign(dataMap, {tablesValue: subData})
return dataMap
}
/**
* 验证并获取一个或多个表格的所有值
*

View File

@ -26,18 +26,24 @@ import JSlider from './JSlider.vue'
import JSwitch from './JSwitch.vue'
import JTime from './JTime.vue'
import JTreeTable from './JTreeTable.vue'
import JEasyCron from '@/components/jeecg/JEasyCron'
//jeecgbiz
import JSelectDepart from '../jeecgbiz/JSelectDepart.vue'
import JSelectMultiUser from '../jeecgbiz/JSelectMultiUser.vue'
import JSelectPosition from '../jeecgbiz/JSelectPosition.vue'
import JSelectRole from '../jeecgbiz/JSelectRole.vue'
import JSelectUserByDep from '../jeecgbiz/JSelectUserByDep.vue'
//引入需要全局注册的js函数和变量
import { Modal, notification,message } from 'ant-design-vue'
import lodash_object from 'lodash'
import debounce from 'lodash/debounce'
import pick from 'lodash.pick'
import data from 'china-area-data'
export default {
install(Vue) {
Vue.use(JModal)
Vue.component('JMarkdownEditor', JMarkdownEditor)
Vue.component(JModal.name, JModal)
Vue.component('JPopupOnlReport', JPopupOnlReport)
Vue.component('JFilePop', JFilePop)
Vue.component('JInputPop', JInputPop)
@ -71,5 +77,15 @@ export default {
Vue.component('JSelectPosition', JSelectPosition)
Vue.component('JSelectRole', JSelectRole)
Vue.component('JSelectUserByDep', JSelectUserByDep)
Vue.component(JEasyCron.name, JEasyCron)
//注册全局js函数和变量
Vue.prototype.$Jnotification = notification
Vue.prototype.$Jmodal = Modal
Vue.prototype.$Jmessage = message
Vue.prototype.$Jlodash = lodash_object
Vue.prototype.$Jdebounce= debounce
Vue.prototype.$Jpick = pick
Vue.prototype.$Jpcaa = data
}
}

View File

@ -7,7 +7,7 @@
<a-icon type="close" @click="visible=false"/>
</span>
</div>
<a-input :value="inputContent" :disabled="disabled" @change="handleInputChange">
<a-input :value="inputContent" :disabled="disabled" @change="handleInputChange" :placeholder="placeholder">
<a-icon slot="suffix" type="fullscreen" @click.stop="pop" />
</a-input>
<div slot="content">
@ -53,6 +53,10 @@
type: Boolean,
default: false,
},
placeholder:{
type:String,
required:false
}
},
data(){

View File

@ -1,6 +1,6 @@
<template>
<a-modal
title="corn表达式"
title="cron表达式"
:width="modalWidth"
:visible="visible"
:confirmLoading="confirmLoading"

View File

@ -74,11 +74,12 @@
import {filterObj} from '@/utils/util'
import { filterMultiDictText } from '@/components/dict/JDictSelectUtil'
import { httpGroupRequest } from '@/api/GroupRequest.js'
import md5 from 'md5'
const MODAL_WIDTH = 1200;
export default {
name: 'JPopupOnlReport',
props: ['multi', 'code', 'groupId', 'param'],
props: ['multi', 'code', 'sorter', 'groupId', 'param'],
components:{
},
data(){
@ -122,8 +123,9 @@
cgRpConfigId:"",
modalWidth:MODAL_WIDTH,
tableScroll:{x:true},
dynamicParam:{}
dynamicParam:{},
// 排序字段,默认无排序
iSorter: null,
}
},
mounted() {
@ -136,10 +138,35 @@
param:{
deep:true,
handler(){
this.dynamicParamHandler()
this.loadData();
// update--begin--autor:liusq-----date:20210706------forJPopup组件在modal中使用报错#2729------
if(this.visible){
this.dynamicParamHandler()
this.loadData();
}
// update--begin--autor:liusq-----date:20210706------forJPopup组件在modal中使用报错#2729------
},
}
},
sorter: {
immediate: true,
handler() {
if (this.sorter) {
let arr = this.sorter.split('=')
if (arr.length === 2 && ['asc', 'desc'].includes(arr[1].toLowerCase())) {
this.iSorter = {column: arr[0], order: arr[1].toLowerCase()}
// 排序字段受控
this.table.columns.forEach(col => {
if (col.dataIndex === this.iSorter.column) {
this.$set(col, 'sortOrder', this.iSorter.order === 'asc' ? 'ascend' : 'descend')
} else {
this.$set(col, 'sortOrder', false)
}
})
} else {
console.warn('【JPopup】sorter参数不合法')
}
}
},
},
},
computed:{
showSearchFlag(){
@ -167,6 +194,10 @@
return filterMultiDictText(this.dictOptions[dictCode], text+"");
}
}
// 排序字段受控
if (this.iSorter && currColumns[a].dataIndex === this.iSorter.column) {
currColumns[a].sortOrder = this.iSorter.order === 'asc' ? 'ascend' : 'descend'
}
}
this.table.columns = [...currColumns]
this.initQueryInfo()
@ -253,7 +284,7 @@
paramTarget['self_'+key] = this.dynamicParam[key]
})
}
let param = Object.assign(paramTarget, this.queryParam, this.sorter);
let param = Object.assign(paramTarget, this.queryParam, this.iSorter);
param.pageNo = this.table.pagination.current;
param.pageSize = this.table.pagination.pageSize;
return filterObj(param);
@ -288,8 +319,18 @@
handleChangeInTable(pagination, filters, sorter) {
//分页、排序、筛选变化时触发
if (Object.keys(sorter).length > 0) {
this.sorter.column = sorter.field
this.sorter.order = 'ascend' == sorter.order ? 'asc' : 'desc'
this.iSorter = {
column: sorter.field,
order: 'ascend' === sorter.order ? 'asc' : 'desc'
}
// 排序字段受控
this.table.columns.forEach(col => {
if (col.dataIndex === sorter.field) {
this.$set(col, 'sortOrder',sorter.order)
} else {
this.$set(col, 'sortOrder', false)
}
})
}
this.table.pagination = pagination
this.loadData()
@ -342,11 +383,20 @@
combineRowKey(record){
let res = ''
Object.keys(record).forEach(key=>{
res+=record[key]
//update-begin---author:liusq Date:20210203 forpop选择器列主键问题 issues/I29P9Q------------
if(key=='id'){
res=record[key]+res
}else{
res+=record[key]
}
//update-end---author:liusq Date:20210203 forpop选择器列主键问题 issues/I29P9Q------------
})
if(res.length>50){
// update-begin---author:taoyan Date:20211025 forjpopup 表格key重复BUG /issues/3121
res = md5(res)
/*if(res.length>50){
res = res.substring(0,50)
}
}*/
// update-end---author:taoyan Date:20211025 forjpopup 表格key重复BUG /issues/3121
return res
},

View File

@ -1,9 +1,9 @@
<template>
<div class="components-input-demo-presuffix">
<!---->
<a-input @click="openModal" placeholder="请点击选择部门" v-model="departNames" readOnly :disabled="disabled">
<a-input @click="openModal" placeholder="请点击选择部门" v-model="textVals" readOnly :disabled="disabled">
<a-icon slot="prefix" type="cluster" title="部门选择控件"/>
<a-icon v-if="departIds" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
<a-icon v-if="storeVals" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<j-select-depart-modal
@ -11,7 +11,10 @@
:modal-width="modalWidth"
:multi="multi"
:rootOpened="rootOpened"
:depart-id="departIds"
:depart-id="value"
:store="storeField"
:text="textField"
:treeOpera="treeOpera"
@ok="handleOK"
@initComp="initComp"/>
</div>
@ -19,6 +22,7 @@
<script>
import JSelectDepartModal from './modal/JSelectDepartModal'
import { underLinetoHump } from '@/components/_util/StringUtil'
export default {
name: 'JSelectDepart',
components:{
@ -52,57 +56,70 @@
// 自定义返回字段,默认返回 id
customReturnField: {
type: String,
default: 'id'
default: ''
},
backDepart: {
type: Boolean,
default: false,
required: false
},
// 存储字段 [key field]
store: {
type: String,
default: 'id',
required: false
},
// 显示字段 [label field]
text: {
type: String,
default: 'departName',
required: false
},
treeOpera: {
type: Boolean,
default: false,
required: false
}
},
data(){
return {
visible:false,
confirmLoading:false,
departNames:"",
departIds:''
storeVals: '', //[key values]
textVals: '' //[label values]
}
},
computed:{
storeField(){
let field = this.customReturnField
if(!field){
field = this.store;
}
return underLinetoHump(field)
},
textField(){
return underLinetoHump(this.text)
}
},
mounted(){
this.departIds = this.value
this.storeVals = this.value
},
watch:{
value(val){
//update-begin-author:wangshuai date:20201124 for:组件 JSelectDepart.vue不是默认id时新内容编辑问题 gitee I247X2
// if (this.customReturnField === 'id') {
this.departIds = val
// }
//update-end-author:wangshuai date:20201124 for:组件 JSelectDepart.vue不是默认id时新内容编辑问题 gitee I247X2
this.storeVals = val
}
},
methods:{
initComp(departNames){
this.departNames = departNames
//update-begin-author:lvdandan date:20200513 for:TESTA-438 部门选择组件自定义返回值,数据无法回填
//TODO 当返回字段为部门名称时会有问题,因为部门名称不唯一
//返回字段不为id时根据返回字段获取id
if(this.customReturnField !== 'id' && this.value){
const dataList = this.$refs.innerDepartSelectModal.dataList;
console.log('this.value',this.value)
this.departIds = this.value.split(',').map(item => {
const data = dataList.filter(d=>d[this.customReturnField] === item)
return data.length > 0 ? data[0].id : ''
}).join(',')
}
//update-end-author:lvdandan date:20200513 for:TESTA-438 部门选择组件自定义返回值,数据无法回填
this.backDeparInfo()
initComp(textVals){
this.textVals = textVals
},
//返回选中的部门信息
backDeparInfo(){
if(this.backDepart===true){
if(this.departIds && this.departIds.length>0){
let arr1 = this.departIds.split(',')
let arr2 = this.departNames.split(',')
let arr1 = this.storeVals.split(',')
let arr2 = this.textVals.split(',')
let info = []
for(let i=0;i<arr1.length;i++){
info.push({
@ -117,17 +134,22 @@
openModal(){
this.$refs.innerDepartSelectModal.show()
},
handleOK(rows, idstr) {
let value = ''
handleOK(rows) {
if (!rows && rows.length <= 0) {
this.departNames = ''
this.departIds = ''
this.textVals = ''
this.storeVals = ''
} else {
value = rows.map(row => row[this.customReturnField]).join(',')
this.departNames = rows.map(row => row['departName']).join(',')
this.departIds = idstr
let arr1 = []
let arr2 = []
for(let dep of rows){
arr1.push(dep[this.storeField])
arr2.push(dep[this.textField])
}
this.storeVals = arr1.join(',')
this.textVals = arr2.join(',')
}
this.$emit("change", value)
this.$emit("change", this.storeVals)
this.backDeparInfo()
},
getDepartNames(){
return this.departNames

View File

@ -1,19 +1,28 @@
<template>
<div>
<a-input-search
v-model="userNames"
v-model="textVals"
placeholder="请先选择用户"
readOnly
unselectable="on"
@search="onSearchDepUser">
<a-button slot="enterButton" :disabled="disabled">选择用户</a-button>
</a-input-search>
<j-select-user-by-dep-modal ref="selectModal" :modal-width="modalWidth" :multi="multi" @ok="selectOK" :user-ids="value" @initComp="initComp"/>
<j-select-user-by-dep-modal
ref="selectModal"
:modal-width="modalWidth"
:multi="multi"
@ok="selectOK"
:user-ids="value"
:store="storeField"
:text="textField"
@initComp="initComp"/>
</div>
</template>
<script>
import JSelectUserByDepModal from './modal/JSelectUserByDepModal'
import { underLinetoHump } from '@/components/_util/StringUtil'
export default {
name: 'JSelectUserByDep',
@ -42,20 +51,44 @@
type: Boolean,
default: false,
required: false
},
// 存储字段 [key field]
store: {
type: String,
default: 'username',
required: false
},
// 显示字段 [label field]
text: {
type: String,
default: 'realname',
required: false
}
},
data() {
return {
userIds: "",
userNames: ""
storeVals: '', //[key values]
textVals: '' //[label values]
}
},
computed:{
storeField(){
let field = this.customReturnField
if(!field){
field = this.store;
}
return underLinetoHump(field)
},
textField(){
return underLinetoHump(this.text)
}
},
mounted() {
this.userIds = this.value
this.storeVals = this.value
},
watch: {
value(val) {
this.userIds = val
this.storeVals = val
}
},
model: {
@ -63,15 +96,15 @@
event: 'change'
},
methods: {
initComp(userNames) {
this.userNames = userNames
initComp(textVals) {
this.textVals = textVals
},
//返回选中的用户信息
backDeparInfo(){
if(this.backUser===true){
if(this.userIds && this.userIds.length>0){
let arr1 = this.userIds.split(',')
let arr2 = this.userNames.split(',')
if(this.storeVals && this.storeVals.length>0){
let arr1 = this.storeVals.split(',')
let arr2 = this.textVals.split(',')
let info = []
for(let i=0;i<arr1.length;i++){
info.push({
@ -86,21 +119,22 @@
onSearchDepUser() {
this.$refs.selectModal.showModal()
},
selectOK(rows, idstr) {
selectOK(rows) {
console.log("当前选中用户", rows)
console.log("当前选中用户ID", idstr)
if (!rows) {
this.userNames = ''
this.userIds = ''
this.storeVals = ''
this.textVals = ''
} else {
let temp = ''
let temp1 = []
let temp2 = []
for (let item of rows) {
temp += ',' + item.realname
temp1.push(item[this.storeField])
temp2.push(item[this.textField])
}
this.userNames = temp.substring(1)
this.userIds = idstr
this.storeVals = temp1.join(',')
this.textVals = temp2.join(',')
}
this.$emit("change", this.userIds)
this.$emit("change", this.storeVals)
}
}
}

View File

@ -6,6 +6,7 @@
:confirmLoading="confirmLoading"
@ok="handleSubmit"
@cancel="handleCancel"
@update:fullscreen="isFullscreen"
wrapClassName="j-depart-select-modal"
switchFullscreen
cancelText="关闭">
@ -13,9 +14,9 @@
<a-input-search style="margin-bottom: 1px" placeholder="请输入部门名称按回车进行搜索" @search="onSearch" />
<a-tree
checkable
class="my-dept-select-tree"
:class="treeScreenClass"
:treeData="treeData"
:checkStrictly="true"
:checkStrictly="checkStrictly"
@check="onCheck"
@select="onSelect"
@expand="onExpand"
@ -32,8 +33,23 @@
<span v-else>{{title}}</span>
</template>
</a-tree>
</a-spin>
<!--底部父子关联操作和确认取消按钮-->
<template slot="footer" v-if="treeOpera && multi">
<div class="drawer-bootom-button">
<a-dropdown style="float: left" :trigger="['click']" placement="topCenter">
<a-menu slot="overlay">
<a-menu-item key="1" @click="switchCheckStrictly(1)">父子关联</a-menu-item>
<a-menu-item key="2" @click="switchCheckStrictly(2)">取消关联</a-menu-item>
</a-menu>
<a-button>
树操作 <a-icon type="up" />
</a-button>
</a-dropdown>
<a-button @click="handleCancel" type="primary" style="margin-right: 0.8rem">关闭</a-button>
<a-button @click="handleSubmit" type="primary" >确认</a-button>
</div>
</template>
</j-modal>
</template>
@ -41,7 +57,7 @@
import { queryDepartTreeList } from '@/api/api'
export default {
name: 'JSelectDepartModal',
props:['modalWidth','multi','rootOpened','departId'],
props:['modalWidth','multi','rootOpened','departId', 'store', 'text','treeOpera'],
data(){
return {
visible:false,
@ -52,7 +68,9 @@
dataList:[],
checkedKeys:[],
checkedRows:[],
searchValue:""
searchValue:"",
checkStrictly: true,
fullscreen:false
}
},
created(){
@ -64,15 +82,18 @@
},
visible: {
handler() {
if (this.departId) {
this.checkedKeys = this.departId.split(",");
// console.log('this.departId', this.departId)
} else {
this.checkedKeys = [];
}
this.initDepartComponent(true)
}
}
},
computed:{
treeScreenClass() {
return {
'my-dept-select-tree': true,
'fullscreen': this.fullscreen,
}
},
},
methods:{
show(){
this.visible=true
@ -80,6 +101,7 @@
this.checkedKeys=[]
},
loadDepart(){
// 这个方法是找到所有的部门信息
queryDepartTreeList().then(res=>{
if(res.success){
let arr = [...res.result]
@ -92,20 +114,23 @@
}
})
},
initDepartComponent(){
let names = ''
initDepartComponent(flag){
let arr = []
//该方法两个地方用 1.visible改变事件重新设置选中项 2.组件编辑页面回显
let fieldName = flag==true?'key':this.text
if(this.departId){
let currDepartId = this.departId
let arr2 = this.departId.split(',')
for(let item of this.dataList){
if(currDepartId.indexOf(item.key)>=0){
names+=","+item.title
if(arr2.indexOf(item[this.store])>=0){
arr.push(item[fieldName])
}
}
if(names){
names = names.substring(1)
}
}
this.$emit("initComp",names)
if(flag==true){
this.checkedKeys = [...arr]
}else{
this.$emit("initComp", arr.join(','))
}
},
reWriterWithSlot(arr){
for(let item of arr){
@ -129,8 +154,11 @@
}
}
this.expandedKeys=[...keys]
//全部keys
//this.allTreeKeys = [...keys]
}else{
this.expandedKeys=[]
//this.allTreeKeys = []
}
},
onCheck (checkedKeys,info) {
@ -139,25 +167,32 @@
this.checkedKeys = [...arr]
this.checkedRows = (this.checkedKeys.length === 0) ? [] : [info.node.dataRef]
}else{
this.checkedKeys = checkedKeys.checked
if(this.checkStrictly){
this.checkedKeys = checkedKeys.checked
}else{
this.checkedKeys = checkedKeys
}
this.checkedRows = this.getCheckedRows(this.checkedKeys)
}
},
onSelect(selectedKeys,info) {
let keys = []
keys.push(selectedKeys[0])
if(!this.checkedKeys || this.checkedKeys.length===0 || !this.multi){
this.checkedKeys = [...keys]
this.checkedRows=[info.node.dataRef]
}else{
let currKey = info.node.dataRef.key
if(this.checkedKeys.indexOf(currKey)>=0){
this.checkedKeys = this.checkedKeys.filter(item=> item !==currKey)
//取消关联的情况下才走onSelect的逻辑
if(this.checkStrictly){
let keys = []
keys.push(selectedKeys[0])
if(!this.checkedKeys || this.checkedKeys.length===0 || !this.multi){
this.checkedKeys = [...keys]
this.checkedRows=[info.node.dataRef]
}else{
this.checkedKeys.push(...keys)
let currKey = info.node.dataRef.key
if(this.checkedKeys.indexOf(currKey)>=0){
this.checkedKeys = this.checkedKeys.filter(item=> item !==currKey)
}else{
this.checkedKeys.push(...keys)
}
}
this.checkedRows = this.getCheckedRows(this.checkedKeys)
}
this.checkedRows = this.getCheckedRows(this.checkedKeys)
},
onExpand (expandedKeys) {
this.expandedKeys = expandedKeys
@ -167,7 +202,9 @@
if(!this.checkedKeys || this.checkedKeys.length==0){
this.$emit("ok",'')
}else{
this.$emit("ok",this.checkedRows,this.checkedKeys.join(","))
let checkRow = this.getCheckedRows(this.checkedKeys)
let keyStr = this.checkedKeys.join(",")
this.$emit("ok", checkRow, keyStr)
}
this.handleClear()
},
@ -233,6 +270,16 @@
}
}
return rows
},
switchCheckStrictly (v) {
if(v==1){
this.checkStrictly = false
}else if(v==2){
this.checkStrictly = true
}
},
isFullscreen(val){
this.fullscreen=val
}
}
}
@ -242,8 +289,22 @@
<style lang="less" scoped>
// 限制部门选择树高度,避免部门太多时点击确定不便
.my-dept-select-tree{
height: 350px;
height:350px;
&.fullscreen{
height: calc(100vh - 250px);
}
overflow-y: scroll;
}
.drawer-bootom-button {
position: absolute;
bottom: 0;
width: 100%;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
left: 0;
background: #fff;
border-radius: 0 0 2px 2px;
}
</style>

View File

@ -56,13 +56,14 @@
</template>
<script>
import {filterObj} from '@/utils/util'
import { pushIfNotExist, filterObj } from '@/utils/util'
import {queryDepartTreeList, getUserList, queryUserByDepId} from '@/api/api'
import { getAction } from '@/api/manage'
export default {
name: 'JSelectUserByDepModal',
components: {},
props: ['modalWidth', 'multi', 'userIds'],
props: ['modalWidth', 'multi', 'userIds', 'store', 'text'],
data() {
return {
queryParam: {
@ -106,6 +107,7 @@
],
scrollTrigger: {},
dataSource: [],
selectionRows: [],
selectedRowKeys: [],
selectUserRows: [],
selectUserIds: [],
@ -157,45 +159,43 @@
if (this.userIds) {
// 这里最后加一个 , 的原因是因为无论如何都要使用 in 查询,防止后台进行了模糊匹配,导致查询结果不准确
let values = this.userIds.split(',') + ','
getUserList({
username: values,
pageNo: 1,
pageSize: values.length
}).then((res) => {
if (res.success) {
let selectedRowKeys = []
let realNames = []
res.result.records.forEach(user => {
realNames.push(user['realname'])
let param = {[this.store]: values}
getAction('/sys/user/getMultiUser', param).then((list)=>{
this.selectionRows = []
let selectedRowKeys = []
let textArray = []
if(list && list.length>0){
for(let user of list){
textArray.push(user[this.text])
selectedRowKeys.push(user['id'])
})
this.selectedRowKeys = selectedRowKeys
this.$emit('initComp', realNames.join(','))
this.selectionRows.push(user)
}
}
this.selectedRowKeys = selectedRowKeys
this.$emit('initComp', textArray.join(','))
})
} else {
// JSelectUserByDep组件bug issues/I16634
this.$emit('initComp', '')
// 前端用户选择单选无法置空的问题 #2610
this.selectedRowKeys = []
}
},
async loadData(arg) {
if (arg === 1) {
this.ipagination.current = 1;
}
if (this.selectedDepIds && this.selectedDepIds.length > 0) {
await this.initQueryUserByDepId(this.selectedDepIds)
} else {
this.loading = true
let params = this.getQueryParams()//查询条件
await getUserList(params).then((res) => {
if (res.success) {
this.dataSource = res.result.records
this.ipagination.total = res.result.total
}
}).finally(() => {
this.loading = false
})
}
let params = this.getQueryParams()//查询条件
this.loading = true
getAction('/sys/user/queryUserComponentData', params).then(res=>{
if (res.success) {
this.dataSource = res.result.records
this.ipagination.total = res.result.total
}
}).finally(() => {
this.loading = false
})
},
// 触发屏幕自适应
resetScreenSize() {
@ -218,6 +218,7 @@
param.field = this.getQueryField();
param.pageNo = this.ipagination.current;
param.pageSize = this.ipagination.pageSize;
param.departId = this.selectedDepIds.join(',')
return filterObj(param);
},
getQueryField() {
@ -229,13 +230,13 @@
},
searchReset(num) {
let that = this;
that.selectedRowKeys = [];
that.selectUserIds = [];
that.selectedDepIds = [];
if (num !== 0) {
that.queryParam = {};
that.loadData(1);
}
that.selectedRowKeys = [];
that.selectUserIds = [];
that.selectedDepIds = [];
},
close() {
this.searchReset(0);
@ -253,35 +254,32 @@
handleSubmit() {
let that = this;
this.getSelectUserRows();
that.$emit('ok', that.selectUserRows, that.selectUserIds);
that.$emit('ok', that.selectUserRows);
that.searchReset(0)
that.close();
},
//获取选择用户信息
getSelectUserRows(rowId) {
let dataSource = this.dataSource;
let userIds = "";
this.selectUserRows = [];
for (let i = 0, len = dataSource.length; i < len; i++) {
if (this.selectedRowKeys.includes(dataSource[i].id)) {
this.selectUserRows.push(dataSource[i]);
userIds = userIds + "," + dataSource[i].username
getSelectUserRows() {
this.selectUserRows = []
for (let row of this.selectionRows) {
if (this.selectedRowKeys.includes(row.id)) {
this.selectUserRows.push(row)
}
}
this.selectUserIds = userIds.substring(1);
this.selectUserIds = this.selectUserRows.map(row => row.username).join(',')
},
// 点击树节点,筛选出对应的用户
onDepSelect(selectedDepIds) {
if (selectedDepIds[0] != null) {
this.initQueryUserByDepId(selectedDepIds); // 调用方法根据选选择的id查询用户信息
if (this.selectedDepIds[0] !== selectedDepIds[0]) {
this.selectedDepIds = [selectedDepIds[0]];
}
this.loadData(1);
}
},
onSelectChange(selectedRowKeys, selectionRows) {
this.selectedRowKeys = selectedRowKeys;
this.selectionRows = selectionRows;
selectionRows.forEach(row => pushIfNotExist(this.selectionRows, row, 'id'))
},
onSearch() {
this.loadData(1);

View File

@ -0,0 +1,214 @@
<template>
<span v-if="syncToApp || syncToLocal">
<j-third-app-dropdown v-if="enabledTypes.wechatEnterprise" type="wechatEnterprise" name="企微" v-bind="bindAttrs" v-on="bindEvents"/>
<j-third-app-dropdown v-if="enabledTypes.dingtalk" type="dingtalk" name="钉钉" v-bind="bindAttrs" v-on="bindEvents"/>
</span>
<span v-else>未设置任何同步方向</span>
</template>
<script>
import { getAction } from '@/api/manage'
import { cloneObject } from '@/utils/util'
import JThirdAppDropdown from './JThirdAppDropdown'
const backEndUrl = {
// 获取启用的第三方App
getEnabledType: '/sys/thirdApp/getEnabledType',
// 企业微信
wechatEnterprise: {
user: '/sys/thirdApp/sync/wechatEnterprise/user',
depart: '/sys/thirdApp/sync/wechatEnterprise/depart',
},
// 钉钉
dingtalk: {
user: '/sys/thirdApp/sync/dingtalk/user',
depart: '/sys/thirdApp/sync/dingtalk/depart',
}
}
export default {
name: 'JThirdAppButton',
components: {JThirdAppDropdown},
props: {
// 同步类型,可以是 user、depart
bizType: {
type: String,
required: true,
},
// 是否允许同步到第三方APP
syncToApp: Boolean,
// 是否允许第三方APP同步到本地
syncToLocal: Boolean,
// 选择的行
selectedRowKeys: Array,
},
data() {
return {
enabledTypes: {},
attrs: {
dingtalk: {},
},
}
},
computed: {
bindAttrs() {
return {
syncToApp: this.syncToApp,
syncToLocal: this.syncToLocal
}
},
bindEvents() {
return {
'to-app': this.onToApp,
'to-local': this.onToLocal,
}
},
},
created() {
this.loadEnabledTypes()
},
methods: {
handleMenuClick() {
console.log(arguments)
},
onToApp(e) {
this.doSync(e.type, '/toApp')
},
onToLocal(e) {
this.doSync(e.type, '/toLocal')
},
// 获取启用的第三方App
async loadEnabledTypes() {
this.enabledTypes = await loadEnabledTypes()
},
// 开始同步第三方App
doSync(type, direction) {
let urls = backEndUrl[type]
if (!(urls && urls[this.bizType])) {
console.warn('配置出错')
return
}
let url = urls[this.bizType] + direction
let selectedRowKeys = this.selectedRowKeys
let content = '确定要开始同步全部数据吗可能花费较长时间'
if (Array.isArray(selectedRowKeys) && selectedRowKeys.length > 0) {
content = `确定要开始同步这 ${selectedRowKeys.length} 项吗`
} else {
selectedRowKeys = []
}
return new Promise((resolve, reject) => {
let model = this.$confirm({
title: '同步',
content,
onOk: () => {
model.update({
keyboard: false,
okText: '同步中',
cancelButtonProps: {props: {disabled: true}}
})
return getAction(url, {
ids: selectedRowKeys.join(',')
}).then(res => {
let options = null
if (res.result) {
options = {
width: 600,
title: res.message,
content: (h) => {
let nodes
let successInfo = [
`成功信息如下`,
this.renderTextarea(h, res.result.successInfo.map((v, i) => `${i + 1}. ${v}`).join('\n')),
]
if (res.success) {
nodes = [
...successInfo,
h('br'),
`无失败信息`,
]
} else {
nodes = [
`失败信息如下`,
this.renderTextarea(h, res.result.failInfo.map((v, i) => `${i + 1}. ${v}`).join('\n')),
h('br'),
...successInfo,
]
}
return nodes
}
}
}
if (res.success) {
if (options != null) {
this.$success(options)
} else {
this.$message.success(res.message)
}
this.$emit('sync-ok')
} else {
if (options != null) {
this.$warning(options)
} else {
this.$message.warning(res.message)
}
this.$emit('sync-error')
}
}).catch(() => model.destroy()).finally(() => {
resolve()
this.$emit('sync-finally', {
type,
direction,
isToApp: direction === '/toApp',
isToLocal: direction === '/toLocal',
})
})
},
onCancel() {
resolve()
},
})
})
},
renderTextarea(h, value) {
return h('a-textarea', {
props: {
value: value,
readOnly: true,
autosize: {minRows: 5, maxRows: 10},
},
style: {
// 关闭textarea的自动换行使其可以左右滚动
whiteSpace: 'pre',
overflow: 'auto',
}
})
}
},
}
// 启用了哪些第三方App在此缓存
let enabledTypes = null
// 获取启用的第三方App
export async function loadEnabledTypes() {
// 获取缓存
if (enabledTypes != null) {
return cloneObject(enabledTypes)
} else {
let {success, result} = await getAction(backEndUrl.getEnabledType)
if (success) {
// 在此缓存
enabledTypes = cloneObject(result)
return result
} else {
console.warn('getEnabledType查询失败', res)
}
}
return {}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,34 @@
<template>
<a-dropdown v-if="syncToApp && syncToLocal">
<a-button type="primary" icon="sync">同步{{name}}</a-button>
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item v-if="syncToApp" key="to-app">同步到{{name}}</a-menu-item>
<a-menu-item v-if="syncToLocal" key="to-local">同步到本地</a-menu-item>
</a-menu>
</a-dropdown>
<a-button v-else-if="syncToApp" type="primary" icon="sync" @click="handleMenuClick({key:'to-app'})">同步{{name}}</a-button>
<a-button v-else type="primary" icon="sync" @click="handleMenuClick({key:'to-local'})">同步{{name}}到本地</a-button>
</template>
<script>
/* JThirdAppButton 的子组件,不可单独使用 */
export default {
name: 'JThirdAppDropdown',
props: {
type: String,
name: String,
syncToApp: Boolean,
syncToLocal: Boolean,
},
methods: {
handleMenuClick(event) {
this.$emit(event.key, {type: this.type})
},
},
}
</script>
<style scoped>
</style>

View File

@ -38,16 +38,20 @@
//url = "http://www.baidu.com"
console.log("------url------"+url)
if (url !== null && url !== undefined) {
this.url = url;
//-----------------------------------------------------------------------------------------
//url支持通过 ${token}方式传递当前登录TOKEN
let tokenStr = "${token}";
if(url.indexOf(tokenStr)!=-1) {
let token = Vue.ls.get(ACCESS_TOKEN);
this.url = url.replace(tokenStr, token);
} else {
this.url = url
}
//-----------------------------------------------------------------------------------------
/*update_begin author:wuxianquan date:20190908 for:判断打开方式新窗口打开时this.$route.meta.internalOrExternal==true */
if(this.$route.meta.internalOrExternal != undefined && this.$route.meta.internalOrExternal==true){
this.closeCurrent();
//外部url加入token
let tokenStr = "${token}";
if(url.indexOf(tokenStr)!=-1){
let token = Vue.ls.get(ACCESS_TOKEN);
this.url = url.replace(tokenStr,token);
}
window.open(this.url);
}
/*update_end author:wuxianquan date:20190908 for:判断打开方式新窗口打开时this.$route.meta.internalOrExternal==true */

View File

@ -36,9 +36,10 @@
import Contextmenu from '@/components/menu/Contextmenu'
import { mixin, mixinDevice } from '@/utils/mixin.js'
import { triggerWindowResizeEvent } from '@/utils/util'
const indexKey = '/dashboard/analysis'
import Vue from 'vue'
import { CACHE_INCLUDED_ROUTES } from "@/store/mutation-types"
import { CACHE_INCLUDED_ROUTES } from '@/store/mutation-types'
const indexKey = '/dashboard/analysis'
export default {
name: 'TabLayout',
@ -86,13 +87,6 @@
// 复制一个route对象出来不能影响原route
let currentRoute = Object.assign({}, this.$route)
currentRoute.meta = Object.assign({}, currentRoute.meta)
// update-begin-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
let storeKey = 'route:title:' + currentRoute.fullPath
let routeTitle = this.$ls.get(storeKey)
if (routeTitle) {
currentRoute.meta.title = routeTitle
}
// update-end-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
this.pageList.push(currentRoute)
this.linkList.push(currentRoute.fullPath)
this.activePage = currentRoute.fullPath
@ -171,7 +165,7 @@
// update-begin-author:sunjianlei date:20200120 for: 动态更改页面标题
changeTitle(title) {
let projectTitle = "Jeecg-Boot 企业级快速开发平台"
let projectTitle = "Jeecg-Boot 企业级低代码平台"
// 首页特殊处理
if (this.$route.path === indexKey) {
document.title = projectTitle
@ -225,10 +219,20 @@
cacheRouterArray.splice(cacheRouterArray.findIndex(item => item === componentName), 1)
Vue.ls.set(CACHE_INCLUDED_ROUTES, cacheRouterArray)
}
this.emitPageClosed(removeRoute[0])
}
//update-end--Author:scott Date:20201015 for路由缓存问题关闭了tab页时再打开就不刷新 #842
},
// 触发 page-closed (页面关闭)全局事件
emitPageClosed(closedRoute) {
this.$root.$emit('page-closed', {
closedRoute,
pageList: this.pageList,
linkList: this.linkList,
activePage: this.activePage
})
},
onContextmenu(e) {
const pagekey = this.getPageKey(e.target)
if (pagekey !== null) {

View File

@ -9,7 +9,7 @@
</a>
</div>
<div class="desc">
Jeecg Boot 是中国最具影响力的 企业级 快速开发平台
Jeecg Boot 是中国最具影响力的 企业级 低代码平台
</div>
</div>

View File

@ -57,7 +57,8 @@ import {
Cascader,
Slider,
Transfer,
Rate
Rate,
Collapse,
} from 'ant-design-vue'
import Viser from 'viser-vue'
@ -112,6 +113,7 @@ Vue.use(Cascader)
Vue.use(Slider)
Vue.use(Transfer)
Vue.use(Rate)
Vue.use(Collapse)
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message

View File

@ -11,6 +11,7 @@
:menu="menus"
:theme="theme"
@select="onSelect"
@updateMenuTitle="onUpdateMenuTitle"
:mode="mode"
:style="smenuStyle">
</s-menu>
@ -19,7 +20,7 @@
</template>
<script>
import ALayoutSider from "ant-design-vue/es/layout/Sider"
import ALayoutSider from 'ant-design-vue/es/layout/Sider'
import Logo from '../tools/Logo'
import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin.js'
@ -68,6 +69,9 @@
methods: {
onSelect (obj) {
this.$emit('menuSelect', obj)
},
onUpdateMenuTitle (obj) {
this.$emit('updateMenuTitle', obj)
}
}
}

View File

@ -82,18 +82,49 @@ export default {
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
let openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
// update-begin-author:sunjianlei date:20210409 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
// 包含冒号的是动态菜单
if (this.selectedKeys[0].includes(':')) {
let selectedKey = this.$route.fullPath
this.selectedKeys = [selectedKey]
let newOpenKeys = []
this.fullOpenKeys(this.menu, selectedKey, newOpenKeys)
if (newOpenKeys.length > 0) {
openKeys = newOpenKeys.reverse()
}
}
// update-end-author:sunjianlei date:20210409 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
//update-begin-author:taoyan date:20190510 for:online表单菜单点击展开的一级目录不对
if(!this.selectedKeys || this.selectedKeys[0].indexOf(":")<0){
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
}
//update-end-author:taoyan date:20190510 for:online表单菜单点击展开的一级目录不对
},
// update-begin-author:sunjianlei date:20210409 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
// 递归查找当前选中的菜单和父级菜单填充openKeys
fullOpenKeys(menus, selectedKey, openKeys) {
for (let item of menus) {
if (item.path === selectedKey) {
openKeys.push(item.path)
this.$emit('updateMenuTitle', item)
return true
} else if (Array.isArray(item.children)) {
if (this.fullOpenKeys(item.children, selectedKey, openKeys)) {
openKeys.push(item.path)
return true
}
}
}
},
// update-end-author:sunjianlei date:20210409 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
// render
renderItem (menu) {

View File

@ -17,7 +17,7 @@
:type="collapsed ? 'menu-unfold' : 'menu-fold'"
@click="toggle"/>
<span v-if="device === 'desktop'">欢迎进入 Jeecg-Boot 企业级快速开发平台</span>
<span v-if="device === 'desktop'">欢迎进入 Jeecg-Boot 企业级低代码平台</span>
<span v-else>Jeecg-Boot</span>
<user-menu :theme="theme"/>
@ -31,7 +31,9 @@
<s-menu
mode="horizontal"
:menu="menus"
:theme="theme"></s-menu>
:theme="theme"
@updateMenuTitle="handleUpdateMenuTitle"
></s-menu>
</div>
<a-icon
v-else
@ -50,7 +52,6 @@
import UserMenu from '../tools/UserMenu'
import SMenu from '../menu/'
import Logo from '../tools/Logo'
import { mixin } from '@/utils/mixin.js'
export default {
@ -156,8 +157,15 @@
this.topMenuStyle.headerIndexLeft = { 'width': `calc(100% - ${rightWidth})` }
}
}
}
},
//update-begin--author:sunjianlei---date:20190508------for: 顶部导航栏过长时显示更多按钮-----
// update-begin-author:sunjianlei date:20210508 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
handleUpdateMenuTitle(value) {
this.$emit('updateMenuTitle', value)
},
// update-end-author:sunjianlei date:20210508 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
}
}
</script>

View File

@ -16,6 +16,7 @@
v-if="device === 'mobile'"
:menus="menus"
@menuSelect="menuSelect"
@updateMenuTitle="handleUpdateMenuTitle"
:theme="navTheme"
:collapsed="false"
:collapsible="true"></side-menu>
@ -26,6 +27,7 @@
mode="inline"
:menus="menus"
@menuSelect="myMenuSelect"
@updateMenuTitle="handleUpdateMenuTitle"
:theme="navTheme"
:collapsed="collapsed"
:collapsible="true"></side-menu>
@ -45,6 +47,7 @@
mode="inline"
:menus="menus"
@menuSelect="menuSelect"
@updateMenuTitle="handleUpdateMenuTitle"
:theme="navTheme"
:collapsed="false"
:collapsible="true"></side-menu>
@ -62,6 +65,7 @@
:collapsed="collapsed"
:device="device"
@toggle="toggle"
@updateMenuTitle="handleUpdateMenuTitle"
/>
<!-- layout content -->
@ -85,15 +89,14 @@
import SideMenu from '@/components/menu/SideMenu'
import GlobalHeader from '@/components/page/GlobalHeader'
import GlobalFooter from '@/components/page/GlobalFooter'
import { triggerWindowResizeEvent } from '@/utils/util'
import { mapActions, mapState } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js'
// update-start---- author:os_chengtgen -- date:20190830 -- for:issues/463 -编译主题颜色已生效,但还一直转圈,显示主题 正在编译 ------
// import SettingDrawer from '@/components/setting/SettingDrawer'
// 注释这个因为在个人设置模块已经加载了SettingDrawer页面
// update-end ---- author:os_chengtgen -- date:20190830 -- for:issues/463 -编译主题颜色已生效,但还一直转圈,显示主题 正在编译 ------
import { triggerWindowResizeEvent } from '@/utils/util'
import { mapState, mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js'
export default {
name: 'GlobalLayout',
components: {
@ -160,10 +163,6 @@
//此处触发动态路由被点击事件
this.findMenuBykey(this.menus,value.key)
this.$emit("dynamicRouterShow",value.key,this.activeMenu.meta.title)
// update-begin-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
let storeKey = 'route:title:' + this.activeMenu.path
this.$ls.set(storeKey, this.activeMenu.meta.title)
// update-end-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
},
findMenuBykey(menus,key){
for(let i of menus){
@ -173,8 +172,17 @@
this.findMenuBykey(i.children,key)
}
}
}
},
//update-end-author:taoyan date:20190430 for:动态路由title显示配置的菜单title而不是其对应路由的title
// update-begin-author:sunjianlei date:20210409 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
handleUpdateMenuTitle(value) {
this.findMenuBykey(this.menus, value.path)
this.activeMenu.meta.title = value.meta.title
this.$emit('dynamicRouterShow', value.path, this.activeMenu.meta.title)
},
// update-end-author:sunjianlei date:20210409 for: 修复动态功能测试菜单、带参数菜单标题错误、展开错误的问题
}
}

View File

@ -1,6 +1,6 @@
<template>
<div class="logo">
<router-link :to="{name:'dashboard'}">
<router-link :to="routerLinkTo">
<!-- update-begin- author:sunjianlei --- date:20190814 --- for: logo颜色根据主题颜色变化 -->
<img v-if="navTheme === 'dark'" src="~@/assets/logo-white.png" alt="logo">
@ -28,7 +28,12 @@
type: Boolean,
default: true,
required: false
}
},
// 点击Logo跳转地址
routerLinkTo: {
type: Object,
default: () => ({name: 'dashboard'}),
},
}
}
</script>

View File

@ -185,8 +185,8 @@
return that.Logout({}).then(() => {
// update-begin author:wangshuai date:20200601 for: 退出登录跳转登录页面
that.$router.push({ path: '/user/login' });
window.location.reload()
// update-end author:wangshuai date:20200601 for: 退出登录跳转登录页面
//window.location.reload()
}).catch(err => {
that.$message.error({
title: '错误',
@ -225,11 +225,17 @@
// update_begin author:sunjianlei date:20191230 for: 解决外部链接打开失败的问题
searchMethods(value) {
let route = this.searchMenuOptions.filter(item => item.id === value)[0]
if (route.meta.internalOrExternal === true || route.component.includes('layouts/IframePageView')) {
//update-begin-author:taoyan date:20210528 for: 【菜单问题】配置一个iframe地址的菜单内部打开在搜索菜单上打开却新开了一个窗口
if (route.meta.internalOrExternal === true) {
window.open(route.meta.url, '_blank')
} else {
this.$router.push({ path: route.path })
if(route.component.includes('layouts/IframePageView')){
this.$router.push(route)
}else{
this.$router.push({ path: route.path })
}
}
//update-end-author:taoyan date:20210528 for: 【菜单问题】配置一个iframe地址的菜单内部打开在搜索菜单上打开却新开了一个窗口
this.searchMenuVisible = false
},
// update_end author:sunjianlei date:20191230 for: 解决外部链接打开失败的问题

View File

@ -347,6 +347,21 @@ export const constantRouterMap = [
// ]
// },
{
// OAuth2 APP页面路由
path: '/oauth2-app',
component: BlankLayout,
redirect: '/oauth2-app/login',
children: [
{
// OAuth2 登录路由
path: 'login',
name: 'login',
component: () => import(/* webpackChunkName: "oauth2-app.login" */ '@/views/user/oauth2/OAuth2Login')
},
]
},
{
path: '/test',
component: BlankLayout,

View File

@ -47,7 +47,9 @@ import '@/assets/less/JAreaLinkage.less'
import VueAreaLinkage from 'vue-area-linkage'
import '@/components/jeecg/JVxeTable/install'
import '@/components/JVxeCells/install'
//表单验证
import { rules } from '@/utils/rules'
Vue.prototype.rules = rules
Vue.config.productionTip = false
Vue.use(Storage, config.storageOptions)
Vue.use(Antd)

View File

@ -53,6 +53,7 @@ export const JEditableTableMixin = {
this.tableReset();
resolve();
}).then(() => {
if (typeof this.addBefore === 'function') this.addBefore()
// 默认新增空数据
let rowNum = this.addDefaultRowNum
if (typeof rowNum !== 'number') {

View File

@ -0,0 +1,191 @@
import JEditableTable from '@/components/jeecg/JEditableTable'
import { VALIDATE_NO_PASSED, getRefPromise,validateFormModelAndTables} from '@/utils/JEditableTableUtil'
import { httpAction, getAction } from '@/api/manage'
export const JEditableTableModelMixin = {
components: {
JEditableTable
},
data() {
return {
title: '操作',
visible: false,
confirmLoading: false,
model:{},
labelCol: {
xs: { span: 24 },
sm: { span: 6 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 }
}
}
},
methods: {
/** 获取所有的editableTable实例 */
getAllTable() {
if (!(this.refKeys instanceof Array)) {
throw this.throwNotArray('refKeys')
}
let values = this.refKeys.map(key => getRefPromise(this, key))
return Promise.all(values)
},
/** 遍历所有的JEditableTable实例 */
eachAllTable(callback) {
// 开始遍历
this.getAllTable().then(tables => {
tables.forEach((item, index) => {
if (typeof callback === 'function') {
callback(item, index)
}
})
})
},
/** 当点击新增按钮时调用此方法 */
add() {
//update-begin-author:lvdandan date:20201113 for:LOWCOD-1049 JEditaTable,子表默认添加一条数据addDefaultRowNum设置无效 #1930
return new Promise((resolve) => {
this.tableReset();
resolve();
}).then(() => {
if (typeof this.addBefore === 'function') this.addBefore()
// 默认新增空数据
let rowNum = this.addDefaultRowNum
if (typeof rowNum !== 'number') {
rowNum = 1
console.warn('由于你没有在 data 中定义 addDefaultRowNum addDefaultRowNum 不是数字所以默认添加一条空数据如果不想默认添加空数据请将定义 addDefaultRowNum 0')
}
this.eachAllTable((item) => {
item.add(rowNum)
})
if (typeof this.addAfter === 'function') this.addAfter(this.model)
this.edit(this.model)
})
//update-end-author:lvdandan date:20201113 for:LOWCOD-1049 JEditaTable,子表默认添加一条数据addDefaultRowNum设置无效 #1930
},
/** 当点击了编辑(修改)按钮时调用此方法 */
edit(record) {
if(record && '{}'!=JSON.stringify(record)&&record.id){
this.tableReset();
}
if (typeof this.editBefore === 'function') this.editBefore(record)
this.visible = true
this.activeKey = this.refKeys[0]
this.$refs.form.resetFields()
this.model = Object.assign({}, record)
if (typeof this.editAfter === 'function') this.editAfter(this.model)
},
/** 关闭弹窗并将所有JEditableTable实例回归到初始状态 */
close() {
this.visible = false
this.$emit('close')
},
//清空子表table的数据
tableReset(){
this.eachAllTable((item) => {
item.clearRow()
})
},
/** 查询某个tab的数据 */
requestSubTableData(url, params, tab, success) {
tab.loading = true
getAction(url, params).then(res => {
let { result } = res
let dataSource = []
if (result) {
if (Array.isArray(result)) {
dataSource = result
} else if (Array.isArray(result.records)) {
dataSource = result.records
}
}
tab.dataSource = dataSource
typeof success === 'function' ? success(res) : ''
}).finally(() => {
tab.loading = false
})
},
/** 发起请求,自动判断是执行新增还是修改操作 */
request(formData) {
let url = this.url.add, method = 'post'
if (this.model.id) {
url = this.url.edit
method = 'put'
}
this.confirmLoading = true
httpAction(url, formData, method).then((res) => {
if (res.success) {
this.$message.success(res.message)
this.$emit('ok')
this.close()
} else {
this.$message.warning(res.message)
}
}).finally(() => {
this.confirmLoading = false
})
},
/* --- handle 事件 --- */
/** ATab 选项卡切换事件 */
handleChangeTabs(key) {
// 自动重置scrollTop状态防止出现白屏
getRefPromise(this, key).then(editableTable => {
editableTable.resetScrollTop()
})
},
/** 关闭按钮点击事件 */
handleCancel() {
this.close()
},
/** 确定按钮点击事件 */
handleOk() {
/** 触发表单验证 */
this.getAllTable().then(tables => {
/** 一次性验证主表和所有的次表 */
return validateFormModelAndTables(this.$refs.form,this.model, tables)
}).then(allValues => {
/** 一次性验证一对一的所有子表 */
return this.validateSubForm(allValues)
}).then(allValues => {
if (typeof this.classifyIntoFormData !== 'function') {
throw this.throwNotFunction('classifyIntoFormData')
}
let formData = this.classifyIntoFormData(allValues)
// 发起请求
return this.request(formData)
}).catch(e => {
if (e.error === VALIDATE_NO_PASSED) {
// 如果有未通过表单验证的子表就自动跳转到它所在的tab
//update--begin--autor:liusq-----date:20210316------for未通过表单验证跳转tab问题------
this.activeKey = e.index == null ? this.activeKey : (e.paneKey?e.paneKey:this.refKeys[e.index])
//update--end--autor:liusq-----date:20210316------for未通过表单验证跳转tab问题------
} else {
console.error(e)
}
})
},
//校验所有子表表单
validateSubForm(allValues){
return new Promise((resolve) => {
resolve(allValues)
})
},
/* --- throw --- */
/** not a function */
throwNotFunction(name) {
return `${name} 未定义或不是一个函数`
},
/** not a array */
throwNotArray(name) {
return `${name} 未定义或不是一个数组`
}
}
}

View File

@ -52,10 +52,13 @@ export const JVxeTableMixin = {
rowNum = 1
console.warn('由于你没有在 data 中定义 addDefaultRowNum addDefaultRowNum 不是数字所以默认添加一条空数据如果不想默认添加空数据请将定义 addDefaultRowNum 0')
}
//update-begin-author:taoyan date:20210315 for: 一对多jvex 默认几行不好使了 LOWCOD-1349
this.eachAllTable((item) => {
item.addRows()
//item.add(rowNum)
setTimeout(()=>{
item.addRows()
}, 30)
})
//update-end-author:taoyan date:20210315 for: 一对多jvex 默认几行不好使了 LOWCOD-1349
if (typeof this.addAfter === 'function') this.addAfter(this.model)
this.edit({})
},

View File

@ -0,0 +1,181 @@
import { VALIDATE_FAILED, getRefPromise, validateFormAndTables,validateFormModelAndTables} from '@/components/jeecg/JVxeTable/utils/vxeUtils.js'
import { httpAction, getAction } from '@/api/manage'
export const JVxeTableModelMixin = {
data() {
return {
title: '操作',
visible: false,
confirmLoading: false,
scrolling: true,
model: {},
labelCol: {
xs: { span: 24 },
sm: { span: 6 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 18 }
}
}
},
methods: {
/** 获取所有的JVxeTable实例 */
getAllTable() {
if (!(this.refKeys instanceof Array)) {
throw this.throwNotArray('refKeys')
}
let values = this.refKeys.map(key => getRefPromise(this, key))
return Promise.all(values)
},
/** 遍历所有的JVxeTable实例 */
eachAllTable(callback) {
// 开始遍历
this.getAllTable().then(tables => {
console.log("tables",tables)
tables.forEach((item, index) => {
if (typeof callback === 'function') {
callback(item, index)
}
})
})
},
/** 当点击新增按钮时调用此方法 */
add() {
if (typeof this.addBefore === 'function') this.addBefore()
// 默认新增空数据
let rowNum = this.addDefaultRowNum
if (typeof rowNum !== 'number') {
rowNum = 1
console.warn('由于你没有在 data 中定义 addDefaultRowNum addDefaultRowNum 不是数字所以默认添加一条空数据如果不想默认添加空数据请将定义 addDefaultRowNum 0')
}
this.eachAllTable((item) => {
//update-begin-author:taoyan date:20210315 for: 一对多jvex 默认几行不好使了 LOWCOD-1349
setTimeout(()=>{
item.addRows()
}, 30)
//update-end-author:taoyan date:20210315 for: 一对多jvex 默认几行不好使了 LOWCOD-1349
})
if (typeof this.addAfter === 'function') this.addAfter(this.model)
this.edit(this.model)
},
/** 当点击了编辑(修改)按钮时调用此方法 */
edit(record) {
if (typeof this.editBefore === 'function') this.editBefore(record)
this.visible = true
this.activeKey = this.refKeys[0]
this.$refs.form.resetFields()
this.model = Object.assign({}, record)
if (typeof this.editAfter === 'function') this.editAfter(this.model)
},
/** 关闭弹窗并将所有JVxeTable实例回归到初始状态 */
close() {
this.visible = false
this.eachAllTable((item) => {
item._remove()
})
this.$emit('close')
},
/** 查询某个tab的数据 */
requestSubTableData(url, params, tab, success) {
tab.loading = true
getAction(url, params).then(res => {
let { result } = res
let dataSource = []
if (result) {
if (Array.isArray(result)) {
dataSource = result
} else if (Array.isArray(result.records)) {
dataSource = result.records
}
}
tab.dataSource = dataSource
typeof success === 'function' ? success(res) : ''
}).finally(() => {
tab.loading = false
})
},
/** 发起请求,自动判断是执行新增还是修改操作 */
request(formData) {
let url = this.url.add, method = 'post'
if (this.model.id) {
url = this.url.edit
method = 'put'
}
this.confirmLoading = true
console.log("formData===>",formData);
httpAction(url, formData, method).then((res) => {
if (res.success) {
this.$message.success(res.message)
this.$emit('ok')
this.close()
} else {
this.$message.warning(res.message)
}
}).finally(() => {
this.confirmLoading = false
})
},
/* --- handle 事件 --- */
/** ATab 选项卡切换事件 */
handleChangeTabs(key) {
// 自动重置scrollTop状态防止出现白屏
getRefPromise(this, key).then(vxeTable => {
vxeTable.resetScrollTop()
})
},
/** 关闭按钮点击事件 */
handleCancel() {
this.close()
},
/** 确定按钮点击事件 */
handleOk() {
/** 触发表单验证 */
this.getAllTable().then(tables => {
/** 一次性验证主表和所有的次表 */
return validateFormModelAndTables(this.$refs.form,this.model, tables)
}).then(allValues => {
/** 一次性验证一对一的所有子表 */
return this.validateSubForm(allValues)
}).then(allValues => {
if (typeof this.classifyIntoFormData !== 'function') {
throw this.throwNotFunction('classifyIntoFormData')
}
let formData = this.classifyIntoFormData(allValues)
// 发起请求
return this.request(formData)
}).catch(e => {
if (e.error === VALIDATE_FAILED) {
// 如果有未通过表单验证的子表就自动跳转到它所在的tab
this.activeKey = e.index == null ? this.activeKey : this.refKeys[e.index]
} else {
console.error(e)
}
})
},
//校验所有子表表单
validateSubForm(allValues){
return new Promise((resolve) => {
resolve(allValues)
})
},
/* --- throw --- */
/** not a function */
throwNotFunction(name) {
return `${name} 未定义或不是一个函数`
},
/** not a array */
throwNotArray(name) {
return `${name} 未定义或不是一个数组`
}
}
}

View File

@ -8,7 +8,6 @@ import { deleteAction, getAction,downFile,getFileAccessHttpUrl } from '@/api/man
import Vue from 'vue'
import { ACCESS_TOKEN, TENANT_ID } from "@/store/mutation-types"
import store from '@/store'
import {Modal} from 'ant-design-vue'
export const JeecgListMixin = {
data(){
@ -66,7 +65,7 @@ export const JeecgListMixin = {
let head = {'X-Access-Token': Vue.ls.get(ACCESS_TOKEN)}
let tenantid = Vue.ls.get(TENANT_ID)
if(tenantid){
head['tenant_id'] = tenantid
head['tenant-id'] = tenantid
}
return head;
}
@ -94,11 +93,11 @@ export const JeecgListMixin = {
this.ipagination.total = 0;
}
//update-end---author:zhangyafei Date:20201118 for适配不分页的数据列表------------
}
if(res.code===510){
}else{
this.$message.warning(res.message)
}
this.loading = false;
}).finally(() => {
this.loading = false
})
},
initDictConfig(){
@ -177,6 +176,8 @@ export const JeecgListMixin = {
that.loading = true;
deleteAction(that.url.deleteBatch, {ids: ids}).then((res) => {
if (res.success) {
//重新计算分页问题
that.reCalculatePage(that.selectedRowKeys.length)
that.$message.success(res.message);
that.loadData();
that.onClearSelected();
@ -198,6 +199,8 @@ export const JeecgListMixin = {
var that = this;
deleteAction(that.url.delete, {id: id}).then((res) => {
if (res.success) {
//重新计算分页问题
that.reCalculatePage(1)
that.$message.success(res.message);
that.loadData();
} else {
@ -205,6 +208,17 @@ export const JeecgListMixin = {
}
});
},
reCalculatePage(count){
//总数量-count
let total=this.ipagination.total-count;
//获取删除后的分页数
let currentIndex=Math.ceil(total/this.ipagination.pageSize);
//删除后的分页数<所在当前页
if(currentIndex<this.ipagination.current){
this.ipagination.current=currentIndex;
}
console.log('currentIndex',currentIndex)
},
handleEdit: function (record) {
this.$refs.modalForm.edit(record);
this.$refs.modalForm.title = "编辑";
@ -218,6 +232,7 @@ export const JeecgListMixin = {
handleTableChange(pagination, filters, sorter) {
//分页、排序、筛选变化时触发
//TODO 筛选
console.log(pagination)
if (Object.keys(sorter).length > 0) {
this.isorter.column = sorter.field;
this.isorter.order = "ascend" == sorter.order ? "asc" : "desc"
@ -280,10 +295,12 @@ export const JeecgListMixin = {
},
/* 导入 */
handleImportExcel(info){
this.loading = true;
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
this.loading = false;
if (info.file.response.success) {
// this.$message.success(`${info.file.name} 文件上传成功`);
if (info.file.response.code === 201) {
@ -305,11 +322,12 @@ export const JeecgListMixin = {
this.$message.error(`${info.file.name} ${info.file.response.message}.`);
}
} else if (info.file.status === 'error') {
this.loading = false;
if (info.file.response.status === 500) {
let data = info.file.response
const token = Vue.ls.get(ACCESS_TOKEN)
if (token && data.message.includes("Token失效")) {
Modal.error({
this.$error({
title: '登录已过期',
content: '很抱歉登录已过期请重新登录',
okText: '重新登录',

View File

@ -1,10 +1,12 @@
import { formatDate } from '@/utils/util'
import Area from '@/components/_util/Area'
import { postAction } from '@/api/manage'
const onlUtil = {
data(){
return {
mixin_pca:''
mixin_pca:'',
flowCodePre: 'onl_'
}
},
created(){

View File

@ -4,19 +4,20 @@ import store from './store'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import notification from 'ant-design-vue/es/notification'
import { ACCESS_TOKEN,INDEX_MAIN_PAGE_PATH } from '@/store/mutation-types'
import { generateIndexRouter } from "@/utils/util"
import { ACCESS_TOKEN,INDEX_MAIN_PAGE_PATH, OAUTH2_LOGIN_PAGE_PATH } from '@/store/mutation-types'
import { generateIndexRouter, isOAuth2AppEnv } from '@/utils/util'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/user/login', '/user/register', '/user/register-result','/user/alteration'] // no redirect whitelist
whiteList.push(OAUTH2_LOGIN_PAGE_PATH)
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
if (Vue.ls.get(ACCESS_TOKEN)) {
/* has token */
if (to.path === '/user/login') {
if (to.path === '/user/login' || to.path === OAUTH2_LOGIN_PAGE_PATH) {
next({ path: INDEX_MAIN_PAGE_PATH })
NProgress.done()
} else {
@ -59,10 +60,18 @@ router.beforeEach((to, from, next) => {
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
// 在免登录白名单,如果进入的页面是login页面并且当前是OAuth2app环境就进入OAuth2登录页面
if (to.path === '/user/login' && isOAuth2AppEnv()) {
next({path: OAUTH2_LOGIN_PAGE_PATH})
} else {
// 在免登录白名单,直接进入
next()
}
NProgress.done()
} else {
next({ path: '/user/login', query: { redirect: to.fullPath } })
// 如果当前是在OAuth2APP环境就跳转到OAuth2登录页面
let path = isOAuth2AppEnv() ? OAUTH2_LOGIN_PAGE_PATH : '/user/login'
next({ path: path, query: { redirect: to.fullPath } })
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
}
}

View File

@ -16,7 +16,8 @@ const getters = {
enhanceJs:(state) => (code) => {
state.enhance.enhanceJs[code] = Vue.ls.get(ENHANCE_PRE+code);
return state.enhance.enhanceJs[code]
}
},
sysSafeMode: state => state.user.sysSafeMode,
}

View File

@ -3,9 +3,9 @@ import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
import permission from './modules/permission'
import enhance from './modules/enhance'
import online from './modules/online'
import permission from './modules/permission'
import getters from './getters'
Vue.use(Vuex)
@ -16,7 +16,7 @@ export default new Vuex.Store({
user,
permission,
enhance,
online
online,
},
state: {

View File

@ -14,7 +14,9 @@ const user = {
welcome: '',
avatar: '',
permissionList: [],
info: {}
info: {},
// 系统安全模式
sysSafeMode: null,
},
mutations: {
@ -38,6 +40,13 @@ const user = {
SET_TENANT: (state, id) => {
state.tenantid = id
},
SET_SYS_SAFE_MODE: (state, sysSafeMode) => {
if (typeof sysSafeMode === 'boolean') {
state.sysSafeMode = sysSafeMode
} else {
state.sysSafeMode = false
}
},
},
actions: {
@ -124,20 +133,22 @@ const user = {
sessionStorage.setItem(USER_AUTH,JSON.stringify(authData));
sessionStorage.setItem(SYS_BUTTON_AUTH,JSON.stringify(allAuthData));
if (menuData && menuData.length > 0) {
//update--begin--autor:qinfeng-----date:20200109------forJEECG-63 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
menuData.forEach((item, index) => {
if (item["children"]) {
let hasChildrenMenu = item["children"].filter((i) => {
return !i.hidden || i.hidden == false
})
if (hasChildrenMenu == null || hasChildrenMenu.length == 0) {
item["hidden"] = true
}
}
})
//console.log(" menu show json ", menuData)
//update--end--autor:qinfeng-----date:20200109------forJEECG-63 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
// //update--begin--autor:qinfeng-----date:20200109------forJEECG-63 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
// menuData.forEach((item, index) => {
// if (item["children"]) {
// let hasChildrenMenu = item["children"].filter((i) => {
// return !i.hidden || i.hidden == false
// })
// if (hasChildrenMenu == null || hasChildrenMenu.length == 0) {
// item["hidden"] = true
// }
// }
// })
// //console.log(" menu show json ", menuData)
// //update--end--autor:qinfeng-----date:20200109------forJEECG-63 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
commit('SET_PERMISSIONLIST', menuData)
// 设置系统安全模式
commit('SET_SYS_SAFE_MODE', response.result.sysSafeMode)
} else {
reject('getPermissionList: permissions must be a non-null array !')
}
@ -159,6 +170,7 @@ const user = {
Vue.ls.remove(USER_NAME)
Vue.ls.remove(UI_CACHE_DB_DICT_DATA)
Vue.ls.remove(CACHE_INCLUDED_ROUTES)
Vue.ls.remove(TENANT_ID)
//console.log('logoutToken: '+ logoutToken)
logout(logoutToken).then(() => {
if (process.env.VUE_APP_SSO == 'true') {

View File

@ -17,6 +17,7 @@ export const ENCRYPTED_STRING = 'ENCRYPTED_STRING'
export const ENHANCE_PRE = 'enhance_'
export const UI_CACHE_DB_DICT_DATA = 'UI_CACHE_DB_DICT_DATA'
export const INDEX_MAIN_PAGE_PATH = '/dashboard/analysis'
export const OAUTH2_LOGIN_PAGE_PATH = '/oauth2-app/login'
export const TENANT_ID = 'TENANT_ID'
export const ONL_AUTH_FIELDS = 'ONL_AUTH_FIELDS'
//路由缓存问题关闭了tab页时再打开就不刷新 #842

View File

@ -8,12 +8,14 @@ const FormTypes = {
select: 'select',
date: 'date',
datetime: 'datetime',
time: 'time',
upload: 'upload',
file: 'file',
image: 'image',
popup:'popup',
list_multi:"list_multi",
sel_search:"sel_search",
sel_search_async:"sel_search_async",
radio:'radio',
checkbox_meta:"checkbox_meta",
input_pop:'input_pop',
@ -76,6 +78,36 @@ export function validateFormAndTables(form, cases) {
return Promise.reject(error)
})
}
/**
* 一次性验证主表单和所有的次表单(新版本)
* @param form 主表单 form 对象
* @param cases 接收一个数组每项都是一个JEditableTable实例
* @returns {Promise<any>}
* @author sunjianlei
*/
export function validateFormModelAndTables(form,values, cases) {
if (!(form && typeof form.validate === 'function')) {
throw `form 参数需要的是一个form对象而传入的却是${typeof form}`
}
let options = {}
return new Promise((resolve, reject) => {
// 验证主表表单
form.validate((valid,obj) => {
valid ?resolve(values):reject({ error: VALIDATE_NO_PASSED })
})
}).then(values => {
Object.assign(options, { formValue: values })
// 验证所有子表的表单
return validateTables(cases)
}).then(all => {
Object.assign(options, { tablesValue: all })
return Promise.resolve(options)
}).catch(error => {
return Promise.reject(error)
})
}
/**

View File

@ -9,10 +9,10 @@ export function disabledAuthFilter(code,formData) {
}
function nodeDisabledAuth(code,formData){
console.log("页面权限禁用--NODE--开始");
//console.log("页面权限禁用--NODE--开始");
let permissionList = [];
try {
console.log("页面权限禁用--NODE--开始",formData);
//console.log("页面权限禁用--NODE--开始",formData);
if (formData) {
let bpmList = formData.permissionList;
permissionList = bpmList.filter(item=>item.type=='2')
@ -53,7 +53,7 @@ function nodeDisabledAuth(code,formData){
}
function globalDisabledAuth(code){
console.log("全局页面禁用权限--Global--开始");
//console.log("全局页面禁用权限--Global--开始");
let permissionList = [];
let allPermissionList = [];
@ -106,7 +106,7 @@ function globalDisabledAuth(code){
}else{
for (let item2 of permissionList) {
if(code === item2.action){
console.log("全局页面权限解除禁用--Global--生效");
//console.log("全局页面权限解除禁用--Global--生效");
gFlag = false;
}
}

View File

@ -0,0 +1,129 @@
import md5 from 'md5'
//签名密钥串(前后端要一致,正式发布请自行修改)
const signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a";
export default class signMd5Utils {
/**
* json参数升序
* @param jsonObj 发送参数
*/
static sortAsc(jsonObj) {
let arr = new Array();
let num = 0;
for (let i in jsonObj) {
arr[num] = i;
num++;
}
let sortArr = arr.sort();
let sortObj = {};
for (let i in sortArr) {
sortObj[sortArr[i]] = jsonObj[sortArr[i]];
}
return sortObj;
}
/**
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
* @param requestParams 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
static getSign(url, requestParams) {
let urlParams = this.parseQueryString(url);
let jsonObj = this.mergeObject(urlParams, requestParams);
//console.log("sign jsonObj: ",jsonObj)
let requestBody = this.sortAsc(jsonObj);
console.log("sign requestBody: ",requestBody)
return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();
}
/**
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
static parseQueryString(url) {
let urlReg = /^[^\?]+\?([\w\W]+)$/,
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
urlArray = urlReg.exec(url),
result = {};
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
//【这边条件没有encode】带条件参数例子/sys/dict/getDictItems/sys_user,realname,id,username!='admin'%20order%20by%20create_time
let lastpathVariable = url.substring(url.lastIndexOf('/') + 1);
if(lastpathVariable.includes(",")){
if(lastpathVariable.includes("?")){
lastpathVariable = lastpathVariable.substring(0, lastpathVariable.indexOf("?"));
}
//解决Sign 签名校验失败 #2728
result["x-path-variable"] = decodeURIComponent(lastpathVariable);
}
if (urlArray && urlArray[1]) {
let paramString = urlArray[1], paramResult;
while ((paramResult = paramReg.exec(paramString)) != null) {
//数字值转为string类型前后端加密规则保持一致
if(this.myIsNaN(paramResult[2])){
paramResult[2] = paramResult[2].toString()
}
result[paramResult[1]] = paramResult[2];
}
}
return result;
}
/**
* @returns {*} 将两个对象合并成一个
*/
static mergeObject(objectOne, objectTwo) {
if (objectTwo && Object.keys(objectTwo).length > 0) {
for (let key in objectTwo) {
if (objectTwo.hasOwnProperty(key) === true) {
//数字值转为string类型前后端加密规则保持一致
if(this.myIsNaN(objectTwo[key])){
objectTwo[key] = objectTwo[key].toString()
}
objectOne[key] = objectTwo[key];
}
}
}
return objectOne;
}
static urlEncode(param, key, encode) {
if (param == null) return '';
let paramStr = '';
let t = typeof (param);
if (t == 'string' || t == 'number' || t == 'boolean') {
paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param);
} else {
for (let i in param) {
let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);
paramStr += this.urlEncode(param[i], k, encode);
}
}
return paramStr;
};
static getDateTimeToString() {
const date_ = new Date()
const year = date_.getFullYear()
let month = date_.getMonth() + 1
let day = date_.getDate()
if (month < 10) month = '0' + month
if (day < 10) day = '0' + day
let hours = date_.getHours()
let mins = date_.getMinutes()
let secs = date_.getSeconds()
const msecs = date_.getMilliseconds()
if (hours < 10) hours = '0' + hours
if (mins < 10) mins = '0' + mins
if (secs < 10) secs = '0' + secs
if (msecs < 10) secs = '0' + msecs
return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs
}
// true:数值型的false非数值型
static myIsNaN(value) {
return typeof value === 'number' && !isNaN(value);
}
}

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