Compare commits

...

63 Commits

Author SHA1 Message Date
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
3e2f640b68 autopoi导入导出组件系列问题
导出excel实体反射,时间格式转换错误 #1573
配置groupName多生成一列
导出列表数据时, 不支持合并后的单元格.提前结束列的循环了 #1426
Excel导出错误原因,value为""字符串,gitee I249JF
一对多导出needMerge 子表数据对应数量小于2时报错 github#1840、gitee I1YH6B
2020-11-18 22:40:28 +08:00
bf6c5f4623 Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot.git 2020-11-14 18:31:43 +08:00
df259385ec jeecgboot-mysql-5.7.sql被别人提交导致不完整 #1985 2020-11-14 18:30:20 +08:00
c5f51a17da Merge pull request #1966 from tank99tank/no_query_params
导出参数没有高级查询参数
2020-11-11 15:47:27 +08:00
ccf4946b70 导出参数没有高级查询参数 #1860 2020-11-11 12:59:12 +08:00
3b4a65ba04 Merge pull request #1731 from CharlieJ0hn/master
修改 qutz_ 前缀的表名为大写,用于修复 jeecg 项目中 Qrutz 表名不区分大小写在 Linux 和 Mac 上的报错
2020-11-10 15:13:07 +08:00
3a8ae4c089 Merge pull request #1900 from Pa-Star-Home/master
bugfix: fix mybatis param import error; fix integer object compare use == error
2020-11-10 15:05:20 +08:00
88eb98c0e6 解决前端页面改变浏览器窗口大小后,菜单展开按钮失效,无法展开菜单,无法操作 #1913 2020-10-30 16:34:03 +08:00
63048d8c64 bugfix: fix mybatis param import error; fix integer object compare use == error 2020-10-27 15:49:27 +08:00
e8a18fc642 Online一分钟快速体验 2020-10-23 14:33:00 +08:00
fa0c930886 解决j-image-upload图片组件单张图片详情回显空白 #1810 2020-10-19 12:01:15 +08:00
708aa54f3a QQ交流群 : ③816531124、②769925425(满)、①284271917(满) 2020-10-13 14:18:29 +08:00
57337f7980 代码生成器模板(内嵌子表风格列表页面)按钮错位问题修复 issues/I1WHR0 2020-10-13 11:51:57 +08:00
89f4d122dd 在线切换部门,用户缓存信息更新 issues/I1X4DT 2020-10-12 16:23:45 +08:00
b92bec3eed 登入生成token的小bug issues/I1XOVS 2020-10-12 15:41:17 +08:00
72f32e47a0 微服务技术:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking、SpringBootAdmin监控 2020-10-09 16:32:21 +08:00
43908013e3 微服务整体解决方案 2020-10-09 16:28:45 +08:00
80bd1a3f23 支持Spring Cloud Alibaba 2020-10-09 15:04:41 +08:00
de60ea050c 微服务架构图 2020-09-30 18:30:02 +08:00
7cb3dce262 [版本发布](v2.3) 2020-09-29 14:45:19 +08:00
416c8df729 解决token失效 ,登录页面重复跳转登录页面问题 2020-09-29 11:52:22 +08:00
b13a4b01df 选取职务名称出现全选 #1753
切换导航模式,导致菜单栏丢失 #1763
2020-09-29 11:29:48 +08:00
075c3532dc 遗漏的路由配置 2020-09-25 14:43:09 +08:00
fa40b08049 大屏设计下的两个示例没有样式和JS #1799 2020-09-25 11:51:15 +08:00
55c0a4e051 Mybatis-plus的IdType配置问题 #1789 2020-09-23 18:02:16 +08:00
0795808075 jeecg-cloud-application-beta.yml有配置重复问题 #1775 2020-09-23 10:06:02 +08:00
0fa6316f52 Jeecg Boot 2.3 里程碑版本发布,支持微服务和单体自由切换 2020-09-21 14:17:39 +08:00
9cd111ca58 fegin token问题 2020-09-20 18:28:06 +08:00
2c18182dd0 解决严重问题, 缺少表结构eoa_mailbox_info issues/I1VN0E 2020-09-20 17:12:59 +08:00
36cad9373e 解决切换微服务后无法使用Online相关功能 #1760 2020-09-18 10:30:24 +08:00
fcbed695b0 解决,找不到jeecg-cloud-module在其子目录config下有两个配置文件 #1754 2020-09-17 19:19:51 +08:00
5a01d8587b 解决2.3严重权限问题: issues/1740 JAVA访问权限控制 无法使用的问题 2020-09-15 18:52:52 +08:00
4570a21a63 解决2.3 [bug]online表单开发的权限控制使用报错#1733 \ IE11兼容问题 2020-09-14 23:53:33 +08:00
8775472470 Update jeecgboot-mysql-5.7.sql
修改 qutz_ 前缀的表名为大写,用于修复 jeecg 项目中 Qrutz 表名不区分大小写在 Linux 和 Mac 上的报错
2020-09-14 15:22:41 +08:00
bb72341519 修复2.3数据库文件缺少 #1728 2020-09-14 15:01:14 +08:00
78afc86411 修复jeecg 2.3 管理员还是没有编辑用户管理的权限 #1729 2020-09-14 14:34:27 +08:00
91ff6a7aa2 2.3版本, oracle11g、SqlServer2017数据库脚步 2020-09-14 13:55:37 +08:00
b3ec97ffcc 解决2.3 所有类型的角色都没有编辑用户管理的权限 #1726 2020-09-14 11:47:26 +08:00
24fe9e425c Jeecg Boot 2.3 里程碑版本发布,基于 SpringBoot 的低代码平台 2020-09-14 10:48:53 +08:00
09d9e79f40 Jeecg Boot 2.3 里程碑版本发布,支持微服务和单体自由切换、提供新行编辑表格JVXETable 2020-09-13 18:26:18 +08:00
7f30a186df JeecgBoot 2.3 里程碑版本发布,支持微服务和单体自由切换、提供新行编辑表格JVXETable 2020-09-13 18:23:23 +08:00
985 changed files with 397726 additions and 13496 deletions

185
README.md
View File

