Commit 9ab6f4b663d8853476cdd57c3011d3b5e8098f8e

Authored by zhangdaiscott
1 parent 0304e06a

Jeecg-Boot 2.2.0 版本发布 | 重磅升级

Showing 335 changed files with 18928 additions and 10839 deletions

Too many changes to show.

To preserve performance only 40 of 335 files are displayed.

.github/ISSUE_TEMPLATE.md 0 → 100644
  1 +##### 版本号:
  2 +
  3 +
  4 +##### 问题描述:
  5 +
  6 +
  7 +##### 截图&代码:
  8 +
  9 +
  10 +
  11 +
  12 +友情提示: 未按格式要求发帖,会直接删掉。
... ...
README.md
... ... @@ -7,12 +7,12 @@
7 7 Jeecg-Boot 快速开发平台(前后端分离版本)
8 8 ===============
9 9  
10   -当前最新版本: 2.1.4(发布日期:2020-02-24
  10 +当前最新版本: 2.2.0(发布日期:2020-05-06
11 11  
12 12  
13 13 [![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
14 14 [![](https://img.shields.io/badge/Author-JEECG团队-orange.svg)](http://www.jeecg.com)
15   -[![](https://img.shields.io/badge/version-2.1.4-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
  15 +[![](https://img.shields.io/badge/version-2.2.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
16 16 [![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
17 17 [![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
18 18  
... ...
ant-design-vue-jeecg/README.md
1 1 Ant Design Jeecg Vue
2 2 ====
3 3  
4   -当前最新版本: 2.1.4(发布日期:2020-02-24
  4 +当前最新版本: 2.2.0(发布日期:2020-05-06
5 5  
6 6 Overview
7 7 ----
... ...
ant-design-vue-jeecg/idea.config.js
... ... @@ -6,19 +6,19 @@ function resolve (dir) {
6 6 }
7 7  
8 8 module.exports = {
9   - context: path.resolve(__dirname, './'),
10   - resolve: {
11   - extensions: ['.js', '.vue', '.json'],
12   - alias: {
13   - 'config': resolve('config'),
14   - '@': resolve('src'),
15   - '@views': resolve('src/views'),
16   - '@comp': resolve('src/components'),
17   - '@core': resolve('src/core'),
18   - '@utils': resolve('src/utils'),
19   - '@entry': resolve('src/entry'),
20   - '@router': resolve('src/router'),
21   - '@store': resolve('src/store')
22   - }
23   - }
24   -}
  9 + context: path.resolve(__dirname, './'),
  10 + resolve: {
  11 + extensions: ['.js', '.vue', '.json'],
  12 + alias: {
  13 + 'config': resolve('config'),
  14 + '@': resolve('src'),
  15 + '@views': resolve('src/views'),
  16 + '@comp': resolve('src/components'),
  17 + '@core': resolve('src/core'),
  18 + '@utils': resolve('src/utils'),
  19 + '@entry': resolve('src/entry'),
  20 + '@router': resolve('src/router'),
  21 + '@store': resolve('src/store')
  22 + }
  23 + },
  24 +}
25 25 \ No newline at end of file
... ...
ant-design-vue-jeecg/package.json
1 1 {
2 2 "name": "vue-antd-jeecg",
3   - "version": "2.1.4",
  3 + "version": "2.2.0",
4 4 "private": true,
5 5 "scripts": {
6   - "pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
  6 + "pre": "yarn --registry https://registry.npm.taobao.org || cnpm install || npm install --registry https://registry.npm.taobao.org ",
7 7 "serve": "vue-cli-service serve",
8 8 "build": "vue-cli-service build",
9 9 "lint": "vue-cli-service lint"
10 10 },
11 11 "dependencies": {
12   - "@antv/data-set": "^0.10.2",
13   - "@jeecg/antd-online-214": "^2.1.41",
  12 + "@antv/data-set": "^0.11.2",
  13 + "@jeecg/antd-online-beta220": "^1.0.1",
14 14 "@tinymce/tinymce-vue": "^2.0.0",
15   - "ant-design-vue": "^1.4.11",
16   - "apexcharts": "^3.6.5",
  15 + "ant-design-vue": "^1.5.2",
  16 + "area-data": "^5.0.6",
17 17 "axios": "^0.18.0",
18 18 "clipboard": "^2.0.4",
19 19 "codemirror": "^5.46.0",
... ... @@ -25,22 +25,20 @@
25 25 "md5": "^2.2.1",
26 26 "nprogress": "^0.2.0",
27 27 "tinymce": "^5.1.4",
  28 + "tui-editor": "^1.4.10",
28 29 "viser-vue": "^2.4.4",
29 30 "vue": "^2.6.10",
30   - "vue-apexcharts": "^1.3.2",
31   - "vue-class-component": "^6.0.0",
  31 + "vue-area-linkage": "^5.1.0",
32 32 "vue-cropper": "^0.4.8",
33 33 "vue-i18n": "^8.7.0",
34 34 "vue-loader": "^15.7.0",
35 35 "vue-ls": "^3.2.0",
36 36 "vue-photo-preview": "^1.1.3",
37 37 "vue-print-nb-jeecg": "^1.0.9",
38   - "vue-property-decorator": "^7.3.0",
39 38 "vue-router": "^3.0.1",
40 39 "vue-splitpane": "^1.0.4",
41 40 "vuedraggable": "^2.20.0",
42   - "vuex": "^3.0.1",
43   - "vuex-class": "^0.3.1"
  41 + "vuex": "^3.1.0"
44 42 },
45 43 "devDependencies": {
46 44 "@babel/polyfill": "^7.2.5",
... ... @@ -49,9 +47,10 @@
49 47 "@vue/cli-service": "^3.3.0",
50 48 "@vue/eslint-config-standard": "^4.0.0",
51 49 "babel-eslint": "^10.0.1",
  50 + "compression-webpack-plugin": "^3.1.0",
52 51 "eslint": "^5.16.0",
53 52 "eslint-plugin-vue": "^5.1.0",
54   - "html-webpack-plugin": "^4.0.0-beta.11",
  53 + "html-webpack-plugin": "^4.2.0",
55 54 "less": "^3.9.0",
56 55 "less-loader": "^4.1.0",
57 56 "vue-template-compiler": "^2.6.10"
... ... @@ -91,7 +90,7 @@
91 90 "vue/no-use-v-if-with-v-for": 0,
92 91 "vue/html-closing-bracket-newline": 0,
93 92 "vue/no-parsing-error": 0,
94   - "no-console": 0,
  93 + "no-console": 0,
95 94 "no-tabs": 0,
96 95 "indent": [1, 4]
97 96 }
... ...
ant-design-vue-jeecg/public/color.less
... ... @@ -7679,3 +7679,23 @@ font.medium {
7679 7679 font.weak {
7680 7680 color: #f5222d;
7681 7681 }
  7682 +
  7683 +
  7684 +// begin -------- JAreaLinkage 三级联动样式 --------------
  7685 +.cascader-menu-list .cascader-menu-option.hover,
  7686 +.cascader-menu-list .cascader-menu-option:hover {
  7687 + background-color: color(~`colorPalette("@{primary-color}", 1)`);
  7688 +}
  7689 +
  7690 +.area-selectable-list .area-select-option.hover {
  7691 + background-color: color(~`colorPalette("@{primary-color}", 1)`);
  7692 +}
  7693 +
  7694 +.area-select:hover {
  7695 + border-color: @primary-color;
  7696 +}
  7697 +
  7698 +.area-select:active {
  7699 + box-shadow: 0 0 0 2px color(~`colorPalette("@{primary-color}", 1)`);
  7700 +}
  7701 +// end -------- JAreaLinkage 三级联动样式 --------------
7682 7702 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/api/GroupRequest.js 0 → 100644
  1 +import Vue from 'vue'
  2 +
  3 +/**
  4 + * 将一个请求分组
  5 + *
  6 + * @param getPromise 传入一个可以获取到Promise对象的方法
  7 + * @param groupId 分组ID,如果不传或者为空则不分组
  8 + * @param expire 过期时间,默认 半分钟
  9 + */
  10 +export function httpGroupRequest(getPromise, groupId, expire = 1000 * 30) {
  11 + if (groupId == null || groupId === '') {
  12 + console.log("--------popup----------getFrom DB-------with---no--groupId ")
  13 + return getPromise()
  14 + }
  15 +
  16 + if (Vue.ls.get(groupId)) {
  17 + console.log("---------popup--------getFrom Cache--------groupId = " + groupId)
  18 + return Promise.resolve(Vue.ls.get(groupId));
  19 + } else {
  20 + console.log("--------popup----------getFrom DB---------groupId = " + groupId)
  21 + }
  22 +
  23 + // 还没有发出请求,就发出第一次的请求
  24 + return getPromise().then(res => {
  25 + Vue.ls.set(groupId, res, expire);
  26 + return Promise.resolve(res);
  27 + })
  28 +}
  29 +
  30 +
... ...
ant-design-vue-jeecg/src/api/api.js
1   -import { getAction,deleteAction,putAction,postAction} from '@/api/manage'
  1 +import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
  2 +import Vue from 'vue'
  3 +import {UI_CACHE_DB_DICT_DATA } from "@/store/mutation-types"
2 4  
3   -////根路径
4   -// const doMian = "/jeecg-boot/";
5   -////图片预览请求地址
6   -// const imgView = "http://localhost:8080/jeecg-boot/sys/common/view/";
7 5  
8 6 //角色管理
9 7 const addRole = (params)=>postAction("/sys/role/add",params);
10 8 const editRole = (params)=>putAction("/sys/role/edit",params);
11   -// const getRoleList = (params)=>getAction("/sys/role/list",params);
12   -// const deleteRole = (params)=>deleteAction("/sys/role/delete",params);
13   -// const deleteRoleList = (params)=>deleteAction("/sys/role/deleteBatch",params);
14 9 const checkRoleCode = (params)=>getAction("/sys/role/checkRoleCode",params);
15 10 const queryall = (params)=>getAction("/sys/role/queryall",params);
16 11  
... ... @@ -19,8 +14,6 @@ const addUser = (params)=>postAction("/sys/user/add",params);
19 14 const editUser = (params)=>putAction("/sys/user/edit",params);
20 15 const queryUserRole = (params)=>getAction("/sys/user/queryUserRole",params);
21 16 const getUserList = (params)=>getAction("/sys/user/list",params);
22   -// const deleteUser = (params)=>deleteAction("/sys/user/delete",params);
23   -// const deleteUserList = (params)=>deleteAction("/sys/user/deleteBatch",params);
24 17 const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
25 18 //验证用户是否存在
26 19 const checkOnlyUser = (params)=>getAction("/sys/user/checkOnlyUser",params);
... ... @@ -31,20 +24,15 @@ const changePassword = (params)=>putAction("/sys/user/changePassword",params);
31 24 const addPermission= (params)=>postAction("/sys/permission/add",params);
32 25 const editPermission= (params)=>putAction("/sys/permission/edit",params);
33 26 const getPermissionList = (params)=>getAction("/sys/permission/list",params);
34   -/*update_begin author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
35 27 const getSystemMenuList = (params)=>getAction("/sys/permission/getSystemMenuList",params);
36 28 const getSystemSubmenu = (params)=>getAction("/sys/permission/getSystemSubmenu",params);
37 29 const getSystemSubmenuBatch = (params) => getAction('/sys/permission/getSystemSubmenuBatch', params)
38   -/*update_end author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
39 30  
40   -// const deletePermission = (params)=>deleteAction("/sys/permission/delete",params);
41   -// const deletePermissionList = (params)=>deleteAction("/sys/permission/deleteBatch",params);
42 31 const queryTreeList = (params)=>getAction("/sys/permission/queryTreeList",params);
43 32 const queryTreeListForRole = (params)=>getAction("/sys/role/queryTreeList",params);
44 33 const queryListAsync = (params)=>getAction("/sys/permission/queryListAsync",params);
45 34 const queryRolePermission = (params)=>getAction("/sys/permission/queryRolePermission",params);
46 35 const saveRolePermission = (params)=>postAction("/sys/permission/saveRolePermission",params);
47   -//const queryPermissionsByUser = (params)=>getAction("/sys/permission/queryByUser",params);
48 36 const queryPermissionsByUser = (params)=>getAction("/sys/permission/getUserPermissionByToken",params);
49 37 const loadAllRoleIds = (params)=>getAction("/sys/permission/loadAllRoleIds",params);
50 38 const getPermissionRuleList = (params)=>getAction("/sys/permission/getPermRuleListByPermId",params);
... ... @@ -66,24 +54,26 @@ const saveDeptRolePermission = (params)=>postAction("/sys/sysDepartPermission/sa
66 54 const queryMyDepartTreeList = (params)=>getAction("/sys/sysDepart/queryMyDeptTreeList",params);
67 55  
68 56 //日志管理
69   -//const getLogList = (params)=>getAction("/sys/log/list",params);
70 57 const deleteLog = (params)=>deleteAction("/sys/log/delete",params);
71 58 const deleteLogList = (params)=>deleteAction("/sys/log/deleteBatch",params);
72 59  
73 60 //数据字典
74 61 const addDict = (params)=>postAction("/sys/dict/add",params);
75 62 const editDict = (params)=>putAction("/sys/dict/edit",params);
76   -//const getDictList = (params)=>getAction("/sys/dict/list",params);
77 63 const treeList = (params)=>getAction("/sys/dict/treeList",params);
78   -// const delDict = (params)=>deleteAction("/sys/dict/delete",params);
79   -//const getDictItemList = (params)=>getAction("/sys/dictItem/list",params);
80 64 const addDictItem = (params)=>postAction("/sys/dictItem/add",params);
81 65 const editDictItem = (params)=>putAction("/sys/dictItem/edit",params);
82   -//const delDictItem = (params)=>deleteAction("/sys/dictItem/delete",params);
83   -//const delDictItemList = (params)=>deleteAction("/sys/dictItem/deleteBatch",params);
84 66  
85 67 //字典标签专用(通过code获取字典数组)
86 68 export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItems/${code}`,params);
  69 +//从缓存中获取字典配置
  70 +function getDictItemsFromCache(dictCode) {
  71 + if (Vue.ls.get(UI_CACHE_DB_DICT_DATA) && Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode]) {
  72 + let dictItems = Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode];
  73 + console.log("-----------getDictItemsFromCache----------dictCode="+dictCode+"---- dictItems=",dictItems)
  74 + return dictItems;
  75 + }
  76 +}
87 77  
88 78 //系统通告
89 79 const doReleaseData = (params)=>getAction("/sys/annountCement/doReleaseData",params);
... ... @@ -91,23 +81,18 @@ const doReovkeData = (params)=>getAction("/sys/annountCement/doReovkeData",param
91 81 //获取系统访问量
92 82 const getLoginfo = (params)=>getAction("/sys/loginfo",params);
93 83 const getVisitInfo = (params)=>getAction("/sys/visitInfo",params);
94   -//数据日志访问
95   -// const getDataLogList = (params)=>getAction("/sys/dataLog/list",params);
96 84  
97 85 // 根据部门主键查询用户信息
98 86 const queryUserByDepId = (params)=>getAction("/sys/user/queryUserByDepId",params);
99   -
100   -// 查询用户角色表里的所有信息
101   -const queryUserRoleMap = (params)=>getAction("/sys/user/queryUserRoleMap",params);
102 87 // 重复校验
103 88 const duplicateCheck = (params)=>getAction("/sys/duplicate/check",params);
104 89 // 加载分类字典
105 90 const loadCategoryData = (params)=>getAction("/sys/category/loadAllData",params);
106 91 const checkRuleByCode = (params) => getAction('/sys/checkRule/checkByCode', params)
  92 +//我的通告
  93 +const getUserNoticeInfo= (params)=>getAction("/sys/sysAnnouncementSend/getMyAnnouncementSend",params);
107 94  
108 95 export {
109   - // imgView,
110   - // doMian,
111 96 addRole,
112 97 editRole,
113 98 checkRoleCode,
... ... @@ -147,7 +132,6 @@ export {
147 132 getLoginfo,
148 133 getVisitInfo,
149 134 queryUserByDepId,
150   - queryUserRoleMap,
151 135 duplicateCheck,
152 136 queryTreeListForRole,
153 137 getSystemMenuList,
... ... @@ -160,7 +144,9 @@ export {
160 144 queryTreeListForDeptRole,
161 145 queryDeptRolePermission,
162 146 saveDeptRolePermission,
163   - queryMyDepartTreeList
  147 + queryMyDepartTreeList,
  148 + getUserNoticeInfo,
  149 + getDictItemsFromCache
164 150 }
165 151  
166 152  
... ...
ant-design-vue-jeecg/src/api/login.js
... ... @@ -55,4 +55,19 @@ export function logout(logoutToken) {
55 55 'X-Access-Token': logoutToken
56 56 }
57 57 })
  58 +}
  59 +
  60 +/**
  61 + * 第三方登录
  62 + * @param token
  63 + * @returns {*}
  64 + */
  65 +export function thirdLogin(token) {
  66 + return axios({
  67 + url: `/thirdLogin/getLoginUser/${token}`,
  68 + method: 'get',
  69 + headers: {
  70 + 'Content-Type': 'application/json;charset=UTF-8'
  71 + }
  72 + })
58 73 }
59 74 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/api/manage.js
  1 +import Vue from 'vue'
1 2 import { axios } from '@/utils/request'
2 3  
3 4 const api = {
... ... @@ -113,16 +114,64 @@ export function downFile(url,parameter){
113 114 }
114 115  
115 116 /**
116   - * 获取文件访问路径
  117 + * 下载文件
  118 + * @param url 文件路径
  119 + * @param fileName 文件名
  120 + * @param parameter
  121 + * @returns {*}
  122 + */
  123 +export function downloadFile(url, fileName, parameter) {
  124 + return downFile(url, parameter).then((data) => {
  125 + if (!data || data.size === 0) {
  126 + Vue.prototype['$message'].warning('文件下载失败')
  127 + return
  128 + }
  129 + if (typeof window.navigator.msSaveBlob !== 'undefined') {
  130 + window.navigator.msSaveBlob(new Blob([data]), fileName)
  131 + } else {
  132 + let url = window.URL.createObjectURL(new Blob([data]))
  133 + let link = document.createElement('a')
  134 + link.style.display = 'none'
  135 + link.href = url
  136 + link.setAttribute('download', fileName)
  137 + document.body.appendChild(link)
  138 + link.click()
  139 + document.body.removeChild(link) //下载完成移除元素
  140 + window.URL.revokeObjectURL(url) //释放掉blob对象
  141 + }
  142 + })
  143 +}
  144 +
  145 +/**
  146 + * 文件上传 用于富文本上传图片
  147 + * @param url
  148 + * @param parameter
  149 + * @returns {*}
  150 + */
  151 +export function uploadAction(url,parameter){
  152 + return axios({
  153 + url: url,
  154 + data: parameter,
  155 + method:'post' ,
  156 + headers: {
  157 + 'Content-Type': 'multipart/form-data', // 文件上传
  158 + },
  159 + })
  160 +}
  161 +
  162 +/**
  163 + * 获取文件服务访问路径
117 164 * @param avatar
118   - * @param imgerver
119   - * @param str
  165 + * @param subStr
120 166 * @returns {*}
121 167 */
122   -export function getFileAccessHttpUrl(avatar,imgerver,subStr) {
123   - if(avatar && avatar.indexOf(subStr) != -1 ){
  168 +export function getFileAccessHttpUrl(avatar,subStr) {
  169 + if(!subStr) subStr = 'http'
  170 + if(avatar && avatar.startsWith(subStr)){
124 171 return avatar;
125 172 }else{
126   - return imgerver + "/" + avatar;
  173 + if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
  174 + return window._CONFIG['staticDomainURL'] + "/" + avatar;
  175 + }
127 176 }
128 177 }
... ...
ant-design-vue-jeecg/src/assets/less/JAreaLinkage.less 0 → 100644
  1 +.area-zoom-in-top-enter-active,
  2 +.area-zoom-in-top-leave-active {
  3 + opacity: 1;
  4 + transform: scaleY(1);
  5 +}
  6 +
  7 +.area-zoom-in-top-enter,
  8 +.area-zoom-in-top-leave-active {
  9 + opacity: 0;
  10 + transform: scaleY(0);
  11 +}
  12 +
  13 +.area-select {
  14 + box-sizing: border-box;
  15 + margin: 0;
  16 + padding: 0;
  17 + color: rgba(0, 0, 0, 0.65);
  18 + font-size: 14px;
  19 + font-variant: tabular-nums;
  20 + line-height: 1.5;
  21 + list-style: none;
  22 + font-feature-settings: 'tnum';
  23 + position: relative;
  24 + outline: 0;
  25 + display: block;
  26 + background-color: #fff;
  27 + border: 1px solid #d9d9d9;
  28 + border-top-width: 1.02px;
  29 + border-radius: 4px;
  30 + outline: none;
  31 + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  32 + -webkit-user-select: none;
  33 + -ms-user-select: none;
  34 + user-select: none;
  35 +}
  36 +
  37 +.area-select-wrap .area-select {
  38 + display: inline-block;
  39 +}
  40 +
  41 +.area-select * {
  42 + box-sizing: border-box;
  43 +}
  44 +
  45 +.area-select:hover {
  46 + border-color: #40a9ff;
  47 + border-right-width: 1px !important;
  48 + outline: 0;
  49 +}
  50 +
  51 +
  52 +.area-select:active {
  53 + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
  54 +}
  55 +
  56 +.area-select.small {
  57 + width: 126px;
  58 +}
  59 +
  60 +.area-select.medium {
  61 + width: 160px;
  62 +}
  63 +
  64 +.area-select.large {
  65 + width: 194px;
  66 +}
  67 +
  68 +.area-select.is-disabled {
  69 + background: #eceff5;
  70 + cursor: not-allowed;
  71 +}
  72 +
  73 +.area-select.is-disabled:hover {
  74 + border-color: #e1e2e6;
  75 +}
  76 +
  77 +.area-select.is-disabled .area-selected-trigger {
  78 + cursor: not-allowed;
  79 +}
  80 +
  81 +.area-select .area-selected-trigger {
  82 + position: relative;
  83 + display: block;
  84 + font-size: 14px;
  85 + cursor: pointer;
  86 + margin: 0;
  87 + overflow: hidden;
  88 + white-space: nowrap;
  89 + text-overflow: ellipsis;
  90 + height: 100%;
  91 + padding: 8px 20px 7px 12px;
  92 +}
  93 +
  94 +.area-select .area-select-icon {
  95 + position: absolute;
  96 + top: 50%;
  97 + margin-top: -2px;
  98 + right: 6px;
  99 + content: "";
  100 + width: 0;
  101 + height: 0;
  102 + border: 6px solid transparent;
  103 + border-top-color: rgba(0, 0, 0, 0.25);
  104 + transition: all .3s linear;
  105 + transform-origin: center;
  106 +}
  107 +
  108 +.area-select .area-select-icon.active {
  109 + margin-top: -8px;
  110 + transform: rotate(180deg);
  111 +}
  112 +
  113 +.area-selectable-list-wrap {
  114 + position: absolute;
  115 + width: 100%;
  116 + max-height: 275px;
  117 + z-index: 15000;
  118 + background-color: #fff;
  119 + box-sizing: border-box;
  120 + overflow-x: auto;
  121 + margin: 2px 0;
  122 + border-radius: 4px;
  123 + outline: none;
  124 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  125 +
  126 + transition: opacity 0.15s, transform 0.3s !important;
  127 + transform-origin: center top !important;
  128 +}
  129 +
  130 +.area-selectable-list {
  131 + position: relative;
  132 + margin: 0;
  133 + padding: 6px 0;
  134 + width: 100%;
  135 + font-size: 14px;
  136 + color: #565656;
  137 + list-style: none;
  138 +}
  139 +
  140 +.area-selectable-list .area-select-option {
  141 + position: relative;
  142 + white-space: nowrap;
  143 + overflow: hidden;
  144 + text-overflow: ellipsis;
  145 + cursor: pointer;
  146 + padding: 0 15px 0 10px;
  147 + height: 32px;
  148 + line-height: 32px;
  149 +}
  150 +
  151 +.area-selectable-list .area-select-option.hover {
  152 + background-color: #e6f7ff;
  153 +}
  154 +
  155 +.area-selectable-list .area-select-option.selected {
  156 + color: rgba(0, 0, 0, 0.65);
  157 + font-weight: 600;
  158 + background-color: #efefef;
  159 +}
  160 +
  161 +.cascader-menu-list-wrap {
  162 + position: absolute;
  163 + white-space: nowrap;
  164 + z-index: 15000;
  165 + background-color: #fff;
  166 + box-sizing: border-box;
  167 + overflow: hidden;
  168 + font-size: 0;
  169 + margin: 2px 0;
  170 + border-radius: 4px;
  171 + outline: none;
  172 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  173 +
  174 + transition: opacity 0.15s, transform 0.3s !important;
  175 + transform-origin: center top !important;
  176 +}
  177 +
  178 +.cascader-menu-list {
  179 + position: relative;
  180 + margin: 0;
  181 + font-size: 14px;
  182 + color: #565656;
  183 + padding: 6px 0;
  184 + list-style: none;
  185 + display: inline-block;
  186 + height: 204px;
  187 + overflow-x: hidden;
  188 + overflow-y: auto;
  189 + min-width: 160px;
  190 + vertical-align: top;
  191 + background-color: #fff;
  192 + border-right: 1px solid #e4e7ed;
  193 +}
  194 +
  195 +.cascader-menu-list:last-child {
  196 + border-right: none;
  197 +}
  198 +
  199 +.cascader-menu-list .cascader-menu-option {
  200 + position: relative;
  201 + white-space: nowrap;
  202 + overflow: hidden;
  203 + text-overflow: ellipsis;
  204 + cursor: pointer;
  205 + padding: 0 15px 0 10px;
  206 + height: 32px;
  207 + line-height: 32px;
  208 +}
  209 +
  210 +.cascader-menu-list .cascader-menu-option.hover,
  211 +.cascader-menu-list .cascader-menu-option:hover {
  212 + background-color: #e6f7ff;
  213 +}
  214 +
  215 +.cascader-menu-list .cascader-menu-option.selected {
  216 + color: rgba(0, 0, 0, 0.65);
  217 + font-weight: 600;
  218 + background-color: #efefef;
  219 +}
  220 +
  221 +.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after {
  222 + position: absolute;
  223 + top: 50%;
  224 + margin-top: -4px;
  225 + right: 5px;
  226 + content: "";
  227 + width: 0;
  228 + height: 0;
  229 + border: 4px solid transparent;
  230 + border-left-color: #a1a4ad;
  231 +}
  232 +
  233 +.cascader-menu-list::-webkit-scrollbar,
  234 +.area-selectable-list-wrap::-webkit-scrollbar {
  235 + width: 8px;
  236 + background: transparent;
  237 +}
  238 +
  239 +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen,
  240 +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement,
  241 +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment,
  242 +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment,
  243 +.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen,
  244 +.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement,
  245 +.cascader-menu-list::-webkit-scrollbar-button:vertical:increment,
  246 +.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment {
  247 + display: none;
  248 +}
  249 +
  250 +.cascader-menu-list::-webkit-scrollbar-thumb:vertical,
  251 +.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical {
  252 + background-color: #b8b8b8;
  253 + border-radius: 4px;
  254 +}
  255 +
  256 +.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover,
  257 +.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover {
  258 + background-color: #777;
  259 +}
0 260 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/assets/less/TableExpand.less 0 → 100644
  1 +/** [表格主题样式一] 表格强制列不换行 */
  2 +.j-table-force-nowrap {
  3 + td, th {
  4 + white-space: nowrap;
  5 + }
  6 +
  7 + .ant-table-selection-column {
  8 + padding: 12px 22px !important;
  9 + }
  10 +
  11 + /** 列自适应,弊端会导致列宽失效 */
  12 + &.ant-table-wrapper .ant-table-content {
  13 + overflow-x: auto;
  14 + }
  15 +}
... ...
ant-design-vue-jeecg/src/assets/less/common.less
1 1  
2 2 /*列表上方操作按钮区域*/
3 3 .ant-card-body .table-operator {
4   - margin-bottom: 18px;
  4 + margin-bottom: 8px;
5 5 }
6 6 /** Button按钮间距 */
7 7 .table-operator .ant-btn {
8   - margin: 8px 8px 0 0;
  8 + margin: 0 8px 8px 0;
  9 +}
  10 +.table-operator .ant-btn-group .ant-btn {
  11 + margin: 0;
  12 +}
  13 +
  14 +.table-operator .ant-btn-group .ant-btn:last-child {
  15 + margin: 0 8px 8px 0;
9 16 }
10 17 /*列表td的padding设置 可以控制列表大小*/
11 18 .ant-table-tbody .ant-table-row td {
... ... @@ -45,3 +52,7 @@
45 52 /*erp风格子表外框padding设置*/
46 53 .ant-card-wider-padding.cust-erp-sub-tab>.ant-card-body{padding:5px 12px}
47 54  
  55 +/* 内嵌子表背景颜色 */
  56 +.j-inner-table-wrapper /deep/ .ant-table-expanded-row .ant-table-wrapper .ant-table-tbody .ant-table-row {
  57 + background-color: #FFFFFF;
  58 +}
48 59 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/cas/sso.js
... ... @@ -8,14 +8,14 @@ const init = (callback) => {
8 8 console.log("-------单点登录开始-------");
9 9 let token = Vue.ls.get(ACCESS_TOKEN);
10 10 let st = getUrlParam("ticket");
11   - var sevice = "http://"+window.location.host+"/";
  11 + let sevice = "http://"+window.location.host+"/";
12 12 if(token){
13 13 loginSuccess(callback);
14 14 }else{
15 15 if(st){
16 16 validateSt(st,sevice,callback);
17 17 }else{
18   - var serviceUrl = encodeURIComponent(sevice);
  18 + let serviceUrl = encodeURIComponent(sevice);
19 19 window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
20 20 }
21 21 }
... ... @@ -26,14 +26,14 @@ const SSO = {
26 26 };
27 27  
28 28 function getUrlParam(paraName) {
29   - var url = document.location.toString();
30   - var arrObj = url.split("?");
  29 + let url = document.location.toString();
  30 + let arrObj = url.split("?");
31 31  
32 32 if (arrObj.length > 1) {
33   - var arrPara = arrObj[1].split("&");
34   - var arr;
  33 + let arrPara = arrObj[1].split("&");
  34 + let arr;
35 35  
36   - for (var i = 0; i < arrPara.length; i++) {
  36 + for (let i = 0; i < arrPara.length; i++) {
37 37 arr = arrPara[i].split("=");
38 38  
39 39 if (arr != null && arr[0] == paraName) {
... ... @@ -57,8 +57,8 @@ function validateSt(ticket,service,callback){
57 57 if(res.success){
58 58 loginSuccess(callback);
59 59 }else{
60   - var sevice = "http://"+window.location.host+"/";
61   - var serviceUrl = encodeURIComponent(sevice);
  60 + let sevice = "http://"+window.location.host+"/";
  61 + let serviceUrl = encodeURIComponent(sevice);
62 62 window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
63 63 }
64 64 }).catch((err) => {
... ...
ant-design-vue-jeecg/src/components/Ellipsis/Ellipsis.vue
1 1 <script>
2   - import Tooltip from 'ant-design-vue/es/tooltip'
3 2 import { cutStrByFullLength, getStrFullLength } from '@/components/_util/StringUtil'
4   -/*
5   - const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
6   -
7   - const TooltipOverlayStyle = {
8   - overflowWrap: 'break-word',
9   - wordWrap: 'break-word',
10   - };
11   -*/
12 3  
13 4 export default {
14 5 name: 'Ellipsis',
15   - components: {
16   - Tooltip
17   - },
18 6 props: {
19 7 prefixCls: {
20 8 type: String,
21 9 default: 'ant-pro-ellipsis'
22 10 },
23 11 tooltip: {
24   - type: Boolean
  12 + type: Boolean,
  13 + default: true,
25 14 },
26 15 length: {
27 16 type: Number,
28   - required: true
  17 + default: 25,
29 18 },
30 19 lines: {
31 20 type: Number,
... ... @@ -36,28 +25,25 @@
36 25 default: false
37 26 }
38 27 },
39   - methods: {
40   - getStrDom (str) {
41   - return (
42   - <span>{ cutStrByFullLength(str, this.length) + '...' }</span>
43   - )
44   - },
45   - getTooltip ( fullStr) {
  28 + methods: {},
  29 + render() {
  30 + const { tooltip, length } = this.$props
  31 + let text = ''
  32 + // 处理没有default插槽时的特殊情况
  33 + if (this.$slots.default) {
  34 + text = this.$slots.default.map(vNode => vNode.text).join('')
  35 + }
  36 + // 判断是否显示 tooltip
  37 + if (tooltip && getStrFullLength(text) > length) {
46 38 return (
47   - <Tooltip>
48   - <template slot="title">{ fullStr }</template>
49   - { this.getStrDom(fullStr) }
50   - </Tooltip>
  39 + <a-tooltip>
  40 + <template slot="title">{text}</template>
  41 + <span>{cutStrByFullLength(text, this.length) + '…'}</span>
  42 + </a-tooltip>
51 43 )
  44 + } else {
  45 + return (<span>{text}</span>)
52 46 }
53   - },
54   - render () {
55   - const { tooltip, length } = this.$props
56   - let str = this.$slots.default.map(vNode => vNode.text).join("")
57   - const strDom = tooltip && getStrFullLength(str) > length ? this.getTooltip(str) : this.getStrDom(str);
58   - return (
59   - strDom
60   - )
61 47 }
62 48 }
63 49 </script>
64 50 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/_util/Area.js 0 → 100644
  1 +import { pcaa } from 'area-data'
  2 +
  3 +/**
  4 + * 省市区
  5 + */
  6 +export default class Area {
  7 + /**
  8 + * 构造器
  9 + * @param express
  10 + */
  11 + constructor() {
  12 + let arr = []
  13 + const province = pcaa['86']
  14 + Object.keys(province).map(key=>{
  15 + arr.push({id:key, text:province[key], pid:'86'});
  16 + const city = pcaa[key];
  17 + Object.keys(city).map(key2=>{
  18 + arr.push({id:key2, text:city[key2], pid:key});
  19 + const qu = pcaa[key2];
  20 + Object.keys(qu).map(key3=>{
  21 + arr.push({id:key3, text:qu[key3], pid:key2});
  22 + })
  23 + })
  24 + })
  25 + this.all = arr;
  26 + }
  27 +
  28 + get pca(){
  29 + return this.all;
  30 + }
  31 +
  32 + getCode(text){
  33 + if(!text || text.length==0){
  34 + return ''
  35 + }
  36 + for(let item of this.all){
  37 + if(item.text === text){
  38 + return item.id;
  39 + }
  40 + }
  41 + }
  42 +
  43 + getText(code){
  44 + if(!code || code.length==0){
  45 + return ''
  46 + }
  47 + let arr = []
  48 + this.getAreaBycode(code,arr);
  49 + return arr.join('/')
  50 + }
  51 +
  52 + getRealCode(code){
  53 + let arr = []
  54 + this.getPcode(code, arr)
  55 + return arr;
  56 + }
  57 +
  58 + getPcode(id, arr){
  59 + for(let item of this.all){
  60 + if(item.id === id){
  61 + arr.unshift(id)
  62 + if(item.pid != '86'){
  63 + this.getPcode(item.pid,arr)
  64 + }
  65 + }
  66 + }
  67 + }
  68 +
  69 + getAreaBycode(code,arr){
  70 + //console.log("this.all.length",this.all)
  71 + for(let item of this.all){
  72 + if(item.id === code){
  73 + arr.unshift(item.text);
  74 + this.getAreaBycode(item.pid,arr)
  75 + }
  76 + }
  77 + }
  78 +
  79 +}
0 80 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/chart/StackBar.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <v-chart :forceFit="true" :height="height" :data="data">
  4 + <v-coord type="rect" direction="LB" />
  5 + <v-tooltip />
  6 + <v-legend />
  7 + <v-axis dataKey="State" :label="label" />
  8 + <v-stack-bar position="State*流程数量" color="流程状态" />
  9 + </v-chart>
  10 + </div>
  11 +
  12 +</template>
  13 +
  14 +<script>
  15 + const DataSet = require('@antv/data-set');
  16 +
  17 + export default {
  18 + name: 'StackBar',
  19 + props: {
  20 + dataSource: {
  21 + type: Array,
  22 + required: true,
  23 + default: () => [
  24 + { 'State': '请假', '流转中': 25, '已归档': 18 },
  25 + { 'State': '出差', '流转中': 30, '已归档': 20 },
  26 + { 'State': '加班', '流转中': 38, '已归档': 42},
  27 + { 'State': '用车', '流转中': 51, '已归档': 67}
  28 + ]
  29 + },
  30 + height: {
  31 + type: Number,
  32 + default: 254
  33 + }
  34 + },
  35 + data() {
  36 + return {
  37 + label: { offset: 12 }
  38 + }
  39 + },
  40 + computed: {
  41 + data() {
  42 + const dv = new DataSet.View().source(this.dataSource);
  43 + dv.transform({
  44 + type: 'fold',
  45 + fields: ['流转中', '已归档'],
  46 + key: '流程状态',
  47 + value: '流程数量',
  48 + retains: ['State'],
  49 + });
  50 + return dv.rows;
  51 + }
  52 + }
  53 + }
  54 +</script>
0 55 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/chart/chart.scss deleted
1   -.antv-chart-mini {
2   - position: relative;
3   - width: 100%;
4   -
5   - .chart-wrapper {
6   - position: absolute;
7   - bottom: -28px;
8   - width: 100%;
9   -
10   -/* margin: 0 -5px;
11   - overflow: hidden;*/
12   - }
13   -}
14 0 \ No newline at end of file
ant-design-vue-jeecg/src/components/dict/JDictSelectTag.vue
... ... @@ -3,6 +3,10 @@
3 3 <a-radio v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio>
4 4 </a-radio-group>
5 5  
  6 + <a-radio-group v-else-if="tagType=='radioButton'" buttonStyle="solid" @change="handleInput" :value="getValueSting" :disabled="disabled">
  7 + <a-radio-button v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio-button>
  8 + </a-radio-group>
  9 +
6 10 <a-select v-else-if="tagType=='select'" :getPopupContainer = "(target) => target.parentNode" :placeholder="placeholder" :disabled="disabled" :value="getValueSting" @change="handleInput">
7 11 <a-select-option :value="undefined">请选择</a-select-option>
8 12 <a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value">
... ... @@ -14,7 +18,7 @@
14 18 </template>
15 19  
16 20 <script>
17   - import {ajaxGetDictItems} from '@/api/api'
  21 + import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
18 22  
19 23 export default {
20 24 name: "JDictSelectTag",
... ... @@ -52,11 +56,17 @@
52 56 },
53 57 computed: {
54 58 getValueSting(){
55   - return this.value ? this.value.toString() : null;
  59 + return this.value != null ? this.value.toString() : null;
56 60 },
57 61 },
58 62 methods: {
59 63 initDictData() {
  64 + //优先从缓存中读取字典配置
  65 + if(getDictItemsFromCache(this.dictCode)){
  66 + this.dictOptions = getDictItemsFromCache(this.dictCode);
  67 + return
  68 + }
  69 +
60 70 //根据字典Code, 初始化字典数组
61 71 ajaxGetDictItems(this.dictCode, null).then((res) => {
62 72 if (res.success) {
... ...
ant-design-vue-jeecg/src/components/dict/JDictSelectUtil.js
... ... @@ -4,7 +4,7 @@
4 4 * date: 20190109
5 5 */
6 6  
7   -import {ajaxGetDictItems} from '@/api/api'
  7 +import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
8 8 import {getAction} from '@/api/manage'
9 9  
10 10 /**
... ... @@ -16,6 +16,13 @@ export async function initDictOptions(dictCode) {
16 16 if (!dictCode) {
17 17 return '字典Code不能为空!';
18 18 }
  19 + //优先从缓存中读取字典配置
  20 + if(getDictItemsFromCache(dictCode)){
  21 + let res = {}
  22 + res.result = getDictItemsFromCache(dictCode);
  23 + res.success = true;
  24 + return res;
  25 + }
19 26 //获取字典数组
20 27 let res = await ajaxGetDictItems(dictCode);
21 28 return res;
... ... @@ -28,16 +35,25 @@ export async function initDictOptions(dictCode) {
28 35 * @return String
29 36 */
30 37 export function filterDictText(dictOptions, text) {
31   - //--update-begin----author:sunjianlei---date:20191025------for:修复字典替换方法在字典没有加载完成之前报错的问题、修复没有找到字典时返回空值的问题---
32   - if (dictOptions instanceof Array) {
33   - for (let dictItem of dictOptions) {
34   - if (text === dictItem.value) {
35   - return dictItem.text
  38 + // --update-begin----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
  39 + if (text != null && dictOptions instanceof Array) {
  40 + let result = []
  41 + // 允许多个逗号分隔
  42 + let splitText = text.toString().trim().split(',')
  43 + for (let txt of splitText) {
  44 + let dictText = txt
  45 + for (let dictItem of dictOptions) {
  46 + if (txt === dictItem.value.toString()) {
  47 + dictText = dictItem.text
  48 + break
  49 + }
36 50 }
  51 + result.push(dictText)
37 52 }
  53 + return result.join(',')
38 54 }
39 55 return text
40   -//--update-end----author:sunjianlei---date:20191025------for:修复字典替换方法在字典没有加载完成之前报错的问题、修复没有找到字典时返回空值的问题---
  56 + // --update-end----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
41 57 }
42 58  
43 59 /**
... ... @@ -49,24 +65,28 @@ export function filterDictText(dictOptions, text) {
49 65 export function filterMultiDictText(dictOptions, text) {
50 66 //js “!text” 认为0为空,所以做提前处理
51 67 if(text === 0 || text === '0'){
52   - for (let dictItem of dictOptions) {
53   - if (text == dictItem.value) {
54   - return dictItem.text
  68 + if(dictOptions){
  69 + for (let dictItem of dictOptions) {
  70 + if (text == dictItem.value) {
  71 + return dictItem.text
  72 + }
55 73 }
56 74 }
57 75 }
58 76  
59   - if(!text || !dictOptions || dictOptions.length==0){
  77 + if(!text || text=='null' || !dictOptions || dictOptions.length==0){
60 78 return ""
61 79 }
62 80 let re = "";
63 81 text = text.toString()
64 82 let arr = text.split(",")
65 83 dictOptions.forEach(function (option) {
66   - for(let i=0;i<arr.length;i++){
67   - if (arr[i] === option.value) {
68   - re += option.text+",";
69   - break;
  84 + if(option){
  85 + for(let i=0;i<arr.length;i++){
  86 + if (arr[i] === option.value) {
  87 + re += option.text+",";
  88 + break;
  89 + }
70 90 }
71 91 }
72 92 });
... ... @@ -81,20 +101,42 @@ export function filterMultiDictText(dictOptions, text) {
81 101 * @param children
82 102 * @returns string
83 103 */
84   -export async function ajaxFilterDictText(dictCode, key) {
  104 +export function filterDictTextByCache(dictCode, key) {
  105 + if(key==null ||key.length==0){
  106 + return;
  107 + }
85 108 if (!dictCode) {
86 109 return '字典Code不能为空!';
87 110 }
88   - //console.log(`key : ${key}`);
89   - if (!key) {
90   - return '';
91   - }
92   - //通过请求读取字典文本
93   - let res = await getAction(`/sys/dict/getDictText/${dictCode}/${key}`);
94   - if (res.success) {
95   - // console.log('restult: '+ res.result);
96   - return res.result;
97   - } else {
98   - return '';
  111 + //优先从缓存中读取字典配置
  112 + if(getDictItemsFromCache(dictCode)){
  113 + let item = getDictItemsFromCache(dictCode).filter(t => t["value"] == key)
  114 + if(item && item.length>0){
  115 + return item[0]["text"]
  116 + }
99 117 }
  118 +}
  119 +
  120 +/** 通过code获取字典数组 */
  121 +export async function getDictItems(dictCode, params) {
  122 + //优先从缓存中读取字典配置
  123 + if(getDictItemsFromCache(dictCode)){
  124 + let desformDictItems = getDictItemsFromCache(dictCode).map(item => ({...item, label: item.text}))
  125 + return desformDictItems;
  126 + }
  127 +
  128 + //缓存中没有,就请求后台
  129 + return await ajaxGetDictItems(dictCode, params).then(({success, result}) => {
  130 + if (success) {
  131 + let res = result.map(item => ({...item, label: item.text}))
  132 + console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res)
  133 + return Promise.resolve(res)
  134 + } else {
  135 + console.error('getDictItems error: : ', res)
  136 + return Promise.resolve([])
  137 + }
  138 + }).catch((res) => {
  139 + console.error('getDictItems error: ', res)
  140 + return Promise.resolve([])
  141 + })
100 142 }
101 143 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/dict/JMultiSelectTag.vue
... ... @@ -10,6 +10,7 @@
10 10 :disabled="disabled"
11 11 mode="multiple"
12 12 :placeholder="placeholder"
  13 + :getPopupContainer="(node) => node.parentNode"
13 14 allowClear>
14 15 <a-select-option
15 16 v-for="(item,index) in dictOptions"
... ... @@ -24,7 +25,7 @@
24 25 </template>
25 26  
26 27 <script>
27   - import {ajaxGetDictItems} from '@/api/api'
  28 + import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
28 29 export default {
29 30 name: 'JMultiSelectTag',
30 31 props: {
... ... @@ -49,12 +50,18 @@
49 50 this.tagType = this.type
50 51 }
51 52 //获取字典数据
52   - this.initDictData();
  53 + //this.initDictData();
53 54 },
54 55 watch:{
55 56 options: function(val){
56 57 this.setCurrentDictOptions(val);
57 58 },
  59 + dictCode:{
  60 + immediate:true,
  61 + handler() {
  62 + this.initDictData()
  63 + },
  64 + },
58 65 value (val) {
59 66 if(!val){
60 67 this.arrayValue = []
... ... @@ -68,6 +75,11 @@
68 75 if(this.options && this.options.length>0){
69 76 this.dictOptions = [...this.options]
70 77 }else{
  78 + //优先从缓存中读取字典配置
  79 + if(getDictItemsFromCache(this.dictCode)){
  80 + this.dictOptions = getDictItemsFromCache(this.dictCode);
  81 + return
  82 + }
71 83 //根据字典Code, 初始化字典数组
72 84 ajaxGetDictItems(this.dictCode, null).then((res) => {
73 85 if (res.success) {
... ...
ant-design-vue-jeecg/src/components/dict/JSearchSelectTag.vue
... ... @@ -4,6 +4,8 @@
4 4 v-if="async"
5 5 showSearch
6 6 labelInValue
  7 + :disabled="disabled"
  8 + :getPopupContainer="(node) => node.parentNode"
7 9 @search="loadData"
8 10 :placeholder="placeholder"
9 11 v-model="selectedAsyncValue"
... ... @@ -19,7 +21,9 @@
19 21  
20 22 <a-select
21 23 v-else
  24 + :getPopupContainer="(node) => node.parentNode"
22 25 showSearch
  26 + :disabled="disabled"
23 27 :placeholder="placeholder"
24 28 optionFilterProp="children"
25 29 style="width: 100%"
... ... @@ -35,7 +39,7 @@
35 39 </template>
36 40  
37 41 <script>
38   - import { ajaxGetDictItems } from '@/api/api'
  42 + import { ajaxGetDictItems,getDictItemsFromCache } from '@/api/api'
39 43 import debounce from 'lodash/debounce';
40 44 import { getAction } from '../../api/manage'
41 45  
... ... @@ -43,7 +47,7 @@
43 47 name: 'JSearchSelectTag',
44 48 props:{
45 49 disabled: Boolean,
46   - value: String,
  50 + value: [String, Number],
47 51 dict: String,
48 52 dictOptions: Array,
49 53 async: Boolean,
... ... @@ -71,8 +75,12 @@
71 75 immediate:true,
72 76 handler(val){
73 77 if(!val){
74   - this.selectedValue=[]
75   - this.selectedAsyncValue=[]
  78 + if(val==0){
  79 + this.initSelectValue()
  80 + }else{
  81 + this.selectedValue=[]
  82 + this.selectedAsyncValue=[]
  83 + }
76 84 }else{
77 85 this.initSelectValue()
78 86 }
... ... @@ -100,7 +108,7 @@
100 108 })
101 109 }
102 110 }else{
103   - this.selectedValue = this.value
  111 + this.selectedValue = this.value.toString()
104 112 }
105 113 },
106 114 loadData(value){
... ... @@ -132,11 +140,28 @@
132 140 this.options = [...this.dictOptions]
133 141 }else{
134 142 //根据字典Code, 初始化字典数组
135   - ajaxGetDictItems(this.dict, null).then((res) => {
136   - if (res.success) {
137   - this.options = res.result;
138   - }
139   - })
  143 + let dictStr = ''
  144 + if(this.dict){
  145 + let arr = this.dict.split(',')
  146 + if(arr[0].indexOf('where')>0){
  147 + let tbInfo = arr[0].split('where')
  148 + dictStr = tbInfo[0].trim()+','+arr[1]+','+arr[2]+','+encodeURIComponent(tbInfo[1])
  149 + }else{
  150 + dictStr = this.dict
  151 + }
  152 + if (this.dict.indexOf(",") == -1) {
  153 + //优先从缓存中读取字典配置
  154 + if (getDictItemsFromCache(this.dictCode)) {
  155 + this.options = getDictItemsFromCache(this.dictCode);
  156 + return
  157 + }
  158 + }
  159 + ajaxGetDictItems(dictStr, null).then((res) => {
  160 + if (res.success) {
  161 + this.options = res.result;
  162 + }
  163 + })
  164 + }
140 165 }
141 166 }
142 167 },
... ...
ant-design-vue-jeecg/src/components/jeecg/JAreaLinkage.vue 0 → 100644
  1 +<template>
  2 + <div v-if="!reloading" class="j-area-linkage">
  3 + <area-cascader
  4 + v-if="_type === enums.type[0]"
  5 + :value="innerValue"
  6 + :data="pcaa"
  7 + :level="1"
  8 + :style="{width}"
  9 + v-bind="$attrs"
  10 + v-on="_listeners"
  11 + @change="handleChange"
  12 + />
  13 + <area-select
  14 + v-else-if="_type === enums.type[1]"
  15 + :value="innerValue"
  16 + :data="pcaa"
  17 + :level="2"
  18 + v-bind="$attrs"
  19 + v-on="_listeners"
  20 + @change="handleChange"
  21 + />
  22 + <div v-else>
  23 + <span style="color:red;"> Bad type value: {{_type}}</span>
  24 + </div>
  25 + </div>
  26 +</template>
  27 +
  28 +<script>
  29 + import { pcaa } from 'area-data'
  30 + import Area from '@/components/_util/Area'
  31 +
  32 + export default {
  33 + name: 'JAreaLinkage',
  34 + props: {
  35 + value: {
  36 + type: String,
  37 + required:false
  38 + },
  39 + // 组件的类型,可选值:
  40 + // select 下拉样式
  41 + // cascader 级联样式(默认)
  42 + type: {
  43 + type: String,
  44 + default: 'cascader'
  45 + },
  46 + width: {
  47 + type: String,
  48 + default: '100%'
  49 + }
  50 + },
  51 + data() {
  52 + return {
  53 + pcaa,
  54 + innerValue: [],
  55 + usedListeners: ['change'],
  56 + enums: {
  57 + type: ['cascader', 'select']
  58 + },
  59 + reloading: false,
  60 + areaData:''
  61 + }
  62 + },
  63 + computed: {
  64 + _listeners() {
  65 + let listeners = { ...this.$listeners }
  66 + // 去掉已使用的事件,防止冲突
  67 + this.usedListeners.forEach(key => {
  68 + delete listeners[key]
  69 + })
  70 + return listeners
  71 + },
  72 + _type() {
  73 + if (this.enums.type.includes(this.type)) {
  74 + return this.type
  75 + } else {
  76 + console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')})`)
  77 + return this.enums.type[0]
  78 + }
  79 + },
  80 + },
  81 + watch: {
  82 + value: {
  83 + immediate: true,
  84 + handler() {
  85 + this.loadDataByValue(this.value)
  86 + }
  87 + },
  88 + },
  89 + created() {
  90 + this.initAreaData();
  91 + },
  92 + methods: {
  93 + /** 通过 value 反推 options */
  94 + loadDataByValue(value) {
  95 + if(!value || value.length==0){
  96 + this.innerValue = []
  97 + this.reloading = true;
  98 + setTimeout(()=>{
  99 + this.reloading = false
  100 + },100)
  101 + }else{
  102 + this.initAreaData();
  103 + let arr = this.areaData.getRealCode(value);
  104 + this.innerValue = arr
  105 + }
  106 + },
  107 + /** 通过地区code获取子级 */
  108 + loadDataByCode(value) {
  109 + let options = []
  110 + let data = pcaa[value]
  111 + if (data) {
  112 + for (let key in data) {
  113 + if (data.hasOwnProperty(key)) {
  114 + options.push({ value: key, label: data[key], })
  115 + }
  116 + }
  117 + return options
  118 + } else {
  119 + return []
  120 + }
  121 + },
  122 + /** 判断是否有子节点 */
  123 + hasChildren(options) {
  124 + options.forEach(option => {
  125 + let data = this.loadDataByCode(option.value)
  126 + option.isLeaf = data.length === 0
  127 + })
  128 + },
  129 + handleChange(values) {
  130 + let value = values[values.length - 1]
  131 + this.$emit('change', value)
  132 + },
  133 + initAreaData(){
  134 + if(!this.areaData){
  135 + this.areaData = new Area();
  136 + }
  137 + },
  138 +
  139 + },
  140 + model: { prop: 'value', event: 'change' },
  141 + }
  142 +</script>
  143 +
  144 +<style lang="less" scoped>
  145 + .j-area-linkage {
  146 + height:40px;
  147 + /deep/ .area-cascader-wrap .area-select {
  148 + width: 100%;
  149 + }
  150 +
  151 + /deep/ .area-select .area-selected-trigger {
  152 + line-height: 1.15;
  153 + }
  154 + }
  155 +
  156 +</style>
0 157 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/jeecg/JCategorySelect.vue
... ... @@ -96,7 +96,7 @@
96 96 loadRoot(){
97 97 let param = {
98 98 pid:this.pid,
99   - pcode:this.pcode,
  99 + pcode:!this.pcode?'0':this.pcode,
100 100 condition:this.condition
101 101 }
102 102 getAction(this.url,param).then(res=>{
... ... @@ -122,8 +122,6 @@
122 122 this.treeValue = []
123 123 }else{
124 124 getAction(this.view,{ids:this.value}).then(res=>{
125   - console.log(124345)
126   - console.log(124345,res)
127 125 if(res.success){
128 126 let values = this.value.split(',')
129 127 this.treeValue = res.result.map((item, index) => ({
... ...
ant-design-vue-jeecg/src/components/jeecg/JCodeEditor.vue
1 1 <template>
2 2 <div v-bind="fullScreenParentProps">
3   - <a-icon v-if="fullScreen" class="full-screen-icon" type="fullscreen" @click="()=>fullCoder=!fullCoder"/>
  3 + <a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
4 4  
5 5 <div class="code-editor-cust full-screen-child">
6 6 <textarea ref="textarea"></textarea>
... ... @@ -91,6 +91,7 @@
91 91 return {
92 92 // 内部真实的内容
93 93 code: '',
  94 + iconType: 'fullscreen',
94 95 hasCode:false,
95 96 // 默认的语法类型
96 97 mode: 'javascript',
... ... @@ -155,6 +156,15 @@
155 156 }
156 157 },
157 158 watch: {
  159 + fullCoder:{
  160 + handler(value) {
  161 + if(value){
  162 + this.iconType="fullscreen-exit"
  163 + }else{
  164 + this.iconType="fullscreen"
  165 + }
  166 + }
  167 + },
158 168 // value: {
159 169 // immediate: false,
160 170 // handler(value) {
... ... @@ -408,6 +418,7 @@
408 418 .full-screen-child {
409 419 min-height: 120px;
410 420 max-height: 320px;
  421 + overflow:hidden;
411 422 }
412 423  
413 424 }
... ...
ant-design-vue-jeecg/src/components/jeecg/JDate.vue
... ... @@ -51,7 +51,7 @@
51 51 },
52 52 getCalendarContainer: {
53 53 type: Function,
54   - default: () => document.body
  54 + default: (node) => node.parentNode
55 55 }
56 56 },
57 57 data () {
... ...
ant-design-vue-jeecg/src/components/jeecg/JEditableTable.vue
... ... @@ -11,13 +11,13 @@
11 11 <a-col>
12 12 <!-- 操作按钮 -->
13 13 <div v-if="actionButton" class="action-button">
14   - <a-button type="primary" icon="plus" @click="handleClickAdd">新增</a-button>
  14 + <a-button type="primary" icon="plus" @click="handleClickAdd" :disabled="disabled">新增</a-button>
15 15 <span class="gap"></span>
16 16 <template v-if="selectedRowIds.length>0">
17 17 <a-popconfirm
18 18 :title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
19 19 @confirm="handleConfirmDelete">
20   - <a-button type="primary" icon="minus">删除</a-button>
  20 + <a-button type="primary" icon="minus" :disabled="disabled">删除</a-button>
21 21 <span class="gap"></span>
22 22 </a-popconfirm>
23 23 <template v-if="showClearSelectButton">
... ... @@ -79,7 +79,12 @@
79 79 <span>暂无数据</span>
80 80 </div>
81 81 <!-- v-model="rows"-->
82   - <draggable :value="rows" handle=".td-ds-icons" @end="handleDragMoveEnd">
  82 + <draggable
  83 + :value="rows"
  84 + handle=".td-ds-icons"
  85 + @start="handleDragMoveStart"
  86 + @end="handleDragMoveEnd"
  87 + >
83 88  
84 89 <!-- 动态生成tr -->
85 90 <template v-for="(row,rowIndex) in rows">
... ... @@ -200,6 +205,7 @@
200 205 @change="(v)=>handleChangeSelectCommon(v,id,row,col)"
201 206 @search="(v)=>handleSearchSelect(v,id,row,col)"
202 207 @blur="(v)=>handleBlurSearch(v,id,row,col)"
  208 + allowClear
203 209 >
204 210  
205 211 <!--<template v-for="(opt,optKey) in col.options">-->
... ... @@ -236,12 +242,43 @@
236 242 :trigger-change="true"
237 243 :showTime="col.type === formTypes.datetime"
238 244 :dateFormat="col.type === formTypes.date? 'YYYY-MM-DD':'YYYY-MM-DD HH:mm:ss'"
  245 + allowClear
239 246 @change="(v)=>handleChangeJDateCommon(v,id,row,col,col.type === formTypes.datetime)"/>
240 247  
241 248 </span>
242 249 </a-tooltip>
243 250 </template>
244 251  
  252 + <!-- input_pop -->
  253 + <template v-else-if="col.type === formTypes.input_pop">
  254 + <a-tooltip
  255 + :key="i"
  256 + :id="id"
  257 + placement="top"
  258 + :title="(tooltips[id] || {}).title"
  259 + :visible="(tooltips[id] || {}).visible || false"
  260 + :autoAdjustOverflow="true"
  261 + :getPopupContainer="getParentContainer">
  262 + <span
  263 + @mouseover="()=>{handleMouseoverCommono(row,col)}"
  264 + @mouseout="()=>{handleMouseoutCommono(row,col)}">
  265 + <j-input-pop
  266 + :id="id"
  267 + :key="i"
  268 + :width="300"
  269 + :height="210"
  270 + :pop-container="`${caseId}tbody`"
  271 + v-bind="buildProps(row,col)"
  272 + style="width: 100%;"
  273 + :value="jInputPopValues[id]"
  274 + :getCalendarContainer="getParentContainer"
  275 + :placeholder="replaceProps(col, col.placeholder)"
  276 + @change="(v)=>handleChangeJInputPopCommon(v,id,row,col)">
  277 + </j-input-pop>
  278 + </span>
  279 + </a-tooltip>
  280 + </template>
  281 +
245 282 <div v-else-if="col.type === formTypes.upload" :key="i">
246 283 <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
247 284 <a-input
... ... @@ -340,10 +377,11 @@
340 377 :placeholder="replaceProps(col, col.placeholder)"
341 378 style="width: 100%;"
342 379 :value="getPopupValue(id)"
343   - :field="col.key"
344   - :org-fields="col.orgFieldse"
  380 + :field="col.field || col.key"
  381 + :org-fields="col.orgFields"
345 382 :dest-fields="col.destFields"
346 383 :code="col.popupCode"
  384 + :groupId="caseId"
347 385 @input="(value,others)=>popupCallback(value,others,id,row,col,rowIndex)"/>
348 386 </span>
349 387 </a-tooltip>
... ... @@ -353,34 +391,42 @@
353 391 <!-- update-beign-author:taoyan date:0827 for:文件/图片逻辑新增 -->
354 392 <div v-else-if="col.type === formTypes.file" :key="i">
355 393 <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
356   - <a-input
357   - :key="fileKey"
358   - :readOnly="true"
359   - :value="file.name"
360   - >
361   - <template slot="addonBefore" style="width: 30px">
362   - <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
363   - <a-icon type="loading"/>
364   - </a-tooltip>
365   - <a-tooltip v-else-if="file.status==='done'" title="上传完成">
366   - <a-icon type="check-circle" style="color:#00DB00;"/>
367   - </a-tooltip>
368   - <a-tooltip v-else title="上传失败">
369   - <a-icon type="exclamation-circle" style="color:red;"/>
370   - </a-tooltip>
371   - </template>
  394 + <div :key="fileKey" style="position: relative;">
  395 + <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
  396 + <a-icon type="loading" style="color:red;"/>
  397 + <span style="color:red;margin-left:5px">{{ file.status }}</span>
  398 + </a-tooltip>
372 399  
373   - <template slot="addonAfter" style="width: 30px">
374   - <a-tooltip title="删除并重新上传">
375   - <a-icon
376   - v-if="file.status!=='uploading'"
377   - type="close-circle"
378   - style="cursor: pointer;"
379   - @click="()=>handleClickDelFile(id)"/>
380   - </a-tooltip>
381   - </template>
  400 + <a-tooltip v-else-if="file.status==='done'" :title="file.name">
  401 + <a-icon type="paper-clip" />
  402 + <span style="margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
  403 + </a-tooltip>
382 404  
383   - </a-input>
  405 + <a-tooltip v-else :title="file.name">
  406 + <a-icon type="paper-clip" style="color:red;"/>
  407 + <span style="color:red;margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
  408 + </a-tooltip>
  409 +
  410 + <template style="width: 30px">
  411 + <a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
  412 + <a-tooltip title="操作" :getPopupContainer="getParentContainer">
  413 + <a-icon v-if="file.status!=='uploading'" type="setting" style="cursor: pointer;"/>
  414 + </a-tooltip>
  415 +
  416 + <a-menu slot="overlay">
  417 + <a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
  418 + <span><a-icon type="download"/>&nbsp;下载</span>
  419 + </a-menu-item>
  420 + <a-menu-item @click="handleClickDelFile(id)">
  421 + <span><a-icon type="delete"/>&nbsp;删除</span>
  422 + </a-menu-item>
  423 + <a-menu-item @click="handleMoreOperation(id)">
  424 + <span><a-icon type="bars" /> 更多</span>
  425 + </a-menu-item>
  426 + </a-menu>
  427 + </a-dropdown>
  428 + </template>
  429 + </div>
384 430 </template>
385 431  
386 432 <div :hidden="uploadValues[id] != null">
... ... @@ -407,7 +453,7 @@
407 453 v-bind="buildProps(row,col)"
408 454 @change="(v)=>handleChangeUpload(v,id,row,col)"
409 455 >
410   - <a-button icon="upload">{{ col.placeholder }}</a-button>
  456 + <a-button icon="upload">上传文件</a-button>
411 457 </a-upload>
412 458 </span>
413 459 </a-tooltip>
... ... @@ -418,7 +464,15 @@
418 464 <div v-else-if="col.type === formTypes.image" :key="i">
419 465 <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
420 466 <div :key="fileKey" style="position: relative;">
421   - <img :src="getCellImageView(id)" style="height:32px;max-width:100px !important;" alt="无图片"/>
  467 + <template v-if="!uploadValues[id] || !(uploadValues[id]['url'] || uploadValues[id]['path'] || uploadValues[id]['message'])">
  468 + <a-icon type="loading"/>
  469 + </template>
  470 + <template v-else-if="uploadValues[id]['path']">
  471 + <img class="j-editable-image" :src="getCellImageView(id)" alt="无图片" @click="handleMoreOperation(id,'img')"/>
  472 + </template>
  473 + <template v-else>
  474 + <a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError(id)"/>
  475 + </template>
422 476 <template slot="addonBefore" style="width: 30px">
423 477 <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
424 478 <a-icon type="loading"/>
... ... @@ -432,13 +486,26 @@
432 486 </template>
433 487  
434 488 <template style="width: 30px">
435   - <a-tooltip title="删除并重新上传" style="margin-left:5px">
436   - <a-icon
437   - v-if="file.status!=='uploading'"
438   - type="close-circle"
439   - style="cursor: pointer;"
440   - @click="()=>handleClickDelFile(id)"/>
441   - </a-tooltip>
  489 + <a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
  490 + <a-tooltip title="操作" :getPopupContainer="getParentContainer">
  491 + <a-icon
  492 + v-if="file.status!=='uploading'"
  493 + type="setting"
  494 + style="cursor: pointer;"/>
  495 + </a-tooltip>
  496 +
  497 + <a-menu slot="overlay">
  498 + <a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
  499 + <span><a-icon type="download"/>&nbsp;下载</span>
  500 + </a-menu-item>
  501 + <a-menu-item @click="handleClickDelFile(id)">
  502 + <span><a-icon type="delete"/>&nbsp;删除</span>
  503 + </a-menu-item>
  504 + <a-menu-item @click="handleMoreOperation(id,'img')">
  505 + <span><a-icon type="bars" /> 更多</span>
  506 + </a-menu-item>
  507 + </a-menu>
  508 + </a-dropdown>
442 509 </template>
443 510  
444 511 </div>
... ... @@ -468,7 +535,7 @@
468 535 v-bind="buildProps(row,col)"
469 536 @change="(v)=>handleChangeUpload(v,id,row,col)"
470 537 >
471   - <a-button icon="upload">上传图片</a-button>
  538 + <a-button icon="upload">上传图片</a-button>
472 539 </a-upload>
473 540 </span>
474 541 </a-tooltip>
... ... @@ -599,7 +666,7 @@
599 666 :text="slotValues[id]"
600 667 :value="slotValues[id]"
601 668 :column="col"
602   - :rowId="removeCaseId(row.id)"
  669 + :rowId="getCleanId(row.id)"
603 670 :getValue="()=>_getValueForSlot(row.id)"
604 671 :caseId="caseId"
605 672 :allValues="_getAllValuesForSlot()"
... ... @@ -621,8 +688,45 @@
621 688 </template>
622 689 </draggable>
623 690  
  691 +
  692 + <!-- 统计行 -->
  693 + <div
  694 + v-if="showStatisticsRow"
  695 + class="tr"
  696 + :style="{
  697 + ...buildTrStyle(rows.length),
  698 + height: '32px'
  699 + }"
  700 + >
  701 + <div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
  702 + </div>
  703 + <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
  704 + 统计
  705 + </div>
  706 + <div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
  707 + <span v-if="!rowSelection">统计</span>
  708 + </div>
  709 +
  710 + <!-- 右侧动态生成td -->
  711 + <template v-for="col in columns">
  712 + <div
  713 + :key="col.key"
  714 + class="td"
  715 + v-show="col.type !== formTypes.hidden"
  716 + :style="buildTdStyle(col)"
  717 + >
  718 + <span
  719 + v-show="col.type === formTypes.inputNumber"
  720 + style="padding: 0 5px;"
  721 + >{{statisticsColumns[col.key]}}</span>
  722 + </div>
  723 + </template>
  724 +
  725 + </div>
  726 +
624 727 </div>
625 728 </div>
  729 + <j-file-pop ref="filePop" @ok="handleFileSuccess"></j-file-pop>
626 730 </div>
627 731 </a-spin>
628 732 </template>
... ... @@ -632,17 +736,25 @@
632 736 import Draggable from 'vuedraggable'
633 737 import { ACCESS_TOKEN } from '@/store/mutation-types'
634 738 import { FormTypes, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
635   - import { cloneObject, randomString } from '@/utils/util'
  739 + import { cloneObject, randomString, randomNumber } from '@/utils/util'
636 740 import JDate from '@/components/jeecg/JDate'
637 741 import { initDictOptions } from '@/components/dict/JDictSelectUtil'
638   -
  742 + import { getFileAccessHttpUrl } from '@/api/manage';
  743 + import JInputPop from '@/components/jeecg/minipop/JInputPop'
  744 + import JFilePop from '@/components/jeecg/minipop/JFilePop'
639 745  
640 746 // 行高,需要在实例加载完成前用到
641 747 let rowHeight = 61
642 748  
643 749 export default {
644 750 name: 'JEditableTable',
645   - components: { JDate, Draggable },
  751 + components: { JDate, Draggable, JInputPop, JFilePop },
  752 + provide() {
  753 + return {
  754 + parentIsJEditableTable: true,
  755 + getDestroyCleanGroupRequest: () => this.destroyCleanGroupRequest,
  756 + }
  757 + },
646 758 props: {
647 759 // 列信息
648 760 columns: {
... ... @@ -704,8 +816,15 @@
704 816 },
705 817 data() {
706 818 return {
  819 + // 是否首次运行
  820 + isFirst: true,
  821 + // 当前实例是否是行编辑
  822 + isJEditableTable: true,
707 823 // caseId,用于防止有多个实例的时候会冲突
  824 + caseIdPrefix: '_jet-',
708 825 caseId: `_jet-${randomString(6)}-`,
  826 + // 临时ID标识,凡是以该标识结尾的ID都是临时ID,不添加到数据库中
  827 + tempId: `_tid-${randomString(6)}`,
709 828 // 存储document element 对象
710 829 el: {
711 830 inputTable: null,
... ... @@ -733,6 +852,8 @@
733 852 checkboxValues: {},
734 853 // 绑定 jdate 的值
735 854 jdateValues: {},
  855 + // 绑定jinputpop
  856 + jInputPopValues:{},
736 857 // 绑定插槽数据
737 858 slotValues: {},
738 859 // file 信息
... ... @@ -751,7 +872,15 @@
751 872 // 存储显示tooltip的信息
752 873 tooltips: {},
753 874 // 存储没有通过验证的inputId
754   - notPassedIds: []
  875 + notPassedIds: [],
  876 +
  877 + // 当前是否正在拖拽排序
  878 + dragging: false,
  879 + // 是否有统计列
  880 + hasStatisticsColumn: false,
  881 + statisticsColumns: {},
  882 + // 只有在行编辑被销毁时才主动清空GroupRequest的内存
  883 + destroyCleanGroupRequest: false,
755 884 }
756 885 },
757 886 created() {
... ... @@ -764,7 +893,15 @@
764 893 computed: {
765 894 // expandHeight = rows.length * rowHeight
766 895 getExpandHeight() {
767   - return this.rows.length * this.rowHeight
  896 + let length = this.rows.length * this.rowHeight
  897 + if (this.showStatisticsRow) {
  898 + length += 34
  899 + }
  900 + return length
  901 + },
  902 + // 是否显示统计行
  903 + showStatisticsRow() {
  904 + return this.hasStatisticsColumn && this.rows.length > 0
768 905 },
769 906 // 获取是否选择了部分
770 907 getSelectIndeterminate() {
... ... @@ -792,6 +929,7 @@
792 929 return Vue.ls.get(ACCESS_TOKEN)
793 930 },
794 931 realTrWidth() {
  932 + let splice = ' + '
795 933 let calcWidth = 'calc('
796 934 this.columns.forEach((column, i) => {
797 935 let { type, width } = column
... ... @@ -804,12 +942,12 @@
804 942 } else {
805 943 calcWidth += '120px'
806 944 }
807   -
808   - if (i < this.columns.length - 1) {
809   - calcWidth += ' + '
810   - }
  945 + calcWidth += splice
811 946 }
812 947 })
  948 + if (calcWidth.endsWith(splice)) {
  949 + calcWidth = calcWidth.substring(0, calcWidth.length - splice.length)
  950 + }
813 951 calcWidth += ')'
814 952 // console.log('calcWidth: ', calcWidth)
815 953 return calcWidth
... ... @@ -836,125 +974,8 @@
836 974 handler: function (newValue) {
837 975 // 兼容IE
838 976 this.getElementPromise('tbody').then(() => {
839   -
840 977 this.initialize()
841   -
842   - let rows = []
843   - let checkboxValues = {}
844   - let selectValues = {}
845   - let jdateValues = {}
846   - let slotValues = {}
847   - let uploadValues = {}
848   - let popupValues = {}
849   - let radioValues = {}
850   - let multiSelectValues = {}
851   - let searchSelectValues = {}
852   -
853   - // 禁用行的id
854   - let disabledRowIds = (this.disabledRowIds || [])
855   - newValue.forEach((data, newValueIndex) => {
856   - // 判断源数据是否带有id
857   - if (data.id == null || data.id === '') {
858   - data.id = this.removeCaseId(this.generateId() + newValueIndex)
859   - }
860   -
861   - let value = { id: this.caseId + data.id }
862   - let row = { id: value.id }
863   - let disabled = false
864   - this.columns.forEach(column => {
865   - let inputId = column.key + value.id
866   - let sourceValue = (data[column.key] == null ? '' : data[column.key]).toString()
867   - if (column.type === FormTypes.checkbox) {
868   -
869   - // 判断是否设定了customValue(自定义值)
870   - if (column.customValue instanceof Array) {
871   - let customValue = (column.customValue[0] || '').toString()
872   - checkboxValues[inputId] = (sourceValue === customValue)
873   - } else {
874   - checkboxValues[inputId] = sourceValue
875   - }
876   -
877   - } else if (column.type === FormTypes.select) {
878   - if (sourceValue) {
879   - // 判断是否是多选
880   - selectValues[inputId] = (column.props || {})['mode'] === 'multiple' ? sourceValue.split(',') : sourceValue
881   - } else {
882   - selectValues[inputId] = undefined
883   - }
884   -
885   - } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
886   - jdateValues[inputId] = sourceValue
887   -
888   - } else if (column.type === FormTypes.slot) {
889   - if (sourceValue !== 0 && !sourceValue) {
890   - slotValues[inputId] = column.defaultValue
891   - } else {
892   - slotValues[inputId] = sourceValue
893   - }
894   -
895   - } else if (column.type === FormTypes.popup) {
896   - popupValues[inputId] = sourceValue
897   - } else if (column.type === FormTypes.radio) {
898   - radioValues[inputId] = sourceValue
899   - } else if (column.type === FormTypes.sel_search) {
900   - searchSelectValues[inputId] = sourceValue
901   - } else if (column.type === FormTypes.list_multi) {
902   - if (sourceValue.length > 0) {
903   - multiSelectValues[inputId] = sourceValue.split(',')
904   - } else {
905   - multiSelectValues[inputId] = []
906   - }
907   - } else if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
908   - if (sourceValue) {
909   - let fileName = sourceValue.substring(sourceValue.lastIndexOf('/') + 1)
910   - uploadValues[inputId] = {
911   - name: fileName,
912   - status: 'done',
913   - path: sourceValue
914   - }
915   - }
916   - } else {
917   - value[column.key] = sourceValue
918   - }
919   -
920   - // 解析disabledRows
921   - for (let columnKey in this.disabledRows) {
922   - // 判断是否有该属性
923   - if (this.disabledRows.hasOwnProperty(columnKey) && data.hasOwnProperty(columnKey)) {
924   - if (disabled !== true) {
925   - let temp = this.disabledRows[columnKey]
926   - // 禁用规则可以是一个数组
927   - if (temp instanceof Array) {
928   - disabled = temp.includes(data[columnKey])
929   - } else {
930   - disabled = (temp === data[columnKey])
931   - }
932   - if (disabled) {
933   - disabledRowIds.push(row.id)
934   - }
935   - }
936   - }
937   - }
938   - })
939   - this.inputValues.push(value)
940   - rows.push(row)
941   - })
942   - this.disabledRowIds = disabledRowIds
943   - this.checkboxValues = checkboxValues
944   - this.selectValues = selectValues
945   - this.jdateValues = jdateValues
946   - this.slotValues = slotValues
947   - this.rows = rows
948   - this.uploadValues = uploadValues
949   - this.popupValues = popupValues
950   - this.radioValues = radioValues
951   - this.multiSelectValues = multiSelectValues
952   - this.searchSelectValues = searchSelectValues
953   -
954   - // 更新form表单的值
955   - this.$nextTick(() => {
956   - this.updateFormValues()
957   - })
  978 + this._pushByDataSource(newValue)
958 979 })
959 980 }
960 981 },
... ... @@ -988,7 +1009,7 @@
988 1009 },
989 1010 // 当selectRowIds改变时触发事件
990 1011 selectedRowIds(newValue) {
991   - this.$emit('selectRowChange', cloneObject(newValue).map(i => this.removeCaseId(i)))
  1012 + this.$emit('selectRowChange', cloneObject(newValue).map(i => this.getCleanId(i)))
992 1013 }
993 1014 },
994 1015 mounted() {
... ... @@ -1008,8 +1029,6 @@
1008 1029  
1009 1030 thead.scrollLeft = event.target.scrollLeft
1010 1031  
1011   - // vm.recalcTrHiddenItem(event.target.scrollTop)
1012   -
1013 1032 vm.recalcTrHiddenItem(event.target.scrollTop)
1014 1033  
1015 1034 }
... ... @@ -1038,36 +1057,42 @@
1038 1057  
1039 1058 /** 初始化列表 */
1040 1059 initialize() {
1041   - // inputValues:用来存储input表单的值
1042   - // 数组里的每项都是一个对象,对象里每个key都是input的rowKey,值就是input的值,其中有个id的字段来区分
1043   - // 示例:
1044   - // [{
1045   - // id: "_jet-4sp0iu-15541771111770"
1046   - // dbDefaultVal: "aaa",
1047   - // dbFieldName: "bbb",
1048   - // dbFieldTxt: "ccc",
1049   - // dbLength: 32
1050   - // }]
1051   - this.inputValues = []
1052 1060 this.visibleTrEls = []
1053   - this.rows = []
1054   - this.deleteIds = []
1055   - this.selectValues = {}
1056   - this.checkboxValues = {}
1057   - this.jdateValues = {}
1058   - this.slotValues = {}
1059   - this.selectedRowIds = []
1060   - this.tooltips = {}
1061   - this.notPassedIds = []
1062   - this.uploadValues = []
1063   - this.popupValues = []
1064   - this.radioValues = []
1065   - this.multiSelectValues = []
1066   - this.searchSelectValues = []
1067   - this.scrollTop = 0
1068   - this.$nextTick(() => {
1069   - this.getElement('tbody').scrollTop = 0
1070   - })
  1061 + // 判断是否是首次进入该方法,如果是就不清空行,防止删除了预添加的数据
  1062 + if (!this.isFirst) {
  1063 + // inputValues:用来存储input表单的值
  1064 + // 数组里的每项都是一个对象,对象里每个key都是input的rowKey,值就是input的值,其中有个id的字段来区分
  1065 + // 示例:
  1066 + // [{
  1067 + // id: "_jet-4sp0iu-15541771111770"
  1068 + // dbDefaultVal: "aaa",
  1069 + // dbFieldName: "bbb",
  1070 + // dbFieldTxt: "ccc",
  1071 + // dbLength: 32
  1072 + // }]
  1073 + this.inputValues = []
  1074 + this.rows = []
  1075 + this.deleteIds = []
  1076 + this.selectValues = {}
  1077 + this.checkboxValues = {}
  1078 + this.jdateValues = {}
  1079 + this.jInputPopValues = {}
  1080 + this.slotValues = {}
  1081 + this.selectedRowIds = []
  1082 + this.tooltips = {}
  1083 + this.notPassedIds = []
  1084 + this.uploadValues = []
  1085 + this.popupValues = []
  1086 + this.radioValues = []
  1087 + this.multiSelectValues = []
  1088 + this.searchSelectValues = []
  1089 + this.scrollTop = 0
  1090 + this.$nextTick(() => {
  1091 + this.getElement('tbody').scrollTop = 0
  1092 + })
  1093 + } else {
  1094 + this.isFirst = false
  1095 + }
1071 1096 },
1072 1097  
1073 1098 /** 同步滚动条状态 */
... ... @@ -1105,94 +1130,212 @@
1105 1130 rows = this.rows || []
1106 1131 }
1107 1132 let timestamp = new Date().getTime()
1108   - return `${this.caseId}${timestamp}${rows.length}`
  1133 + return `${this.caseId}${timestamp}${rows.length}${randomNumber(6)}${this.tempId}`
1109 1134 },
1110 1135 /** push 一条数据 */
1111   - push(record, update = true, rows, insertIndex = null) {
  1136 + push(record, update = true, rows, insertIndex = null, setDefaultValue = true) {
  1137 + return this._pushByDataSource([record], [insertIndex], update, rows, setDefaultValue)
  1138 + },
  1139 +
  1140 + /**
  1141 + * push 数据
  1142 + *
  1143 + * @param dataSource 数据源
  1144 + * @param insertIndexes 行插入位置,和dataSource的下标一一对应
  1145 + * @param update 是否更新
  1146 + * @param rows 若不传就使用 this.rows
  1147 + * @param setDefaultValue 是否填充默认值
  1148 + *
  1149 + */
  1150 + _pushByDataSource(dataSource, insertIndexes = null, update = true, rows = null, setDefaultValue = false) {
1112 1151 if (!(rows instanceof Array)) {
1113   - rows = cloneObject(this.rows) || []
1114   - }
1115   -
1116   - if (record.id == null) {
1117   - record.id = this.generateId(rows)
1118   - // let timestamp = new Date().getTime()
1119   - // record.id = `${this.caseId}${timestamp}${rows.length}`
1120   - }
1121   - if (record.id.indexOf(this.caseId) === -1) {
1122   - record.id = this.caseId + record.id
1123   - }
1124   - let row = { id: record.id }
1125   - let value = { id: row.id }
1126   - let checkboxValues = Object.assign({}, this.checkboxValues)
1127   - let selectValues = Object.assign({}, this.selectValues)
1128   - let jdateValues = Object.assign({}, this.jdateValues)
1129   - let slotValues = Object.assign({}, this.slotValues)
1130   - this.columns.forEach(column => {
1131   - let key = column.key
1132   - let inputId = key + row.id
1133   - // record中是否有该列的值
1134   - let recordHasValue = record[key] != null
1135   - if (column.type === FormTypes.input) {
1136   - value[key] = recordHasValue ? record[key] : (column.defaultValue || (column.defaultValue === 0 ? 0 : ''))
1137   -
1138   - } else if (column.type === FormTypes.inputNumber) {
1139   - // 判断是否是排序字段,如果是就赋最大值
1140   - if (column.isOrder === true) {
1141   - value[key] = this.getInputNumberMaxValue(column) + 1
1142   - } else {
1143   - value[key] = recordHasValue ? record[key] : (column.defaultValue || (column.defaultValue === 0 ? 0 : ''))
1144   - }
  1152 + rows = [...this.rows] || []
  1153 + }
  1154 + let checkboxValues = { ...this.checkboxValues }
  1155 + let selectValues = { ...this.selectValues }
  1156 + let jdateValues = { ...this.jdateValues }
  1157 + let jInputPopValues = { ...this.jInputPopValues }
  1158 + let slotValues = { ...this.slotValues }
  1159 + let uploadValues = { ...this.uploadValues }
  1160 + let popupValues = { ...this.popupValues }
  1161 + let radioValues = { ...this.radioValues }
  1162 + let multiSelectValues = { ...this.multiSelectValues }
  1163 + let searchSelectValues = { ...this.searchSelectValues }
  1164 + // 禁用行的id
  1165 + let disabledRowIds = (this.disabledRowIds || [])
  1166 + dataSource.forEach((data, newValueIndex) => {
  1167 + // 不能直接更改数据源的id
  1168 + let dataId = data.id
  1169 + // 判断源数据是否带有id
  1170 + if (dataId == null || dataId === '') {
  1171 + dataId = this.generateId(rows)
  1172 + } else if(!this.hasCaseId(dataId)) {
  1173 + dataId = this.caseId + dataId
  1174 + }
  1175 + let row = { id: dataId }
  1176 + let value = { id: dataId }
  1177 + let disabled = false
  1178 + this.columns.forEach(column => {
  1179 + let inputId = column.key + value.id
  1180 + let sourceValue = (data[column.key] == null ? '' : data[column.key]).toString()
1145 1181  
1146   - } else if (column.type === FormTypes.checkbox) {
1147   - checkboxValues[inputId] = recordHasValue ? record[key] : column.defaultChecked
  1182 + let defaultValue = null;
  1183 + if (setDefaultValue) {
  1184 + defaultValue = column.defaultValue || (column.defaultValue === 0 ? 0 : '')
  1185 + if (defaultValue instanceof Array) {
  1186 + defaultValue = defaultValue.join(',')
  1187 + }
1148 1188  
1149   - } else if (column.type === FormTypes.select) {
1150   - let selected = column.defaultValue
1151   - if (selected !== 0 && !selected) {
1152   - selected = undefined
  1189 + sourceValue = (typeof sourceValue === 'number' || sourceValue) ? sourceValue : defaultValue
1153 1190 }
1154   - // 判断多选
1155   - if (typeof selected === 'string' && (column.props || {})['mode'] === 'multiple') {
1156   - selected = selected.split(',')
1157   - }
1158   - selectValues[inputId] = recordHasValue ? record[key] : selected
  1191 + let sourceValueIsEmpty = (sourceValue == null || sourceValue === '')
1159 1192  
1160   - } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
1161   - jdateValues[inputId] = recordHasValue ? record[key] : column.defaultValue
  1193 + if (column.type === FormTypes.inputNumber) {
  1194 + // 判断是否是排序字段,如果是就赋最大值
  1195 + if (column.isOrder === true) {
  1196 + value[column.key] = this.getInputNumberMaxValue(column) + 1
  1197 + } else {
  1198 + value[column.key] = sourceValue
  1199 + }
  1200 + // 判断是否是统计列
  1201 + if (column.statistics) {
  1202 + this.hasStatisticsColumn = true
  1203 + if (!this.statisticsColumns[column.key]) {
  1204 + this.$set(this.statisticsColumns, column.key, 0)
  1205 + }
  1206 + }
1162 1207  
1163   - } else if (column.type === FormTypes.slot) {
1164   - slotValues[inputId] = recordHasValue ? record[key] : (column.defaultValue || '')
  1208 + } else if (column.type === FormTypes.checkbox) {
  1209 + // 判断是否设定了customValue(自定义值)
  1210 + if (column.customValue instanceof Array) {
  1211 + let customValue = (column.customValue[0] || '').toString()
  1212 + if (sourceValueIsEmpty && setDefaultValue) {
  1213 + sourceValue = column.defaultChecked ? customValue : sourceValue
  1214 + }
  1215 + checkboxValues[inputId] = (sourceValue === customValue)
  1216 + } else {
  1217 + if (sourceValueIsEmpty && setDefaultValue) {
  1218 + checkboxValues[inputId] = !!column.defaultChecked
  1219 + } else {
  1220 + checkboxValues[inputId] = sourceValue
  1221 + }
  1222 + }
1165 1223  
1166   - } else {
1167   - value[key] = recordHasValue ? record[key] : ''
  1224 + } else if (column.type === FormTypes.select) {
  1225 + if (!sourceValueIsEmpty) {
  1226 + // 判断是否是多选
  1227 + if (typeof sourceValue === 'string' && (column.props || {})['mode'] === 'multiple') {
  1228 + sourceValue = sourceValue.split(',')
  1229 + }
  1230 + selectValues[inputId] = sourceValue
  1231 + } else {
  1232 + selectValues[inputId] = undefined
  1233 + }
  1234 +
  1235 + } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
  1236 + jdateValues[inputId] = sourceValue
  1237 +
  1238 + } else if (column.type === FormTypes.slot) {
  1239 + slotValues[inputId] = sourceValue
  1240 +
  1241 + } else if (column.type === FormTypes.popup) {
  1242 + popupValues[inputId] = sourceValue
  1243 + } else if (column.type === FormTypes.input_pop) {
  1244 + jInputPopValues[inputId] = sourceValue
  1245 + } else if (column.type === FormTypes.radio) {
  1246 + radioValues[inputId] = sourceValue
  1247 + } else if (column.type === FormTypes.sel_search) {
  1248 + searchSelectValues[inputId] = sourceValue
  1249 + } else if (column.type === FormTypes.list_multi) {
  1250 + if (typeof sourceValue === 'string' && sourceValue.length > 0) {
  1251 + multiSelectValues[inputId] = sourceValue.split(',')
  1252 + } else {
  1253 + multiSelectValues[inputId] = []
  1254 + }
  1255 + } else if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
  1256 + if (sourceValue) {
  1257 + let fileName = ''
  1258 + if (sourceValue.indexOf(',') > 0) {
  1259 + let sourceValue2 = sourceValue.split(',')[0]
  1260 + fileName = sourceValue2.substring(sourceValue2.lastIndexOf('/') + 1)
  1261 + } else {
  1262 + fileName = sourceValue.substring(sourceValue.lastIndexOf('/') + 1)
  1263 + }
  1264 + uploadValues[inputId] = {
  1265 + name: fileName,
  1266 + status: 'done',
  1267 + path: sourceValue
  1268 + }
  1269 + }
  1270 + } else {
  1271 + value[column.key] = sourceValue
  1272 + }
  1273 +
  1274 + // 解析disabledRows
  1275 + for (let columnKey in this.disabledRows) {
  1276 + // 判断是否有该属性
  1277 + if (this.disabledRows.hasOwnProperty(columnKey) && data.hasOwnProperty(columnKey)) {
  1278 + if (disabled !== true) {
  1279 + let temp = this.disabledRows[columnKey]
  1280 + // 禁用规则可以是一个数组
  1281 + if (temp instanceof Array) {
  1282 + disabled = temp.includes(data[columnKey])
  1283 + } else {
  1284 + disabled = (temp === data[columnKey])
  1285 + }
  1286 + if (disabled) {
  1287 + disabledRowIds.push(row.id)
  1288 + }
  1289 + }
  1290 + }
  1291 + }
  1292 + })
  1293 + // 插入行而不是添加到最后
  1294 + let added = false
  1295 + if (insertIndexes instanceof Array) {
  1296 + let insertIndex = insertIndexes[newValueIndex]
  1297 + if (typeof insertIndex === 'number') {
  1298 + added = true
  1299 + rows.splice(insertIndex, 0, row)
  1300 + this.inputValues.splice(insertIndex, 0, value)
  1301 + }
  1302 + }
  1303 + if (!added) {
  1304 + rows.push(row)
  1305 + this.inputValues.push(value)
1168 1306 }
1169 1307 })
1170   - if (typeof insertIndex === 'number') {
1171   - rows.splice(insertIndex, 0, row)
1172   - this.inputValues.splice(insertIndex, 0, value)
1173   - } else {
1174   - rows.push(row)
1175   - this.inputValues.push(value)
1176   - }
1177   - this.checkboxValues = checkboxValues
1178   - this.selectValues = selectValues
1179   - this.jdateValues = jdateValues
1180   - this.slotValues = slotValues
1181   -
  1308 + // 启用了拖动排序,就重新计算排序编号
1182 1309 if (this.dragSort) {
1183 1310 this.inputValues.forEach((item, index) => {
1184 1311 item[this.dragSortKey] = (index + 1)
1185 1312 })
1186 1313 }
1187   -
  1314 + this.disabledRowIds = disabledRowIds
  1315 + this.checkboxValues = checkboxValues
  1316 + this.selectValues = selectValues
  1317 + this.jdateValues = jdateValues
  1318 + this.jInputPopValues = jInputPopValues
  1319 + this.slotValues = slotValues
  1320 + this.uploadValues = uploadValues
  1321 + this.popupValues = popupValues
  1322 + this.radioValues = radioValues
  1323 + this.multiSelectValues = multiSelectValues
  1324 + this.searchSelectValues = searchSelectValues
  1325 + // 重新计算所有统计列
  1326 + this.recalcAllStatisticsColumns()
  1327 + // 更新到 dom
1188 1328 if (update) {
1189 1329 this.rows = rows
  1330 +
  1331 + // 更新form表单的值
1190 1332 this.$nextTick(() => {
1191 1333 this.updateFormValues()
1192 1334 })
1193 1335 }
1194 1336 return rows
1195 1337 },
  1338 +
1196 1339 /** 获取某一数字输入框列中的最大的值 */
1197 1340 getInputNumberMaxValue(column) {
1198 1341 let maxNum = 0
... ... @@ -1219,9 +1362,8 @@
1219 1362 let rows = this.rows
1220 1363 let row
1221 1364 for (let i = 0; i < num; i++) {
1222   - // row = { id: `${this.caseId}${timestamp}${rows.length}` }
1223   - row = { id: this.generateId(rows) }
1224   - rows = this.push(row, false, rows)
  1365 + rows = this.push({}, false, rows)
  1366 + row = rows[rows.length - 1]
1225 1367 }
1226 1368 this.rows = rows
1227 1369  
... ... @@ -1232,7 +1374,7 @@
1232 1374 this.$emit('added', {
1233 1375 row: (() => {
1234 1376 let r = Object.assign({}, row)
1235   - r.id = this.removeCaseId(r.id)
  1377 + r.id = this.getCleanId(r.id)
1236 1378 return r
1237 1379 })(),
1238 1380 target: this
... ... @@ -1275,7 +1417,7 @@
1275 1417 this.$emit('inserted', {
1276 1418 rows: newRows.map(row => {
1277 1419 let r = cloneObject(row)
1278   - r.id = this.removeCaseId(r.id)
  1420 + r.id = this.getCleanId(r.id)
1279 1421 return r
1280 1422 }),
1281 1423 num, insertIndex,
... ... @@ -1300,10 +1442,12 @@
1300 1442  
1301 1443 let rows = cloneObject(this.rows)
1302 1444 ids.forEach(removeId => {
  1445 + removeId = this.getCleanId(removeId)
1303 1446 // 找到每个id对应的真实index并删除
1304 1447 const findAndDelete = (arr) => {
1305 1448 for (let i = 0; i < arr.length; i++) {
1306   - if (arr[i].id === removeId || arr[i].id === this.caseId + removeId) {
  1449 + let currentId = this.getCleanId(arr[i].id)
  1450 + if (currentId === removeId) {
1307 1451 arr.splice(i, 1)
1308 1452 return true
1309 1453 }
... ... @@ -1314,7 +1458,7 @@
1314 1458 // 找到values对应的index,并删除
1315 1459 findAndDelete(this.inputValues)
1316 1460 // 将caseId去除
1317   - let id = this.removeCaseId(removeId)
  1461 + let id = this.getCleanId(removeId)
1318 1462 this.deleteIds.push(id)
1319 1463 }
1320 1464 })
... ... @@ -1323,15 +1467,19 @@
1323 1467 this.$nextTick(() => {
1324 1468 // 更新formValues
1325 1469 this.updateFormValues()
  1470 + // 重新计算统计
  1471 + this.recalcAllStatisticsColumns()
1326 1472 })
1327 1473 return true
1328 1474 },
1329 1475  
1330 1476 /** 获取表格表单里的值(异步版) */
1331 1477 getValuesAsync(options = {}, callback) {
1332   - let { validate, rowIds } = options
  1478 + let { validate, rowIds, deleteTempId } = options
1333 1479 if (typeof validate !== 'boolean') validate = true
1334 1480 if (!(rowIds instanceof Array)) rowIds = null
  1481 + // 是否删除临时ID,默认为 false
  1482 + if (typeof deleteTempId !== 'boolean') deleteTempId = false
1335 1483 // console.log('options:', { validate, rowIds })
1336 1484  
1337 1485 let asyncCount = 0
... ... @@ -1349,7 +1497,7 @@
1349 1497 rowIdsFlag = true
1350 1498 } else {
1351 1499 for (let rowId of rowIds) {
1352   - if (rowId === value.id || `${this.caseId}${rowId}` === value.id) {
  1500 + if (this.getCleanId(rowId) === this.getCleanId(value.id)) {
1353 1501 rowIdsFlag = true
1354 1502 break
1355 1503 }
... ... @@ -1379,6 +1527,9 @@
1379 1527 } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
1380 1528 value[column.key] = this.jdateValues[inputId]
1381 1529  
  1530 + } else if (column.type === FormTypes.input_pop) {
  1531 + value[column.key] = this.jInputPopValues[inputId]
  1532 +
1382 1533 } else if (column.type === FormTypes.upload) {
1383 1534 value[column.key] = cloneObject(this.uploadValues[inputId] || null)
1384 1535  
... ... @@ -1432,10 +1583,14 @@
1432 1583 handleValidateOneInput(results)
1433 1584 }
1434 1585 })
1435   - // 将caseId去除
1436   - value.id = this.removeCaseId(value.id)
1437   - values.push(value)
  1586 + // 删除 tempId
  1587 + if (deleteTempId && this.isTempId(value.id)) {
  1588 + delete value.id
  1589 + } else {
  1590 + value.id = this.getCleanId(value.id)
  1591 + }
1438 1592  
  1593 + values.push(value)
1439 1594 }
1440 1595  
1441 1596 if (validate === true) {
... ... @@ -1450,7 +1605,7 @@
1450 1605 callback({ error, values })
1451 1606 }
1452 1607 }
1453   - }, 50)
  1608 + }, 10)
1454 1609  
1455 1610 return { error, values }
1456 1611 },
... ... @@ -1469,9 +1624,9 @@
1469 1624 })
1470 1625 },
1471 1626 /** getValues的Promise版 */
1472   - getValuesPromise(validate = true, rowIds) {
  1627 + getValuesPromise(validate = true, rowIds, deleteTempId) {
1473 1628 return new Promise((resolve, reject) => {
1474   - this.getValuesAsync({ validate, rowIds }, ({ error, values }) => {
  1629 + this.getValuesAsync({ validate, rowIds, deleteTempId }, ({ error, values }) => {
1475 1630 if (error === 0) {
1476 1631 resolve(values)
1477 1632 } else {
... ... @@ -1485,10 +1640,10 @@
1485 1640 return cloneObject(this.deleteIds)
1486 1641 },
1487 1642 /** 获取所有的数据,包括values、deleteIds */
1488   - getAll(validate) {
  1643 + getAll(validate, deleteTempId) {
1489 1644 return new Promise((resolve, reject) => {
1490 1645 let deleteIds = this.getDeleteIds()
1491   - this.getValuesPromise(validate).then((values) => {
  1646 + this.getValuesPromise(validate, null, deleteTempId).then((values) => {
1492 1647 resolve({ values, deleteIds })
1493 1648 }).catch(error => {
1494 1649 reject(error)
... ... @@ -1496,8 +1651,8 @@
1496 1651 })
1497 1652 },
1498 1653 /** Sync 获取所有的数据,包括values、deleteIds */
1499   - getAllSync(validate, rowIds) {
1500   - let result = this.getValuesSync({ validate, rowIds })
  1654 + getAllSync(validate, rowIds, deleteTempId) {
  1655 + let result = this.getValuesSync({ validate, rowIds, deleteTempId })
1501 1656 result.deleteIds = this.getDeleteIds()
1502 1657 return result
1503 1658 },
... ... @@ -1511,6 +1666,7 @@
1511 1666 selectValues: this.selectValues,
1512 1667 checkboxValues: this.checkboxValues,
1513 1668 jdateValues: this.jdateValues,
  1669 + jInputPopValues: this.jInputPopValues,
1514 1670 slotValues: this.slotValues,
1515 1671 uploadValues: this.uploadValues,
1516 1672 popupValues: this.popupValues,
... ... @@ -1524,13 +1680,14 @@
1524 1680  
1525 1681 values.forEach(item => {
1526 1682 let { rowKey, values: newValues } = item
  1683 + rowKey = this.getCleanId(rowKey)
1527 1684 for (let newValueKey in newValues) {
1528 1685 if (newValues.hasOwnProperty(newValueKey)) {
1529 1686 let newValue = newValues[newValueKey]
1530 1687 let edited = false // 已被修改
1531 1688 this.inputValues.forEach(value => {
1532 1689 // 在inputValues中找到了该字段
1533   - if (`${this.caseId}${rowKey}` === value.id) {
  1690 + if (rowKey === this.getCleanId(value.id)) {
1534 1691 if (value.hasOwnProperty(newValueKey)) {
1535 1692 edited = true
1536 1693 value[newValueKey] = newValue
... ... @@ -1539,28 +1696,48 @@
1539 1696 })
1540 1697 let modelKey = `${newValueKey}${this.caseId}${rowKey}`
1541 1698 // 在 selectValues 中寻找值
1542   - if (!edited && this.selectValues.hasOwnProperty(modelKey)) {
  1699 + if (!edited) {
1543 1700 if (newValue !== 0 && !newValue) {
1544   - this.selectValues[modelKey] = undefined
  1701 + edited = this.setOneValue(this.selectValues, modelKey, undefined)
1545 1702 } else {
1546   - this.selectValues[modelKey] = newValue
  1703 + edited = this.setOneValue(this.selectValues, modelKey, newValue)
1547 1704 }
1548   - edited = true
1549 1705 }
1550 1706 // 在 checkboxValues 中寻找值
1551   - if (!edited && this.checkboxValues.hasOwnProperty(modelKey)) {
1552   - this.checkboxValues[modelKey] = newValue
1553   - edited = true
  1707 + if (!edited) {
  1708 + edited = this.setOneValue(this.checkboxValues, modelKey, newValue)
1554 1709 }
1555 1710 // 在 jdateValues 中寻找值
1556   - if (!edited && this.jdateValues.hasOwnProperty(modelKey)) {
1557   - this.jdateValues[modelKey] = newValue
1558   - edited = true
  1711 + if (!edited) {
  1712 + edited = this.setOneValue(this.jdateValues, modelKey, newValue)
  1713 + }
  1714 + // 在 jInputPopValues 中寻找值
  1715 + if (!edited) {
  1716 + edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
1559 1717 }
1560 1718 // 在 slotValues 中寻找值
1561   - if (!edited && this.slotValues.hasOwnProperty(modelKey)) {
1562   - this.slotValues[modelKey] = newValue
1563   - edited = true
  1719 + if (!edited) {
  1720 + edited = this.setOneValue(this.slotValues, modelKey, newValue)
  1721 + }
  1722 + // 在 uploadValues 中寻找值
  1723 + if (!edited) {
  1724 + edited = this.setOneValue(this.uploadValues, modelKey, newValue)
  1725 + }
  1726 + // 在 popupValues 中寻找值
  1727 + if (!edited) {
  1728 + edited = this.setOneValue(this.popupValues, modelKey, newValue)
  1729 + }
  1730 + // 在 radioValues 中寻找值
  1731 + if (!edited) {
  1732 + edited = this.setOneValue(this.radioValues, modelKey, newValue)
  1733 + }
  1734 + // 在 multiSelectValues 中寻找值
  1735 + if (!edited) {
  1736 + edited = this.setOneValue(this.multiSelectValues, modelKey, newValue)
  1737 + }
  1738 + // 在 searchSelectValues 中寻找值
  1739 + if (!edited) {
  1740 + edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
1564 1741 }
1565 1742 }
1566 1743 }
... ... @@ -1568,6 +1745,24 @@
1568 1745 // 强制更新formValues
1569 1746 this.forceUpdateFormValues()
1570 1747 },
  1748 + setOneValue(valuesObject, modelKey, value) {
  1749 + let key = this.valuesHasOwnProperty(valuesObject, modelKey)
  1750 + if (key) {
  1751 + this.$set(valuesObject, key, value)
  1752 + return true
  1753 + }
  1754 + return false
  1755 + },
  1756 + valuesHasOwnProperty(values, ownProperty) {
  1757 + let key = ownProperty
  1758 + if (values.hasOwnProperty(key)) {
  1759 + return key
  1760 + }
  1761 + if (values.hasOwnProperty(key + this.tempId)) {
  1762 + return key + this.tempId
  1763 + }
  1764 + return null
  1765 + },
1571 1766  
1572 1767 /** 跳转到指定位置 */
1573 1768 // jumpToId(id, element) {
... ... @@ -1602,44 +1797,40 @@
1602 1797  
1603 1798 const nextThen = res => {
1604 1799 let [passed, message] = res
1605   - if (passed == null) {
1606   - // debugger
1607   - }
1608   - if (passed == null && tooltips[inputId].visible != null) {
1609   - return
1610   - }
1611   - passed = passed == null ? true : passed
1612   - tooltips[inputId].visible = !passed
1613   - tooltips[inputId].passed = passed
1614   - let index = notPassedIds.indexOf(inputId)
1615   - let borderColor = null, boxShadow = null
1616   - if (!passed) {
1617   - tooltips[inputId].title = this.replaceProps(column, message)
1618   - borderColor = 'red'
1619   - boxShadow = `0 0 0 2px rgba(255, 0, 0, 0.2)`
1620   - if (index === -1) notPassedIds.push(inputId)
1621   - } else {
1622   - if (index !== -1) notPassedIds.splice(index, 1)
1623   - }
1624   -
1625   - let element = document.getElementById(inputId)
1626   - if (element != null) {
1627   - // select 在 .ant-select-selection 上设置 border-color
1628   - if (column.type === FormTypes.select) {
1629   - element = element.getElementsByClassName('ant-select-selection')[0]
1630   - }
1631   - // jdate 在 input 上设置 border-color
1632   - if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
1633   - element = element.getElementsByTagName('input')[0]
1634   - }
1635   - // upload 在 .ant-upload .ant-btn 上设置 border-color
1636   - if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
1637   - element = element.getElementsByClassName('ant-upload')[0].getElementsByClassName('ant-btn')[0]
  1800 + // !(passed == null && tooltips[inputId].visible != null)
  1801 + if (passed != null) {
  1802 + tooltips[inputId].visible = !passed
  1803 + tooltips[inputId].passed = passed
  1804 + let index = notPassedIds.indexOf(inputId)
  1805 + let borderColor = null, boxShadow = null
  1806 + if (!passed) {
  1807 + tooltips[inputId].title = this.replaceProps(column, message)
  1808 + borderColor = 'red'
  1809 + boxShadow = `0 0 0 2px rgba(255, 0, 0, 0.2)`
  1810 + if (index === -1) notPassedIds.push(inputId)
  1811 + } else {
  1812 + if (index !== -1) notPassedIds.splice(index, 1)
1638 1813 }
1639   - element.style.borderColor = borderColor
1640   - element.style.boxShadow = boxShadow
1641   - if (element.tagName === 'SPAN') {
1642   - element.style.display = 'block'
  1814 +
  1815 + let element = document.getElementById(inputId)
  1816 + if (element != null) {
  1817 + // select 在 .ant-select-selection 上设置 border-color
  1818 + if (column.type === FormTypes.select) {
  1819 + element = element.getElementsByClassName('ant-select-selection')[0]
  1820 + }
  1821 + // jdate 在 input 上设置 border-color
  1822 + if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
  1823 + element = element.getElementsByTagName('input')[0]
  1824 + }
  1825 + // upload 在 .ant-upload .ant-btn 上设置 border-color
  1826 + if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
  1827 + element = element.getElementsByClassName('ant-upload')[0].getElementsByClassName('ant-btn')[0]
  1828 + }
  1829 + element.style.borderColor = borderColor
  1830 + element.style.boxShadow = boxShadow
  1831 + if (element.tagName === 'SPAN') {
  1832 + element.style.display = 'block'
  1833 + }
1643 1834 }
1644 1835 }
1645 1836 // 是否更新到data
... ... @@ -1656,16 +1847,16 @@
1656 1847  
1657 1848 if (typeof passed === 'function') {
1658 1849 let executed = false
1659   - passed(validType, value, row, column, (flag, msg) => {
  1850 + passed(validType, value, { id: this.getCleanId(row.id) }, { ...column }, (flag, msg) => {
1660 1851 if (executed) return
1661 1852 executed = true
1662 1853 if (typeof msg === 'string') {
1663 1854 message = msg
1664 1855 }
1665   - if (flag == null || flag === true) {
1666   - nextThen([true, message])
  1856 + if (flag == null) {
  1857 + nextThen([null, message])
1667 1858 } else {
1668   - nextThen([false, message])
  1859 + nextThen([!!flag, message])
1669 1860 }
1670 1861 }, this)
1671 1862 } else {
... ... @@ -1710,17 +1901,17 @@
1710 1901  
1711 1902 // 兼容 online 的规则
1712 1903 let foo = [
1713   - { title: '6到16位数字', value: 'n6-16', pattern: /\d{6,18}/ },
  1904 + { title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,18}$/ },
1714 1905 { title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/ },
  1906 + { title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/ },
1715 1907 { title: '网址', value: 'url', pattern: /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/ },
1716 1908 { title: '电子邮件', value: 'e', pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/ },
1717 1909 { title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/ },
1718 1910 { title: '邮政编码', value: 'p', pattern: /^[1-9]\d{5}$/ },
1719 1911 { title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/ },
1720   - { title: '数字', value: 'n', pattern: /^-?\d+\.?\d*$/ },
  1912 + { title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/ },
1721 1913 { title: '整数', value: 'z', pattern: /^-?\d+$/ },
1722 1914 { title: '非空', value: '*', pattern: /^.+$/ },
1723   - { title: '6到18位字符串', value: 's6-18', pattern: /^.{6,18}$/ },
1724 1915 { title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/ },
1725 1916 ]
1726 1917 let flag = false
... ... @@ -1793,6 +1984,43 @@
1793 1984 this.updateFormValues()
1794 1985 },
1795 1986  
  1987 + // 重新计算所有统计列
  1988 + recalcAllStatisticsColumns() {
  1989 + if (this.hasStatisticsColumn) {
  1990 + Object.keys(this.statisticsColumns).forEach(key => this.recalcOneStatisticsColumn(key))
  1991 + }
  1992 + },
  1993 + // 重新计算单个统计列
  1994 + recalcOneStatisticsColumn(key) {
  1995 + if (this.hasStatisticsColumn) {
  1996 + if (this.statisticsColumns.hasOwnProperty(key)) {
  1997 + // 计算合计值
  1998 + let count = 0
  1999 + this.inputValues.forEach(item => {
  2000 + let value = item[key]
  2001 + if (value && count !== '-') {
  2002 + try {
  2003 + count += Number.parseInt(value)
  2004 + } catch (e) {
  2005 + count = '-'
  2006 + }
  2007 + }
  2008 + })
  2009 + this.statisticsColumns[key] = count
  2010 + }
  2011 + }
  2012 + },
  2013 +
  2014 + /** 获取某个统计字段的值 */
  2015 + getStatisticsValue(key) {
  2016 + if (this.hasStatisticsColumn) {
  2017 + if (this.statisticsColumns.hasOwnProperty(key)) {
  2018 + return this.statisticsColumns[key]
  2019 + }
  2020 + }
  2021 + return null
  2022 + },
  2023 +
1796 2024 /** 全选或取消全选 */
1797 2025 handleChangeCheckedAll() {
1798 2026 let selectedRowIds = []
... ... @@ -1835,7 +2063,7 @@
1835 2063 },
1836 2064 /** 用于搜索下拉框中的内容 */
1837 2065 handleSelectFilterOption(input, option, column) {
1838   - if (column.allowSearch === true) {
  2066 + if (column.allowSearch === true || column.allowInput === true) {
1839 2067 return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
1840 2068 }
1841 2069 return true
... ... @@ -1879,6 +2107,8 @@
1879 2107 }
1880 2108  
1881 2109 }
  2110 + // 做单个表单验证
  2111 + this.validateOneInput(value, row, col, this.notPassedIds, true, 'blur')
1882 2112 },
1883 2113  
1884 2114 /** 触发已拖动事件 */
... ... @@ -1886,8 +2116,16 @@
1886 2116 this.$emit('dragged', { oldIndex, newIndex, target: this })
1887 2117 },
1888 2118  
  2119 + handleDragMoveStart(event) {
  2120 + this.dragging = true
  2121 + this.$refs.scrollView.style.overflow = 'hidden'
  2122 + },
  2123 +
1889 2124 /** 拖动结束,交换inputValue中的值 */
1890 2125 handleDragMoveEnd(event) {
  2126 + this.dragging = false
  2127 + this.$refs.scrollView.style.overflow = 'auto'
  2128 +
1891 2129 let { oldIndex, newIndex, item: { dataset: { idx: dataIdx } } } = event
1892 2130  
1893 2131 // 由于动态显示隐藏行导致index有误差,需要算出真实的index
... ... @@ -1965,16 +2203,17 @@
1965 2203 },
1966 2204 /** input事件 */
1967 2205 handleInputCommono(target, index, row, column) {
  2206 + let oldValue = this.inputValues[index][column.key] || ''
1968 2207 let { value, dataset, selectionStart } = target
1969 2208 let type = FormTypes.input
1970 2209 let change = true
1971 2210 if (`${dataset.inputNumber}` === 'true') {
1972 2211 type = FormTypes.inputNumber
1973   - let replace = value.replace(/[^0-9]/g, '')
1974   - if (value !== replace) {
  2212 + // 判断输入的值是否匹配数字正则表达式,不匹配就还原
  2213 + if (!/^-?\d+\.?\d*$/.test(value) && (value !== '' && value !== '-')) {
1975 2214 change = false
1976   - value = replace
1977   - target.value = replace
  2215 + value = oldValue
  2216 + target.value = value
1978 2217 if (typeof selectionStart === 'number') {
1979 2218 target.selectionStart = selectionStart - 1
1980 2219 target.selectionEnd = selectionStart - 1
... ... @@ -1986,6 +2225,10 @@
1986 2225 // 做单个表单验证
1987 2226 this.validateOneInput(value, row, column, this.notPassedIds, true, 'input')
1988 2227  
  2228 + if (type === FormTypes.inputNumber) {
  2229 + this.recalcOneStatisticsColumn(column.key)
  2230 + }
  2231 +
1989 2232 // 触发valueChange 事件
1990 2233 if (change) {
1991 2234 this.elemValueChange(type, row, column, value)
... ... @@ -2000,7 +2243,16 @@
2000 2243 this.elemValueChange(FormTypes.slot, row, column, value)
2001 2244 },
2002 2245 handleBlurCommono(target, index, row, column) {
2003   - let { value } = target
  2246 + let { value, dataset } = target
  2247 + if (dataset && `${dataset.inputNumber}` === 'true') {
  2248 + // 判断输入的值是否匹配数字正则表达式,不匹配就置空
  2249 + if (!/^-?\d+\.?\d*$/.test(value)) {
  2250 + value = ''
  2251 + } else {
  2252 + value = Number.parseFloat(value)
  2253 + }
  2254 + target.value = value
  2255 + }
2004 2256 // 做单个表单验证
2005 2257 this.validateOneInput(value, row, column, this.notPassedIds, true, 'blur')
2006 2258 },
... ... @@ -2030,6 +2282,13 @@
2030 2282 this.elemValueChange(FormTypes.date, row, column, value)
2031 2283 }
2032 2284 },
  2285 + handleChangeJInputPopCommon(value, id, row, column){
  2286 + this.jInputPopValues = this.bindValuesChange(value, id, 'jInputPopValues')
  2287 + // 做单个表单验证
  2288 + this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
  2289 + // 触发valueChange 事件
  2290 + this.elemValueChange(FormTypes.input_pop, row, column, value)
  2291 + },
2033 2292 handleChangeUpload(info, id, row, column) {
2034 2293 let { file } = info
2035 2294 let value = {
... ... @@ -2042,11 +2301,26 @@
2042 2301 if (column.responseName && file.response) {
2043 2302 value['responseName'] = file.response[column.responseName]
2044 2303 }
2045   - if (file.status == 'done') {
  2304 + if (file.status === 'done') {
2046 2305 value['path'] = file.response[column.responseName]
  2306 + } else if (file.status === 'error') {
  2307 + value['message'] = file.response.message || '未知错误'
2047 2308 }
2048 2309 this.uploadValues = this.bindValuesChange(value, id, 'uploadValues')
2049 2310 },
  2311 + handleMoreOperation(id,flag){
  2312 + //console.log("this.uploadValues[id]",this.uploadValues[id])
  2313 + let path = ''
  2314 + if(this.uploadValues && this.uploadValues[id]){
  2315 + path = this.uploadValues[id].path
  2316 + }
  2317 + this.$refs.filePop.show(id,path,flag)
  2318 + },
  2319 + handleFileSuccess(obj){
  2320 + if(obj.id){
  2321 + this.uploadValues = this.bindValuesChange(obj, obj.id, 'uploadValues')
  2322 + }
  2323 + },
2050 2324 /** 记录用到数据绑定的组件的值 */
2051 2325 bindValuesChange(value, id, key) {
2052 2326 // let values = Object.assign({}, this[key])
... ... @@ -2074,7 +2348,7 @@
2074 2348 let column = Object.assign({}, columnSource)
2075 2349 // 将caseId去除
2076 2350 let row = Object.assign({}, rowSource)
2077   - row.id = this.removeCaseId(row.id)
  2351 + row.id = this.getCleanId(row.id)
2078 2352 // 获取整行的数据
2079 2353 let { values } = this.getValuesSync({ validate: false, rowIds: [row.id] })
2080 2354 if (values.length > 0) {
... ... @@ -2083,10 +2357,37 @@
2083 2357 this.$emit('valueChange', { type, row, column, value, target: this })
2084 2358 },
2085 2359  
  2360 + /** 获取干净的ID(不包含任何杂质的ID) */
  2361 + getCleanId(id) {
  2362 + id = this.removeCaseId(id)
  2363 + id = this.removeTempId(id)
  2364 + return id
  2365 + },
  2366 +
  2367 + /** 判断某个ID是否包含了caseId */
  2368 + hasCaseId(id) {
  2369 + return id && id.startsWith(this.caseId)
  2370 + },
  2371 +
2086 2372 /** 将caseId去除 */
2087 2373 removeCaseId(id) {
2088   - let remove = id.split(this.caseId)[1]
2089   - return remove ? remove : id
  2374 + if (this.hasCaseId(id)) {
  2375 + return id.substring(this.caseId.length, id.length)
  2376 + }
  2377 + return id
  2378 + },
  2379 +
  2380 + // 判断 id 是否是临时Id
  2381 + isTempId(id) {
  2382 + return (id || '').endsWith(this.tempId)
  2383 + },
  2384 +
  2385 + /** 将tempId去除 */
  2386 + removeTempId(id) {
  2387 + if (this.isTempId(id)) {
  2388 + return id.substring(0, id.length - this.tempId.length)
  2389 + }
  2390 + return id;
2090 2391 },
2091 2392  
2092 2393 handleClickDelFile(id) {
... ... @@ -2095,10 +2396,28 @@
2095 2396 handleClickDownloadFile(id) {
2096 2397 let { path } = this.uploadValues[id] || {}
2097 2398 if (path) {
2098   - let url = window._CONFIG['staticDomainURL'] + '/' + path
  2399 + let url = getFileAccessHttpUrl(path)
  2400 + window.open(url)
  2401 + }
  2402 + },
  2403 + handleClickDownFileByUrl(id){
  2404 + let { url,path } = this.uploadValues[id] || {}
  2405 + if (!url || url.length===0) {
  2406 + if(path && path.length>0){
  2407 + url = getFileAccessHttpUrl(path.split(',')[0])
  2408 + }
  2409 + }
  2410 + if(url){
2099 2411 window.open(url)
2100 2412 }
2101 2413 },
  2414 + handleClickShowImageError(id) {
  2415 + let currUploadObj = this.uploadValues[id] || null
  2416 + if (currUploadObj && currUploadObj['message']) {
  2417 + this.$error({ title: '上传出错', content: '错误信息:' + currUploadObj['message'], maskClosable: true })
  2418 + }
  2419 + },
  2420 +
2102 2421 /** 加载数据字典并合并到 options */
2103 2422 _loadDictConcatToOptions(column) {
2104 2423 initDictOptions(column.dictCode).then((res) => {
... ... @@ -2122,7 +2441,11 @@
2122 2441 /* --- 以下是辅助方法,多用于动态构造页面中的数据 --- */
2123 2442  
2124 2443 /** 辅助方法:打印日志 */
2125   - log: console.log,
  2444 + log() {
  2445 + if (this.$attrs.logger) {
  2446 + console.log.apply(null, arguments)
  2447 + }
  2448 + },
2126 2449  
2127 2450 getVM() {
2128 2451 return this
... ... @@ -2265,11 +2588,15 @@
2265 2588 /** 预览图片地址 */
2266 2589 getCellImageView(id) {
2267 2590 let currUploadObj = this.uploadValues[id] || null
2268   - if (currUploadObj && currUploadObj['path']) {
2269   - return window._CONFIG['staticDomainURL'] + '/' + currUploadObj['path']
2270   - } else {
2271   - return ''
  2591 + if (currUploadObj) {
  2592 + if(currUploadObj['url']){
  2593 + return currUploadObj['url'];
  2594 + }else if(currUploadObj['path']){
  2595 + let readpath = currUploadObj['path'].split(',')[0]
  2596 + return getFileAccessHttpUrl(readpath)
  2597 + }
2272 2598 }
  2599 + return ''
2273 2600 },
2274 2601 /** popup回调 */
2275 2602 popupCallback(value, others, id, row, column, index) {
... ... @@ -2277,7 +2604,18 @@
2277 2604 this.popupValues[id] = value
2278 2605 if (others) {
2279 2606 Object.keys(others).map((key) => {
2280   - this.inputValues[index][key] = others[key]
  2607 + this.columns.map(k=>{
  2608 + if(k.key === key){
  2609 + let tempId = id.substring(id.indexOf(this.caseIdPrefix))
  2610 + if(k.type === 'date'){
  2611 + this.handleChangeJDateCommon(others[key], key+tempId, {id:tempId}, k, false)
  2612 + }else if(k.type === 'datetime'){
  2613 + this.handleChangeJDateCommon(others[key], key+tempId, {id:tempId}, k, true)
  2614 + }else{
  2615 + this.inputValues[index][key] = others[key]
  2616 + }
  2617 + }
  2618 + })
2281 2619 })
2282 2620 }
2283 2621 // 做单个表单验证
... ... @@ -2315,9 +2653,20 @@
2315 2653 filterOption(input, option) {
2316 2654 return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
2317 2655 },
  2656 + getEllipsisWord(content, len){
  2657 + if(!content || content.length==0){
  2658 + return ''
  2659 + }
  2660 + if(content.length>len){
  2661 + return content.substr(0,len)
  2662 + }
  2663 + return content;
  2664 + }
2318 2665  
2319   -
2320   - }
  2666 + },
  2667 + beforeDestroy() {
  2668 + this.destroyCleanGroupRequest = true
  2669 + },
2321 2670 }
2322 2671 </script>
2323 2672  
... ... @@ -2538,6 +2887,21 @@
2538 2887  
2539 2888 }
2540 2889  
  2890 + .j-editable-image {
  2891 + height: 32px;
  2892 + max-width: 100px !important;
  2893 + cursor: pointer;
  2894 +
  2895 + &:hover {
  2896 + opacity: 0.8;
  2897 + }
  2898 +
  2899 + &:active {
  2900 + opacity: 0.6;
  2901 + }
  2902 +
  2903 + }
  2904 +
2541 2905 }
2542 2906  
2543 2907 }
... ...
ant-design-vue-jeecg/src/components/jeecg/JEditor.vue
... ... @@ -23,6 +23,7 @@
23 23 import 'tinymce/plugins/colorpicker'
24 24 import 'tinymce/plugins/textcolor'
25 25 import 'tinymce/plugins/fullscreen'
  26 + import { uploadAction,getFileAccessHttpUrl } from '@/api/manage'
26 27 export default {
27 28 components: {
28 29 Editor
... ... @@ -65,8 +66,21 @@
65 66 menubar: false,
66 67 toolbar_drawer: false,
67 68 images_upload_handler: (blobInfo, success) => {
68   - const img = 'data:image/jpeg;base64,' + blobInfo.base64()
69   - success(img)
  69 + let formData = new FormData()
  70 + formData.append('file', blobInfo.blob(), blobInfo.filename());
  71 + formData.append('biz', "jeditor");
  72 + formData.append("jeditor","1");
  73 + uploadAction(window._CONFIG['domianURL']+"/sys/common/upload", formData).then((res) => {
  74 + if (res.success) {
  75 + if(res.message == 'local'){
  76 + const img = 'data:image/jpeg;base64,' + blobInfo.base64()
  77 + success(img)
  78 + }else{
  79 + let img = getFileAccessHttpUrl(res.message)
  80 + success(img)
  81 + }
  82 + }
  83 + })
70 84 }
71 85 },
72 86 myValue: this.value
... ...
ant-design-vue-jeecg/src/components/jeecg/JFormContainer.vue
1 1 <template>
2 2 <div :class="disabled?'jeecg-form-container-disabled':''">
3   - <fieldset disabled>
  3 + <fieldset :disabled="disabled">
4 4 <slot name="detail"></slot>
5 5 </fieldset>
6 6 <slot name="edit"></slot>
... ... @@ -46,4 +46,16 @@
46 46 -ms-pointer-events: none;
47 47 pointer-events: none;
48 48 }
  49 +
  50 + .jeecg-form-container-disabled .ant-upload-select{display:none}
  51 + .jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
  52 + .jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
  53 + -ms-pointer-events: auto !important;
  54 + pointer-events: auto !important;
  55 + }
  56 +
  57 + .jeecg-form-container-disabled .ant-upload-list-item-actions .anticon-delete,
  58 + .jeecg-form-container-disabled .ant-upload-list-item .anticon-close{
  59 + display: none;
  60 + }
49 61 </style>
50 62 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/jeecg/JGraphicCode.vue deleted
1   -<template>
2   - <div class="gc-canvas" @click="reloadPic">
3   - <canvas id="gc-canvas" :width="contentWidth" :height="contentHeight"></canvas>
4   - </div>
5   -</template>
6   -
7   -<script>
8   - import { getAction } from '@/api/manage'
9   -
10   - export default {
11   - name: 'JGraphicCode',
12   - props: {
13   - length:{
14   - type: Number,
15   - default: 4
16   - },
17   - fontSizeMin: {
18   - type: Number,
19   - default: 20
20   - },
21   - fontSizeMax: {
22   - type: Number,
23   - default: 45
24   - },
25   - backgroundColorMin: {
26   - type: Number,
27   - default: 180
28   - },
29   - backgroundColorMax: {
30   - type: Number,
31   - default: 240
32   - },
33   - colorMin: {
34   - type: Number,
35   - default: 50
36   - },
37   - colorMax: {
38   - type: Number,
39   - default: 160
40   - },
41   - lineColorMin: {
42   - type: Number,
43   - default: 40
44   - },
45   - lineColorMax: {
46   - type: Number,
47   - default: 180
48   - },
49   - dotColorMin: {
50   - type: Number,
51   - default: 0
52   - },
53   - dotColorMax: {
54   - type: Number,
55   - default: 255
56   - },
57   - contentWidth: {
58   - type: Number,
59   - default:136
60   - },
61   - contentHeight: {
62   - type: Number,
63   - default: 38
64   - },
65   - remote:{
66   - type:Boolean,
67   - default:false,
68   - required:false
69   - }
70   - },
71   - methods: {
72   - // 生成一个随机数
73   - randomNum (min, max) {
74   - return Math.floor(Math.random() * (max - min) + min)
75   - },
76   - // 生成一个随机的颜色
77   - randomColor (min, max) {
78   - let r = this.randomNum(min, max)
79   - let g = this.randomNum(min, max)
80   - let b = this.randomNum(min, max)
81   - return 'rgb(' + r + ',' + g + ',' + b + ')'
82   - },
83   - drawPic () {
84   - this.randomCode().then(()=>{
85   - let canvas = document.getElementById('gc-canvas')
86   - let ctx = canvas.getContext('2d')
87   - ctx.textBaseline = 'bottom'
88   - // 绘制背景
89   - ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
90   - ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
91   - // 绘制文字
92   - for (let i = 0; i < this.code.length; i++) {
93   - this.drawText(ctx, this.code[i], i)
94   - }
95   - this.drawLine(ctx)
96   - this.drawDot(ctx)
97   - this.$emit("success",this.code)
98   - })
99   - },
100   - drawText (ctx, txt, i) {
101   - ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
102   - let fontSize = this.randomNum(this.fontSizeMin, this.fontSizeMax)
103   - ctx.font = fontSize + 'px SimHei'
104   - let padding = 10;
105   - let offset = (this.contentWidth-40)/(this.code.length-1)
106   - let x=padding;
107   - if(i>0){
108   - x = padding+(i*offset)
109   - }
110   - //let x = (i + 1) * (this.contentWidth / (this.code.length + 1))
111   - let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
112   - if(fontSize>40){
113   - y=40
114   - }
115   - var deg = this.randomNum(-10,10)
116   - // 修改坐标原点和旋转角度
117   - ctx.translate(x, y)
118   - ctx.rotate(deg * Math.PI / 180)
119   - ctx.fillText(txt, 0, 0)
120   - // 恢复坐标原点和旋转角度
121   - ctx.rotate(-deg * Math.PI / 180)
122   - ctx.translate(-x, -y)
123   - },
124   - drawLine (ctx) {
125   - // 绘制干扰线
126   - for (let i = 0; i <1; i++) {
127   - ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
128   - ctx.beginPath()
129   - ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
130   - ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
131   - ctx.stroke()
132   - }
133   - },
134   - drawDot (ctx) {
135   - // 绘制干扰点
136   - for (let i = 0; i < 100; i++) {
137   - ctx.fillStyle = this.randomColor(0, 255)
138   - ctx.beginPath()
139   - ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
140   - ctx.fill()
141   - }
142   - },
143   - reloadPic(){
144   - this.drawPic()
145   - },
146   - randomCode(){
147   - return new Promise((resolve)=>{
148   - if(this.remote==true){
149   - getAction("/sys/getCheckCode").then(res=>{
150   - console.log("aaaaa",res)
151   - if(res.success){
152   - this.checkKey = res.result.key
153   - this.code = window.atob(res.result.code)
154   - resolve();
155   - }else{
156   - this.$message.error("生成验证码错误,请联系系统管理员")
157   - this.code = 'BUG'
158   - resolve();
159   - }
160   - }).catch(()=>{
161   - console.log("生成验证码连接服务器异常")
162   - this.code = 'BUG'
163   - resolve();
164   - })
165   - }else{
166   - this.randomLocalCode();
167   - resolve();
168   - }
169   - })
170   - },
171   - randomLocalCode(){
172   - let random = ''
173   - //去掉了I l i o O
174   - let str = "QWERTYUPLKJHGFDSAZXCVBNMqwertyupkjhgfdsazxcvbnm1234567890"
175   - for(let i = 0; i < this.length; i++) {
176   - let index = Math.floor(Math.random()*57);
177   - random += str[index];
178   - }
179   - this.code = random
180   - },
181   - getLoginParam(){
182   - return {
183   - checkCode:this.code,
184   - checkKey:this.checkKey
185   - }
186   - }
187   - },
188   - mounted () {
189   - this.drawPic()
190   - },
191   - data(){
192   - return {
193   - code:"",
194   - checkKey:""
195   - }
196   - }
197   -
198   - }
199   -</script>
200   -
201   -<style scoped>
202   -
203   -</style>
204 0 \ No newline at end of file
ant-design-vue-jeecg/src/components/jeecg/JImageUpload.vue
... ... @@ -44,7 +44,6 @@
44 44 data(){
45 45 return {
46 46 uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
47   - urlView:window._CONFIG['staticDomainURL'],
48 47 uploadLoading:false,
49 48 picUrl:false,
50 49 headers:{},
... ... @@ -103,7 +102,7 @@
103 102 let fileList = [];
104 103 let arr = paths.split(",")
105 104 for(var a=0;a<arr.length;a++){
106   - let url = getFileAccessHttpUrl(arr[a],this.urlView,"http");
  105 + let url = getFileAccessHttpUrl(arr[a]);
107 106 fileList.push({
108 107 uid: uidGenerator(),
109 108 name: getFileName(arr[a]),
... ... @@ -156,7 +155,7 @@
156 155 getAvatarView(){
157 156 if(this.fileList.length>0){
158 157 let url = this.fileList[0].url
159   - return getFileAccessHttpUrl(url,this.urlView,"http")
  158 + return getFileAccessHttpUrl(url)
160 159 }
161 160 },
162 161 handlePathChange(){
... ...
ant-design-vue-jeecg/src/components/jeecg/JInput.vue
... ... @@ -32,7 +32,12 @@
32 32 handler:function(){
33 33 this.initVal();
34 34 }
35   - }
  35 + },
  36 + // update-begin author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
  37 + type() {
  38 + this.backValue({ target: { value: this.inputVal } })
  39 + },
  40 + // update-end author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
36 41 },
37 42 model: {
38 43 prop: 'value',
... ...
ant-design-vue-jeecg/src/components/jeecg/JMarkdownEditor/default-options.js 0 → 100644
  1 +export default {
  2 + minHeight: '200px',
  3 + previewStyle: 'vertical',
  4 + useCommandShortcut: true,
  5 + useDefaultHTMLSanitizer: true,
  6 + usageStatistics: false,
  7 + hideModeSwitch: false,
  8 + toolbarItems: [
  9 + 'heading',
  10 + 'bold',
  11 + 'italic',
  12 + 'strike',
  13 + 'divider',
  14 + 'hr',
  15 + 'quote',
  16 + 'divider',
  17 + 'ul',
  18 + 'ol',
  19 + 'task',
  20 + 'indent',
  21 + 'outdent',
  22 + 'divider',
  23 + 'table',
  24 + 'image',
  25 + 'link',
  26 + 'divider',
  27 + 'code',
  28 + 'codeblock'
  29 + ]
  30 +}
... ...
ant-design-vue-jeecg/src/components/jeecg/JMarkdownEditor/index.vue 0 → 100644
  1 +<template>
  2 + <div :id="id" />
  3 +</template>
  4 +
  5 +<script>
  6 +import 'codemirror/lib/codemirror.css'
  7 +import 'tui-editor/dist/tui-editor.css'
  8 +import 'tui-editor/dist/tui-editor-contents.css'
  9 +
  10 +import Editor from 'tui-editor'
  11 +import defaultOptions from './default-options'
  12 +
  13 +export default {
  14 + name: 'JMarkdownEditor',
  15 + props: {
  16 + value: {
  17 + type: String,
  18 + default: ''
  19 + },
  20 + id: {
  21 + type: String,
  22 + required: false,
  23 + default() {
  24 + return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
  25 + }
  26 + },
  27 + options: {
  28 + type: Object,
  29 + default() {
  30 + return defaultOptions
  31 + }
  32 + },
  33 + mode: {
  34 + type: String,
  35 + default: 'markdown'
  36 + },
  37 + height: {
  38 + type: String,
  39 + required: false,
  40 + default: '300px'
  41 + },
  42 + language: {
  43 + type: String,
  44 + required: false,
  45 + default: 'en_US'
  46 + }
  47 + },
  48 + data() {
  49 + return {
  50 + editor: null
  51 + }
  52 + },
  53 + computed: {
  54 + editorOptions() {
  55 + const options = Object.assign({}, defaultOptions, this.options)
  56 + options.initialEditType = this.mode
  57 + options.height = this.height
  58 + options.language = this.language
  59 + return options
  60 + }
  61 + },
  62 + watch: {
  63 + value(newValue, preValue) {
  64 + if (newValue !== preValue && newValue !== this.editor.getValue()) {
  65 + this.editor.setValue(newValue)
  66 + }
  67 + },
  68 + language(val) {
  69 + this.destroyEditor()
  70 + this.initEditor()
  71 + },
  72 + height(newValue) {
  73 + this.editor.height(newValue)
  74 + },
  75 + mode(newValue) {
  76 + this.editor.changeMode(newValue)
  77 + }
  78 + },
  79 + mounted() {
  80 + this.initEditor()
  81 + },
  82 + destroyed() {
  83 + this.destroyEditor()
  84 + },
  85 + methods: {
  86 + initEditor() {
  87 + this.editor = new Editor({
  88 + el: document.getElementById(this.id),
  89 + ...this.editorOptions
  90 + })
  91 + if (this.value) {
  92 + this.editor.setValue(this.value)
  93 + }
  94 + this.editor.on('change', () => {
  95 + this.$emit('change', this.editor.getValue())
  96 + })
  97 + },
  98 + destroyEditor() {
  99 + if (!this.editor) return
  100 + this.editor.off('change')
  101 + this.editor.remove()
  102 + },
  103 + setValue(value) {
  104 + this.editor.setValue(value)
  105 + },
  106 + getValue() {
  107 + return this.editor.getValue()
  108 + },
  109 + setHtml(value) {
  110 + this.editor.setHtml(value)
  111 + },
  112 + getHtml() {
  113 + return this.editor.getHtml()
  114 + }
  115 + },
  116 + model: {
  117 + prop: 'value',
  118 + event: 'change'
  119 + }
  120 +}
  121 +</script>
... ...
ant-design-vue-jeecg/src/components/jeecg/JModal/index.vue
1 1 <template>
2 2 <a-modal
3 3 ref="modal"
4   - class="j-modal-box"
5   - :class="{'fullscreen':innerFullscreen,'no-title':isNoTitle,'no-footer':isNoFooter,}"
  4 + :class="getClass(modalClass)"
  5 + :style="getStyle(modalStyle)"
6 6 :visible="visible"
7 7 v-bind="_attrs"
8 8 v-on="$listeners"
... ... @@ -38,6 +38,8 @@
38 38  
39 39 <script>
40 40  
  41 + import { getClass, getStyle } from '@/utils/props-util'
  42 +
41 43 export default {
42 44 name: 'JModal',
43 45 props: {
... ... @@ -47,13 +49,18 @@
47 49 // 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
48 50 fullscreen: {
49 51 type: Boolean,
50   - default: true
  52 + default: false
51 53 },
52 54 // 是否允许切换全屏(允许后右上角会出现一个按钮)
53 55 switchFullscreen: {
54 56 type: Boolean,
55 57 default: false
56 58 },
  59 + // 点击确定按钮的时候是否关闭弹窗
  60 + okClose: {
  61 + type: Boolean,
  62 + default: true
  63 + },
57 64 },
58 65 data() {
59 66 return {
... ... @@ -73,6 +80,22 @@
73 80 }
74 81 return attrs
75 82 },
  83 + modalClass() {
  84 + return {
  85 + 'j-modal-box': true,
  86 + 'fullscreen': this.innerFullscreen,
  87 + 'no-title': this.isNoTitle,
  88 + 'no-footer': this.isNoFooter,
  89 + }
  90 + },
  91 + modalStyle() {
  92 + let style = {}
  93 + // 如果全屏就将top设为 0
  94 + if (this.innerFullscreen) {
  95 + style['top'] = '0'
  96 + }
  97 + return style
  98 + },
76 99 isNoTitle() {
77 100 return !this.title && !this.allSlotsKeys.includes('title')
78 101 },
... ... @@ -90,7 +113,7 @@
90 113 },
91 114 // 切换全屏的按钮图标
92 115 fullscreenButtonIcon() {
93   - return this.innerFullscreen ? 'fullscreen' : 'fullscreen-exit'
  116 + return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
94 117 },
95 118 },
96 119 watch: {
... ... @@ -105,12 +128,21 @@
105 128 },
106 129 methods: {
107 130  
  131 + getClass(clazz) {
  132 + return { ...getClass(this), ...clazz }
  133 + },
  134 + getStyle(style) {
  135 + return { ...getStyle(this), ...style }
  136 + },
  137 +
108 138 close() {
109 139 this.$emit('update:visible', false)
110 140 },
111 141  
112 142 handleOk() {
113   - this.close()
  143 + if (this.okClose) {
  144 + this.close()
  145 + }
114 146 },
115 147 handleCancel() {
116 148 this.close()
... ... @@ -167,6 +199,7 @@
167 199  
168 200 .right {
169 201 width: 56px;
  202 + position: inherit;
170 203  
171 204 .ant-modal-close {
172 205 right: 56px;
... ... @@ -180,5 +213,13 @@
180 213 }
181 214 }
182 215  
  216 +
  217 + }
  218 +
  219 + @media (max-width: 767px) {
  220 + .j-modal-box.fullscreen {
  221 + margin: 0;
  222 + max-width: 100vw;
  223 + }
183 224 }
184 225 </style>
185 226 \ No newline at end of file
... ...
ant-design-vue-jeecg/src/components/jeecg/JSuperQuery.vue
1 1 <template>
2 2 <div class="j-super-query-box">
3 3  
4   - <div @click="visible=true">
5   - <slot>
6   - <a-tooltip v-if="superQueryFlag" :mouseLeaveDelay="0.2">
7   - <template slot="title">
8   - <span>已有高级查询条件生效</span>
9   - <a-divider type="vertical"/>
10   - <a @click="handleReset">清空</a>
11   - </template>
12   - <a-button type="primary">
13   - <a-icon type="appstore" theme="twoTone" :spin="true"></a-icon>
  4 + <slot name="button" :isActive="superQueryFlag" :isMobile="izMobile" :open="handleOpen" :reset="handleReset">
  5 + <a-tooltip v-if="superQueryFlag" v-bind="tooltipProps" :mouseLeaveDelay="0.2">
  6 + <!-- begin 不知道为什么不加上这段代码就无法生效 -->
  7 + <span v-show="false">{{tooltipProps}}</span>
  8 + <!-- end 不知道为什么不加上这段代码就无法生效 -->
  9 + <template slot="title">
  10 + <span>已有高级查询条件生效</span>
  11 + <a-divider type="vertical"/>
  12 + <a @click="handleReset">清空</a>
  13 + </template>
  14 + <a-button-group>
  15 + <a-button type="primary" @click="handleOpen">
  16 + <a-icon type="appstore" theme="twoTone" spin/>
14 17 <span>高级查询</span>
15 18 </a-button>
16   - </a-tooltip>
17   - <a-button v-else type="primary" icon="filter" @click="visible=true">高级查询</a-button>
18   - </slot>
19   - </div>
  19 + <a-button v-if="izMobile" type="primary" icon="delete" @click="handleReset"/>
  20 + </a-button-group>
  21 + </a-tooltip>
  22 + <a-button v-else type="primary" icon="filter" @click="handleOpen">高级查询</a-button>
  23 + </slot>
20 24  
21   - <a-modal
  25 + <j-modal
22 26 title="高级查询构造器"
23 27 :width="1000"
24 28 :visible="visible"
25 29 @cancel="handleCancel"
26 30 :mask="false"
  31 + :fullscreen="izMobile"
27 32 class="j-super-query-modal"
28   - style="top:5%;max-height: 95%;">
  33 + style="top:5%;max-height: 95%;"
  34 + >
29 35  
30 36 <template slot="footer">
31 37 <div style="float: left">
... ... @@ -40,7 +46,7 @@
40 46 <a-row>
41 47 <a-col :sm="24" :md="24-5">
42 48  
43   - <a-empty v-if="queryParamsModel.length === 0">
  49 + <a-empty v-if="queryParamsModel.length === 0" style="margin-bottom: 12px;">
44 50 <div slot="description">
45 51 <span>没有任何查询条件</span>
46 52 <a-divider type="vertical"/>
... ... @@ -50,16 +56,20 @@
50 56  
51 57 <a-form v-else layout="inline">
52 58  
53   - <a-form-item label="过滤条件匹配" style="margin-bottom: 12px;">
54   - <a-select v-model="selectValue" :getPopupContainer="node=>node.parentNode">
55   - <a-select-option value="and">AND(所有条件都要求匹配)</a-select-option>
56   - <a-select-option value="or">OR(条件中的任意一个匹配)</a-select-option>
57   - </a-select>
58   - </a-form-item>
  59 + <a-row style="margin-bottom: 12px;">
  60 + <a-col :md="12" :xs="24">
  61 + <a-form-item label="过滤条件匹配" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
  62 + <a-select v-model="matchType" :getPopupContainer="node=>node.parentNode" style="width: 100%;">
  63 + <a-select-option value="and">AND(所有条件都要求匹配)</a-select-option>
  64 + <a-select-option value="or">OR(条件中的任意一个匹配)</a-select-option>
  65 + </a-select>
  66 + </a-form-item>
  67 + </a-col>
  68 + </a-row>
59 69  
60 70 <a-row type="flex" style="margin-bottom:10px" :gutter="16" v-for="(item, index) in queryParamsModel" :key="index">
61 71  
62   - <a-col :span="8">
  72 + <a-col :md="8" :xs="24" style="margin-bottom: 12px;">
63 73 <a-tree-select
64 74 showSearch
65 75 v-model="item.field"
... ... @@ -75,22 +85,22 @@
75 85 </a-tree-select>
76 86 </a-col>
77 87  
78   - <a-col :span="4">
79   - <a-select placeholder="匹配规则" v-model="item.rule" :getPopupContainer="node=>node.parentNode">
  88 + <a-col :md="4" :xs="24" style="margin-bottom: 12px;">
  89 + <a-select placeholder="匹配规则" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
80 90 <a-select-option value="eq">等于</a-select-option>
  91 + <a-select-option value="like">包含</a-select-option>
  92 + <a-select-option value="right_like">以..开始</a-select-option>
  93 + <a-select-option value="left_like">以..结尾</a-select-option>
  94 + <a-select-option value="in">在...中</a-select-option>
81 95 <a-select-option value="ne">不等于</a-select-option>
82 96 <a-select-option value="gt">大于</a-select-option>
83 97 <a-select-option value="ge">大于等于</a-select-option>
84 98 <a-select-option value="lt">小于</a-select-option>
85 99 <a-select-option value="le">小于等于</a-select-option>
86   - <a-select-option value="right_like">以..开始</a-select-option>
87   - <a-select-option value="left_like">以..结尾</a-select-option>
88   - <a-select-option value="like">包含</a-select-option>
89   - <a-select-option value="in">在...中</a-select-option>
90 100 </a-select>
91 101 </a-col>
92 102  
93   - <a-col :span="8">
  103 + <a-col :md="8" :xs="24" style="margin-bottom: 12px;">
94 104 <template v-if="item.dictCode">
95 105 <template v-if="item.type === 'table-dict'">
96 106 <j-popup
... ... @@ -101,10 +111,14 @@
101 111 :destFields="item.dictCode"
102 112 ></j-popup>
103 113 </template>
104   - <j-dict-select-tag v-else v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
  114 + <template v-else>
  115 + <j-multi-select-tag v-show="allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
  116 + <j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
  117 + </template>
105 118 </template>
  119 + <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)"/>
106 120 <j-select-multi-user
107   - v-else-if="item.type === 'select-user'"
  121 + v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
108 122 v-model="item.val"
109 123 :buttons="false"
110 124 :multiple="false"
... ... @@ -112,20 +126,34 @@
112 126 :returnKeys="['id', item.customReturnField || 'username']"
113 127 />
114 128 <j-select-depart
115   - v-else-if="item.type === 'select-depart'"
  129 + v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
116 130 v-model="item.val"
117 131 :multi="false"
118 132 placeholder="请选择部门"
119 133 :customReturnField="item.customReturnField || 'id'"
120 134 />
121   - <a-select v-else-if="item.options instanceof Array" v-model="item.val" :options="item.options" allowClear placeholder="请选择"/>
  135 + <a-select
  136 + v-else-if="item.options instanceof Array"
  137 + v-model="item.val"
  138 + :options="item.options"
  139 + allowClear
  140 + placeholder="请选择"
  141 + :mode="allowMultiple(item)?'multiple':''"
  142 + />
  143 + <j-area-linkage v-model="item.val" v-else-if="item.type==='area-linkage' || item.type==='pca'" style="width: 100%"/>
122 144 <j-date v-else-if=" item.type=='date' " v-model="item.val" placeholder="请选择日期" style="width: 100%"></j-date>
123 145 <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>
  146 + <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"/>
124 147 <a-input-number v-else-if=" item.type=='int'||item.type=='number' " style="width: 100%" placeholder="请输入数值" v-model="item.val"/>
125 148 <a-input v-else v-model="item.val" placeholder="请输入值"/>
126 149 </a-col>
127 150  
128   - <a-col :span="4">
  151 + <a-col :md="4" :xs="0" style="margin-bottom: 12px;">
  152 + <a-button @click="handleAdd" icon="plus"></a-button>&nbsp;
  153 + <a-button @click="handleDel( index )" icon="minus"></a-button>
  154 + </a-col>
  155 +
  156 + <a-col :md="0" :xs="24" style="margin-bottom: 12px;text-align: right;">
129 157 <a-button @click="handleAdd" icon="plus"></a-button>&nbsp;
130 158 <a-button @click="handleDel( index )" icon="minus"></a-button>
131 159 </a-col>
... ... @@ -142,14 +170,14 @@
142 170 保存的查询
143 171 </div>
144 172  
145   - <a-empty v-if="treeData.length === 0" class="j-super-query-history-empty" description="没有保存任何查询"/>
  173 + <a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" description="没有保存任何查询"/>
146 174 <a-tree
147 175 v-else
148 176 class="j-super-query-history-tree"
149 177 showIcon
150   - :treeData="treeData"
  178 + :treeData="saveTreeData"
  179 + :selectedKeys="[]"
151 180 @select="handleTreeSelect"
152   - @rightClick="handleTreeRightClick"
153 181 >
154 182 </a-tree>
155 183 </a-card>
... ... @@ -165,19 +193,24 @@
165 193 <a-input v-model="prompt.value"></a-input>
166 194 </a-modal>
167 195  
168   - </a-modal>
  196 + </j-modal>
169 197 </div>
170 198 </template>
171 199  
172 200 <script>
  201 + import moment from 'moment'
173 202 import * as utils from '@/utils/util'
  203 + import { mixinDevice } from '@/utils/mixin'
174 204 import JDate from '@/components/jeecg/JDate.vue'
175 205 import JSelectDepart from '@/components/jeecgbiz/JSelectDepart'
176 206 import JSelectMultiUser from '@/components/jeecgbiz/JSelectMultiUser'
  207 + import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
  208 + import JAreaLinkage from '@comp/jeecg/JAreaLinkage'
177 209  
178 210 export default {
179 211 name: 'JSuperQuery',
180   - components: { JDate, JSelectDepart, JSelectMultiUser },
  212 + mixins: [mixinDevice],
  213 + components: { JAreaLinkage, JMultiSelectTag, JDate, JSelectDepart, JSelectMultiUser },
181 214 props: {
182 215 /*
183 216 fieldList: [{
... ... @@ -208,14 +241,16 @@
208 241 },
209 242  
210 243 // 保存查询条件的唯一 code,通过该 code 区分
  244 + // 默认为 null,代表以当前路由全路径为区分Code
211 245 saveCode: {
212 246 type: String,
213   - default: 'testSaveCode'
  247 + default: null
214 248 }
215 249  
216 250 },
217 251 data() {
218 252 return {
  253 + moment,
219 254 fieldTreeData: [],
220 255  
221 256 prompt: {
... ... @@ -224,26 +259,40 @@
224 259 },
225 260  
226 261 visible: false,
227   - queryParamsModel: [{}],
  262 + queryParamsModel: [],
228 263 treeIcon: <a-icon type="file-text"/>,
229   - treeData: [],
  264 + // 保存查询条件的treeData
  265 + saveTreeData: [],
230 266 // 保存查询条件的前缀名
231 267 saveCodeBefore: 'JSuperQuerySaved_',
232   - selectValue: 'and',
233   - superQueryFlag: false
  268 + // 查询类型,过滤条件匹配(and、or)
  269 + matchType: 'and',
  270 + superQueryFlag: false,
234 271 }
235 272 },
  273 + computed: {
  274 + izMobile() {
  275 + return this.device === 'mobile'
  276 + },
  277 + tooltipProps() {
  278 + return this.izMobile ? { visible: false } : {}
  279 + },
  280 + fullSaveCode() {
  281 + let saveCode = this.saveCode
  282 + if (saveCode == null || saveCode === '') {
  283 + saveCode = this.$route.fullPath
  284 + }
  285 + return this.saveCodeBefore + saveCode
  286 + },
  287 + },
236 288 watch: {
237 289 // 当 saveCode 变化时,重新查询已保存的条件
238   - saveCode: {
  290 + fullSaveCode: {
239 291 immediate: true,
240   - handler(val) {
241   - let list = this.$ls.get(this.saveCodeBefore + val)
  292 + handler() {
  293 + let list = this.$ls.get(this.fullSaveCode)
242 294 if (list instanceof Array) {
243   - this.treeData = list.map(item => {
244   - item.icon = this.treeIcon
245   - return item
246   - })
  295 + this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
247 296 }
248 297 }
249 298 },
... ... @@ -280,25 +329,35 @@
280 329  
281 330 methods: {
282 331 show() {
283   - if (!this.queryParamsModel || this.queryParamsModel.length == 0) {
284   - this.queryParamsModel = [{}]
  332 + if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
  333 + this.resetLine()
285 334 }
286 335 this.visible = true
287 336 },
288 337 handleOk() {
289 338 if (!this.isNullArray(this.queryParamsModel)) {
290 339 let event = {
291   - matchType: this.selectValue,
292   - params: this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
  340 + matchType: this.matchType,
  341 + params: this.removeEmptyObject(this.queryParamsModel)
  342 + }
  343 + // 移动端模式下关闭弹窗
  344 + if (this.izMobile) {
  345 + this.visible = false
293 346 }
294   - console.log('---高级查询参数--->', event)
295   - this.emitCallback(event.params, event.matchType)
  347 + this.emitCallback(event)
296 348 } else {
297   - this.emitCallback()
  349 + this.$message.warn("不能查询空条件")
298 350 }
299 351 },
300   - emitCallback(params, matchType) {
301   - this.superQueryFlag = !!params
  352 + emitCallback(event = {}) {
  353 + let { params = [], matchType = this.matchType } = event
  354 + this.superQueryFlag = (params && params.length > 0)
  355 + for (let param of params) {
  356 + if (Array.isArray(param.val)) {
  357 + param.val = param.val.join(',')
  358 + }
  359 + }
  360 + console.debug('---高级查询参数--->', { params, matchType })
302 361 this.$emit(this.callback, params, matchType)
303 362 },
304 363 handleCancel() {
... ... @@ -309,27 +368,40 @@
309 368 this.visible = false
310 369 },
311 370 handleAdd() {
312   - this.queryParamsModel.push({})
  371 + this.addNewLine()
  372 + },
  373 + addNewLine() {
  374 + this.queryParamsModel.push({ rule: 'eq' })
  375 + },
  376 + resetLine() {
  377 + this.superQueryFlag = false
  378 + this.queryParamsModel = []
  379 + this.addNewLine()
313 380 },
314 381 handleDel(index) {
315 382 this.queryParamsModel.splice(index, 1)
316 383 },
317 384 handleSelected(node, item) {
318   - let { type, options, dictCode, dictTable, customReturnField } = node.dataRef
  385 + let { type, options, dictCode, dictTable, customReturnField, popup } = node.dataRef
319 386 item['type'] = type
320 387 item['options'] = options
321 388 item['dictCode'] = dictCode
322 389 item['dictTable'] = dictTable
323 390 item['customReturnField'] = customReturnField
  391 + if (popup) {
  392 + item['popup'] = popup
  393 + }
324 394 this.$set(item, 'val', undefined)
325 395 },
  396 + handleOpen() {
  397 + this.show()
  398 + },
326 399 handleReset() {
327   - this.superQueryFlag = false
328   - this.queryParamsModel = [{}]
  400 + this.resetLine()
329 401 this.emitCallback()
330 402 },
331 403 handleSave() {
332   - let queryParams = this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
  404 + let queryParams = this.removeEmptyObject(this.queryParamsModel)
333 405 if (this.isNullArray(queryParams)) {
334 406 this.$message.warning('空条件不能保存')
335 407 } else {
... ... @@ -338,56 +410,65 @@
338 410 }
339 411 },
340 412 handlePromptOk() {
341   -
342 413 let { value } = this.prompt
343   - // 判断有没有重名
344   -
345   - let filterList = this.treeData.filter(i => i.title === value)
  414 + if(!value){
  415 + this.$message.warning('保存名称不能为空')
  416 + return
  417 + }
  418 + // 取出查询条件
  419 + let records = this.removeEmptyObject(this.queryParamsModel)
  420 + // 判断有没有重名的
  421 + let filterList = this.saveTreeData.filter(i => i.originTitle === value)
346 422 if (filterList.length > 0) {
347 423 this.$confirm({
348 424 content: `${value} 已存在,是否覆盖?`,
349 425 onOk: () => {
350 426 this.prompt.visible = false
351   - filterList[0].records = this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
  427 + filterList[0].records = records
352 428 this.saveToLocalStore()
353 429 this.$message.success('保存成功')
354 430 }
355 431 })
356 432 } else {
  433 + // 没有重名的,直接添加
357 434 this.prompt.visible = false
358   - this.treeData.push({
  435 + // 添加到树列表中
  436 + this.saveTreeData.push(this.renderSaveTreeData({
359 437 title: value,
360   - icon: this.treeIcon,
361   - records: this.removeEmptyObject(utils.cloneObject(this.queryParamsModel))
362   - })
  438 + matchType: this.matchType,
  439 + records: records
  440 + }))
  441 + // 保存到 LocalStore
363 442 this.saveToLocalStore()
364 443 this.$message.success('保存成功')
365 444 }
366   -
367   -
368 445 },
369 446 handleTreeSelect(idx, event) {
370 447 if (event.selectedNodes[0]) {
371   - this.queryParamsModel = utils.cloneObject(event.selectedNodes[0].data.props.records)
  448 + let { matchType, records } = event.selectedNodes[0].data.props
  449 + // 将保存的matchType取出,兼容旧数据,如果没有保存就还是使用原来的
  450 + this.matchType = matchType || this.matchType
  451 + this.queryParamsModel = utils.cloneObject(records)
372 452 }
373 453 },
374   - handleTreeRightClick(args) {
  454 + handleRemoveSaveTreeItem(event, vNode) {
  455 + // 阻止事件冒泡
  456 + event.stopPropagation()
  457 +
375 458 this.$confirm({
376 459 content: '是否删除当前查询?',
377 460 onOk: () => {
378   - let { node: { eventKey } } = args
379   - this.treeData.splice(Number.parseInt(eventKey.substring(2)), 1)
  461 + let { eventKey } = vNode
  462 + this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
380 463 this.saveToLocalStore()
381   - this.$message.success('删除成功')
382 464 },
383 465 })
384 466 },
385 467  
386 468 // 将查询保存到 LocalStore 里
387 469 saveToLocalStore() {
388   - this.$ls.set(this.saveCodeBefore + this.saveCode, this.treeData.map(item => {
389   - return { title: item.title, records: item.records }
390   - }))
  470 + let saveValue = this.saveTreeData.map(({ originTitle, matchType, records }) => ({ title: originTitle, matchType, records }))
  471 + this.$ls.set(this.fullSaveCode, saveValue)
391 472 },
392 473  
393 474 isNullArray(array) {
... ... @@ -404,18 +485,70 @@
404 485 return false
405 486 },
406 487 // 去掉数组中的空对象
407   - removeEmptyObject(array) {
  488 + removeEmptyObject(arr) {
  489 + let array = utils.cloneObject(arr)
408 490 for (let i = 0; i < array.length; i++) {
409 491 let item = array[i]
410 492 if (item == null || Object.keys(item).length <= 0) {
411 493 array.splice(i--, 1)
412 494 } else {
413   - // 去掉特殊属性
414   - delete item.options
  495 + if (Array.isArray(item.options)) {
  496 + // 如果有字典属性,就不需要保存 options 了
  497 + if (item.dictCode) {
  498 + // 去掉特殊属性
  499 + delete item.options
  500 + }
  501 + }
415 502 }
416 503 }
417 504 return array
418   - }
  505 + },
  506 +
  507 + /** 渲染保存查询条件的 title(加个删除按钮) */
  508 + renderSaveTreeData(item) {
  509 + item.icon = this.treeIcon
  510 + item.originTitle = item['title']
  511 + item.title = (fn, vNode) => {
  512 + let { originTitle } = vNode.dataRef
  513 + return (
  514 + <div class="j-history-tree-title">
  515 + <span>{originTitle}</span>
  516 +
  517 + <div class="j-history-tree-title-closer" onClick={e => this.handleRemoveSaveTreeItem(e, vNode)}>
  518 + <a-icon type="close-circle"/>
  519 + </div>
  520 + </div>
  521 + )
  522 + }
  523 + return item
  524 + },
  525 +
  526 + /** 判断是否允许多选 */
  527 + allowMultiple(item) {
  528 + return item.rule === 'in'
  529 + },
  530 +
  531 + handleRuleChange(item, newValue) {
  532 + let oldValue = item.rule
  533 + this.$set(item, 'rule', newValue)
  534 + // 上一个规则是否是 in,且type是字典或下拉
  535 + if (oldValue === 'in') {
  536 + if (item.dictCode || item.options instanceof Array) {
  537 + let value = item.val
  538 + if (typeof item.val === 'string') {
  539 + value = item.val.split(',')[0]
  540 + } else if (Array.isArray(item.val)) {
  541 + value = item.val[0]
  542 + }
  543 + this.$set(item, 'val', value)
  544 + }
  545 + }
  546 + },
  547 +
  548 + handleChangeJPopup(item, e, values) {
  549 + item.val = values[item.popup['destFields']]
  550 + },
  551 +
419 552 }
420 553 }
421 554 </script>
... ... @@ -428,42 +561,76 @@
428 561  
429 562 .j-super-query-modal {
430 563  
431   - .j-super-query-history-card /deep/ {
432   - .ant-card-body,
433   - .ant-card-head-title {
  564 + .j-super-query-history-card {
  565 + /deep/ .ant-card-body,
  566 + /deep/ .ant-card-head-title {
434 567 padding: 0;
435 568 }
436 569  
437   - .ant-card-head {
  570 + /deep/ .ant-card-head {
438 571 padding: 4px 8px;
439 572 min-height: initial;
440 573 }
441 574 }
442 575  
443   - .j-super-query-history-empty /deep/ {
444   - .ant-empty-image {
  576 + .j-super-query-history-empty {
  577 + /deep/ .ant-empty-image {
445 578 height: 80px;
446 579 line-height: 80px;
447 580 margin-bottom: 0;
448 581 }
449 582  
450   - img {
  583 + /deep/ img {
451 584 width: 80px;
452 585 height: 65px;
453 586 }
454 587  
455   - .ant-empty-description {
  588 + /deep/ .ant-empty-description {
456 589 color: #afafaf;
457 590 margin: 8px 0;
458 591 }
459 592 }
460 593  
461   - .j-super-query-history-tree /deep/ {
462   - .ant-tree-switcher {
  594 + .j-super-query-history-tree {
  595 +
  596 + .j-history-tree-title {
  597 + width: calc(100% - 24px);
  598 + position: relative;
  599 + display: inline-block;
  600 +
  601 + &-closer {
  602 + color: #999999;
  603 + position: absolute;
  604 + top: 0;
  605 + right: 0;
  606 + width: 24px;
  607 + height: 24px;
  608 + text-align: center;
  609 + opacity: 0;
  610 + transition: opacity 0.3s, color 0.3s;
  611 +
  612 + &:hover {
  613 + color: #666666;
  614 + }
  615 +
  616 + &:active {
  617 + color: #333333;
  618 + }
  619 + }
  620 +
  621 + &:hover {
  622 + .j-history-tree-title-closer {
  623 + opacity: 1;
  624 + }
  625 + }
  626 +
  627 + }
  628 +
  629 + /deep/ .ant-tree-switcher {
463 630 display: none;
464 631 }
465 632  
466   - .ant-tree-node-content-wrapper {
  633 + /deep/ .ant-tree-node-content-wrapper {
467 634 width: 100%;
468 635 }
469 636 }
... ...
ant-design-vue-jeecg/src/components/jeecg/JSwitch.vue 0 → 100644
  1 +<template>
  2 + <a-switch v-model="checkStatus" :disabled="disabled" @change="handleChange"/>
  3 +</template>
  4 +<script>
  5 +
  6 + export default {
  7 + name: 'JSwitch',
  8 + props: {
  9 + value:{
  10 + type: String,
  11 + required: false
  12 + },
  13 + disabled:{
  14 + type: Boolean,
  15 + required: false,
  16 + default: false
  17 + },
  18 + options:{
  19 + type:Array,
  20 + required:false,
  21 + default:()=>['Y','N']
  22 + }
  23 + },
  24 + data () {
  25 + return {
  26 + checkStatus: false
  27 + }
  28 + },
  29 + watch: {
  30 + value:{
  31 + immediate: true,
  32 + handler(val){
  33 + if(!val){
  34 + this.checkStatus = false
  35 + this.$emit('change', this.options[1]);
  36 + }else{
  37 + if(this.options[0]==val){
  38 + this.checkStatus = true
  39 + }else{
  40 + this.checkStatus = false
  41 + }
  42 + }
  43 + }
  44 + }
  45 + },
  46 + methods: {
  47 + handleChange(checked){
  48 + let flag = checked===false?this.options[1]:this.options[0];
  49 + this.$emit('change', flag);
  50 + }
  51 + },
  52 + model: {
  53 + prop: 'value',
  54 + event: 'change'
  55 + }
  56 + }
  57 +</script>
... ...
ant-design-vue-jeecg/src/components/jeecg/JTreeDict.vue
... ... @@ -180,7 +180,11 @@
180 180 },
181 181 onChange(value){
182 182 console.log(value)
183   - this.$emit('change', value.value);
  183 + if(!value){
  184 + this.$emit('change', '');
  185 + }else{
  186 + this.$emit('change', value.value);
  187 + }
184 188 this.treeValue = value
185 189 },
186 190 onSearch(value){
... ...
ant-design-vue-jeecg/src/components/jeecg/JTreeSelect.vue
... ... @@ -2,6 +2,7 @@
2 2 <a-tree-select
3 3 allowClear
4 4 labelInValue
  5 + :getPopupContainer="(node) => node.parentNode"
5 6 style="width: 100%"
6 7 :disabled="disabled"
7 8 :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
... ...
ant-design-vue-jeecg/src/components/jeecg/JUpload.vue
1 1 <template>
2   - <a-upload
3   - name="file"
4   - :multiple="true"
5   - :action="uploadAction"
6   - :headers="headers"
7   - :data="{'biz':bizPath}"
8   - :fileList="fileList"
9   - :beforeUpload="beforeUpload"
10   - @change="handleChange"
11   - :disabled="disabled"
12   - :returnUrl="returnUrl">
13   - <a-button>
14   - <a-icon type="upload" />{{ text }}
15   - </a-button>
16   - </a-upload>
  2 + <div :id="containerId" style="position: relative">
  3 +
  4 + <!-- ---------------------------- begin 图片左右换位置 ------------------------------------- -->
  5 + <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;">
  6 + <div :id="containerId+'-mover'" :class="showMoverTask?'uploadty-mover-mask':'movety-opt'" style="margin-top: 12px">
  7 + <a @click="moveLast" style="margin: 0 5px;"><a-icon type="arrow-left" style="color: #fff;font-size: 16px"/></a>
  8 + <a @click="moveNext" style="margin: 0 5px;"><a-icon type="arrow-right" style="color: #fff;font-size: 16px"/></a>
  9 + </div>
  10 + </div>
  11 + <!-- ---------------------------- end 图片左右换位置 ------------------------------------- -->
  12 +
  13 + <a-upload
  14 + name="file"
  15 + :multiple="true"
  16 + :action="uploadAction"
  17 + :headers="headers"
  18 + :data="{'biz':bizPath}"
  19 + :fileList="fileList"
  20 + :beforeUpload="beforeUpload"
  21 + @change="handleChange"
  22 + :disabled="disabled"
  23 + :returnUrl="returnUrl"
  24 + :listType="complistType"
  25 + @preview="handlePreview"
  26 + :class="{'uploadty-disabled':disabled}">
  27 + <template>
  28 + <div v-if="isImageComp">
  29 + <a-icon type="plus" />
  30 + <div class="ant-upload-text">{{ text }}</div>
  31 + </div>
  32 + <a-button v-else-if="buttonVisible">
  33 + <a-icon type="upload" />{{ text }}
  34 + </a-button>
  35 + </template>
  36 + </a-upload>
  37 + <a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
  38 + <img alt="example" style="width: 100%" :src="previewImage" />
  39 + </a-modal>
  40 + </div>
17 41 </template>
18 42  
19 43 <script>
... ... @@ -40,10 +64,21 @@
40 64 data(){
41 65 return {
42 66 uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
43   - urlDownload:window._CONFIG['staticDomainURL'],
44 67 headers:{},
45 68 fileList: [],
46 69 newFileList: [],
  70 + uploadGoOn:true,
  71 + previewVisible: false,
  72 + //---------------------------- begin 图片左右换位置 -------------------------------------
  73 + previewImage: '',
  74 + containerId:'',
  75 + top:'',
  76 + left:'',
  77 + moveDisplay:'none',
  78 + showMoverTask:false,
  79 + moverHold:false,
  80 + currentImg:''
  81 + //---------------------------- end 图片左右换位置 -------------------------------------
47 82 }
48 83 },
49 84 props:{
... ... @@ -90,23 +125,48 @@
90 125 required:false,
91 126 default: true
92 127 },
  128 + number:{
  129 + type:Number,
  130 + required:false,
  131 + default: 0
  132 + },
  133 + buttonVisible:{
  134 + type:Boolean,
  135 + required:false,
  136 + default: true
  137 + },
93 138 },
94 139 watch:{
95   - value(val){
96   - if (val instanceof Array) {
97   - if(this.returnUrl){
98   - this.initFileList(val.join(','))
99   - }else{
100   - this.initFileListArr(val);
  140 + value:{
  141 + immediate: true,
  142 + handler() {
  143 + let val = this.value
  144 + if (val instanceof Array) {
  145 + if(this.returnUrl){
  146 + this.initFileList(val.join(','))
  147 + }else{
  148 + this.initFileListArr(val);
  149 + }
  150 + } else {
  151 + this.initFileList(val)
101 152 }
102   - } else {
103   - this.initFileList(val)
104 153 }
105 154 }
106 155 },
  156 + computed:{
  157 + isImageComp(){
  158 + return this.fileType === FILE_TYPE_IMG
  159 + },
  160 + complistType(){
  161 + return this.fileType === FILE_TYPE_IMG?'picture-card':'text'
  162 + }
  163 + },
107 164 created(){
108 165 const token = Vue.ls.get(ACCESS_TOKEN);
109   - this.headers = {"X-Access-Token":token}
  166 + //---------------------------- begin 图片左右换位置 -------------------------------------
  167 + this.headers = {"X-Access-Token":token};
  168 + this.containerId = 'container-ty-'+new Date().getTime();
  169 + //---------------------------- end 图片左右换位置 -------------------------------------
110 170 },
111 171  
112 172 methods:{
... ... @@ -117,11 +177,12 @@
117 177 }
118 178 let fileList = [];
119 179 for(var a=0;a<val.length;a++){
  180 + let url = getFileAccessHttpUrl(val[a].filePath);
120 181 fileList.push({
121 182 uid:uidGenerator(),
122 183 name:val[a].fileName,
123 184 status: 'done',
124   - url: val[a].filePath,
  185 + url: url,
125 186 response:{
126 187 status:"history",
127 188 message:val[a].filePath
... ... @@ -141,7 +202,7 @@
141 202 let fileList = [];
142 203 let arr = paths.split(",")
143 204 for(var a=0;a<arr.length;a++){
144   - let url = getFileAccessHttpUrl(arr[a],this.urlDownload,"http");
  205 + let url = getFileAccessHttpUrl(arr[a]);
145 206 fileList.push({
146 207 uid:uidGenerator(),
147 208 name:getFileName(arr[a]),
... ... @@ -172,15 +233,12 @@
172 233 this.$emit('change', path);
173 234 },
174 235 beforeUpload(file){
  236 + this.uploadGoOn=true
175 237 var fileType = file.type;
176   - if(fileType===FILE_TYPE_IMG){
  238 + if(this.fileType===FILE_TYPE_IMG){
177 239 if(fileType.indexOf('image')<0){
178 240 this.$message.warning('请上传图片');
179   - return false;
180   - }
181   - }else if(fileType===FILE_TYPE_TXT){
182   - if(fileType.indexOf('image')>=0){
183   - this.$message.warning('请上传文件');
  241 + this.uploadGoOn=false
184 242 return false;
185 243 }
186 244 }
... ... @@ -189,13 +247,19 @@
189 247 },
190 248 handleChange(info) {
191 249 console.log("--文件列表改变--")
  250 + if(!info.file.status && this.uploadGoOn === false){
  251 + info.fileList.pop();
  252 + }
192 253 let fileList = info.fileList
193 254 if(info.file.status==='done'){
  255 + if(this.number>0){
  256 + fileList = fileList.slice(-this.number);
  257 + }
194 258 if(info.file.response.success){
195 259 fileList = fileList.map((file) => {
196 260 if (file.response) {
197 261 let reUrl = file.response.message;
198   - file.url = getFileAccessHttpUrl(reUrl,this.urlDownload,"http");
  262 + file.url = getFileAccessHttpUrl(reUrl);
199 263 }
200 264 return file;
201 265 });
... ... @@ -213,20 +277,16 @@
213 277 this.handlePathChange()
214 278 }else{
215 279 //returnUrl为false时返回文件名称、文件路径及文件大小
216   - fileList = fileList.filter((file) => {
217   - if (file.response) {
218   - return file.response.success === true;
219   - }
220   - return false;
221   - }).map((file) => {
  280 + this.newFileList = [];
  281 + for(var a=0;a<fileList.length;a++){
222 282 var fileJson = {
223   - fileName:file.name,
224   - filePath:file.response.message,
225   - fileSize:file.size
  283 + fileName:fileList[a].name,
  284 + filePath:fileList[a].response.message,
  285 + fileSize:fileList[a].size
226 286 };
227 287 this.newFileList.push(fileJson);
228   - this.$emit('change', this.newFileList);
229   - });
  288 + }
  289 + this.$emit('change', this.newFileList);
230 290 }
231 291 }
232 292 },
... ... @@ -234,6 +294,115 @@
234 294 //如有需要新增 删除逻辑
235 295 console.log(file)
236 296 },
  297 + handlePreview(file){
  298 + if(this.fileType === FILE_TYPE_IMG){
  299 + this.previewImage = file.url || file.thumbUrl;
  300 + this.previewVisible = true;
  301 + }else{
  302 + location.href=file.url
  303 + }
  304 + },
  305 + handleCancel(){
  306 + this.previewVisible = false;
  307 + },
  308 + //---------------------------- begin 图片左右换位置 -------------------------------------
  309 + moveLast(){
  310 + //console.log(ev)
  311 + //console.log(this.fileList)
  312 + //console.log(this.currentImg)
  313 + let index = this.getIndexByUrl();
  314 + if(index==0){
  315 + this.$message.warn('未知的操作')
  316 + }else{
  317 + let curr = this.fileList[index].url;
  318 + let last = this.fileList[index-1].url;
  319 + let arr =[]
  320 + for(let i=0;i<this.fileList.length;i++){
  321 + if(i==index-1){
  322 + arr.push(curr)
  323 + }else if(i==index){
  324 + arr.push(last)
  325 + }else{
  326 + arr.push(this.fileList[i].url)
  327 + }
  328 + }
  329 + this.currentImg = last
  330 + this.$emit('change',arr.join(','))
  331 + }
  332 + },
  333 + moveNext(){
  334 + let index = this.getIndexByUrl();
  335 + if(index==this.fileList.length-1){
  336 + this.$message.warn('已到最后~')
  337 + }else{
  338 + let curr = this.fileList[index].url;
  339 + let next = this.fileList[index+1].url;
  340 + let arr =[]
  341 + for(let i=0;i<this.fileList.length;i++){
  342 + if(i==index+1){
  343 + arr.push(curr)
  344 + }else if(i==index){
  345 + arr.push(next)
  346 + }else{
  347 + arr.push(this.fileList[i].url)
  348 + }
  349 + }
  350 + this.currentImg = next
  351 + this.$emit('change',arr.join(','))
  352 + }
  353 + },
  354 + getIndexByUrl(){
  355 + for(let i=0;i<this.fileList.length;i++){
  356 + if(this.fileList[i].url === this.currentImg || encodeURI(this.fileList[i].url) === this.currentImg){
  357 + return i;
  358 + }
  359 + }
  360 + return -1;
  361 + }
  362 + },
  363 + mounted(){
  364 + const moverObj = document.getElementById(this.containerId+'-mover');
  365 + moverObj.addEventListener('mouseover',()=>{
  366 + this.moverHold = true
  367 + this.moveDisplay = 'block';
  368 + });
  369 + moverObj.addEventListener('mouseout',()=>{
  370 + this.moverHold = false
  371 + this.moveDisplay = 'none';
  372 + });
  373 + let picList = document.getElementById(this.containerId)?document.getElementById(this.containerId).getElementsByClassName('ant-upload-list-picture-card'):[];
  374 + if(picList && picList.length>0){
  375 + picList[0].addEventListener('mouseover',(ev)=>{
  376 + ev = ev || window.event;
  377 + let target = ev.target || ev.srcElement;
  378 + if('ant-upload-list-item-info' == target.className){
  379 + this.showMoverTask=false
  380 + let item = target.parentElement
  381 + this.left = item.offsetLeft
  382 + this.top=item.offsetTop+item.offsetHeight-50;
  383 + this.moveDisplay = 'block';
  384 + this.currentImg = target.getElementsByTagName('img')[0].src
  385 + }
  386 +
  387 + });
  388 +
  389 + picList[0].addEventListener('mouseout',(ev)=>{
  390 + ev = ev || window.event;
  391 + let target = ev.target || ev.srcElement;
  392 + //console.log('移除',target)
  393 + if('ant-upload-list-item-info' == target.className){
  394 + this.showMoverTask=true
  395 + setTimeout(()=>{
  396 + if(this.moverHold === false)
  397 + this.moveDisplay = 'none';
  398 + },100)
  399 + }
  400 + if('ant-upload-list-item ant-upload-list-item-done' == target.className || 'ant-upload-list ant-upload-list-picture-card'== target.className){
  401 + this.moveDisplay = 'none';
  402 + }
  403 + })
  404 + //---------------------------- end 图片左右换位置 -------------------------------------
  405 + }
237 406 },
238 407 model: {
239 408 prop: 'value',
... ... @@ -242,6 +411,24 @@
242 411 }
243 412 </script>
244 413  
245   -<style scoped>
246   -
  414 +<style lang="less">
  415 +.uploadty-disabled{
  416 + .ant-upload-list-item {
  417 + .anticon-close{
  418 + display: none;
  419 + }
  420 + .anticon-delete{
  421 + display: none;
  422 + }
  423 + }
  424 +}
  425 + //---------------------------- begin 图片左右换位置 -------------------------------------
  426 + .uploadty-mover-mask{
  427 + background-color: rgba(0, 0, 0, 0.5);
  428 + opacity: .8;
  429 + color: #fff;
  430 + height: 28px;
  431 + line-height: 28px;
  432 + }
  433 + //---------------------------- end 图片左右换位置 -------------------------------------
247 434 </style>
248 435 \ No newline at end of file
... ...