Compare commits

...

100 Commits

Author SHA1 Message Date
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
65d1e6a879 更新文档 2020-09-08 14:52:14 +08:00
e963603d22 Merge pull request #1336 from wcdsdy/master
修复路由传参的值传送到jinput框被前后各截取了一位
2020-08-28 13:51:09 +08:00
3b68f4d4d9 Merge pull request #1380 from HiJackXD/master
logback:控制台日志增加高亮及颜色
2020-08-28 11:30:49 +08:00
4fd30c495f 解决代码生成器模板几个问题:
1.主键换成IdType.ASSIGN_ID
2.生成代码,自定义实体名代码报错问题
3.新模式表单,流程参数个性化定义
2020-07-21 11:28:51 +08:00
37158397be 解决 issues/I1OAM9 2.2.1的ERP模板不可用 2020-07-20 17:48:00 +08:00
b4bdb0b3f7 解决 issues/I1N6Z1 online表单开发,勾上“是否排序”,页面无排序功能 2020-07-20 17:10:36 +08:00
cd6cbf8c26 解决 issues/I1NV8M 关于在线开发中的表单开发和报表配置的问题 2020-07-20 15:43:06 +08:00
bb5fe95d30 bug[代码生成器] 树代码生成器模板,实体写死了 2020-07-18 17:41:13 +08:00
1c63f74a6d Jeecg Boot 2.2.1 版本发布,基于SpringBoot的低代码平台 2020-07-13 16:43:23 +08:00
0094b17ec2 Jeecg Boot 2.2.1 版本发布,基于SpringBoot的低代码平台 2020-07-11 18:53:06 +08:00
80f377ecd6 Jeecg Boot 2.2.1 版本发布,基于SpringBoot的低代码平台 2020-07-11 18:51:41 +08:00
55ce5ce919 Jeecg Boot 2.2.1 版本发布,基于SpringBoot的低代码平台 2020-07-11 18:30:59 +08:00
a09fa134f8 Jeecg Boot 2.2.1 版本发布,低代码平台 2020-07-11 14:13:46 +08:00
f2854b29aa Jeecg Boot 2.2.1 版本发布,低代码平台 2020-07-11 14:10:54 +08:00
1ff06c2bc3 Jeecg Boot 2.2.1 版本发布,低代码平台 2020-07-11 13:55:30 +08:00
604ca8b312 Jeecg Boot 2.2.1 版本发布,低代码平台 2020-07-11 13:04:11 +08:00
e6bb5eb0a1 Jeecg Boot 2.2.1 版本发布,低代码平台 2020-07-11 12:59:31 +08:00
109a95a96b Jeecg Boot 2.2.1 版本发布,低代码平台 2020-07-11 12:54:57 +08:00
cb7ae9ca6f 低代码能力 2020-07-01 11:35:43 +08:00
e4b4898e27 JEECG BOOT 低代码开发平台 2020-07-01 11:34:17 +08:00
5066ef526d JEECG BOOT 低代码开发平台 2020-07-01 11:16:12 +08:00
088ff85dcc Jeecg-Boot 低代码开发平台 2020-07-01 09:22:25 +08:00
96cfb8a2b2 logback:控制台日志增加高亮及颜色 2020-06-24 09:44:53 +08:00
32ee9f040c 修复路由传参的值传送到jinput框被前后各截取了一位 2020-06-13 17:30:38 +08:00
dadf6a78a1 fastjson 新安全漏洞修复解决;
第三方登录bug处理;
IE兼容js本地化提高前端加载速度
2020-06-04 14:22:40 +08:00
8a8e92cda3 功能蓝图 2020-05-31 16:09:06 +08:00
4566e50dd2 Jeecg Boot 功能蓝图 2020-05-31 16:04:04 +08:00
01ce2e8dab mysql数据库表名和字段默认小写、启动国际化警告问题解决 2020-05-19 00:24:50 +08:00
dc804d9372 jeecgboot 2.2新功能说明 2020-05-15 11:15:45 +08:00
9c64b9a8ed jeecgboot 2.2新功能说明 2020-05-15 11:11:40 +08:00
d52979621f OnLine表单-二次新增数据报错【严重】 2020-05-13 17:52:51 +08:00
5ffc3e4d13 www.jeecg.com官网地址更新 2020-05-08 18:24:50 +08:00
ea7ac65814 2.2新版——10分钟快速入门(每日更新,请大家关注B站) 2020-05-08 10:16:22 +08:00
cdfa6f8ac6 降低前端搭建难度,暂时删除tui-editor markdown编辑器 2020-05-07 10:03:00 +08:00
6ce3a892b6 Jeecg-Boot 2.2.0 版本发布 | 重磅升级 2020-05-05 21:13:08 +08:00
28e9a0ddfb Jeecg-Boot 2.2.0 版本发布 | 重磅升级 2020-05-03 22:43:59 +08:00
1f27948814 Jeecg-Boot 2.2.0 版本发布 | 重磅升级 2020-05-03 17:49:49 +08:00
3bff16a446 Jeecg-Boot 2.2.0 版本发布 | 重磅升级 2020-05-03 15:43:49 +08:00
d5bfccdeb0 Jeecg-Boot 2.2.0 版本发布 | 重磅升级 2020-05-03 14:12:46 +08:00
9e046a07d4 Jeecg-Boot 2.2.0 版本发布 | 重磅升级 2020-05-03 12:43:53 +08:00
046831e700 浏览器支持说明 2020-03-31 11:20:24 +08:00
e7ad502390 提供oracle11g、sqlserver2017脚步 2020-03-12 18:47:42 +08:00
9f1a2b89b7 oracle11g 脚步提供 2020-03-12 18:37:51 +08:00
5878fcb12b 删除不需要的代码 2020-03-11 21:19:21 +08:00
27e280bd97 build后online表单无法添加/编辑问题处理 2020-03-09 10:54:08 +08:00
91594ddb1a 1.项目样式 scss 全部转换为 less,且移除了项目 node-sass 依赖;
2.online开发ButtonExpHandler is not defined #957;
3.关于打包后JS文件过大,导致加载缓慢的建议 #969
2020-03-04 22:37:17 +08:00
4c0c0945a1 修改新版上传组件,保存路径问题 2020-02-25 20:45:53 +08:00
2b3e157d88 Jeecg-Boot 2.1.4 版本发布 (版本问题修复) 2020-02-25 17:14:10 +08:00
e2d4bb29db Jeecg-Boot 2.1.4 版本发布 (升级ant-design-vue版本) 2020-02-25 16:37:20 +08:00
865ace6db0 Jeecg-Boot 2.1.4 版本发布 (新版问题修复) 2020-02-25 14:52:29 +08:00
7bc3931e1a Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 18:10:58 +08:00
94a945291d Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 17:55:42 +08:00
49a303d5e8 解决新版2.1.4 online表单插入行,添加报错问题 2020-02-24 17:48:23 +08:00
1cabc94cea Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 11:58:21 +08:00
760f8e9f46 Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 11:47:24 +08:00
38e6586c84 Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 03:24:28 +08:00
d7ca307fa2 Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 03:15:14 +08:00
188cec1646 Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 02:48:58 +08:00
4a4f236772 Jeecg-Boot 2.1.4 版本发布 | 重构较大,较多新功能 2020-02-24 02:44:53 +08:00
922 changed files with 249546 additions and 19095 deletions

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2019 <a href="http://www.jeecg.org">Jeecg Boot</a> All rights reserved.
Copyright (c) 2019 <a href="http://www.jeecg.com">Jeecg Boot</a> All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

169
README.md
View File

