Compare commits

...

355 Commits

Author SHA1 Message Date
f36bebb19a Delete CASServiceUtil.java 2022-07-29 19:13:07 +08:00
a9c2079c79 JeecgBoot 3.3.0 版本发布,更新数据库脚本 2022-07-25 14:37:50 +08:00
50ac6c4977 新版数据库脚本 2022-07-25 10:45:11 +08:00
d25915f663 删除多余的测试菜单 2022-07-21 16:45:13 +08:00
c0a95521e4 docker快速启动项目 2022-07-21 15:41:21 +08:00
e2af6826b4 镜像启动 2022-07-21 14:45:37 +08:00
db4793faad docker启动后台(单体/微服务) 2022-07-21 14:43:53 +08:00
dc073c6a4d 前后端支持通过docker镜像启动 2022-07-21 14:37:04 +08:00
079b415e80 文档写错了 2022-07-21 14:23:44 +08:00
be7744a0da docker微服务镜像启动,增加sentinel 2022-07-21 14:19:22 +08:00
80e26d2b94 微服务docker镜像制作 2022-07-21 11:26:50 +08:00
a2b8777d3d 微服务docker问题修复 2022-07-21 10:38:14 +08:00
a54192ebaf 项目介绍修改 2022-07-20 18:25:33 +08:00
04a8fe7e50 Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot.git 2022-07-20 18:22:43 +08:00
71bbf0f303 项目介绍修改 2022-07-20 18:17:21 +08:00
94b89f2135 Delete XSSUtils.java 2022-07-20 18:15:40 +08:00
d3c139ab5d Delete OSSFileServiceImpl.java 2022-07-20 18:14:50 +08:00
0e749d830b Delete IOSSFileService.java 2022-07-20 18:14:42 +08:00
5158e6e5ec Delete OSSFileMapper.java 2022-07-20 18:14:33 +08:00
1832b9a636 Delete OSSFile.java 2022-07-20 18:14:25 +08:00
db8bd1e464 Delete OSSFileController.java 2022-07-20 18:14:08 +08:00
372adbd580 Delete SystemAPIController.java 2022-07-20 18:13:56 +08:00
0cbdc092d1 JeecgBoot 3.3.0 版本发布,基于代码生成器的企业级低代码平台 2022-07-20 18:09:53 +08:00
55201e82eb Apache Shiro存在身份认证绕过漏洞,漏洞编号CVE-2022-32532 2022-06-30 22:51:34 +08:00
e4673ecdaa 代码生成器大升级
1.支持插入菜单sql生成
2.vue3模板大升级
3.提供vue3原生表单模板生成
2022-06-21 18:14:32 +08:00
71de6bec63 1 2022-06-20 19:30:44 +08:00
ce704c8e2a 新版微服务图 2022-06-18 18:57:14 +08:00
64569f8898 vue3技术栈介绍 2022-06-16 10:01:55 +08:00
8d3e1282a4 readme 2022-06-15 19:22:15 +08:00
d5e829bbcf vue3源码下载地址 2022-06-15 19:18:37 +08:00
f5d4843c55 Vue3版前端文档,项目介绍调整 2022-06-15 19:14:38 +08:00
1b5616282e 字典拦截器性能优化(无@dict注解不走拦截器)
issues/I561IU 3.2版本,跑测试用例代码抛出异常
处理报错: Table 'jeecg-boot.user_tab_cols' doesn't exist
2022-06-13 13:11:57 +08:00
9e774a457c 非开源功能说明 2022-06-10 17:30:31 +08:00
d5ce6a409a ⑥730954414 2022-06-09 10:17:10 +08:00
b972eb1068 jeecgboot-vue3 1.2.0发布,支持online表单在线低代码 2022-06-06 13:54:22 +08:00
efd17cdd60 【I57I6O】 修复路由添加时Path无法添加问题
【I59M95】自定义JeecgCloudException异常供微服务,及starter模块使用,解决注解添加之后无法实现重复提交的拦截提示
【I58FJ5】修改quartz默认配置参数,延迟启动解决服务重启多次执行问题
【#3755】优化rabbitmq代码删除setQueueNames方法避免发送延迟
升级springcloud到2021.0.3,解决Spring Cloud Gateway SpEL表达式注入问题
升级积木报表到最新版
【issues/3666】同步钉钉用户到本地,违反数据库唯一主键规则,导致插入失败
2022-05-31 10:35:46 +08:00
63505e8f6e 代码提示:
2.切换微服务 勾选profile的SpringCloud,这个类就无法启动,启动会报错
2022-05-24 17:50:55 +08:00
bd4814581a 升级fastjson版本至1.2.83,解决fastjson低版本高危漏洞 2022-05-24 17:20:39 +08:00
c7840a7382 文档与默认配置的库名不一致
nacos服务模块指定版本号
2022-05-19 23:55:49 +08:00
c3e049cbd0 SpringCloud运行环境的配置,排除system模块jar里的yaml 2022-05-18 18:00:24 +08:00
a6ae4080cb SpringCloud运行环境,排除system模块jar中的application*.yaml配置文件
测试类默认注释掉,减少启动问题
yml格式不规范,导致启动报错
2022-05-18 16:16:36 +08:00
2e90f73da2 优化单元测试代码 2022-05-10 16:17:44 +08:00
85b26230d1 修复vue3代码生成器的一些问题 2022-05-09 23:06:01 +08:00
fa70a83391 修改vue3、vue2模板问题
代码规范化补充对应注释和修正规范写法
2022-05-07 20:48:34 +08:00
bbf85093d5 [issues/I55DJD] 启动报错,单体应用升级至V3.2.0版本 2022-05-05 18:02:19 +08:00
d712776c46 版本号错误 2022-05-03 00:10:03 +08:00
9710b8bc7d 修复代码生成器模板的一些bug(重点vue3的) 2022-04-29 14:43:06 +08:00
3909eb5610 【代码生成器】修复vue3模板引用路径和文件路径大小写不一致的问题 2022-04-27 10:57:36 +08:00
cd5fd2f4d4 【代码生成器】修复vue3模板引用路径和文件路径大小写不一致的问题 2022-04-27 10:24:30 +08:00
c8a26d73e3 模板问题修复 2022-04-25 09:28:49 +08:00
e89423c666 微服务优化 2022-04-24 17:49:34 +08:00
67d8cdbc6c 微服务优化 2022-04-24 17:49:15 +08:00
c14a793906 JeecgBoot3.2.0 版本发布 [优化微服务模块] 2022-04-24 15:51:09 +08:00
7f87042e59 JeecgBoot3.2.0 版本发布 2022-04-24 15:01:06 +08:00
4aac17a5e2 版本发布 3.2.0 2022-04-24 13:40:40 +08:00
5f1d0dafa4 vue3代码生成器功能优化,几个bug处理 2022-04-23 17:06:20 +08:00
727b67a50d 【issues/I52J6R】gateway,在网关路由页面如何设置路由条件Header
默认不启用xxljob
2022-04-22 19:12:10 +08:00
dfbbd1bd1f JeecgBoot3.2.0-beta代码发布了,重构很大!
- 改了类名大小写,git对大小写不敏感,导致提交出问题修复。
2022-04-18 11:33:59 +08:00
2d9de317c4 Delete VXESocket.java 2022-04-18 11:21:51 +08:00
56912b766d Delete IPUtils.java 2022-04-18 11:21:20 +08:00
8baced93a3 Delete MD5Util.java 2022-04-18 11:21:09 +08:00
0c545517b6 Delete VXESocketConst.java 2022-04-18 11:20:52 +08:00
a3d6e0ce08 git大小写不敏感,导致重构类名未提交成功 2022-04-18 11:10:38 +08:00
9dcff93372 3.2.0-beta,重构很大:升级springboot2.6.6、spring-cloud-alibaba 2021.1、mybatisplus3.5.1、代码规范部分重构 2022-04-18 09:37:42 +08:00
948e1668b6 【issues/I515ZE】online代码生成vue3,activeKey.value不需要ref赋值] 2022-04-16 17:16:36 +08:00
b17908b581 【issues/I515ZE】online代码生成vue3,activeKey.value不需要ref赋值] 2022-04-16 17:13:03 +08:00
3bfe099e21 开源协议 2022-04-01 09:59:39 +08:00
93e755a09c 【issues/I4ZRF3】代码生成器componentProps{}后面少一个逗号 2022-03-31 09:38:53 +08:00
6196f0a463 升级:简化微服务切换,增加profile模式 SpringCloud 2022-03-30 13:45:43 +08:00
1481b86fb3 issues/I4SKUS 分子字典树前端代码生成错误 2022-03-23 18:30:45 +08:00
eacaa7ed81 生成代码返回结果泛型支持问题处理 2022-03-22 22:51:28 +08:00
bc711932f7 小bug处理和vue3代码生成器模板完善 2022-03-18 22:38:45 +08:00
44d6f3228f GUI代码生成器适用于vue3 2022-03-18 19:54:18 +08:00
dad784228b vue3最近版本代码生成模板 2022-03-18 16:24:05 +08:00
ce02dbbd30 入门视频教程 2022-03-18 15:19:21 +08:00
d4c2c02602 jdk版本支持描述错误 2022-03-17 21:42:39 +08:00
7935ce86de 解决 3.1版本 online表单在线-数据库表格式为Datetime时,通过日期查询组件(yyyy-MM-dd格式)查询报错 #3489
解决代码生成器路径不支持含中文和空格的问题
2022-03-17 13:49:18 +08:00
1224660d1d 1 2022-03-15 11:23:41 +08:00
1b9fc77ec4 1 2022-03-15 11:05:07 +08:00
4a21f16ade 1 2022-03-15 11:01:11 +08:00
a9e6d2ec0c 简化介绍 2022-03-15 10:58:48 +08:00
e00ac210fe readme调整 2022-03-15 10:50:34 +08:00
cdc245699e Apache Shiro 1.8.0 2022-03-15 10:40:56 +08:00
9de7f261bb 升级Shiro版本,解决安全漏洞提示 #3498 2022-03-14 22:15:16 +08:00
ae434bfce4 代码生成器模板问题、docker file 3306端口冲突 2022-03-14 14:14:26 +08:00
0ee985da5a Vue3版前端(Beta版)发布 2022-03-10 11:33:08 +08:00
dbba190980 升级积木报表到最新版1.4.32,解决严重安全漏洞 2022-03-09 09:45:07 +08:00
07c538a1b2 代码提醒,减少走弯路 2022-03-02 15:29:32 +08:00
472bf3f35a JeecgBoot 3.1.0 版本发布,基于代码生成器的企业级低代码平台 2022-03-01 22:13:54 +08:00
b66fff6c42 严重安全漏洞修复
1.SQL注入检测存在绕过风险
2./upload接口存在任意文件上传漏洞
2022-02-26 22:46:52 +08:00
2be616ee49 拆分独立微服务测试模块jeecg-cloud-test,提供更多测试示例: 分布式事务、分库分表、mq、xxljob等 2022-02-26 17:43:55 +08:00
51c63e8057 Nacos服务地址配置,默认用host jeecg-boot-nacos
默认注释掉mq测试示例代码,减少入门难度
2022-02-25 22:19:41 +08:00
758c347eb0 微服务Docker镜像制作 3.1+ 2022-02-25 14:26:00 +08:00
7bdc1b06d3 解决okhttp引用了kotlin,应用启动有警告日志问题
文件大小超限提示不准确问题
2022-02-25 11:09:17 +08:00
e364b950ca 删除非必须maven插件,降低使用难度 2022-02-24 22:33:11 +08:00
3690a6e014 swagger接口返回值,显示的是object问题 2022-02-24 20:01:42 +08:00
897dcbca78 JeecgBoot 3.1.0 版本发布,基于代码生成器的企业级低代码平台 2022-02-24 17:03:59 +08:00
f8c7ddd223 JeecgBoot 3.1.0 版本发布,基于代码生成器的企业级低代码平台 2022-02-24 15:13:05 +08:00
8c143f35f8 生产环境swagger默认不关闭,需要自己设置,减少疑问 2022-02-18 11:28:23 +08:00
a5f30ec51f 开源协议补充 2022-02-16 11:24:27 +08:00
9226ae0aeb 11 2022-02-05 20:37:38 +08:00
baefc1338d 导入mysql db时报错 2022-01-11 09:44:23 +08:00
bb6ec4cc2f 微服务启动报错:Could not resolve placeholder 'jeecg.signatureSecret' in value "${jeecg.signatureSecret}" 2021-12-20 11:18:52 +08:00
94053034b4 Log4j2惊爆0Day漏洞 2021-12-13 15:45:01 +08:00
bdbc714233 Log4j2惊爆0Day漏洞,大家排查修复 2021-12-11 13:12:03 +08:00
84f3ce8340 【#3232】JS增强中获取用户名为空 2021-12-10 12:02:36 +08:00
b6219301e1 【issues/I4KTU1】点击查询不会清空列表选中行 2021-12-10 11:46:09 +08:00
a4603a2963 【issues/I4K3Z7】横板顶部栏导航,会把退出登陆按钮挤掉 2021-12-10 11:43:37 +08:00
5bfa15d628 【#3225】常见案例-自定义组件-cron表达式显示错误 2021-12-10 11:19:29 +08:00
a39bb0ce5f 修改mysql脚本执行报错问题、支持生成大写QRTZ表 2021-12-07 19:16:25 +08:00
a43ccf6f31 用户数据量较大时,功能测试报错 issues/3196 2021-12-06 09:32:13 +08:00
1e2a74a65f 用户数据量较大时,功能测试报错(通过部门选择用户,左侧部门数据改成异步加载) issues/3196 2021-12-06 00:15:27 +08:00
6c362861a0 【#3203】JSelectBizComponent columns 建议开放customRender等方法类配置] 2021-12-06 00:08:29 +08:00
2a5ec11660 【issues/I4IFWX】定时任务表达式,”星期“一栏错误] 2021-12-06 00:04:58 +08:00
509be1e382 Result OK(String msg)方法会造成兼容性问题 issues/I4IP3D issues/3195 2021-11-22 15:13:59 +08:00
80601e0ccd 修改Dockerfile,将代码生成模板文件直接放到与jar包同级目录,确保发布后,代码生成功能直接可用,不需要做二次处理 2021-11-22 09:35:33 +08:00
435f467646 富文本框JEditor,属性设置下拉选层级显示 #3170 2021-11-18 09:37:13 +08:00
644c6801a0 富文本框JEditor,属性设置下拉选层级显示 #3170 2021-11-18 09:36:54 +08:00
1f75c124f1 在线表单的href跳转不能进行筛选 #3154
online 报表 bug #3162
2021-11-17 09:40:50 +08:00
bcbdd383ae 瘦jar打包方式下,所有 excel 文件导入失败 issues/I4CMHK 2021-11-17 09:17:38 +08:00
7a1d4cda96 自定义树控件只显示父节点,子节点无法展开 (此处还原不可再改) /issues/I4HZAL 2021-11-16 21:17:17 +08:00
8bae42049c jeecg.path.webapp配置无效 issues/3126 2021-11-16 21:16:58 +08:00
09a63610cf oracle数据库,在线表单编辑保存会报错issues/I4HW20 2021-11-16 18:08:12 +08:00
46e024bfbc 暂时删除vue3模板,因为vue3前端项目未正式发布,减少大家的误解 2021-11-08 15:25:38 +08:00
c767f094fc 我的部门下面的用户分配角色报错(无角色的情况下) 2021-11-06 19:04:20 +08:00
42e0a938da 我的部门空数据的时候报错 2021-11-06 19:02:53 +08:00
3f4b40ea74 云存储上传文件,不自定义域名出问题 2021-11-06 19:01:04 +08:00
f210df80e6 严重bug,解决部分功能操作,提示文本空问题 2021-11-06 19:00:08 +08:00
0f79e8efef 严重bug,解决【用户管理】选择部门和上级以后,负责部门没有数据可选 2021-11-06 18:59:24 +08:00
d6251419e6 严重bug,解决菜单数据规则,选择自定义SQL 规则值无法输入空格问题 2021-11-06 18:58:57 +08:00
7db5f7f3e9 1 2021-11-05 12:53:35 +08:00
cd79eb7597 开源协议补充:不得基于该平台软件的基础,修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售,或与JeecgBoot参与同类软件产品市场的竞争 2021-11-05 12:40:39 +08:00
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
5eb2541834 gateway访问默认进入swagger文档首页 2021-03-04 09:48:40 +08:00
462b0d17b1 字典【是否启用】按钮会错误的保存状态 #2311 2021-03-04 09:40:22 +08:00
7cc54084dc docker-compose脚本优化,nacos采用mysql方式自动初始化配置 2021-03-03 15:58:38 +08:00
32297110d9 docker-compose 微服务模式脚本优化 2021-03-02 19:25:55 +08:00
b7717d0461 docker-compose 微服务模式脚本优化 2021-03-02 19:09:39 +08:00
97c078c46a docker-compose 微服务模式脚步优化 2021-03-02 19:09:18 +08:00
17019e6261 docker-compose 镜像制作脚步 2021-03-01 10:31:54 +08:00
4a49479985 关于测边菜单遮挡内容问题详细说明 #2255 2021-02-24 15:26:21 +08:00
e49d76bc80 部门管理员添加上级用户时缺失负责部门列issues/I2SDU1 2021-02-24 15:25:31 +08:00
d6cf2b502b 批量导入部门以后,不能追加下一级部门 #2245 2021-02-24 15:23:13 +08:00
c741d779f2 【严重问题】issues/I37PNL 微服务化后-cloud-demo项目导出无法和字典关联 2021-02-21 15:45:24 +08:00
7f847c9721 删除jeecg-cloud-example例子模块,测试示例重构到jeecg-cloud-system-start中--- 2021-02-21 14:37:07 +08:00
fa2c5ecb73 删除jeecg-cloud-example例子模块,测试示例重构到jeecg-cloud-system-start中--- 2021-02-21 14:36:55 +08:00
eb1578ada3 删除jeecg-cloud-example例子模块,测试示例重构到jeecg-cloud-system-start中--- 2021-02-21 14:36:44 +08:00
90758f3b2a 有个小Bug issues/I3854N 2021-02-21 09:40:03 +08:00
766c48bdbf feign 动态创建client,拦截器执行多次 #2275 2021-02-20 20:59:13 +08:00
d16520cc59 swagger密码访问不生效 #2253 2021-02-17 23:04:22 +08:00
0252213b6e 微服务Feign调用Provider报错Token为空的问题 #2263 2021-02-17 21:05:14 +08:00
ce44147e6e 集成xxl-job-2.2.0之后,注解没有删掉,导致启动报端口冲突 #2228 2021-02-06 12:14:22 +08:00
0ea4ac910a 代码生成单表开关控件有问题 2021-02-06 12:13:28 +08:00
4f61f0ad48 重复check接口,sql注入检查 2021-02-01 20:33:10 +08:00
4a5ff61ef7 导入excel校验示例代码 2021-02-01 20:32:41 +08:00
88a0bb2d2d 文档地址写错了 2021-02-01 20:32:14 +08:00
b153669b69 图片导出报错,本地upload情况下,ImageBasePath未设置 2021-02-01 20:31:59 +08:00
449a7759be JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-25 18:31:46 +08:00
337920964d JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-25 16:43:09 +08:00
2a7946ae42 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-25 15:03:14 +08:00
c233f95f5f JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-25 14:13:20 +08:00
f87cc2d97f 更新数据库脚本 2021-01-25 12:55:15 +08:00
02bb521b8f Cloud demo 命名让人误解,改名为example 2021-01-25 12:38:31 +08:00
53e84ae34b Cloud demo 命名让人误解,更名 2021-01-25 12:32:41 +08:00
d6feeef7b5 Cloud demo 命名让人误解,更名 2021-01-25 12:32:25 +08:00
18fc7b146a Cloud demo 命名让人误解,改名为example 2021-01-25 12:31:42 +08:00
3792f46388 FeignConfig 重复注册问题处理 2021-01-25 12:01:40 +08:00
f35236609c TomcatServletWebServerFactory重复注册问题处理 2021-01-25 11:43:50 +08:00
e46b653bc4 JeecgBoot 2.4.2 积木报表版本发布(注释掉可以不用的测试类) 2021-01-25 11:29:00 +08:00
4437c749ca JeecgBoot 2.4.2 积木报表版本发布(微服务启动报错) 2021-01-25 11:26:50 +08:00
74ebac4967 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 14:44:10 +08:00
a9f21795cb JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 14:12:28 +08:00
a5ca8183cd JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 01:14:43 +08:00
e7970e52bc JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 01:14:13 +08:00
74a9e10ce6 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 01:01:12 +08:00
df5c106d94 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 00:13:15 +08:00
ae0f9edb27 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 00:06:48 +08:00
3d4bb704f1 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-24 00:02:38 +08:00
85efec3730 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-23 23:59:55 +08:00
8b0ee2d0a2 JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台 2021-01-23 23:59:31 +08:00
41c1572ad8 简化项目默认单体 2021-01-12 19:02:55 +08:00
5d555158fe 文字修改 2021-01-12 18:29:09 +08:00
1fd0b5d4db --date:20201226--for:--升级aliyun短信依赖、aliyun.oss云存储依赖、minio依赖、qiniu云依赖、justauth授权登录okhttp冲突问题----
jeecg-boot-base/jeecg-boot-base-core/org/jeecg/common/util/MinioUtil.java
jeecg-boot-base/jeecg-boot-base-core/pom.xml
pom.xml
2020-12-26 14:12:51 +08:00
693d86cf00 上传图片报错 #2090 cn.hutool.core.lang.Singleton.get(Ljava/lang/String;Lcn/hutool/core/lang/func/Func0;)Ljava/lang/Object; 2020-12-24 22:19:52 +08:00
9a3f872d63 Merge pull request #2107 from Junvary/master
修复  菜单管理-编辑-默认跳转地址   不显示内容的bug
2020-12-24 22:03:49 +08:00
5b82a1aa06 Merge pull request #1967 from tank99tank/char_400
SpringBoot 请求参数包含 [] 特殊符号 返回400状态
2020-12-24 22:01:03 +08:00
3cb88452dc 增量升级脚步语法错误修复 2020-12-15 19:40:39 +08:00
2fa1c7c17b 修复 菜单管理-编辑-默认跳转地址 不显示内容的bug
菜单管理-编辑-默认跳转地址   如果填写了内容并保存,再次编辑时,不会显示这些内容,实际上是有的。
2020-12-15 09:29:09 +08:00
b6a3085083 2,4版本问题如果url中有包括中文(已编码),就报400 Bad Request #2069 2020-12-14 11:06:02 +08:00
8a7fc033cd demo示例设置展示列效果优化
系统设置菜单消失 #2079
Failed to execute 'getComputedStyle' on 'Window
升级代码生成器模板

