Compare commits

..

148 Commits

Author SHA1 Message Date
c2db7691d1 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-30 22:20:59 +08:00
543a49fcb6 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-30 18:59:37 +08:00
df6cf7137c 路由配置修改 2020-11-30 12:44:22 +08:00
d7450cab04 修复发现的 JeecgBoot 2.4版本问题 2020-11-29 17:41:29 +08:00
496d85265b JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 22:04:12 +08:00
b4b1162ea0 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:59:33 +08:00
33e39941fb 低代码和零代码在线开发概念 2020-11-28 18:43:50 +08:00
1b5ba9f56b 低代码和零代码在线开发概念 2020-11-28 18:36:06 +08:00
0aadc70e74 JEECG 低代码和零代码在线开发概念 2020-11-28 18:34:19 +08:00
94aed3ade4 JEECG 低代码和零代码在线开发概念 2020-11-28 18:32:16 +08:00
9657eac673 jeecg 零代码和低代码开发介绍 2020-11-28 18:29:31 +08:00
680a8b3c42 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:21:31 +08:00
6e6f88620a JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:19:18 +08:00
14f4f92d84 低代码模块说明 2020-11-28 18:05:29 +08:00
2c4597c0a9 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 18:03:53 +08:00
757f1cd5a8 低代码功能说明 2020-11-28 18:00:48 +08:00
3c62a58d64 JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 17:56:34 +08:00
bec39867a9 mysql脚本位置文档 2020-11-28 17:48:50 +08:00
dca8713a2e 数据库脚步 2020-11-28 17:46:36 +08:00
a004acee4b JeecgBoot 2.4 微服务正式版本发布,基于SpringBoot的低代码平台 2020-11-28 17:20:10 +08:00
35ef0eff90 文档更新 2020-11-27 14:23:47 +08:00
c70fd3383e 文档更新 2020-11-27 14:20:16 +08:00
3e2f640b68 autopoi导入导出组件系列问题
导出excel实体反射,时间格式转换错误 #1573
配置groupName多生成一列
导出列表数据时, 不支持合并后的单元格.提前结束列的循环了 #1426
Excel导出错误原因,value为""字符串,gitee I249JF
一对多导出needMerge 子表数据对应数量小于2时报错 github#1840、gitee I1YH6B
2020-11-18 22:40:28 +08:00
bf6c5f4623 Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot.git 2020-11-14 18:31:43 +08:00
df259385ec jeecgboot-mysql-5.7.sql被别人提交导致不完整 #1985 2020-11-14 18:30:20 +08:00
c5f51a17da Merge pull request #1966 from tank99tank/no_query_params
导出参数没有高级查询参数
2020-11-11 15:47:27 +08:00
ccf4946b70 导出参数没有高级查询参数 #1860 2020-11-11 12:59:12 +08:00
3b4a65ba04 Merge pull request #1731 from CharlieJ0hn/master
修改 qutz_ 前缀的表名为大写,用于修复 jeecg 项目中 Qrutz 表名不区分大小写在 Linux 和 Mac 上的报错
2020-11-10 15:13:07 +08:00
3a8ae4c089 Merge pull request #1900 from Pa-Star-Home/master
bugfix: fix mybatis param import error; fix integer object compare use == error
2020-11-10 15:05:20 +08:00
88eb98c0e6 解决前端页面改变浏览器窗口大小后,菜单展开按钮失效,无法展开菜单,无法操作 #1913 2020-10-30 16:34:03 +08:00
63048d8c64 bugfix: fix mybatis param import error; fix integer object compare use == error 2020-10-27 15:49:27 +08:00
e8a18fc642 Online一分钟快速体验 2020-10-23 14:33:00 +08:00
fa0c930886 解决j-image-upload图片组件单张图片详情回显空白 #1810 2020-10-19 12:01:15 +08:00
708aa54f3a QQ交流群 : ③816531124、②769925425(满)、①284271917(满) 2020-10-13 14:18:29 +08:00
57337f7980 代码生成器模板(内嵌子表风格列表页面)按钮错位问题修复 issues/I1WHR0 2020-10-13 11:51:57 +08:00
89f4d122dd 在线切换部门,用户缓存信息更新 issues/I1X4DT 2020-10-12 16:23:45 +08:00
b92bec3eed 登入生成token的小bug issues/I1XOVS 2020-10-12 15:41:17 +08:00
72f32e47a0 微服务技术:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking、SpringBootAdmin监控 2020-10-09 16:32:21 +08:00
43908013e3 微服务整体解决方案 2020-10-09 16:28:45 +08:00
80bd1a3f23 支持Spring Cloud Alibaba 2020-10-09 15:04:41 +08:00
de60ea050c 微服务架构图 2020-09-30 18:30:02 +08:00
7cb3dce262 [版本发布](v2.3) 2020-09-29 14:45:19 +08:00
416c8df729 解决token失效 ,登录页面重复跳转登录页面问题 2020-09-29 11:52:22 +08:00
b13a4b01df 选取职务名称出现全选 #1753
切换导航模式,导致菜单栏丢失 #1763
2020-09-29 11:29:48 +08:00
075c3532dc 遗漏的路由配置 2020-09-25 14:43:09 +08:00
fa40b08049 大屏设计下的两个示例没有样式和JS #1799 2020-09-25 11:51:15 +08:00
55c0a4e051 Mybatis-plus的IdType配置问题 #1789 2020-09-23 18:02:16 +08:00
0795808075 jeecg-cloud-application-beta.yml有配置重复问题 #1775 2020-09-23 10:06:02 +08:00
0fa6316f52 Jeecg Boot 2.3 里程碑版本发布,支持微服务和单体自由切换 2020-09-21 14:17:39 +08:00
9cd111ca58 fegin token问题 2020-09-20 18:28:06 +08:00
2c18182dd0 解决严重问题, 缺少表结构eoa_mailbox_info issues/I1VN0E 2020-09-20 17:12:59 +08:00
36cad9373e 解决切换微服务后无法使用Online相关功能 #1760 2020-09-18 10:30:24 +08:00
fcbed695b0 解决,找不到jeecg-cloud-module在其子目录config下有两个配置文件 #1754 2020-09-17 19:19:51 +08:00
5a01d8587b 解决2.3严重权限问题: issues/1740 JAVA访问权限控制 无法使用的问题 2020-09-15 18:52:52 +08:00
4570a21a63 解决2.3 [bug]online表单开发的权限控制使用报错#1733 \ IE11兼容问题 2020-09-14 23:53:33 +08:00
8775472470 Update jeecgboot-mysql-5.7.sql
修改 qutz_ 前缀的表名为大写,用于修复 jeecg 项目中 Qrutz 表名不区分大小写在 Linux 和 Mac 上的报错
2020-09-14 15:22:41 +08:00
bb72341519 修复2.3数据库文件缺少 #1728 2020-09-14 15:01:14 +08:00
78afc86411 修复jeecg 2.3 管理员还是没有编辑用户管理的权限 #1729 2020-09-14 14:34:27 +08:00
91ff6a7aa2 2.3版本, oracle11g、SqlServer2017数据库脚步 2020-09-14 13:55:37 +08:00
b3ec97ffcc 解决2.3 所有类型的角色都没有编辑用户管理的权限 #1726 2020-09-14 11:47:26 +08:00
24fe9e425c Jeecg Boot 2.3 里程碑版本发布,基于 SpringBoot 的低代码平台 2020-09-14 10:48:53 +08:00
09d9e79f40 Jeecg Boot 2.3 里程碑版本发布,支持微服务和单体自由切换、提供新行编辑表格JVXETable 2020-09-13 18:26:18 +08:00
7f30a186df JeecgBoot 2.3 里程碑版本发布,支持微服务和单体自由切换、提供新行编辑表格JVXETable 2020-09-13 18:23:23 +08:00
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
06847cd801 论坛变更域名:bbs.jeecg.com 2020-01-19 09:28:12 +08:00
365810d4e6 字典排序问题合并 2020-01-13 12:03:30 +08:00
ed83135062 Merge pull request #844 from yyri/master
<a-form-item> should be nested under <a-col>
2020-01-13 11:47:00 +08:00
beabf9b6dd README.md 2020-01-13 11:37:30 +08:00
238b4898c7 20191227 取消系统功能的权限注解 2019-12-27 15:00:22 +08:00
4372773924 <a-form-item> should be nested under <a-col> 2019-12-26 11:11:12 +08:00
d399ec904a Jeecg-Boot 2.1.3 大屏版本发布 2019-12-25 15:50:15 +08:00
8ecc971940 Jeecg-Boot 2.1.3 大屏版本发布 2019-12-25 15:14:54 +08:00
66b3b290fb Jeecg-Boot 2.1.3 大屏版本发布 2019-12-25 13:36:36 +08:00
a9c7f3e547 Jeecg-Boot 2.1.3 大屏版本发布 2019-12-25 13:34:06 +08:00
b54dada91e 2.1.2 大屏版本发布 2019-12-25 13:28:04 +08:00
3ead7c3ae1 Merge branch 'master' of https://github.com/zhangdaiscott/jeecg-boot.git 2019-12-25 13:26:13 +08:00
9c44ffaa8e 2.1.3 大屏版本发布 2019-12-25 13:25:10 +08:00
ea5ef384f2 2.1.3 大屏版本发布 2019-12-25 13:14:08 +08:00
73f7acfd5a Merge pull request #787 from a63149300/patch-1
Update UserList.vue
2019-12-15 16:38:43 +08:00
728d62f851 Jeecg-Boot 2.1.2版本发布 2019-12-09 14:46:40 +08:00
cd471735d7 Update UserList.vue
修改状态文字
2019-12-06 09:13:44 +08:00
30b35af000 提供oracle11g\sqlserver2017脚步 2019-11-27 15:56:17 +08:00
3372c88607 统计 2019-11-25 15:04:34 +08:00
ce95008fdd 统计下载量 2019-11-25 14:58:00 +08:00
bb54b20734 统计下载 2019-11-25 14:56:27 +08:00
bd790ee24b 统计下载 2019-11-25 14:54:46 +08:00
11018ab29f 统计下载 2019-11-25 14:53:26 +08:00
67c4f7b3d9 Jeecg-Boot 2.1.2 版本发布 2019-11-22 09:45:28 +08:00
9bd67f9905 Jeecg-Boot 2.1.2版本发布 2019-11-21 18:20:40 +08:00
283be0480e Jeecg-Boot 2.1.2版本发布 2019-11-21 18:17:25 +08:00
1309 changed files with 515110 additions and 32800 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
## ide
**/.idea
*.iml
## backend
**/target
**/logs
## front
**/*.lock

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.

229
README.md
View File

@ -1,21 +1,18 @@
2019年度最受欢迎中国开源软件评选
请给JeecgBoot 投票,谢谢支持。
https://www.oschina.net/p/jeecg-boot
![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.1发布日期20191021
当前最新版本: 2.4.0发布日期2020-12-01
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-JEECG团队-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-2.0-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.4-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
@ -24,85 +21,98 @@ 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.xSpringCloudAnt Design&VueMybatis-plusShiroJWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE) 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零代码`Online表单开发、Online报表、报表配置能力、在线图表设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力可插拔等等
JEECG宗旨是: 简单功能由Online Coding配置实现在线配置表单、在线配置报表、在线图表设计、在线设计流程、在线设计表单复杂功能由代码生成器生成进行手工Merge既保证了智能又兼顾了灵活;
业务流程采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。
`JEECG宗旨是:` 简单功能由OnlineCoding配置实现,做到`零代码开发`复杂功能由代码生成器生成进行手工Merge 实现`低代码开发`,既保证了`智能`又兼顾`灵活`;实现了低代码开发的同时又支持灵活编码,解决了当前低代码产品普遍不灵活的弊端!
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、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)
- 视频教程 [JeecgBoot入门系列视频](https://space.bilibili.com/454617261/channel/detail?cid=84186)
- 微服务启动: [单体升级为微服务启动文档2.4+](http://doc.jeecg.com/2043906)
- 常见问题: [入门常见问题大全](http://www.jeecg.org/forum.php?mod=viewthread&tid=7816&extra=page%3D1)
- 在线演示 [http://boot.jeecg.com](http://boot.jeecg.com)
- 更新日志: [版本日志](http://www.jeecg.com/#/doc/changelog)
- 视频教程 [JeecgBoot入门视频](http://www.jeecg.com/doc/video)
- 常见问题: [入门常见问题Q&A](http://jeecg.com/doc/qa)
- 更新日志: [版本日志](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布局拥有更高的性能、更灵活的扩展、更强大的功能
@ -125,13 +135,17 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
#### 后端
- 基础框架Spring Boot 2.1.3.RELEASE
- 基础框架Spring Boot 2.3.5.RELEASE
- 持久层框架Mybatis-plus_3.1.2
- 微服务框架: Spring Cloud Alibaba 2.2.3.RELEASE
- 安全框架Apache Shiro 1.4.0Jwt_3.7.0
- 持久层框架Mybatis-plus 3.4.1
- 数据库连接池阿里巴巴Druid 1.1.10
- 安全框架Apache Shiro 1.7.0Jwt 3.11.0
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywarking
- 数据库连接池阿里巴巴Druid 1.1.22
- 缓存框架redis
@ -155,6 +169,7 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
### 功能模块
```
├─系统管理
@ -164,14 +179,17 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ ├─权限设置(支持按钮权限、数据权限)
│ ├─表单权限(控制字段禁用、隐藏)
│ ├─部门管理
│ ├─我的部门(二级管理员)
│ └─字典管理
│ └─分类字典
│ └─分类字典
│ └─系统公告
│ └─我的组织机构
│ └─职务管理
│ └─通讯录
│ └─多租户管理
├─消息中心
│ ├─消息管理
│ ├─模板管理
├─智能化功能
├─代码生成器(低代码)
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
│ ├─代码生成器模板提供4套模板分别支持单表和一对多模型不同风格选择
│ ├─代码生成器模板生成代码自带excel导入导出
@ -180,6 +198,7 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ ├─Excel导入导出工具集成支持单表一对多 导入导出)
│ ├─平台移动自适应支持
├─系统监控
│ ├─Gateway路由网关
│ ├─性能扫描监控
│ │ ├─监控 Redis
│ │ ├─Tomcat
@ -205,7 +224,13 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ └─进度条
│ └─排名列表
│ └─等等
│─大屏模板
│ ├─作战指挥中心大屏
│ └─物流服务中心大屏
│─常用示例
│ ├─自定义组件
│ ├─对象存储(对接阿里云)
│ ├─JVXETable示例各种复杂ERP布局示例
│ ├─单表模型例子
│ └─一对多模型例子
│ └─打印例子
@ -214,7 +239,10 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ └─常用选择组件
│ └─异步树table
│ └─接口模拟测试
│ └─表格合计示例
│ └─异步树列表示例
│ └─一对多JEditable
│ └─JEditable组件示例
│ └─图片拖拽排序
│ └─图片翻页
│ └─图片预览
@ -246,16 +274,23 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
│ └─异常页面
│ └─个人页面
├─高级功能
│ ├─系统编码规则
│ ├─提供单点登录CAS集成方案
│ ├─提供APP发布方案
│ ├─集成Websocket消息通知机制
├─Online在线开发(暂未开源)
├─Online在线开发(低代码)
│ ├─Online在线表单 - 功能已开放
│ ├─在线代码生成器 - 功能已开放
│ ├─Online在线图表
│ ├─Online图表模板配置
│ ├─Online在线报表
│ ├─高级表单设计器
│ ├─Online代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放
│ ├─Online在线图表(暂不开源)
│ ├─Online图表模板配置(暂不开源)
│ ├─Online布局设计(暂不开源)
│ ├─多数据源管理 - 功能已开放
├─积木报表设计器(低代码)
│ ├─打印设计器
│ ├─数据报表设计
│ ├─图形报表设计支持echart
│ ├─大屏设计器(暂不开源)
│─流程模块功能 (暂不开源)
│ ├─流程设计器
│ ├─在线表单设计
@ -274,6 +309,47 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
```
## 微服务整体解决方案(2.4+版本)
1、服务注册和发现 Nacos √
2、统一配置中心 Nacos √
3、路由网关 gateway(三种加载方式) √
4、分布式 http feign √
5、熔断和降级 Sentinel √
6、分布式文件 Minio、阿里OSS √
7、统一权限控制 JWT + Shiro √
8、服务监控 SpringBootAdmin√
9、链路跟踪 Skywarking [参考文档](https://www.kancloud.cn/zhangdaiscott/jeecgcloud/1771670)
10、消息中间件 RabbitMQ √
11、分布式任务 xxl-job √
12、分布式事务 Seata
13、分布式日志 elk + kafa
14、支持 docker-compose、k8s、jenkins
15、CAS 单点登录 √
16、路由限流 √
#### 微服务架构图
![微服务架构图](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/jeecgboot-weifuwu-cloud.png "在这里输入图片标题")
### Jeecg Boot 产品功能蓝图
![功能蓝图](https://static.jeecg.com/upload/test/Jeecg-Boot-lantu202005_1590912449914.jpg "在这里输入图片标题")
后台开发环境和依赖
@ -283,7 +359,7 @@ Jeecg-Boot快速开发平台可以应用在任何J2EE项目的开发中
- jdk8
- mysql
- redis
- 数据库脚jeecg-boot\docs\jeecg-boot-mysql.sql
- 数据库脚jeecg-boot/db/jeecgboot-mysql-5.7.sql
- 默认登录账号: admin/123456
@ -333,10 +409,13 @@ yarn run lint
系统效果
----
##### 大屏模板
![输入图片说明](https://static.oschina.net/uploads/img/201912/25133248_Ag1C.jpg "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201912/25133301_k9Kc.jpg "在这里输入图片标题")
##### PC端
![输入图片说明](https://static.oschina.net/uploads/img/201904/14155402_AmlV.png "在这里输入图片标题")
![输入图片说明](https://oscimg.oschina.net/oscnet/ba807921197596ba56f495d4b22ee3280ca.jpg "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160657_cHwb.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160813_KmXS.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201904/14160935_Nibs.png "在这里输入图片标题")
@ -402,15 +481,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,3 @@
NODE_ENV=production
VUE_APP_PLATFORM_NAME=Jeecg-Boot 企业级快速开发平台
VUE_APP_SSO=false

View File

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

View File

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

View File

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

View File

@ -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,7 +1,7 @@
Ant Design Jeecg Vue
====
当前最新版本: 2.0.2发布日期20190708
当前最新版本: 2.4.0发布日期20201201
Overview
----
@ -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' }]
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,20 @@
{
"name": "vue-antd-jeecg",
"version": "2.1.1",
"version": "2.4.0",
"private": true,
"scripts": {
"pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
"serve": "vue-cli-service serve",
"build:test": "vue-cli-service build --mode test",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@antv/data-set": "^0.10.2",
"@jeecg/antd-online": "1.2.0",
"@tinymce/tinymce-vue": "^2.0.0",
"ant-design-vue": "^1.4.0",
"apexcharts": "^3.6.5",
"@jeecg/antd-online-mini": "2.4.0-beta4",
"ant-design-vue": "^1.7.2",
"@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 +22,28 @@
"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.8",
"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",
"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,14 +51,14 @@
"@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"
"vue-template-compiler": "^2.6.10",
"html-webpack-plugin": "^4.2.0",
"compression-webpack-plugin": "^3.1.0"
},
"eslintConfig": {
"root": true,
@ -64,7 +67,7 @@
},
"extends": [
"plugin:vue/strongly-recommended",
"eslint:recommended"
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
@ -92,7 +95,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": {
@ -103,6 +109,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,9 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Jeecg-Boot 快速开发平台</title>
<title>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="<%= BASE_URL %>cdn/babel-polyfill/polyfill_7_2_5.js"></script>
<style>
html,
body,
@ -242,16 +242,10 @@
<!-- 全局配置 -->
<script>
window._CONFIG = {};
window._CONFIG['domianURL'] = 'http://127.0.0.1:8080/jeecg-boot';
window._CONFIG['casPrefixUrl'] = 'http://cas.example.org:8443/cas';
window._CONFIG['imgDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/view';
window._CONFIG['downloadUrl'] = window._CONFIG['domianURL'] + '/sys/common/download';
window._CONFIG['pdfDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/pdf/pdfPreviewIframe';
</script>
</head>
<body>
<!-- built files will be auto injected -->
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>

View File

@ -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,16 +1,10 @@
import { getAction,deleteAction,putAction,postAction} from '@/api/manage'
////根路径
// const doMian = "/jeecg-boot/";
////图片预览请求地址
// const imgView = "http://localhost:8080/jeecg-boot/sys/common/view/";
import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
import Vue from 'vue'
import {UI_CACHE_DB_DICT_DATA } from "@/store/mutation-types"
//角色管理
const addRole = (params)=>postAction("/sys/role/add",params);
const editRole = (params)=>putAction("/sys/role/edit",params);
// const getRoleList = (params)=>getAction("/sys/role/list",params);
// const deleteRole = (params)=>deleteAction("/sys/role/delete",params);
// const deleteRoleList = (params)=>deleteAction("/sys/role/deleteBatch",params);
const checkRoleCode = (params)=>getAction("/sys/role/checkRoleCode",params);
const queryall = (params)=>getAction("/sys/role/queryall",params);
@ -19,32 +13,25 @@ const addUser = (params)=>postAction("/sys/user/add",params);
const editUser = (params)=>putAction("/sys/user/edit",params);
const queryUserRole = (params)=>getAction("/sys/user/queryUserRole",params);
const getUserList = (params)=>getAction("/sys/user/list",params);
// const deleteUser = (params)=>deleteAction("/sys/user/delete",params);
// const deleteUserList = (params)=>deleteAction("/sys/user/deleteBatch",params);
const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
//验证用户是否存在
const checkOnlyUser = (params)=>getAction("/sys/user/checkOnlyUser",params);
//改变密码
const changPassword = (params)=>putAction("/sys/user/changPassword",params);
const changePassword = (params)=>putAction("/sys/user/changePassword",params);
//权限管理
const addPermission= (params)=>postAction("/sys/permission/add",params);
const editPermission= (params)=>putAction("/sys/permission/edit",params);
const getPermissionList = (params)=>getAction("/sys/permission/list",params);
/*update_begin author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
const getSystemMenuList = (params)=>getAction("/sys/permission/getSystemMenuList",params);
const getSystemSubmenu = (params)=>getAction("/sys/permission/getSystemSubmenu",params);
/*update_end author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
const getSystemSubmenuBatch = (params) => getAction('/sys/permission/getSystemSubmenuBatch', params)
// const deletePermission = (params)=>deleteAction("/sys/permission/delete",params);
// const deletePermissionList = (params)=>deleteAction("/sys/permission/deleteBatch",params);
const queryTreeList = (params)=>getAction("/sys/permission/queryTreeList",params);
const queryTreeListForRole = (params)=>getAction("/sys/role/queryTreeList",params);
const queryListAsync = (params)=>getAction("/sys/permission/queryListAsync",params);
const queryRolePermission = (params)=>getAction("/sys/permission/queryRolePermission",params);
const saveRolePermission = (params)=>postAction("/sys/permission/saveRolePermission",params);
//const queryPermissionsByUser = (params)=>getAction("/sys/permission/queryByUser",params);
const queryPermissionsByUser = (params)=>getAction("/sys/permission/getUserPermissionByToken",params);
const queryPermissionsByUser = ()=>getAction("/sys/permission/getUserPermissionByToken");
const loadAllRoleIds = (params)=>getAction("/sys/permission/loadAllRoleIds",params);
const getPermissionRuleList = (params)=>getAction("/sys/permission/getPermRuleListByPermId",params);
const queryPermissionRule = (params)=>getAction("/sys/permission/queryPermissionRule",params);
@ -56,25 +43,35 @@ 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);
const deleteLogList = (params)=>deleteAction("/sys/log/deleteBatch",params);
//数据字典
const addDict = (params)=>postAction("/sys/dict/add",params);
const editDict = (params)=>putAction("/sys/dict/edit",params);
//const getDictList = (params)=>getAction("/sys/dict/list",params);
const treeList = (params)=>getAction("/sys/dict/treeList",params);
// const delDict = (params)=>deleteAction("/sys/dict/delete",params);
//const getDictItemList = (params)=>getAction("/sys/dictItem/list",params);
const addDictItem = (params)=>postAction("/sys/dictItem/add",params);
const editDictItem = (params)=>putAction("/sys/dictItem/edit",params);
//const delDictItem = (params)=>deleteAction("/sys/dictItem/delete",params);
//const delDictItemList = (params)=>deleteAction("/sys/dictItem/deleteBatch",params);
//字典标签专用通过code获取字典数组
export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItems/${code}`,params);
//从缓存中获取字典配置
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);
@ -82,22 +79,25 @@ const doReovkeData = (params)=>getAction("/sys/annountCement/doReovkeData",param
//获取系统访问量
const getLoginfo = (params)=>getAction("/sys/loginfo",params);
const getVisitInfo = (params)=>getAction("/sys/visitInfo",params);
//数据日志访问
// const getDataLogList = (params)=>getAction("/sys/dataLog/list",params);
// 根据部门主键查询用户信息
const queryUserByDepId = (params)=>getAction("/sys/user/queryUserByDepId",params);
// 查询用户角色表里的所有信息
const queryUserRoleMap = (params)=>getAction("/sys/user/queryUserRoleMap",params);
// 重复校验
const duplicateCheck = (params)=>getAction("/sys/duplicate/check",params);
// 加载分类字典
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,
// doMian,
addRole,
editRole,
checkRoleCode,
@ -108,7 +108,7 @@ export {
queryall,
frozenBatch,
checkOnlyUser,
changPassword,
changePassword,
getPermissionList,
addPermission,
editPermission,
@ -137,12 +137,21 @@ export {
getLoginfo,
getVisitInfo,
queryUserByDepId,
queryUserRoleMap,
duplicateCheck,
queryTreeListForRole,
getSystemMenuList,
getSystemSubmenu,
loadCategoryData
getSystemSubmenuBatch,
loadCategoryData,
checkRuleByCode,
queryDepartPermission,
saveDepartPermission,
queryTreeListForDeptRole,
queryDeptRolePermission,
saveDeptRolePermission,
queryMyDepartTreeList,
getUserNoticeInfo,
getDictItemsFromCache
}

View File

@ -36,15 +36,15 @@ export function getSmsCaptcha(parameter) {
})
}
export function getInfo() {
return axios({
url: '/api/user/info',
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
// export function getInfo() {
// return axios({
// url: '/api/user/info',
// method: 'get',
// headers: {
// 'Content-Type': 'application/json;charset=UTF-8'
// }
// })
// }
export function logout(logoutToken) {
return axios({
@ -56,3 +56,19 @@ export function logout(logoutToken) {
}
})
}
/**
* 第三方登录
* @param token
* @param thirdType
* @returns {*}
*/
export function thirdLogin(token,thirdType) {
return axios({
url: `/sys/thirdLogin/getLoginUser/${token}/${thirdType}`,
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}

View File

@ -1,11 +1,12 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
const api = {
user: '/api/user',
role: '/api/role',
service: '/api/service',
permission: '/api/permission',
permissionNoPager: '/api/permission/no-pager'
user: '/mock/api/user',
role: '/mock/api/role',
service: '/mock/api/service',
permission: '/mock/api/permission',
permissionNoPager: '/mock/api/permission/no-pager'
}
export default api
@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 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 {
@ -40,3 +47,15 @@
/*列表中范围查询样式*/
.query-group-cust{width: calc(50% - 10px)}
.query-group-split-cust:before{content:"~";width: 20px;display: inline-block;text-align: center}
/*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}

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -5,35 +5,39 @@ import store from '@/store'
* 单点登录
*/
const init = (callback) => {
if (process.env.VUE_APP_SSO == 'true') {
console.log("-------单点登录开始-------");
let token = Vue.ls.get(ACCESS_TOKEN);
let st = getUrlParam("ticket");
var sevice = "http://"+window.location.host+"/";
if(token){
let sevice = "http://" + window.location.host + "/";
if (token) {
loginSuccess(callback);
}else{
if(st){
validateSt(st,sevice,callback);
}else{
var serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
} else {
if (st) {
validateSt(st, sevice, callback);
} else {
let serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl'] + "/login?service=" + serviceUrl;
}
}
console.log("-------单点登录结束-------");
}else{
callback && callback()
}
};
const SSO = {
init: init
};
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 +61,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,217 @@
<template>
<div>
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
<div :key="fileKey" style="position: relative;">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
<span style="margin-left:5px">上传中</span>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" :title="file.name">
<a-icon type="paper-clip"/>
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
</a-tooltip>
<a-tooltip v-else :title="file.name">
<a-icon type="paper-clip" style="color:red;"/>
<span style="color:red;margin-left:5px">{{ ellipsisFileName }}</span>
</a-tooltip>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
<a-tooltip title="操作">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '上传文件'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
<script>
import { getFileAccessHttpUrl } from '@api/manage'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import JFilePop from '@/components/jeecg/minipop/JFilePop'
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
export default {
name: 'JVxeFileCell',
mixins: [JVxeCellMixins],
components: {JFilePop},
props: {},
data() {
return {
innerFile: null,
number:0,
}
},
computed: {
/** upload headers */
uploadHeaders() {
let {originColumn: col} = this
let headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
/** 上传请求地址 */
uploadAction() {
if (!this.originColumn.action) {
return window._CONFIG['domianURL'] + '/sys/common/upload'
} else {
return this.originColumn.action
}
},
hasFile() {
return this.innerFile != null
},
ellipsisFileName() {
let length = 5
let file = this.innerFile
if (!file || !file.name) {
return ''
}
if (file.name.length > length) {
return file.name.substr(0, length) + '…'
}
return file.name
},
responseName() {
if (this.originColumn.responseName) {
return this.originColumn.responseName
} else {
return 'message'
}
},
},
watch: {
innerValue: {
immediate: true,
handler() {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
},
},
},
methods: {
// 点击更多按钮
handleMoreOperation(originColumn) {
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if (originColumn.number) {
this.number = originColumn.number
} else {
this.number = 0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(originColumn && originColumn.fieldExtendJson){
let json = JSON.parse(originColumn.fieldExtendJson);
this.number = json.uploadnum?json.uploadnum:0;
}
let path = ''
if (this.innerFile) {
path = this.innerFile.path
}
this.$refs.filePop.show('', path)
},
// 更多上传回调
handleFileSuccess(file) {
if (file) {
this.innerFile.path = file.path
this.handleChangeCommon(this.innerFile)
}
},
handleChangeUpload(info) {
let {originColumn: col} = this
let {file} = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (file.response) {
value['responseName'] = file.response[this.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
},
handleClickDownloadFile() {
let {url, path} = this.innerFile || {}
if (!url || url.length === 0) {
if (path && path.length > 0) {
url = getFileAccessHttpUrl(path.split(',')[0])
}
}
if (url) {
window.open(url)
}
},
handleClickDeleteFile() {
this.handleChangeCommon(null)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {visible: true},
getValue: value => JVxeUploadCell.enhanced.getValue(value),
setValue: value => JVxeUploadCell.enhanced.setValue(value),
}
}
</script>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,242 @@
<template>
<div>
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
<div :key="fileKey" style="position: relative;">
<template v-if="!file || !(file['url'] || file['path'] || file['message'])">
<a-tooltip :title="'请稍后: ' + JSON.stringify (file) + ((file['url'] || file['path'] || file['message']))">
<a-icon type="loading"/>
</a-tooltip>
</template>
<template v-else-if="file['path']">
<img class="j-editable-image" :src="imgSrc" alt="无图片" @click="handleMoreOperation"/>
</template>
<template v-else>
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError"/>
</template>
<template slot="addonBefore" style="width: 30px">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
<a-tooltip title="操作">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '上传图片'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
<script>
import { getFileAccessHttpUrl } from '@api/manage'
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import JFilePop from '@/components/jeecg/minipop/JFilePop'
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
export default {
name: 'JVxeImageCell',
mixins: [JVxeCellMixins],
components: {JFilePop},
props: {},
data() {
return {
innerFile: null,
number:0
}
},
computed: {
/** upload headers */
uploadHeaders() {
let {originColumn: col} = this
let headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
/** 上传请求地址 */
uploadAction() {
if (!this.originColumn.action) {
return window._CONFIG['domianURL'] + '/sys/common/upload'
} else {
return this.originColumn.action
}
},
hasFile() {
return this.innerFile != null
},
/** 预览图片地址 */
imgSrc() {
if (this.innerFile) {
if (this.innerFile['url']) {
return this.innerFile['url']
} else if (this.innerFile['path']) {
let path = this.innerFile['path'].split(',')[0]
return getFileAccessHttpUrl(path)
}
}
return ''
},
responseName() {
if (this.originColumn.responseName) {
return this.originColumn.responseName
} else {
return 'message'
}
},
},
watch: {
innerValue: {
immediate: true,
handler() {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
},
},
},
methods: {
// 点击更多按钮
handleMoreOperation(originColumn) {
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if (originColumn.number) {
this.number = originColumn.number
} else {
this.number = 0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(originColumn && originColumn.fieldExtendJson){
let json = JSON.parse(originColumn.fieldExtendJson);
this.number = json.uploadnum?json.uploadnum:0;
}
let path = ''
if (this.innerFile) {
path = this.innerFile.path
}
this.$refs.filePop.show('', path, 'img')
},
// 更多上传回调
handleFileSuccess(file) {
if (file) {
this.innerFile.path = file.path
this.handleChangeCommon(this.innerFile)
}
},
// 弹出上传出错详细信息
handleClickShowImageError() {
let file = this.innerFile || null
if (file && file['message']) {
this.$error({title: '上传出错', content: '错误信息:' + file['message'], maskClosable: true})
}
},
handleChangeUpload(info) {
let {originColumn: col} = this
let {file} = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (file.response) {
value['responseName'] = file.response[this.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[this.responseName]
this.handleChangeCommon(value)
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
},
handleClickDownloadFile() {
if (this.imgSrc) {
window.open(this.imgSrc)
}
},
handleClickDeleteFile() {
this.handleChangeCommon(null)
},
},
// 【组件增强】注释详见JVxeCellMixins.js
enhanced: {
switches: {visible: true},
getValue: value => JVxeUploadCell.enhanced.getValue(value),
setValue: value => JVxeUploadCell.enhanced.setValue(value),
}
}
</script>
<style scoped lang="less">
.j-editable-image {
height: 32px;
max-width: 100px !important;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

@ -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 0 32px 32px' }">
<div :style="{ padding: '0 50px 32px 0' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
<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,
@ -23,13 +25,13 @@
dataSource: {
type: Array,
default: () => [
{ type: '10:10', bar: 2, line: 2 },
{ type: '10:15', bar: 6, line: 3 },
{ type: '10:20', bar: 2, line: 5 },
{ type: '10:25', bar: 9, line: 1 },
{ type: '10:30', bar: 2, line: 3 },
{ type: '10:35', bar: 2, line: 1 },
{ type: '10:40', bar: 1, line: 2 }
{ type: '10:10', bar: 200, line: 1000 },
{ type: '10:15', bar: 600, line: 1000},
{ type: '10:20', bar: 200, line: 1000},
{ type: '10:25', bar: 900, line: 1000},
{ type: '10:30', bar: 200, line: 1000},
{ type: '10:35', bar: 200, line: 1000},
{ type: '10:40', bar: 100, line: 1000}
]
},
height: {
@ -39,6 +41,7 @@
},
data() {
return {
padding: { top:50, right:50, bottom:100, left:50 },
scale: [{
dataKey: 'bar',
min: 0

View File

@ -1,36 +1,43 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="height" :data="data">
<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'
export default {
name: 'BarMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource:{
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:{
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
@ -55,13 +62,30 @@
})
// bar 使用不了 - 和 / 所以替换下
return dv.rows.map(row => {
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,6 +1,6 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<v-chart :forceFit="true" :height="350" :data="chartData" :scale="scale">
<v-chart :forceFit="true" :height="300" :data="chartData" :scale="scale">
<v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
<v-axis
dataKey="value"
@ -32,7 +32,7 @@
type="arc"
:zIndex="1"
:start="arcGuide2Start"
:end="getArcGuide2End()"
:end="getArcGuide2End"
:vStyle="arcGuide2Style"
></v-guide>
<v-guide
@ -88,7 +88,7 @@
}];
const data = [
{ value: 0},
{ value: 7.0 },
];
export default {
@ -96,7 +96,7 @@
props:{
datasource:{
type: Number,
default:0
default:7
},
title: {
type: String,

View File

@ -0,0 +1,61 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="datasource"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const data = []
for (let i = 0; i < 12; i += 1) {
data.push({
x: `${i + 1}`,
y: Math.floor(Math.random() * 1000) + 200
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: "Bar",
props: {
title: {
type: String,
default: ''
}
},
mounted(){
this.datasource = data
},
data () {
return {
datasource:[],
scale,
tooltip
}
}
}
</script>

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,
@ -42,6 +44,11 @@
type: Array,
default: () => ['jeecg', 'jeebt']
},
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
aliases:{
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
@ -50,6 +57,7 @@
data() {
return {
scale: [{
type: 'cat',
dataKey: 'x',
min: 0,
max: 1
@ -66,7 +74,17 @@
key: 'x',
value: 'y'
})
return dv.rows
let rows = dv.rows
// 替换别名
rows.forEach(row => {
for (let item of this.aliases) {
if (item.field === row.x) {
row.x = item.alias
break
}
}
})
return rows
}
}
}

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'" :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,13 +35,30 @@ export async function initDictOptions(dictCode) {
* @return String
*/
export function filterDictText(dictOptions, text) {
let re = "";
dictOptions.forEach(function (option) {
if (text === option.value) {
re = option.text;
// --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(',')
}
});
return re;
for (let txt of splitText) {
let dictText = txt
for (let dictItem of dictOptions) {
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:20200323------for: 字典翻译 text 允许逗号分隔 ---
}
/**
@ -44,21 +68,35 @@ 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 "";
return text;
}
return re.substring(0,re.length-1);
}
@ -68,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,9 @@
:disabled="disabled"
mode="multiple"
:placeholder="placeholder"
:getPopupContainer="(node) => node.parentNode"
optionFilterProp="children"
:filterOption="filterOption"
allowClear>
<a-select-option
v-for="(item,index) in dictOptions"
@ -24,7 +27,7 @@
</template>
<script>
import {ajaxGetDictItems} from '@/api/api'
import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
export default {
name: 'JMultiSelectTag',
props: {
@ -49,12 +52,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 +77,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) {
@ -85,7 +99,12 @@
},
getCurrentDictOptions(){
return this.dictOptions
},
// update--begin--autor:lvdandan-----date:20201120------forLOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
filterOption(input, option) {
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
// update--end--autor:lvdandan-----date:20201120------forLOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
},
model: {
prop: 'value',

View File

@ -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

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

View File

@ -0,0 +1,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=>{
@ -119,11 +119,9 @@
/** 数据回显*/
loadItemByCode(){
if(!this.value || this.value=="0"){
this.treeValue = ""
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"
@ -14,6 +15,7 @@
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/media'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
@ -22,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
@ -42,11 +47,12 @@
},
plugins: {
type: [String, Array],
default: 'lists image media table textcolor wordcount contextmenu fullscreen'
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
},
toolbar: {
type: [String, Array],
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists image media table | removeformat | fullscreen'
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
branding:false
}
},
data() {
@ -61,26 +67,77 @@
toolbar: this.toolbar,
branding: false,
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,238 @@
<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':''">
<div style="width:104px;height:104px">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>
<div v-else class="iconp">
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
<div class="ant-upload-text">{{ text }}</div>
</div>
</div>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
<img alt="example" style="width: 100%" :src="previewImage"/>
</a-modal>
</a-upload>
</div>
</template>
<script>
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
},
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性用于判断上传数量
number:{
type:Number,
required:false,
default:0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性用于判断上传数量
},
watch:{
value: {
handler(val,oldValue) {
if (val instanceof Array) {
this.initFileList(val.join(','))
} else {
this.initFileList(val)
}
if(!val || val.length==0){
this.picUrl = false;
}
},
//立刻执行handler
immediate: true
}
},
created(){
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
//update-begin-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选返回选定的元素。
if(this.number>0 && this.isMultiple){
fileList = fileList.slice(-this.number);
}
//update-end-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选返回选定的元素。
if(info.file.status==='done'){
if(info.file.response.success){
this.picUrl = true;
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,29 @@
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',
'link',
'divider',
'code',
'codeblock'
]
}

View File

@ -0,0 +1,255 @@
<template>
<div>
<div class="j-markdown-editor" :id="id"/>
<div v-if="isShow">
<j-modal
title="图片上传"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
@ok="handleOk">
<a-tabs default-active-key="1" @change="handleChange">
<a-tab-pane tab="本地图片上传" key="1" :forceRender="true">
<j-upload v-model="fileList" :number="1"></j-upload>
<div style="margin-top: 20px">
<a-input v-model="remark" placeholder="请填写备注"></a-input>
</div>
</a-tab-pane>
<a-tab-pane tab="网络图片地址" key="2" :forceRender="true">
<a-input v-model="networkPic" placeholder="请填写网络图片地址"></a-input>
<a-input style="margin-top: 20px" v-model="remark" placeholder="请填写备注"></a-input>
</a-tab-pane>
</a-tabs>
</j-modal>
</div>
</div>
</template>
<script>
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'
import JUpload from '@/components/jeecg/JUpload'
import { getFileAccessHttpUrl } from '@/api/manage'
export default {
name: 'JMarkdownEditor',
components: {
JUpload,
},
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,
isShow:false,
activeIndex:"1",
dialogVisible:false,
index:"1",
fileList:[],
remark:"",
imageName:"",
imageUrl:"",
networkPic:""
}
},
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())
})
//--begin 添加自定义上传按钮
/*
* 添加自定义按钮
*/
//获取编辑器上的功能条
let toolbar = this.editor.getUI().getToolbar();
let fileDom = this.$refs.files;
//添加图片点击事件
this.editor.eventManager.addEventType('isShowClickEvent');
this.editor.eventManager.listen('isShowClickEvent', () => {
this.isShow = true
this.dialogVisible = true
});
//addImageBlobHook图片上传、剪切、拖拽都会走此方法
// 删除默认监听事件
this.editor.eventManager.removeEventHandler('addImageBlobHook')
// 添加自定义监听事件
this.editor.eventManager.listen('addImageBlobHook', (blob, callback) => {
this.upload(blob, url => {
callback(url)
})
})
// 添加自定义按钮 第二个参数代表位置,不传默认放在最后
toolbar.insertItem(15,{
type: 'button',
options:{
name: 'customize',
className: 'tui-image tui-toolbar-icons',
event: 'isShowClickEvent',
tooltip: '上传图片',
}
//
});
//--end 添加自定义上传按钮
},
destroyEditor() {
if (!this.editor) return
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()
},
handleOk(){
if(this.index=='1'){
this.imageUrl = getFileAccessHttpUrl(this.fileList)
if(this.remark){
this.addImgToMd(this.imageUrl,this.remark)
}else{
this.addImgToMd(this.imageUrl,"")
}
}else{
if(this.remark){
this.addImgToMd(this.networkPic,this.remark)
}else{
this.addImgToMd(this.networkPic,"")
}
}
this.index="1"
this.fileList=[]
this.imageName="";
this.imageUrl="";
this.remark=""
this.networkPic=""
this.dialogVisible=false
this.isShow=false;
},
handleClose(done) {
done();
},
handleChange(val){
this.fileList=[]
this.remark=""
this.imageName=""
this.imageUrl=""
this.networkPic=""
this.index=val
},
//添加图片到markdown
addImgToMd(data,name) {
let editor = this.editor.getCodeMirror();
let editorHtml = this.editor.getCurrentModeEditor();
let isMarkdownMode = this.editor.isMarkdownMode();
if (isMarkdownMode) {
editor.replaceSelection(`![${name}](${data})`);
} else {
let range = editorHtml.getRange();
let img = document.createElement('img');
img.src = `${data}`;
img.alt = name;
range.insertNode(img);
}
},
},
model: {
prop: 'value',
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,212 @@
<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"
:param="param"
@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
},
//popup动态参数 支持系统变量语法
param:{
type: Object,
required: false,
default: ()=>{}
},
/** 分组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,13 +1,37 @@
<template>
<a-modal
<div class="j-super-query-box">
<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="handleOpen">高级查询</a-button>
</slot>
<j-modal
title="高级查询构造器"
:width="1000"
:visible="visible"
@cancel="handleCancel"
:mask="false"
wrapClassName="ant-modal-cust-warp"
: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">
@ -22,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"/>
@ -32,45 +56,108 @@
<a-form v-else layout="inline">
<a-form-item label="过滤条件匹配" style="margin-bottom: 12px;">
<a-select v-model="selectValue">
<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-select placeholder="选择查询字段" v-model="item.field" @select="(val,option)=>handleSelected(option,item)">
<a-select-option v-for="(f,fIndex) in fieldList" :key=" 'field'+fIndex" :value="f.value" :data-idx="fIndex">{{ f.text }}</a-select-option>
</a-select>
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<a-tree-select
showSearch
v-model="item.field"
:treeData="fieldTreeData"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
placeholder="选择查询字段"
allowClear
treeDefaultExpandAll
:getPopupContainer="node=>node.parentNode"
style="width: 100%"
@select="(val,option)=>handleSelected(option,item)"
>
</a-tree-select>
</a-col>
<a-col :span="4">
<a-select placeholder="匹配规则" v-model="item.rule">
<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">
<j-dict-select-tag v-if="item.dictCode" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<template v-if="item.dictCode">
<template v-if="item.type === 'table-dict'">
<j-popup
v-model="item.val"
:code="item.dictTable"
:field="item.dictCode"
:orgFields="item.dictCode"
:destFields="item.dictCode"
></j-popup>
</template>
<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' || item.type === 'sel_user'"
v-model="item.val"
:buttons="false"
:multiple="false"
placeholder="请选择用户"
:returnKeys="['id', item.customReturnField || 'username']"
/>
<j-select-depart
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
v-model="item.val"
:multi="false"
placeholder="请选择部门"
:customReturnField="item.customReturnField || 'id'"
/>
<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>
@ -86,12 +173,15 @@
<div slot="title">
保存的查询
</div>
<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>
@ -107,16 +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 },
mixins: [mixinDevice],
components: { JAreaLinkage, JMultiSelectTag, JDate, JSelectDepart, JSelectMultiUser },
props: {
/*
fieldList: [{
@ -147,14 +245,17 @@
},
// 保存查询条件的唯一 code通过该 code 区分
// 默认为 null代表以当前路由全路径为区分Code
saveCode: {
type: String,
default: 'testSaveCode'
default: null
}
},
data() {
return {
moment,
fieldTreeData: [],
prompt: {
visible: false,
@ -162,50 +263,107 @@
},
visible: false,
queryParamsModel: [{}],
queryParamsModel: [],
treeIcon: <a-icon type="file-text"/>,
treeData: [],
// 保存查询条件的treeData
saveTreeData: [],
// 保存查询条件的前缀名
saveCodeBefore: 'JSuperQuerySaved_',
selectValue: 'and',
// 查询类型过滤条件匹配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() {
let list = this.$ls.get(this.fullSaveCode)
if (list instanceof Array) {
this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
}
}
},
fieldList: {
deep: true,
immediate: true,
handler(val) {
let list = this.$ls.get(this.saveCodeBefore + val)
if (list instanceof Array) {
this.treeData = list.map(item => {
item.icon = this.treeIcon
return item
let mainData = [], subData = []
val.forEach(item => {
let data = { ...item }
data.label = data.label || data.text
let hasChildren = (data.children instanceof Array)
data.disabled = hasChildren
data.selectable = !hasChildren
if (hasChildren) {
data.children = data.children.map(item2 => {
let child = { ...item2 }
child.label = child.label || child.text
child.label = data.label + '-' + child.label
child.value = data.value + ',' + child.value
child.val = ''
return child
})
data.val = ''
subData.push(data)
} else {
mainData.push(data)
}
console.log({ list })
})
this.fieldTreeData = mainData.concat(subData)
}
}
},
methods: {
show() {
if (!this.queryParamsModel || this.queryParamsModel.length == 0) {
this.queryParamsModel = [{}]
if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
this.resetLine()
}
this.visible = true
},
handleOk() {
console.log('---高级查询参数--->', this.queryParamsModel)
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)
}
this.$emit(this.callback, event.params, event.matchType)
// 移动端模式下关闭弹窗
if (this.izMobile) {
this.visible = false
}
this.emitCallback(event)
} else {
this.$emit(this.callback)
this.$message.warn("不能查询空条件")
}
},
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() {
this.close()
},
@ -214,24 +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(option, item) {
let index = option.data.attrs['data-idx']
let { type, dictCode } = this.fieldList[index]
handleSelected(node, item) {
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.queryParamsModel = [{}]
this.$emit(this.callback)
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 {
@ -240,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) {
@ -299,51 +482,167 @@
}
if (array.length === 1) {
let obj = array[0]
if (!obj.field || !obj.val || !obj.rule) {
if (!obj.field || (obj.val == null || obj.val === '') || !obj.rule) {
return true
}
}
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;
}
.j-super-query-modal {
/deep/ {
}
.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-tree /deep/ {
.ant-tree-switcher {
.j-super-query-history-empty {
/deep/ .ant-empty-image {
height: 80px;
line-height: 80px;
margin-bottom: 0;
}
/deep/ img {
width: 80px;
height: 65px;
}
/deep/ .ant-empty-description {
color: #afafaf;
margin: 8px 0;
}
}
.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

@ -22,7 +22,7 @@
data(){
return {
treeData:[],
treeValue:"",
treeValue: null,
url_root:"/sys/category/loadTreeRoot",
url_children:"/sys/category/loadTreeChildren",
url_view:'/sys/category/loadOne',
@ -97,7 +97,7 @@
methods:{
loadViewInfo(){
if(!this.value || this.value=="0"){
this.treeValue = ""
this.treeValue = null
}else{
let param = {
field:this.field,
@ -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' }"
@ -77,7 +78,7 @@
},
data () {
return {
treeValue:"",
treeValue: null,
treeData:[],
url:"/sys/dict/loadTreeData",
view:'/sys/dict/loadDictItem/',
@ -106,7 +107,7 @@
methods: {
loadItemByCode(){
if(!this.value || this.value=="0"){
this.treeValue = ""
this.treeValue = null
}else{
getAction(`${this.view}${this.dict}`,{key:this.value}).then(res=>{
if(res.success){
@ -211,7 +212,7 @@
onChange(value){
if(!value){
this.$emit('change', '');
this.treeValue = ''
this.treeValue = null
} else if (value instanceof Array) {
this.$emit('change', value.map(item => item.value).join(','))
this.treeValue = value
@ -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:{
@ -61,7 +99,7 @@
default:"temp"
},
value:{
type:String,
type:[String,Array],
required:false
},
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
@ -77,18 +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 [];
@ -100,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]
@ -122,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(",")
@ -130,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;
}
}
@ -147,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'){
@ -165,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',
@ -180,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()])
},
}

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