@ -1,18 +1,18 @@
![JEECG](https://static.oschina.net/uploads/img/201905/24164523_XDhg.png "JeecgBoot快速开发平台")
![JEECG](https://static.oschina.net/uploads/img/201905/24164523_XDhg.png "JeecgBoot低代码开发平台")
Jeecg-Boot 快速开发平台(前后端分离版本)
JEECG BOOT 低代码开发平台(前后端分离版本)
===============
当前最新版本: 2.1.3发布日期20191226
当前最新版本: 2.3发布日期2020-09-14
[![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-JEECG团队-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-2.1.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-2.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![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)
@ -21,85 +21,93 @@ Jeecg-Boot 快速开发平台(前后端分离版本)
项目介绍:
-----------------------------------
<h3 align="center">Java RAD framework for enterprise web applications</h3>
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
JeecgBoot 是一款基于代码生成器的J2EE快速开发平台采用前后端分离架构SpringBoot2.xAnt Design&VueMybatis-plusShiroJWT。强大的代码生成器让前后端代码一键生成无需写任何代码! JeecgBoot引领新的开发模式(Online Coding模式-> 代码生成器模式-> 手工MERGE智能开发) 帮助解决Java项目70%的重复工作让开发更多关注业务逻辑。既能快速提高开发效率帮助公司节省成本同时又不失灵活性JeecgBoot还独创在线开发模式No代码概念在线表单配置表单设计器、移动配置能力、工作流配置在线设计流程、报表配置能力、在线图表配置、插件能力可插拔等等
JeecgBoot 是一款基于代码生成器的`低代码`开发平台,零代码开发采用前后端分离架构SpringBoot2.xAnt Design&VueMybatis-plusShiroJWT。强大的代码生成器让前后端代码一键生成无需写任何代码! JeecgBoot引领新的开发模式(Online Coding模式-> 代码生成器模式-> 手工MERGE智能开发) 帮助解决Java项目70%的重复工作让开发更多关注业务逻辑。既能快速提高开发效率帮助公司节省成本同时又不失灵活性JeecgBoot还独创在线开发模式No代码概念在线表单配置表单设计器、移动配置能力、工作流配置在线设计流程、报表配置能力、在线图表配置、插件能力可插拔等等
JEECG宗旨是: 简单功能由Online Coding配置实现在线配置表单、在线配置报表、在线图表设计、在线设计流程、在线设计表单复杂功能由代码生成器生成进行手工Merge既保证了智能又兼顾了灵活;
`JEECG宗旨是:` 简单功能由Online Coding配置实现`零代码开发`在线配置表单、在线配置报表、在线图表设计、在线设计流程、在线设计表单复杂功能由代码生成器生成进行手工Merge既保证了`智能`又兼顾了`灵活`;
业务流程采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
适用项目
-----------------------------------
Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中尤其适合企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRM其半智能手工Merge的开发方式可以显著提高开发效率70%以上,极大降低开发成本。
Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中尤其适合SAAS项目、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRM其半智能手工Merge的开发方式可以显著提高开发效率70%以上,极大降低开发成本。
技术文档
-----------------------------------
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 技术官网: [http://www.jeecg.com](http://www.jeecg.com)
- 开发文档: [http://doc.jeecg.com/1273753](http://doc.jeecg.com/1273753)
- 开发文档: [http://doc.jeecg.com](http://doc.jeecg.com/1273753)
- 视频教程 [JeecgBoot入门系列视频](https://space.bilibili.com/454617261/channel/detail?cid=84186)
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 视频教程 [JeecgBoot入门视频教程](http://www.jeecg.com/doc/video)
- 常见问题: [入门常见问题大全](http://bbs.jeecg.com/forum.php?mod=viewthread&tid=7816&extra=page%3D1)
- 更新日志: [版本日志](http://www.jeecg.com/#/doc/changelog)
- 更新日志: [版本日志](http://www.jeecg.com/doc/log)
交流互动
-----------------------------------
- QQ交流群 ②769925425、①284271917
- QQ交流群 ③816531124、②769925425(满)、①284271917
- 反馈问题: [反馈问题请按格式发Issues](https://github.com/zhangdaiscott/jeecg-boot/issues/new)
- 参与开源: [欢迎加入JEECG开源团队共同进步](http://www.jeecg.com/#/doc/canyu-os)
- 参与开源: [欢迎加入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.封装完善的用户、角色、菜单、组织机构、数据字典、在线定时任务等基础功能,支持访问授权、按钮权限、数据权限等功能
* 5.常用共通封装,各种工具类(定时任务,短信接口,邮件发送,Excel导入导出等),基本满足80%项目需求
* 6.简易Excel导入导出支持单表导出和一对多表模式导出生成的代码自带导入导出功能
* 7.集成简易报表工具图像报表和数据导出非常方便可极其方便的生成图形报表、pdf、excel、word等报表
* 8.采用前后分离技术页面UI风格精美针对常用组件做了封装时间、行表格控件、截取显示控件、报表组件编辑器等等
* 9.查询过滤器查询功能自动生成后台动态拼SQL追加查询条件支持多种匹配方式全匹配/模糊查询/包含查询/不匹配查询);
* 10.数据权限(精细化数据权限控制,控制到行级,列表级,表单字段级,实现不同人看不同数据,不同人对同一个页面操作不同字段
* 11.在线配置报表(无需编码,通过在线配置方式,实现曲线图,柱状图,数据等报表)
* 12.页面校验自动生成(必须输入、数字校验、金额校验、时间空间等);
* 13.集成工作流activiti并实现了只需在页面配置流程转向可极大的简化bpm工作流的开发用bpm的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的java代码
* 14.在线流程设计采用开源Activiti流程引擎实现在线画流程,自定义表单,表单挂靠,业务流转
* 15.数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
* 16.提供单点登录CAS集成方案项目中已经提供完善的对接代码
* 17.表单设计器支持用户自定义表单布局支持单表一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件
* 18.专业接口对接机制统一采用restful接口方式集成swagger-ui在线接口文档Jwt token安全验证方便客户端对接
* 19.接口安全机制,可细化控制接口授权,非常简便实现不同客户端只看自己数据等控制
* 20.高级组合查询功能,在线配置支持主子表关联查询,可保存查询历史
* 21.提供各种系统监控,实时跟踪系统运行情况(监控 Redis、Tomcat、jvm、服务器信息、请求追踪、SQL监控
* 22.消息中心(支持短信、邮件、微信推送等等)
* 21.集成Websocket消息通知机制
* 22.提供APP发布方案国际化
* 23.支持多语言,提供国际化方案;
* 24.数据变更记录日志,可记录数据每次变更内容,通过版本对比功能查看历史变化
* 25.平台UI强大实现了移动自适应
* 26.平台首页风格,提供多种组合模式,支持自定义风格
* 27.提供简单易用的打印插件支持谷歌、IE浏览器等各种浏览器
* 28.示例代码丰富,提供很多学习案例参考
* 29.采用maven分模块开发方式
* 30.支持菜单动态路由
* 31.权限控制采用 RBACRole-Based Access Control基于角色的访问控制
* 1.采用最新主流前后分离框架Springboot+Mybatis+antd容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
* 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布局拥有更高的性能、更灵活的扩展、更强大的功能
@ -122,12 +130,14 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
#### 后端
- 基础框架Spring Boot 2.1.3.RELEASE
- 基础框架Spring Boot 2.1.3.RELEASE \ Spring Cloud Alibaba 2.1.0.RELEASE
- 持久层框架Mybatis-plus_3.1.2
- 安全框架Apache Shiro 1.4.0Jwt_3.7.0
- 微服务技术Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking
- 数据库连接池阿里巴巴Druid 1.1.10
- 缓存框架redis
@ -152,6 +162,7 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
### 功能模块
```
├─系统管理
@ -161,12 +172,13 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ ├─权限设置(支持按钮权限、数据权限)
│ ├─表单权限(控制字段禁用、隐藏)
│ ├─部门管理
│ ├─我的部门(二级管理员)
│ └─字典管理
│ └─分类字典
│ └─分类字典
│ └─系统公告
│ └─我的组织机构
│ └─职务管理
│ └─通讯录
│ └─多租户管理
├─消息中心
│ ├─消息管理
│ ├─模板管理
@ -179,6 +191,7 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ ├─Excel导入导出工具集成支持单表一对多 导入导出)
│ ├─平台移动自适应支持
├─系统监控
│ ├─Gateway路由网关
│ ├─性能扫描监控
│ │ ├─监控 Redis
│ │ ├─Tomcat
@ -210,6 +223,7 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│─常用示例
│ ├─自定义组件
│ ├─对象存储(对接阿里云)
│ ├─JVXETable示例各种复杂ERP布局示例
│ ├─单表模型例子
│ └─一对多模型例子
│ └─打印例子
@ -257,10 +271,11 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ ├─提供单点登录CAS集成方案
│ ├─提供APP发布方案
│ ├─集成Websocket消息通知机制
├─Online在线开发(暂未开源)
├─Online在线低代码开发(暂未开源)
│ ├─Online在线表单 - 功能已开放
│ ├─在线代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放
│ ├─多数据源管理
│ ├─Online在线图表
│ ├─Online图表模板配置
│ ├─高级表单设计器
@ -282,6 +297,44 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
```
## 微服务整体解决方案(2.3+版本)
1、服务注册和发现 Nacos √
2、统一配置中心 Nacos √
3、路由网关 gateway √
4、分布式 http feign √
5、熔断和降级 Sentinel √
6、分布式文件 Minio、阿里OSS √
7、统一权限控制 JWT + Shiro √
8、服务监控 SpringBootAdmin [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1761865)
9、链路跟踪 Skywarking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
10、消息中间件 SpringCloudStream+RabbitMQ [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1744409)
11、分布式任务 xxl-job [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1801263)
12、分布式事务 Seata
13、分布式日志 elk + kafa
14、支持 docker-compose、k8s、jenkins
15、CAS 单点登录 √
#### 微服务架构图
![微服务架构图](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/jeecgboot-weifuwu-cloud.png "在这里输入图片标题")
### Jeecg Boot 产品功能蓝图
![功能蓝图](https://static.jeecg.com/upload/test/Jeecg-Boot-lantu202005_1590912449914.jpg "在这里输入图片标题")
后台开发环境和依赖
@ -413,15 +466,15 @@ yarn run lint
附属文档
----
- [Ant Design Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn)
- [Ant Design Vue](https://www.antdv.com/docs/vue/introduce-cn)
- [报表 viser-vue](https://viserjs.github.io/demo.html#/viser/bar/basic-bar)
- [报表 viser-vue](https://viserjs.github.io/demo.html#/viser/line/basic-line)
- [Vue](https://cn.vuejs.org/v2/guide)
- [路由/菜单说明](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/router/README.md)
- [路由/菜单说明](https://gitee.com/jeecg/jeecg-boot/tree/v1.1/ant-design-jeecg-vue/src/router/README.md)
- [ANTD 默认配置项](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/defaultSettings.js)
- [ANTD 默认配置项](https://gitee.com/jeecg/jeecg-boot/blob/v1.1/ant-design-jeecg-vue/src/defaultSettings.js)
- 其他待补充...

View File

@ -0,0 +1,23 @@
docs
public
src
.dockerignore
.editorconfig
.eslintignore
.gitattributes
.gitignore
.prettierrc
babel.config.js
Dockerfile
idea.config.js
LICENSE
package.json
package-lock.json
README.md
vue.config.js
yarn
yarn.lock
yarn-error.log
.idea
.svn
node_modules

View File

@ -0,0 +1,29 @@
FROM nginx
MAINTAINER jeecgos@163.com
VOLUME /tmp
ENV LANG en_US.UTF-8
RUN echo "server { \
listen 80; \
location ^~ /jeecg-boot { \
proxy_pass http://jeecg-boot-system:8080/jeecg-boot/; \
proxy_set_header Host jeecg-boot-system; \
proxy_set_header X-Real-IP \$remote_addr; \
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; \
} \
#解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \
location / { \
root /var/www/html/; \
index index.html index.htm; \
if (!-e \$request_filename) { \
rewrite ^(.*)\$ /index.html?s=\$1 last; \
break; \
} \
} \
access_log /var/log/nginx/access.log ; \
} " > /etc/nginx/conf.d/default.conf \
&& mkdir -p /var/www \
&& mkdir -p /var/www/html
ADD dist/ /var/www/html/
EXPOSE 80
EXPOSE 443

View File

@ -1,13 +1,13 @@
Ant Design Jeecg Vue
====
当前最新版本: 2.1.3发布日期20191226
当前最新版本: 2.3.0发布日期20200914
Overview
----
基于 [Ant Design of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 实现的 Ant Design Pro Vue 版
Jeecg-boot 的前UI框架采用前后端分离框架,提供强大代码生成器的快速开发平台。
Jeecg-boot 的前UI框架采用前后端分离方案,提供强大代码生成器的快速开发平台。
前端页面代码和后端功能代码一键生成不需要写任何代码保持jeecg一贯的强大
@ -104,3 +104,32 @@ yarn run lint
----
> @vue/cli 升级后eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules
Docker 镜像使用
----
```
# 1.修改前端项目的后台域名
public/index.html
域名改成: http://jeecg-boot-system:8080/jeecg-boot
# 2.先进入打包前端项目
yarn run build
# 3.构建镜像
docker build -t nginx:jeecgboot .
# 4.启动镜像
docker run --name jeecg-boot-nginx -p 80:80 -d nginx:jeecgboot
# 5.配置host
# jeecgboot
127.0.0.1 jeecg-boot-redis
127.0.0.1 jeecg-boot-mysql
127.0.0.1 jeecg-boot-system
# 6.访问前台项目
http://localhost:80
```

View File

@ -1,5 +1,6 @@
module.exports = {
presets: [
'@vue/app'
['@vue/app',
{ useBuiltIns: 'entry' }]
]
}

18182
ant-design-vue-jeecg/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "vue-antd-jeecg",
"version": "2.1.3",
"version": "2.3.0",
"private": true,
"scripts": {
"pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
@ -9,14 +9,11 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@antv/data-set": "^0.10.2",
"@jeecg/antd-online-re": "2.1.3",
"@tinymce/tinymce-vue": "^2.0.0",
"ant-design-vue": "^1.4.0",
"apexcharts": "^3.6.5",
"ant-design-vue": "^1.6.3",
"@jeecg/antd-online-mini": "2.3.0",
"@antv/data-set": "^0.11.4",
"viser-vue": "^2.4.8",
"axios": "^0.18.0",
"clipboard": "^2.0.4",
"codemirror": "^5.46.0",
"dayjs": "^1.8.0",
"enquire.js": "^2.1.6",
"js-cookie": "^2.2.0",
@ -24,23 +21,29 @@
"lodash.pick": "^4.4.0",
"md5": "^2.2.1",
"nprogress": "^0.2.0",
"tinymce": "^5.0.2",
"viser-vue": "^2.4.4",
"vue": "^2.6.10",
"vue-apexcharts": "^1.3.2",
"vue-class-component": "^6.0.0",
"vue-cropper": "^0.4.8",
"vue-cropper": "^0.5.4",
"vue-i18n": "^8.7.0",
"vue-loader": "^15.7.0",
"vue-ls": "^3.2.0",
"vue-photo-preview": "^1.1.3",
"vue-print-nb-jeecg": "^1.0.9",
"vue-property-decorator": "^7.3.0",
"vue-router": "^3.0.1",
"vuex": "^3.1.0",
"vue-print-nb-jeecg": "^1.0.9",
"clipboard": "^2.0.4",
"vue-photo-preview": "^1.1.3",
"vue-splitpane": "^1.0.4",
"vuedraggable": "^2.20.0",
"vuex": "^3.0.1",
"vuex-class": "^0.3.1"
"codemirror": "^5.46.0",
"@tinymce/tinymce-vue": "^2.1.0",
"tinymce": "^5.3.2",
"@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,15 +51,15 @@
"@vue/cli-plugin-eslint": "^3.3.0",
"@vue/cli-service": "^3.3.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"babel-eslint": "7.2.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.1.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.0.1",
"vue-template-compiler": "^2.6.10",
"html-webpack-plugin": "^4.0.0-beta.11"
"html-webpack-plugin": "^4.2.0",
"compression-webpack-plugin": "^3.1.0",
"babel-plugin-import": "^1.13.0"
},
"eslintConfig": {
"root": true,
@ -65,7 +68,7 @@
},
"extends": [
"plugin:vue/strongly-recommended",
"eslint:recommended"
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
@ -93,7 +96,10 @@
"vue/no-use-v-if-with-v-for": 0,
"vue/html-closing-bracket-newline": 0,
"vue/no-parsing-error": 0,
"no-console": 0
"no-tabs": 0,
"indent": ["off", 2],
"no-console": 0,
"space-before-function-paren": 0
}
},
"postcss": {
@ -104,6 +110,6 @@
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
"not ie <= 10"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -7679,3 +7679,43 @@ font.medium {
font.weak {
color: #f5222d;
}
// 侧边导航栏首页颜色跟随主题变化
.ant-menu.ant-menu-root > .ant-menu-item:first-child.ant-menu-item-selected {
& > a,
& > a:hover {
color: @primary-color;
}
}
.ant-menu-submenu-selected {
color: @primary-color;
}
// begin -------- JAreaLinkage 三级联动样式 --------------
.cascader-menu-list .cascader-menu-option.hover,
.cascader-menu-list .cascader-menu-option:hover {
background-color: color(~`colorPalette("@{primary-color}", 1)`);
}
.area-selectable-list .area-select-option.hover {
background-color: color(~`colorPalette("@{primary-color}", 1)`);
}
.area-select:hover {
border-color: @primary-color;
}
.area-select:active {
box-shadow: 0 0 0 2px color(~`colorPalette("@{primary-color}", 1)`);
}
// end -------- JAreaLinkage 三级联动样式 --------------
// TESTA-521
.ant-menu-submenu-selected {
color: @primary-color;
}
// TESTA-521
.tab-layout-tabs.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active {
border-color: @primary-color !important;
}

View File

@ -5,9 +5,10 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Jeecg-Boot 快速开发平台</title>
<title>Jeecg-Boot 企业级快速开发平台</title>
<link rel="icon" href="<%= BASE_URL %>logo.png">
<script src="https://cdn.bootcss.com/babel-polyfill/7.6.0/polyfill.js"></script>
<script src="/cdn/babel-polyfill/polyfill_7_2_5.js"></script>
<style>
html,
body,
@ -244,8 +245,9 @@
window._CONFIG = {};
window._CONFIG['domianURL'] = 'http://127.0.0.1:8080/jeecg-boot';
window._CONFIG['casPrefixUrl'] = 'http://cas.example.org:8443/cas';
window._CONFIG['imgDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/view';
window._CONFIG['downloadUrl'] = window._CONFIG['domianURL'] + '/sys/common/download';
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>

View File

@ -1,9 +1,9 @@
<template>
<a-locale-provider :locale="locale">
<a-config-provider :locale="locale">
<div id="app">
<router-view/>
</div>
</a-locale-provider>
</a-config-provider>
</template>
<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'

View File

@ -0,0 +1,30 @@
import Vue from 'vue'
/**
* 将一个请求分组
*
* @param getPromise 传入一个可以获取到Promise对象的方法
* @param groupId 分组ID如果不传或者为空则不分组
* @param expire 过期时间,默认 半分钟
*/
export function httpGroupRequest(getPromise, groupId, expire = 1000 * 30) {
if (groupId == null || groupId === '') {
console.log("--------popup----------getFrom DB-------with---no--groupId ")
return getPromise()
}
if (Vue.ls.get(groupId)) {
console.log("---------popup--------getFrom Cache--------groupId = " + groupId)
return Promise.resolve(Vue.ls.get(groupId));
} else {
console.log("--------popup----------getFrom DB---------groupId = " + groupId)
}
// 还没有发出请求,就发出第一次的请求
return getPromise().then(res => {
Vue.ls.set(groupId, res, expire);
return Promise.resolve(res);
})
}

View File

@ -1,4 +1,6 @@
import { getAction,deleteAction,putAction,postAction} from '@/api/manage'
import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
import Vue from 'vue'
import {UI_CACHE_DB_DICT_DATA } from "@/store/mutation-types"
////根路径
// const doMian = "/jeecg-boot/";
@ -25,7 +27,7 @@ const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
//验证用户是否存在
const checkOnlyUser = (params)=>getAction("/sys/user/checkOnlyUser",params);
//改变密码
const changPassword = (params)=>putAction("/sys/user/changPassword",params);
const changePassword = (params)=>putAction("/sys/user/changePassword",params);
//权限管理
const addPermission= (params)=>postAction("/sys/permission/add",params);
@ -34,6 +36,7 @@ const getPermissionList = (params)=>getAction("/sys/permission/list",params);
/*update_begin author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
const getSystemMenuList = (params)=>getAction("/sys/permission/getSystemMenuList",params);
const getSystemSubmenu = (params)=>getAction("/sys/permission/getSystemSubmenu",params);
const getSystemSubmenuBatch = (params) => getAction('/sys/permission/getSystemSubmenuBatch', params)
/*update_end author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
// const deletePermission = (params)=>deleteAction("/sys/permission/delete",params);
@ -56,6 +59,14 @@ const queryParentName = (params)=>getAction("/sys/sysDepart/queryParentName",p
const searchByKeywords = (params)=>getAction("/sys/sysDepart/searchBy",params);
const deleteByDepartId = (params)=>deleteAction("/sys/sysDepart/delete",params);
//二级部门管理
const queryDepartPermission = (params)=>getAction("/sys/permission/queryDepartPermission",params);
const saveDepartPermission = (params)=>postAction("/sys/permission/saveDepartPermission",params);
const queryTreeListForDeptRole = (params)=>getAction("/sys/sysDepartPermission/queryTreeListForDeptRole",params);
const queryDeptRolePermission = (params)=>getAction("/sys/sysDepartPermission/queryDeptRolePermission",params);
const saveDeptRolePermission = (params)=>postAction("/sys/sysDepartPermission/saveDeptRolePermission",params);
const queryMyDepartTreeList = (params)=>getAction("/sys/sysDepart/queryMyDeptTreeList",params);
//日志管理
//const getLogList = (params)=>getAction("/sys/log/list",params);
const deleteLog = (params)=>deleteAction("/sys/log/delete",params);
@ -75,6 +86,14 @@ const editDictItem = (params)=>putAction("/sys/dictItem/edit",params);
//字典标签专用通过code获取字典数组
export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItems/${code}`,params);
//从缓存中获取字典配置
function getDictItemsFromCache(dictCode) {
if (Vue.ls.get(UI_CACHE_DB_DICT_DATA) && Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode]) {
let dictItems = Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode];
console.log("-----------getDictItemsFromCache----------dictCode="+dictCode+"---- dictItems=",dictItems)
return dictItems;
}
}
//系统通告
const doReleaseData = (params)=>getAction("/sys/annountCement/doReleaseData",params);
@ -89,11 +108,22 @@ const getVisitInfo = (params)=>getAction("/sys/visitInfo",params);
const queryUserByDepId = (params)=>getAction("/sys/user/queryUserByDepId",params);
// 查询用户角色表里的所有信息
const queryUserRoleMap = (params)=>getAction("/sys/user/queryUserRoleMap",params);
// const queryUserRoleMap = (params)=>getAction("/sys/user/queryUserRoleMap",params);
// 重复校验
const duplicateCheck = (params)=>getAction("/sys/duplicate/check",params);
// 加载分类字典
const loadCategoryData = (params)=>getAction("/sys/category/loadAllData",params);
const checkRuleByCode = (params) => getAction('/sys/checkRule/checkByCode', params)
//加载我的通告信息
const getUserNoticeInfo= (params)=>getAction("/sys/sysAnnouncementSend/getMyAnnouncementSend",params);
const getTransitURL = url => `/sys/common/transitRESTful?url=${encodeURIComponent(url)}`
// 中转HTTP请求
export const transitRESTful = {
get: (url, parameter) => getAction(getTransitURL(url), parameter),
post: (url, parameter) => postAction(getTransitURL(url), parameter),
put: (url, parameter) => putAction(getTransitURL(url), parameter),
http: (url, parameter) => httpAction(getTransitURL(url), parameter),
}
export {
// imgView,
@ -108,7 +138,7 @@ export {
queryall,
frozenBatch,
checkOnlyUser,
changPassword,
changePassword,
getPermissionList,
addPermission,
editPermission,
@ -137,12 +167,21 @@ export {
getLoginfo,
getVisitInfo,
queryUserByDepId,
queryUserRoleMap,
duplicateCheck,
queryTreeListForRole,
getSystemMenuList,
getSystemSubmenu,
loadCategoryData
getSystemSubmenuBatch,
loadCategoryData,
checkRuleByCode,
queryDepartPermission,
saveDepartPermission,
queryTreeListForDeptRole,
queryDeptRolePermission,
saveDeptRolePermission,
queryMyDepartTreeList,
getUserNoticeInfo,
getDictItemsFromCache
}

View File

@ -56,3 +56,18 @@ export function logout(logoutToken) {
}
})
}
/**
* 第三方登录
* @param token
* @returns {*}
*/
export function thirdLogin(token) {
return axios({
url: `/thirdLogin/getLoginUser/${token}`,
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}

View File

@ -1,3 +1,4 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
const api = {
@ -112,3 +113,69 @@ export function downFile(url,parameter){
})
}
/**
* 下载文件
* @param url 文件路径
* @param fileName 文件名
* @param parameter
* @returns {*}
*/
export function downloadFile(url, fileName, parameter) {
return downFile(url, parameter).then((data) => {
if (!data || data.size === 0) {
Vue.prototype['$message'].warning('文件下载失败')
return
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(new Blob([data]), fileName)
} else {
let url = window.URL.createObjectURL(new Blob([data]))
let link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link) //下载完成移除元素
window.URL.revokeObjectURL(url) //释放掉blob对象
}
})
}
/**
* 文件上传 用于富文本上传图片
* @param url
* @param parameter
* @returns {*}
*/
export function uploadAction(url,parameter){
return axios({
url: url,
data: parameter,
method:'post' ,
headers: {
'Content-Type': 'multipart/form-data', // 文件上传
},
})
}
/**
* 获取文件服务访问路径
* @param avatar
* @param subStr
* @returns {*}
*/
export function getFileAccessHttpUrl(avatar,subStr) {
if(!subStr) subStr = 'http'
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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,259 @@
.area-zoom-in-top-enter-active,
.area-zoom-in-top-leave-active {
opacity: 1;
transform: scaleY(1);
}
.area-zoom-in-top-enter,
.area-zoom-in-top-leave-active {
opacity: 0;
transform: scaleY(0);
}
.area-select {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
font-feature-settings: 'tnum';
position: relative;
outline: 0;
display: block;
background-color: #fff;
border: 1px solid #d9d9d9;
border-top-width: 1.02px;
border-radius: 4px;
outline: none;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.area-select-wrap .area-select {
display: inline-block;
}
.area-select * {
box-sizing: border-box;
}
.area-select:hover {
border-color: #40a9ff;
border-right-width: 1px !important;
outline: 0;
}
.area-select:active {
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.area-select.small {
width: 126px;
}
.area-select.medium {
width: 160px;
}
.area-select.large {
width: 194px;
}
.area-select.is-disabled {
background: #eceff5;
cursor: not-allowed;
}
.area-select.is-disabled:hover {
border-color: #e1e2e6;
}
.area-select.is-disabled .area-selected-trigger {
cursor: not-allowed;
}
.area-select .area-selected-trigger {
position: relative;
display: block;
font-size: 14px;
cursor: pointer;
margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
height: 100%;
padding: 8px 20px 7px 12px;
}
.area-select .area-select-icon {
position: absolute;
top: 50%;
margin-top: -2px;
right: 6px;
content: "";
width: 0;
height: 0;
border: 6px solid transparent;
border-top-color: rgba(0, 0, 0, 0.25);
transition: all .3s linear;
transform-origin: center;
}
.area-select .area-select-icon.active {
margin-top: -8px;
transform: rotate(180deg);
}
.area-selectable-list-wrap {
position: absolute;
width: 100%;
max-height: 275px;
z-index: 15000;
background-color: #fff;
box-sizing: border-box;
overflow-x: auto;
margin: 2px 0;
border-radius: 4px;
outline: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: opacity 0.15s, transform 0.3s !important;
transform-origin: center top !important;
}
.area-selectable-list {
position: relative;
margin: 0;
padding: 6px 0;
width: 100%;
font-size: 14px;
color: #565656;
list-style: none;
}
.area-selectable-list .area-select-option {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
padding: 0 15px 0 10px;
height: 32px;
line-height: 32px;
}
.area-selectable-list .area-select-option.hover {
background-color: #e6f7ff;
}
.area-selectable-list .area-select-option.selected {
color: rgba(0, 0, 0, 0.65);
font-weight: 600;
background-color: #efefef;
}
.cascader-menu-list-wrap {
position: absolute;
white-space: nowrap;
z-index: 15000;
background-color: #fff;
box-sizing: border-box;
overflow: hidden;
font-size: 0;
margin: 2px 0;
border-radius: 4px;
outline: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: opacity 0.15s, transform 0.3s !important;
transform-origin: center top !important;
}
.cascader-menu-list {
position: relative;
margin: 0;
font-size: 14px;
color: #565656;
padding: 6px 0;
list-style: none;
display: inline-block;
height: 204px;
overflow-x: hidden;
overflow-y: auto;
min-width: 160px;
vertical-align: top;
background-color: #fff;
border-right: 1px solid #e4e7ed;
}
.cascader-menu-list:last-child {
border-right: none;
}
.cascader-menu-list .cascader-menu-option {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
padding: 0 15px 0 10px;
height: 32px;
line-height: 32px;
}
.cascader-menu-list .cascader-menu-option.hover,
.cascader-menu-list .cascader-menu-option:hover {
background-color: #e6f7ff;
}
.cascader-menu-list .cascader-menu-option.selected {
color: rgba(0, 0, 0, 0.65);
font-weight: 600;
background-color: #efefef;
}
.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after {
position: absolute;
top: 50%;
margin-top: -4px;
right: 5px;
content: "";
width: 0;
height: 0;
border: 4px solid transparent;
border-left-color: #a1a4ad;
}
.cascader-menu-list::-webkit-scrollbar,
.area-selectable-list-wrap::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen,
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement,
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment,
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment,
.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen,
.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement,
.cascader-menu-list::-webkit-scrollbar-button:vertical:increment,
.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment {
display: none;
}
.cascader-menu-list::-webkit-scrollbar-thumb:vertical,
.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical {
background-color: #b8b8b8;
border-radius: 4px;
}
.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover,
.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover {
background-color: #777;
}

View File

@ -0,0 +1,15 @@
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td, th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}

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

@ -1,11 +1,18 @@
/*列表上方操作按钮区域*/
.ant-card-body .table-operator {
margin-bottom: 18px;
margin-bottom: 8px;
}
/** Button按钮间距 */
.table-operator .ant-btn {
margin-right: 6px
margin: 0 8px 8px 0;
}
.table-operator .ant-btn-group .ant-btn {
margin: 0;
}
.table-operator .ant-btn-group .ant-btn:last-child {
margin: 0 8px 8px 0;
}
/*列表td的padding设置 可以控制列表大小*/
.ant-table-tbody .ant-table-row td {
@ -45,3 +52,10 @@
/*erp风格子表外框padding设置*/
.ant-card-wider-padding.cust-erp-sub-tab>.ant-card-body{padding:5px 12px}
/* 内嵌子表背景颜色 */
.j-inner-table-wrapper /deep/ .ant-table-expanded-row .ant-table-wrapper .ant-table-tbody .ant-table-row {
background-color: #FFFFFF;
}
/**隐藏样式-modal确定按钮 */
.jee-hidden{display: none}

View File

@ -8,14 +8,14 @@ const init = (callback) => {
console.log("-------单点登录开始-------");
let token = Vue.ls.get(ACCESS_TOKEN);
let st = getUrlParam("ticket");
var sevice = "http://"+window.location.host+"/";
let sevice = "http://"+window.location.host+"/";
if(token){
loginSuccess(callback);
}else{
if(st){
validateSt(st,sevice,callback);
}else{
var serviceUrl = encodeURIComponent(sevice);
let serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
}
}
@ -26,14 +26,14 @@ const SSO = {
};
function getUrlParam(paraName) {
var url = document.location.toString();
var arrObj = url.split("?");
let url = document.location.toString();
let arrObj = url.split("?");
if (arrObj.length > 1) {
var arrPara = arrObj[1].split("&");
var arr;
let arrPara = arrObj[1].split("&");
let arr;
for (var i = 0; i < arrPara.length; i++) {
for (let i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split("=");
if (arr != null && arr[0] == paraName) {
@ -57,8 +57,8 @@ function validateSt(ticket,service,callback){
if(res.success){
loginSuccess(callback);
}else{
var sevice = "http://"+window.location.host+"/";
var serviceUrl = encodeURIComponent(sevice);
let sevice = "http://"+window.location.host+"/";
let serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
}
}).catch((err) => {

View File

@ -42,7 +42,7 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
.chart-card-header {
position: relative;
overflow: hidden;

View File

@ -1,31 +1,20 @@
<script>
import Tooltip from 'ant-design-vue/es/tooltip'
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/StringUtil'
/*
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
const TooltipOverlayStyle = {
overflowWrap: 'break-word',
wordWrap: 'break-word',
};
*/
export default {
name: 'Ellipsis',
components: {
Tooltip
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-ellipsis'
},
tooltip: {
type: Boolean
type: Boolean,
default: true,
},
length: {
type: Number,
required: true
default: 25,
},
lines: {
type: Number,
@ -36,28 +25,25 @@
default: false
}
},
methods: {
getStrDom (str) {
return (
<span>{ cutStrByFullLength(str, this.length) + '...' }</span>
)
},
getTooltip ( fullStr) {
return (
<Tooltip>
<template slot="title">{ fullStr }</template>
{ this.getStrDom(fullStr) }
</Tooltip>
)
}
},
render () {
methods: {},
render() {
const { tooltip, length } = this.$props
let str = this.$slots.default.map(vNode => vNode.text).join("")
const strDom = tooltip && getStrFullLength(str) > length ? this.getTooltip(str) : this.getStrDom(str);
let text = ''
// 处理没有default插槽时的特殊情况
if (this.$slots.default) {
text = this.$slots.default.map(vNode => vNode.text).join('')
}
// 判断是否显示 tooltip
if (tooltip && getStrFullLength(text) > length) {
return (
strDom
<a-tooltip>
<template slot="title">{text}</template>
<span>{cutStrByFullLength(text, this.length) + '…'}</span>
</a-tooltip>
)
} else {
return (<span>{text}</span>)
}
}
}
</script>

View File

@ -0,0 +1,205 @@
<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">
<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"/>
</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,
}
},
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() {
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,230 @@
<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">
<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"/>
</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,
}
},
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() {
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

@ -0,0 +1,79 @@
import { pcaa } from 'area-data'
/**
* 省市区
*/
export default class Area {
/**
* 构造器
* @param express
*/
constructor() {
let arr = []
const province = pcaa['86']
Object.keys(province).map(key=>{
arr.push({id:key, text:province[key], pid:'86'});
const city = pcaa[key];
Object.keys(city).map(key2=>{
arr.push({id:key2, text:city[key2], pid:key});
const qu = pcaa[key2];
Object.keys(qu).map(key3=>{
arr.push({id:key3, text:qu[key3], pid:key2});
})
})
})
this.all = arr;
}
get pca(){
return this.all;
}
getCode(text){
if(!text || text.length==0){
return ''
}
for(let item of this.all){
if(item.text === text){
return item.id;
}
}
}
getText(code){
if(!code || code.length==0){
return ''
}
let arr = []
this.getAreaBycode(code,arr);
return arr.join('/')
}
getRealCode(code){
let arr = []
this.getPcode(code, arr)
return arr;
}
getPcode(id, arr){
for(let item of this.all){
if(item.id === id){
arr.unshift(id)
if(item.pid != '86'){
this.getPcode(item.pid,arr)
}
}
}
}
getAreaBycode(code,arr){
//console.log("this.all.length",this.all)
for(let item of this.all){
if(item.id === code){
arr.unshift(item.text);
this.getAreaBycode(item.pid,arr)
}
}
}
}

View File

@ -83,6 +83,6 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
@import "chart";
</style>

View File

@ -1,7 +1,7 @@
<template>
<div :style="{ padding: '0 50px 32px 0' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding">
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding" :onClick="handleClick">
<v-tooltip/>
<v-legend/>
<v-axis/>
@ -12,9 +12,11 @@
</template>
<script>
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'BarMultid',
name: 'BarAndLine',
mixins: [ChartEventMixins],
props: {
title: {
type: String,

View File

@ -1,71 +1,91 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="254" :data="chartData" :padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-legend />
<v-bar position="x*y" color="type" :adjust="adjust" />
<v-chart :data="data" :height="height" :force-fit="true" :scale="scale" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
<v-bar position="x*y" color="type" :adjust="adjust"/>
</v-chart>
</div>
</template>
<script>
import { DataSet } from '@antv/data-set'
import { ChartEventMixins } from './mixins/ChartMixins'
const sourceDataConst = [
{ type: 'Jeecg', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, 'May': 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
{ type: 'Jeebt', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, 'May': 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 },
];
const fieldsConst = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.'];
export default {
name: 'BarMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
sourceData:{
type:Array,
default:()=>[]
dataSource: {
type: Array,
default: () => [
{ type: 'Jeecg', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, 'May': 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
{ type: 'Jeebt', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, 'May': 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 }
]
},
fields:{
type:Array,
default:()=>[]
fields: {
type: Array,
default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
},
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
aliases: {
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data() {
return {
chartData:"",
height: 400,
adjust: [{
type: 'dodge',
marginRatio: 1 / 32,
}],
};
},
watch: {
'sourceData': function () {
this.drawChart();
marginRatio: 1 / 32
}]
}
},
mounted(){
this.drawChart()
},
methods:{
drawChart(){
let temp = sourceDataConst;
if(this.sourceData && this.sourceData.length>0){
temp = this.sourceData
}
const dv = new DataSet.View().source(temp);
computed: {
data() {
const dv = new DataSet.View().source(this.dataSource)
dv.transform({
type: 'fold',
fields:(!this.fields||this.fields.length==0)?fieldsConst:this.fields,
fields: this.fields,
key: 'x',
value: 'y',
});
this.chartData=dv.rows;
value: 'y'
})
// bar 使用不了 - 和 / 所以替换下
let rows = dv.rows.map(row => {
if (typeof row.x === 'string') {
row.x = row.x.replace(/[-/]/g, '_')
}
return row
})
// 替换别名
rows.forEach(row => {
for (let item of this.aliases) {
if (item.field === row.type) {
row.type = item.alias
break
}
}
})
return rows
},
scale() {
return [
{
type: 'cat',
dataKey: 'x'
}
]
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale">
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
@ -13,9 +13,11 @@
<script>
import { DataSet } from '@antv/data-set'
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'LineChartMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
@ -55,6 +57,7 @@
data() {
return {
scale: [{
type: 'cat',
dataKey: 'x',
min: 0,
max: 1

View File

@ -64,6 +64,6 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
@import "chart";
</style>

View File

@ -71,6 +71,6 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
@import "chart";
</style>

View File

@ -34,7 +34,7 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
.chart-mini-progress {
padding: 5px 0;
position: relative;

View File

@ -1,5 +1,5 @@
<template>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
<v-tooltip :showTitle="false" dataKey="item*percent"/>
<v-axis/>
<v-legend dataKey="item"/>
@ -10,8 +10,11 @@
<script>
const DataSet = require('@antv/data-set')
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'Pie',
mixins: [ChartEventMixins],
props: {
title: {
type: String,

View File

@ -32,7 +32,7 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
.rank {
padding: 0 32px 32px 72px;

View File

@ -0,0 +1,54 @@
<template>
<div>
<v-chart :forceFit="true" :height="height" :data="data">
<v-coord type="rect" direction="LB" />
<v-tooltip />
<v-legend />
<v-axis dataKey="State" :label="label" />
<v-stack-bar position="State*流程数量" color="流程状态" />
</v-chart>
</div>
</template>
<script>
const DataSet = require('@antv/data-set');
export default {
name: 'StackBar',
props: {
dataSource: {
type: Array,
required: true,
default: () => [
{ 'State': '请假', '流转中': 25, '已归档': 18 },
{ 'State': '出差', '流转中': 30, '已归档': 20 },
{ 'State': '加班', '流转中': 38, '已归档': 42},
{ 'State': '用车', '流转中': 51, '已归档': 67}
]
},
height: {
type: Number,
default: 254
}
},
data() {
return {
label: { offset: 12 }
}
},
computed: {
data() {
const dv = new DataSet.View().source(this.dataSource);
dv.transform({
type: 'fold',
fields: ['流转中', '已归档'],
key: '流程状态',
value: '流程数量',
retains: ['State'],
});
return dv.rows;
}
}
}
</script>

View File

@ -52,7 +52,7 @@
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
.chart-trend {
display: inline-block;
font-size: 14px;

View File

@ -0,0 +1,10 @@
export const ChartEventMixins = {
methods: {
handleClick(event, chart) {
this.handleEvent('click', event, chart)
},
handleEvent(eventName, event, chart) {
this.$emit(eventName, event, chart)
},
}
}

View File

@ -1,10 +1,14 @@
<template>
<a-radio-group v-if="tagType=='radio'" @change="handleInput" :value="value" :disabled="disabled">
<a-radio-group v-if="tagType=='radio'" @change="handleInput" :value="getValueSting" :disabled="disabled">
<a-radio v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio>
</a-radio-group>
<a-select v-else-if="tagType=='select'" :getPopupContainer = "(target) => target.parentNode" :placeholder="placeholder" :disabled="disabled" :value="value" @change="handleInput">
<a-select-option value="">请选择</a-select-option>
<a-radio-group v-else-if="tagType=='radioButton'" buttonStyle="solid" @change="handleInput" :value="getValueSting" :disabled="disabled">
<a-radio-button v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio-button>
</a-radio-group>
<a-select v-else-if="tagType=='select'" :getPopupContainer = "getPopupContainer" :placeholder="placeholder" :disabled="disabled" :value="getValueSting" @change="handleInput">
<a-select-option :value="undefined">请选择</a-select-option>
<a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value">
<span style="display: inline-block;width: 100%" :title=" item.text || item.label ">
{{ item.text || item.label }}
@ -14,7 +18,7 @@
</template>
<script>
import {ajaxGetDictItems} from '@/api/api'
import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
export default {
name: "JDictSelectTag",
@ -23,8 +27,12 @@
placeholder: String,
triggerChange: Boolean,
disabled: Boolean,
value: String,
type: String
value: [String, Number],
type: String,
getPopupContainer:{
type: Function,
default: (node) => node.parentNode
}
},
data() {
return {
@ -50,8 +58,22 @@
//获取字典数据
// this.initDictData();
},
computed: {
getValueSting(){
// update-begin author:wangshuai date:20200601 for: 不显示placeholder的文字 ------
// 当有null或“” placeholder不显示
return this.value != null ? this.value.toString() : undefined;
// update-end author:wangshuai date:20200601 for: 不显示placeholder的文字 ------
},
},
methods: {
initDictData() {
//优先从缓存中读取字典配置
if(getDictItemsFromCache(this.dictCode)){
this.dictOptions = getDictItemsFromCache(this.dictCode);
return
}
//根据字典Code, 初始化字典数组
ajaxGetDictItems(this.dictCode, null).then((res) => {
if (res.success) {

View File

@ -4,7 +4,7 @@
* date: 20190109
*/
import {ajaxGetDictItems} from '@/api/api'
import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
import {getAction} from '@/api/manage'
/**
@ -16,6 +16,13 @@ export async function initDictOptions(dictCode) {
if (!dictCode) {
return '字典Code不能为空!';
}
//优先从缓存中读取字典配置
if(getDictItemsFromCache(dictCode)){
let res = {}
res.result = getDictItemsFromCache(dictCode);
res.success = true;
return res;
}
//获取字典数组
let res = await ajaxGetDictItems(dictCode);
return res;
@ -28,16 +35,30 @@ export async function initDictOptions(dictCode) {
* @return String
*/
export function filterDictText(dictOptions, text) {
//--update-begin----author:sunjianlei---date:20191025------for:修复字典替换方法在字典没有加载完成之前报错的问题、修复没有找到字典时返回空值的问题---
if (dictOptions instanceof Array) {
// --update-begin----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
if (text != null && Array.isArray(dictOptions)) {
let result = []
// 允许多个逗号分隔,允许传数组对象
let splitText
if (Array.isArray(text)) {
splitText = text
} else {
splitText = text.toString().trim().split(',')
}
for (let txt of splitText) {
let dictText = txt
for (let dictItem of dictOptions) {
if (text === dictItem.value) {
return dictItem.text
if (txt.toString() === dictItem.value.toString()) {
dictText = (dictItem.text || dictItem.title || dictItem.label)
break
}
}
result.push(dictText)
}
return result.join(',')
}
return text
//--update-end----author:sunjianlei---date:20191025------for:修复字典替换方法在字典没有加载完成之前报错的问题、修复没有找到字典时返回空值的问题---
// --update-end----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
}
/**
@ -47,18 +68,32 @@ export function filterDictText(dictOptions, text) {
* @return String
*/
export function filterMultiDictText(dictOptions, text) {
if(!text || !dictOptions || dictOptions.length==0){
//js “!text” 认为0为空所以做提前处理
if(text === 0 || text === '0'){
if(dictOptions){
for (let dictItem of dictOptions) {
if (text == dictItem.value) {
return dictItem.text
}
}
}
}
if(!text || text=='null' || !dictOptions || dictOptions.length==0){
return ""
}
let re = "";
text = text.toString()
let arr = text.split(",")
dictOptions.forEach(function (option) {
if(option){
for(let i=0;i<arr.length;i++){
if (arr[i] === option.value) {
re += option.text+",";
break;
}
}
}
});
if(re==""){
return text;
@ -71,20 +106,42 @@ export function filterMultiDictText(dictOptions, text) {
* @param children
* @returns string
*/
export async function ajaxFilterDictText(dictCode, key) {
export function filterDictTextByCache(dictCode, key) {
if(key==null ||key.length==0){
return;
}
if (!dictCode) {
return '字典Code不能为空!';
}
//console.log(`key : ${key}`);
if (!key) {
return '';
//优先从缓存中读取字典配置
if(getDictItemsFromCache(dictCode)){
let item = getDictItemsFromCache(dictCode).filter(t => t["value"] == key)
if(item && item.length>0){
return item[0]["text"]
}
//通过请求读取字典文本
let res = await getAction(`/sys/dict/getDictText/${dictCode}/${key}`);
if (res.success) {
// console.log('restult: '+ res.result);
return res.result;
} else {
return '';
}
}
/** 通过code获取字典数组 */
export async function getDictItems(dictCode, params) {
//优先从缓存中读取字典配置
if(getDictItemsFromCache(dictCode)){
let desformDictItems = getDictItemsFromCache(dictCode).map(item => ({...item, label: item.text}))
return desformDictItems;
}
//缓存中没有,就请求后台
return await ajaxGetDictItems(dictCode, params).then(({success, result}) => {
if (success) {
let res = result.map(item => ({...item, label: item.text}))
console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res)
return Promise.resolve(res)
} else {
console.error('getDictItems error: : ', res)
return Promise.resolve([])
}
}).catch((res) => {
console.error('getDictItems error: ', res)
return Promise.resolve([])
})
}

View File

@ -10,6 +10,7 @@
:disabled="disabled"
mode="multiple"
:placeholder="placeholder"
:getPopupContainer="(node) => node.parentNode"
allowClear>
<a-select-option
v-for="(item,index) in dictOptions"
@ -24,7 +25,7 @@
</template>
<script>
import {ajaxGetDictItems} from '@/api/api'
import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
export default {
name: 'JMultiSelectTag',
props: {
@ -49,12 +50,18 @@
this.tagType = this.type
}
//获取字典数据
this.initDictData();
//this.initDictData();
},
watch:{
options: function(val){
this.setCurrentDictOptions(val);
},
dictCode:{
immediate:true,
handler() {
this.initDictData()
},
},
value (val) {
if(!val){
this.arrayValue = []
@ -68,6 +75,11 @@
if(this.options && this.options.length>0){
this.dictOptions = [...this.options]
}else{
//优先从缓存中读取字典配置
if(getDictItemsFromCache(this.dictCode)){
this.dictOptions = getDictItemsFromCache(this.dictCode);
return
}
//根据字典Code, 初始化字典数组
ajaxGetDictItems(this.dictCode, null).then((res) => {
if (res.success) {

View File

@ -4,6 +4,8 @@
v-if="async"
showSearch
labelInValue
:disabled="disabled"
:getPopupContainer="(node) => node.parentNode"
@search="loadData"
:placeholder="placeholder"
v-model="selectedAsyncValue"
@ -19,7 +21,9 @@
<a-select
v-else
:getPopupContainer="(node) => node.parentNode"
showSearch
:disabled="disabled"
:placeholder="placeholder"
optionFilterProp="children"
style="width: 100%"
@ -35,7 +39,7 @@
</template>
<script>
import { ajaxGetDictItems } from '@/api/api'
import { ajaxGetDictItems,getDictItemsFromCache } from '@/api/api'
import debounce from 'lodash/debounce';
import { getAction } from '../../api/manage'
@ -43,7 +47,7 @@
name: 'JSearchSelectTag',
props:{
disabled: Boolean,
value: String,
value: [String, Number],
dict: String,
dictOptions: Array,
async: Boolean,
@ -71,8 +75,12 @@
immediate:true,
handler(val){
if(!val){
if(val==0){
this.initSelectValue()
}else{
this.selectedValue=[]
this.selectedAsyncValue=[]
}
}else{
this.initSelectValue()
}
@ -82,6 +90,14 @@
handler(){
this.initDictData()
}
},
'dictOptions':{
deep: true,
handler(val){
if(val && val.length>0){
this.options = [...val]
}
}
}
},
methods:{
@ -100,7 +116,7 @@
})
}
}else{
this.selectedValue = this.value
this.selectedValue = this.value.toString()
}
},
loadData(value){
@ -132,13 +148,30 @@
this.options = [...this.dictOptions]
}else{
//根据字典Code, 初始化字典数组
ajaxGetDictItems(this.dict, null).then((res) => {
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) {
//优先从缓存中读取字典配置
if (getDictItemsFromCache(this.dictCode)) {
this.options = getDictItemsFromCache(this.dictCode);
return
}
}
ajaxGetDictItems(dictStr, null).then((res) => {
if (res.success) {
this.options = res.result;
}
})
}
}
}
},
filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0

View File

@ -0,0 +1,163 @@
<template>
<div class="j-area-linkage">
<div v-if="reloading">
<span> Reloading... </span>
</div>
<area-cascader
v-else-if="_type === enums.type[0]"
:value="innerValue"
:data="pcaa"
:level="1"
:style="{width}"
v-bind="$attrs"
v-on="_listeners"
@change="handleChange"
/>
<area-select
v-else-if="_type === enums.type[1]"
:value="innerValue"
:data="pcaa"
:level="2"
v-bind="$attrs"
v-on="_listeners"
@change="handleChange"
/>
<div v-else>
<span style="color:red;"> Bad type value: {{_type}}</span>
</div>
</div>
</template>
<script>
import { pcaa } from 'area-data'
import Area from '@/components/_util/Area'
export default {
name: 'JAreaLinkage',
props: {
value: {
type: String,
required:false
},
// 组件的类型,可选值:
// select 下拉样式
// cascader 级联样式(默认)
type: {
type: String,
default: 'cascader'
},
width: {
type: String,
default: '100%'
}
},
data() {
return {
pcaa,
innerValue: [],
usedListeners: ['change'],
enums: {
type: ['cascader', 'select']
},
reloading: false,
areaData:''
}
},
computed: {
_listeners() {
let listeners = { ...this.$listeners }
// 去掉已使用的事件,防止冲突
this.usedListeners.forEach(key => {
delete listeners[key]
})
return listeners
},
_type() {
if (this.enums.type.includes(this.type)) {
return this.type
} else {
console.error(`JAreaLinkage的type属性只能接收指定的值${this.enums.type.join('|')}`)
return this.enums.type[0]
}
},
},
watch: {
value: {
immediate: true,
handler() {
this.loadDataByValue(this.value)
}
},
},
created() {
this.initAreaData();
},
methods: {
/** 重新加载组件 */
reload() {
this.reloading = true
this.$nextTick(() => this.reloading = false)
},
/** 通过 value 反推 options */
loadDataByValue(value) {
if (!value || value.length === 0) {
this.innerValue = []
} else {
this.initAreaData()
let arr = this.areaData.getRealCode(value)
this.innerValue = arr
}
this.reload()
},
/** 通过地区code获取子级 */
loadDataByCode(value) {
let options = []
let data = pcaa[value]
if (data) {
for (let key in data) {
if (data.hasOwnProperty(key)) {
options.push({ value: key, label: data[key], })
}
}
return options
} else {
return []
}
},
/** 判断是否有子节点 */
hasChildren(options) {
options.forEach(option => {
let data = this.loadDataByCode(option.value)
option.isLeaf = data.length === 0
})
},
handleChange(values) {
let value = values[values.length - 1]
this.$emit('change', value)
},
initAreaData(){
if(!this.areaData){
this.areaData = new Area();
}
},
},
model: { prop: 'value', event: 'change' },
}
</script>
<style lang="less" scoped>
.j-area-linkage {
height:40px;
/deep/ .area-cascader-wrap .area-select {
width: 100%;
}
/deep/ .area-select .area-selected-trigger {
line-height: 1.15;
}
}
</style>

View File

@ -96,7 +96,7 @@
loadRoot(){
let param = {
pid:this.pid,
pcode:this.pcode,
pcode:!this.pcode?'0':this.pcode,
condition:this.condition
}
getAction(this.url,param).then(res=>{
@ -122,8 +122,6 @@
this.treeValue = []
}else{
getAction(this.view,{ids:this.value}).then(res=>{
console.log(124345)
console.log(124345,res)
if(res.success){
let values = this.value.split(',')
this.treeValue = res.result.map((item, index) => ({
@ -198,9 +196,14 @@
if(!value){
this.$emit('change', '');
this.treeValue = ''
} else if (value instanceof Array) {
//this.$emit('change', value.map(item => item.value).join(','))
//this.treeValue = value
} else if (Array.isArray(value)) {
let labels = []
let values = value.map(item => {
labels.push(item.label)
return item.value
})
this.backValue(values.join(','), labels.join(','))
this.treeValue = value
} else {
this.backValue(value.value,value.label)
this.treeValue = value

View File

@ -1,6 +1,6 @@
<template>
<div v-bind="fullScreenParentProps">
<a-icon v-if="fullScreen" class="full-screen-icon" type="fullscreen" @click="()=>fullCoder=!fullCoder"/>
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
<div class="code-editor-cust full-screen-child">
<textarea ref="textarea"></textarea>
@ -46,6 +46,8 @@
import 'codemirror/mode/swift/swift.js'
import 'codemirror/mode/vue/vue.js'
import { isIE11, isIE } from '@/utils/browser'
// 尝试获取全局实例
const CodeMirror = window.CodeMirror || _CodeMirror
@ -85,12 +87,27 @@
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'
},
},
data () {
return {
// 内部真实的内容
code: '',
iconType: 'fullscreen',
hasCode:false,
// 默认的语法类型
mode: 'javascript',
@ -155,6 +172,15 @@
}
},
watch: {
fullCoder:{
handler(value) {
if(value){
this.iconType="fullscreen-exit"
}else{
this.iconType="fullscreen"
}
}
},
// value: {
// immediate: false,
// handler(value) {
@ -207,14 +233,30 @@
hintOptions: this.options.hintOptions
}
},
fullScreenParentProps(){
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', this.fullCoder ? 'full-screen' : ''],
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
}
},
@ -230,7 +272,8 @@
// 编辑器赋值
if(this.value||this.code){
this.hasCode=true
this.coder.setValue(this.value || this.code)
//this.coder.setValue(this.value || this.code)
this.setCodeContent(this.value || this.code)
}else{
this.coder.setValue('')
this.hasCode=false
@ -398,6 +441,7 @@
top: 12px;
right: 12px;
}
.full-screen-child {
height: 100%;
max-height: 100%;
@ -405,9 +449,23 @@
}
}
.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%;
}
}
}

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

@ -1,5 +1,6 @@
<template>
<a-date-picker
dropdownClassName="j-date-picker"
:disabled="disabled || readOnly"
:placeholder="placeholder"
@change="handleDateChange"
@ -51,7 +52,7 @@
},
getCalendarContainer: {
type: Function,
default: () => document.body
default: (node) => node.parentNode
}
},
data () {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
<template>
<div class="tinymce-editor">
<editor
v-if="!reloading"
v-model="myValue"
:init="init"
:disabled="disabled"
@ -23,6 +24,9 @@
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/fullscreen'
import 'tinymce/icons/default'
import { uploadAction,getFileAccessHttpUrl } from '@/api/manage'
import { getVmParentByName } from '@/utils/util'
export default {
components: {
Editor
@ -65,25 +69,75 @@
menubar: false,
toolbar_drawer: false,
images_upload_handler: (blobInfo, success) => {
let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename());
formData.append('biz', "jeditor");
formData.append("jeditor","1");
uploadAction(window._CONFIG['domianURL']+"/sys/common/upload", formData).then((res) => {
if (res.success) {
if(res.message == 'local'){
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
success(img)
}else{
let img = getFileAccessHttpUrl(res.message)
success(img)
}
}
})
}
},
myValue: this.value
myValue: this.value,
reloading: false,
}
},
mounted() {
tinymce.init({})
this.initATabsChangeAutoReload()
},
methods: {
reload() {
this.reloading = true
this.$nextTick(() => this.reloading = false)
},
onClick(e) {
this.$emit('onClick', e, tinymce)
},
//可以添加一些自己的自定义事件,如清空内容
clear() {
this.myValue = ''
},
/**
* 自动判断父级是否是 <a-tabs/> 组件然后添加事件监听自动触发reload()
*
* 由于 tabs 组件切换会导致 tinymce 无法输入,
* 只有重新加载才能使用无论是vue版的还是jQuery版tinymce都有这个通病
*/
initATabsChangeAutoReload() {
// 获取父级
let tabs = getVmParentByName(this, 'ATabs')
let tabPane = getVmParentByName(this, 'ATabPane')
if (tabs && tabPane) {
// 用户自定义的 key
let currentKey = tabPane.$vnode.key
// 添加事件监听
tabs.$on('change', (key) => {
// 切换到自己时执行reload
if (currentKey === key) {
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无法修改------
}
},
},
watch: {
value(newValue) {

View File

@ -1,6 +1,6 @@
<template>
<div :class="disabled?'jeecg-form-container-disabled':''">
<fieldset disabled>
<fieldset :disabled="disabled">
<slot name="detail"></slot>
</fieldset>
<slot name="edit"></slot>
@ -46,4 +46,16 @@
-ms-pointer-events: none;
pointer-events: none;
}
.jeecg-form-container-disabled .ant-upload-select{display:none}
.jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
-ms-pointer-events: auto !important;
pointer-events: auto !important;
}
.jeecg-form-container-disabled .ant-upload-list-item-actions .anticon-delete,
.jeecg-form-container-disabled .ant-upload-list-item .anticon-close{
display: none;
}
</style>

View File

@ -1,203 +0,0 @@
<template>
<div class="gc-canvas" @click="reloadPic">
<canvas id="gc-canvas" :width="contentWidth" :height="contentHeight"></canvas>
</div>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
name: 'JGraphicCode',
props: {
length:{
type: Number,
default: 4
},
fontSizeMin: {
type: Number,
default: 20
},
fontSizeMax: {
type: Number,
default: 45
},
backgroundColorMin: {
type: Number,
default: 180
},
backgroundColorMax: {
type: Number,
default: 240
},
colorMin: {
type: Number,
default: 50
},
colorMax: {
type: Number,
default: 160
},
lineColorMin: {
type: Number,
default: 40
},
lineColorMax: {
type: Number,
default: 180
},
dotColorMin: {
type: Number,
default: 0
},
dotColorMax: {
type: Number,
default: 255
},
contentWidth: {
type: Number,
default:136
},
contentHeight: {
type: Number,
default: 38
},
remote:{
type:Boolean,
default:false,
required:false
}
},
methods: {
// 生成一个随机数
randomNum (min, max) {
return Math.floor(Math.random() * (max - min) + min)
},
// 生成一个随机的颜色
randomColor (min, max) {
let r = this.randomNum(min, max)
let g = this.randomNum(min, max)
let b = this.randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
},
drawPic () {
this.randomCode().then(()=>{
let canvas = document.getElementById('gc-canvas')
let ctx = canvas.getContext('2d')
ctx.textBaseline = 'bottom'
// 绘制背景
ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
// 绘制文字
for (let i = 0; i < this.code.length; i++) {
this.drawText(ctx, this.code[i], i)
}
this.drawLine(ctx)
this.drawDot(ctx)
this.$emit("success",this.code)
})
},
drawText (ctx, txt, i) {
ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
let fontSize = this.randomNum(this.fontSizeMin, this.fontSizeMax)
ctx.font = fontSize + 'px SimHei'
let padding = 10;
let offset = (this.contentWidth-40)/(this.code.length-1)
let x=padding;
if(i>0){
x = padding+(i*offset)
}
//let x = (i + 1) * (this.contentWidth / (this.code.length + 1))
let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
if(fontSize>40){
y=40
}
var deg = this.randomNum(-10,10)
// 修改坐标原点和旋转角度
ctx.translate(x, y)
ctx.rotate(deg * Math.PI / 180)
ctx.fillText(txt, 0, 0)
// 恢复坐标原点和旋转角度
ctx.rotate(-deg * Math.PI / 180)
ctx.translate(-x, -y)
},
drawLine (ctx) {
// 绘制干扰线
for (let i = 0; i <1; i++) {
ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
ctx.beginPath()
ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
ctx.stroke()
}
},
drawDot (ctx) {
// 绘制干扰点
for (let i = 0; i < 100; i++) {
ctx.fillStyle = this.randomColor(0, 255)
ctx.beginPath()
ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
ctx.fill()
}
},
reloadPic(){
this.drawPic()
},
randomCode(){
return new Promise((resolve)=>{
if(this.remote==true){
getAction("/sys/getCheckCode").then(res=>{
console.log("aaaaa",res)
if(res.success){
this.checkKey = res.result.key
this.code = res.result.code
resolve();
}else{
this.$message.error("生成验证码错误,请联系系统管理员")
this.code = 'BUG'
resolve();
}
}).catch(()=>{
console.log("生成验证码连接服务器异常")
this.code = 'BUG'
resolve();
})
}else{
this.randomLocalCode();
resolve();
}
})
},
randomLocalCode(){
let random = ''
//去掉了I l i o O
let str = "QWERTYUPLKJHGFDSAZXCVBNMqwertyupkjhgfdsazxcvbnm1234567890"
for(let i = 0; i < this.length; i++) {
let index = Math.floor(Math.random()*57);
random += str[index];
}
this.code = random
},
getLoginParam(){
return {
checkCode:this.code,
checkKey:this.checkKey
}
}
},
mounted () {
this.drawPic()
},
data(){
return {
code:"",
checkKey:""
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,220 @@
<template>
<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':''">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="height:104px;max-width:300px"/>
<div v-else class="iconp">
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
<div class="ant-upload-text">{{ text }}</div>
</div>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
<img alt="example" style="width: 100%" :src="previewImage"/>
</a-modal>
</a-upload>
</div>
</template>
<script>
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import { getFileAccessHttpUrl } from '@/api/manage'
const uidGenerator=()=>{
return '-'+parseInt(Math.random()*10000+1,10);
}
const getFileName=(path)=>{
if(path.lastIndexOf("\\")>=0){
let reg=new RegExp("\\\\","g");
path = path.replace(reg,"/");
}
return path.substring(path.lastIndexOf("/")+1);
}
export default {
name: 'JImageUpload',
data(){
return {
uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
uploadLoading:false,
picUrl:false,
headers:{},
fileList: [],
previewImage:"",
previewVisible: false,
}
},
props:{
text:{
type:String,
required:false,
default:"上传"
},
/*这个属性用于控制文件上传的业务路径*/
bizPath:{
type:String,
required:false,
default:"temp"
},
value:{
type:[String,Array],
required:false
},
disabled:{
type:Boolean,
required:false,
default: false
},
isMultiple:{
type:Boolean,
required:false,
default: false
}
},
watch:{
value(val){
if (val instanceof Array) {
this.initFileList(val.join(','))
} else {
this.initFileList(val)
}
if(!val || val.length==0){
this.picUrl = false;
}
}
},
created(){
const token = Vue.ls.get(ACCESS_TOKEN);
this.headers = {"X-Access-Token":token}
},
methods:{
initFileList(paths){
if(!paths || paths.length==0){
this.fileList = [];
return;
}
this.picUrl = true;
let fileList = [];
let arr = paths.split(",")
for(var a=0;a<arr.length;a++){
let url = getFileAccessHttpUrl(arr[a]);
fileList.push({
uid: uidGenerator(),
name: getFileName(arr[a]),
status: 'done',
url: url,
response:{
status:"history",
message:arr[a]
}
})
}
this.fileList = fileList
},
beforeUpload: function(file){
var fileType = file.type;
if(fileType.indexOf('image')<0){
this.$message.warning('请上传图片');
return false;
}
},
handleChange(info) {
this.picUrl = false;
let fileList = info.fileList
if(info.file.status==='done'){
if(info.file.response.success){
this.picUrl = true;
fileList = fileList.map((file) => {
if (file.response) {
file.url = file.response.message;
}
return file;
});
}
//this.$message.success(`${info.file.name} 上传成功!`);
}else if (info.file.status === 'error') {
this.$message.error(`${info.file.name} 上传失败.`);
}else if(info.file.status === 'removed'){
this.handleDelete(info.file)
}
this.fileList = fileList
if(info.file.status==='done' || info.file.status === 'removed'){
this.handlePathChange()
}
},
// 预览
handlePreview (file) {
this.previewImage = file.url || file.thumbUrl
this.previewVisible = true
},
getAvatarView(){
if(this.fileList.length>0){
let url = this.fileList[0].url
return getFileAccessHttpUrl(url)
}
},
handlePathChange(){
let uploadFiles = this.fileList
let path = ''
if(!uploadFiles || uploadFiles.length==0){
path = ''
}
let arr = [];
if(!this.isMultiple){
arr.push(uploadFiles[uploadFiles.length-1].response.message)
}else{
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){
path = arr.join(",")
}
this.$emit('change', path);
},
handleDelete(file){
//如有需要新增 删除逻辑
console.log(file)
},
handleCancel() {
this.close();
this.previewVisible = false;
},
close () {
},
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
/* 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

@ -42,6 +42,11 @@
type: String,
default: '',
required: false
},
biz:{
type: String,
default: '',
required: false
}
},
data(){
@ -49,7 +54,8 @@
visible:false,
uploading:false,
fileList:[],
uploadAction:''
uploadAction:'',
foreignKeys:''
}
},
watch: {
@ -67,10 +73,11 @@
handleClose(){
this.visible=false
},
show(){
show(arg){
this.fileList = []
this.uploading = false
this.visible = true
this.foreignKeys = arg;
},
handleRemove(file) {
const index = this.fileList.indexOf(file);
@ -85,6 +92,12 @@
handleImport() {
const { fileList } = this;
const formData = new FormData();
if(this.biz){
formData.append('isSingleTableImport',this.biz);
}
if(this.foreignKeys && this.foreignKeys.length>0){
formData.append('foreignKeys',this.foreignKeys);
}
fileList.forEach((file) => {
formData.append('files[]', file);
});

View File

@ -24,6 +24,11 @@
type:String,
required:false,
default:''
},
trim:{
type: Boolean,
required: false,
default:false
}
},
watch:{
@ -32,7 +37,12 @@
handler:function(){
this.initVal();
}
}
},
// update-begin author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
type() {
this.backValue({ target: { value: this.inputVal } })
},
// update-end author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
},
model: {
prop: 'value',
@ -51,7 +61,10 @@
let text = this.value
switch (this.type) {
case JINPUT_QUERY_LIKE:
//修复路由传参的值传送到jinput框被前后各截取了一位 #1336
if(text.indexOf("*") != -1){
text = text.substring(1,text.length-1);
}
break;
case JINPUT_QUERY_NE:
text = text.substring(1);
@ -69,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

@ -0,0 +1,30 @@
export default {
minHeight: '200px',
previewStyle: 'vertical',
useCommandShortcut: true,
useDefaultHTMLSanitizer: true,
usageStatistics: false,
hideModeSwitch: false,
toolbarItems: [
'heading',
'bold',
'italic',
'strike',
'divider',
'hr',
'quote',
'divider',
'ul',
'ol',
'task',
'indent',
'outdent',
'divider',
'table',
'image',
'link',
'divider',
'code',
'codeblock'
]
}

View File

@ -0,0 +1,134 @@
<template>
<div class="j-markdown-editor" :id="id"/>
</template>
<script>
import 'codemirror/lib/codemirror.css'
import '@toast-ui/editor/dist/toastui-editor.css';
import '@toast-ui/editor/dist/i18n/zh-cn';
import Editor from '@toast-ui/editor';
import defaultOptions from './default-options'
export default {
name: 'JMarkdownEditor',
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
}
},
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.initEditor()
},
destroyed() {
this.destroyEditor()
},
methods: {
initEditor() {
this.editor = new Editor({
el: document.getElementById(this.id),
...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,228 @@
<template>
<a-modal
ref="modal"
:class="getClass(modalClass)"
:style="getStyle(modalStyle)"
:visible="visible"
v-bind="_attrs"
v-on="$listeners"
@ok="handleOk"
@cancel="handleCancel"
>
<slot></slot>
<template v-if="!isNoTitle" slot="title">
<a-row class="j-modal-title-row" type="flex">
<a-col class="left">
<slot name="title">{{ title }}</slot>
</a-col>
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
</a-col>
</a-row>
</template>
<!-- 处理 scopedSlots -->
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
<slot :name="slotName"></slot>
</template>
<!-- 处理 slots -->
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
<slot :name="slotName"></slot>
</template>
</a-modal>
</template>
<script>
import { getClass, getStyle } from '@/utils/props-util'
import { triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'JModal',
props: {
title: String,
// 可使用 .sync 修饰符
visible: Boolean,
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
fullscreen: {
type: Boolean,
default: false
},
// 是否允许切换全屏(允许后右上角会出现一个按钮)
switchFullscreen: {
type: Boolean,
default: false
},
// 点击确定按钮的时候是否关闭弹窗
okClose: {
type: Boolean,
default: true
},
},
data() {
return {
// 内部使用的 slots ,不再处理
usedSlots: ['title'],
// 实际控制是否全屏的参数
innerFullscreen: this.fullscreen,
}
},
computed: {
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
_attrs() {
let attrs = { ...this.$attrs }
// 如果全屏就将宽度设为 100%
if (this.innerFullscreen) {
attrs['width'] = '100%'
}
return attrs
},
modalClass() {
return {
'j-modal-box': true,
'fullscreen': this.innerFullscreen,
'no-title': this.isNoTitle,
'no-footer': this.isNoFooter,
}
},
modalStyle() {
let style = {}
// 如果全屏就将top设为 0
if (this.innerFullscreen) {
style['top'] = '0'
}
return style
},
isNoTitle() {
return !this.title && !this.allSlotsKeys.includes('title')
},
isNoFooter() {
return this._attrs['footer'] === null
},
slotsKeys() {
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
},
scopedSlotsKeys() {
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
},
allSlotsKeys() {
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
},
// 切换全屏的按钮图标
fullscreenButtonIcon() {
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
},
},
watch: {
visible() {
if (this.visible) {
this.innerFullscreen = this.fullscreen
}
},
innerFullscreen(val) {
this.$emit('update:fullscreen', val)
},
},
methods: {
getClass(clazz) {
return { ...getClass(this), ...clazz }
},
getStyle(style) {
return { ...getStyle(this), ...style }
},
close() {
this.$emit('update:visible', false)
},
handleOk() {
if (this.okClose) {
this.close()
}
},
handleCancel() {
this.close()
},
/** 切换全屏 */
toggleFullscreen() {
this.innerFullscreen = !this.innerFullscreen
triggerWindowResizeEvent()
},
}
}
</script>
<style lang="less">
.j-modal-box {
&.fullscreen {
top: 0;
left: 0;
padding: 0;
// 兼容1.6.2版本的antdv
& .ant-modal {
top: 0;
padding: 0;
height: 100vh;
}
& .ant-modal-content {
height: 100vh;
border-radius: 0;
& .ant-modal-body {
/* title 和 footer 各占 55px */
height: calc(100% - 55px - 55px);
overflow: auto;
}
}
&.no-title, &.no-footer {
.ant-modal-body {
height: calc(100% - 55px);
}
}
&.no-title.no-footer {
.ant-modal-body {
height: 100%;
}
}
}
.j-modal-title-row {
.left {
width: calc(100% - 56px - 56px);
}
.right {
width: 56px;
position: inherit;
.ant-modal-close {
right: 56px;
color: rgba(0, 0, 0, 0.45);
&:hover {
color: rgba(0, 0, 0, 0.75);
}
}
}
}
}
@media (max-width: 767px) {
.j-modal-box.fullscreen {
margin: 0;
max-width: 100vw;
}
}
</style>

View File

@ -0,0 +1,205 @@
<template>
<div class="components-input-demo-presuffix" v-if="avalid">
<!---->
<a-input @click="openModal" :placeholder="placeholder" v-model="showText" readOnly :disabled="disabled">
<a-icon slot="prefix" type="cluster" :title="title"/>
<a-icon v-if="showText" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<j-popup-onl-report
ref="jPopupOnlReport"
:code="code"
:multi="multi"
:groupId="uniqGroupId"
@ok="callBack"
/>
</div>
</template>
<script>
import JPopupOnlReport from './modal/JPopupOnlReport'
export default {
name: 'JPopup',
components: {
JPopupOnlReport
},
props: {
code: {
type: String,
default: '',
required: false
},
field: {
type: String,
default: '',
required: false
},
orgFields: {
type: String,
default: '',
required: false
},
destFields: {
type: String,
default: '',
required: false
},
width: {
type: Number,
default: 1200,
required: false
},
placeholder: {
type: String,
default: '请选择',
required: false
},
value: {
type: String,
required: false
},
triggerChange: {
type: Boolean,
required: false,
default: false
},
disabled: {
type: Boolean,
required: false,
default: false
},
multi: {
type: Boolean,
required: false,
default: false
},
/** 分组ID用于将多个popup的请求合并到一起不传不分组 */
groupId: String
},
data() {
return {
showText: '',
title: '',
avalid: true
}
},
computed: {
uniqGroupId() {
if (this.groupId) {
let { groupId, code, field, orgFields, destFields } = this
return `${groupId}_${code}_${field}_${orgFields}_${destFields}`
}
}
},
watch: {
value: {
immediate: true,
handler: function(val) {
if (!val) {
this.showText = ''
} else {
this.showText = val
}
}
}
},
created() {
},
mounted() {
if (!this.orgFields || !this.destFields || !this.code) {
this.$message.error('popup参数未正确配置!')
this.avalid = false
}
if (this.destFields.split(',').length != this.orgFields.split(',').length) {
this.$message.error('popup参数未正确配置,原始值和目标值数量不一致!')
this.avalid = false
}
},
methods: {
openModal() {
if (this.disabled === false) {
this.$refs.jPopupOnlReport.show()
}
},
handleEmpty() {
this.showText = ''
let destFieldsArr = this.destFields.split(',')
if (destFieldsArr.length === 0) {
return
}
let res = {}
for (let i = 0; i < destFieldsArr.length; i++) {
res[destFieldsArr[i]] = ''
}
if (this.triggerChange) {
this.$emit('callback', res)
} else {
this.$emit('input', '', res)
}
},
callBack(rows) {
// update--begin--autor:lvdandan-----date:20200630------for多选时未带回多个值------
let orgFieldsArr = this.orgFields.split(',')
let destFieldsArr = this.destFields.split(',')
let resetText = false
if (this.field && this.field.length > 0) {
this.showText = ''
resetText = true
}
let res = {}
if (orgFieldsArr.length > 0) {
for (let i = 0; i < orgFieldsArr.length; i++) {
let tempDestArr = []
for(let rw of rows){
let val = rw[orgFieldsArr[i]]
if(!val){
val = ""
}
tempDestArr.push(val)
}
res[destFieldsArr[i]] = tempDestArr.join(",")
}
if (resetText === true) {
let tempText = []
for(let rw of rows){
let val = rw[orgFieldsArr[destFieldsArr.indexOf(this.field)]]
if(!val){
val = ""
}
tempText.push(val)
}
this.showText = tempText.join(",")
}
// update--end--autor:lvdandan-----date:20200630------for多选时未带回多个值------
}
if (this.triggerChange) {
//v-dec时即triggerChange为true时 将整个对象给form页面 让他自己setFieldsValue
this.$emit('callback', res)
} else {
//v-model时 需要传一个参数field 表示当前这个字段 从而根据这个字段的顺序找到原始值
// this.$emit("input",row[orgFieldsArr[destFieldsArr.indexOf(this.field)]])
this.$emit('input', this.showText, res)
}
}
}
}
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>

View File

@ -1,24 +1,37 @@
<template>
<div class="j-super-query-box">
<slot>
<a-tooltip v-if="superQueryFlag" title="已有高级查询条件生效">
<a-button type="primary" @click="visible=true">
<a-icon type="appstore" theme="twoTone" :spin="true"></a-icon>
<slot name="button" :isActive="superQueryFlag" :isMobile="izMobile" :open="handleOpen" :reset="handleReset">
<a-tooltip v-if="superQueryFlag" v-bind="tooltipProps" :mouseLeaveDelay="0.2">
<!-- begin 不知道为什么不加上这段代码就无法生效 -->
<span v-show="false">{{tooltipProps}}</span>
<!-- end 不知道为什么不加上这段代码就无法生效 -->
<template slot="title">
<span>已有高级查询条件生效</span>
<a-divider type="vertical"/>
<a @click="handleReset">清空</a>
</template>
<a-button-group>
<a-button type="primary" @click="handleOpen">
<a-icon type="appstore" theme="twoTone" spin/>
<span>高级查询</span>
</a-button>
<a-button v-if="izMobile" type="primary" icon="delete" @click="handleReset"/>
</a-button-group>
</a-tooltip>
<a-button v-else type="primary" icon="filter" @click="visible=true">高级查询</a-button>
<a-button v-else type="primary" icon="filter" @click="handleOpen">高级查询</a-button>
</slot>
<a-modal
<j-modal
title="高级查询构造器"
:width="1000"
:visible="visible"
@cancel="handleCancel"
:mask="false"
:fullscreen="izMobile"
class="j-super-query-modal"
style="top:5%;max-height: 95%;">
style="top:5%;max-height: 95%;"
>
<template slot="footer">
<div style="float: left">
@ -33,7 +46,7 @@
<a-row>
<a-col :sm="24" :md="24-5">
<a-empty v-if="queryParamsModel.length === 0">
<a-empty v-if="queryParamsModel.length === 0" style="margin-bottom: 12px;">
<div slot="description">
<span>没有任何查询条件</span>
<a-divider type="vertical"/>
@ -43,16 +56,20 @@
<a-form v-else layout="inline">
<a-form-item label="过滤条件匹配" style="margin-bottom: 12px;">
<a-select v-model="selectValue" :getPopupContainer="node=>node.parentNode">
<a-row style="margin-bottom: 12px;">
<a-col :md="12" :xs="24">
<a-form-item label="过滤条件匹配" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
<a-select v-model="matchType" :getPopupContainer="node=>node.parentNode" style="width: 100%;">
<a-select-option value="and">AND所有条件都要求匹配</a-select-option>
<a-select-option value="or">OR条件中的任意一个匹配</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row type="flex" style="margin-bottom:10px" :gutter="16" v-for="(item, index) in queryParamsModel" :key="index">
<a-col :span="8">
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<a-tree-select
showSearch
v-model="item.field"
@ -68,22 +85,22 @@
</a-tree-select>
</a-col>
<a-col :span="4">
<a-select placeholder="匹配规则" v-model="item.rule" :getPopupContainer="node=>node.parentNode">
<a-col :md="4" :xs="24" style="margin-bottom: 12px;">
<a-select placeholder="匹配规则" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
<a-select-option value="eq">等于</a-select-option>
<a-select-option value="like">包含</a-select-option>
<a-select-option value="right_like">以..开始</a-select-option>
<a-select-option value="left_like">以..结尾</a-select-option>
<a-select-option value="in">在...中</a-select-option>
<a-select-option value="ne">不等于</a-select-option>
<a-select-option value="gt">大于</a-select-option>
<a-select-option value="ge">大于等于</a-select-option>
<a-select-option value="lt">小于</a-select-option>
<a-select-option value="le">小于等于</a-select-option>
<a-select-option value="right_like">以..开始</a-select-option>
<a-select-option value="left_like">以..结尾</a-select-option>
<a-select-option value="like">包含</a-select-option>
<a-select-option value="in">在...中</a-select-option>
</a-select>
</a-col>
<a-col :span="8">
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<template v-if="item.dictCode">
<template v-if="item.type === 'table-dict'">
<j-popup
@ -94,10 +111,14 @@
:destFields="item.dictCode"
></j-popup>
</template>
<j-dict-select-tag v-else v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
<template v-else>
<j-multi-select-tag v-show="allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
</template>
</template>
<j-popup v-else-if="item.type === 'popup'" :value="item.val" v-bind="item.popup" group-id="superQuery" @input="(e,v)=>handleChangeJPopup(item,e,v)"/>
<j-select-multi-user
v-else-if="item.type === 'select-user'"
v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
v-model="item.val"
:buttons="false"
:multiple="false"
@ -105,20 +126,38 @@
:returnKeys="['id', item.customReturnField || 'username']"
/>
<j-select-depart
v-else-if="item.type === 'select-depart'"
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
v-model="item.val"
:multi="false"
placeholder="请选择部门"
:customReturnField="item.customReturnField || 'id'"
/>
<a-select v-else-if="item.options instanceof Array" v-model="item.val" :options="item.options" allowClear placeholder="请选择"/>
<a-select
v-else-if="item.options instanceof Array"
v-model="item.val"
:options="item.options"
allowClear
placeholder="请选择"
:mode="allowMultiple(item)?'multiple':''"
/>
<j-area-linkage v-model="item.val" v-else-if="item.type==='area-linkage' || item.type==='pca'" style="width: 100%"/>
<j-date v-else-if=" item.type=='date' " v-model="item.val" placeholder="请选择日期" style="width: 100%"></j-date>
<j-date v-else-if=" item.type=='datetime' " v-model="item.val" placeholder="请选择时间" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"></j-date>
<a-time-picker v-else-if="item.type==='time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
<a-input-number v-else-if=" item.type=='int'||item.type=='number' " style="width: 100%" placeholder="请输入数值" v-model="item.val"/>
<a-select v-else-if="item.type=='switch'" placeholder="请选择" v-model="item.val">
<a-select-option value="Y">是</a-select-option>
<a-select-option value="N">否</a-select-option>
</a-select>
<a-input v-else v-model="item.val" placeholder="请输入值"/>
</a-col>
<a-col :span="4">
<a-col :md="4" :xs="0" style="margin-bottom: 12px;">
<a-button @click="handleAdd" icon="plus"></a-button>&nbsp;
<a-button @click="handleDel( index )" icon="minus"></a-button>
</a-col>
<a-col :md="0" :xs="24" style="margin-bottom: 12px;text-align: right;">
<a-button @click="handleAdd" icon="plus"></a-button>&nbsp;
<a-button @click="handleDel( index )" icon="minus"></a-button>
</a-col>
@ -135,14 +174,14 @@
保存的查询
</div>
<a-empty v-if="treeData.length === 0" class="j-super-query-history-empty" description="没有保存任何查询"/>
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" description="没有保存任何查询"/>
<a-tree
v-else
class="j-super-query-history-tree"
showIcon
:treeData="treeData"
:treeData="saveTreeData"
:selectedKeys="[]"
@select="handleTreeSelect"
@rightClick="handleTreeRightClick"
>
</a-tree>
</a-card>
@ -158,19 +197,24 @@
<a-input v-model="prompt.value"></a-input>
</a-modal>
</a-modal>
</j-modal>
</div>
</template>
<script>
import moment from 'moment'
import * as utils from '@/utils/util'
import { mixinDevice } from '@/utils/mixin'
import JDate from '@/components/jeecg/JDate.vue'
import JSelectDepart from '@/components/jeecgbiz/JSelectDepart'
import JSelectMultiUser from '@/components/jeecgbiz/JSelectMultiUser'
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
import JAreaLinkage from '@comp/jeecg/JAreaLinkage'
export default {
name: 'JSuperQuery',
components: { JDate, JSelectDepart, JSelectMultiUser },
mixins: [mixinDevice],
components: { JAreaLinkage, JMultiSelectTag, JDate, JSelectDepart, JSelectMultiUser },
props: {
/*
fieldList: [{
@ -201,14 +245,16 @@
},
// 保存查询条件的唯一 code通过该 code 区分
// 默认为 null代表以当前路由全路径为区分Code
saveCode: {
type: String,
default: 'testSaveCode'
default: null
}
},
data() {
return {
moment,
fieldTreeData: [],
prompt: {
@ -217,26 +263,40 @@
},
visible: false,
queryParamsModel: [{}],
queryParamsModel: [],
treeIcon: <a-icon type="file-text"/>,
treeData: [],
// 保存查询条件的treeData
saveTreeData: [],
// 保存查询条件的前缀名
saveCodeBefore: 'JSuperQuerySaved_',
selectValue: 'and',
superQueryFlag: false
// 查询类型过滤条件匹配and、or
matchType: 'and',
superQueryFlag: false,
}
},
computed: {
izMobile() {
return this.device === 'mobile'
},
tooltipProps() {
return this.izMobile ? { visible: false } : {}
},
fullSaveCode() {
let saveCode = this.saveCode
if (saveCode == null || saveCode === '') {
saveCode = this.$route.fullPath
}
return this.saveCodeBefore + saveCode
},
},
watch: {
// 当 saveCode 变化时,重新查询已保存的条件
saveCode: {
fullSaveCode: {
immediate: true,
handler(val) {
let list = this.$ls.get(this.saveCodeBefore + val)
handler() {
let list = this.$ls.get(this.fullSaveCode)
if (list instanceof Array) {
this.treeData = list.map(item => {
item.icon = this.treeIcon
return item
})
this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
}
}
},
@ -273,25 +333,35 @@
methods: {
show() {
if (!this.queryParamsModel || this.queryParamsModel.length == 0) {
this.queryParamsModel = [{}]
if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
this.resetLine()
}
this.visible = true
},
handleOk() {
if (!this.isNullArray(this.queryParamsModel)) {
let event = {
matchType: this.selectValue,
params: this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
matchType: this.matchType,
params: this.removeEmptyObject(this.queryParamsModel)
}
console.log('---高级查询参数--->', event)
this.emitCallback(event.params, event.matchType)
// 移动端模式下关闭弹窗
if (this.izMobile) {
this.visible = false
}
this.emitCallback(event)
} else {
this.emitCallback()
this.$message.warn("不能查询空条件")
}
},
emitCallback(params, matchType) {
this.superQueryFlag = !!params
emitCallback(event = {}) {
let { params = [], matchType = this.matchType } = event
this.superQueryFlag = (params && params.length > 0)
for (let param of params) {
if (Array.isArray(param.val)) {
param.val = param.val.join(',')
}
}
console.debug('---高级查询参数--->', { params, matchType })
this.$emit(this.callback, params, matchType)
},
handleCancel() {
@ -302,27 +372,40 @@
this.visible = false
},
handleAdd() {
this.queryParamsModel.push({})
this.addNewLine()
},
addNewLine() {
this.queryParamsModel.push({ rule: 'eq' })
},
resetLine() {
this.superQueryFlag = false
this.queryParamsModel = []
this.addNewLine()
},
handleDel(index) {
this.queryParamsModel.splice(index, 1)
},
handleSelected(node, item) {
let { type, options, dictCode, dictTable, customReturnField } = node.dataRef
let { type, options, dictCode, dictTable, customReturnField, popup } = node.dataRef
item['type'] = type
item['options'] = options
item['dictCode'] = dictCode
item['dictTable'] = dictTable
item['customReturnField'] = customReturnField
if (popup) {
item['popup'] = popup
}
this.$set(item, 'val', undefined)
},
handleOpen() {
this.show()
},
handleReset() {
this.superQueryFlag = false
this.queryParamsModel = [{}]
this.resetLine()
this.emitCallback()
},
handleSave() {
let queryParams = this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
let queryParams = this.removeEmptyObject(this.queryParamsModel)
if (this.isNullArray(queryParams)) {
this.$message.warning('空条件不能保存')
} else {
@ -331,56 +414,65 @@
}
},
handlePromptOk() {
let { value } = this.prompt
// 判断有没有重名
let filterList = this.treeData.filter(i => i.title === value)
if(!value){
this.$message.warning('保存名称不能为空')
return
}
// 取出查询条件
let records = this.removeEmptyObject(this.queryParamsModel)
// 判断有没有重名的
let filterList = this.saveTreeData.filter(i => i.originTitle === value)
if (filterList.length > 0) {
this.$confirm({
content: `${value} 已存在,是否覆盖?`,
onOk: () => {
this.prompt.visible = false
filterList[0].records = this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
filterList[0].records = records
this.saveToLocalStore()
this.$message.success('保存成功')
}
})
} else {
// 没有重名的,直接添加
this.prompt.visible = false
this.treeData.push({
// 添加到树列表中
this.saveTreeData.push(this.renderSaveTreeData({
title: value,
icon: this.treeIcon,
records: this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
})
matchType: this.matchType,
records: records
}))
// 保存到 LocalStore
this.saveToLocalStore()
this.$message.success('保存成功')
}
},
handleTreeSelect(idx, event) {
if (event.selectedNodes[0]) {
this.queryParamsModel = utils.cloneObject(event.selectedNodes[0].data.props.records)
let { matchType, records } = event.selectedNodes[0].data.props
// 将保存的matchType取出兼容旧数据如果没有保存就还是使用原来的
this.matchType = matchType || this.matchType
this.queryParamsModel = utils.cloneObject(records)
}
},
handleTreeRightClick(args) {
handleRemoveSaveTreeItem(event, vNode) {
// 阻止事件冒泡
event.stopPropagation()
this.$confirm({
content: '是否删除当前查询?',
onOk: () => {
let { node: { eventKey } } = args
this.treeData.splice(Number.parseInt(eventKey.substring(2)), 1)
let { eventKey } = vNode
this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
this.saveToLocalStore()
this.$message.success('删除成功')
},
})
},
// 将查询保存到 LocalStore 里
saveToLocalStore() {
this.$ls.set(this.saveCodeBefore + this.saveCode, this.treeData.map(item => {
return { title: item.title, records: item.records }
}))
let saveValue = this.saveTreeData.map(({ originTitle, matchType, records }) => ({ title: originTitle, matchType, records }))
this.$ls.set(this.fullSaveCode, saveValue)
},
isNullArray(array) {
@ -397,23 +489,83 @@
return false
},
// 去掉数组中的空对象
removeEmptyObject(array) {
removeEmptyObject(arr) {
let array = utils.cloneObject(arr)
for (let i = 0; i < array.length; i++) {
let item = array[i]
if (item == null || Object.keys(item).length <= 0) {
array.splice(i--, 1)
} else {
// 去掉特殊属性
if (Array.isArray(item.options)) {
// 如果有字典属性,就不需要保存 options 了
//update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
delete item.options
//update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
}
}
}
return array
},
/** 渲染保存查询条件的 title加个删除按钮 */
renderSaveTreeData(item) {
item.icon = this.treeIcon
item.originTitle = item['title']
item.title = (arg1, arg2) => {
let vNode
// 兼容旧版的Antdv
if (arg1.dataRef) {
vNode = arg1
} else if (arg2.dataRef) {
vNode = arg2
} else {
return <span style="color:red;">Antdv版本不支持</span>
}
let {originTitle} = vNode.dataRef
return (
<div class="j-history-tree-title">
<span>{originTitle}</span>
<div class="j-history-tree-title-closer" onClick={e => this.handleRemoveSaveTreeItem(e, vNode)}>
<a-icon type="close-circle"/>
</div>
</div>
)
}
return item
},
/** 判断是否允许多选 */
allowMultiple(item) {
return item.rule === 'in'
},
handleRuleChange(item, newValue) {
let oldValue = item.rule
this.$set(item, 'rule', newValue)
// 上一个规则是否是 in且type是字典或下拉
if (oldValue === 'in') {
if (item.dictCode || item.options instanceof Array) {
let value = item.val
if (typeof item.val === 'string') {
value = item.val.split(',')[0]
} else if (Array.isArray(item.val)) {
value = item.val[0]
}
this.$set(item, 'val', value)
}
}
},
handleChangeJPopup(item, e, values) {
item.val = values[item.popup['destFields']]
},
}
}
</script>
<style lang="scss" scoped>
<style lang="less" scoped>
.j-super-query-box {
display: inline-block;
@ -421,42 +573,76 @@
.j-super-query-modal {
.j-super-query-history-card /deep/ {
.ant-card-body,
.ant-card-head-title {
.j-super-query-history-card {
/deep/ .ant-card-body,
/deep/ .ant-card-head-title {
padding: 0;
}
.ant-card-head {
/deep/ .ant-card-head {
padding: 4px 8px;
min-height: initial;
}
}
.j-super-query-history-empty /deep/ {
.ant-empty-image {
.j-super-query-history-empty {
/deep/ .ant-empty-image {
height: 80px;
line-height: 80px;
margin-bottom: 0;
}
img {
/deep/ img {
width: 80px;
height: 65px;
}
.ant-empty-description {
/deep/ .ant-empty-description {
color: #afafaf;
margin: 8px 0;
}
}
.j-super-query-history-tree /deep/ {
.ant-tree-switcher {
.j-super-query-history-tree {
.j-history-tree-title {
width: calc(100% - 24px);
position: relative;
display: inline-block;
&-closer {
color: #999999;
position: absolute;
top: 0;
right: 0;
width: 24px;
height: 24px;
text-align: center;
opacity: 0;
transition: opacity 0.3s, color 0.3s;
&:hover {
color: #666666;
}
&:active {
color: #333333;
}
}
&:hover {
.j-history-tree-title-closer {
opacity: 1;
}
}
}
/deep/ .ant-tree-switcher {
display: none;
}
.ant-tree-node-content-wrapper {
/deep/ .ant-tree-node-content-wrapper {
width: 100%;
}
}

View File

@ -0,0 +1,83 @@
<template>
<div>
<a-select v-if="query" style="width: 100%" @change="handleSelectChange">
<a-select-option v-for="(item, index) in queryOption" :key="index" :value="item.value">
{{ item.text }}
</a-select-option>
</a-select>
<a-switch v-else v-model="checkStatus" :disabled="disabled" @change="handleChange"/>
</div>
</template>
<script>
export default {
name: 'JSwitch',
props: {
value:{
type: String | Number,
required: false
},
disabled:{
type: Boolean,
required: false,
default: false
},
options:{
type:Array,
required:false,
default:()=>['Y','N']
},
query:{
type: Boolean,
required: false,
default: false
}
},
data () {
return {
checkStatus: false
}
},
watch: {
value:{
immediate: true,
handler(val){
if(!this.query){
if(!val){
this.checkStatus = false
this.$emit('change', this.options[1]);
}else{
if(this.options[0]==val){
this.checkStatus = true
}else{
this.checkStatus = false
}
}
}
}
}
},
computed:{
queryOption(){
let arr = []
arr.push({value:this.options[0],text:'是'})
arr.push({value:this.options[1],text:'否'})
return arr;
}
},
methods: {
handleChange(checked){
let flag = checked===false?this.options[1]:this.options[0];
this.$emit('change', flag);
},
handleSelectChange(value){
this.$emit('change', value);
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>

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

@ -121,7 +121,6 @@
getAction(this.url_root,param).then(res=>{
if(res.success){
this.handleTreeNodeValue(res.result)
console.log("aaaa",res.result)
this.treeData = [...res.result]
}else{
this.$message.error(res.message)
@ -180,7 +179,11 @@
},
onChange(value){
console.log(value)
if(!value){
this.$emit('change', '');
}else{
this.$emit('change', value.value);
}
this.treeValue = value
},
onSearch(value){

View File

@ -2,6 +2,7 @@
<a-tree-select
allowClear
labelInValue
:getPopupContainer="(node) => node.parentNode"
style="width: 100%"
:disabled="disabled"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
@ -235,7 +236,6 @@
}else{
try {
let test=JSON.parse(mycondition);
console.log("aaaaasdsdd",typeof test)
if(typeof test == 'object' && test){
resolve()
}else{

View File

@ -1,24 +1,50 @@
<template>
<div :id="containerId" style="position: relative">
<!-- ---------------------------- begin 图片左右换位置 ------------------------------------- -->
<div class="movety-container" :style="{top:top+'px',left:left+'px',display:moveDisplay}" style="padding:0 8px;position: absolute;z-index: 91;height: 32px;width: 104px;text-align: center;">
<div :id="containerId+'-mover'" :class="showMoverTask?'uploadty-mover-mask':'movety-opt'" style="margin-top: 12px">
<a @click="moveLast" style="margin: 0 5px;"><a-icon type="arrow-left" style="color: #fff;font-size: 16px"/></a>
<a @click="moveNext" style="margin: 0 5px;"><a-icon type="arrow-right" style="color: #fff;font-size: 16px"/></a>
</div>
</div>
<!-- ---------------------------- end 图片左右换位置 ------------------------------------- -->
<a-upload
name="file"
:multiple="true"
:action="uploadAction"
:headers="headers"
:data="{'isup':1,'bizPath':bizPath}"
:data="{'biz':bizPath}"
:fileList="fileList"
:beforeUpload="beforeUpload"
@change="handleChange"
:disabled="disabled">
<a-button>
:disabled="disabled"
:returnUrl="returnUrl"
:listType="complistType"
@preview="handlePreview"
:class="{'uploadty-disabled':disabled}">
<template>
<div v-if="isImageComp">
<a-icon type="plus" />
<div class="ant-upload-text">{{ text }}</div>
</div>
<a-button v-else-if="buttonVisible">
<a-icon type="upload" />{{ text }}
</a-button>
</template>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</div>
</template>
<script>
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import { getFileAccessHttpUrl } from '@/api/manage';
const FILE_TYPE_ALL = "all"
const FILE_TYPE_IMG = "image"
@ -38,9 +64,21 @@
data(){
return {
uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
urlDownload:window._CONFIG['domianURL'] + "/sys/common/download/",
headers:{},
fileList: []
fileList: [],
newFileList: [],
uploadGoOn:true,
previewVisible: false,
//---------------------------- begin 图片左右换位置 -------------------------------------
previewImage: '',
containerId:'',
top:'',
left:'',
moveDisplay:'none',
showMoverTask:false,
moverHold:false,
currentImg:''
//---------------------------- end 图片左右换位置 -------------------------------------
}
},
props:{
@ -77,22 +115,82 @@
required: false,
default: false
},
/**
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url
* true仅返回url
* false返回fileName filePath fileSize
*/
returnUrl:{
type:Boolean,
required:false,
default: true
},
number:{
type:Number,
required:false,
default: 0
},
buttonVisible:{
type:Boolean,
required:false,
default: true
},
},
watch:{
value(val){
value:{
immediate: true,
handler() {
let val = this.value
if (val instanceof Array) {
if(this.returnUrl){
this.initFileList(val.join(','))
}else{
this.initFileListArr(val);
}
} else {
this.initFileList(val)
}
}
}
},
computed:{
isImageComp(){
return this.fileType === FILE_TYPE_IMG
},
complistType(){
return this.fileType === FILE_TYPE_IMG?'picture-card':'text'
}
},
created(){
const token = Vue.ls.get(ACCESS_TOKEN);
this.headers = {"X-Access-Token":token}
//---------------------------- begin 图片左右换位置 -------------------------------------
this.headers = {"X-Access-Token":token};
this.containerId = 'container-ty-'+new Date().getTime();
//---------------------------- end 图片左右换位置 -------------------------------------
},
methods:{
initFileListArr(val){
if(!val || val.length==0){
this.fileList = [];
return;
}
let fileList = [];
for(var a=0;a<val.length;a++){
let url = getFileAccessHttpUrl(val[a].filePath);
fileList.push({
uid:uidGenerator(),
name:val[a].fileName,
status: 'done',
url: url,
response:{
status:"history",
message:val[a].filePath
}
})
}
this.fileList = fileList
},
initFileList(paths){
if(!paths || paths.length==0){
//return [];
@ -104,11 +202,12 @@
let fileList = [];
let arr = paths.split(",")
for(var a=0;a<arr.length;a++){
let url = getFileAccessHttpUrl(arr[a]);
fileList.push({
uid:uidGenerator(),
name:getFileName(arr[a]),
status: 'done',
url: this.urlDownload+arr[a],
url: url,
response:{
status:"history",
message:arr[a]
@ -126,7 +225,13 @@
let arr = [];
for(var a=0;a<uploadFiles.length;a++){
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时控制台报错
if(uploadFiles[a].status === 'done' ) {
arr.push(uploadFiles[a].response.message)
}else{
return;
}
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时控制台报错
}
if(arr.length>0){
path = arr.join(",")
@ -134,15 +239,12 @@
this.$emit('change', path);
},
beforeUpload(file){
this.uploadGoOn=true
var fileType = file.type;
if(fileType===FILE_TYPE_IMG){
if(this.fileType===FILE_TYPE_IMG){
if(fileType.indexOf('image')<0){
this.$message.warning('请上传图片');
return false;
}
}else if(fileType===FILE_TYPE_TXT){
if(fileType.indexOf('image')>=0){
this.$message.warning('请上传文件');
this.uploadGoOn=false
return false;
}
}
@ -151,17 +253,24 @@
},
handleChange(info) {
console.log("--文件列表改变--")
if(!info.file.status && this.uploadGoOn === false){
info.fileList.pop();
}
let fileList = info.fileList
if(info.file.status==='done'){
if(this.number>0){
fileList = fileList.slice(-this.number);
}
if(info.file.response.success){
fileList = fileList.map((file) => {
if (file.response) {
file.url = this.urlDownload+file.response.message;
let reUrl = file.response.message;
file.url = getFileAccessHttpUrl(reUrl);
}
return file;
});
}
this.$message.success(`${info.file.name} 上传成功!`);
//this.$message.success(`${info.file.name} 上传成功!`);
}else if (info.file.status === 'error') {
this.$message.error(`${info.file.name} 上传失败.`);
}else if(info.file.status === 'removed'){
@ -169,13 +278,143 @@
}
this.fileList = fileList
if(info.file.status==='done' || info.file.status === 'removed'){
//returnUrl为true时仅返回文件路径
if(this.returnUrl){
this.handlePathChange()
}else{
//returnUrl为false时返回文件名称、文件路径及文件大小
this.newFileList = [];
for(var a=0;a<fileList.length;a++){
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时控制台报错
if(fileList[a].status === 'done' ) {
var fileJson = {
fileName:fileList[a].name,
filePath:fileList[a].response.message,
fileSize:fileList[a].size
};
this.newFileList.push(fileJson);
}else{
return;
}
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时控制台报错
}
this.$emit('change', this.newFileList);
}
}
},
handleDelete(file){
//如有需要新增 删除逻辑
console.log(file)
},
handlePreview(file){
if(this.fileType === FILE_TYPE_IMG){
this.previewImage = file.url || file.thumbUrl;
this.previewVisible = true;
}else{
location.href=file.url
}
},
handleCancel(){
this.previewVisible = false;
},
//---------------------------- begin 图片左右换位置 -------------------------------------
moveLast(){
//console.log(ev)
//console.log(this.fileList)
//console.log(this.currentImg)
let index = this.getIndexByUrl();
if(index==0){
this.$message.warn('未知的操作')
}else{
let curr = this.fileList[index].url;
let last = this.fileList[index-1].url;
let arr =[]
for(let i=0;i<this.fileList.length;i++){
if(i==index-1){
arr.push(curr)
}else if(i==index){
arr.push(last)
}else{
arr.push(this.fileList[i].url)
}
}
this.currentImg = last
this.$emit('change',arr.join(','))
}
},
moveNext(){
let index = this.getIndexByUrl();
if(index==this.fileList.length-1){
this.$message.warn('已到最后~')
}else{
let curr = this.fileList[index].url;
let next = this.fileList[index+1].url;
let arr =[]
for(let i=0;i<this.fileList.length;i++){
if(i==index+1){
arr.push(curr)
}else if(i==index){
arr.push(next)
}else{
arr.push(this.fileList[i].url)
}
}
this.currentImg = next
this.$emit('change',arr.join(','))
}
},
getIndexByUrl(){
for(let i=0;i<this.fileList.length;i++){
if(this.fileList[i].url === this.currentImg || encodeURI(this.fileList[i].url) === this.currentImg){
return i;
}
}
return -1;
}
},
mounted(){
const moverObj = document.getElementById(this.containerId+'-mover');
moverObj.addEventListener('mouseover',()=>{
this.moverHold = true
this.moveDisplay = 'block';
});
moverObj.addEventListener('mouseout',()=>{
this.moverHold = false
this.moveDisplay = 'none';
});
let picList = document.getElementById(this.containerId)?document.getElementById(this.containerId).getElementsByClassName('ant-upload-list-picture-card'):[];
if(picList && picList.length>0){
picList[0].addEventListener('mouseover',(ev)=>{
ev = ev || window.event;
let target = ev.target || ev.srcElement;
if('ant-upload-list-item-info' == target.className){
this.showMoverTask=false
let item = target.parentElement
this.left = item.offsetLeft
this.top=item.offsetTop+item.offsetHeight-50;
this.moveDisplay = 'block';
this.currentImg = target.getElementsByTagName('img')[0].src
}
});
picList[0].addEventListener('mouseout',(ev)=>{
ev = ev || window.event;
let target = ev.target || ev.srcElement;
//console.log('移除',target)
if('ant-upload-list-item-info' == target.className){
this.showMoverTask=true
setTimeout(()=>{
if(this.moverHold === false)
this.moveDisplay = 'none';
},100)
}
if('ant-upload-list-item ant-upload-list-item-done' == target.className || 'ant-upload-list ant-upload-list-picture-card'== target.className){
this.moveDisplay = 'none';
}
})
//---------------------------- end 图片左右换位置 -------------------------------------
}
},
model: {
prop: 'value',
@ -184,6 +423,24 @@
}
</script>
<style scoped>
<style lang="less">
.uploadty-disabled{
.ant-upload-list-item {
.anticon-close{
display: none;
}
.anticon-delete{
display: none;
}
}
}
//---------------------------- begin 图片左右换位置 -------------------------------------
.uploadty-mover-mask{
background-color: rgba(0, 0, 0, 0.5);
opacity: .8;
color: #fff;
height: 28px;
line-height: 28px;
}
//---------------------------- end 图片左右换位置 -------------------------------------
</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

@ -0,0 +1,65 @@
# JPopup 弹窗选择组件
## 参数配置
| 参数 | 类型 | 必填 |说明|
|--------------|---------|----|---------|
| placeholder |string | | placeholder |
| code |string | | online报表编码 |
| orgFields |string | | online报表中显示的列,多个以逗号隔开 |
| destFields |string | | 回调对象的属性,多个以逗号隔开,其顺序和orgFields一一对应 |
| field |string | | v-model模式专用,表示从destFields中选择一个属性的值返回给当前组件 |
| triggerChange |Boolean | | v-decorator模式下需设置成true |
| callback(事件) |function | | 回调事件,v-decorator模式下用到,用于设置form控件的值 |
使用示例
----
```vue
<template>
<a-form :form="form">
<a-form-item label="v-model模式指定一个值返回至当前组件" style="width: 300px">
<j-popup
v-model="selectValue"
code="user_msg"
org-fields="username,realname"
dest-fields="popup,other"
field="popup"/>
{{ selectValue }}
</a-form-item>
<a-form-item label="v-decorator模式支持回调多个值至当前表单" style="width: 300px">
<j-popup
v-decorator="['one']"
:trigger-change="true"
code="user_msg"
org-fields="username,realname"
dest-fields="one,two"
@callback="popupCallback"/>
{{ getFormFieldValue('one') }}
</a-form-item>
<a-form-item label="v-decorator模式被回调的值" style="width: 300px">
<a-input v-decorator="['two']"></a-input>
</a-form-item>
</a-form >
</template>
<script>
export default {
data() {
return {
form: this.$form.createForm(this),
selectValue:"",
}
},
methods:{
getFormFieldValue(field){
return this.form.getFieldValue(field)
},
popupCallback(row){
this.form.setFieldsValue(row)
}
}
}
</script>

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>

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