1、支持自定义下拉树的生成
2、oracle下 三级联动配置了,导致生成报错
3、高级查询支持下拉搜索和下拉多选
4、树表单支持更多的控件生成
2020-12-11 20:06:21 +08:00
e0cf946d18 字典明细禁用样式
上传图片组件大小不一致问题
注册用户总是提示“手机验证码错误” #2081
接口测试支持更多请求方式
菜单删除确认按钮样式变形问题
示例模板优化TableInnerEditList.vue
当用户单租户多部门时存在未setTenant的BUG #2053
2020-12-09 17:44:15 +08:00
c5b33b6bd1 解决前端增加用户模块 密码的校验规则没有生效 #2063 2020-12-08 15:42:43 +08:00
aec00d9ba2 上传中文文件名转为拼音、Long类型精度丢失问题 issues/I24KXI、达梦数据库兼容修改 2020-12-06 17:46:10 +08:00
341830c5a0 2.4版本更新后子表onlChange方法不生效 #2059 2020-12-04 11:23:05 +08:00
ff45fe4858 解决性能监控(请求追踪、tomcat信息)菜单,打开报错的问题 2020-12-03 15:09:17 +08:00
c13ed675a7 单体和微服务启动类加注释 2020-12-02 20:13:45 +08:00
6917e91398 system微服务启动类更名,方便区分单机和微服务 2020-12-02 13:39:20 +08:00
18a5c247d8 解决QRTZ_*表区分大小写的问题 2020-12-02 13:27:49 +08:00
51bf9a7c14 【通知公告】发送指定人,admin收不到信息 2020-12-02 11:15:36 +08:00
8278041aeb 解决SqlServer下online表单普通同步,会删除了表,未保留数据 2020-12-02 10:44:53 +08:00
39bb7a46fd gateway路由采用database方式加载,失败问题解决 2020-12-02 10:12:08 +08:00
1c234fbaff 更新文档地址 2020-12-01 23:01:59 +08:00
ca1f5872be 解决oracle数据库,代码生成报错的问题 2020-12-01 22:50:34 +08:00
2ae3844d5e 解决jeecgboot-mysql-5.7.sql导入不成功 #2051 2020-12-01 17:30:24 +08:00
c2db7691d1 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-30 22:20:59 +08:00
543a49fcb6 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-30 18:59:37 +08:00
df6cf7137c 路由配置修改 2020-11-30 12:44:22 +08:00
d7450cab04 修复发现的 JeecgBoot 2.4版本问题 2020-11-29 17:41:29 +08:00
496d85265b JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 22:04:12 +08:00
b4b1162ea0 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:59:33 +08:00
33e39941fb 低代码和零代码在线开发概念 2020-11-28 18:43:50 +08:00
1b5ba9f56b 低代码和零代码在线开发概念 2020-11-28 18:36:06 +08:00
0aadc70e74 JEECG 低代码和零代码在线开发概念 2020-11-28 18:34:19 +08:00
94aed3ade4 JEECG 低代码和零代码在线开发概念 2020-11-28 18:32:16 +08:00
9657eac673 jeecg 零代码和低代码开发介绍 2020-11-28 18:29:31 +08:00
680a8b3c42 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:21:31 +08:00
6e6f88620a JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:19:18 +08:00
14f4f92d84 低代码模块说明 2020-11-28 18:05:29 +08:00
2c4597c0a9 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:03:53 +08:00
757f1cd5a8 低代码功能说明 2020-11-28 18:00:48 +08:00
3c62a58d64 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 17:56:34 +08:00
bec39867a9 mysql脚本位置文档 2020-11-28 17:48:50 +08:00
dca8713a2e 数据库脚步 2020-11-28 17:46:36 +08:00
a004acee4b JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 17:20:10 +08:00
35ef0eff90 文档更新 2020-11-27 14:23:47 +08:00
c70fd3383e 文档更新 2020-11-27 14:20:16 +08:00
94c0610496 SpringBoot 请求参数包含 [] 特殊符号 返回400状态 #1795 2020-11-11 13:04:29 +08:00
1317 changed files with 311047 additions and 46817 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在线功能(需说明用的主题模板),还是生成的代码功能;
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;

14
LICENSE
View File

@ -198,4 +198,16 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
开源协议补充
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
1.允许基于本平台软件开展业务系统开发。
2.不得基于该平台软件的基础修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售或与JeecgBoot参与同类软件产品市场的竞争。
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
解释权归http://www.jeecg.com

322
README.md
View File