@ -7,12 +7,12 @@
JEECG BOOT 低代码开发平台(前后端分离版本)
===============
当前最新版本: 2.2.1发布日期2020-07-13
当前最新版本: 2.4.0发布日期2020-12-01
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-2.2.1-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/version-2.4-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
@ -23,11 +23,14 @@ 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配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
适用项目
@ -39,15 +42,17 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
技术文档
-----------------------------------
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 技术官网: [http://www.jeecg.com](http://www.jeecg.com)
- 开发文档: [http://doc.jeecg.com](http://doc.jeecg.com/1273753)
- 开发文档: [http://doc.jeecg.com](http://doc.jeecg.com)
- 视频教程 [JeecgBoot入门视频教程](http://www.jeecg.com/doc/video)
- 微服务启动: [单体升级为微服务启动文档2.4+](http://doc.jeecg.com/2043906)
- 常见问题 [入门常见问题大全](http://bbs.jeecg.com/forum.php?mod=viewthread&tid=7816&extra=page%3D1)
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 视频教程 [JeecgBoot入门视频](http://www.jeecg.com/doc/video)
- 常见问题: [入门常见问题Q&A](http://jeecg.com/doc/qa)
- 更新日志: [版本日志](http://www.jeecg.com/doc/log)
@ -56,56 +61,58 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
交流互动
-----------------------------------
- QQ交流群 ②769925425、①284271917
- QQ交流群 ③816531124、②769925425(满)、①284271917
- 反馈问题: [反馈问题请按格式发Issues](https://github.com/zhangdaiscott/jeecg-boot/issues/new)
- 参与开源: [欢迎加入JEECG开源团队共同进步](http://www.jeecg.com/doc/join)
- Online一分钟 [1分钟快速学习](https://my.oschina.net/jeecg/blog/3083313)
- Online一分钟 [1分钟快速学习](https://jeecg.blog.csdn.net/article/details/106078912)
为什么选择JEECG-BOOT?
-----------------------------------
* 1.采用最新主流前后分离框架Springboot+Mybatis+antd容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
* 2.开发效率高,采用代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用;
* 3.代码生成器提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)
* 4.代码生成器非常智能在线业务建模、在线配置、所见即所得支持23种类控件一键生成前后端代码大幅度提升开发效率不再为重复工作发愁。
* 5.代码能力Online在线表单无需编码通过在线配置表单实现表单的增删改查支持单表、树、一对多、一对一等模型实现人人皆可编码
* 5-1.低代码能力Online在线表(无需编码,通过在线配置方式,实现数据报表,可以快速抽取数据,减轻开发压力,实现人人皆可编码)
* 5-2.低代码能力Online在线表(无需编码,通过在线配置方式,实现曲线图,柱状图,数据报表等,支持自定义排版布局,实现人人皆可编码)
* 6.封装完善的用户、角色、菜单、组织机构、数据字典、在线定时任务等基础功能,支持访问授权、按钮权限、数据权限等功能
* 7.常用共通封装,各种工具类(定时任务,短信接口,邮件发送,Excel导入导出等),基本满足80%项目需求
* 8.简易Excel导入导出支持单表导出和一对多表模式导出生成的代码自带导入导出功能
* 9.集成简易报表工具图像报表和数据导出非常方便可极其方便的生成图形报表、pdf、excel、word等报表
* 10.采用前后分离技术页面UI风格精美针对常用组件做了封装时间、行表格控件、截取显示控件、报表组件编辑器等等
* 11.查询过滤器查询功能自动生成后台动态拼SQL追加查询条件支持多种匹配方式全匹配/模糊查询/包含查询/不匹配查询);
* 12.数据权限(精细化数据权限控制,控制到行级,列表级,表单字段级,实现不同人看不同数据,不同人对同一个页面操作不同字段
* 13.页面校验自动生成(必须输入、数字校验、金额校验、时间空间等);
* 14.支持SAAS服务模式提供SaaS多租户架构方案。
* 15.分布式文件服务集成minio、阿里OSS等优秀的第三方提供便捷的文件上传与管理同时也支持本地存储
* 16.主流数据库兼容一套代码完全兼容Mysql、Postgresql、Oracle三大主流数据库
* 17.集成工作流activiti并实现了只需在页面配置流程转向可极大的简化bpm工作流的开发用bpm的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的java代码
* 18.低代码能力在线流程设计采用开源Activiti流程引擎实现在线画流程,自定义表单,表单挂靠,业务流转
* 19.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
* 20.提供单点登录CAS集成方案项目中已经提供完善的对接代码
* 21.低代码能力表单设计器支持用户自定义表单布局支持单表一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件
* 22.专业接口对接机制统一采用restful接口方式集成swagger-ui在线接口文档Jwt token安全验证方便客户端对接
* 23.接口安全机制,可细化控制接口授权,非常简便实现不同客户端只看自己数据等控制
* 24.高级组合查询功能,在线配置支持主子表关联查询,可保存查询历史
* 25.提供各种系统监控,实时跟踪系统运行情况(监控 Redis、Tomcat、jvm、服务器信息、请求追踪、SQL监控
* 26.消息中心(支持短信、邮件、微信推送等等
* 27.集成Websocket消息通知机制
* 28.移动自适应效果优秀提供APP发布方案
* 29.支持多语言,提供国际化方案
* 30.数据变更记录日志,可记录数据每次变更内容,通过版本对比功能查看历史变化
* 31.平台UI强大实现了移动自适应
* 32.平台首页风格,提供多种组合模式,支持自定义风格
* 33.提供简单易用的打印插件支持谷歌、火狐、IE11+ 等各种浏览器
* 34.示例代码丰富,提供很多学习案例参考
* 35.采用maven分模块开发方式
* 36.支持菜单动态路由
* 37.权限控制采用 RBACRole-Based Access Control基于角色的访问控制
* 2.支持微服务SpringCloud Alibaba(Nacos、Gateway、Sentinel、Skywarking),提供切换机制支持单体和微服务自由切换
* 3.开发效率高,采用代码生成器,单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用;
* 4.代码生成器提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)
* 5.代码生成器非常智能在线业务建模、在线配置、所见即所得支持23种类控件一键生成前后端代码大幅度提升开发效率不再为重复工作发愁。
* 6.低代码能力Online在线表(无需编码,通过在线配置表单,实现表单的增删改查,支持单表、树、一对多、一对一等模型,实现人人皆可编码)
* 7.低代码能力Online在线表(无需编码,通过在线配置方式,实现数据报表,可以快速抽取数据,减轻开发压力,实现人人皆可编码)
* 8.低代码能力Online在线图表无需编码通过在线配置方式实现曲线图柱状图数据报表等支持自定义排版布局实现人人皆可编码
* 9.封装完善的用户、角色、菜单、组织机构、数据字典、在线定时任务等基础功能,支持访问授权、按钮权限、数据权限等功能
* 10.常用共通封装,各种工具类(定时任务,短信接口,邮件发送,Excel导入导出等),基本满足80%项目需求
* 11.简易Excel导入导出支持单表导出和一对多表模式导出生成的代码自带导入导出功能
* 12.集成简易报表工具图像报表和数据导出非常方便可极其方便的生成图形报表、pdf、excel、word等报表
* 13.采用前后分离技术页面UI风格精美针对常用组件做了封装时间、行表格控件、截取显示控件、报表组件编辑器等等
* 14.查询过滤器查询功能自动生成后台动态拼SQL追加查询条件支持多种匹配方式全匹配/模糊查询/包含查询/不匹配查询);
* 15.数据权限(精细化数据权限控制,控制到行级,列表级,表单字段级,实现不同人看不同数据,不同人对同一个页面操作不同字段
* 16.页面校验自动生成(必须输入、数字校验、金额校验、时间空间等);
* 17.支持SAAS服务模式提供SaaS多租户架构方案
* 18.分布式文件服务集成minio、阿里OSS等优秀的第三方提供便捷的文件上传与管理同时也支持本地存储
* 19.主流数据库兼容一套代码完全兼容Mysql、Postgresql、Oracle三大主流数据库。
* 20.集成工作流activiti并实现了只需在页面配置流程转向可极大的简化bpm工作流的开发用bpm的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的java代码
* 21.低代码能力在线流程设计采用开源Activiti流程引擎实现在线画流程,自定义表单,表单挂靠,业务流转
* 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
* 23.提供单点登录CAS集成方案项目中已经提供完善的对接代码
* 24.低代码能力表单设计器支持用户自定义表单布局支持单表一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件
* 25.专业接口对接机制统一采用restful接口方式集成swagger-ui在线接口文档Jwt token安全验证方便客户端对接
* 26.接口安全机制,可细化控制接口授权,非常简便实现不同客户端只看自己数据等控制
* 27.高级组合查询功能,在线配置支持主子表关联查询,可保存查询历史
* 28.提供各种系统监控,实时跟踪系统运行情况(监控 Redis、Tomcat、jvm、服务器信息、请求追踪、SQL监控
* 29.消息中心(支持短信、邮件、微信推送等等)
* 30.集成Websocket消息通知机制
* 31.移动自适应效果优秀提供APP发布方案
* 32.支持多语言,提供国际化方案;
* 33.数据变更记录日志,可记录数据每次变更内容,通过版本对比功能查看历史变化
* 34.平台UI强大实现了移动自适应
* 35.平台首页风格,提供多种组合模式,支持自定义风格
* 36.提供简单易用的打印插件支持谷歌、火狐、IE11+ 等各种浏览器
* 37.示例代码丰富,提供很多学习案例参考
* 38.采用maven分模块开发方式
* 39.支持菜单动态路由
* 40.权限控制采用 RBACRole-Based Access Control基于角色的访问控制
* 41.提供新行编辑表格JVXETable轻松满足各种复杂ERP布局拥有更高的性能、更灵活的扩展、更强大的功能
@ -128,13 +135,17 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
#### 后端
- 基础框架Spring Boot 2.1.3.RELEASE
- 基础框架Spring Boot 2.3.5.RELEASE
- 持久层框架Mybatis-plus_3.1.2
- 微服务框架: Spring Cloud Alibaba 2.2.3.RELEASE
- 安全框架Apache Shiro 1.4.0Jwt_3.7.0
- 持久层框架Mybatis-plus 3.4.1
- 数据库连接池阿里巴巴Druid 1.1.10
- 安全框架Apache Shiro 1.7.0Jwt 3.11.0
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking
- 数据库连接池阿里巴巴Druid 1.1.22
- 缓存框架redis
@ -158,6 +169,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
### 功能模块
```
├─系统管理
@ -167,16 +179,17 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ ├─权限设置(支持按钮权限、数据权限)
│ ├─表单权限(控制字段禁用、隐藏)
│ ├─部门管理
│ ├─我的部门(二级管理员)
│ └─字典管理
│ └─分类字典
│ └─分类字典
│ └─系统公告
│ └─我的组织机构
│ └─职务管理
│ └─通讯录
│ └─多租户管理
├─消息中心
│ ├─消息管理
│ ├─模板管理
├─智能化功能
├─代码生成器(低代码)
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
│ ├─代码生成器模板提供4套模板分别支持单表和一对多模型不同风格选择
│ ├─代码生成器模板生成代码自带excel导入导出
@ -185,6 +198,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ ├─Excel导入导出工具集成支持单表一对多 导入导出)
│ ├─平台移动自适应支持
├─系统监控
│ ├─Gateway路由网关
│ ├─性能扫描监控
│ │ ├─监控 Redis
│ │ ├─Tomcat
@ -216,6 +230,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│─常用示例
│ ├─自定义组件
│ ├─对象存储(对接阿里云)
│ ├─JVXETable示例各种复杂ERP布局示例
│ ├─单表模型例子
│ └─一对多模型例子
│ └─打印例子
@ -263,13 +278,19 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ ├─提供单点登录CAS集成方案
│ ├─提供APP发布方案
│ ├─集成Websocket消息通知机制
├─Online在线低代码开发(暂未开源)
├─Online在线开发(低代码)
│ ├─Online在线表单 - 功能已开放
│ ├─在线代码生成器 - 功能已开放
│ ├─Online代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放
│ ├─Online在线图表
│ ├─Online图表模板配置
│ ├─高级表单设计器
│ ├─Online在线图表(暂不开源)
│ ├─Online图表模板配置(暂不开源)
│ ├─Online布局设计(暂不开源)
│ ├─多数据源管理 - 功能已开放
├─积木报表设计器(低代码)
│ ├─打印设计器
│ ├─数据报表设计
│ ├─图形报表设计支持echart
│ ├─大屏设计器(暂不开源)
│─流程模块功能 (暂不开源)
│ ├─流程设计器
│ ├─在线表单设计
@ -287,7 +308,45 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
└─更多功能开发中。。
```
## 微服务整体解决方案(2.4+版本)
1、服务注册和发现 Nacos √
2、统一配置中心 Nacos √
3、路由网关 gateway(三种加载方式) √
4、分布式 http feign √
5、熔断和降级 Sentinel √
6、分布式文件 Minio、阿里OSS √
7、统一权限控制 JWT + Shiro √
8、服务监控 SpringBootAdmin√
9、链路跟踪 Skywarking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
10、消息中间件 RabbitMQ √
11、分布式任务 xxl-job √
12、分布式事务 Seata
13、分布式日志 elk + kafa
14、支持 docker-compose、k8s、jenkins
15、CAS 单点登录 √
16、路由限流 √
#### 微服务架构图
![微服务架构图](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 "在这里输入图片标题")
@ -300,7 +359,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
- jdk8
- mysql
- redis
- 数据库脚本jeecg-boot\docs\jeecg-boot-mysql.sql
- 数据库脚本jeecg-boot/db/jeecgboot-mysql-5.7.sql
- 默认登录账号: admin/123456

View File

@ -0,0 +1,3 @@
NODE_ENV=production
VUE_APP_PLATFORM_NAME=Jeecg-Boot 企业级快速开发平台
VUE_APP_SSO=false

View File

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

View File

@ -0,0 +1,4 @@
NODE_ENV=production
VUE_APP_API_BASE_URL=https://bootapi.jeecg.com
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,7 +1,7 @@
Ant Design Jeecg Vue
====
当前最新版本: 2.2.1发布日期20200713
当前最新版本: 2.4.0发布日期20201201
Overview
----

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,18 @@
{
"name": "vue-antd-jeecg",
"version": "2.2.1",
"version": "2.4.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.4.0-beta4",
"ant-design-vue": "^1.7.2",
"@antv/data-set": "^0.11.4",
"@jeecg/antd-online-mini": "2.2.12",
"viser-vue": "^2.4.8",
"axios": "^0.18.0",
"dayjs": "^1.8.0",
@ -39,7 +40,10 @@
"@toast-ui/editor": "^2.1.2",
"vue-area-linkage": "^5.1.0",
"area-data": "^5.0.6",
"jsoneditor": "^9.0.0"
"dom-align": "1.12.0",
"xe-utils": "2.4.8",
"vxe-table": "2.9.13",
"vxe-table-plugin-antd": "1.8.10"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",
@ -48,13 +52,13 @@
"@vue/cli-service": "^3.3.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "7.2.3",
"compression-webpack-plugin": "^3.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.1.0",
"html-webpack-plugin": "^4.2.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"vue-template-compiler": "^2.6.10"
"vue-template-compiler": "^2.6.10",
"html-webpack-plugin": "^4.2.0",
"compression-webpack-plugin": "^3.1.0"
},
"eslintConfig": {
"root": true,

View File

@ -7687,6 +7687,9 @@ font.weak {
color: @primary-color;
}
}
.ant-menu-submenu-selected {
color: @primary-color;
}
// begin -------- JAreaLinkage 三级联动样式 --------------
.cascader-menu-list .cascader-menu-option.hover,

View File

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Jeecg-Boot 企业级快速开发平台</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,
@ -242,17 +242,10 @@
<!-- 全局配置 -->
<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>
</head>
<body>
<!-- built files will be auto injected -->
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>

View File

@ -18,12 +18,10 @@ const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
const checkOnlyUser = (params)=>getAction("/sys/user/checkOnlyUser",params);
//改变密码
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)
@ -33,7 +31,7 @@ const queryTreeListForRole = (params)=>getAction("/sys/role/queryTreeList",param
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/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);
@ -54,7 +52,6 @@ const saveDeptRolePermission = (params)=>postAction("/sys/sysDepartPermission/sa
const queryMyDepartTreeList = (params)=>getAction("/sys/sysDepart/queryMyDeptTreeList",params);
//日志管理
//const getLogList = (params)=>getAction("/sys/log/list",params);
const deleteLog = (params)=>deleteAction("/sys/log/delete",params);
const deleteLogList = (params)=>deleteAction("/sys/log/deleteBatch",params);
@ -82,10 +79,8 @@ const doReovkeData = (params)=>getAction("/sys/annountCement/doReovkeData",param
//获取系统访问量
const getLoginfo = (params)=>getAction("/sys/loginfo",params);
const getVisitInfo = (params)=>getAction("/sys/visitInfo",params);
// 根据部门主键查询用户信息
const queryUserByDepId = (params)=>getAction("/sys/user/queryUserByDepId",params);
// 重复校验
const duplicateCheck = (params)=>getAction("/sys/duplicate/check",params);
// 加载分类字典

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,11 +60,12 @@ 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'

View File

@ -2,11 +2,11 @@ import Vue from 'vue'
import { axios } from '@/utils/request'
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
@ -167,11 +167,15 @@ export function uploadAction(url,parameter){
*/
export function getFileAccessHttpUrl(avatar,subStr) {
if(!subStr) subStr = 'http'
if(avatar && avatar.startsWith(subStr)){
return avatar;
}else{
if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
return window._CONFIG['staticDomainURL'] + "/" + avatar;
try {
if(avatar && avatar.startsWith(subStr)){
return avatar;
}else{
if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
return window._CONFIG['staticDomainURL'] + "/" + avatar;
}
}
}catch(err){
return;
}
}

View File

@ -0,0 +1 @@
.cm-s-idea span.cm-meta{color:olive}.cm-s-idea span.cm-number{color:#00f}.cm-s-idea span.cm-keyword{line-height:1em;font-weight:700;color:navy}.cm-s-idea span.cm-atom{font-weight:700;color:navy}.cm-s-idea span.cm-def{color:#000}.cm-s-idea span.cm-variable{color:#000}.cm-s-idea span.cm-variable-2{color:#000}.cm-s-idea span.cm-type,.cm-s-idea span.cm-variable-3{color:#000}.cm-s-idea span.cm-property{color:#000}.cm-s-idea span.cm-operator{color:#000}.cm-s-idea span.cm-comment{color:grey}.cm-s-idea span.cm-string{color:green}.cm-s-idea span.cm-string-2{color:green}.cm-s-idea span.cm-qualifier{color:#555}.cm-s-idea span.cm-error{color:red}.cm-s-idea span.cm-attribute{color:#00f}.cm-s-idea span.cm-tag{color:navy}.cm-s-idea span.cm-link{color:#00f}.cm-s-idea .CodeMirror-activeline-background{background:#fffae3}.cm-s-idea span.cm-builtin{color:#30a}.cm-s-idea span.cm-bracket{color:#cc7}.cm-s-idea{font-family:Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif}.cm-s-idea .CodeMirror-matchingbracket{outline:1px solid grey;color:#000!important}.CodeMirror-hints.idea{font-family:Menlo,Monaco,Consolas,'Courier New',monospace;color:#616569;background-color:#ebf3fd!important}.CodeMirror-hints.idea .CodeMirror-hint-active{background-color:#a2b8c9!important;color:#5c6065!important}

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

@ -0,0 +1,217 @@
<template>
<div>
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
<div :key="fileKey" style="position: relative;">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
<span style="margin-left:5px">上传中</span>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" :title="file.name">
<a-icon type="paper-clip"/>
<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>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
<a-tooltip title="操作">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<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(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '上传文件'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
<script>
import { getFileAccessHttpUrl } from '@api/manage'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import JFilePop from '@/components/jeecg/minipop/JFilePop'
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
export default {
name: 'JVxeFileCell',
mixins: [JVxeCellMixins],
components: {JFilePop},
props: {},
data() {
return {
innerFile: null,
number:0,
}
},
computed: {
/** upload headers */
uploadHeaders() {
let {originColumn: col} = this
let headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
/** 上传请求地址 */
uploadAction() {
if (!this.originColumn.action) {
return window._CONFIG['domianURL'] + '/sys/common/upload'
} else {
return this.originColumn.action
}
},
hasFile() {
return this.innerFile != null
},
ellipsisFileName() {
let length = 5
let file = this.innerFile
if (!file || !file.name) {
return ''
}
if (file.name.length > length) {
return file.name.substr(0, length) + '…'
}
return file.name
},
responseName() {
if (this.originColumn.responseName) {
return this.originColumn.responseName
} else {
return 'message'
}
},
},
watch: {
innerValue: {
immediate: true,
handler() {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
},
},
},
methods: {
// 点击更多按钮
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
}
this.$refs.filePop.show('', path)
},
// 更多上传回调
handleFileSuccess(file) {
if (file) {
this.innerFile.path = file.path
this.handleChangeCommon(this.innerFile)
}
},
handleChangeUpload(info) {
let {originColumn: col} = this
let {file} = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (file.response) {
value['responseName'] = file.response[this.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
},
handleClickDownloadFile() {
let {url, path} = this.innerFile || {}
if (!url || url.length === 0) {
if (path && path.length > 0) {
url = getFileAccessHttpUrl(path.split(',')[0])
}
}
if (url) {
window.open(url)
}
},
handleClickDeleteFile() {
this.handleChangeCommon(null)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {visible: true},
getValue: value => JVxeUploadCell.enhanced.getValue(value),
setValue: value => JVxeUploadCell.enhanced.setValue(value),
}
}
</script>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,242 @@
<template>
<div>
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
<div :key="fileKey" style="position: relative;">
<template v-if="!file || !(file['url'] || file['path'] || file['message'])">
<a-tooltip :title="'请稍后: ' + JSON.stringify (file) + ((file['url'] || file['path'] || file['message']))">
<a-icon type="loading"/>
</a-tooltip>
</template>
<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>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
<a-tooltip title="操作">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<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(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '上传图片'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
<script>
import { getFileAccessHttpUrl } from '@api/manage'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import JFilePop from '@/components/jeecg/minipop/JFilePop'
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
export default {
name: 'JVxeImageCell',
mixins: [JVxeCellMixins],
components: {JFilePop},
props: {},
data() {
return {
innerFile: null,
number:0
}
},
computed: {
/** upload headers */
uploadHeaders() {
let {originColumn: col} = this
let headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
/** 上传请求地址 */
uploadAction() {
if (!this.originColumn.action) {
return window._CONFIG['domianURL'] + '/sys/common/upload'
} else {
return this.originColumn.action
}
},
hasFile() {
return this.innerFile != null
},
/** 预览图片地址 */
imgSrc() {
if (this.innerFile) {
if (this.innerFile['url']) {
return this.innerFile['url']
} else if (this.innerFile['path']) {
let path = this.innerFile['path'].split(',')[0]
return getFileAccessHttpUrl(path)
}
}
return ''
},
responseName() {
if (this.originColumn.responseName) {
return this.originColumn.responseName
} else {
return 'message'
}
},
},
watch: {
innerValue: {
immediate: true,
handler() {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
},
},
},
methods: {
// 点击更多按钮
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
}
this.$refs.filePop.show('', path, 'img')
},
// 更多上传回调
handleFileSuccess(file) {
if (file) {
this.innerFile.path = file.path
this.handleChangeCommon(this.innerFile)
}
},
// 弹出上传出错详细信息
handleClickShowImageError() {
let file = this.innerFile || null
if (file && file['message']) {
this.$error({title: '上传出错', content: '错误信息:' + file['message'], maskClosable: true})
}
},
handleChangeUpload(info) {
let {originColumn: col} = this
let {file} = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (file.response) {
value['responseName'] = file.response[this.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
},
handleClickDownloadFile() {
if (this.imgSrc) {
window.open(this.imgSrc)
}
},
handleClickDeleteFile() {
this.handleChangeCommon(null)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {visible: true},
getValue: value => JVxeUploadCell.enhanced.getValue(value),
setValue: value => JVxeUploadCell.enhanced.setValue(value),
}
}
</script>
<style scoped lang="less">
.j-editable-image {
height: 32px;
max-width: 100px !important;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<j-popup
v-bind="popupProps"
@input="handlePopupInput"
/>
</template>
<script>
import JVxeCellMixins, { vModel, dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxePopupCell',
mixins: [JVxeCellMixins],
computed: {
popupProps() {
const {innerValue, originColumn: col, caseId, cellProps} = this
return {
...cellProps,
value: innerValue,
field: col.field || col.key,
code: col.popupCode,
orgFields: col.orgFields,
destFields: col.destFields,
groupId: caseId,
}
},
},
methods: {
/** popup回调 */
handlePopupInput(value, others) {
const {row, originColumn: col} = this
// 存储输入的值
let popupValue = value
if (others && Object.keys(others).length > 0) {
Object.keys(others).forEach(key => {
let currentValue = others[key]
// 当前列直接赋值其他列通过vModel赋值
if (key === col.key) {
popupValue = currentValue
} else {
vModel.call(this, currentValue, row, key)
}
})
}
this.handleChangeCommon(popupValue)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-input'),
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,60 @@
<template>
<a-radio-group
:class="clazz"
:value="innerValue"
v-bind="cellProps"
@change="(e)=>handleChangeCommon(e.target.value)"
>
<a-radio
v-for="item of originColumn.options"
:key="item.value"
:value="item.value"
@click="$event=>handleRadioClick(item,$event)"
>{{ item.text }}
</a-radio>
</a-radio-group>
</template>
<script>
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeRadioCell',
mixins: [JVxeCellMixins],
computed: {
scrolling() {
return !!this.renderOptions.scrolling
},
clazz() {
return {
'j-vxe-radio': true,
'no-animation': this.scrolling
}
},
},
methods: {
handleRadioClick(item) {
if (this.originColumn.allowClear === true) {
// 取消选择
if (item.value === this.innerValue) {
this.handleChangeCommon(null)
}
}
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {visible: true},
}
}
</script>
<style lang="less">
// 关闭动画,防止滚动时动态赋值出现问题
.j-vxe-radio.no-animation {
.ant-radio-inner,
.ant-radio-inner::after {
transition: none !important;
}
}
</style>

View File

@ -0,0 +1,260 @@
import debounce from 'lodash/debounce'
import { getAction } from '@/api/manage'
import { cloneObject } from '@/utils/util'
import { filterDictText } from '@/components/dict/JDictSelectUtil'
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
/** 公共资源 */
const common = {
/** value - label map防止重复查询刷新清空缓存 */
labelMap: new Map(),
/** 公共data */
data() {
return {
loading: false,
innerSelectValue: null,
innerOptions: [],
}
},
/** 公共计算属性 */
computed: {
dict() {
return this.originColumn.dict
},
options() {
if (this.isAsync) {
return this.innerOptions
} else {
return this.originColumn.options || []
}
},
// 是否是异步模式
isAsync() {
let isAsync = this.originColumn.async
return (isAsync != null && isAsync !== '') ? !!isAsync : true
},
},
/** 公共属性监听 */
watch: {
innerValue: {
immediate: true,
handler(value) {
if (value == null || value === '') {
this.innerSelectValue = null
} else {
this.loadDataByValue(value)
}
}
},
dict() {
this.loadDataByDict()
}
},
/** 公共方法 */
methods: {
// 根据 value 查询数据,用于回显
async loadDataByValue(value) {
if (this.isAsync) {
if (this.innerSelectValue !== value) {
if (common.labelMap.has(value)) {
this.innerOptions = cloneObject(common.labelMap.get(value))
} else {
let {success, result} = await getAction(`/sys/dict/loadDictItem/${this.dict}`, {key: value})
if (success && result && result.length > 0) {
this.innerOptions = [{value: value, text: result[0]}]
common.labelMap.set(value, cloneObject(this.innerOptions))
}
}
}
}
this.innerSelectValue = (value || '').toString()
},
// 初始化字典
async loadDataByDict() {
if (!this.isAsync) {
// 如果字典项集合有数据
if (!this.originColumn.options || this.originColumn.options.length === 0) {
// 根据字典Code, 初始化字典数组
let dictStr = ''
if (this.dict) {
let arr = this.dict.split(',')
if (arr[0].indexOf('where') > 0) {
let tbInfo = arr[0].split('where')
dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1])
} else {
dictStr = this.dict
}
if (this.dict.indexOf(',') === -1) {
//优先从缓存中读取字典配置
let cache = getDictItemsFromCache(this.dict)
if (cache) {
this.innerOptions = cache
return
}
}
let {success, result} = await ajaxGetDictItems(dictStr, null)
if (success) {
this.innerOptions = result
}
}
}
}
},
},
}
// 显示组件,自带翻译
export const DictSearchSpanCell = {
name: 'JVxeSelectSearchSpanCell',
mixins: [JVxeCellMixins],
data() {
return {
...common.data.apply(this),
}
},
computed: {
...common.computed,
},
watch: {
...common.watch,
},
methods: {
...common.methods,
},
render(h) {
return h('span', {}, [
filterDictText(this.innerOptions, this.innerSelectValue || this.innerValue)
])
},
}
// 请求id
let requestId = 0
// 输入选择组件
export const DictSearchInputCell = {
name: 'JVxeSelectSearchInputCell',
mixins: [JVxeCellMixins],
data() {
return {
...common.data.apply(this),
hasRequest: false,
scopedSlots: {
notFoundContent: () => {
if (this.loading) {
return <a-spin size="small"/>
} else if (this.hasRequest) {
return <div>没有查询到任何数据</div>
} else {
return <div>{this.tipsContent}</div>
}
}
}
}
},
computed: {
...common.computed,
tipsContent() {
return this.originColumn.tipsContent || '请输入搜索内容'
},
filterOption() {
if (this.isAsync) {
return null
}
return (input, option) => option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
},
},
watch: {
...common.watch,
},
created() {
this.loadData = debounce(this.loadData, 300)//消抖
},
methods: {
...common.methods,
loadData(value) {
const currentRequestId = ++requestId
this.loading = true
this.innerOptions = []
if (value == null || value.trim() === '') {
this.loading = false
this.hasRequest = false
return
}
// 字典code格式table,text,code
this.hasRequest = true
getAction(`/sys/dict/loadDict/${this.dict}`, {keyword: value}).then(res => {
if (currentRequestId !== requestId) {
return
}
let {success, result, message} = res
if (success) {
this.innerOptions = result
result.forEach((item) => {
common.labelMap.set(item.value, [item])
})
} else {
this.$message.warning(message)
}
}).finally(() => {
this.loading = false
})
},
handleChange(selectedValue) {
this.innerSelectValue = selectedValue
this.handleChangeCommon(this.innerSelectValue)
},
handleSearch(value) {
if (this.isAsync) {
// 在输入时也应该开启加载因为loadData加了消抖所以会有800ms的用户主观上认为的卡顿时间
this.loading = true
if (this.innerOptions.length > 0) {
this.innerOptions = []
}
this.loadData(value)
}
},
renderOptionItem() {
let options = []
this.options.forEach(({value, text, label, title, disabled}) => {
options.push(
<a-select-option key={value} value={value} disabled={disabled}>{text || label || title}</a-select-option>
)
})
return options
},
},
render() {
return (
<a-select
showSearch
allowClear
value={this.innerSelectValue}
filterOption={this.filterOption}
style="width: 100%"
{...this.cellProps}
onSearch={this.handleSearch}
onChange={this.handleChange}
scopedSlots={this.scopedSlots}
>
{this.renderOptionItem()}
</a-select>
)
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-select'),
},
}
}

View File

@ -0,0 +1,36 @@
import { installCell, JVXETypes } from '@/components/jeecg/JVxeTable'
import JVxePopupCell from './JVxePopupCell'
import { DictSearchInputCell, DictSearchSpanCell } from './JVxeSelectDictSearchCell'
import JVxeFileCell from './JVxeFileCell'
import JVxeImageCell from './JVxeImageCell'
import JVxeRadioCell from './JVxeRadioCell'
import JVxeSelectCell from '@comp/jeecg/JVxeTable/components/cells/JVxeSelectCell'
import JVxeTextareaCell from '@comp/jeecg/JVxeTable/components/cells/JVxeTextareaCell'
// 注册online组件
JVXETypes.input_pop = 'input_pop'
JVXETypes.list_multi = 'list_multi'
JVXETypes.sel_search = 'sel_search'
installCell(JVXETypes.input_pop, JVxeTextareaCell)
installCell(JVXETypes.list_multi, JVxeSelectCell)
installCell(JVXETypes.sel_search, JVxeSelectCell)
// 注册【popup】组件普通封装方式
JVXETypes.popup = 'popup'
installCell(JVXETypes.popup, JVxePopupCell)
// 注册【字典搜索下拉】组件(高级封装方式)
JVXETypes.selectDictSearch = 'select-dict-search'
installCell(JVXETypes.selectDictSearch, DictSearchInputCell, DictSearchSpanCell)
// 注册【文件上传】组件
JVXETypes.file = 'file'
installCell(JVXETypes.file, JVxeFileCell)
// 注册【图片上传】组件
JVXETypes.image = 'image'
installCell(JVXETypes.image, JVxeImageCell)
// 注册【单选框】组件
JVXETypes.radio = 'radio'
installCell(JVXETypes.radio, JVxeRadioCell)

View File

@ -1,7 +1,7 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :data="data" :height="height" :force-fit="true" :onClick="handleClick">
<v-chart :data="data" :height="height" :force-fit="true" :scale="scale" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
@ -78,6 +78,14 @@
}
})
return rows
},
scale() {
return [
{
type: 'cat',
dataKey: 'x'
}
]
}
}
}

View File

@ -57,6 +57,7 @@
data() {
return {
scale: [{
type: 'cat',
dataKey: 'x',
min: 0,
max: 1

View File

@ -11,6 +11,8 @@
mode="multiple"
:placeholder="placeholder"
:getPopupContainer="(node) => node.parentNode"
optionFilterProp="children"
:filterOption="filterOption"
allowClear>
<a-select-option
v-for="(item,index) in dictOptions"
@ -97,7 +99,12 @@
},
getCurrentDictOptions(){
return this.dictOptions
},
// 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

@ -90,6 +90,14 @@
handler(){
this.initDictData()
}
},
'dictOptions':{
deep: true,
handler(val){
if(val && val.length>0){
this.options = [...val]
}
}
}
},
methods:{

View File

@ -1,7 +1,11 @@
import T from './JDictSelectTag.vue'
const JDictSelectTag = {
import JDictSelectTag from './JDictSelectTag.vue'
import JMultiSelectTag from './JMultiSelectTag.vue'
import JSearchSelectTag from './JSearchSelectTag.vue'
export default {
install: function (Vue) {
Vue.component('JDictSelectTag',T);
Vue.component('JDictSelectTag',JDictSelectTag);
Vue.component('JMultiSelectTag',JMultiSelectTag);
Vue.component('JSearchSelectTag',JSearchSelectTag);
}
}
export default JDictSelectTag;
}

View File

@ -0,0 +1,282 @@
<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

@ -39,7 +39,7 @@
<div class="thead" ref="thead">
<div class="tr" :style="{width: this.realTrWidth}">
<!-- 左侧固定td -->
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft">
<span></span>
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
@ -59,7 +59,7 @@
v-show="col.type !== formTypes.hidden"
class="td"
:key="col.key"
:style="buildTdStyle(col)">
:style="buildTdStyle(col,true)">
<span>{{ col.title }}</span>
</div>
@ -104,7 +104,7 @@
>
<!-- 左侧固定td -->
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs" @dblclick="_handleRowInsertDown(rowIndex)" >
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft" @dblclick="_handleRowInsertDown(rowIndex)" >
<a-dropdown :trigger="['click']" :getPopupContainer="getParentContainer">
<div class="td-ds-icons">
<a-icon type="align-left"/>
@ -375,7 +375,7 @@
<a-menu-item @click="handleClickDelFile(id)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(id)">
<a-menu-item @click="handleMoreOperation(id,col,col)">
<span><a-icon type="bars" /> 更多</span>
</a-menu-item>
</a-menu>
@ -410,7 +410,7 @@
<a-icon type="loading"/>
</template>
<template v-else-if="uploadValues[id]['path']">
<img class="j-editable-image" :src="getCellImageView(id)" alt="无图片" @click="handleMoreOperation(id,'img')"/>
<img class="j-editable-image" :src="getCellImageView(id)" alt="无图片" @click="handleMoreOperation(id,'img',col)"/>
</template>
<template v-else>
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError(id)"/>
@ -443,7 +443,7 @@
<a-menu-item @click="handleClickDelFile(id)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(id,'img')">
<a-menu-item @click="handleMoreOperation(id,'img',col)">
<span><a-icon type="bars" /> 更多</span>
</a-menu-item>
</a-menu>
@ -584,7 +584,7 @@
height: '32px'
}"
>
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft">
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
统计
@ -612,7 +612,7 @@
</div>
</div>
<j-file-pop ref="filePop" @ok="handleFileSuccess"></j-file-pop>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"></j-file-pop>
</div>
</a-spin>
</template>
@ -673,7 +673,7 @@
type: Boolean,
default: false
},
// 页面是否在加载中
// 表格内容区域最大高度
maxHeight: {
type: Number,
default: 400
@ -725,8 +725,7 @@
// 'max-height': '400px'
tbody: { left: '0px' },
// 左侧固定td的style
tdLeft: { 'min-width': '4%', 'max-width': '45px' },
tdLeftDs: { 'min-width': '30px', 'max-width': '35px' },
tdLeft: {},
},
// 表单的类型
formTypes: FormTypes,
@ -775,6 +774,7 @@
currentEditRows: {},
// 上次push数据的事件用于判断是否点击过快
lastPushTimeMap: new Map(),
number:0,
}
},
created() {
@ -1204,6 +1204,12 @@
this.inputValues.splice(insertIndex, 0, value)
}
}
//update-begin-author:lvdandan date:20201105 for:LOWCOD-987 【online】js增强的问题--数据对象带有id且和现有数据一致时替换患有数据
if(-1 !== rows.findIndex(item => item.id === row.id)){
added = true
this.inputValues = this.inputValues.map(item => item.id === row.id ? value : item)
}
//update-begin-author:lvdandan date:20201105 for:LOWCOD-987 【online】js增强的问题--数据对象带有id且和现有数据一致时替换患有数据
if (!added) {
rows.push(row)
this.inputValues.push(value)
@ -1398,7 +1404,6 @@
let values = []
// 遍历inputValues来获取每行的值
for (let value of inputValues) {
if (value == null) console.warn(this.caseId, '+++++++++ value.1: ', value, cloneObject(inputValues))
let rowIdsFlag = false
// 如果带有rowIds那么就只存这几行的数据
if (rowIds == null) {
@ -1591,78 +1596,68 @@
rowKey = this.getCleanId(rowKey)
for (let newValueKey in newValues) {
if (newValues.hasOwnProperty(newValueKey)) {
let newValue = newValues[newValueKey]
let edited = false // 已被修改
this.inputValues.forEach(value => {
// 在inputValues中找到了该字段
if (rowKey === this.getCleanId(value.id)) {
if (value.hasOwnProperty(newValueKey)) {
edited = true
value[newValueKey] = newValue
}
}
})
let modelKey = `${newValueKey}${this.caseId}${rowKey}`
// 在 selectValues 中寻找值
if (!edited) {
if (newValue !== 0 && !newValue) {
edited = this.setOneValue(this.selectValues, modelKey, undefined)
} else {
edited = this.setOneValue(this.selectValues, modelKey, newValue)
}
}
// 在 checkboxValues 中寻找值
if (!edited) {
// checkbox 特殊处理 CustomValue
let key = this.valuesHasOwnProperty(this.checkboxValues, modelKey)
// 找到对应的column
let sourceValue
for (let column of this.columns) {
if (column.key === newValueKey) {
edited = true
// 判断是否设定了customValue自定义值
if (column.customValue instanceof Array) {
let customValue = (column.customValue[0] || '').toString()
sourceValue = (newValue === customValue)
} else {
sourceValue = !!newValue
for (let column of this.columns) {
if (column.key === newValueKey) {
let newValue = newValues[newValueKey]
this.inputValues.forEach(value => {
// 在inputValues中找到了该字段
if (rowKey === this.getCleanId(value.id)) {
if (value.hasOwnProperty(newValueKey)) {
edited = true
value[newValueKey] = newValue
}
}
this.$set(this.checkboxValues, key, sourceValue)
break
})
if (!edited) {
let modelKey = `${newValueKey}${this.caseId}${rowKey}`
if (column.type === FormTypes.select) {
if (newValue !== 0 && !newValue) {
edited = this.setOneValue(this.selectValues, modelKey, undefined)
} else {
edited = this.setOneValue(this.selectValues, modelKey, newValue)
}
} else if (column.type === FormTypes.checkbox) {
// checkbox 特殊处理 CustomValue
let key = this.valuesHasOwnProperty(this.checkboxValues, modelKey)
// 找到对应的column
let sourceValue
// 判断是否设定了customValue自定义值
if (column.customValue instanceof Array) {
let customValue = (column.customValue[0] || '').toString()
sourceValue = (newValue === customValue)
} else {
sourceValue = !!newValue
}
this.$set(this.checkboxValues, key, sourceValue)
edited = true
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
edited = this.setOneValue(this.jdateValues, modelKey, newValue)
} else if (column.type === FormTypes.input_pop) {
edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
} else if (column.type === FormTypes.slot) {
edited = this.setOneValue(this.slotValues, modelKey, newValue)
} else if (column.type === FormTypes.upload || column.type === FormTypes.image || column.type === FormTypes.file) {
edited = this.setOneValue(this.uploadValues, modelKey, newValue)
} else if (column.type === FormTypes.popup) {
edited = this.setOneValue(this.popupValues, modelKey, newValue)
} else if (column.type === FormTypes.radio) {
edited = this.setOneValue(this.radioValues, modelKey, newValue)
} else if (column.type === FormTypes.list_multi) {
edited = this.setOneValue(this.multiSelectValues, modelKey, newValue, true)
} else if (column.type === FormTypes.sel_search) {
edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
} else {
edited = false
}
}
if (edited) {
this.elemValueChange(column.type, {[newValueKey]: newValue}, column, newValue)
}
}
}
// 在 jdateValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.jdateValues, modelKey, newValue)
}
// 在 jInputPopValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
}
// 在 slotValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.slotValues, modelKey, newValue)
}
// 在 uploadValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.uploadValues, modelKey, newValue)
}
// 在 popupValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.popupValues, modelKey, newValue)
}
// 在 radioValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.radioValues, modelKey, newValue)
}
// 在 multiSelectValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.multiSelectValues, modelKey, newValue, true)
}
// 在 searchSelectValues 中寻找值
if (!edited) {
edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
console.warn(`JEditableTable.setValues没有找到"${newValueKey}"列`)
}
}
}
@ -2155,8 +2150,12 @@
}
target.value = value
}
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'blur')
//update--begin--autor:lvdandan-----date:20201126------forLOWCOD-1088 JEditableTable输入校验提示框位置偏移 #2005
setTimeout(()=>{
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'blur')
}, 100)
//update--end--autor:lvdandan-----date:20201126------forLOWCOD-1088 JEditableTable输入校验提示框位置偏移 #2005
},
handleChangeCheckboxCommon(event, row, column) {
let { id, checked } = event.target
@ -2209,8 +2208,22 @@
value['message'] = file.response.message || '未知错误'
}
this.uploadValues = this.bindValuesChange(value, id, 'uploadValues')
// 触发valueChange 事件
this.elemValueChange(column.type, row, column, value)
},
handleMoreOperation(id,flag){
handleMoreOperation(id,flag,column){
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(column.number){
this.number = column.number;
}else{
this.number = 0;
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(column && column.fieldExtendJson){
let json = JSON.parse(column.fieldExtendJson);
this.number = json.uploadnum?json.uploadnum:0;
}
//console.log("this.uploadValues[id]",this.uploadValues[id])
let path = ''
if(this.uploadValues && this.uploadValues[id]){
@ -2449,7 +2462,7 @@
}
},
/** view辅助方法构建 td style */
buildTdStyle(col) {
buildTdStyle(col,isTitle) {
const isEmptyWidth = (column) => (column.type === FormTypes.hidden || column.width === '0px' || column.width === '0' || column.width === 0)
let style = {}
@ -2461,6 +2474,17 @@
} else {
style['width'] = '120px'
}
//update-begin-author:lvdandan date:20201116 for:LOWCOD-984 默认风格功能测试附表样式问题 日期时间控件长度太大
//是否为标题如果是时间控件设为200时间控件的标题设为240 时间
if(col.type === FormTypes.datetime){
if(true === isTitle){
style['width'] = '240px'
}else{
style['width'] = '200px'
}
}
//update-end-author:lvdandan date:20201116 for:LOWCOD-984 默认风格功能测试附表样式问题 日期时间控件长度太大
// checkbox 居中显示
let isCheckbox = col.type === FormTypes.checkbox
if (isCheckbox) {
@ -2747,8 +2771,9 @@
flex-direction: column;
&.td-cb, &.td-num {
min-width: 4%;
max-width: 45px;
width: 45px;
min-width: 45px;
max-width: 50px;
margin-right: 0;
padding-left: 0;
padding-right: 0;
@ -2757,6 +2782,9 @@
}
&.td-ds {
width: 30px;
min-width: 30px;
max-width: 35px;
margin-right: 0;
padding-left: 0;
padding-right: 0;
@ -2942,6 +2970,10 @@
label {
height: 32px;
&.ant-checkbox-wrapper {
height: auto;
}
}
.j-td-span {
@ -3062,4 +3094,4 @@
}
</style>
</style>

View File

@ -128,6 +128,13 @@
this.reload()
}
})
}else{
//update--begin--autor:wangshuai-----date:20200724------for富文本编辑器切换tab无法修改------
let tabLayout = getVmParentByName(this, 'TabLayout')
tabLayout.excuteCallback(()=>{
this.reload()
})
//update--begin--autor:wangshuai-----date:20200724------for文本编辑器切换tab无法修改------
}
},

View File

@ -1,27 +1,32 @@
<template>
<a-upload
name="file"
listType="picture-card"
:multiple="isMultiple"
:action="uploadAction"
:headers="headers"
:data="{biz:bizPath}"
:fileList="fileList"
:beforeUpload="beforeUpload"
:disabled="disabled"
:isMultiple="isMultiple"
:showUploadList="isMultiple"
@change="handleChange"
@preview="handlePreview">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="height:104px;max-width:300px"/>
<div v-else >
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
<div class="ant-upload-text">{{ text }}</div>
</div>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
<img alt="example" style="width: 100%" :src="previewImage"/>
</a-modal>
</a-upload>
<div class="img">
<a-upload
name="file"
listType="picture-card"
:multiple="isMultiple"
:action="uploadAction"
:headers="headers"
:data="{biz:bizPath}"
:fileList="fileList"
:beforeUpload="beforeUpload"
:disabled="disabled"
:isMultiple="isMultiple"
:showUploadList="isMultiple"
@change="handleChange"
@preview="handlePreview"
:class="!isMultiple?'imgupload':''">
<div style="width:104px;height:104px">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>
<div v-else 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"/>
</a-modal>
</a-upload>
</div>
</template>
<script>
@ -77,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(){
@ -129,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,8 +192,14 @@
if(!this.isMultiple){
arr.push(uploadFiles[uploadFiles.length-1].response.message)
}else{
for(var a=0;a<uploadFiles.length;a++){
arr.push(uploadFiles[a].response.message)
for(let a=0;a<uploadFiles.length;a++){
// update-begin-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
if(uploadFiles[a].status === 'done' ) {
arr.push(uploadFiles[a].response.message)
}else{
return;
}
// update-end-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
}
}
if(arr.length>0){
@ -200,5 +227,12 @@
</script>
<style scoped>
</style>
/* update--begin--autor:lvdandan-----date:20201016------forj-image-upload图片组件单张图片详情回显空白
* 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;}
/* update--end--autor:lvdandan-----date:20201016------forj-image-upload图片组件单张图片详情回显空白*/
</style>

View File

@ -24,6 +24,11 @@
type:String,
required:false,
default:''
},
trim:{
type: Boolean,
required: false,
default:false
}
},
watch:{
@ -56,8 +61,8 @@
let text = this.value
switch (this.type) {
case JINPUT_QUERY_LIKE:
//修复路由传参的值传送到jinput框被前后各截取了一位
if(text.indexOf("*") != -1){
//修复路由传参的值传送到jinput框被前后各截取了一位 #1336
if(text.indexOf("*") != -1){
text = text.substring(1,text.length-1);
}
break;
@ -77,6 +82,9 @@
},
backValue(e){
let text = e.target.value
if(text && this.trim===true){
text = text.trim()
}
switch (this.type) {
case JINPUT_QUERY_LIKE:
text = "*"+text+"*";

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

@ -110,7 +110,7 @@
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
},
allSlotsKeys() {
return this.slotsKeys.concat(this.scopedSlotsKeys)
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
},
// 切换全屏的按钮图标
fullscreenButtonIcon() {

View File

@ -11,6 +11,7 @@
:code="code"
:multi="multi"
:groupId="uniqGroupId"
:param="param"
@ok="callBack"
/>
@ -75,6 +76,12 @@
required: false,
default: false
},
//popup动态参数 支持系统变量语法
param:{
type: Object,
required: false,
default: ()=>{}
},
/** 分组ID用于将多个popup的请求合并到一起不传不分组 */
groupId: String

View File

@ -498,10 +498,9 @@
} else {
if (Array.isArray(item.options)) {
// 如果有字典属性,就不需要保存 options 了
if (item.dictCode) {
// 去掉特殊属性
delete item.options
}
//update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
delete item.options
//update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
}
}
}

View File

@ -0,0 +1,77 @@
<template>
<a-time-picker
:disabled="disabled || readOnly"
:placeholder="placeholder"
:value="momVal"
:format="dateFormat"
:getCalendarContainer="getCalendarContainer"
@change="handleTimeChange"/>
</template>
<script>
import moment from 'moment'
export default {
name: 'JTime',
props: {
placeholder:{
type: String,
default: '',
required: false
},
value:{
type: String,
required: false
},
dateFormat:{
type: String,
default: 'HH:mm:ss',
required: false
},
readOnly:{
type: Boolean,
required: false,
default: false
},
disabled:{
type: Boolean,
required: false,
default: false
},
getCalendarContainer: {
type: Function,
default: (node) => node.parentNode
}
},
data () {
let timeStr = this.value;
return {
decorator:"",
momVal:!timeStr?null:moment(timeStr,this.dateFormat)
}
},
watch: {
value (val) {
if(!val){
this.momVal = null
}else{
this.momVal = moment(val,this.dateFormat)
}
}
},
methods: {
moment,
handleTimeChange(mom,timeStr){
this.$emit('change', timeStr);
}
},
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,75 @@
<template>
<j-modal
title="详细信息"
:width="1200"
:visible="visible"
@ok="handleOk"
@cancel="close"
switch-fullscreen
:fullscreen.sync="fullscreen"
>
<transition name="fade">
<div v-if="visible">
<slot name="mainForm" :row="row" :column="column"/>
<slot name="subForm" :row="row" :column="column"/>
</div>
</transition>
</j-modal>
</template>
<script>
import { cloneObject } from '@/utils/util'
export default {
name: 'JVxeDetailsModal',
inject: ['superTrigger'],
data() {
return {
visible: false,
fullscreen: false,
row: null,
column: null,
}
},
created() {
},
methods: {
open(event) {
let {row, column} = event
this.row = cloneObject(row)
this.column = column
this.visible = true
},
close() {
this.visible = false
},
handleOk() {
this.superTrigger('detailsConfirm', {
row: this.row,
column: this.column,
callback: (success) => {
this.visible = !success
},
})
},
},
}
</script>
<style lang="less">
.fade-enter-active,
.fade-leave-active {
opacity: 1;
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<div :class="boxClass">
<a-pagination
:disabled="disabled"
v-bind="bindProps"
@change="handleChange"
@showSizeChange="handleShowSizeChange"
/>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types'
export default {
name: 'JVxePagination',
props: {
size: String,
disabled: PropTypes.bool,
pagination: PropTypes.object.def({}),
},
data() {
return {
defaultPagination: {
current: 1,
pageSize: 10,
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return range[0] + '-' + range[1] + ' 共 ' + total + ' 条'
},
showQuickJumper: true,
showSizeChanger: true,
total: 100
}
}
},
computed: {
bindProps() {
return {
...this.defaultPagination,
...this.pagination,
size: this.size === 'tiny' ? 'small' : ''
}
},
boxClass() {
return {
'j-vxe-pagination': true,
'show-quick-jumper': !!this.bindProps.showQuickJumper
}
},
},
methods: {
handleChange(current, pageSize) {
this.$set(this.pagination, 'current', current)
this.$emit('change', {current, pageSize})
},
handleShowSizeChange(current, pageSize) {
this.$set(this.pagination, 'pageSize', pageSize)
this.$emit('change', {current, pageSize})
},
},
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,164 @@
<template>
<a-popover :visible="visible" placement="bottom" 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">
<a-icon type="close"/>
</div>
</div>
<template slot="content">
<transition name="fade">
<slot v-if="visible" name="subForm" :row="row" :column="column"/>
</transition>
</template>
<div ref="div" class="j-vxe-popover-div"></div>
</a-popover>
</template>
<script>
import domAlign from 'dom-align'
import { getParentNodeByTagName } from '../utils/vxeUtils'
import { cloneObject, triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'JVxeSubPopover',
data() {
return {
visible: false,
// 当前行
row: null,
column: null,
overlayStyle: {
width: null,
zIndex: 100
},
}
},
created() {
},
methods: {
toggle(event) {
if (this.row == null) {
this.open(event)
} else {
this.row.id === event.row.id ? this.close() : this.reopen(event)
}
},
open(event, level = 0) {
if (level > 3) {
this.$message.error('打开子表失败')
console.warn('【JVxeSubPopover】打开子表失败')
return
}
let {row, column, $table, $event: {target}} = event
this.row = cloneObject(row)
this.column = column
let className = target.className || ''
className = typeof className === 'string' ? className : className.toString()
// 点击的是expand不做处理
if (className.includes('vxe-table--expand-btn')) {
return
}
// 点击的是checkbox不做处理
if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) {
return
}
// 点击的是radio不做处理
if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) {
return
}
let table = $table.$el
let tr = getParentNodeByTagName(target, 'tr')
if (table && tr) {
let clientWidth = table.clientWidth
let clientHeight = tr.clientHeight
this.$refs.div.style.width = clientWidth + 'px'
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, {
points: ['tl', 'tl'],
offset: [0, 0],
overflow: {
alwaysByViewport: true
},
})
this.$nextTick(() => {
this.visible = true
this.$nextTick(() => {
triggerWindowResizeEvent()
})
})
} else {
let num = ++level
console.warn('【JVxeSubPopover】table or tr 获取失败,正在进行第 ' + num + '次重试', {event, table, tr})
window.setTimeout(() => this.open(event, num), 100)
}
},
close() {
if (this.visible) {
this.row = null
this.visible = false
}
},
reopen(event) {
this.close()
this.open(event)
},
},
}
</script>
<style scoped lang="less">
.j-vxe-popover-title {
.j-vxe-popover-title-close {
position: absolute;
right: 0;
top: 0;
width: 31px;
height: 31px;
text-align: center;
line-height: 31px;
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: color 300ms;
&:hover {
color: rgba(0, 0, 0, 0.8);
}
}
}
.j-vxe-popover-div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 31px;
z-index: -1;
}
</style>
<style lang="less">
.j-vxe-popover-overlay.ant-popover {
.ant-popover-title {
position: relative;
}
}
.fade-enter-active,
.fade-leave-active {
opacity: 1;
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,127 @@
<template>
<div :class="boxClass">
<!-- 工具按钮 -->
<div class="j-vxe-tool-button div" :size="btnSize">
<slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize"/>
<a-button v-if="showAdd" icon="plus" @click="trigger('add')" :disabled="disabled" type="primary">新增</a-button>
<a-button v-if="showSave" icon="save" @click="trigger('save')" :disabled="disabled">保存</a-button>
<template v-if="selectedRowIds.length > 0">
<a-popconfirm
v-if="showRemove"
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
@confirm="trigger('remove')"
>
<a-button icon="minus" :disabled="disabled">删除</a-button>
</a-popconfirm>
<template v-if="showClearSelection">
<a-button icon="delete" @click="trigger('clearSelection')">清空选择</a-button>
</template>
</template>
<slot v-if="showSuffix" name="toolbarSuffix" :size="btnSize"/>
<a v-if="showCollapse" @click="toggleCollapse" style="margin-left: 4px">
<span>{{ collapsed ? '展开' : '收起' }}</span>
<a-icon :type="collapsed ? 'down' : 'up'"/>
</a>
</div>
</div>
</template>
<script>
export default {
name: 'JVxeToolbar',
props: {
toolbarConfig: Object,
excludeCode: Array,
size: String,
disabled: Boolean,
disabledRows: Object,
selectedRowIds: Array,
},
data() {
return {
// 是否收起
collapsed: true,
}
},
computed: {
boxClass() {
return {
'j-vxe-toolbar': true,
'j-vxe-toolbar-collapsed': this.collapsed,
}
},
btns() {
let arr = this.toolbarConfig.btn || ['add', 'remove', 'clearSelection']
let exclude = [...this.excludeCode]
// TODO 需要将remove替换batch_delete
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
if(exclude.indexOf('batch_delete')>=0){
exclude.add('remove')
}
// 按钮权限 需要去掉不被授权的按钮
return arr.filter(item=>{
return exclude.indexOf(item)<0
})
},
slots() {
return this.toolbarConfig.slot || ['prefix', 'suffix']
},
showPrefix() {
return this.slots.includes('prefix')
},
showSuffix() {
return this.slots.includes('suffix')
},
showAdd() {
return this.btns.includes('add')
},
showSave() {
return this.btns.includes('save')
},
showRemove() {
return this.btns.includes('remove')
},
showClearSelection() {
if (this.btns.includes('clearSelection')) {
// 有禁用行时才显示清空选择按钮
// 因为禁用行会阻止选择行,导致无法取消全选
let length = Object.keys(this.disabledRows).length
return length > 0
}
return false
},
showCollapse() {
return this.btns.includes('collapse')
},
btnSize() {
return this.size === 'tiny' ? 'small' : null
},
},
methods: {
/** 触发事件 */
trigger(name) {
this.$emit(name)
},
// 切换展开收起
toggleCollapse() {
this.collapsed = !this.collapsed
},
},
}
</script>
<style lang="less">
.j-vxe-toolbar-collapsed {
[data-collapse] {
display: none;
}
}
.j-vxe-tool-button.div .ant-btn {
margin-right: 8px;
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div :class="clazz" :style="boxStyle">
<a-checkbox
ref="checkbox"
:checked="innerValue"
v-bind="cellProps"
@change="handleChange"
/>
</div>
</template>
<script>
import { neverNull } from '@/utils/util'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeCheckboxCell',
mixins: [JVxeCellMixins],
props: {},
computed: {
bordered() {
return !!this.renderOptions.bordered
},
scrolling() {
return !!this.renderOptions.scrolling
},
clazz() {
return {
'j-vxe-checkbox': true,
'no-animation': this.scrolling
}
},
boxStyle() {
const style = {}
// 如果有边框且未设置align属性就强制居中
if (this.bordered && !this.originColumn.align) {
style['text-align'] = 'center'
}
return style
},
},
methods: {
handleChange(event) {
this.handleChangeCommon(event.target.checked)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {
visible: true,
},
getValue(value) {
let {own: col} = this.column
// 处理 customValue
if (Array.isArray(col.customValue)) {
let customValue = getCustomValue(col)
if (typeof value === 'boolean') {
return value ? customValue[0] : customValue[1]
} else {
return value
}
} else {
return value
}
},
setValue(value) {
let {own: col} = this.column
// 判断是否设定了customValue自定义值
if (Array.isArray(col.customValue)) {
let customValue = getCustomValue(col)
return neverNull(value).toString() === customValue[0].toString()
} else {
return !!value
}
},
createValue({column}) {
let {own: col} = column
if (Array.isArray(col.customValue)) {
let customValue = getCustomValue(col)
return col.defaultChecked ? customValue[0] : customValue[1]
} else {
return !!col.defaultChecked
}
},
}
}
function getCustomValue(col) {
let customTrue = neverNull(col.customValue[0], true)
let customFalse = neverNull(col.customValue[1], false)
return [customTrue, customFalse]
}
</script>
<style lang="less">
// 关闭动画,防止滚动时动态赋值出现问题
.j-vxe-checkbox.no-animation {
.ant-checkbox-inner,
.ant-checkbox-inner::after {
transition: none !important;
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<a-date-picker
ref="datePicker"
:value="innerDateValue"
allowClear
:format="dateFormat"
:showTime="isDatetime"
dropdownClassName="j-vxe-date-picker"
style="min-width: 0;"
v-bind="cellProps"
@change="handleChange"
/>
</template>
<script>
import moment from 'moment'
import { JVXETypes } from '@/components/jeecg/JVxeTable/index'
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeDateCell',
mixins: [JVxeCellMixins],
props: {},
data() {
return {
innerDateValue: null,
}
},
computed: {
isDatetime() {
return this.$type === JVXETypes.datetime
},
dateFormat() {
let format = this.originColumn.format
return format ? format : (this.isDatetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')
},
},
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(event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event)),
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,138 @@
<template>
<a-dropdown :trigger="['click']">
<div class="j-vxe-ds-icons">
<a-icon type="align-left"/>
<a-icon type="align-right"/>
</div>
<!-- <div class="j-vxe-ds-btns">-->
<!-- <a-button icon="caret-up" size="small" :disabled="disabledMoveUp" @click="handleRowMoveUp"/>-->
<!-- <a-button icon="caret-down" size="small" :disabled="disabledMoveDown" @click="handleRowMoveDown"/>-->
<!-- </div>-->
<a-menu slot="overlay">
<a-menu-item key="0" :disabled="disabledMoveUp" @click="handleRowMoveUp">向上移</a-menu-item>
<a-menu-item key="1" :disabled="disabledMoveDown" @click="handleRowMoveDown">向下移</a-menu-item>
<a-menu-divider/>
<a-menu-item key="3" @click="handleRowInsertDown">插入一行</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeDragSortCell',
mixins: [JVxeCellMixins],
computed: {
// 排序结果保存字段
dragSortKey() {
return this.renderOptions.dragSortKey || 'orderNum'
},
disabledMoveUp() {
return this.rowIndex === 0
},
disabledMoveDown() {
return this.rowIndex === (this.rows.length - 1)
},
},
methods: {
/** 向上移 */
handleRowMoveUp(event) {
// event.target.blur()
if (!this.disabledMoveUp) {
this.trigger('rowMoveUp', this.rowIndex)
}
},
/** 向下移 */
handleRowMoveDown(event) {
// event.target.blur()
if (!this.disabledMoveDown) {
this.trigger('rowMoveDown', this.rowIndex)
}
},
/** 插入一行 */
handleRowInsertDown() {
this.trigger('rowInsertDown', this.rowIndex)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
// 【功能开关】
switches: {
editRender: false
},
},
}
</script>
<style lang="less">
.j-vxe-ds-icons {
position: relative;
/*cursor: move;*/
cursor: pointer;
width: 14px;
height: 100%;
display: inline-block;
.anticon-align-left,
.anticon-align-right {
position: absolute;
top: 30%;
}
.anticon-align-left {
left: 0;
}
.anticon-align-right {
right: 0;
}
}
.j-vxe-ds-btns {
position: relative;
cursor: pointer;
width: 24px;
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-content: center;
.ant-btn {
border: none;
z-index: 0;
padding: 0;
width: 100%;
/*height: 30%;*/
height: 40%;
display: block;
border-radius: 0;
&:hover {
z-index: 1;
/* height: 40%;*/
/* & .anticon-caret-up,*/
/* & .anticon-caret-down {*/
/* top: 2px;*/
/* }*/
}
&:last-child {
margin-top: -1px;
}
& .anticon-caret-up,
& .anticon-caret-down {
vertical-align: top;
position: relative;
top: 0;
transition: top 0.3s;
}
}
}
</style>

View File

@ -0,0 +1,87 @@
<template>
<a-input
ref="input"
:value="innerValue"
v-bind="cellProps"
@blur="handleBlur"
@change="handleChange"
/>
</template>
<script>
import { JVXETypes } from '@/components/jeecg/JVxeTable'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
const NumberRegExp = /^-?\d+\.?\d*$/
export default {
name: 'JVxeInputCell',
mixins: [JVxeCellMixins],
methods: {
/** 处理change事件 */
handleChange(event) {
let {$type} = this
let {target} = event
let {value, selectionStart} = target
let change = true
if ($type === JVXETypes.inputNumber) {
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
if (!NumberRegExp.test(value) && (value !== '' && value !== '-')) {
change = false
value = this.innerValue
target.value = value || ''
if (typeof selectionStart === 'number') {
target.selectionStart = selectionStart - 1
target.selectionEnd = selectionStart - 1
}
}
}
// 触发事件,存储输入的值
if (change) {
this.handleChangeCommon(value)
}
if ($type === JVXETypes.inputNumber) {
// this.recalcOneStatisticsColumn(col.key)
}
},
/** 处理blur失去焦点事件 */
handleBlur(event) {
let {$type} = this
let {target} = event
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
if ($type === JVXETypes.inputNumber) {
if (!NumberRegExp.test(target.value)) {
target.value = ''
} else {
target.value = Number.parseFloat(target.value)
}
this.handleChangeCommon(target.value)
}
this.handleBlurCommon(target.value)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
installOptions: {
// 自动聚焦的 class 类名
autofocus: '.ant-input',
},
getValue(value) {
if (this.$type === JVXETypes.inputNumber && typeof value === 'string') {
if (NumberRegExp.test(value)) {
return Number.parseFloat(value)
}
}
return value
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,42 @@
<template>
<reload-effect
:vNode="innerValue"
:effect="reloadEffect"
@effect-end="handleEffectEnd"
/>
</template>
<script>
import ReloadEffect from './ReloadEffect'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeNormalCell',
mixins: [JVxeCellMixins],
components: {ReloadEffect},
computed: {
reloadEffectRowKeysMap() {
return this.renderOptions.reloadEffectRowKeysMap
},
reloadEffect() {
return (this.renderOptions.reloadEffect && this.reloadEffectRowKeysMap[this.row.id]) === true
},
},
methods: {
// 特效结束
handleEffectEnd() {
this.$delete(this.reloadEffectRowKeysMap, this.row.id)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {
editRender: false,
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,60 @@
<template>
<a-progress
:class="clazz"
:percent="innerValue"
size="small"
v-bind="cellProps"
/>
</template>
<script>
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
// JVxe 进度条组件
export default {
name: 'JVxeProgressCell',
mixins: [JVxeCellMixins],
data() {
return {}
},
computed: {
clazz() {
return {
'j-vxe-progress': true,
'no-animation': this.scrolling
}
},
scrolling() {
return !!this.renderOptions.scrolling
},
},
methods: {},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {
editRender: false,
},
setValue(value) {
try {
if (typeof value !== 'number') {
return Number.parseFloat(value)
} else {
return value
}
} catch {
return 0
}
},
}
}
</script>
<style scoped lang="less">
// 关闭进度条的动画,防止滚动时动态赋值出现问题
.j-vxe-progress.no-animation {
/deep/ .ant-progress-success-bg,
/deep/ .ant-progress-bg {
transition: none !important;
}
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<a-select
ref="select"
:value="innerValue"
allowClear
:filterOption="handleSelectFilterOption"
v-bind="selectProps"
style="width: 100%;"
@blur="handleBlur"
@change="handleChangeCommon"
@search="handleSearchSelect"
>
<template v-for="option of originColumn.options">
<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>
</template>
</a-select>
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { JVXETypes } from '@comp/jeecg/JVxeTable/index'
export default {
name: 'JVxeSelectCell',
mixins: [JVxeCellMixins],
computed: {
selectProps() {
let props = {...this.cellProps}
// 判断select是否允许输入
let {allowSearch, allowInput} = this.originColumn
if (allowInput === true || allowSearch === true) {
props['showSearch'] = true
}
return props
},
},
created() {
let multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
let search = [JVXETypes.selectSearch, JVXETypes.sel_search]
if (multiple.includes(this.$type)) {
// 处理多选
let props = this.originColumn.props || {}
props['mode'] = 'multiple'
props['maxTagCount'] = 1
this.$set(this.originColumn, 'props', props)
} else if (search.includes(this.$type)) {
// 处理搜索
this.$set(this.originColumn, 'allowSearch', true)
}
},
methods: {
/** 处理blur失去焦点事件 */
handleBlur(value) {
let {allowInput, options} = this.originColumn
if (allowInput === true) {
// 删除无用的因搜索(用户输入)而创建的项
if (typeof value === 'string') {
let indexes = []
options.forEach((option, index) => {
if (option.value.toLocaleString() === value.toLocaleString()) {
delete option.searchAdd
} else if (option.searchAdd === true) {
indexes.push(index)
}
})
// 翻转删除数组中的项
for (let index of indexes.reverse()) {
options.splice(index, 1)
}
}
}
this.handleBlurCommon(value)
},
/** 用于搜索下拉框中的内容 */
handleSelectFilterOption(input, option) {
let {allowSearch, allowInput} = this.originColumn
if (allowSearch === true || allowInput === true) {
//update-begin-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
//update-end-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
}
return true
},
/** select 搜索时的事件用于动态添加options */
handleSearchSelect(value) {
let {allowSearch, allowInput, options} = this.originColumn
if (allowSearch !== true && allowInput === true) {
// 是否找到了对应的项,找不到则添加这一项
let flag = false
for (let option of options) {
if (option.value.toLocaleString() === value.toLocaleString()) {
flag = true
break
}
}
// !!value :不添加空值
if (!flag && !!value) {
// searchAdd 是否是通过搜索添加的
options.push({title: value, value: value, searchAdd: true})
}
}
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-select'),
},
translate: {enabled: true},
getValue(value) {
if (Array.isArray(value)) {
return value.join(',')
} else {
return value
}
},
setValue(value) {
let {column: {own: col}, params: {$table}} = this
// 判断是否是多选
if ((col.props || {})['mode'] === 'multiple') {
$table.$set(col.props, 'maxTagCount', 1)
}
if (value != null && value !== '') {
if (typeof value === 'string') {
return value === '' ? [] : value.split(',')
}
return value
} else {
return undefined
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,46 @@
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
// 插槽
export default {
name: 'JVxeSlotCell',
mixins: [JVxeCellMixins],
computed: {
slotProps() {
return {
value: this.innerValue,
row: this.row,
column: this.originColumn,
params: this.params,
$table: this.params.$table,
rowId: this.params.rowid,
index: this.params.rowIndex,
rowIndex: this.params.rowIndex,
columnIndex: this.params.columnIndex,
target: this.renderOptions.target,
caseId: this.renderOptions.target.caseId,
scrolling: this.renderOptions.scrolling,
reloadEffect: this.renderOptions.reloadEffect,
triggerChange: (v) => this.handleChangeCommon(v),
}
},
},
render(h) {
let {slot} = this.renderOptions
if (slot) {
return h('div', {}, slot(this.slotProps))
} else {
return h('div')
}
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {
editRender: false
},
}
}
// :isNotPass="notPassedIds.includes(col.key+row.id)"

View File

@ -0,0 +1,145 @@
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
// tags 组件的显示组件
export const TagsSpanCell = {
name: 'JVxeTagsCell',
mixins: [JVxeCellMixins],
data() {
return {
innerTags: [],
}
},
watch: {
innerValue: {
immediate: true,
handler(value) {
if (value !== this.innerTags.join(';')) {
let rv = replaceValue(value)
this.innerTags = rv.split(';')
this.handleChangeCommon(rv)
}
},
},
},
methods: {
renderTags(h) {
let tags = []
for (let tag of this.innerTags) {
if (tag) {
let tagProps = {}
let tagStyle = {}
let setTagColor = this.originColumn.setTagColor
if (typeof setTagColor === 'function') {
/**
* 设置 tag 颜色
*
* @param event 包含的字段:
* event.tagValue 当前tag的值
* event.value 当前原始值
* event.row 当前行的所有值
* event.column 当前列的配置
* event.column.own 当前列的原始配置
* @return Array | String 可以返回一个数组数据第一项是tag背景颜色第二项是字体颜色。也可以返回一个字符串即tag背景颜色
*/
let color = setTagColor({
tagValue: tag,
value: this.innerValue,
row: this.row,
column: this.column,
})
if (Array.isArray(color)) {
tagProps.color = color[0]
tagStyle.color = color[1]
} else if (color && typeof color === 'string') {
tagProps.color = color
}
}
tags.push(h('a-tag', {
props: tagProps,
style: tagStyle,
}, [tag]))
}
}
return tags
},
},
render(h) {
return h('div', {}, [
this.renderTags(h)
])
},
}
// tags 组件的输入框
export const TagsInputCell = {
name: 'JVxeTagsInputCell',
mixins: [JVxeCellMixins],
data() {
return {
innerTagValue: '',
}
},
watch: {
innerValue: {
immediate: true,
handler(value) {
if (value !== this.innerTagValue) {
this.handleInputChange(value)
}
},
},
},
methods: {
handleInputChange(value, event) {
this.innerTagValue = replaceValue(value, event)
this.handleChangeCommon(this.innerTagValue)
return this.innerTagValue
},
},
render(h) {
return h('a-input', {
props: {
value: this.innerValue,
...this.cellProps
},
on: {
change: (event) => {
let {target, target: {value}} = event
let newValue = this.handleInputChange(value, event)
if (newValue !== value) {
target.value = newValue
}
}
},
})
},
}
// 将值每隔两位加上一个分号
function replaceValue(value, event) {
if (value) {
// 首先去掉现有的分号
value = value.replace(/;/g, '')
// 然后再遍历添加分号
let rv = ''
let splitArr = value.split('')
let count = 0
splitArr.forEach((val, index) => {
rv += val
let position = index + 1
if (position % 2 === 0 && position < splitArr.length) {
count++
rv += ';'
}
})
if (event && count > 0) {
let {target, target: {selectionStart}} = event
target.selectionStart = selectionStart + count
target.selectionEnd = selectionStart + count
}
return rv
}
return ''
}

View File

@ -0,0 +1,34 @@
<template>
<j-input-pop
:value="innerValue"
:width="300"
:height="210"
v-bind="cellProps"
style="width: 100%;"
@change="handleChangeCommon"
/>
</template>
<script>
import JInputPop from '@/components/jeecg/minipop/JInputPop'
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeTextareaCell',
mixins: [JVxeCellMixins],
components: {JInputPop},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
installOptions: {
autofocus: '.ant-input',
},
aopEvents: {
editActived: event => dispatchEvent(event, 'anticon-fullscreen'),
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,178 @@
<template>
<div>
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
<a-input
:key="fileKey"
:readOnly="true"
:value="file.name"
>
<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>
<span v-if="file.status === 'uploading'" slot="addonAfter">{{ Math.floor(file.percent) }}%</span>
<template v-else-if="originColumn.allowDownload !== false || originColumn.allowRemove !== false" slot="addonAfter">
<a-dropdown :trigger="['click']" placement="bottomRight">
<a-tooltip title="操作">
<a-icon
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<!-- <a-menu-item @click="handleClickPreviewFile">-->
<!-- <span><a-icon type="eye"/>&nbsp;预览</span>-->
<!-- </a-menu-item>-->
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-input>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="originColumn.action"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '点击上传'}}</a-button>
</a-upload>
</div>
</template>
<script>
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { getFileAccessHttpUrl } from '@api/manage'
export default {
name: 'JVxeUploadCell',
mixins: [JVxeCellMixins],
props: {},
data() {
return {
innerFile: null,
}
},
computed: {
/** upload headers */
uploadHeaders() {
let {originColumn: col} = this
let headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
hasFile() {
return this.innerFile != null
},
},
watch: {
innerValue: {
immediate: true,
handler() {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
},
},
},
methods: {
handleChangeUpload(info) {
let {row, originColumn: col} = this
let {file} = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (col.responseName && file.response) {
value['responseName'] = file.response[col.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[col.responseName]
this.handleChangeCommon(value)
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
},
// handleClickPreviewFile(id) {
// this.$message.info('尚未实现')
// },
handleClickDownloadFile(id) {
let {path} = this.value || {}
if (path) {
let url = getFileAccessHttpUrl(path)
window.open(url)
}
},
handleClickDeleteFile() {
this.handleChangeCommon(null)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {visible: true},
getValue: value => fileGetValue(value),
setValue: value => fileSetValue(value),
}
}
function fileGetValue(value) {
if (value && value.path) {
return value.path
}
return value
}
function fileSetValue(value) {
if (value) {
let first = value.split(',')[0]
let name = first.substring(first.lastIndexOf('/') + 1)
return {
name: name,
path: value,
status: 'done',
}
}
return value
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,84 @@
import '../../less/reload-effect.less'
import { randomString } from '@/utils/util'
// 修改数据特效
export default {
props: {
vNode: null,
// 是否启用特效
effect: Boolean,
},
data() {
return {
// vNode: null,
innerEffect: false,
// 应付同时多个特效
effectIdx: 0,
effectList: [],
}
},
watch: {
vNode: {
deep: true,
immediate: true,
handler(vNode, old) {
this.innerEffect = this.effect
if (this.innerEffect && old != null) {
let topLayer = this.renderSpan(old, 'top')
this.effectList.push(topLayer)
}
},
},
},
methods: {
// 条件渲染内容 span
renderVNode() {
if (this.vNode == null) {
return null
}
let bottom = this.renderSpan(this.vNode, 'bottom')
// 启用了特效,并且有旧数据,就渲染特效顶层
if (this.innerEffect && this.effectList.length > 0) {
this.$emit('effect-begin')
// 1.4s 以后关闭特效
window.setTimeout(() => {
let item = this.effectList[this.effectIdx]
if (item && item.elm) {
// 特效结束后,展示先把 display 设为 none而不是直接删掉该元素
// 目的是为了防止页面重新渲染,导致动画重置
item.elm.style.display = 'none'
}
// 当所有的层级动画都结束时,再删掉所有元素
if (++this.effectIdx === this.effectList.length) {
this.innerEffect = false
this.effectIdx = 0
this.effectList = []
this.$emit('effect-end')
}
}, 1400)
return [this.effectList, bottom]
} else {
return bottom
}
},
// 渲染内容 span
renderSpan(vNode, layer) {
let options = {
key: layer + this.effectIdx + randomString(6),
class: ['j-vxe-reload-effect-span', `layer-${layer}`],
style: {},
}
if (layer === 'top') {
// 最新渲染的在下面
options.style['z-index'] = (9999 - this.effectIdx)
}
return this.$createElement('span', options, [vNode])
},
},
render(h) {
return h('div', {
class: ['j-vxe-reload-effect-box'],
}, [this.renderVNode()])
},
}

View File

@ -0,0 +1,81 @@
import { installCell, mapCell } from './install'
import JVxeTable from './components/JVxeTable'
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 JVxeSelectCell from './components/cells/JVxeSelectCell'
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell'
import JVxeUploadCell from './components/cells/JVxeUploadCell'
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'
// 组件类型
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 AllCells = {
...mapCell(JVXETypes.normal, JVxeNormalCell),
...mapCell(JVXETypes.input, JVxeInputCell),
...mapCell(JVXETypes.inputNumber, JVxeInputCell),
...mapCell(JVXETypes.checkbox, JVxeCheckboxCell),
...mapCell(JVXETypes.select, JVxeSelectCell),
...mapCell(JVXETypes.selectSearch, JVxeSelectCell), // 下拉搜索
...mapCell(JVXETypes.selectMultiple, JVxeSelectCell), // 下拉多选
...mapCell(JVXETypes.date, JVxeDateCell),
...mapCell(JVXETypes.datetime, JVxeDateCell),
...mapCell(JVXETypes.upload, JVxeUploadCell),
...mapCell(JVXETypes.textarea, JVxeTextareaCell),
...mapCell(JVXETypes.tags, TagsInputCell, TagsSpanCell),
...mapCell(JVXETypes.progress, JVxeProgressCell),
...mapCell(JVXETypes.rowDragSort, JVxeDragSortCell),
...mapCell(JVXETypes.slot, JVxeSlotCell),
/* hidden 是特殊的组件,不在这里注册 */
}
export { installCell, mapCell }
export default JVxeTable

View File

@ -0,0 +1,105 @@
import Vue from 'vue'
import { getEventPath } from '@/utils/util'
import JVxeTable, { AllCells, JVXETypes } from './index'
import './less/j-vxe-table.less'
// 引入 vxe-table
import 'xe-utils'
import VXETable, { Grid } from 'vxe-table'
import VXETablePluginAntd from 'vxe-table-plugin-antd'
import 'vxe-table/lib/index.css'
import 'vxe-table-plugin-antd/dist/style.css'
import { getEnhancedMixins, installAllCell, installOneCell } from '@/components/jeecg/JVxeTable/utils/cellUtils'
// VxeGrid所有的方法映射
const VxeGridMethodsMap = {}
Object.keys(Grid.methods).forEach(key => {
// 使用eval可以避免闭包但是要注意不要写es6的代码
VxeGridMethodsMap[key] = eval(`(function(){return this.$refs.vxe.${key}.apply(this.$refs.vxe,arguments)})`)
})
// 将Grid所有的方法都映射继承到JVxeTable上
JVxeTable.methods = Object.assign({}, VxeGridMethodsMap, JVxeTable.methods)
// VXETable 全局配置
const VXETableSettings = {
// z-index 起始值
zIndex: 1000,
table: {
validConfig: {
// 校验提示方式强制使用tooltip
message: 'tooltip'
}
}
}
// 执行注册方法
Vue.use(VXETable, VXETableSettings)
VXETable.use(VXETablePluginAntd)
Vue.component(JVxeTable.name, JVxeTable)
// 注册自定义组件
installAllCell(VXETable)
// 添加事件拦截器 event.clearActived
// 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
VXETable.interceptor.add('event.clearActived', function (params, event, target) {
// 获取组件增强
let col = params.column.own
const interceptor = getEnhancedMixins(col.$type, 'interceptor')
// 执行增强
let flag = interceptor['event.clearActived'].apply(this, arguments)
if (flag === false) {
return false
}
let path = getEventPath(event)
for (let p of path) {
let className = p.className || ''
className = typeof className === 'string' ? className : className.toString()
/* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */
// 点击的标签是JInputPop
if (className.includes('j-input-pop')) {
return false
}
// 点击的标签是JPopup的弹出层
if (className.includes('j-popup-modal')) {
return false
}
// 执行增强
let flag = interceptor['event.clearActived.className'].apply(this, [className, ...arguments])
if (flag === false) {
return false
}
}
})
/**
* 注册map
* @param type 类型
* @param cell 输入组件
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
*/
export function mapCell(type, cell, span) {
let cells = {[type]: cell}
if (span) {
cells[type + ':span'] = span
}
return cells
}
/**
* 注册自定义组件
*
* @param type 类型
* @param cell 输入组件
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
*/
export function installCell(type, cell, span) {
let exclude = [JVXETypes.rowNumber, JVXETypes.rowCheckbox, JVXETypes.rowRadio, JVXETypes.rowExpand, JVXETypes.rowDragSort]
if (exclude.includes(type)) {
throw new Error(`installCell不能使用"${type}"作为组件的type因为这是关键字`)
}
Object.assign(AllCells, mapCell(type, cell, span))
installOneCell(VXETable, type)
}

View File

@ -0,0 +1,59 @@
@import "size/tiny";
.j-vxe-table-box {
// 工具栏
.j-vxe-toolbar {
margin-bottom: 8px;
}
// 分页器
.j-vxe-pagination {
margin-top: 8px;
text-align: right;
.ant-pagination-options-size-changer.ant-select {
margin-right: 0;
}
&.show-quick-jumper {
.ant-pagination-options-size-changer.ant-select {
margin-right: 8px;
}
}
}
// 更改 header 底色
.vxe-table.border--default .vxe-table--header-wrapper,
.vxe-table.border--full .vxe-table--header-wrapper,
.vxe-table.border--outer .vxe-table--header-wrapper {
background-color: #FFFFFF;
}
}
// 更改 tooltip 校验失败的颜色
.vxe-table--tooltip-wrapper.vxe-table--valid-error {
background-color: #f5222d !important;
}
// 更改 输入框 校验失败的颜色
.col--valid-error > .vxe-cell > .ant-input,
.col--valid-error > .vxe-cell > .ant-select .ant-input,
.col--valid-error > .vxe-cell > .ant-select .ant-select-selection,
.col--valid-error > .vxe-cell > .ant-input-number,
.col--valid-error > .vxe-cell > .ant-cascader-picker .ant-cascader-input,
.col--valid-error > .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input,
.col--valid-error > .vxe-tree-cell > .ant-input,
.col--valid-error > .vxe-tree-cell > .ant-select .ant-input,
.col--valid-error > .vxe-tree-cell > .ant-select .ant-select-selection,
.col--valid-error > .vxe-tree-cell > .ant-input-number,
.col--valid-error > .vxe-tree-cell > .ant-cascader-picker .ant-cascader-input,
.col--valid-error > .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input {
border-color: #f5222d !important;
}
// 拖拽排序列样式
.vxe-table .col--row-drag-sort .vxe-cell {
height: 100%;
}

View File

@ -0,0 +1,46 @@
.j-vxe-reload-effect-box {
&,
.j-vxe-reload-effect-span {
display: inline;
height: 100%;
position: relative;
}
.j-vxe-reload-effect-span {
&.layer-top {
display: inline-block;
width: 100%;
position: absolute;
z-index: 2;
background-color: white;
transform-origin: 0 0;
animation: reload-effect 1.5s forwards;
}
&.layer-bottom {
z-index: 1;
}
}
// 定义动画
@keyframes reload-effect {
0% {
opacity: 1;
transform: rotateX(0);
}
10% {
opacity: 1;
}
90% {
opacity: 0;
}
100% {
opacity: 0;
transform: rotateX(180deg);
}
}
}

View File

@ -0,0 +1,332 @@
.j-vxe-table-box {
@height: 24px;
@lineHeight: 1.5;
@spacing: 4px;
@fontSize: 14px;
@borderRadius: 2px;
&.size--tiny {
.vxe-table--header .vxe-cell--checkbox {
position: relative;
top: 2px;
right: 1px;
}
.vxe-table--body .vxe-cell--checkbox {
line-height: 2;
}
.vxe-cell {
padding: 0 5px;
font-size: @fontSize;
line-height: @lineHeight;
}
.vxe-table .vxe-header--column .vxe-cell {
font-size: 12px;
}
.vxe-body--column.col--actived {
padding: 0;
.vxe-cell {
padding: 0;
}
}
// ant输入框
.ant-input,
// ant下拉框
.ant-select-selection {
padding: 2px @spacing;
height: @height;
font-size: @fontSize;
border-radius: @borderRadius;
line-height: @lineHeight;
}
// 输入框图标对齐
.ant-input-affix-wrapper {
& .ant-input-prefix {
left: 4px;
}
& .ant-input:not(:first-child) {
padding-left: 20px;
}
}
// 按钮 addon
.ant-input-group-addon {
border-color: transparent;
border-radius: @borderRadius;
}
// ant下拉多选框
.ant-select-selection--multiple {
min-height: @height;
& .ant-select-selection__rendered > ul > li {
height: calc(@height - 6px);
font-size: calc(@fontSize - 2px);
margin-top: 0;
line-height: @lineHeight;
padding: 0 18px 0 4px;
}
& .ant-select-selection__clear,
& .ant-select-arrow {
top: 12px;
}
}
// ant按钮
.ant-upload {
width: 100%;
.ant-btn {
width: 100%;
height: @height;
padding: 0 8px;
font-size: @fontSize;
border-color: transparent;
background-color: transparent;
border-radius: @borderRadius;
&:hover {
background-color: rgba(255, 255, 255, 0.3);
}
}
}
.ant-select-selection__rendered {
line-height: @lineHeight;
margin-left: 0;
}
// 工具栏
.j-vxe-toolbar {
margin-bottom: 4px;
.ant-form-item-label,
.ant-form-item-control {
line-height: 22px;
}
.ant-form-inline .ant-form-item {
margin-right: 4px;
}
}
}
/** 内置属性 */
.vxe-table.size--tiny {
& .vxe-table--expanded {
padding-right: 0;
}
& .vxe-body--expanded-cell {
padding: 8px;
}
}
.size--tiny .vxe-loading .vxe-loading--spinner {
width: 38px;
height: 38px
}
.vxe-table.size--tiny .vxe-body--column.col--ellipsis,
.vxe-table.size--tiny .vxe-footer--column.col--ellipsis,
.vxe-table.size--tiny .vxe-header--column.col--ellipsis,
.vxe-table.vxe-editable.size--tiny .vxe-body--column {
height: @height;
}
.vxe-table.size--tiny {
font-size: 12px
}
.vxe-table.size--tiny .vxe-table--empty-block,
.vxe-table.size--tiny .vxe-table--empty-placeholder {
min-height: @height;
}
.vxe-table.size--tiny .vxe-body--column:not(.col--ellipsis),
.vxe-table.size--tiny .vxe-footer--column:not(.col--ellipsis),
.vxe-table.size--tiny .vxe-header--column:not(.col--ellipsis) {
padding: 4px 0
}
.vxe-table.size--tiny .vxe-cell .vxe-default-input,
.vxe-table.size--tiny .vxe-cell .vxe-default-select,
.vxe-table.size--tiny .vxe-cell .vxe-default-textarea {
height: @height;
}
.vxe-table.size--tiny .vxe-cell .vxe-default-input[type=date]::-webkit-inner-spin-button {
margin-top: 1px
}
.vxe-table.size--tiny.virtual--x .col--ellipsis .vxe-cell,
.vxe-table.size--tiny.virtual--y .col--ellipsis .vxe-cell,
.vxe-table.size--tiny .vxe-body--column.col--ellipsis .vxe-cell,
.vxe-table.size--tiny .vxe-footer--column.col--ellipsis .vxe-cell,
.vxe-table.size--tiny .vxe-header--column.col--ellipsis .vxe-cell {
max-height: @height;
}
.vxe-table.size--tiny .vxe-cell--checkbox .vxe-checkbox--icon,
.vxe-table.size--tiny .vxe-cell--radio .vxe-radio--icon {
font-size: 14px
}
.vxe-table.size--tiny .vxe-table--filter-option > .vxe-checkbox--icon,
.vxe-table.size--small .vxe-table--filter-option > .vxe-checkbox--icon {
font-size: 14px
}
.vxe-modal--wrapper.size--tiny .vxe-export--panel-column-option > .vxe-checkbox--icon,
.vxe-modal--wrapper.size--small .vxe-export--panel-column-option > .vxe-checkbox--icon {
font-size: 14px
}
.vxe-grid.size--tiny {
font-size: 12px
}
.vxe-toolbar.size--tiny {
font-size: 12px;
height: 46px
}
.vxe-toolbar.size--tiny .vxe-custom--option > .vxe-checkbox--icon {
font-size: 14px
}
.vxe-pager.size--tiny {
font-size: 12px;
height: @height;
}
.vxe-checkbox.size--tiny {
font-size: 12px
}
.vxe-checkbox.size--tiny .vxe-checkbox--icon {
font-size: 14px
}
.vxe-radio-button.size--tiny .vxe-radio--label {
line-height: 26px
}
.vxe-radio.size--tiny {
font-size: 12px
}
.vxe-radio.size--tiny .vxe-radio--icon {
font-size: 14px
}
.vxe-input.size--tiny {
font-size: 12px;
height: @height;
}
.vxe-input.size--tiny .vxe-input--inner[type=date]::-webkit-inner-spin-button,
.vxe-input.size--tiny .vxe-input--inner[type=month]::-webkit-inner-spin-button,
.vxe-input.size--tiny .vxe-input--inner[type=week]::-webkit-inner-spin-button {
margin-top: 0
}
.vxe-dropdown--panel.size--tiny {
font-size: 12px
}
.vxe-textarea--autosize.size--tiny,
.vxe-textarea.size--tiny {
font-size: 12px
}
.vxe-textarea.size--tiny:not(.is--autosize) {
min-height: @height;
}
.vxe-button.size--tiny {
font-size: 12px
}
.vxe-button.size--tiny.type--button {
height: @height;
}
.vxe-button.size--tiny.type--button.is--circle {
min-width: @height;
}
.vxe-button.size--tiny.type--button.is--round {
border-radius: 14px
}
.vxe-button.size--tiny .vxe-button--icon,
.vxe-button.size--tiny .vxe-button--loading-icon {
min-width: 12px
}
.vxe-modal--wrapper.size--tiny {
font-size: 12px
}
.vxe-form.size--tiny {
font-size: 12px
}
.vxe-form.size--tiny .vxe-form--item-inner {
min-height: 30px
}
.vxe-form.size--tiny .vxe-default-input[type=reset],
.vxe-form.size--tiny .vxe-default-input[type=submit] {
line-height: 26px
}
.vxe-form.size--tiny .vxe-default-input,
.vxe-form.size--tiny .vxe-default-select {
height: @height;
}
.vxe-select--panel.size--tiny,
.vxe-select.size--tiny {
font-size: 12px
}
.vxe-select--panel.size--tiny .vxe-optgroup--title,
.vxe-select--panel.size--tiny .vxe-select-option {
height: 24px;
line-height: 24px
}
.vxe-switch.size--tiny {
font-size: 12px
}
.vxe-pulldown--panel.size--tiny,
.vxe-pulldown.size--tiny {
font-size: 12px
}
}

View File

@ -0,0 +1,303 @@
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { filterDictText } from '@/components/dict/JDictSelectUtil'
import { getEnhancedMixins, JVXERenderType, replaceProps } from '@/components/jeecg/JVxeTable/utils/cellUtils'
// noinspection JSUnusedLocalSymbols
export default {
inject: {
getParentContainer: {default: () => ((node) => node.parentNode)},
},
props: {
value: PropTypes.any,
row: PropTypes.object,
column: PropTypes.object,
// 组件参数
params: PropTypes.object,
// 渲染选项
renderOptions: PropTypes.object,
// 渲染类型
renderType: PropTypes.string.def('default'),
},
data() {
return {
innerValue: null,
}
},
computed: {
caseId() {
return this.renderOptions.caseId
},
originColumn() {
return this.column.own
},
$type() {
return this.originColumn.$type
},
rows() {
return this.params.data
},
rowIndex() {
return this.params.rowIndex
},
columnIndex() {
return this.params.columnIndex
},
cellProps() {
let {originColumn: col, renderOptions} = this
let props = {}
// 输入占位符
props['placeholder'] = replaceProps(col, col.placeholder)
// 解析props
if (typeof col.props === 'object') {
Object.keys(col.props).forEach(key => {
props[key] = replaceProps(col, col.props[key])
})
}
// 判断是否是禁用的列
props['disabled'] = (typeof col['disabled'] === 'boolean' ? col['disabled'] : props['disabled'])
// TODO 判断是否是禁用的行
// if (props['disabled'] !== true) {
// props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
// }
// 判断是否禁用所有组件
if (renderOptions.disabled === true) {
props['disabled'] = true
}
return props
},
},
watch: {
$type: {
immediate: true,
handler($type) {
this.enhanced = getEnhancedMixins($type)
this.listeners = getListeners.call(this)
},
},
value: {
immediate: true,
handler(val) {
let value = val
// 验证值格式
let originValue = this.row[this.column.property]
let getValue = this.enhanced.getValue.call(this, originValue)
if (originValue !== getValue) {
// 值格式不正确,重新赋值
value = getValue
vModel.call(this, value)
}
this.innerValue = this.enhanced.setValue.call(this, value)
// 判断是否启用翻译
if (this.renderType === JVXERenderType.spaner && this.enhanced.translate.enabled) {
this.innerValue = this.enhanced.translate.handler.call(this, value)
}
},
},
},
created() {
},
methods: {
/** 通用处理change事件 */
handleChangeCommon(value) {
let handle = this.enhanced.getValue.call(this, value)
this.trigger('change', {value: handle})
// 触发valueChange事件
this.parentTrigger('valueChange', {
type: this.$type,
value: handle,
oldValue: this.value,
col: this.originColumn,
rowIndex: this.params.rowIndex,
columnIndex: this.params.columnIndex,
})
},
/** 通用处理blur事件 */
handleBlurCommon(value) {
this.trigger('blur', {value})
},
/**
* 如果事件存在的话,就触发
* @param name 事件名
* @param event 事件参数
* @param args 其他附带参数
*/
trigger(name, event, args = []) {
let listener = this.listeners[name]
if (typeof listener === 'function') {
if (typeof event === 'object') {
event = this.packageEvent(name, event)
}
listener(event, ...args)
}
},
parentTrigger(name, event, args = []) {
args.unshift(this.packageEvent(name, event))
this.trigger('trigger', name, args)
},
packageEvent(name, event = {}) {
event.row = this.row
event.column = this.column
event.cellTarget = this
if (!event.type) {
event.type = name
}
if (!event.cellType) {
event.cellType = this.$type
}
// 是否校验表单默认为true
if (typeof event.validate !== 'boolean') {
event.validate = true
}
return event
},
},
model: {
prop: 'value',
event: 'change'
},
/**
* 【自定义增强】用于实现一些增强事件
* 【注】这里只是定义接口,具体功能需要到各个组件内实现(也有部分功能实现)
* 【注】该属性不是Vue官方属性是JVxeTable组件自定义的
* 所以方法内的 this 指向并不是当前组件,而是方法自身,
* 也就是说并不能 this 打点调实例里的任何方法
*/
enhanced: {
// 注册参数详见https://xuliangzhan_admin.gitee.io/vxe-table/#/table/renderer/edit
installOptions: {
// 自动聚焦的 class 类名
autofocus: '',
},
// 事件拦截器(用于兼容)
interceptor: {
// 已实现event.clearActived
// 说明:比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
['event.clearActived'](params, event, target) {
return true
},
// 自定义event.clearActived.className
// 说明比原生的多了一个参数className用于判断点击的元素的样式名递归到顶层
['event.clearActived.className'](params, event, target) {
return true
},
},
// 【功能开关】
switches: {
// 是否使用 editRender 模式(仅当前组件,并非全局)
// 如果设为true则表头上方会出现一个可编辑的图标
editRender: true,
// false = 组件触发后可视true = 组件一直可视
visible: false,
},
// 【切面增强】切面事件处理,一般在某些方法执行后同步执行
aopEvents: {
// 单元格被激活编辑时会触发该事件
editActived() {
},
// 单元格编辑状态下被关闭时会触发该事件
editClosed() {
},
},
// 【翻译增强】可以实现例如select组件保存的value但是span模式下需要显示成text
translate: {
// 是否启用翻译
enabled: false,
/**
* 【翻译处理方法】如果handler留空则使用默认的翻译方法
* (this指向当前组件)
*
* @param value 需要翻译的值
* @returns{*} 返回翻译后的数据
*/
handler(value,) {
// 默认翻译方法
return filterDictText(this.column.own.options, value)
},
},
/**
* 【获取值增强】组件抛出的值
* (this指向当前组件)
*
* @param value 保存到数据库里的值
* @returns{*} 返回处理后的值
*/
getValue(value) {
return value
},
/**
* 【设置值增强】设置给组件的值
* (this指向当前组件)
*
* @param value 组件触发的值
* @returns{*} 返回处理后的值
*/
setValue(value) {
return value
},
/**
* 【新增行增强】在用户点击新增时触发的事件,返回新行的默认值
*
* @param row 行数据
* @param column 列配置,.own 是用户配置的参数
* @param $table vxe 实例
* @param renderOptions 渲染选项
* @param params 可以在这里获取 $table
*
* @returns 返回新值
*/
createValue({row, column, $table, renderOptions, params}) {
return column.own.defaultValue
},
}
}
function getListeners() {
let listeners = Object.assign({}, (this.renderOptions.listeners || {}))
if (!listeners.change) {
listeners.change = async (event) => {
vModel.call(this, event.value)
await this.$nextTick()
// 处理 change 事件相关逻辑(例如校验)
this.params.$table.updateStatus(this.params)
}
}
return listeners
}
export function vModel(value, row, property) {
if (!row) {
row = this.row
}
if (!property) {
property = this.column.property
}
this.$set(row, property, value)
}
/** 模拟触发事件 */
export function dispatchEvent({cell, $event}, className, handler) {
window.setTimeout(() => {
let element = cell.getElementsByClassName(className)
if (element && element.length > 0) {
if (typeof handler === 'function') {
handler(element[0])
} else {
// 模拟触发点击事件
element[0].dispatchEvent($event)
}
}
}, 10)
}

View File

@ -0,0 +1,264 @@
import store from '@/store/'
import { randomUUID } from '@/utils/util'
// vxe socket
const vs = {
// 页面唯一 id用于标识同一用户不同页面的websocket
pageId: randomUUID(),
// webSocket 对象
ws: null,
// 一些常量
constants: {
// 消息类型
TYPE: 'type',
// 消息数据
DATA: 'data',
// 消息类型:心跳检测
TYPE_HB: 'heart_beat',
// 消息类型:通用数据传递
TYPE_CSD: 'common_send_date',
// 消息类型更新vxe table数据
TYPE_UVT: 'update_vxe_table',
},
// 心跳检测
heartCheck: {
// 间隔时间,间隔多久发送一次心跳消息
interval: 10000,
// 心跳消息超时时间,心跳消息多久没有回复后重连
timeout: 6000,
timeoutTimer: null,
clear() {
clearTimeout(this.timeoutTimer)
return this
},
start() {
vs.sendMessage(vs.constants.TYPE_HB, '')
// 如果超过一定时间还没重置,说明后端主动断开了
this.timeoutTimer = window.setTimeout(() => {
vs.reconnect()
}, this.timeout)
return this
},
// 心跳消息返回
back() {
this.clear()
window.setTimeout(() => this.start(), this.interval)
},
},
/** 初始化 WebSocket */
initialWebSocket() {
if (this.ws === null) {
const userId = store.getters.userInfo.id
const domain = window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://')
const url = `${domain}/vxeSocket/${userId}/${this.pageId}`
this.ws = new WebSocket(url)
this.ws.onopen = this.on.open.bind(this)
this.ws.onerror = this.on.error.bind(this)
this.ws.onmessage = this.on.message.bind(this)
this.ws.onclose = this.on.close.bind(this)
console.log('this.ws: ', this.ws)
}
},
// 发送消息
sendMessage(type, message) {
try {
let ws = this.ws
if (ws != null && ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({
type: type,
data: message
}))
}
} catch (err) {
console.warn('VXEWebSocket发送消息失败(' + err.code + ')')
}
},
/** 绑定全局VXE表格 */
tableMap: new Map(),
CSDMap: new Map(),
/** 添加绑定 */
addBind(map, key, value) {
let binds = map.get(key)
if (Array.isArray(binds)) {
binds.push(value)
} else {
map.set(key, [value])
}
},
/** 移除绑定 */
removeBind(map, key, value) {
let binds = map.get(key)
if (Array.isArray(binds)) {
for (let i = 0; i < binds.length; i++) {
let bind = binds[i]
if (bind === value) {
binds.splice(i, 1)
break
}
}
if (binds.length === 0) {
map.delete(key)
}
} else {
map.delete(key)
}
},
// 呼叫绑定的表单
callBind(map, key, callback) {
let binds = map.get(key)
if (Array.isArray(binds)) {
binds.forEach(callback)
}
},
lockReconnect: false,
/** 尝试重连 */
reconnect() {
if (this.lockReconnect) return
this.lockReconnect = true
setTimeout(() => {
if (this.ws && this.ws.close) {
this.ws.close()
}
this.ws = null
console.info('VXEWebSocket尝试重连...')
this.initialWebSocket()
this.lockReconnect = false
}, 5000)
},
on: {
open() {
console.log('VXEWebSocket连接成功')
this.heartCheck.start()
},
error(e) {
console.warn('VXEWebSocket连接发生错误:', e)
this.reconnect()
},
message(e) {
// 解析消息
let json
try {
json = JSON.parse(e.data)
} catch (e) {
console.warn('VXEWebSocket收到无法解析的消息:', e.data)
return
}
let type = json[this.constants.TYPE]
let data = json[this.constants.DATA]
switch (type) {
// 心跳检测
case this.constants.TYPE_HB:
this.heartCheck.back()
break
// 通用数据传递
case this.constants.TYPE_CSD:
this.callBind(this.CSDMap, data.key, (fn) => fn.apply(this, data.args))
break
// 更新form数据
case this.constants.TYPE_UVT:
this.callBind(this.tableMap, data.socketKey, (vm) => this.onVM['onUpdateTable'].apply(vm, data.args))
break
default:
console.warn('VXEWebSocket收到不识别的消息类型:' + type)
break
}
},
close(e) {
console.log('VXEWebSocket连接被关闭:', e)
this.reconnect()
},
},
onVM: {
/** 收到更新表格的消息 */
onUpdateTable(row, caseId) {
// 判断是不是自己发的消息
if (this.caseId !== caseId) {
const tableRow = this.getIfRowById(row.id).row
// 局部保更新数据
if (tableRow) {
// 特殊处理拖轮状态
if (row['tug_status'] && tableRow['tug_status']) {
row['tug_status'] = Object.assign({}, tableRow['tug_status'], row['tug_status'])
}
// 判断是否启用重载特效
if (this.reloadEffect) {
this.$set(this.reloadEffectRowKeysMap, row.id, true)
}
Object.keys(row).forEach(key => {
if (key !== 'id') {
this.$set(tableRow, key, row[key])
}
})
this.$refs.vxe.reloadRow(tableRow)
}
}
},
},
}
export default {
props: {
// 是否开启使用 webSocket 无痕刷新
socketReload: {
type: Boolean,
default: false
},
socketKey: {
type: String,
default: 'vxe-default'
},
},
data() {
return {}
},
mounted() {
if (this.socketReload) {
vs.initialWebSocket()
vs.addBind(vs.tableMap, this.socketKey, this)
}
},
methods: {
/** 发送socket消息更新行 */
socketSendUpdateRow(row) {
vs.sendMessage(vs.constants.TYPE_UVT, {
socketKey: this.socketKey,
args: [row, this.caseId],
})
},
},
beforeDestroy() {
vs.removeBind(vs.tableMap, this.socketKey, this)
},
}
/**
* 添加WebSocket通用数据传递绑定相同的key可以添加多个方法绑定
* @param key 唯一key
* @param fn 当消息来的时候触发的回调方法
*/
export function addBindSocketCSD(key, fn) {
if (typeof fn === 'function') {
vs.addBind(vs.CSDMap, key, fn)
}
}
/**
* 移除WebSocket通用数据传递绑定
* @param key 唯一key
* @param fn 要移除的方法,必须和添加时的方法内存层面上保持一致才可以正确移除
*/
export function removeBindSocketCSD(key, fn) {
if (typeof fn === 'function') {
vs.removeBind(vs.CSDMap, key, fn)
}
}

View File

@ -0,0 +1,129 @@
import { AllCells, JVXETypes } from '@/components/jeecg/JVxeTable'
import JVxeCellMixins from '../mixins/JVxeCellMixins'
export const JVXERenderType = {
editer: 'editer',
spaner: 'spaner',
default: 'default',
}
/** 安装所有vxe组件 */
export function installAllCell(VXETable) {
// 遍历所有组件批量注册
Object.keys(AllCells).forEach(type => installOneCell(VXETable, type))
}
/** 安装单个vxe组件 */
export function installOneCell(VXETable, type) {
const switches = getEnhancedMixins(type, 'switches')
if (switches.editRender === false) {
installCellRender(VXETable, type, AllCells[type])
} else {
installEditRender(VXETable, type, AllCells[type])
}
}
/** 注册可编辑组件 */
export function installEditRender(VXETable, type, comp, spanComp) {
// 获取当前组件的增强
const enhanced = getEnhancedMixins(type)
// span 组件
if (!spanComp && AllCells[type + ':span']) {
spanComp = AllCells[type + ':span']
} else {
spanComp = AllCells[JVXETypes.normal]
}
// 添加渲染
VXETable.renderer.add(JVXETypes._prefix + type, {
// 可编辑模板
renderEdit: createRender(comp, enhanced, JVXERenderType.editer),
// 显示模板
renderCell: createRender(spanComp, enhanced, JVXERenderType.spaner),
// 增强注册
...enhanced.installOptions,
})
}
/** 注册普通组件 */
export function installCellRender(VXETable, type, comp = AllCells[JVXETypes.normal]) {
// 获取当前组件的增强
const enhanced = getEnhancedMixins(type)
VXETable.renderer.add(JVXETypes._prefix + type, {
// 默认显示模板
renderDefault: createRender(comp, enhanced, JVXERenderType.default),
// 增强注册
...enhanced.installOptions,
})
}
function createRender(comp, enhanced, renderType) {
return function (h, renderOptions, params) {
return [h(comp, {
props: {
value: params.row[params.column.property],
row: params.row,
column: params.column,
params: params,
renderOptions: renderOptions,
renderType: renderType,
}
})]
}
}
// 已混入的组件增强
const AllCellsMixins = new Map()
/** 获取某个组件的增强 */
export function getEnhanced(type) {
let cell = AllCells[type]
if (cell && cell.enhanced) {
return cell.enhanced
}
return null
}
/**
* 获取某个组件的增强(混入默认值)
*
* @param type JVXETypes
* @param name 可空,增强名称,留空返回所有增强
*/
export function getEnhancedMixins(type, name) {
const getByName = (e) => name ? e[name] : e
if (AllCellsMixins.has(type)) {
return getByName(AllCellsMixins.get(type))
}
let defEnhanced = JVxeCellMixins.enhanced
let enhanced = getEnhanced(type)
if (enhanced) {
Object.keys(defEnhanced).forEach(key => {
let def = defEnhanced[key]
if (enhanced.hasOwnProperty(key)) {
// 方法如果存在就不覆盖
if (typeof def !== 'function' && typeof def !== 'string') {
enhanced[key] = Object.assign({}, def, enhanced[key])
}
} else {
enhanced[key] = def
}
})
AllCellsMixins.set(type, enhanced)
return getByName(enhanced)
}
AllCellsMixins.set(type, defEnhanced)
return getByName(defEnhanced)
}
/** 辅助方法:替换${...}变量 */
export function replaceProps(col, value) {
if (value && typeof value === 'string') {
let text = value
text = text.replace(/\${title}/g, col.title)
text = text.replace(/\${key}/g, col.key)
text = text.replace(/\${defaultValue}/g, col.defaultValue)
return text
}
return value
}

View File

@ -0,0 +1,190 @@
import { getVmParentByName } from '@/utils/util'
import { JVXETypes } from '@comp/jeecg/JVxeTable/index'
export const VALIDATE_FAILED = Symbol()
/**
* 获取指定的 $refs 对象
* 有时候可能会遇到组件未挂载到页面中的情况,导致无法获取 $refs 中的某个对象
* 这个方法可以等待挂载完成之后再返回 $refs 的对象,避免报错
* @author sunjianlei
**/
export function getRefPromise(vm, name) {
return new Promise((resolve) => {
(function next() {
let ref = vm.$refs[name]
if (ref) {
resolve(ref)
} else {
setTimeout(() => {
next()
}, 10)
}
})()
})
}
/** 获取某一数字输入框列中的最大的值 */
export function getInputNumberMaxValue(col, rowsValues) {
let maxNum = 0
Object.values(rowsValues).forEach((rowValue, index) => {
let val = rowValue[col.key], num
try {
num = Number.parseFloat(val)
} catch {
num = 0
}
// 把首次循环的结果当成最大值
if (index === 0) {
maxNum = num
} else {
maxNum = (num > maxNum) ? num : maxNum
}
})
return maxNum
}
/**
*
* 根据 tagName 获取父级节点
*
* @param dom 一级dom节点
* @param tagName 标签名,不区分大小写
* @return {HTMLElement | NULL}
*/
export function getParentNodeByTagName(dom, tagName = 'body') {
if (tagName === 'body') {
return document.body
}
if (dom.parentNode) {
if (dom.parentNode.tagName.toLowerCase() === tagName.trim().toLowerCase()) {
return dom.parentNode
} else {
return getParentNodeByTagName(dom.parentNode, tagName)
}
} else {
return null
}
}
/**
* vxe columns 封装成高级查询可识别的选项
* @param columns
* @param handler 单独处理方法
*/
export function vxePackageToSuperQuery(columns, handler) {
if (Array.isArray(columns)) {
// 高级查询所需要的参数
let fieldList = []
// 遍历列
for (let i = 0; i < columns.length; i++) {
let col = columns[i]
if (col.type === JVXETypes.rowCheckbox ||
col.type === JVXETypes.rowRadio ||
col.type === JVXETypes.rowExpand ||
col.type === JVXETypes.rowNumber
) {
continue
}
let field = {
type: 'string',
value: col.key,
text: col.title,
dictCode: col.dictCode || col.dict,
}
if (col.type === JVXETypes.date || col.type === JVXETypes.datetime) {
field.type = col.type
field.format = col.format
}
if (col.type === JVXETypes.inputNumber) {
field.type = 'int'
}
if (Array.isArray(col.options)) {
field.options = col.options
}
if (typeof handler === 'function') {
Object.assign(field, handler(col, idx))
}
fieldList.push(field)
}
return fieldList
} else {
console.error('columns必须是一个数组')
}
return null
}
/**
* 一次性验证主表单和所有的次表单
* @param form 主表单 form 对象
* @param cases 接收一个数组每项都是一个JVxeTable实例
* @param autoJumpTab
* @returns {Promise<any>}
* @author sunjianlei
*/
export async function validateFormAndTables(form, cases, autoJumpTab) {
if (!(form && typeof form.validateFields === 'function')) {
throw `form 参数需要的是一个form对象而传入的却是${typeof form}`
}
let dataMap = {}
let values = await new Promise((resolve, reject) => {
// 验证主表表单
form.validateFields((err, values) => {
err ? reject({error: VALIDATE_FAILED, originError: err}) : resolve(values)
})
})
Object.assign(dataMap, {formValue: values})
// 验证所有子表的表单
let subData = await validateTables(cases, autoJumpTab)
// 合并最终数据
dataMap = Object.assign(dataMap, {tablesValue: subData})
return dataMap
}
/**
* 验证并获取一个或多个表格的所有值
*
* @param cases 接收一个数组每项都是一个JVxeTable实例
* @param autoJumpTab 校验失败后是否自动跳转tab选项
*/
export function validateTables(cases, autoJumpTab = true) {
if (!Array.isArray(cases)) {
throw `'validateTables'函数的'cases'参数需要的是一个数组而传入的却是${typeof cases}`
}
return new Promise((resolve, reject) => {
let tablesData = []
let index = 0
if (!cases || cases.length === 0) {
resolve()
}
(function next() {
let vm = cases[index]
vm.validateTable().then(errMap => {
// 校验通过
if (!errMap) {
tablesData[index] = vm.getAll()
// 判断校验是否全部完成,完成返回成功,否则继续进行下一步校验
if (++index === cases.length) {
resolve(tablesData)
} else (
next()
)
} else {
// 尝试获取tabKey如果在ATab组件内即可获取
let paneKey
let tabPane = getVmParentByName(vm, 'ATabPane')
if (tabPane) {
paneKey = tabPane.$vnode.key
// 自动跳转到该表格
if (autoJumpTab) {
let tabs = getVmParentByName(tabPane, 'Tabs')
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey)
}
}
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
reject({error: VALIDATE_FAILED, index, paneKey, errMap})
}
})
})()
})
}

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

@ -0,0 +1,246 @@
<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

@ -0,0 +1,142 @@
<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

@ -0,0 +1,325 @@
<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

@ -0,0 +1,319 @@
<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

@ -0,0 +1,24 @@
/**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

@ -0,0 +1,177 @@
// 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

@ -0,0 +1,305 @@
// 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

@ -0,0 +1,92 @@
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,61 @@
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'
export default {
install(Vue) {
Vue.component('JFormContainer', JFormContainer)
Vue.component('JPopup', JPopup)
Vue.component('JMarkdownEditor', JMarkdownEditor)
Vue.component(JModal.name, JModal)
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('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)
}
}

View File

@ -1,14 +1,14 @@
<template>
<div>
<a-modal
title="文件上传"
:title="fileType === 'image' ? '图片上传' : '文件上传'"
:width="width"
:visible="visible"
@ok="ok"
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>
@ -59,6 +59,11 @@
type:Boolean,
default:false,
required:false
},
number:{
type:Number,
required:false,
default: 0
}
},
data(){

View File

@ -11,7 +11,7 @@
<a-icon slot="suffix" type="fullscreen" @click.stop="pop" />
</a-input>
<div slot="content">
<textarea :value="inputContent" :disabled="disabled" @input="handleInputChange" :style="{ height: height + 'px', width: width + 'px' }"></textarea>
<a-textarea ref="textarea" :value="inputContent" :disabled="disabled" @input="handleInputChange" :style="{ height: height + 'px', width: width + 'px' }"/>
</div>
</a-popover>
</template>
@ -84,6 +84,9 @@
},
pop(){
this.visible=true
this.$nextTick(() => {
this.$refs.textarea.focus()
})
},
getPopupContainer(node){
if(!this.popContainer){

View File

@ -78,7 +78,7 @@
const MODAL_WIDTH = 1200;
export default {
name: 'JPopupOnlReport',
props: ['multi', 'code', 'groupId'],
props: ['multi', 'code', 'groupId', 'param'],
components:{
},
data(){
@ -121,16 +121,24 @@
},
cgRpConfigId:"",
modalWidth:MODAL_WIDTH,
tableScroll:{x:MODAL_WIDTH-100}
tableScroll:{x:MODAL_WIDTH-100},
dynamicParam:{}
}
},
mounted() {
this.loadColumnsInfo()
//this.loadColumnsInfo()
},
watch: {
code() {
this.loadColumnsInfo()
},
param:{
deep:true,
handler(){
this.dynamicParamHandler()
this.loadData();
},
}
},
computed:{
@ -162,7 +170,6 @@
}
this.table.columns = [...currColumns]
this.initQueryInfo()
this.loadData(1)
}
})
},
@ -176,12 +183,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,14 +246,44 @@
})
},
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.sorter);
param.pageNo = this.table.pagination.current;
param.pageSize = this.table.pagination.pageSize;
return filterObj(param);
},
handleChangeInTableSelect(selectedRowKeys, selectionRows) {
//update-begin-author:taoyan date:2020902 for:【issue】开源online的几个问题 LOWCOD-844
if(!selectedRowKeys || selectedRowKeys.length==0){
this.table.selectionRows = []
}else if(selectedRowKeys.length == selectionRows.length){
this.table.selectionRows = selectionRows
}else{
//当两者长度不一的时候 需要判断
let keys = this.table.selectedRowKeys
let rows = this.table.selectionRows;
//这个循环 添加新的记录
for(let i=0;i<selectionRows.length;i++){
let combineKey = this.combineRowKey(selectionRows[i])
if(keys.indexOf(combineKey)<0){
//如果 原来的key 不包含当前记录 push
rows.push(selectionRows[i])
}
}
//这个循环 移除取消选中的数据
this.table.selectionRows = rows.filter(item=>{
let combineKey = this.combineRowKey(item)
return selectedRowKeys.indexOf(combineKey)>=0
})
}
//update-end-author:taoyan date:2020902 for:【issue】开源online的几个问题 LOWCOD-844
this.table.selectedRowKeys = selectedRowKeys
this.table.selectionRows = selectionRows
},
handleChangeInTable(pagination, filters, sorter) {
//分页、排序、筛选变化时触发
@ -249,7 +317,8 @@
this.onClearSelected()
},
show(){
this.visible = true;
this.visible = true
this.loadColumnsInfo()
},
handleToggleSearch(){
this.toggleSearchStatus = !this.toggleSearchStatus;

View File

@ -12,25 +12,21 @@
<a-row :gutter="18">
<a-col :span="16">
<!-- 查询区域 -->
<div class="table-page-search-wrapper">
<a-form layout="inline">
<a-row :gutter="24">
<a-col :span="14">
<a-form-item :label="(queryParamText||name)">
<a-input v-model="queryParam[queryParamCode||valueKey]" :placeholder="'请输入' + (queryParamText||name)" @pressEnter="searchQuery"/>
</a-form-item>
</a-col>
<a-col :span="8">
<span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
<a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
</span>
</a-col>
</a-row>
</a-form>
</div>
<a-form layout="inline" class="j-inline-form">
<!-- 固定条件 -->
<a-form-item :label="(queryParamText||name)">
<a-input v-model="queryParam[queryParamCode||valueKey]" :placeholder="'请输入' + (queryParamText||name)" @pressEnter="searchQuery"/>
</a-form-item>
<!-- 动态生成的查询条件 -->
<j-select-biz-query-item v-if="queryConfig.length>0" v-show="showMoreQueryItems" :queryParam="queryParam" :queryConfig="queryConfig" @pressEnter="searchQuery"/>
<!-- 按钮 -->
<a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
<a v-if="queryConfig.length>0" @click="showMoreQueryItems=!showMoreQueryItems" style="margin-left: 8px">
{{ showMoreQueryItems ? '收起' : '展开' }}
<a-icon :type="showMoreQueryItems ? 'up' : 'down'"/>
</a>
</a-form>
<a-table
size="middle"
@ -67,11 +63,12 @@
import Ellipsis from '@/components/Ellipsis'
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
import { cloneObject, pushIfNotExist } from '@/utils/util'
import JSelectBizQueryItem from './JSelectBizQueryItem'
export default {
name: 'JSelectBizComponentModal',
mixins: [JeecgListMixin],
components: { Ellipsis },
components: {Ellipsis, JSelectBizQueryItem},
props: {
value: {
type: Array,
@ -127,6 +124,11 @@
type: String,
default: null
},
// 查询配置
queryConfig: {
type: Array,
default: () => []
},
rowKey: {
type: String,
default: 'id'
@ -169,6 +171,7 @@
},
options: [],
dataSourceMap: {},
showMoreQueryItems: false,
}
},
computed: {
@ -206,6 +209,11 @@
immediate: true,
deep: true,
handler(val) {
//update--begin--autor:scott-----date:20200927------for选取职务名称出现全选 #1753-----
if(this.innerValue){
this.innerValue.length=0;
}
//update--end--autor:scott-----date:20200927------for选取职务名称出现全选 #1753-----
this.selectedTable.dataSource = val.map(key => {
for (let data of this.dataSource) {
if (data[this.rowKey] === key) {
@ -299,11 +307,15 @@
this.$emit('input', value)
this.close()
},
/** 删除已选择的 */
handleDeleteSelected(record, index) {
this.selectedRowKeys.splice(this.selectedRowKeys.indexOf(record[this.rowKey]), 1)
this.selectedTable.dataSource.splice(index, 1)
//update--begin--autor:wangshuai-----date:20200722------forJSelectBizComponent组件切换页数值问题------
this.selectedTable.dataSource.splice(this.selectedTable.dataSource.indexOf(record), 1)
this.innerValue.splice(this.innerValue.indexOf(record[this.valueKey]), 1)
console.log("this.selectedRowKeys:",this.selectedRowKeys)
console.log("this.selectedTable.dataSource:",this.selectedTable.dataSource)
//update--begin--autor:wangshuai-----date:20200722------forJSelectBizComponent组件切换页数值问题------
},
customRowFn(record) {
@ -332,4 +344,29 @@
}
</script>
<style lang="less" scoped>
.full-form-item {
display: flex;
margin-right: 0;
/deep/ .ant-form-item-control-wrapper {
flex: 1 1;
display: inline-block;
}
}
.j-inline-form {
/deep/ .ant-form-item {
margin-bottom: 12px;
}
/deep/ .ant-form-item-label {
line-height: 32px;
width: auto;
}
/deep/ .ant-form-item-control {
height: 32px;
line-height: 32px;
}
}
</style>

View File

@ -0,0 +1,48 @@
export default {
name: 'JSelectBizQueryItem',
props: {
queryParam: Object,
queryConfig: Array,
},
data() {
return {}
},
methods: {
renderQueryItem() {
return this.queryConfig.map(queryItem => {
const {key, label, placeholder, dictCode, props, customRender} = queryItem
const options = {
props: {},
on: {
pressEnter: () => this.$emit('pressEnter'),
}
}
if (props != null) {
Object.assign(options.props, props)
}
if (placeholder === undefined) {
if (dictCode) {
options.props['placeholder'] = `请选择${label}`
} else {
options.props['placeholder'] = `请输入${label}`
}
} else {
options.props['placeholder'] = placeholder
}
let input
if (typeof customRender === 'function') {
input = customRender.call(this, {key, options, queryParam: this.queryParam})
} else if (dictCode) {
input = <j-dict-select-tag {...options} vModel={this.queryParam[key]} dictCode={dictCode} style="width:180px;"/>
} else {
input = <a-input {...options} vModel={this.queryParam[key]}/>
}
return <a-form-item key={key} label={label}>{input}</a-form-item>
})
},
},
render() {
return <span>{this.renderQueryItem()}</span>
},
}

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>

View File

@ -68,9 +68,11 @@
},
watch:{
value(val){
if (this.customReturnField === 'id') {
//update-begin-author:wangshuai date:20201124 for:组件 JSelectDepart.vue不是默认id时新内容编辑问题 gitee I247X2
// if (this.customReturnField === 'id') {
this.departIds = val
}
// }
//update-end-author:wangshuai date:20201124 for:组件 JSelectDepart.vue不是默认id时新内容编辑问题 gitee I247X2
}
},
methods:{

View File

@ -11,12 +11,19 @@
</template>
<script>
import JDate from '@comp/jeecg/JDate'
import JSelectBizComponent from './JSelectBizComponent'
export default {
name: 'JSelectMultiUser',
components: { JSelectBizComponent },
props: ['value'],
components: {JDate, JSelectBizComponent},
props: {
value: null, // any type
queryConfig: {
type: Array,
default: () => []
},
},
data() {
return {
url: { list: '/sys/user/list' },
@ -33,12 +40,32 @@
displayKey: 'realname',
returnKeys: ['id', 'username'],
queryParamText: '账号',
}
},
// 多条件查询配置
queryConfigDefault: [
{
key: 'sex',
label: '性别',
// 如果包含 dictCode那么就会显示成下拉框
dictCode: 'sex',
},
{
key: 'birthday',
label: '生日',
placeholder: '请选择出生日期',
// 如果想要使用局部注册的组件,就必须要使用箭头函数
customRender: ({key, queryParam, options}) => {
return <j-date {...options} vModel={queryParam[key]} style="width:180px;"/>
},
},
],
}
},
computed: {
attrs() {
return Object.assign(this.default, this.$attrs)
return Object.assign(this.default, this.$attrs, {
queryConfig: this.queryConfigDefault.concat(this.queryConfig)
})
}
}
}

View File

@ -12,6 +12,7 @@
<a-input-search style="margin-bottom: 1px" placeholder="请输入部门名称按回车进行搜索" @search="onSearch" />
<a-tree
checkable
class="my-dept-select-tree"
:treeData="treeData"
:checkStrictly="true"
@check="onCheck"
@ -237,6 +238,11 @@
</script>
<style scoped>
<style lang="less" scoped>
// 限制部门选择树高度,避免部门太多时点击确定不便
.my-dept-select-tree{
height: 350px;
overflow-y: scroll;
}
</style>

View File

@ -100,7 +100,7 @@
{
title: '部门',
align: 'center',
dataIndex: 'orgCode'
dataIndex: 'orgCodeTxt'
}
],
scrollTrigger: {},

View File

@ -1,6 +1,6 @@
<template>
<div class="main">
<keep-alive>
<keep-alive :include="includedComponents">
<router-view v-if="keepAlive" />
</keep-alive>
<router-view v-if="!keepAlive" />
@ -8,9 +8,32 @@
</template>
<script>
import Vue from 'vue'
import { CACHE_INCLUDED_ROUTES } from "@/store/mutation-types"
export default {
name: "RouteView",
computed: {
//update-begin--Author:scott Date:20201015 for路由缓存问题关闭了tab页时再打开就不刷新 #842
includedComponents() {
const includedRouters = Vue.ls.get(CACHE_INCLUDED_ROUTES)
console.log("includedRouters" + includedRouters)
//如果是缓存路由,则加入到 cache_included_routes
if (this.$route.meta.keepAlive && this.$route.meta.componentName) {
let cacheRouterArray = Vue.ls.get(CACHE_INCLUDED_ROUTES) || []
if(!cacheRouterArray.includes(this.$route.meta.componentName)){
cacheRouterArray.push(this.$route.meta.componentName)
// cacheRouterArray.push("OnlCgformHeadList")
console.log("Vue ls set componentName " + this.$route.meta.componentName)
Vue.ls.set(CACHE_INCLUDED_ROUTES, cacheRouterArray)
console.log("Vue ls includedRouterArrays " + Vue.ls.get(CACHE_INCLUDED_ROUTES))
return cacheRouterArray;
}
}
return includedRouters;
},
//update-end--Author:scott Date:20201015 for路由缓存问题关闭了tab页时再打开就不刷新 #842
keepAlive () {
return this.$route.meta.keepAlive
}

View File

@ -14,7 +14,7 @@
@change="changePage"
@tabClick="tabCallBack"
@edit="editPage">
<a-tab-pane :id="page.fullPath" :key="page.fullPath" v-for="page in pageList">
<a-tab-pane :id="page.fullPath" :key="page.fullPath" v-for="page in pageList" :closable="!(page.meta.title=='首页')">
<span slot="tab" :pagekey="page.fullPath">{{ page.meta.title }}</span>
</a-tab-pane>
</a-tabs>
@ -36,8 +36,9 @@
import Contextmenu from '@/components/menu/Contextmenu'
import { mixin, mixinDevice } from '@/utils/mixin.js'
import { triggerWindowResizeEvent } from '@/utils/util'
const indexKey = '/dashboard/analysis'
import Vue from 'vue'
import { CACHE_INCLUDED_ROUTES } from "@/store/mutation-types"
export default {
name: 'TabLayout',
@ -115,11 +116,11 @@
}else if (this.linkList.indexOf(newRoute.fullPath) < 0) {
this.linkList.push(newRoute.fullPath)
this.pageList.push(Object.assign({},newRoute))
// update-begin-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍
if (newRoute.meta.keepAlive) {
this.routeReload()
}
// update-end-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍
//// update-begin-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍 #842
// if (newRoute.meta.keepAlive) {
// this.routeReload()
// }
//// update-end-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍 #842
} else if (this.linkList.indexOf(newRoute.fullPath) >= 0) {
let oldIndex = this.linkList.indexOf(newRoute.fullPath)
let oldPositionRoute = this.pageList[oldIndex]
@ -132,8 +133,8 @@
// 【TESTA-523】修复不允许重复跳转路由异常
if (waitRouter.fullPath !== this.$route.fullPath) {
this.$router.push(Object.assign({}, waitRouter))
this.changeTitle(waitRouter.meta.title)
}
this.changeTitle(waitRouter.meta.title)
},
'multipage': function(newVal) {
if(this.reloadFlag){
@ -201,11 +202,27 @@
return
}
console.log("this.pageList ",this.pageList );
let removeRoute = this.pageList.filter(item => item.fullPath == key)
this.pageList = this.pageList.filter(item => item.fullPath !== key)
let index = this.linkList.indexOf(key)
this.linkList = this.linkList.filter(item => item !== key)
index = index >= this.linkList.length ? this.linkList.length - 1 : index
this.activePage = this.linkList[index]
//update-begin--Author:scott Date:20201015 for路由缓存问题关闭了tab页时再打开就不刷新 #842
//关闭页面则从缓存cache_included_routes中删除路由下次点击菜单会重新加载页面
let cacheRouterArray = Vue.ls.get(CACHE_INCLUDED_ROUTES) || []
if (removeRoute && removeRoute[0]) {
let componentName = removeRoute[0].meta.componentName
console.log("key: ", key);
console.log("componentName: ", componentName);
if(cacheRouterArray.includes(componentName)){
cacheRouterArray.splice(cacheRouterArray.findIndex(item => item === componentName), 1)
Vue.ls.set(CACHE_INCLUDED_ROUTES, cacheRouterArray)
}
}
//update-end--Author:scott Date:20201015 for路由缓存问题关闭了tab页时再打开就不刷新 #842
},
onContextmenu(e) {
const pagekey = this.getPageKey(e.target)
@ -308,8 +325,12 @@
this.$store.dispatch(ToggleMultipage,true)
this.reloadFlag = true
})
}
},
//update-end-author:taoyan date:20191008 for:路由刷新
//新增一个返回方法
excuteCallback(callback){
callback()
},
}
}
</script>
@ -397,4 +418,4 @@
}
</style>
</style>

View File

@ -0,0 +1,116 @@
/**
* 按需加载antd组件
*/
import Vue from 'vue'
// base library
import {
ConfigProvider,
Layout,
Input,
InputNumber,
Button,
Switch,
Radio,
Checkbox,
Select,
Card,
Form,
Row,
Col,
Modal,
Table,
Tabs,
Icon,
Badge,
Popover,
Dropdown,
List,
Avatar,
Breadcrumb,
Steps,
Spin,
Menu,
Drawer,
Tooltip,
Alert,
Tag,
Divider,
DatePicker,
TimePicker,
Upload,
Progress,
Skeleton,
Popconfirm,
PageHeader,
Result,
Statistic,
Descriptions,
message,
notification,
Empty,
Tree,
TreeSelect,
Carousel,
Pagination,
FormModel,
} from 'ant-design-vue'
import Viser from 'viser-vue'
Vue.use(ConfigProvider)
Vue.use(Layout)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Button)
Vue.use(Switch)
Vue.use(Radio)
Vue.use(Checkbox)
Vue.use(Select)
Vue.use(Card)
Vue.use(Form)
Vue.use(Row)
Vue.use(Col)
Vue.use(Modal)
Vue.use(Table)
Vue.use(Tabs)
Vue.use(Icon)
Vue.use(Badge)
Vue.use(Popover)
Vue.use(Dropdown)
Vue.use(List)
Vue.use(Avatar)
Vue.use(Breadcrumb)
Vue.use(Steps)
Vue.use(Spin)
Vue.use(Menu)
Vue.use(Drawer)
Vue.use(Tooltip)
Vue.use(Alert)
Vue.use(Tag)
Vue.use(Divider)
Vue.use(DatePicker)
Vue.use(TimePicker)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Skeleton)
Vue.use(Popconfirm)
Vue.use(PageHeader)
Vue.use(Result)
Vue.use(Statistic)
Vue.use(Descriptions)
Vue.use(Empty)
Vue.use(Tree)
Vue.use(TreeSelect)
Vue.use(Carousel)
Vue.use(Pagination)
Vue.use(FormModel)
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message
Vue.prototype.$notification = notification
Vue.prototype.$info = Modal.info
Vue.prototype.$success = Modal.success
Vue.prototype.$error = Modal.error
Vue.prototype.$warning = Modal.warning
process.env.NODE_ENV !== 'production' && console.warn('[jeecg-boot-vue] NOTICE: Antd use lazy-load.')

View File

@ -1,7 +1,7 @@
<template>
<a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
width="200px"
width="208px"
:collapsible="collapsible"
v-model="collapsed"
:trigger="null">
@ -136,12 +136,44 @@
background-color: #999999;
}
}
background-color: rgb(48, 65, 86);
/deep/ .ant-menu-submenu-title:hover{
background-color: #263445;
}
/deep/ .ant-menu-item:hover{
background-color: #263445;
}
/deep/ .ant-menu-item-selected {
background-color: #263445;
}
/deep/ .ant-menu-item-selected i{
color: rgb(24, 144, 255);
}
/deep/ .ant-menu-item-selected span{
color: rgb(24, 144, 255);
}
/deep/ .ant-menu-inline.ant-menu-sub{
background-color: #1f2d3d;
}
/deep/ .ant-menu-inline.ant-menu-sub li:hover{
background-color: #1f2d3d;
}
/deep/ .ant-menu-inline.ant-menu-sub .ant-menu-submenu-title:hover{
background-color: #1f2d3d;
}
/deep/ .ant-menu-inline.ant-menu-sub .ant-menu-item-selected{
background-color: #1f2d3d;
}
/deep/ .ant-menu-inline.ant-menu-sub .ant-menu-item-selected span{
color: rgb(24, 144, 255);
}
/deep/ .ant-menu-inline.ant-menu-sub .ant-menu-item-selected i{
color: rgb(24, 144, 255);
}
}
}
/* update_end author:sunjianlei date:20190509 for: 修改侧边导航栏滚动条的样式 */
</style>
<!-- update_begin author:sunjianlei date:20190530 for: 选中首页的时候不显示背景颜色 -->
@ -174,4 +206,4 @@
}
}
</style>
<!-- update_end author:sunjianlei date:20190530 for: 选中首页的时候不显示背景颜色 -->
<!-- update_end author:sunjianlei date:20190530 for: 选中首页的时候不显示背景颜色 -->

View File

@ -42,8 +42,6 @@
<user-menu class="header-index-right" :theme="theme" :style="topMenuStyle.headerIndexRight"/>
</div>
</div>
</a-layout-header>
</template>

View File

@ -13,6 +13,7 @@
>
<side-menu
mode="inline"
v-if="device === 'mobile'"
:menus="menus"
@menuSelect="menuSelect"
:theme="navTheme"
@ -21,7 +22,7 @@
</a-drawer>
<side-menu
v-else
v-show="device === 'desktop'"
mode="inline"
:menus="menus"
@menuSelect="myMenuSelect"
@ -343,7 +344,7 @@
}
.anticon {
color: white;
color: inherit;
}
}
}
@ -359,7 +360,7 @@
}
.anticon {
color: black;
color: inherit;
}
}
}
@ -527,8 +528,43 @@
margin: 0 auto;
width: 100%;
}
/deep/ .ant-menu-dark{
background-color: rgb(48, 65, 86);
/deep/ .ant-menu-submenu:hover{
background-color: #263445;
}
/deep/ .ant-menu-item:hover{
background-color: #263445;
}
}
/deep/ .ant-menu.ant-menu-dark .ant-menu-item-selected{
background-color: #263445;
}
/deep/ .ant-menu.ant-menu-dark .ant-menu-item-selected i{
color: rgb(24, 144, 255);
}
/deep/ .ant-menu.ant-menu-dark .ant-menu-item-selected span{
color: rgb(24, 144, 255);
}
/deep/ .ant-menu-dark .ant-menu-submenu-active{
color: #FFFFFF !important;
}
}
.dark.header-index-right{
background-color: rgb(48, 65, 86) !important;
}
.layout .top-nav-header-index.dark .user-wrapper .action:hover{
background-color: #263445 !important;
}
.layout .top-nav-header-index .dark .user-wrapper .action i{
color: #FFFFFF !important;
}
.layout .top-nav-header-index .user-wrapper .action .anticon{
color: inherit !important;
}
.dark.ant-dropdown-menu{
background-color: #999999;
}
// drawer-sider 自定义
.ant-drawer.drawer-sider {
.sider {
@ -679,4 +715,22 @@
}
}
}
</style>
.ant-menu-dark .ant-menu-vertical.ant-menu-sub li:hover{
background-color: #001528;
}
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected{
background-color: #001528 !important;
}
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected span{
color: rgb(24, 144, 255);
}
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected i{
color: rgb(24, 144, 255);
}
.ant-menu-dark .ant-menu-sub{
background: #1f2d3d !important;
.ant-menu-submenu-open,.ant-menu-submenu-active{
color: #FFFFFF !important;
}
}
</style>

View File

@ -87,7 +87,7 @@
</div>
<div :style="{ marginTop: '24px' }">
<a-list :split="false">
<a-list-item>
<a-list-item slot="renderItem" slot-scope="item, index">
<a-tooltip slot="actions">
<template slot="title">
该设定仅 [顶部栏导航] 时有效
@ -101,19 +101,19 @@
<div slot="title">内容区域宽度</div>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item slot="renderItem" slot-scope="item, index">
<a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
<a-list-item-meta>
<div slot="title">固定 Header</div>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item slot="renderItem" slot-scope="item, index">
<a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
<a-list-item-meta>
<div slot="title" :style="{ textDecoration: !fixedHeader ? 'line-through' : 'unset' }">下滑时隐藏 Header</div>
</a-list-item-meta>
</a-list-item>
<a-list-item >
<a-list-item slot="renderItem" slot-scope="item, index">
<a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :checked="dataFixSiderbar" @change="handleFixSiderbar" />
<a-list-item-meta>
<div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
@ -128,13 +128,13 @@
<h3 class="setting-drawer-index-title">其他设置</h3>
<div>
<a-list :split="false">
<a-list-item>
<a-list-item slot="renderItem" slot-scope="item, index">
<a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
<a-list-item-meta>
<div slot="title">色弱模式</div>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item slot="renderItem" slot-scope="item, index">
<a-switch slot="actions" size="small" :defaultChecked="multipage" @change="onMultipageWeak" />
<a-list-item-meta>
<div slot="title">多页签模式</div>
@ -327,4 +327,4 @@
font-size: 20px;
}
}
</style>
</style>