@ -1,33 +1,54 @@
![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.3发布日期2020-09-14
当前最新版本: 3.3.0发布日期2022-07-25
[![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.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://my.oschina.net/jeecg)
[![](https://img.shields.io/badge/version-3.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)
项目介绍
项目介绍
-----------------------------------
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
JeecgBoot 是一款基于代码生成器的`低代码`开发平台,零代码开发!采用前后端分离架构SpringBoot2.xAnt Design&VueMybatis-plusShiroJWT。强大的代码生成器让前后端代码一键生成无需写任何代码! JeecgBoot引领新的开发模式(Online Coding模式-> 代码生成器模式-> 手工MERGE智能开发) 帮助解决Java项目70%的重复工作,让开发更多关注业务逻辑。既能快速提高开发效率,帮助公司节省成本,同时又不失灵活性!JeecgBoot还独创在线开发模式No代码概念在线表单配置表单设计器、移动配置能力、工作流配置在线设计流程、报表配置能力、在线图表配置、插件能力可插拔等等
JeecgBoot 是一款基于代码生成器的`低代码开发平台`前后端分离架构 SpringBoot2.xSpringCloudAnt Design&VueMybatis-plusShiroJWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE) 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零代码`Online表单开发、Online报表、报表配置能力、在线图表设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力可插拔等等
`JEECG宗旨是:` 简单功能由Online Coding配置实现`零代码开发`(在线配置表单、在线配置报表、在线图表设计、在线设计流程、在线设计表单),复杂功能由代码生成器生成进行手工Merge既保证了`智能`又兼顾`灵活`;
业务流程采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
`JEECG宗旨是:` 简单功能由OnlineCoding配置实现,做到`零代码开发`复杂功能由代码生成器生成进行手工Merge 实现`低代码开发`,既保证了`智能`又兼顾`灵活`;实现了低代码开发的同时又支持灵活编码,解决了当前低代码产品普遍不灵活的弊端!
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
项目源码
-----------------------------------
| 仓库 |前端源码Vue3版 | 前端源码Vue2版 | 后端源码 |
|-|-|-|-|
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [ant-design-vue-jeecg](https://github.com/jeecgboot/jeecg-boot/tree/master/ant-design-vue-jeecg) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
| 码云 | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [ant-design-vue-jeecg](https://gitee.com/jeecg/jeecg-boot/tree/master/ant-design-vue-jeecg) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
##### 项目说明
| 项目名 | 说明 |
|--------------------|------------------------|
| `jeecg-boot` | SpringBoot后台源码支持微服务 |
| `ant-design-vue-jeecg` |Vue2版前端源码与主项目一起 |
| `jeecgboot-vue3` | Vue3版前端源码 |
适用项目
@ -41,34 +62,28 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
- 技术官网: [http://www.jeecg.com](http://www.jeecg.com)
- 开发文档: [http://doc.jeecg.com](http://doc.jeecg.com/1273753)
- 在线演示 [Vue2版本](http://boot.jeecg.com) | [Vue3版本](http://boot3.jeecg.com)
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 开发文档 [主项目文档](http://doc.jeecg.com) | [Vue3文档](http://vue3.jeecg.com)
- 视频教程 [JeecgBoot入门视频教程](http://www.jeecg.com/doc/video)
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [常见问题 ](http://www.jeecg.com/doc/qa) | [技术支持](http://jeecg.com/doc/help) | [1分钟体验低代码](https://my.oschina.net/jeecg/blog/3083313)
- 常见问题: [入门常见问题大全](http://bbs.jeecg.com/forum.php?mod=viewthread&tid=7816&extra=page%3D1)
- 微服务开发: [单体切换为微服务](http://doc.jeecg.com/2704725)
- 更新日志: [版本日志](http://www.jeecg.com/doc/log)
- QQ交流群 ⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
> ` 提醒【QQ群是自助服务群建议给帮助您解决问题的同学发送指定红包表示感谢】 `
交流互动
Docker启动项目
-----------------------------------
- QQ交流群 ③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)
- [Docker镜像单体启动项目](http://doc.jeecg.com/2043889)
- [Docker镜像微服务启动项目](http://doc.jeecg.com/2656147)
- [jeecgboot-vue3Docker启动](http://vue3.jeecg.com/3028878)
为什么选择JEECG-BOOT?
-----------------------------------
* 1.采用最新主流前后分离框架Springboot+Mybatis+antd容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
* 2.支持微服务SpringCloud Alibaba(Nacos、Gateway、Sentinel、Skywarking),提供切换机制支持单体和微服务自由切换
* 2.支持微服务SpringCloud Alibaba(Nacos、Gateway、Sentinel、Skywalking),提供切换机制支持单体和微服务自由切换
* 3.开发效率高,采用代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用;
* 4.代码生成器提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)
* 5.代码生成器非常智能在线业务建模、在线配置、所见即所得支持23种类控件一键生成前后端代码大幅度提升开发效率不再为重复工作发愁。
@ -85,7 +100,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.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
@ -116,39 +131,44 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
-----------------------------------
#### 开发环境
- 语言Java 8
- 语言Java 8+ (小于17)
- IDE(JAVA) IDEA / Eclipse安装lombok插件
- IDE(JAVA) IDEA (必须安装lombok插件 )
- IDE(前端) WebStorm 或者 IDEA
- IDE(前端) Vscode、WebStormIDEA
- 依赖管理Maven
- 数据库MySQL5.7+ & Oracle 11g & Sqlserver2017
- 缓存Redis
- 数据库脚本MySQL5.7+ & Oracle 11g & Sqlserver2017其他数据库[需要自己转](https://my.oschina.net/jeecg/blog/4905722)
#### 后端
- 基础框架Spring Boot 2.1.3.RELEASE \ Spring Cloud Alibaba 2.1.0.RELEASE
- 持久层框架Mybatis-plus_3.1.2
- 基础框架Spring Boot 2.6.6
- 安全框架Apache Shiro 1.4.0Jwt_3.7.0
- 微服务框架: Spring Cloud Alibaba 2021.1
- 微服务技术Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking
- 持久层框架MybatisPlus 3.5.1
- 数据库连接池阿里巴巴Druid 1.1.10
- 报表工具: JimuReport 1.5.0-beta
- 缓存框架:redis
- 安全框架:Apache Shiro 1.8.0Jwt 3.11.0
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
- 数据库连接池阿里巴巴Druid 1.1.22
- 日志打印logback
- 其他fastjsonpoiSwagger-uiquartz, lombok简化代码等。
- 其他:autopoi, fastjsonpoiSwagger-uiquartz, lombok简化代码等。
#### 前端
> 此处是Vue2的技术栈介绍[Vue3版技术栈看这里](https://github.com/jeecgboot/jeecgboot-vue3)
- [Vue 2.6.10](https://cn.vuejs.org/),[Vuex](https://vuex.vuejs.org/zh/),[Vue Router](https://router.vuejs.org/zh/)
- [Axios](https://github.com/axios/axios)
- [ant-design-vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/)
@ -157,9 +177,68 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation) - antv/g2 封装实现
- eslint[@vue/cli 3.2.1](https://cli.vuejs.org/zh/guide)
- vue-print-nb - 打印
- vue-print-nb-jeecg - 打印
#### 支持库
| 数据库 | 支持 |
| --- | --- |
| MySQL | √ |
| Oracle11g | √ |
| Sqlserver2017 | √ |
| PostgreSQL | √ |
| DB2、Informix | √ |
| MariaDB | √ |
| SQLite、Hsqldb、Derby、H2 | √ |
| 达梦、人大金仓、神通 | √ |
| 华为高斯、虚谷、瀚高数据库 | √ |
| 阿里云PolarDB、PPAS、HerdDB | √ |
| Hive、HBase、CouchBase | √ |
## 微服务解决方案
1、服务注册和发现 Nacos √
2、统一配置中心 Nacos √
3、路由网关 gateway(三种加载方式) √
4、分布式 http feign √
5、熔断降级限流 Sentinel √
6、分布式文件 Minio、阿里OSS √
7、统一权限控制 JWT + Shiro √
8、服务监控 SpringBootAdmin√
9、链路跟踪 Skywalking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
10、消息中间件 RabbitMQ √
11、分布式任务 xxl-job √
12、分布式事务 Seata
13、分布式日志 elk + kafka
14、支持 docker-compose、k8s、jenkins
15、CAS 单点登录 √
16、路由限流 √
#### 微服务架构图
![微服务架构图](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/jeecgboot_springcloud2022.png "在这里输入图片标题")
### Jeecg Boot 产品功能蓝图
![功能蓝图](https://jeecgos.oss-cn-beijing.aliyuncs.com/upload/test/Jeecg-Boot-lantu202005_1590912449914.jpg "在这里输入图片标题")
@ -182,7 +261,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
├─消息中心
│ ├─消息管理
│ ├─模板管理
├─智能化功能
├─代码生成器(低代码)
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
│ ├─代码生成器模板提供4套模板分别支持单表和一对多模型不同风格选择
│ ├─代码生成器模板生成代码自带excel导入导出
@ -271,17 +350,24 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ ├─提供单点登录CAS集成方案
│ ├─提供APP发布方案
│ ├─集成Websocket消息通知机制
├─Online在线低代码开发(暂未开源)
├─Online在线开发(低代码)
│ ├─Online在线表单 - 功能已开放
│ ├─在线代码生成器 - 功能已开放
│ ├─Online代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放
│ ├─多数据源管理
│ ├─Online在线图表
│ ├─Online图表模板配置
│ ├─高级表单设计器
│─流程模块功能 (暂不开源)
│ ├─Online在线图表(未开源)
│ ├─Online图表模板配置(未开源)
│ ├─Online布局设计(未开源)
│ ├─多数据源管理 - 功能已开放
├─积木报表设计器(低代码)
│ ├─打印设计器
│ ├─数据报表设计
│ ├─图形报表设计支持echart
│ ├─大屏设计器(未开源)
│─流程模块功能 (未开源)
│ ├─流程设计器
│ ├─在线表单设计
│ ├─表单设计
├─大屏设计器
├─门户设计/仪表盘设计器
│ └─我的任务
│ └─历史流程
│ └─历史流程
@ -292,102 +378,14 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ └─我的抄送
│ └─流程委派、抄送、跳转
│ └─。。。
│─OA办公组件 (未开源)
│ ├─更多功能
│ └─。。。
└─其他模块
└─更多功能开发中。。
```
## 微服务整体解决方案(2.3+版本)
1、服务注册和发现 Nacos √
2、统一配置中心 Nacos √
3、路由网关 gateway √
4、分布式 http feign √
5、熔断和降级 Sentinel √
6、分布式文件 Minio、阿里OSS √
7、统一权限控制 JWT + Shiro √
8、服务监控 SpringBootAdmin [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1761865)
9、链路跟踪 Skywarking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
10、消息中间件 SpringCloudStream+RabbitMQ [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1744409)
11、分布式任务 xxl-job [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1801263)
12、分布式事务 Seata
13、分布式日志 elk + kafa
14、支持 docker-compose、k8s、jenkins
15、CAS 单点登录 √
#### 微服务架构图
![微服务架构图](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/jeecgboot-weifuwu-cloud.png "在这里输入图片标题")
### Jeecg Boot 产品功能蓝图
![功能蓝图](https://static.jeecg.com/upload/test/Jeecg-Boot-lantu202005_1590912449914.jpg "在这里输入图片标题")
后台开发环境和依赖
----
- java
- maven
- jdk8
- mysql
- redis
- 数据库脚本jeecg-boot\docs\jeecg-boot-mysql.sql
- 默认登录账号: admin/123456
前端开发环境和依赖
----
- node
- yarn
- webpack
- eslint
- @vue/cli 3.2.1
- [ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation) - antv/g2 封装实现
- [jeecg-boot-angular 版本](https://gitee.com/dangzhenghui/jeecg-boot)
项目下载和运行
----
- 拉取项目代码
```bash
git clone https://github.com/zhangdaiscott/jeecg-boot.git
cd jeecg-boot/ant-design-jeecg-vue
```
1. 安装node.js
2. 切换到ant-design-jeecg-vue文件夹下
```
# 安装yarn
npm install -g yarn
# 下载依赖
yarn install
# 启动
yarn run serve
# 编译项目
yarn run build
# Lints and fixes files
yarn run lint
```
@ -437,54 +435,6 @@ yarn run lint
![](https://oscimg.oschina.net/oscnet/59c23b230f52384e588ee16309b44fa20de.jpg)
其他说明
----
- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
- 关闭 Eslint (不推荐) 移除 `package.json``eslintConfig` 整个节点代码
- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
```ecmascript 6
css: {
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#F5222D',
'link-color': '#F5222D',
'border-radius-base': '4px',
},
javascriptEnabled: true,
}
}
}
```
附属文档
----
- [Ant Design Vue](https://www.antdv.com/docs/vue/introduce-cn)
- [报表 viser-vue](https://viserjs.github.io/demo.html#/viser/line/basic-line)
- [Vue](https://cn.vuejs.org/v2/guide)
- [路由/菜单说明](https://gitee.com/jeecg/jeecg-boot/tree/v1.1/ant-design-jeecg-vue/src/router/README.md)
- [ANTD 默认配置项](https://gitee.com/jeecg/jeecg-boot/blob/v1.1/ant-design-jeecg-vue/src/defaultSettings.js)
- 其他待补充...
备注
----
> @vue/cli 升级后eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules
## 捐赠
如果觉得还不错,请作者喝杯咖啡吧 ☺

View File

@ -0,0 +1,6 @@
NODE_ENV=production
VUE_APP_PLATFORM_NAME=JeecgBoot 企业级低代码平台
# 开启单点登录
VUE_APP_SSO=false
# 开启微应用模式
VUE_APP_QIANKUN=false

View File

@ -0,0 +1,7 @@
NODE_ENV=development
VUE_APP_API_BASE_URL=http://localhost:8080/jeecg-boot
VUE_APP_CAS_BASE_URL=http://cas.example.org:8443/cas
VUE_APP_ONLINE_BASE_URL=http://fileview.jeecg.com/onlinePreview
# 微应用列表必须VUE_APP_SUB_开头,jeecg-app-1为子应用的项目名称,也是子应用的路由父路径
VUE_APP_SUB_jeecg-app-1 = '//localhost:8092'

View File

@ -0,0 +1,4 @@
NODE_ENV=production
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

@ -0,0 +1,4 @@
NODE_ENV=production
VUE_APP_API_BASE_URL=http://boot.jeecg.com:8080/jeecg-boot
VUE_APP_CAS_BASE_URL=http://cas.example.org:8443/cas
VUE_APP_ONLINE_BASE_URL=http://fileview.jeecg.com/onlinePreview

View File

@ -1,21 +1,213 @@
MIT License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2019 DaiHao Zhang
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2019 <a href="http://www.jeecg.com">Jeecg Boot</a> All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
开源协议补充
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
1.允许基于本平台软件开展业务系统开发。
2.不得基于该平台软件的基础修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售或与JeecgBoot参与同类软件产品市场的竞争。
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
解释权归http://www.jeecg.com

View File

@ -1,13 +1,13 @@
Ant Design Jeecg Vue
====
当前最新版本: 2.3.0发布日期20200914
当前最新版本: 3.3.0发布日期20220725
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
```
- 安装依赖
@ -56,6 +56,31 @@ yarn run build
yarn run lint
```
Docker镜像启动前端单体模式
----
```
# 1.配置host
127.0.0.1 jeecg-boot-system
# 2.修改前端项目的后台域名
.env.development
域名改成: http://jeecg-boot-system:8080/jeecg-boot
# 3.进入项目根目录,执行打包命令
yarn run build
# 4.构建镜像
docker build -t jeecgboot-ui2 .
# 5.启动镜像
docker run --name jeecgboot-ui-vue2 -p 80:80 -d jeecgboot-ui2
# 6.访问前台项目
http://localhost
```
其他说明
@ -93,9 +118,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)
- 其他待补充...
@ -106,30 +131,3 @@ yarn run lint
> @vue/cli 升级后eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules
Docker 镜像使用
----
```
# 1.修改前端项目的后台域名
public/index.html
域名改成: http://jeecg-boot-system:8080/jeecg-boot
# 2.先进入打包前端项目
yarn run build
# 3.构建镜像
docker build -t nginx:jeecgboot .
# 4.启动镜像
docker run --name jeecg-boot-nginx -p 80:80 -d nginx:jeecgboot
# 5.配置host
# jeecgboot
127.0.0.1 jeecg-boot-redis
127.0.0.1 jeecg-boot-mysql
127.0.0.1 jeecg-boot-system
# 6.访问前台项目
http://localhost:80
```

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,17 @@
{
"name": "vue-antd-jeecg",
"version": "2.3.0",
"version": "3.1.0",
"private": true,
"scripts": {
"pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
"serve": "vue-cli-service serve",
"build:test": "vue-cli-service build --mode test",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"ant-design-vue": "^1.6.3",
"@jeecg/antd-online-mini": "2.3.0",
"@jeecg/antd-online-mini": "3.1.0-beta",
"ant-design-vue": "^1.7.2",
"@antv/data-set": "^0.11.4",
"viser-vue": "^2.4.8",
"axios": "^0.18.0",
@ -28,22 +29,24 @@
"vue-ls": "^3.2.0",
"vue-router": "^3.0.1",
"vuex": "^3.1.0",
"vue-print-nb-jeecg": "^1.0.9",
"vue-print-nb-jeecg": "^1.0.11",
"clipboard": "^2.0.4",
"vue-photo-preview": "^1.1.3",
"vue-splitpane": "^1.0.4",
"vuedraggable": "^2.20.0",
"codemirror": "^5.46.0",
"@tinymce/tinymce-vue": "^2.1.0",
"tinymce": "^5.3.2",
"@tinymce/tinymce-vue": "2.1.0",
"tinymce": "5.4.1",
"@toast-ui/editor": "^2.1.2",
"vue-area-linkage": "^5.1.0",
"area-data": "^5.0.6",
"jsoneditor": "^9.0.0",
"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",
"qiankun": "^2.5.1",
"xss": "^1.0.13"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",
@ -58,8 +61,7 @@
"less-loader": "^4.1.0",
"vue-template-compiler": "^2.6.10",
"html-webpack-plugin": "^4.2.0",
"compression-webpack-plugin": "^3.1.0",
"babel-plugin-import": "^1.13.0"
"compression-webpack-plugin": "^3.1.0"
},
"eslintConfig": {
"root": true,

File diff suppressed because one or more lines are too long

View File

@ -2421,7 +2421,9 @@ a.listItemMetaTitle {
color: rgba(0, 0, 0, 0.85);
}
.main {
background-color: #fff;
//update-begin---author:liusq Date:20210108 for[JT-409]编译主题色,然后退出登录后的首页是这样子------------
//background-color: #fff;
//update-end---author:liusq Date:20210108 for[JT-409]编译主题色,然后退出登录后的首页是这样子------------
}
.main .leftmenu {
border-right: 1px solid #e8e8e8;
@ -7687,9 +7689,6 @@ font.weak {
color: @primary-color;
}
}
.ant-menu-submenu-selected {
color: @primary-color;
}
// begin -------- JAreaLinkage 三级联动样式 --------------
.cascader-menu-list .cascader-menu-option.hover,

View File

@ -5,10 +5,9 @@
<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>Jeecg-Boot 企业级快速开发平台</title>
<title>JeecgBoot 企业级低代码平台</title>
<link rel="icon" href="<%= BASE_URL %>logo.png">
<script src="/cdn/babel-polyfill/polyfill_7_2_5.js"></script>
<script src="<%= BASE_URL %>cdn/babel-polyfill/polyfill_7_2_5.js"></script>
<style>
html,
body,
@ -241,25 +240,16 @@
/* 滚动条优化 end */
</style>
<!-- 全局配置 -->
<script>
window._CONFIG = {};
window._CONFIG['domianURL'] = 'http://127.0.0.1:8080/jeecg-boot';
window._CONFIG['casPrefixUrl'] = 'http://cas.example.org:8443/cas';
window._CONFIG['onlinePreviewDomainURL'] = 'http://fileview.jeecg.com/onlinePreview'
window._CONFIG['staticDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/static';
//window._CONFIG['downloadUrl'] = window._CONFIG['domianURL'] + '/sys/common/download';
window._CONFIG['pdfDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/pdf/pdfPreviewIframe';
</script>
<script src="<%= BASE_URL %>static/config.js"></script>
</head>
<body>
<!-- built files will be auto injected -->
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载 Jeecg-Boot 快速开发平台,请耐心等待
<div class="load_title">正在加载 JeecgBoot 低代码平台,请耐心等待
</div>
</div>

View File

@ -0,0 +1,11 @@
/**
* 存放配置常量(当值不为空时会覆盖env配置)
*/
window._CONFIG = {
//接口父路径
VUE_APP_API_BASE_URL: '',
//单点登录地址
VUE_APP_CAS_BASE_URL: '',
//文件预览路径
VUE_APP_ONLINE_BASE_URL: ''
}

View File

@ -2,17 +2,9 @@ import { getAction, deleteAction, putAction, postAction, httpAction } from '@/ap
import Vue from 'vue'
import {UI_CACHE_DB_DICT_DATA } from "@/store/mutation-types"
////根路径
// const doMian = "/jeecg-boot/";
////图片预览请求地址
// const imgView = "http://localhost:8080/jeecg-boot/sys/common/view/";
//角色管理
const addRole = (params)=>postAction("/sys/role/add",params);
const editRole = (params)=>putAction("/sys/role/edit",params);
// const getRoleList = (params)=>getAction("/sys/role/list",params);
// const deleteRole = (params)=>deleteAction("/sys/role/delete",params);
// const deleteRoleList = (params)=>deleteAction("/sys/role/deleteBatch",params);
const checkRoleCode = (params)=>getAction("/sys/role/checkRoleCode",params);
const queryall = (params)=>getAction("/sys/role/queryall",params);
@ -21,8 +13,6 @@ const addUser = (params)=>postAction("/sys/user/add",params);
const editUser = (params)=>putAction("/sys/user/edit",params);
const queryUserRole = (params)=>getAction("/sys/user/queryUserRole",params);
const getUserList = (params)=>getAction("/sys/user/list",params);
// const deleteUser = (params)=>deleteAction("/sys/user/delete",params);
// const deleteUserList = (params)=>deleteAction("/sys/user/deleteBatch",params);
const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
//验证用户是否存在
const checkOnlyUser = (params)=>getAction("/sys/user/checkOnlyUser",params);
@ -33,27 +23,22 @@ const changePassword = (params)=>putAction("/sys/user/changePassword",params);
const addPermission= (params)=>postAction("/sys/permission/add",params);
const editPermission= (params)=>putAction("/sys/permission/edit",params);
const getPermissionList = (params)=>getAction("/sys/permission/list",params);
/*update_begin author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
const getSystemMenuList = (params)=>getAction("/sys/permission/getSystemMenuList",params);
const getSystemSubmenu = (params)=>getAction("/sys/permission/getSystemSubmenu",params);
const getSystemSubmenuBatch = (params) => getAction('/sys/permission/getSystemSubmenuBatch', params)
/*update_end author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
// const deletePermission = (params)=>deleteAction("/sys/permission/delete",params);
// const deletePermissionList = (params)=>deleteAction("/sys/permission/deleteBatch",params);
const queryTreeList = (params)=>getAction("/sys/permission/queryTreeList",params);
const queryTreeListForRole = (params)=>getAction("/sys/role/queryTreeList",params);
const queryListAsync = (params)=>getAction("/sys/permission/queryListAsync",params);
const queryRolePermission = (params)=>getAction("/sys/permission/queryRolePermission",params);
const saveRolePermission = (params)=>postAction("/sys/permission/saveRolePermission",params);
//const queryPermissionsByUser = (params)=>getAction("/sys/permission/queryByUser",params);
const queryPermissionsByUser = (params)=>getAction("/sys/permission/getUserPermissionByToken",params);
const queryPermissionsByUser = ()=>getAction("/sys/permission/getUserPermissionByToken");
const loadAllRoleIds = (params)=>getAction("/sys/permission/loadAllRoleIds",params);
const getPermissionRuleList = (params)=>getAction("/sys/permission/getPermRuleListByPermId",params);
const queryPermissionRule = (params)=>getAction("/sys/permission/queryPermissionRule",params);
// 部门管理
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);
@ -68,21 +53,15 @@ 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);
//数据字典
const addDict = (params)=>postAction("/sys/dict/add",params);
const editDict = (params)=>putAction("/sys/dict/edit",params);
//const getDictList = (params)=>getAction("/sys/dict/list",params);
const treeList = (params)=>getAction("/sys/dict/treeList",params);
// const delDict = (params)=>deleteAction("/sys/dict/delete",params);
//const getDictItemList = (params)=>getAction("/sys/dictItem/list",params);
const addDictItem = (params)=>postAction("/sys/dictItem/add",params);
const editDictItem = (params)=>putAction("/sys/dictItem/edit",params);
//const delDictItem = (params)=>deleteAction("/sys/dictItem/delete",params);
//const delDictItemList = (params)=>deleteAction("/sys/dictItem/deleteBatch",params);
//字典标签专用通过code获取字典数组
export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItems/${code}`,params);
@ -90,7 +69,6 @@ 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)
return dictItems;
}
}
@ -101,14 +79,10 @@ const doReovkeData = (params)=>getAction("/sys/annountCement/doReovkeData",param
//获取系统访问量
const getLoginfo = (params)=>getAction("/sys/loginfo",params);
const getVisitInfo = (params)=>getAction("/sys/visitInfo",params);
//数据日志访问
// const getDataLogList = (params)=>getAction("/sys/dataLog/list",params);
// 根据部门主键查询用户信息
const queryUserByDepId = (params)=>getAction("/sys/user/queryUserByDepId",params);
// 查询用户角色表里的所有信息
// const queryUserRoleMap = (params)=>getAction("/sys/user/queryUserRoleMap",params);
// 重复校验
const duplicateCheck = (params)=>getAction("/sys/duplicate/check",params);
// 加载分类字典
@ -116,6 +90,7 @@ const loadCategoryData = (params)=>getAction("/sys/category/loadAllData",params)
const checkRuleByCode = (params) => getAction('/sys/checkRule/checkByCode', params)
//加载我的通告信息
const getUserNoticeInfo= (params)=>getAction("/sys/sysAnnouncementSend/getMyAnnouncementSend",params);
//查询图表数据
const getTransitURL = url => `/sys/common/transitRESTful?url=${encodeURIComponent(url)}`
// 中转HTTP请求
export const transitRESTful = {
@ -126,8 +101,6 @@ export const transitRESTful = {
}
export {
// imgView,
// doMian,
addRole,
editRole,
checkRoleCode,
@ -151,6 +124,7 @@ export {
getPermissionRuleList,
queryPermissionRule,
queryDepartTreeList,
queryDepartTreeSync,
queryIdTree,
queryParentName,
searchByKeywords,

View File

@ -36,15 +36,15 @@ export function getSmsCaptcha(parameter) {
})
}
export function getInfo() {
return axios({
url: '/api/user/info',
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
// export function getInfo() {
// return axios({
// url: '/api/user/info',
// method: 'get',
// headers: {
// 'Content-Type': 'application/json;charset=UTF-8'
// }
// })
// }
export function logout(logoutToken) {
return axios({
@ -60,14 +60,28 @@ export function logout(logoutToken) {
/**
* 第三方登录
* @param token
* @param thirdType
* @returns {*}
*/
export function thirdLogin(token) {
export function thirdLogin(token,thirdType) {
return axios({
url: `/thirdLogin/getLoginUser/${token}`,
url: `/sys/thirdLogin/getLoginUser/${token}/${thirdType}`,
method: 'get',
headers: {
'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,31 +1,46 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
import signMd5Utils from '@/utils/encryption/signMd5Utils'
const api = {
user: '/api/user',
role: '/api/role',
service: '/api/service',
permission: '/api/permission',
permissionNoPager: '/api/permission/no-pager'
user: '/mock/api/user',
role: '/mock/api/role',
service: '/mock/api/service',
permission: '/mock/api/permission',
permissionNoPager: '/mock/api/permission/no-pager'
}
export default api
//post
export function postAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getTimestamp()};
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
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
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getTimestamp()};
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
return axios({
url: url,
method:method ,
data: parameter
data: parameter,
headers: signHeader
})
}
@ -40,10 +55,17 @@ export function putAction(url,parameter) {
//get
export function getAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getTimestamp()};
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
return axios({
url: url,
method: 'get',
params: parameter
params: parameter,
headers: signHeader
})
}
@ -104,13 +126,23 @@ export function saveService(parameter) {
* @param parameter
* @returns {*}
*/
export function downFile(url,parameter){
return axios({
url: url,
params: parameter,
method:'get' ,
responseType: 'blob'
})
export function downFile(url,parameter, method='get'){
if(method=='get'){
return axios({
url: url,
params: parameter,
method: method ,
responseType: 'blob'
})
}else{
return axios({
url: url,
method: method,
data: parameter,
responseType: 'blob'
})
}
}
/**

View File

@ -0,0 +1,33 @@
@active-color: #11da75;
ul {
max-height: 700px;
overflow-y: auto;
padding-left: .5rem;
img {
width:64px;
height:64px;
padding: .2rem;
margin: .3rem;
cursor: pointer;
&.active, &:hover {
border: 1px solid @active-color;
border-radius: 2px;
color: #fff;
transition: all .3s;
}
}
li {
list-style: none;
float: left;
text-align: center;
cursor: pointer;
color: #555;
transition: color .3s ease-in-out,background-color .3s ease-in-out;
position: relative;
margin: 3px 0;
border-radius: 4px;
background-color: #fff;
overflow: hidden;
padding: 10px 0 0;
}
}

View File

@ -5,21 +5,25 @@ import store from '@/store'
* 单点登录
*/
const init = (callback) => {
console.log("-------单点登录开始-------");
let token = Vue.ls.get(ACCESS_TOKEN);
let st = getUrlParam("ticket");
let sevice = "http://"+window.location.host+"/";
if(token){
loginSuccess(callback);
}else{
if(st){
validateSt(st,sevice,callback);
}else{
let serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
if (process.env.VUE_APP_SSO == 'true') {
console.log("-------单点登录开始-------");
let token = Vue.ls.get(ACCESS_TOKEN);
let st = getUrlParam("ticket");
let sevice = "http://" + window.location.host + "/";
if (token) {
loginSuccess(callback);
} else {
if (st) {
validateSt(st, sevice, callback);
} else {
let serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl'] + "/login?service=" + serviceUrl;
}
}
console.log("-------单点登录结束-------");
}else{
callback && callback()
}
console.log("-------单点登录结束-------");
};
const SSO = {
init: init

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">
@ -33,7 +33,7 @@
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation">
<a-menu-item @click="handleMoreOperation(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
@ -55,7 +55,7 @@
>
<a-button icon="upload">{{originColumn.btnText || '上传文件'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess"/>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
@ -75,6 +75,7 @@
data() {
return {
innerFile: null,
number:0,
}
},
computed: {
@ -137,7 +138,18 @@
methods: {
// 点击更多按钮
handleMoreOperation() {
handleMoreOperation(originColumn) {
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if (originColumn.number) {
this.number = originColumn.number
} else {
this.number = 0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(originColumn && originColumn.fieldExtendJson){
let json = JSON.parse(originColumn.fieldExtendJson);
this.number = json.uploadnum?json.uploadnum:0;
}
let path = ''
if (this.innerFile) {
path = this.innerFile.path
@ -167,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 || '未知错误'
}
@ -202,4 +225,4 @@
</script>
<style scoped lang="less">
</style>
</style>

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;">
@ -41,7 +30,7 @@
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation">
<a-menu-item @click="handleMoreOperation(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
@ -63,7 +52,7 @@
>
<a-button icon="upload">{{originColumn.btnText || '上传图片'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess"/>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
@ -83,6 +72,7 @@
data() {
return {
innerFile: null,
number:0
}
},
computed: {
@ -146,7 +136,18 @@
methods: {
// 点击更多按钮
handleMoreOperation() {
handleMoreOperation(originColumn) {
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if (originColumn.number) {
this.number = originColumn.number
} else {
this.number = 0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(originColumn && originColumn.fieldExtendJson){
let json = JSON.parse(originColumn.fieldExtendJson);
this.number = json.uploadnum?json.uploadnum:0;
}
let path = ''
if (this.innerFile) {
path = this.innerFile.path
@ -184,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 || '未知错误'
}
@ -227,4 +239,4 @@
}
}
</style>
</style>

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,17 @@
}
})
},
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);
//LOWCOD-2146 【菜单】数据规则选择自定义SQL 规则值无法输入空格
this.$emit('input', val);
},
setCurrentDictOptions(dictOptions){
this.dictOptions = dictOptions
@ -102,6 +99,10 @@
getCurrentDictOptions(){
return this.dictOptions
}
},
model:{
prop: 'value',
event: 'change'
}
}
</script>

View File

@ -10,7 +10,9 @@
:disabled="disabled"
mode="multiple"
:placeholder="placeholder"
:getPopupContainer="(node) => node.parentNode"
:getPopupContainer="getParentContainer"
optionFilterProp="children"
:filterOption="filterOption"
allowClear>
<a-select-option
v-for="(item,index) in dictOptions"
@ -34,13 +36,23 @@
disabled: Boolean,
value: String,
type: String,
options:Array
options:Array,
spliter:{
type: String,
required: false,
default: ','
},
popContainer:{
type:String,
default:'',
required:false
},
},
data() {
return {
dictOptions: [],
tagType:"",
arrayValue:!this.value?[]:this.value.split(",")
arrayValue:!this.value?[]:this.value.split(this.spliter)
}
},
created() {
@ -66,7 +78,7 @@
if(!val){
this.arrayValue = []
}else{
this.arrayValue = this.value.split(",")
this.arrayValue = this.value.split(this.spliter)
}
}
},
@ -76,8 +88,9 @@
this.dictOptions = [...this.options]
}else{
//优先从缓存中读取字典配置
if(getDictItemsFromCache(this.dictCode)){
this.dictOptions = getDictItemsFromCache(this.dictCode);
let cacheOption = getDictItemsFromCache(this.dictCode)
if(cacheOption && cacheOption.length>0){
this.dictOptions = cacheOption
return
}
//根据字典Code, 初始化字典数组
@ -90,14 +103,26 @@
},
onChange (selectedValue) {
this.$emit('change', selectedValue.join(","));
this.$emit('change', selectedValue.join(this.spliter));
},
setCurrentDictOptions(dictOptions){
this.dictOptions = dictOptions
},
getCurrentDictOptions(){
return this.dictOptions
},
getParentContainer(node){
if(!this.popContainer){
return node.parentNode
}else{
return document.querySelector(this.popContainer)
}
},
// update--begin--autor:lvdandan-----date:20201120------forLOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
filterOption(input, option) {
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
// update--end--autor:lvdandan-----date:20201120------forLOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
},
model: {
prop: 'value',

View File

@ -5,7 +5,7 @@
showSearch
labelInValue
:disabled="disabled"
:getPopupContainer="(node) => node.parentNode"
:getPopupContainer="getParentContainer"
@search="loadData"
:placeholder="placeholder"
v-model="selectedAsyncValue"
@ -21,7 +21,7 @@
<a-select
v-else
:getPopupContainer="(node) => node.parentNode"
:getPopupContainer="getParentContainer"
showSearch
:disabled="disabled"
:placeholder="placeholder"
@ -48,14 +48,32 @@
props:{
disabled: Boolean,
value: [String, Number],
dict: String,
dictOptions: Array,
async: Boolean,
placeholder:{
type:String,
default:"请选择",
required:false
}
},
dict:{
type: String,
default: '',
required: false
},
popContainer:{
type:String,
default:'',
required:false
},
pageSize:{
type: Number,
default: 10,
required: false
},
getPopupContainer: {
type:Function,
default: null
},
},
data(){
this.loadData = debounce(this.loadData, 800);//消抖
@ -105,7 +123,17 @@
if(this.async){
if(!this.selectedAsyncValue || !this.selectedAsyncValue.key || this.selectedAsyncValue.key!=this.value){
console.log("这才请求后台")
getAction(`/sys/dict/loadDictItem/${this.dict}`,{key:this.value}).then(res=>{
//update-begin-author:taoyan date:20220112 for: 方法initSelectValue 根据下拉框实际值查询下拉框的显示的文本 因后台接口只处理3个参数所以将过滤条件去掉
// TODO 隐患 查询效率问题 还是应该在后台作筛选
let itemDictStr = this.dict
let arr = itemDictStr.split(',')
if(arr && arr.length==4){
// 删除最后一个元素
arr.pop();
itemDictStr = arr.join(',')
}
//update-end-author:taoyan date:20220112 for: 方法initSelectValue 根据下拉框实际值查询下拉框的显示的文本 因后台接口只处理3个参数所以将过滤条件去掉
getAction(`/sys/dict/loadDictItem/${itemDictStr}`,{key:this.value}).then(res=>{
if(res.success){
let obj = {
key:this.value,
@ -126,7 +154,7 @@
this.options = []
this.loading=true
// 字典code格式table,text,code
getAction(`/sys/dict/loadDict/${this.dict}`,{keyword:value}).then(res=>{
getAction(`/sys/dict/loadDict/${this.dict}`,{keyword:value, pageSize: this.pageSize}).then(res=>{
this.loading=false
if(res.success){
if(currentLoad!=this.lastLoad){
@ -171,6 +199,21 @@
})
}
}
}else{
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) {
@ -182,9 +225,18 @@
this.callback()
},
handleAsyncChange(selectedObj){
this.selectedAsyncValue = selectedObj
this.selectedValue = selectedObj.key
//update-begin-author:scott date:20201222 for:【搜索】搜索查询组件,删除条件,默认下拉还是上次的缓存数据,不好 JT-191
if(selectedObj){
this.selectedAsyncValue = selectedObj
this.selectedValue = selectedObj.key
}else{
this.selectedAsyncValue = null
this.selectedValue = null
this.options = null
this.loadData("")
}
this.callback()
//update-end-author:scott date:20201222 for:【搜索】搜索查询组件,删除条件,默认下拉还是上次的缓存数据,不好 JT-191
},
callback(){
this.$emit('change', this.selectedValue);
@ -194,7 +246,16 @@
},
getCurrentDictOptions(){
return this.options
}
},
getParentContainer(node){
if(typeof this.getPopupContainer === 'function'){
return this.getPopupContainer(node)
} else if(!this.popContainer){
return node.parentNode
}else{
return document.querySelector(this.popContainer)
}
},
},
model: {

View File

@ -1,7 +1,16 @@
import T from './JDictSelectTag.vue'
const JDictSelectTag = {
import JDictSelectTag from './JDictSelectTag.vue'
import JMultiSelectTag from './JMultiSelectTag.vue'
import JSearchSelectTag from './JSearchSelectTag.vue'
import { filterMultiDictText,filterDictText,initDictOptions,filterDictTextByCache } from './JDictSelectUtil'
export default {
install: function (Vue) {
Vue.component('JDictSelectTag',T);
Vue.component('JDictSelectTag',JDictSelectTag);
Vue.component('JMultiSelectTag',JMultiSelectTag);
Vue.component('JSearchSelectTag',JSearchSelectTag);
Vue.prototype.$initDictOptions = (dictCode) => initDictOptions(dictCode)
Vue.prototype.$filterMultiDictText = (dictOptions, text) => filterMultiDictText(dictOptions, text)
Vue.prototype.$filterDictText = (dictOptions, text) => filterDictText(dictOptions, text)
Vue.prototype.$filterDictTextByCache = (...param) => filterDictTextByCache(...param)
}
}
export default JDictSelectTag;
}

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

@ -251,6 +251,9 @@
},
style: {}
}
if(isIE() || isIE11()){
props.style['height'] = '240px'
}
if (this.fullCoder) {
props.style['z-index'] = this.zIndex
}
@ -397,6 +400,10 @@
.null-tip-hidden{
display: none;
}
/**选中样式偶然出现高度不够的情况*/
.CodeMirror-selected{
min-height: 19px !important;
}
}
/* 全屏样式 */

View File

@ -1,282 +0,0 @@
<template>
<div v-bind="fullScreenParentProps">
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
<div class="code-editor-cust full-screen-child">
<a-textarea auto-size v-model="textareaValue" :placeholder="placeholderShow" @change="handleChange" :style="{'max-height': maxHeight+'px','min-height': minHeight+'px'}"></a-textarea>
</div>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'JCodeEditor',
props: {
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: null
},
// 是否显示全屏按钮
fullScreen: {
type: Boolean,
default: false
},
// 全屏以后的z-index
zIndex: {
type: [Number, String],
default: 999
},
// 是否自适应高度可以传String或Boolean
// 传 String 类型只能写"!ie"
// 填写这个字符串,代表其他浏览器自适应高度
// 唯独IE下不自适应高度因为IE下不支持min、max-height样式
// 如果填写的不是"!ie"就视为true
autoHeight: {
type: [String, Boolean],
default: true
},
// 不自适应高度的情况下生效的固定高度
height: {
type: [String, Number],
default: '240px'
},
language: {
type: String,
default: ''
},
minHeight:{
type:Number,
default: 100,
required:false
},
maxHeight:{
type:Number,
default: 320,
required:false
}
},
data () {
return {
textareaValue: '',
// 内部真实的内容
code: '',
iconType: 'fullscreen',
hasCode:false,
// 默认的语法类型
mode: 'javascript',
// 编辑器实例
coder: null,
// 默认配置
options: {
// 缩进格式
tabSize: 2,
// 主题,对应主题库 JS 需要提前引入
theme: 'panda-syntax',
line: true,
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
hintOptions: {
tables: {
users: ['name', 'score', 'birthDate'],
countries: ['name', 'population', 'size']
}
},
},
// code 编辑器 是否全屏
fullCoder: false
}
},
watch: {
fullCoder:{
handler(value) {
if(value){
this.iconType="fullscreen-exit"
}else{
this.iconType="fullscreen"
}
}
}
},
computed: {
placeholderShow() {
if (this.placeholder == null) {
return `请在此输入代码`
} else {
return this.placeholder
}
},
isAutoHeight() {
let {autoHeight} = this
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
autoHeight = !(isIE() || isIE11())
} else {
autoHeight = true
}
return autoHeight
},
fullScreenParentProps() {
let props = {
class: {
'full-screen-parent': true,
'full-screen': this.fullCoder,
'auto-height': this.isAutoHeight
},
style: {}
}
if (this.fullCoder) {
props.style['z-index'] = this.zIndex
}
if (!this.isAutoHeight) {
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
}
return props
}
},
mounted () {
// 初始化
this._initialize()
},
methods: {
// 初始化
_initialize () {
this.setCodeContent(this.value)
},
handleChange(e){
this.$emit('input', e.target.value)
},
getCodeContent(){
return this.value
},
setCodeContent(val){
setTimeout(()=>{
if(!val){
this.textareaValue = ''
}else{
this.textareaValue = val
}
},300)
},
nullTipClick(){
this.coder.focus()
}
}
}
</script>
<style lang="less">
.code-editor-cust{
flex-grow:1;
display:flex;
position:relative;
height:100%;
.CodeMirror{
flex-grow:1;
z-index:1;
.CodeMirror-code{
line-height:19px;
}
}
.code-mode-select{
position:absolute;
z-index:2;
right:10px;
top:10px;
max-width:130px;
}
.CodeMirror{
height: auto;
min-height:100%;
}
.null-tip{
position: absolute;
top: 4px;
left: 36px;
z-index: 10;
color: #ffffffc9;
line-height: initial;
}
.null-tip-hidden{
display: none;
}
}
/* 全屏样式 */
.full-screen-parent {
position: relative;
.full-screen-icon {
opacity: 0;
color: black;
width: 20px;
height: 20px;
line-height: 24px;
background-color: white;
position: absolute;
top: 2px;
right: 2px;
z-index: 9;
cursor: pointer;
transition: opacity 0.3s;
}
&:hover {
.full-screen-icon {
opacity: 1;
&:hover {
background-color: rgba(255, 255, 255, 0.88);
}
}
}
&.full-screen {
position: fixed;
top: 10px;
left: 10px;
width: calc(100% - 20px);
height: calc(100% - 20px);
padding: 10px;
background-color: #f5f5f5;
.full-screen-icon {
top: 12px;
right: 12px;
}
.full-screen-child {
height: 100%;
max-height: 100%;
min-height: 100%;
}
}
.full-screen-child {
height: 100%;
}
&.auto-height {
.full-screen-child {
min-height: 120px;
max-height: 320px;
height: unset;
overflow: hidden;
}
&.full-screen .full-screen-child {
height: 100%;
max-height: 100%;
min-height: 100%;
}
}
}
.CodeMirror-cursor{
height:18.4px !important;
}
</style>

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,317 @@
<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(' ')
if (vs.length >= 6) {
// 将 Quartz 星期 的规则转换为 CronParser 的规则
vs[5] = this.convertQuartzWeekToCParser(vs[5])
}
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()
},
// 将 Quartz 星期 的规则转换为 CronParser 的规则:
// Quartz 的规则1 = 周日2 = 周一3 = 周二4 = 周三5 = 周四6 = 周五7 = 周六
// CronParser 的规则: 0 = 周日1 = 周一2 = 周二3 = 周三4 = 周四5 = 周五6 = 周六7 = 周日
convertQuartzWeekToCParser(week) {
let convert = (v) => {
if (v === '0') {
return '1'
}
if (v === '1') {
return '0'
}
return (Number.parseInt(v) - 1).toString()
}
// 匹配示例 1-7 or 1/7
let patten1 = /^([0-7])([-/])([0-7])$/
// 匹配示例 1,4,7
let patten2 = /^([0-7])(,[0-7])+$/
if (/^[0-7]$/.test(week)) {
return convert(week)
} else if (patten1.test(week)) {
return week.replace(patten1, ($0, before, separator, after) => {
if (separator === '/') {
return convert(before) + separator + after
} else {
return convert(before) + separator + convert(after)
}
})
} else if (patten2.test(week)) {
return week.split(',').map(v => convert(v)).join(',')
}
return week
},
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': '1',
'MON': '2',
'TUE': '3',
'WED': '4',
'THU': '5',
'FRI': '6',
'SAT': '7'
}
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,42 @@
.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;
}
.week {
.list-check-item {
width: 5em;
text-align: left;
}
}
.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 week">
<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="(v,k) in WEEK_MAP">
<a-checkbox class="list-check-item" :key="`key-${v}`" :value="v" :disabled="type!==TYPE_SPECIFY || disabled">{{k}}</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 = {
'周一': 2,
'周二': 3,
'周三': 4,
'周四': 5,
'周五': 6,
'周六': 7,
// 按照国人习惯,将周日放到每周的最后一天
'周日': 1,
}
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
}

File diff suppressed because it is too large Load Diff

View File

@ -68,6 +68,9 @@
branding: false,
menubar: false,
toolbar_drawer: false,
//update-begin-author:taoyan date:2022-5-6 for: issues/I4BCC3 富文本编辑器在服务器图片上传是相对路径
convert_urls: false,
//update-end-author:taoyan date:2022-5-6 for: issues/I4BCC3 富文本编辑器在服务器图片上传是相对路径
images_upload_handler: (blobInfo, success) => {
let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename());
@ -128,12 +131,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

@ -49,7 +49,8 @@
.jeecg-form-container-disabled .ant-upload-select{display:none}
.jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list,
.jeecg-form-container-disabled fieldset[disabled] iframe{
-ms-pointer-events: auto !important;
pointer-events: auto !important;
}

View File

@ -11,14 +11,16 @@
:beforeUpload="beforeUpload"
:disabled="disabled"
:isMultiple="isMultiple"
:showUploadList="isMultiple"
@change="handleChange"
@preview="handlePreview"
:class="!isMultiple?'imgupload':''">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="height:104px;max-width:300px"/>
<div v-else class="iconp">
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
<div class="ant-upload-text">{{ text }}</div>
: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>
</div>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
<img alt="example" style="width: 100%" :src="previewImage"/>
@ -80,18 +82,29 @@
type:Boolean,
required:false,
default: false
},
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性用于判断上传数量
number:{
type:Number,
required:false,
default:0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性用于判断上传数量
},
watch:{
value(val){
if (val instanceof Array) {
this.initFileList(val.join(','))
} else {
this.initFileList(val)
}
if(!val || val.length==0){
this.picUrl = false;
}
value: {
handler(val,oldValue) {
if (val instanceof Array) {
this.initFileList(val.join(','))
} else {
this.initFileList(val)
}
if(!val || val.length==0){
this.picUrl = false;
}
},
//立刻执行handler
immediate: true
}
},
created(){
@ -132,6 +145,11 @@
handleChange(info) {
this.picUrl = false;
let fileList = info.fileList
//update-begin-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选返回选定的元素。
if(this.number>0 && this.isMultiple){
fileList = fileList.slice(-this.number);
}
//update-end-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选返回选定的元素。
if(info.file.status==='done'){
if(info.file.response.success){
this.picUrl = true;
@ -171,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++){
@ -213,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图片组件单张图片详情回显空白*/
</style>
/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

@ -21,7 +21,6 @@ export default {
'outdent',
'divider',
'table',
'image',
'link',
'divider',
'code',

View File

@ -1,5 +1,28 @@
<template>
<div class="j-markdown-editor" :id="id"/>
<div>
<div class="j-markdown-editor" :id="id"/>
<div v-if="isShow">
<j-modal
title="图片上传"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
@ok="handleOk">
<a-tabs default-active-key="1" @change="handleChange">
<a-tab-pane tab="本地图片上传" key="1" :forceRender="true">
<j-upload v-model="fileList" :number="1"></j-upload>
<div style="margin-top: 20px">
<a-input v-model="remark" placeholder="请填写备注"></a-input>
</div>
</a-tab-pane>
<a-tab-pane tab="网络图片地址" key="2" :forceRender="true">
<a-input v-model="networkPic" placeholder="请填写网络图片地址"></a-input>
<a-input style="margin-top: 20px" v-model="remark" placeholder="请填写备注"></a-input>
</a-tab-pane>
</a-tabs>
</j-modal>
</div>
</div>
</template>
<script>
@ -9,9 +32,14 @@ import '@toast-ui/editor/dist/i18n/zh-cn';
import Editor from '@toast-ui/editor';
import defaultOptions from './default-options'
import JUpload from '@/components/jeecg/JUpload'
import { getFileAccessHttpUrl } from '@/api/manage'
export default {
name: 'JMarkdownEditor',
components: {
JUpload,
},
props: {
value: {
type: String,
@ -47,7 +75,16 @@ export default {
},
data() {
return {
editor: null
editor: null,
isShow:false,
activeIndex:"1",
dialogVisible:false,
index:"1",
fileList:[],
remark:"",
imageName:"",
imageUrl:"",
networkPic:""
}
},
computed: {
@ -94,6 +131,40 @@ export default {
this.editor.on('change', () => {
this.$emit('change', this.editor.getMarkdown())
})
//--begin 添加自定义上传按钮
/*
* 添加自定义按钮
*/
//获取编辑器上的功能条
let toolbar = this.editor.getUI().getToolbar();
let fileDom = this.$refs.files;
//添加图片点击事件
this.editor.eventManager.addEventType('isShowClickEvent');
this.editor.eventManager.listen('isShowClickEvent', () => {
this.isShow = true
this.dialogVisible = true
});
//addImageBlobHook图片上传、剪切、拖拽都会走此方法
// 删除默认监听事件
this.editor.eventManager.removeEventHandler('addImageBlobHook')
// 添加自定义监听事件
this.editor.eventManager.listen('addImageBlobHook', (blob, callback) => {
this.upload(blob, url => {
callback(url)
})
})
// 添加自定义按钮 第二个参数代表位置,不传默认放在最后
toolbar.insertItem(15,{
type: 'button',
options:{
name: 'customize',
className: 'tui-image tui-toolbar-icons',
event: 'isShowClickEvent',
tooltip: '上传图片',
}
//
});
//--end 添加自定义上传按钮
},
destroyEditor() {
if (!this.editor) return
@ -111,7 +182,57 @@ export default {
},
getHtml() {
return this.editor.getHtml()
}
},
handleOk(){
if(this.index=='1'){
this.imageUrl = getFileAccessHttpUrl(this.fileList)
if(this.remark){
this.addImgToMd(this.imageUrl,this.remark)
}else{
this.addImgToMd(this.imageUrl,"")
}
}else{
if(this.remark){
this.addImgToMd(this.networkPic,this.remark)
}else{
this.addImgToMd(this.networkPic,"")
}
}
this.index="1"
this.fileList=[]
this.imageName="";
this.imageUrl="";
this.remark=""
this.networkPic=""
this.dialogVisible=false
this.isShow=false;
},
handleClose(done) {
done();
},
handleChange(val){
this.fileList=[]
this.remark=""
this.imageName=""
this.imageUrl=""
this.networkPic=""
this.index=val
},
//添加图片到markdown
addImgToMd(data,name) {
let editor = this.editor.getCodeMirror();
let editorHtml = this.editor.getCurrentModeEditor();
let isMarkdownMode = this.editor.isMarkdownMode();
if (isMarkdownMode) {
editor.replaceSelection(`![${name}](${data})`);
} else {
let range = editorHtml.getRange();
let img = document.createElement('img');
img.src = `${data}`;
img.alt = name;
range.insertNode(img);
}
},
},
model: {
prop: 'value',

View File

@ -8,10 +8,11 @@
v-on="$listeners"
@ok="handleOk"
@cancel="handleCancel"
:destroyOnClose="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,15 +47,19 @@
<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'
import ModalDragMixins from './ModalDragMixins'
export default {
export default {
name: 'JModal',
mixins: [ModalDragMixins],
props: {
title: String,
// 可使用 .sync 修饰符
visible: Boolean,
// 是否开启拖拽
draggable: Boolean,
// 是否全屏弹窗当全屏时无论如何都会禁止 body 滚动可使用 .sync 修饰符
fullscreen: {
type: Boolean,
@ -62,6 +75,11 @@
type: Boolean,
default: true
},
// 关闭时销毁弹窗内容
destroyOnClose: {
type: Boolean,
default: true
},
},
data() {
return {
@ -153,6 +171,16 @@
toggleFullscreen() {
this.innerFullscreen = !this.innerFullscreen
triggerWindowResizeEvent()
// 全屏的时候禁止拖动
if (this.innerFullscreen) {
// 还原弹窗的位置为0,0
this.setModalPosition(0, 0, false)
this.dragSettings.headerEl.style.cursor = null
} else {
// 取消全屏的时候将弹窗移动到上次记录的位置
this.resetModalPosition()
this.dragSettings.headerEl.style.cursor = 'move'
}
},
}
@ -160,8 +188,8 @@
</script>
<style lang="less">
.j-modal-box {
&.fullscreen {
top: 0;
left: 0;
@ -190,7 +218,6 @@
height: calc(100% - 55px);
}
}
&.no-title.no-footer {
.ant-modal-body {
height: 100%;
@ -217,6 +244,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,152 @@
import {getRefPromise} from '@/utils/util'
/** JModal 的拖拽混入 */
export default {
data() {
return {
// 拖动配置
dragSettings: {
// 上次拖动top记录
top: null,
// 上次拖动left记录
left: null,
wrapEl: null,
dragEl: null,
headerEl: null,
},
}
},
watch: {
visible() {
if (!this.visible || !this.draggable) {
return
}
this.handleDrag()
},
draggable() {
if (!this.visible || !this.draggable) {
return
}
this.handleDrag()
},
},
methods: {
async handleDrag() {
let modalRef = await getRefPromise(this, 'modal')
const dragWraps = modalRef.$el.querySelectorAll('.ant-modal-wrap')
let wrapEl = dragWraps[0]
if (!wrapEl) return
this.dragSettings.wrapEl = wrapEl
this.dragSettings.dragEl = wrapEl.querySelector('.ant-modal')
this.dragSettings.headerEl = wrapEl.querySelector('.ant-modal-header')
const display = getStyle(wrapEl, 'display')
const draggable = wrapEl.getAttribute('data-drag')
if (display !== 'none') {
// 拖拽位置
if (draggable === null || this.destroyOnClose) {
this.enableDrag(wrapEl)
}
}
},
/** 启用拖拽 */
enableDrag() {
let {wrapEl, dragEl, headerEl} = this.dragSettings
if (!wrapEl) return
wrapEl.setAttribute('data-drag', this.draggable)
if (!headerEl || !dragEl || !this.draggable) return
// 还原上一次移动的位置
this.resetModalPosition()
headerEl.style.cursor = 'move'
headerEl.onmousedown = (e) => {
if (!e) return
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX
const disY = e.clientY
const screenWidth = document.body.clientWidth // body当前宽度
const screenHeight = document.documentElement.clientHeight // 可见区域高度(应为body高度可某些环境下无法获取)
const dragElWidth = dragEl.offsetWidth // 对话框宽度
const dragElHeight = dragEl.offsetHeight // 对话框高度
const minDragElLeft = dragEl.offsetLeft
const maxDragElLeft = screenWidth - dragEl.offsetLeft - dragElWidth
const minDragElTop = dragEl.offsetTop
const maxDragElTop = screenHeight - dragEl.offsetTop - dragElHeight
// 获取到的值带px 正则匹配替换
const domLeft = getStyle(dragEl, 'left')
const domTop = getStyle(dragEl, 'top')
let styL = +domLeft
let styT = +domTop
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (domLeft.includes('%')) {
styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100)
styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100)
} else {
styL = +domLeft.replace(/px/g, '')
styT = +domTop.replace(/px/g, '')
}
document.onmousemove = (e) => {
// 全屏时不触发移动方法
if (this.innerFullscreen) {
return
}
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
// 边界处理
if (-left > minDragElLeft) {
left = -minDragElLeft
} else if (left > maxDragElLeft) {
left = maxDragElLeft
}
if (-top > minDragElTop) {
top = -minDragElTop
} else if (top > maxDragElTop) {
top = maxDragElTop
}
this.setModalPosition(top + styT, left + styL)
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
}
}
},
/**
* 移动弹窗位置
* @param top 顶部位置
* @param left 左侧位置
* @param remember 是否记录位置,默认 true
*/
setModalPosition(top, left, remember = true) {
// 记录移动位置
if (remember) {
this.dragSettings.top = top
this.dragSettings.left = left
}
// 移动当前元素
this.dragSettings.dragEl.style.cssText += `;left:${left}px;top:${top}px;`
},
/**
* 将弹窗移动到上次记录的位置
*/
resetModalPosition() {
this.setModalPosition(this.dragSettings.top, this.dragSettings.left, false)
},
},
}
function getStyle(dom, attr) {
return getComputedStyle(dom)[attr]
}

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,7 +10,9 @@
ref="jPopupOnlReport"
:code="code"
:multi="multi"
:sorter="sorter"
:groupId="uniqGroupId"
:param="param"
@ok="callBack"
/>
@ -46,6 +48,11 @@
default: '',
required: false
},
/** 排序列,指定要排序的列,使用方式:列名=desc|asc */
sorter: {
type: String,
default: ''
},
width: {
type: Number,
default: 1200,
@ -75,6 +82,17 @@
required: false,
default: false
},
//popup动态参数 支持系统变量语法
param:{
type: Object,
required: false,
default: ()=>{}
},
spliter:{
type: String,
required: false,
default: ','
},
/** 分组ID用于将多个popup的请求合并到一起不传不分组 */
groupId: String
@ -101,7 +119,7 @@
if (!val) {
this.showText = ''
} else {
this.showText = val
this.showText = val.split(this.spliter).join(',')
}
}
}
@ -125,6 +143,10 @@
}
},
handleEmpty() {
// 禁用时,不允许清空内容
if (this.disabled) {
return
}
this.showText = ''
let destFieldsArr = this.destFields.split(',')
if (destFieldsArr.length === 0) {
@ -155,9 +177,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(",")
@ -181,7 +205,11 @@
} else {
//v-model时 需要传一个参数field 表示当前这个字段 从而根据这个字段的顺序找到原始值
// this.$emit("input",row[orgFieldsArr[destFieldsArr.indexOf(this.field)]])
this.$emit('input', this.showText, res)
let str = ''
if(this.showText){
str = this.showText.split(',').join(this.spliter)
}
this.$emit('input', str, res)
}
}
}

View File

@ -1,8 +1,9 @@
<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">
{{ item.text || item.label }}
</a-select-option>
@ -11,6 +12,8 @@
<script>
//option {label:,value:}
import { getAction } from '@api/manage'
export default {
name: 'JSelectMultiple',
props: {
@ -30,36 +33,80 @@
},
options:{
type: Array,
required: true
default:()=>[],
required: false
},
triggerChange:{
type: Boolean,
required: false,
default: false
}
},
spliter:{
type: String,
required: false,
default: ','
},
popContainer:{
type:String,
default:'',
required:false
},
dictCode:{
type:String,
required:false
},
},
data(){
return {
arrayValue:!this.value?[]:this.value.split(",")
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){
this.arrayValue = []
}else{
this.arrayValue = this.value.split(",")
this.arrayValue = this.value.split(this.spliter)
}
}
},
mounted(){
if (this.dictCode) {
this.loadDictOptions()
}
},
methods:{
onChange (selectedValue) {
if(this.triggerChange){
this.$emit('change', selectedValue.join(","));
this.$emit('change', selectedValue.join(this.spliter));
}else{
this.$emit('input', selectedValue.join(","));
this.$emit('input', selectedValue.join(this.spliter));
}
},
getParentContainer(node){
if(!this.popContainer){
return node.parentNode
}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

@ -29,6 +29,7 @@
@cancel="handleCancel"
:mask="false"
:fullscreen="izMobile"
draggable
class="j-super-query-modal"
style="top:5%;max-height: 95%;"
>
@ -101,7 +102,17 @@
</a-col>
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<template v-if="item.dictCode">
<!-- 下拉搜索 -->
<j-search-select-tag v-if="item.type==='sel_search'" v-model="item.val" :dict="getDictInfo(item)" placeholder="请选择"/>
<!-- 下拉框 -->
<j-search-select-tag v-else-if="item.type==='list' && item.dictTable" v-model="item.val" :dict="getDictInfo(item)" placeholder="请选择"/>
<!-- 下拉多选 -->
<template v-else-if="item.type==='list_multi'">
<j-multi-select-tag v-if="item.options" v-model="item.val" :options="item.options" placeholder="请选择"/>
<j-multi-select-tag v-else v-model="item.val" :dictCode="getDictInfo(item)" placeholder="请选择"/>
</template>
<template v-else-if="item.dictCode">
<template v-if="item.type === 'table-dict'">
<j-popup
v-model="item.val"
@ -109,6 +120,7 @@
:field="item.dictCode"
:orgFields="item.dictCode"
:destFields="item.dictCode"
:multi="true"
></j-popup>
</template>
<template v-else>
@ -116,19 +128,25 @@
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
</template>
</template>
<j-popup v-else-if="item.type === 'popup'" :value="item.val" v-bind="item.popup" group-id="superQuery" @input="(e,v)=>handleChangeJPopup(item,e,v)"/>
<j-popup
v-else-if="item.type === 'popup'"
:value="item.val"
v-bind="item.popup"
group-id="superQuery"
@input="(e,v)=>handleChangeJPopup(item,e,v)"
:multi="true"/>
<j-select-multi-user
v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
v-model="item.val"
:buttons="false"
:multiple="false"
:multiple="allowMultiple(item)"
placeholder="请选择用户"
:returnKeys="['id', item.customReturnField || 'username']"
/>
<j-select-depart
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
v-model="item.val"
:multi="false"
:multi="allowMultiple(item)"
placeholder="请选择部门"
:customReturnField="item.customReturnField || 'id'"
/>
@ -146,8 +164,10 @@
<a-time-picker v-else-if="item.type==='time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
<a-input-number v-else-if=" item.type=='int'||item.type=='number' " style="width: 100%" placeholder="请输入数值" v-model="item.val"/>
<a-select v-else-if="item.type=='switch'" placeholder="请选择" v-model="item.val">
<a-select-option value="Y">是</a-select-option>
<a-select-option value="N"></a-select-option>
<!-- update-begin-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时高级查询选择后查询仍然是Y/N -->
<a-select-option :value="item.extendOption[0]"></a-select-option>
<a-select-option :value="item.extendOption[1]">否</a-select-option>
<!-- update-end-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时高级查询选择后查询仍然是Y/N -->
</a-select>
<a-input v-else v-model="item.val" placeholder="请输入值"/>
</a-col>
@ -316,7 +336,12 @@
let child = { ...item2 }
child.label = child.label || child.text
child.label = data.label + '-' + child.label
child.value = data.value + ',' + child.value
// update--begin--author:sunjianlei-----date:20220121------for【JTC-1167】【表单设计器】高级查询一对一字段查询不好使
// 是否仅包含字段名,不需要拼接子表表名
if (!data.onlyFieldName) {
child.value = data.value + ',' + child.value
}
// update--end--author:sunjianlei-----date:20220121------for【JTC-1167】【表单设计器】高级查询一对一字段查询不好使
child.val = ''
return child
})
@ -338,6 +363,17 @@
}
this.visible = true
},
getDictInfo(item) {
let str = ''
if(!item.dictTable){
str = item.dictCode
}else{
str = item.dictTable+','+item.dictText+','+item.dictCode
}
console.log('高级查询字典信息',str)
return str
},
handleOk() {
if (!this.isNullArray(this.queryParamsModel)) {
let event = {
@ -353,7 +389,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) {
@ -362,7 +398,7 @@
}
}
console.debug('---高级查询参数--->', { params, matchType })
this.$emit(this.callback, params, matchType)
this.$emit(this.callback, params, matchType, loadStatus)
},
handleCancel() {
this.close()
@ -386,23 +422,36 @@
this.queryParamsModel.splice(index, 1)
},
handleSelected(node, item) {
let { type, options, dictCode, dictTable, 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
item['dictText'] = dictText
//update-begin-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时高级查询选择后查询仍然是Y/N
item['extendOption'] = node.dataRef.extendOption || ['Y', 'N']
//update-begin-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时高级查询选择后查询仍然是Y/N
item['customReturnField'] = customReturnField
if (popup) {
item['popup'] = popup
}
// 格式化字符串,一般用于高级查询的日期格式处理
if (node.dataRef.formatStr) {
item['formatStr'] = node.dataRef.formatStr
}
this.$set(item, 'val', undefined)
},
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) {
@ -374,14 +384,17 @@
},
mounted(){
const moverObj = document.getElementById(this.containerId+'-mover');
moverObj.addEventListener('mouseover',()=>{
this.moverHold = true
this.moveDisplay = 'block';
});
moverObj.addEventListener('mouseout',()=>{
this.moverHold = false
this.moveDisplay = 'none';
});
if(moverObj){
moverObj.addEventListener('mouseover',()=>{
this.moverHold = true
this.moveDisplay = 'block';
});
moverObj.addEventListener('mouseout',()=>{
this.moverHold = false
this.moveDisplay = 'none';
});
}
let picList = document.getElementById(this.containerId)?document.getElementById(this.containerId).getElementsByClassName('ant-upload-list-picture-card'):[];
if(picList && picList.length>0){
picList[0].addEventListener('mouseover',(ev)=>{

View File

@ -1,5 +1,5 @@
<template>
<a-popover :visible="visible" placement="bottom" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
<a-popover :visible="visible" :placement="placement" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
<div class="j-vxe-popover-title" slot="title">
<div>子表</div>
<div class="j-vxe-popover-title-close" @click="close">
@ -34,6 +34,7 @@
width: null,
zIndex: 100
},
placement: 'bottom'
}
},
created() {
@ -41,6 +42,14 @@
methods: {
toggle(event) {
//update-begin-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
if(document.body.clientHeight - event.$event.clientY > 350){
this.placement = 'bottom'
}else{
this.placement = 'top'
}
//update-end-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
if (this.row == null) {
this.open(event)
} else {
@ -62,6 +71,12 @@
let className = target.className || ''
className = typeof className === 'string' ? className : className.toString()
// 获取 td 父级
let td = getParentNodeByTagName(target, 'td');
// 点击的是拖拽排序列,不做处理
if (td && td.querySelector('.j-vxe-ds-icons')) {
return
}
// 点击的是expand不做处理
if (className.includes('vxe-table--expand-btn')) {
return
@ -83,13 +98,22 @@
this.$refs.div.style.height = clientHeight + 'px'
this.overlayStyle.width = Number.parseInt((clientWidth - clientWidth * 0.04)) + 'px'
this.overlayStyle.maxWidth = this.overlayStyle.width
domAlign(this.$refs.div, tr, {
//update-begin-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
//let realTable = getParentNodeByTagName(tr, 'table')
//let left = realTable.parentNode.scrollLeft
let h = event.$event.clientY
if(h){
h = h-140
}
let toolbar = this.$refs.div.nextSibling
domAlign(this.$refs.div, toolbar, {
points: ['tl', 'tl'],
offset: [0, 0],
offset: [0, h],
overflow: {
alwaysByViewport: true
},
})
//update-end-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
this.$nextTick(() => {
this.visible = true
this.$nextTick(() => {

View File

@ -1,6 +1,6 @@
import XEUtils from 'xe-utils'
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { JVXETypes } from '@/components/jeecg/JVxeTable/index'
import { JVXETypes } from '@/components/jeecg/JVxeTable/jvxeTypes'
import VxeWebSocketMixins from '../mixins/vxe.web.socket.mixins'
import { initDictOptions } from '@/components/dict/JDictSelectUtil'
@ -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,12 +183,46 @@ 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)
}
if (column.cellRender) {
Object.assign(column.cellRender, renderOptions)
}
// update--begin--autor:lvdandan-----date:20201019------for:LOWCOD-882 【新行编辑】列表上带按钮的遮挡问题
if (column.$type === JVXETypes.file || column.$type === JVXETypes.image) {
if (column.width && column.width.endsWith('px')) {
column.width = Number.parseInt(column.width.substr(0,column.width.length-2))+Number.parseInt(1)+'px';
}
}
// update--begin--autor:lvdandan-----date:20201019------for:LOWCOD-882 【新行编辑】列表上带按钮的遮挡问题
// update--begin--autor:lvdandan-----date:20201211------for:JT-118 【online】 日期、时间控件长度较小
if (column.$type === JVXETypes.datetime || column.$type === JVXETypes.userSelect || column.$type === JVXETypes.departSelect) {
let width = column.width && column.width.endsWith('px')?Number.parseInt(column.width.substr(0,column.width.length-2)):0;
if(width <= 190){
column.width = '190px'
}
}
if (column.$type === JVXETypes.date) {
let width = column.width && column.width.endsWith('px')?Number.parseInt(column.width.substr(0,column.width.length-2)):0;
if(width <= 135){
column.width = '135px'
}
}
// update--end--autor:lvdandan-----date:20201211------for:JT-118 【online】 日期、时间控件长度较小
})
return this._innerColumns
},
@ -253,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
@ -332,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
@ -444,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() {
},
@ -643,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 []
},
@ -665,6 +763,8 @@ export default {
console.warn(`JVxeTable.setValues必须传递数组`)
return
}
// 是否更新了数据
let updated = false
values.forEach((item, idx) => {
let {rowKey, values: record} = item
let {row} = this.getIfRowById(rowKey)
@ -677,6 +777,7 @@ export default {
let oldValue = row[colKey]
let newValue = record[colKey]
if (newValue !== oldValue) {
updated = true
this.$set(row, colKey, newValue)
// 触发 valueChange 事件
this.trigger('valueChange', {
@ -693,6 +794,14 @@ export default {
}
})
})
// 【issues/3828】数据更新后重新计算统计列
if (updated && this.statistics.has) {
this.$nextTick(async () => {
let {xTable} = this.$refs.vxe.$refs;
await xTable.updateCache(true);
await xTable.updateData();
});
}
},
/** 获取所有的数据包括values、deleteIds */
@ -702,6 +811,11 @@ export default {
deleteData: this.getDeleteData()
}
},
/** 获取表格表单里的值 */
getValues(callback, rowIds) {
let tableData = this.getTableData({rowIds: rowIds})
callback('', tableData)
},
/** 获取表格数据 */
getTableData(options = {}) {
let {rowIds} = options
@ -727,6 +841,11 @@ export default {
newData.forEach(row => delete row.id)
return newData
},
/** 仅获取新增的数据,带有id */
getNewDataWithId() {
let newData = cloneObject(this.$refs.vxe.getInsertRecords())
return newData
},
/** 根据ID获取行新增的行也能查出来 */
getIfRowById(id) {
let row = this.getRowById(id), isNew = false
@ -758,10 +877,11 @@ export default {
* 添加一行或多行
*
* @param rows
* @param isOnlJs 是否是onlineJS增强触发的
* @return
*/
async addRows(rows = {}) {
return this._addOrInsert(rows, -1, 'added')
async addRows(rows = {}, isOnlJs) {
return this._addOrInsert(rows, -1, 'added', isOnlJs)
},
/**
@ -852,9 +972,94 @@ export default {
trigger(name, event = {}) {
event.$target = this
event.$table = this.$refs.vxe
//online增强参数兼容
event.target = this
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) => {
@ -873,7 +1078,11 @@ export default {
}
})
},
//options自定义赋值 刷新
virtualRefresh(){
this.scrolling = true
this.closeScrolling()
},
// 设置 this.scrolling 防抖模式
closeScrolling: simpleDebounce(function () {
this.scrolling = false
@ -1005,7 +1214,7 @@ export default {
return await xTable.updateData()
},
async _addOrInsert(rows = {}, index, triggerName) {
async _addOrInsert(rows = {}, index, triggerName, isOnlJs) {
let {xTable} = this.$refs.vxe.$refs
let records
if (Array.isArray(rows)) {
@ -1017,14 +1226,19 @@ export default {
records.forEach(record => this._createRow(record))
let result = await this.pushRows(records, {index: index, setActive: true})
// 遍历插入的行
for (let i = 0; i < result.rows.length; i++) {
let row = result.rows[i]
this.trigger(triggerName, {
row: row,
$table: xTable,
target: this,
})
// update--begin--autor:lvdandan-----date:20201117------for:LOWCOD-987 【新行编辑】js增强附表内置方法调用问题 #1819
// online js增强时以传过来值为准不再赋默认值
if (isOnlJs != true) {
for (let i = 0; i < result.rows.length; i++) {
let row = result.rows[i]
this.trigger(triggerName, {
row: row,
$table: xTable,
target: this,
})
}
}
// update--end--autor:lvdandan-----date:20201117------for:LOWCOD-987 【新行编辑】js增强附表内置方法调用问题 #1819
return result
},
// 创建新行,自动添加默认值
@ -1033,11 +1247,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, 'createValue')
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
},
@ -1184,17 +1407,17 @@ export default {
// 兼容 online 的规则
const fooPatterns = [
{title: '非空', value: '*', pattern: /^.+$/},
{title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,18}$/},
{title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,16}$/},
{title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/},
{title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/},
{title: '网址', value: 'url', pattern: /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/},
{title: '电子邮件', value: 'e', pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/},
{title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/},
{title: '邮政编码', value: 'p', pattern: /^[1-9]\d{5}$/},
{title: '邮政编码', value: 'p', pattern: /^[0-9]{6}$/},
{title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/},
{title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/},
{title: '整数', value: 'z', pattern: /^-?\d+$/},
{title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/},
{title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,5}))$/},
]
/** 旧版handler转为新版Validator */
@ -1230,4 +1453,4 @@ function uniqueValidator(event) {
}
}
return Promise.resolve()
}
}

View File

@ -10,6 +10,7 @@
<a-popconfirm
v-if="showRemove"
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
:disabled="disabled"
@confirm="trigger('remove')"
>
<a-button icon="minus" :disabled="disabled">删除</a-button>
@ -59,7 +60,7 @@
// TODO 需要将remove替换batch_delete
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
if(exclude.indexOf('batch_delete')>=0){
exclude.add('remove')
exclude.push('remove')
}
// 按钮权限 需要去掉不被授权的按钮
return arr.filter(item=>{

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

@ -0,0 +1,174 @@
<template>
<div>
<a-input
v-show="!departIds"
@click="openSelect"
placeholder="请点击选择部门"
v-model="departNames"
readOnly
:disabled="componentDisabled"
class="jvxe-select-input">
<a-icon slot="prefix" type="cluster" title="部门选择控件"/>
</a-input>
<j-select-depart-modal
ref="innerDepartSelectModal"
:modal-width="modalWidth"
: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" >
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ departNames }}</span>
<a-icon v-if="!componentDisabled" style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
</span>
</div>
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import JSelectDepartModal from '@/components/jeecgbiz/modal/JSelectDepartModal'
export default {
name: 'JVxeDepartSelectCell',
mixins: [JVxeCellMixins],
components:{
JSelectDepartModal
},
data() {
return {
departNames: '',
departIds: '',
selectedOptions: [],
customReturnField: 'id'
}
},
computed: {
custProps() {
const {departIds, originColumn: col, caseId, cellProps} = this
return {
...cellProps,
value: departIds,
field: col.field || col.key,
groupId: caseId,
class: 'jvxe-select'
}
},
componentDisabled(){
if(this.cellProps.disabled==true){
return true
}
return false
},
modalWidth(){
if(this.cellProps.modalWidth){
return this.cellProps.modalWidth
}else{
return 500
}
},
multi(){
if(this.cellProps.multi==false || this.originColumn.multi===false){
return false
}else{
return true
}
},
rootOpened(){
if(this.cellProps.open==false){
return false
}else{
return true
}
}
},
watch: {
innerValue: {
immediate: true,
handler(val) {
if (val == null || val === '') {
this.departIds = ''
} else {
this.departIds = val
}
}
}
},
methods: {
openSelect(){
// disabled 不弹窗
if (this.componentDisabled) {
return
}
this.$refs.innerDepartSelectModal.show()
},
handleEmpty(){
this.handleOK('')
},
handleOK(rows) {
let value = ''
if (!rows && rows.length <= 0) {
this.departNames = ''
this.departIds = ''
} else {
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)
},
initComp(departNames){
this.departNames = departNames
},
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: {
switches: {
visible: true
},
translate: {
enabled: false
}
}
}
</script>
<style scoped>
/deep/ .jvxe-select-input .ant-input{
border: none !important;
}
</style>

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,12 +118,25 @@
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 || '未知错误'
}
this.innerFile = value
// issues/I5FTO6 JVxeTypes.upload 文件上传的时候,触发不了事件
if (value.path) {
this.handleChangeCommon(fileGetValue(value));
}
},
// handleClickPreviewFile(id) {

View File

@ -0,0 +1,162 @@
<template>
<div>
<a-input
v-show="!userIds"
@click="openSelect"
placeholder="请选择用户"
v-model="userNames"
readOnly
class="jvxe-select-input"
:disabled="componentDisabled">
<a-icon slot="prefix" type="user" title="用户选择控件"/>
</a-input>
<j-select-user-by-dep-modal
ref="selectModal"
:modal-width="modalWidth"
: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>
<a-icon v-if="!componentDisabled" style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
</span>
</div>
<!-- <j-select-user-by-dep
v-bind="custProps"
@change="handleChange"
:trigger-change="true">
</j-select-user-by-dep>-->
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import JSelectUserByDepModal from '@/components/jeecgbiz/modal/JSelectUserByDepModal'
export default {
name: 'JVxeUserSelectCell',
mixins: [JVxeCellMixins],
components: { JSelectUserByDepModal },
data() {
return {
userIds:'',
userNames:'',
innerUserValue: '',
selectedOptions: []
}
},
computed: {
custProps() {
const {userIds, originColumn: col, caseId, cellProps} = this
return {
...cellProps,
value: userIds,
field: col.field || col.key,
groupId: caseId,
class: 'jvxe-select'
}
},
componentDisabled(){
console.log('333',this.cellProps)
if(this.cellProps.disabled==true){
return true
}
return false
},
modalWidth(){
if(this.cellProps.modalWidth){
return this.cellProps.modalWidth
}else{
return 1250
}
},
multi(){
if(this.cellProps.multi==false){
return false
}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: {
innerValue: {
immediate: true,
handler(val) {
if (val == null || val === '') {
this.userIds = ''
} else {
this.userIds = val
}
}
}
},
methods: {
openSelect() {
// disabled 不弹窗
if (this.componentDisabled) {
return
}
this.$refs.selectModal.showModal()
},
selectOK(rows, idstr) {
console.log("当前选中用户", rows)
console.log("当前选中用户ID", idstr)
if (!rows) {
this.userNames = ''
this.userIds = ''
} else {
this.userIds = rows.map(row => row[this.storeField]).join(',')
this.userNames = rows.map(row => row[this.textField]).join(',')
}
this.handleChangeCommon(this.userIds)
},
handleEmpty(){
this.selectOK('')
},
initComp(userNames) {
this.userNames = userNames
},
},
enhanced: {
switches: {
visible: true
},
translate: {
enabled: false
}
}
}
</script>
<style scoped>
/deep/ .jvxe-select-input .ant-input {
border: none !important;
}
</style>

View File

@ -1,3 +1,4 @@
import * as jvxeTypes from './jvxeTypes'
import { installCell, mapCell } from './install'
import JVxeTable from './components/JVxeTable'
@ -5,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'
@ -12,46 +14,13 @@ import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell'
import JVxeProgressCell from './components/cells/JVxeProgressCell'
import JVxeTextareaCell from './components/cells/JVxeTextareaCell'
import JVxeDragSortCell from './components/cells/JVxeDragSortCell'
import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell'
import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell'
//update--begin--autor:lvdandan-----date:20201216------forJVxeTable--JVXETypes 【online】代码结构调整便于online打包
// 组件类型
export const JVXETypes = {
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
// 前缀是自动加的代码中直接用就行JVXETypes.input
_prefix: 'j-',
// 行号列
rowNumber: 'row-number',
// 选择列
rowCheckbox: 'row-checkbox',
// 单选列
rowRadio: 'row-radio',
// 展开列
rowExpand: 'row-expand',
// 上下排序
rowDragSort: 'row-drag-sort',
input: 'input',
inputNumber: 'inputNumber',
textarea: 'textarea',
select: 'select',
date: 'date',
datetime: 'datetime',
checkbox: 'checkbox',
upload: 'upload',
// 下拉搜索
selectSearch: 'select-search',
// 下拉多选
selectMultiple: 'select-multiple',
// 进度条
progress: 'progress',
// 拖轮Tags暂无用
tags: 'tags',
slot: 'slot',
normal: 'normal',
hidden: 'hidden',
}
export const JVXETypes = jvxeTypes.JVXETypes
//update--end--autor:lvdandan-----date:20201216------forJVxeTable--JVXETypes 【online】代码结构调整便于online打包
// 注册自定义组件
export const AllCells = {
@ -64,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),
@ -72,6 +42,8 @@ export const AllCells = {
...mapCell(JVXETypes.rowDragSort, JVxeDragSortCell),
...mapCell(JVXETypes.slot, JVxeSlotCell),
...mapCell(JVXETypes.departSelect, JVxeDepartSelectCell),
...mapCell(JVXETypes.userSelect, JVxeUserSelectCell)
/* hidden 是特殊的组件,不在这里注册 */
}

View File

@ -62,8 +62,8 @@ VXETable.interceptor.add('event.clearActived', function (params, event, target)
if (className.includes('j-input-pop')) {
return false
}
// 点击的标签是JPopup的弹出层
if (className.includes('j-popup-modal')) {
// 点击的标签是JPopup的弹出层、部门选择、用户选择
if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) {
return false
}
// 执行增强

View File

@ -0,0 +1,45 @@
// 组件类型
export default JVXETypes
export const JVXETypes = {
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
// 前缀是自动加的代码中直接用就行JVXETypes.input
_prefix: 'j-',
// 行号列
rowNumber: 'row-number',
// 选择列
rowCheckbox: 'row-checkbox',
// 单选列
rowRadio: 'row-radio',
// 展开列
rowExpand: 'row-expand',
// 上下排序
rowDragSort: 'row-drag-sort',
input: 'input',
inputNumber: 'inputNumber',
textarea: 'textarea',
select: 'select',
date: 'date',
datetime: 'datetime',
time: 'time',
checkbox: 'checkbox',
upload: 'upload',
// 下拉搜索
selectSearch: 'select-search',
// 下拉多选
selectMultiple: 'select-multiple',
// 进度条
progress: 'progress',
//部门选择
departSelect: 'sel_depart',
//用户选择
userSelect: 'sel_user',
// 拖轮Tags暂无用
tags: 'tags',
slot: 'slot',
normal: 'normal',
hidden: 'hidden',
}

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
}
}
},
},
@ -149,6 +168,8 @@ export default {
packageEvent(name, event = {}) {
event.row = this.row
event.column = this.column
//online增强参数兼容
event.column['key'] = this.column['property']
event.cellTarget = this
if (!event.type) {
event.type = name
@ -289,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) {
@ -296,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

@ -1,5 +1,8 @@
import store from '@/store/'
import { randomUUID } from '@/utils/util'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
// vxe socket
const vs = {
// 页面唯一 id用于标识同一用户不同页面的websocket
@ -52,7 +55,10 @@ const vs = {
const domain = window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://')
const url = `${domain}/vxeSocket/${userId}/${this.pageId}`
this.ws = new WebSocket(url)
//update-begin-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
let token = Vue.ls.get(ACCESS_TOKEN)
this.ws = new WebSocket(url, [token])
//update-end-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
this.ws.onopen = this.on.open.bind(this)
this.ws.onerror = this.on.error.bind(this)
this.ws.onmessage = this.on.message.bind(this)

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

@ -258,7 +258,7 @@ export default {
components: { JTreeTable },
data() {
return {
url: '/api/asynTreeList',
url: '/mock/api/asynTreeList',
columns: [
{ title: '菜单名称', dataIndex: 'name' },
{ title: '组件', dataIndex: 'component' },

View File

@ -1,246 +0,0 @@
<template>
<div class="tinymce-containerty" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" @change="ada"/>
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import load from './load'
//const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
//const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
export default {
name: 'JEditorDyn',
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
toolbar: {
type: [String, Array],
required: false,
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
},
menubar: {
type: String,
default: 'file edit insert view format table'
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
},
plugins: {
type: [String, Array],
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
}
}
},
computed: {
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`
}
return width
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''))
}
}
},
mounted() {
this.init()
},
activated() {
if (window.tinymce) {
this.initTinymce()
}
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
ada() {
console.log('change')
},
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
if (err) {
this.$message.error(err.message)
return
}
this.initTinymce()
})
},
initTinymce() {
const _this = this
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['zh'],
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar,
menubar: false,
plugins: this.plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true
this.$emit('input', editor.getContent())
})
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
},
// it will try to keep these URLs intact
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
convert_urls: false
// 整合七牛上传
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
// }
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('err')
// console.log(err);
// });
// },
})
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')
}
if (tinymce) {
tinymce.destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
}
}
}
</script>
<style lang="less" scoped>
.tinymce-containerty {
position: relative;
line-height: normal;
}
.tinymce-containerty {
::v-deep {
.mce-fullscreen {
z-index: 10000;
}
}
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>

View File

@ -1,142 +0,0 @@
<template>
<div class="j-markdown-editor" :id="dynamicId"/>
</template>
<script>
import load from './load'
import { md_js, md_zh_cn_js } from './Resource'
import defaultOptions from '@/components/jeecg/JMarkdownEditor/default-options.js'
export default {
name: 'JMdEditorDyn',
props: {
value: {
type: String,
default: ''
},
id: {
type: String,
required: false,
default() {
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
options: {
type: Object,
default() {
return defaultOptions
}
},
mode: {
type: String,
default: 'markdown'
},
height: {
type: String,
required: false,
default: '300px'
},
language: {
type: String,
required: false,
default: 'zh-CN'
}
},
data() {
return {
editor: null,
dynamicId: this.id
}
},
computed: {
editorOptions() {
const options = Object.assign({}, defaultOptions, this.options)
options.initialEditType = this.mode
options.height = this.height
options.language = this.language
return options
}
},
watch: {
value(newValue, preValue) {
if (newValue !== preValue && newValue !== this.editor.getMarkdown()) {
this.editor.setMarkdown(newValue)
}
},
language(val) {
this.destroyEditor()
this.initEditor()
},
height(newValue) {
this.editor.height(newValue)
},
mode(newValue) {
this.editor.changeMode(newValue)
}
},
mounted() {
this.init()
},
destroyed() {
this.destroyEditor()
},
methods: {
init(){
this.initEditor()
/* load(md_js,'',()=>{
load(md_zh_cn_js,'',()=>{
})
})*/
},
initEditor() {
const Editor = toastui.Editor
this.editor = new Editor({
el: document.getElementById(this.dynamicId),
...this.editorOptions
})
if (this.value) {
this.editor.setMarkdown(this.value)
}
this.editor.on('change', () => {
this.$emit('change', this.editor.getMarkdown())
})
},
destroyEditor() {
if (!this.editor) return
this.editor.off('change')
this.editor.remove()
},
setMarkdown(value) {
this.editor.setMarkdown(value)
},
getMarkdown() {
return this.editor.getMarkdown()
},
setHtml(value) {
this.editor.setHtml(value)
},
getHtml() {
return this.editor.getHtml()
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped lang="less">
.j-markdown-editor {
/deep/ .tui-editor-defaultUI {
.te-mode-switch,
.tui-scrollsync
{
line-height: 1.5;
}
}
}
</style>

View File

@ -1,325 +0,0 @@
<template>
<div class="jeecg-editor-ty" :class="fullCoder?'jeecg-editor-max':'jeecg-editor-min'">
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
<textarea :id="dynamicId" />
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden': hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
</div>
</template>
<script>
import load from './load'
import '@/assets/less/codemirror_idea.css'
import './cm_sql_hint.js'
import { sql_keyword } from './Resource'
export default {
name: 'JSqlCodeEditorDyn',
props:{
id: {
type: String,
default: function() {
return 'vue-editor-' + new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
// 显示行号
lineNumbers: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: ''
},
zIndex: {
type: [Number, String],
default: 999
},
autoHeight: {
type: [String, Boolean],
default: true
},
// 不自适应高度的情况下生效的固定高度
height: {
type: [String, Number],
default: '240px'
},
autoHeight: {
type: [String, Boolean],
default: true
},
// 是否显示全屏按钮
fullScreen: {
type: Boolean,
default: false
},
autoHint:{
type: Boolean,
default: true
}
},
data(){
return {
dynamicId: this.id,
coder: '',
hasCode: false,
code: '',
// code 编辑器 是否全屏
fullCoder: false,
iconType: 'fullscreen',
}
},
computed:{
placeholderShow() {
if (this.placeholder == null) {
return `请在此输入javascript代码`
} else {
return this.placeholder
}
},
nullTipStyle(){
if (this.lineNumbers) {
return { left: '36px' }
} else {
return { left: '12px' }
}
},
isAutoHeight() {
let {autoHeight} = this
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
autoHeight = !(isIE() || isIE11())
} else {
autoHeight = true
}
return autoHeight
},
fullScreenParentProps() {
let props = {
class: {
'full-screen-parent': true,
'full-screen': this.fullCoder,
'auto-height': this.isAutoHeight
},
style: {}
}
if (this.fullCoder) {
props.style['z-index'] = this.zIndex
}
if (!this.isAutoHeight) {
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
}
return props
}
},
watch: {
fullCoder:{
handler(value) {
if(value){
this.iconType="fullscreen-exit"
}else{
this.iconType="fullscreen"
}
}
}
},
mounted() {
this.init()
},
methods:{
init(){
this.main();
},
main(){
let obj = document.getElementById(this.dynamicId);
const that = this;
let editor = CodeMirror.fromTextArea(obj,{
theme:'idea',
lineNumbers: this.lineNumbers,
lineWrapping: true,
mode: "sql",
indentUnit: 1,
indentWithTabs: true,
styleActiveLine: true,
/* styleSelectedText: false, */
extraKeys: {
"F11": function(cm) {
that.fullCoder = !that.fullCoder
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
},
"Esc": function(cm) {
that.fullCoder = false
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
},
"Alt-/": function(cm) {
cm.showHint();
},
"Tab": (cm) => {
if (cm.somethingSelected()) {
cm.indentSelection('add');
} else {
//cm.indentLine(cm.getCursor().line, "add");
//走两格 第三格输入
cm.replaceSelection(Array(3).join(" "), "end", "+input");
}
},
"Shift-Tab": (cm) => {
if (cm.somethingSelected()) {
cm.indentSelection('subtract');
} else {
// cm.indentLine(cm.getCursor().line, "subtract");
const cursor = cm.getCursor();
// 光标回退 indexUnit 字符
cm.setCursor({line: cursor.line, ch: cursor.ch - 4});
}
return ;
}
}
})
this.coder = editor
this.addEvent();
this.setCoderValue();
this.addSystemHint();
},
setCoderValue(){
if(this.value||this.code){
this.hasCode=true
this.setCodeContent(this.value || this.code)
}else{
this.coder.setValue('')
this.hasCode=false
}
},
getCodeContent(){
return this.code
},
setCodeContent(val){
setTimeout(()=>{
if(!val){
this.coder.setValue('')
}else{
this.coder.setValue(val)
}
},300)
},
addSystemHint(){
this.coder.setOption('hintOptions', {
completeSingle: false,
tables: sql_keyword
});
},
addEvent(){
if(this.autoHint){
this.coder.on('cursorActivity', ()=>{
this.coder.showHint();
});
}
this.coder.on('change', (coder) => {
this.code = coder.getValue()
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
if (this.$emit) {
this.$emit('input', this.code)
}
});
this.coder.on('focus', () => {
this.hasCode=true
});
this.coder.on('blur', () => {
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
});
},
loadResource(src,type){
return new Promise((resolve,reject)=>{
load(src,type,(msg)=>{
if(!msg){
resolve();
}else{
reject(msg)
}
})
})
},
nullTipClick(){
this.coder.focus()
},
fullToggle(){
this.fullCoder = !this.fullCoder
this.coder.setOption("fullScreen", this.fullCoder);
}
}
}
</script>
<style lang="less" >
.jeecg-editor-ty{
position: relative;
.full-screen-icon {
opacity: 0;
color: black;
width: 20px;
height: 20px;
line-height: 24px;
background-color: white;
position: absolute;
top: 4px;
right: 2px;
z-index: 9;
cursor: pointer;
transition: opacity 0.3s;
}
&:hover {
.full-screen-icon {
opacity: 1;
&:hover {
background-color: rgba(255, 255, 255, 0.88);
}
}
}
.null-tip{
position: absolute;
top: 4px;
left: 36px;
z-index: 10;
font-size:16px;
color: #acaaaac9;
line-height: initial;
}
.null-tip-hidden{
display: none;
}
}
.jeecg-editor-max{
position: fixed;
left: 0;
top: 0;
z-index: 999;
height: 100%;
width: 100% !important;
.CodeMirror{
position: inherit !important;
width: 100%;
height: 100%;
}
.full-screen-icon{
z-index:9999;
}
}
</style>

View File

@ -1,319 +0,0 @@
<template>
<div class="jeecg-editor-ty" :class="fullCoder?'jeecg-editor-max':'jeecg-editor-min'">
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
<textarea :id="dynamicId" />
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden': hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
</div>
</template>
<script>
import '@/assets/less/codemirror_idea.css'
import './cm_hint.js'
export default {
name: 'JsCodeEditorDyn',
props:{
id: {
type: String,
default: function() {
return 'vue-editor-' + new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
// 显示行号
lineNumbers: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: ''
},
zIndex: {
type: [Number, String],
default: 999
},
autoHeight: {
type: [String, Boolean],
default: true
},
// 不自适应高度的情况下生效的固定高度
height: {
type: [String, Number],
default: '240px'
},
autoHeight: {
type: [String, Boolean],
default: true
},
// 是否显示全屏按钮
fullScreen: {
type: Boolean,
default: false
},
},
data(){
return {
dynamicId: this.id,
coder: '',
hasCode: false,
code: '',
// code 编辑器 是否全屏
fullCoder: false,
iconType: 'fullscreen',
}
},
computed:{
placeholderShow() {
if (this.placeholder == null) {
return `请在此输入javascript代码`
} else {
return this.placeholder
}
},
nullTipStyle(){
if (this.lineNumbers) {
return { left: '36px' }
} else {
return { left: '12px' }
}
},
isAutoHeight() {
let {autoHeight} = this
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
autoHeight = !(isIE() || isIE11())
} else {
autoHeight = true
}
return autoHeight
},
fullScreenParentProps() {
let props = {
class: {
'full-screen-parent': true,
'full-screen': this.fullCoder,
'auto-height': this.isAutoHeight
},
style: {}
}
if (this.fullCoder) {
props.style['z-index'] = this.zIndex
}
if (!this.isAutoHeight) {
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
}
return props
}
},
watch: {
fullCoder:{
handler(value) {
if(value){
this.iconType="fullscreen-exit"
}else{
this.iconType="fullscreen"
}
}
}
},
mounted() {
this.init()
},
methods:{
init(){
this.main();
},
main(){
let obj = document.getElementById(this.dynamicId);
const that = this;
let editor = CodeMirror.fromTextArea(obj,{
theme:'idea',
lineNumbers: this.lineNumbers,
lineWrapping: true,
mode: "javascript",
indentUnit: 1,
indentWithTabs: true,
styleActiveLine: true,
/* styleSelectedText: false, */
extraKeys: {
"F11": function(cm) {
that.fullCoder = !that.fullCoder
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
},
"Esc": function(cm) {
that.fullCoder = false
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
},
"Alt-/": function(cm) {
let a = cm.getValue()+""
console.log('a',a)
cm.showHint();
},
"Tab": (cm) => {
if (cm.somethingSelected()) {
cm.indentSelection('add');
} else {
//cm.indentLine(cm.getCursor().line, "add");
//走两格 第三格输入
cm.replaceSelection(Array(3).join(" "), "end", "+input");
}
},
"Shift-Tab": (cm) => {
if (cm.somethingSelected()) {
cm.indentSelection('subtract');
} else {
// cm.indentLine(cm.getCursor().line, "subtract");
const cursor = cm.getCursor();
// 光标回退 indexUnit 字符
cm.setCursor({line: cursor.line, ch: cursor.ch - 4});
}
return ;
}
}
})
this.coder = editor
this.addEvent();
this.setCoderValue();
},
setCoderValue(){
if(this.value||this.code){
this.hasCode=true
this.setCodeContent(this.value || this.code)
}else{
this.coder.setValue('')
this.hasCode=false
}
},
getCodeContent(){
return this.code
},
setCodeContent(val){
setTimeout(()=>{
if(!val){
this.coder.setValue('')
}else{
this.coder.setValue(val)
}
},300)
},
addEvent(){
const that = this;
this.coder.on('cursorActivity',function(wl) {
let arr = wl.state.activeLines
if(arr && arr.length>0){
let text = arr[0].text
if(text.lastIndexOf('that.')>=0){
that.coder.showHint();
}
}
});
this.coder.on('change', (coder) => {
this.code = coder.getValue()
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
if (this.$emit) {
this.$emit('input', this.code)
}
});
this.coder.on('focus', () => {
this.hasCode=true
});
this.coder.on('blur', () => {
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
});
},
loadResource(src,type){
return new Promise((resolve,reject)=>{
load(src,type,(msg)=>{
if(!msg){
resolve();
}else{
reject(msg)
}
})
})
},
nullTipClick(){
this.coder.focus()
},
fullToggle(){
this.fullCoder = !this.fullCoder
this.coder.setOption("fullScreen", this.fullCoder);
}
}
}
</script>
<style lang="less" >
.jeecg-editor-ty{
position: relative;
.full-screen-icon {
opacity: 0;
color: black;
width: 20px;
height: 20px;
line-height: 24px;
background-color: white;
position: absolute;
top: 4px;
right: 2px;
z-index: 9;
cursor: pointer;
transition: opacity 0.3s;
}
&:hover {
.full-screen-icon {
opacity: 1;
&:hover {
background-color: rgba(255, 255, 255, 0.88);
}
}
}
.null-tip{
position: absolute;
top: 4px;
left: 36px;
z-index: 10;
font-size:16px;
color: #acaaaac9;
line-height: initial;
}
.null-tip-hidden{
display: none;
}
}
.jeecg-editor-max{
position: fixed;
left: 0;
top: 0;
z-index: 999;
height: 100%;
width: 100% !important;
.CodeMirror{
position: inherit !important;
width: 100%;
height: 100%;
}
.full-screen-icon{
z-index:9999;
}
}
</style>

View File

@ -1,24 +0,0 @@
/**js编辑器关键词用于提示*/
const js_keyword = [
'that',
'getAction','postAction','deleteAction',
'beforeAdd','beforeEdit','beforeDelete','mounted','created','show'
]
/**js编辑器 方法名用于提示*/
const js_method = [
'.getSelectOptions','.changeOptions','.triggleChangeValues','.immediateEnhance ','.simpleDateFormat','.lodash'
]
/**sql编辑器 表名字段名用于提示*/
const sql_keyword = {
sys_user: ['USERNAME', 'REALNAME', 'ID','BIRTHDAY','AGE'],
demo: ['name', 'age', 'id', 'sex']
}
export {
js_keyword,
js_method,
sql_keyword
}

View File

@ -1,177 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
import { js_keyword, js_method } from './Resource'
(function(mod) {
mod(CodeMirror);
/*if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env*/
})(function(CodeMirror) {
var Pos = CodeMirror.Pos;
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
function arrayContains(arr, item) {
if (!Array.prototype.indexOf) {
var i = arr.length;
while (i--) {
if (arr[i] === item) {
return true;
}
}
return false;
}
return arr.indexOf(item) != -1;
}
function scriptHint(editor, keywords, getToken, options) {
// Find the token at the cursor
var cur = editor.getCursor(), token = getToken(editor, cur);
if (/\b(?:string|comment)\b/.test(token.type)) return;
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
if (innerMode.mode.helperType === "json") return;
token.state = innerMode.state;
if('.' === token.string){
let arr = []
for(let k of js_method){
arr.push(k)
}
return {
list: arr,
from: Pos(cur.line, token.start),
to: Pos(cur.line, token.end)
};
}
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_]*$/.test(token.string)) {
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
type: token.string == "." ? "property" : null};
} else if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
var tprop = token;
// If it is a property, find out what it is a property of.
while (tprop.type == "property") {
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.string != ".") return;
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (!context) var context = [];
context.push(tprop);
}
return {list: getCompletions(token, context, keywords, options),
from: Pos(cur.line, token.start),
to: Pos(cur.line, token.end)};
}
function javascriptHint(editor, options) {
return scriptHint(editor, javascriptKeywords,
function (e, cur) {return e.getTokenAt(cur);},
options);
};
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
function getCoffeeScriptToken(editor, cur) {
// This getToken, it is for coffeescript, imitates the behavior of
// getTokenAt method in javascript.js, that is, returning "property"
// type and treat "." as indepenent token.
var token = editor.getTokenAt(cur);
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
token.end = token.start;
token.string = '.';
token.type = "property";
}
else if (/^\.[\w$_]*$/.test(token.string)) {
token.type = "property";
token.start++;
token.string = token.string.replace(/\./, '');
}
return token;
}
function coffeescriptHint(editor, options) {
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
}
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
"toUpperCase toLowerCase split concat match replace search").split(" ");
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
var funcProps = "prototype apply call bind".split(" ");
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
"if in import instanceof new null return super switch this throw true try typeof var void while with yield that").split(" ");
for(let jk of js_keyword){
javascriptKeywords.push(jk)
}
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
function forAllProps(obj, callback) {
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
for (var name in obj) callback(name)
} else {
for (var o = obj; o; o = Object.getPrototypeOf(o))
Object.getOwnPropertyNames(o).forEach(callback)
}
}
function getCompletions(token, context, keywords, options) {
var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
function gatherCompletions(obj) {
if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
forAllProps(obj, maybeAdd)
}
if (context && context.length) {
// If this is a property, see if it belongs to some object we can
// find in the current environment.
var obj = context.pop(), base;
if (obj.type && obj.type.indexOf("variable") === 0) {
if (options && options.additionalContext)
base = options.additionalContext[obj.string];
if (!options || options.useGlobalScope !== false)
base = base || global[obj.string];
} else if (obj.type == "string") {
base = "";
} else if (obj.type == "atom") {
base = 1;
} else if (obj.type == "function") {
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
(typeof global.jQuery == 'function'))
base = global.jQuery();
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
base = global._();
}
while (base != null && context.length)
base = base[context.pop().string];
if (base != null) gatherCompletions(base);
} else {
// If not, just look in the global object, any local scope, and optional additional-context
// (reading into JS mode internals to get at the local and global variables)
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
for (var c = token.state.context; c; c = c.prev)
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
if (options && options.additionalContext != null)
for (var key in options.additionalContext)
maybeAdd(key);
if (!options || options.useGlobalScope !== false)
gatherCompletions(global);
forEach(keywords, maybeAdd);
}
return found;
}
});

View File

@ -1,305 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
/*if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
else */
// Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var tables;
var defaultTable;
var keywords;
var identifierQuote;
var CONS = {
QUERY_DIV: ";",
ALIAS_KEYWORD: "AS"
};
var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
function getKeywords(editor) {
var mode = editor.doc.modeOption;
if (mode === "sql") mode = "text/x-sql";
return CodeMirror.resolveMode(mode).keywords;
}
function getIdentifierQuote(editor) {
var mode = editor.doc.modeOption;
if (mode === "sql") mode = "text/x-sql";
return CodeMirror.resolveMode(mode).identifierQuote || "`";
}
function getText(item) {
return typeof item == "string" ? item : item.text;
}
function wrapTable(name, value) {
if (isArray(value)) value = {columns: value}
if (!value.text) value.text = name
return value
}
function parseTables(input) {
var result = {}
if (isArray(input)) {
for (var i = input.length - 1; i >= 0; i--) {
var item = input[i]
result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
}
} else if (input) {
for (var name in input)
result[name.toUpperCase()] = wrapTable(name, input[name])
}
return result
}
function getTable(name) {
return tables[name.toUpperCase()]
}
function shallowClone(object) {
var result = {};
for (var key in object) if (object.hasOwnProperty(key))
result[key] = object[key];
return result;
}
function match(string, word) {
var len = string.length;
var sub = getText(word).substr(0, len);
return string.toUpperCase() === sub.toUpperCase();
}
function addMatches(result, search, wordlist, formatter) {
if (isArray(wordlist)) {
for (var i = 0; i < wordlist.length; i++)
if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
} else {
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
var val = wordlist[word]
if (!val || val === true)
val = word
else
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
if (match(search, val)) result.push(formatter(val))
}
}
}
function cleanName(name) {
// Get rid name from identifierQuote and preceding dot(.)
if (name.charAt(0) == ".") {
name = name.substr(1);
}
// replace doublicated identifierQuotes with single identifierQuotes
// and remove single identifierQuotes
var nameParts = name.split(identifierQuote+identifierQuote);
for (var i = 0; i < nameParts.length; i++)
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
return nameParts.join(identifierQuote);
}
function insertIdentifierQuotes(name) {
var nameParts = getText(name).split(".");
for (var i = 0; i < nameParts.length; i++)
nameParts[i] = identifierQuote +
// doublicate identifierQuotes
nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
identifierQuote;
var escaped = nameParts.join(".");
if (typeof name == "string") return escaped;
name = shallowClone(name);
name.text = escaped;
return name;
}
function nameCompletion(cur, token, result, editor) {
// Try to complete table, column names and return start position of completion
var useIdentifierQuotes = false;
var nameParts = [];
var start = token.start;
var cont = true;
while (cont) {
cont = (token.string.charAt(0) == ".");
useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
start = token.start;
nameParts.unshift(cleanName(token.string));
token = editor.getTokenAt(Pos(cur.line, token.start));
if (token.string == ".") {
cont = true;
token = editor.getTokenAt(Pos(cur.line, token.start));
}
}
// Try to complete table names
var string = nameParts.join(".");
addMatches(result, string, tables, function(w) {
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
// Try to complete columns from defaultTable
addMatches(result, string, defaultTable, function(w) {
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
// Try to complete columns
string = nameParts.pop();
var table = nameParts.join(".");
var alias = false;
var aliasTable = table;
// Check if table is available. If not, find table by Alias
if (!getTable(table)) {
var oldTable = table;
table = findTableByAlias(table, editor);
if (table !== oldTable) alias = true;
}
var columns = getTable(table);
if (columns && columns.columns)
columns = columns.columns;
if (columns) {
addMatches(result, string, columns, function(w) {
var tableInsert = table;
if (alias == true) tableInsert = aliasTable;
if (typeof w == "string") {
w = tableInsert + "." + w;
} else {
w = shallowClone(w);
w.text = tableInsert + "." + w.text;
}
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
}
return start;
}
function eachWord(lineText, f) {
var words = lineText.split(/\s+/)
for (var i = 0; i < words.length; i++)
if (words[i]) f(words[i].replace(/[,;]/g, ''))
}
function findTableByAlias(alias, editor) {
var doc = editor.doc;
var fullQuery = doc.getValue();
var aliasUpperCase = alias.toUpperCase();
var previousWord = "";
var table = "";
var separator = [];
var validRange = {
start: Pos(0, 0),
end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
};
//add separator
var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
while(indexOfSeparator != -1) {
separator.push(doc.posFromIndex(indexOfSeparator));
indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
}
separator.unshift(Pos(0, 0));
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
//find valid range
var prevItem = null;
var current = editor.getCursor()
for (var i = 0; i < separator.length; i++) {
if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
validRange = {start: prevItem, end: separator[i]};
break;
}
prevItem = separator[i];
}
if (validRange.start) {
var query = doc.getRange(validRange.start, validRange.end, false);
for (var i = 0; i < query.length; i++) {
var lineText = query[i];
eachWord(lineText, function(word) {
var wordUpperCase = word.toUpperCase();
if (wordUpperCase === aliasUpperCase && getTable(previousWord))
table = previousWord;
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
previousWord = word;
});
if (table) break;
}
}
return table;
}
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
tables = parseTables(options && options.tables)
var defaultTableName = options && options.defaultTable;
var disableKeywords = options && options.disableKeywords;
defaultTable = defaultTableName && getTable(defaultTableName);
keywords = getKeywords(editor);
identifierQuote = getIdentifierQuote(editor);
if (defaultTableName && !defaultTable)
defaultTable = findTableByAlias(defaultTableName, editor);
defaultTable = defaultTable || [];
if (defaultTable.columns)
defaultTable = defaultTable.columns;
var cur = editor.getCursor();
var result = [];
var token = editor.getTokenAt(cur), start, end, search;
if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) {
search = token.string;
start = token.start;
end = token.end;
} else {
start = end = cur.ch;
search = "";
}
if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
start = nameCompletion(cur, token, result, editor);
} else {
var objectOrClass = function(w, className) {
if (typeof w === "object") {
w.className = className;
} else {
w = { text: w, className: className };
}
return w;
};
addMatches(result, search, defaultTable, function(w) {
return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
});
addMatches(
result,
search,
tables, function(w) {
return objectOrClass(w, "CodeMirror-hint-table");
}
);
if (!disableKeywords)
addMatches(result, search, keywords, function(w) {
return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
});
}
return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
});
});

View File

@ -1,92 +0,0 @@
let callbacks = []
function loadSuccess(key) {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return window[key]
}
const load = (src, type, callback) => {
if(type=='link'){
loadStyle(src, callback)
}else{
let loadKey = ''
if(src.indexOf('tinymce')>=0){
loadKey = 'tinymce'
}else if(src.indexOf('codemirror')>=0){
loadKey = 'CodeMirror'
}
const scriptTag = document.getElementById(src)
//const cb = callback || function() {}
if (!scriptTag) {
const script = document.createElement('script')
script.src = src // src url for the third-party library being loaded.
script.id = src
script.onload=()=>callback()
script.onerror=()=>callback('加载失败'+src)
document.body.appendChild(script)
//callbacks.push(cb)
// const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
// onEnd(script)
}else{
if (loadSuccess(loadKey)) {
callback()
}
}
if (scriptTag) {
/* else {
callbacks.push(cb)
}*/
}
}
function stdOnEnd(script) {
script['onload'] = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
}
callbacks = null
}
script['onerror'] = function() {
this.onerror = this.onload = null
cb(new Error('Failed to load ' + src), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script) // there is no way to catch loading errors in IE8
}
callbacks = null
}
}
function loadStyle(src, callback) {
const link = document.getElementById(src)
if (!link) {
const link = document.createElement('link')
link.setAttribute("rel", "stylesheet");
link.setAttribute("type", "text/css");
link.setAttribute("href", src);
link.id = src
let heads = document.getElementsByTagName("head")
if(heads.length){
heads[0].appendChild(link)
}else{
document.documentElement.appendChild(link)
}
}
callback();
}
}
export default load

View File

@ -1,11 +1,91 @@
import JModal from './JModal'
import JFormContainer from './JFormContainer.vue'
import JPopup from './JPopup.vue'
import JMarkdownEditor from './JMarkdownEditor'
import JCodeEditor from './JCodeEditor.vue'
import JEditor from './JEditor.vue'
import JEditableTable from './JEditableTable.vue'
import JAreaLinkage from './JAreaLinkage.vue'
import JSuperQuery from './JSuperQuery.vue'
import JUpload from './JUpload.vue'
import JTreeSelect from './JTreeSelect.vue'
import JCategorySelect from './JCategorySelect.vue'
import JImageUpload from './JImageUpload.vue'
import JImportModal from './JImportModal.vue'
import JTreeDict from './JTreeDict.vue'
import JCheckbox from './JCheckbox.vue'
import JCron from './JCron.vue'
import JDate from './JDate.vue'
import JEllipsis from './JEllipsis.vue'
import JInput from './JInput.vue'
import JPopupOnlReport from './modal/JPopupOnlReport.vue'
import JFilePop from './minipop/JFilePop.vue'
import JInputPop from './minipop/JInputPop.vue'
import JSelectMultiple from './JSelectMultiple.vue'
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('JPopupOnlReport', JPopupOnlReport)
Vue.component('JFilePop', JFilePop)
Vue.component('JInputPop', JInputPop)
Vue.component('JAreaLinkage', JAreaLinkage)
Vue.component('JCategorySelect', JCategorySelect)
Vue.component('JCheckbox', JCheckbox)
Vue.component('JCodeEditor', JCodeEditor)
Vue.component('JCron', JCron)
Vue.component('JDate', JDate)
Vue.component('JEditableTable', JEditableTable)
Vue.component('JEditor', JEditor)
Vue.component('JEllipsis', JEllipsis)
Vue.component('JFormContainer', JFormContainer)
Vue.component('JImageUpload', JImageUpload)
Vue.component('JImportModal', JImportModal)
Vue.component('JInput', JInput)
Vue.component('JPopup', JPopup)
Vue.component(JModal.name, JModal)
Vue.component('JSelectMultiple', JSelectMultiple)
Vue.component('JSlider', JSlider)
Vue.component('JSuperQuery', JSuperQuery)
Vue.component('JSwitch', JSwitch)
Vue.component('JTime', JTime)
Vue.component('JTreeDict', JTreeDict)
Vue.component('JTreeSelect', JTreeSelect)
Vue.component('JTreeTable', JTreeTable)
Vue.component('JUpload', JUpload)
//jeecgbiz
Vue.component('JSelectDepart', JSelectDepart)
Vue.component('JSelectMultiUser', JSelectMultiUser)
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

@ -8,13 +8,12 @@
cancelText="取消"
@cancel="close">
<!--style="top: 20px;"-->
<j-upload :file-type="fileType" :value="filePath" @change="handleChange" :disabled="disabled"></j-upload>
<j-upload :file-type="fileType" :value="filePath" @change="handleChange" :disabled="disabled" :number="number"></j-upload>
</a-modal>
</div>
</template>
<script>
import JUpload from '@/components/jeecg/JUpload'
import { getFileAccessHttpUrl } from '@/api/manage';
const getFileName=(path)=>{
@ -27,7 +26,7 @@
export default {
name: 'JFilePop',
components: { JUpload },
components: { },
props:{
title:{
type:String,
@ -59,6 +58,11 @@
type:Boolean,
default:false,
required:false
},
number:{
type:Number,
required:false,
default: 0
}
},
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(){
@ -83,6 +87,10 @@
this.$emit('change',this.inputContent)
},
pop(){
// disabled 不弹窗
if (this.disabled) {
return
}
this.visible=true
this.$nextTick(() => {
this.$refs.textarea.focus()

View File

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

View File

@ -58,7 +58,7 @@
:dataSource="table.dataSource"
:pagination="table.pagination"
:loading="table.loading"
:rowSelection="{fixed:true,selectedRowKeys: table.selectedRowKeys, onChange: handleChangeInTableSelect}"
:rowSelection="{type:rowSelectionType,fixed:true,selectedRowKeys: table.selectedRowKeys, onChange: handleChangeInTableSelect}"
@change="handleChangeInTable"
style="min-height: 300px"
:scroll="tableScroll"
@ -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'],
props: ['multi', 'code', 'sorter', 'groupId', 'param'],
components:{
},
data(){
@ -121,22 +122,60 @@
},
cgRpConfigId:"",
modalWidth:MODAL_WIDTH,
tableScroll:{x:MODAL_WIDTH-100}
tableScroll:{x:true},
dynamicParam:{},
// 排序字段,默认无排序
iSorter: null,
}
},
mounted() {
this.loadColumnsInfo()
//this.loadColumnsInfo()
},
watch: {
code() {
this.loadColumnsInfo()
}
},
param:{
deep:true,
handler(){
// 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(){
return this.queryInfo && this.queryInfo.length>0
}
},
// 行选择框类型,根据是否多选来控制显示为单选框还是多选框
rowSelectionType() {
return this.multi ? 'checkbox' : 'radio'
},
},
methods:{
loadColumnsInfo(){
@ -159,10 +198,19 @@
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()
this.loadData(1)
} else {
this.$error({
title: '出错了',
content: (<p>Popup初始化失败请检查你的配置或稍后重试<br/>错误信息如下:{res.message}</p>),
onOk: () => this.close(),
})
}
})
},
@ -176,12 +224,43 @@
httpGroupRequest(() => getAction(url), groupIdKey).then((res) => {
// console.log("获取查询条件", res);
if (res.success) {
this.dynamicParamHandler(res.result)
this.queryInfo = res.result
//查询条件加载后再请求数据
this.loadData(1)
} else {
this.$message.warning(res.message)
}
})
},
//处理动态参数
dynamicParamHandler(arr){
if(arr && arr.length>0){
//第一次加载查询条件前 初始化queryParam为空对象
let queryTemp = {}
for(let item of arr){
if(item.mode==='single'){
queryTemp[item.field] = ''
}
}
this.queryParam = {...queryTemp}
}
let dynamicTemp = {}
if(this.param){
Object.keys(this.param).map(key=>{
let str = this.param[key]
if(key in this.queryParam){
if(str && str.startsWith("'") && str.endsWith("'")){
str = str.substring(1,str.length-1)
}
//如果查询条件包含参数 设置值
this.queryParam[key]=str
}
dynamicTemp[key] = this.param[key]
})
}
this.dynamicParam = {...dynamicTemp}
},
loadData(arg) {
if (arg == 1) {
this.table.pagination.current = 1
@ -208,7 +287,14 @@
})
},
getQueryParams() {
let param = Object.assign({}, this.queryParam, this.sorter);
let paramTarget = {}
if(this.dynamicParam){
//处理自定义参数
Object.keys(this.dynamicParam).map(key=>{
paramTarget['self_'+key] = this.dynamicParam[key]
})
}
let param = Object.assign(paramTarget, this.queryParam, this.iSorter);
param.pageNo = this.table.pagination.current;
param.pageSize = this.table.pagination.pageSize;
return filterObj(param);
@ -243,8 +329,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()
@ -272,7 +368,8 @@
this.onClearSelected()
},
show(){
this.visible = true;
this.visible = true
this.loadColumnsInfo()
},
handleToggleSearch(){
this.toggleSearchStatus = !this.toggleSearchStatus;
@ -296,11 +393,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
},
@ -325,6 +431,11 @@
this.table.selectionRows.splice(rowKey_index,1);
}
}
// 判断是否允许多选,如果不允许多选,就只存储最后一个选中的行
if (!this.multi && this.table.selectedRowKeys.length > 1) {
this.table.selectionRows = [this.table.selectionRows.pop()]
this.table.selectedRowKeys = [this.table.selectedRowKeys.pop()]
}
}
}
}

View File

@ -62,8 +62,9 @@
import { getAction } from '@/api/manage'
import Ellipsis from '@/components/Ellipsis'
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
import { cloneObject, pushIfNotExist } from '@/utils/util'
import { pushIfNotExist } from '@/utils/util'
import JSelectBizQueryItem from './JSelectBizQueryItem'
import {cloneDeep} from 'lodash'
export default {
name: 'JSelectBizComponentModal',
@ -177,11 +178,24 @@
computed: {
// 表头
innerColumns() {
let columns = cloneObject(this.columns)
let columns = cloneDeep(this.columns)
columns.forEach(column => {
// 给所有的列加上过长裁剪
if (this.ellipsisLength !== -1) {
column.customRender = (text) => this.renderEllipsis(text)
// JSelectBizComponent columns 建议开放customRender等方法类配置
// https://github.com/jeecgboot/jeecg-boot/issues/3203
let myCustomRender = column.customRender
column.customRender = (text, record, index) => {
let value = text
if (typeof myCustomRender === 'function') {
// noinspection JSVoidFunctionReturnValueUsed
value = myCustomRender(text, record, index)
}
if (typeof value === 'string') {
return this.renderEllipsis(value)
}
return value
}
}
})
return columns
@ -192,7 +206,7 @@
deep: true,
immediate: true,
handler(val) {
this.innerValue = cloneObject(val)
this.innerValue = cloneDeep(val)
this.selectedRowKeys = []
this.valueWatchHandler(val)
this.queryOptionsByValue(val)

View File

@ -12,7 +12,7 @@
:open="selectOpen"
style="width: 100%;"
@dropdownVisibleChange="handleDropdownVisibleChange"
@click.native="visible=(buttons?visible:true)"
@click.native="visible=(buttons || disabled ?visible:true)"
/>
</slot>
</a-col>

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