View File

@ -112,8 +112,9 @@ export default {
pageSize: (pagination && pagination.pageSize) ||
this.localPagination.pageSize
});
!r.totalCount && ['auto', false].includes(this.showPagination) && (this.localPagination = false)
//update--begin--autor:wangshuai-----date:20200724------for判断showPagination是否为false------
(!this.showPagination || !r.totalCount && this.showPagination === 'auto') && (this.localPagination = false)
//update--end--autor:wangshuai-----date:20200724------for判断showPagination是否为false-----
this.localDataSource = r.data; // 返回结果中的数组数据
this.localLoading = false
});

View File

@ -133,6 +133,15 @@
this.searchMenus(lists,this.permissionMenuList)
this.searchMenuOptions=[...lists]
},
mounted() {
//如果是单点登录模式
if (process.env.VUE_APP_SSO == 'true') {
let depart = this.userInfo().orgCode
if (!depart) {
this.updateCurrentDepart()
}
}
},
computed: {
...mapState({
// 后台菜单

View File

@ -88,7 +88,7 @@
methods: {
show(uname){
if(!uname){
this.$message.warning("当前系统无登用户!");
this.$message.warning("当前系统无登用户!");
return
}else{
this.username = uname

View File

@ -0,0 +1,10 @@
/** init domain config */
import Vue from 'vue'
//设置全局API_BASE_URL
Vue.prototype.API_BASE_URL = process.env.VUE_APP_API_BASE_URL
window._CONFIG['domianURL'] = Vue.prototype.API_BASE_URL
//单点登录地址
window._CONFIG['casPrefixUrl'] = process.env.VUE_APP_CAS_BASE_URL
window._CONFIG['onlinePreviewDomainURL'] = process.env.VUE_APP_ONLINE_BASE_URL
window._CONFIG['staticDomainURL'] = Vue.prototype.API_BASE_URL + '/sys/common/static'
window._CONFIG['pdfDomainURL'] = Vue.prototype.API_BASE_URL+ '/sys/common/pdf/pdfPreviewIframe'

View File

@ -308,17 +308,17 @@ export const constantRouterMap = [
{
path: 'register',
name: 'register',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Register')
component: () => import(/* webpackChunkName: "user" */ '@/views/user/register/Register')
},
{
path: 'register-result',
name: 'registerResult',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/RegisterResult')
component: () => import(/* webpackChunkName: "user" */ '@/views/user/register/RegisterResult')
},
{
path: 'alteration',
name: 'alteration',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Alteration')
component: () => import(/* webpackChunkName: "user" */ '@/views/user/alteration/Alteration')
},
]
},

View File

@ -0,0 +1,15 @@
// src/icons.js
// export what you need
export {
default as SmileOutline
} from '@ant-design/icons/lib/outline/SmileOutline';
export {
default as MehOutline
} from '@ant-design/icons/lib/outline/MehOutline';
// export what antd other components need
export {
default as CloseOutline
} from '@ant-design/icons/lib/outline/CloseOutline';
// and other icons...

View File

@ -1,12 +1,19 @@
/** init domain config */
import './config'
import Vue from 'vue'
import App from './App.vue'
import Storage from 'vue-ls'
import router from './router'
import store from './store/'
import { VueAxios } from "@/utils/request"
import Antd from 'ant-design-vue'
require('@jeecg/antd-online-mini')
require('@jeecg/antd-online-mini/dist/OnlineForm.css')
import Antd, { version } from 'ant-design-vue'
console.log('ant-design-vue version:', version)
import Viser from 'viser-vue'
import 'ant-design-vue/dist/antd.less'; // or 'ant-design-vue/dist/antd.less'
@ -16,10 +23,7 @@ import Print from 'vue-print-nb-jeecg'
/*import '@babel/polyfill'*/
import preview from 'vue-photo-preview'
import 'vue-photo-preview/dist/skin.css'
require('@jeecg/antd-online-mini')
require('@jeecg/antd-online-mini/dist/OnlineForm.css')
import SSO from '@/cas/sso.js'
import {
ACCESS_TOKEN,
DEFAULT_COLOR,
@ -41,6 +45,8 @@ import vueBus from '@/utils/vueBus';
import JeecgComponents from '@/components/jeecg/index'
import '@/assets/less/JAreaLinkage.less'
import VueAreaLinkage from 'vue-area-linkage'
import '@/components/jeecg/JVxeTable/install'
import '@/components/JVxeCells/install'
Vue.config.productionTip = false
Vue.use(Storage, config.storageOptions)
@ -55,21 +61,26 @@ Vue.use(vueBus);
Vue.use(JeecgComponents);
Vue.use(VueAreaLinkage);
new Vue({
router,
store,
mounted () {
store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))
store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
store.commit('SET_MULTI_PAGE',Vue.ls.get(DEFAULT_MULTI_PAGE,config.multipage))
},
render: h => h(App)
}).$mount('#app')
SSO.init(() => {
main()
})
function main() {
new Vue({
router,
store,
mounted () {
store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))
store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
store.commit('SET_MULTI_PAGE',Vue.ls.get(DEFAULT_MULTI_PAGE,config.multipage))
},
render: h => h(App)
}).$mount('#app')
}

View File

@ -48,18 +48,23 @@ export const JEditableTableMixin = {
/** 当点击新增按钮时调用此方法 */
add() {
if (typeof this.addBefore === 'function') this.addBefore()
// 默认新增空数据
let rowNum = this.addDefaultRowNum
if (typeof rowNum !== 'number') {
rowNum = 1
console.warn('由于你没有在 data 中定义 addDefaultRowNum addDefaultRowNum 不是数字所以默认添加一条空数据如果不想默认添加空数据请将定义 addDefaultRowNum 0')
}
this.eachAllTable((item) => {
item.add(rowNum)
//update-begin-author:lvdandan date:20201113 for:LOWCOD-1049 JEditaTable,子表默认添加一条数据,addDefaultRowNum设置无效 #1930
return new Promise((resolve) => {
resolve();
}).then(() => {
// 默认新增空数据
let rowNum = this.addDefaultRowNum
if (typeof rowNum !== 'number') {
rowNum = 1
console.warn('由于你没有在 data 中定义 addDefaultRowNum addDefaultRowNum 不是数字所以默认添加一条空数据如果不想默认添加空数据请将定义 addDefaultRowNum 0')
}
this.eachAllTable((item) => {
item.add(rowNum)
})
if (typeof this.addAfter === 'function') this.addAfter(this.model)
this.edit({})
})
if (typeof this.addAfter === 'function') this.addAfter(this.model)
this.edit({})
//update-end-author:lvdandan date:20201113 for:LOWCOD-1049 JEditaTable,子表默认添加一条数据addDefaultRowNum设置无效 #1930
},
/** 当点击了编辑(修改)按钮时调用此方法 */
edit(record) {

View File

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

View File

@ -7,6 +7,8 @@ import { filterObj } from '@/utils/util';
import { deleteAction, getAction,downFile,getFileAccessHttpUrl } from '@/api/manage'
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import store from '@/store'
import {Modal} from 'ant-design-vue'
export const JeecgListMixin = {
data(){
@ -74,8 +76,13 @@ export const JeecgListMixin = {
this.loading = true;
getAction(this.url.list, params).then((res) => {
if (res.success) {
this.dataSource = res.result.records;
this.ipagination.total = res.result.total;
//update-begin---author:zhangyafei Date:20201118 for适配不分页的数据列表------------
this.dataSource = res.result.records||res.result;
if(res.result.total)
{
this.ipagination.total = res.result.total;
}
//update-end---author:zhangyafei Date:20201118 for适配不分页的数据列表------------
}
if(res.code===510){
this.$message.warning(res.message)
@ -233,7 +240,7 @@ export const JeecgListMixin = {
if(!fileName || typeof fileName != "string"){
fileName = "导出文件"
}
let param = {...this.queryParam};
let param = this.getQueryParams();
if(this.selectedRowKeys && this.selectedRowKeys.length>0){
param['selections'] = this.selectedRowKeys.join(",")
}
@ -286,7 +293,26 @@ export const JeecgListMixin = {
this.$message.error(`${info.file.name} ${info.file.response.message}.`);
}
} else if (info.file.status === 'error') {
this.$message.error(`文件上传失败: ${info.file.msg} `);
if (info.file.response.status === 500) {
let data = info.file.response
const token = Vue.ls.get(ACCESS_TOKEN)
if (token && data.message.includes("Token失效")) {
Modal.error({
title: '登录已过期',
content: '很抱歉登录已过期请重新登录',
okText: '重新登录',
mask: false,
onOk: () => {
store.dispatch('Logout').then(() => {
Vue.ls.remove(ACCESS_TOKEN)
window.location.reload();
})
}
})
}
} else {
this.$message.error(`文件上传失败: ${info.file.msg} `);
}
}
},
/* 图片预览 */

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