소스 검색

Jeecg-Boot 2.1.0 版本发布,Online表单开发&在线代码生成器(迟到的版本)

zhangdaihao 5 년 전
부모
커밋
5069209093
100개의 변경된 파일9338개의 추가작업 그리고 16218개의 파일을 삭제
  1. 2 2
      README.md
  2. 1 1
      ant-design-vue-jeecg/README.md
  3. 7 7
      ant-design-vue-jeecg/package.json
  4. 4 12
      ant-design-vue-jeecg/public/index.html
  5. 5 5
      ant-design-vue-jeecg/src/api/api.js
  6. BIN
      ant-design-vue-jeecg/src/assets/logo-white.png
  7. 73 0
      ant-design-vue-jeecg/src/cas/sso.js
  8. 2 0
      ant-design-vue-jeecg/src/components/README.md
  9. 3 3
      ant-design-vue-jeecg/src/components/chart/DashChartDemo.vue
  10. 68 0
      ant-design-vue-jeecg/src/components/jeecg/JCron.vue
  11. 360 228
      ant-design-vue-jeecg/src/components/jeecg/JEditableTable.vue
  12. 1 1
      ant-design-vue-jeecg/src/components/jeecg/JEditor.vue
  13. 32 27
      ant-design-vue-jeecg/src/components/jeecg/README_JEditableTable.md
  14. 928 0
      ant-design-vue-jeecg/src/components/jeecg/modal/JCronModal.vue
  15. 10 0
      ant-design-vue-jeecg/src/components/jeecgbiz/modal/JSelectDepartModal.vue
  16. 18 1
      ant-design-vue-jeecg/src/components/menu/SideMenu.vue
  17. 4 3
      ant-design-vue-jeecg/src/components/setting/SettingDrawer.vue
  18. 10 2
      ant-design-vue-jeecg/src/components/tools/DepartSelect.vue
  19. 93 6
      ant-design-vue-jeecg/src/components/tools/HeaderNotice.vue
  20. 9 1
      ant-design-vue-jeecg/src/components/tools/Logo.vue
  21. 73 29
      ant-design-vue-jeecg/src/components/tools/ShowAnnouncement.vue
  22. 13 3
      ant-design-vue-jeecg/src/components/tools/UserMenu.vue
  23. 0 24
      ant-design-vue-jeecg/src/config/router.config.js
  24. 3 0
      ant-design-vue-jeecg/src/main.js
  25. 28 0
      ant-design-vue-jeecg/src/store/modules/user.js
  26. 5 3
      ant-design-vue-jeecg/src/utils/request.js
  27. 19 0
      ant-design-vue-jeecg/src/utils/util.js
  28. 4 4
      ant-design-vue-jeecg/src/views/dashboard/Analysis.vue
  29. 19 289
      ant-design-vue-jeecg/src/views/jeecg/JeecgEditableTableExample.vue
  30. 11 18
      ant-design-vue-jeecg/src/views/jeecg/SelectDemo.vue
  31. 91 0
      ant-design-vue-jeecg/src/views/jeecg/TableTotal.vue
  32. 250 0
      ant-design-vue-jeecg/src/views/jeecg/modules/JEditableTable/DefaultTable.vue
  33. 70 0
      ant-design-vue-jeecg/src/views/jeecg/modules/JEditableTable/ReadOnlyTable.vue
  34. 129 0
      ant-design-vue-jeecg/src/views/jeecg/modules/JEditableTable/ThreeLinkage.vue
  35. 1 1
      ant-design-vue-jeecg/src/views/jeecg/modules/VueCronModal.vue
  36. 435 0
      ant-design-vue-jeecg/src/views/modules/online/cgform/OnlCgformHeadList.vue
  37. 619 0
      ant-design-vue-jeecg/src/views/modules/online/cgform/auto/OnlCgformAutoList.vue
  38. 638 0
      ant-design-vue-jeecg/src/views/modules/online/cgform/auto/OnlCgformTreeList.vue
  39. 268 0
      ant-design-vue-jeecg/src/views/modules/online/cgform/util/TableUtils.js
  40. 6 6
      ant-design-vue-jeecg/src/views/system/DepartList.vue
  41. 3 3
      ant-design-vue-jeecg/src/views/system/DepartList2.vue
  42. 39 8
      ant-design-vue-jeecg/src/views/system/LogList.vue
  43. 1 1
      ant-design-vue-jeecg/src/views/system/PermissionList.vue
  44. 3 3
      ant-design-vue-jeecg/src/views/system/SysAnnouncementList.vue
  45. 3 3
      ant-design-vue-jeecg/src/views/system/UserAnnouncementList.vue
  46. 22 6
      ant-design-vue-jeecg/src/views/system/UserList.vue
  47. 1 1
      ant-design-vue-jeecg/src/views/system/modules/DepartModal.vue
  48. 1 1
      ant-design-vue-jeecg/src/views/system/modules/PermissionModal.vue
  49. 49 14
      ant-design-vue-jeecg/src/views/system/modules/QuartzJobModal.vue
  50. 5 1
      ant-design-vue-jeecg/src/views/system/modules/SelectUserListModal.vue
  51. 3 3
      ant-design-vue-jeecg/src/views/system/modules/SysUserAgentModal.vue
  52. 2 1
      ant-design-vue-jeecg/src/views/system/modules/UserModal.vue
  53. 1 1
      ant-design-vue-jeecg/src/views/user/Alteration.vue
  54. 11 3
      ant-design-vue-jeecg/src/views/user/Login.vue
  55. 11 16
      ant-design-vue-jeecg/src/views/user/Step1.vue
  56. 5 5
      ant-design-vue-jeecg/src/views/user/Step2.vue
  57. 6 2
      ant-design-vue-jeecg/vue.config.js
  58. 37 0
      ant-design-vue-jeecg/yarn.lock
  59. 1 1
      jeecg-boot/README.md
  60. 0 2273
      jeecg-boot/db/jeecg-boot-mysql-20190705.sql
  61. 3129 0
      jeecg-boot/db/jeecg-boot-mysql-20190823.sql
  62. 0 3112
      jeecg-boot/db/jeecg-boot-oracle_11g.sql
  63. 0 9967
      jeecg-boot/db/jeecg-boot-sqlserver_2008.sql
  64. 2 0
      jeecg-boot/db/schema_mysql.sql
  65. 0 45
      jeecg-boot/db/增量升级SQL——mysql/jeecgboot2.0.1到2.0.2增量升级SQL
  66. 717 0
      jeecg-boot/db/增量升级SQL——mysql/jeecgboot2.0.2到2.1增量升级.sql
  67. 2 2
      jeecg-boot/jeecg-boot-base-common/pom.xml
  68. 7 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/aspect/annotation/AutoLog.java
  69. 10 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/constant/CacheConstant.java
  70. 31 1
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/constant/CommonConstant.java
  71. 5 2
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/constant/DataBaseConstant.java
  72. 10 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
  73. 1 1
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/query/QueryGenerator.java
  74. 11 6
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/util/JwtUtil.java
  75. 3 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/vo/DictModel.java
  76. 5 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/vo/LoginUser.java
  77. 66 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/DySmsEnum.java
  78. 40 29
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/DySmsHelper.java
  79. 3 2
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/PasswordUtil.java
  80. 24 1
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java
  81. 77 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/SecurityTools.java
  82. 9 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/MyKeyPair.java
  83. 11 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecurityReq.java
  84. 10 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecurityResp.java
  85. 9 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecuritySignReq.java
  86. 10 0
      jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecuritySignResp.java
  87. 4 0
      jeecg-boot/jeecg-boot-module-system/.gitattributes
  88. 20 2
      jeecg-boot/jeecg-boot-module-system/pom.xml
  89. 19 22
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/JeecgApplication.java
  90. 10 3
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/RedisConfig.java
  91. 23 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/ShiroConfig.java
  92. 25 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/StaticConfig.java
  93. 18 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/WebSocketConfig.java
  94. 2 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/mybatis/MybatisInterceptor.java
  95. 110 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/cas/controller/CasClientController.java
  96. 103 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/cas/util/CASServiceUtil.java
  97. 292 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/cas/util/XmlUtils.java
  98. 1 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/demo/mock/json/area_mini.json
  99. 3 1
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/demo/test/controller/JeecgDemoController.java
  100. 0 0
      jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/message/entity/SysMessage.java

+ 2 - 2
README.md

@@ -6,7 +6,7 @@
 Jeecg-Boot 快速开发平台(前后端分离版本)
 ===============
 
-当前最新版本: 2.0.2(发布日期:20190708
+当前最新版本: 2.1.0(发布日期:20190826
 
 [![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
 [![](https://img.shields.io/badge/Author-JEECG团队-orange.svg)](http://www.jeecg.com)
@@ -239,7 +239,7 @@ Jeecg-Boot快速开发平台,可以应用在任何J2EE项目的开发中,尤
 │  └─异常页面
 │  └─个人页面
 ├─Online在线开发(暂未开源)
-│  ├─Online在线表单
+│  ├─Online在线表单 - 已开放功能
 │  ├─Online在线图表
 │  ├─Online图表模板配置
 │  ├─Online在线报表

+ 1 - 1
ant-design-vue-jeecg/README.md

@@ -1,7 +1,7 @@
 Ant Design Jeecg Vue
 ====
 
-当前最新版本: 2.0.2(发布日期:20190708
+当前最新版本: 2.1.0(发布日期:20190826
 
 Overview
 ----

+ 7 - 7
ant-design-vue-jeecg/package.json

@@ -1,16 +1,16 @@
 {
-  "name": "ant-design-vue-jeecg",
-   "version": "2.0.2",
-  "private": true,
+  "name": "vue-antd-jeecg",
+  "version": "2.1.0",
+  "private": false,
   "scripts": {
-    "serve": "vue-cli-service serve --open",
+    "pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
+    "serve": "vue-cli-service serve",
     "build": "vue-cli-service build",
-    "lint": "vue-cli-service lint",
-    "test:unit": "vue-cli-service test:unit",
-    "test:e2e": "vue-cli-service test:e2e"
+    "lint": "vue-cli-service lint"
   },
   "dependencies": {
     "@antv/data-set": "^0.10.2",
+    "@jeecg/antd-onine": "^1.0.1",
     "@tinymce/tinymce-vue": "^2.0.0",
     "ant-design-vue": "^1.3.9",
     "apexcharts": "^3.6.5",

+ 4 - 12
ant-design-vue-jeecg/public/index.html

@@ -242,9 +242,10 @@
   <!-- 全局配置 -->
   <script>
     window._CONFIG = {};
-    window._CONFIG['domianURL'] = 'http://localhost:8080/jeecg-boot';
-    window._CONFIG['imgDomainURL'] = 'http://localhost:8080/jeecg-boot/sys/common/view';
-    window._CONFIG['pdfDomainURL'] = 'http://localhost:8080/jeecg-boot/sys/common/pdf/pdfPreviewIframe';
+    window._CONFIG['domianURL'] = 'http://127.0.0.1:8080/jeecg-boot';
+    window._CONFIG['casPrefixUrl'] = 'http://cas.example.org:8443/cas';
+    window._CONFIG['imgDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/view';
+    window._CONFIG['pdfDomainURL'] =  window._CONFIG['domianURL'] + '/sys/common/pdf/pdfPreviewIframe';
   </script>
 </head>
 
@@ -261,15 +262,6 @@
   </div>
 </div>
 
-<!-- update_begin author:sunjianlei date:20190524 for: 去指定页面的加载动画 -->
-<script>
-  // 去指定页面的加载动画
-  if (location.href.indexOf('online/desform/pureview') !== -1) {
-    document.getElementById('loader-wrapper').style.display = 'none'
-  }
-</script>
-<!-- update_end author:sunjianlei date:20190524 for: 去指定页面的加载动画 -->
-
 </body>
 
 </html>

+ 5 - 5
ant-design-vue-jeecg/src/api/api.js

@@ -45,11 +45,11 @@ const getPermissionRuleList = (params)=>getAction("/sys/permission/getPermRuleLi
 const queryPermissionRule = (params)=>getAction("/sys/permission/queryPermissionRule",params);
 
 // 部门管理
-const queryDepartTreeList = (params)=>getAction("/sysdepart/sysDepart/queryTreeList",params);
-const queryIdTree = (params)=>getAction("/sysdepart/sysDepart/queryIdTree",params);
-const queryParentName   = (params)=>getAction("/sysdepart/sysDepart/queryParentName",params);
-const searchByKeywords   = (params)=>getAction("/sysdepart/sysDepart/searchBy",params);
-const deleteByDepartId   = (params)=>deleteAction("/sysdepart/sysDepart/delete",params);
+const queryDepartTreeList = (params)=>getAction("/sys/sysDepart/queryTreeList",params);
+const queryIdTree = (params)=>getAction("/sys/sysDepart/queryIdTree",params);
+const queryParentName   = (params)=>getAction("/sys/sysDepart/queryParentName",params);
+const searchByKeywords   = (params)=>getAction("/sys/sysDepart/searchBy",params);
+const deleteByDepartId   = (params)=>deleteAction("/sys/sysDepart/delete",params);
 
 //日志管理
 //const getLogList = (params)=>getAction("/sys/log/list",params);

BIN
ant-design-vue-jeecg/src/assets/logo-white.png


+ 73 - 0
ant-design-vue-jeecg/src/cas/sso.js

@@ -0,0 +1,73 @@
+import Vue from 'vue'
+import { ACCESS_TOKEN } from "@/store/mutation-types"
+import store from '@/store'
+/**
+ * 单点登录
+ */
+const init = (callback) => {
+  console.log("-------单点登录开始-------");
+  let token = Vue.ls.get(ACCESS_TOKEN);
+  let st = getUrlParam("ticket");
+  var sevice = "http://"+window.location.host+"/";
+  if(token){
+    loginSuccess(callback);
+  }else{
+    if(st){
+      validateSt(st,sevice,callback);
+    }else{
+      var serviceUrl = encodeURIComponent(sevice);
+      window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
+    }
+  }
+  console.log("-------单点登录结束-------");
+};
+const SSO = {
+  init: init
+};
+
+function getUrlParam(paraName) {
+  var url = document.location.toString();
+  var arrObj = url.split("?");
+
+  if (arrObj.length > 1) {
+    var arrPara = arrObj[1].split("&");
+    var arr;
+
+    for (var i = 0; i < arrPara.length; i++) {
+      arr = arrPara[i].split("=");
+
+      if (arr != null && arr[0] == paraName) {
+        return arr[1];
+      }
+    }
+    return "";
+  }
+  else {
+    return "";
+  }
+}
+
+function validateSt(ticket,service,callback){
+  let params = {
+    ticket: ticket,
+    service:service
+  };
+  store.dispatch('ValidateLogin',params).then(res => {
+    //this.departConfirm(res)
+    if(res.success){
+      loginSuccess(callback);
+    }else{
+      var sevice = "http://"+window.location.host+"/";
+      var serviceUrl = encodeURIComponent(sevice);
+      window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
+    }
+  }).catch((err) => {
+    console.log(err);
+    //that.requestFailed(err);
+  });
+}
+
+function loginSuccess (callback) {
+  callback();
+}
+export default SSO;

+ 2 - 0
ant-design-vue-jeecg/src/components/README.md

@@ -39,3 +39,5 @@ UserMenu.vue:首页右上侧的内容
 ![输入图片说明](https://static.oschina.net/uploads/img/201904/12201226_laQK.png "在这里输入图片标题")
 ####16.trend包 趋势显示组件(如下图)
 ![输入图片说明](https://static.oschina.net/uploads/img/201904/12201600_Wo8K.png "在这里输入图片标题")
+![corn表达式](https://oscimg.oschina.net/oscnet/661f9ac09016395f9f49286143af3241623.jpg)
+![corn控件添加清除按钮](https://oscimg.oschina.net/oscnet/15096e49f2e29bd829e304d56770025d03c.jpg)

+ 3 - 3
ant-design-vue-jeecg/src/components/chart/DashChartDemo.vue

@@ -32,7 +32,7 @@
         type="arc"
         :zIndex="1"
         :start="arcGuide2Start"
-        :end="getArcGuide2End"
+        :end="getArcGuide2End()"
         :vStyle="arcGuide2Style"
       ></v-guide>
       <v-guide
@@ -88,7 +88,7 @@
   }];
 
   const data = [
-    { value: 7.0 },
+    { value: 0},
   ];
 
   export default {
@@ -96,7 +96,7 @@
     props:{
       datasource:{
         type: Number,
-        default:7
+        default:0
       },
       title: {
         type: String,

+ 68 - 0
ant-design-vue-jeecg/src/components/jeecg/JCron.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="components-input-demo-presuffix">
+    <a-input @click="openModal" placeholder="corn表达式" v-model="cron" @change="handleOK">
+      <a-icon slot="prefix" type="schedule" title="corn控件"/>
+      <a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
+    </a-input>
+    <JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
+  </div>
+</template>
+<script>
+  import JCronModal from "./modal/JCronModal";
+  export default {
+    name: 'JCron',
+    components: {
+      JCronModal
+    },
+    props: {
+      value: {
+        required: false,
+        type: String,
+        default:()=>{
+          return '* * * * * ? *'
+        }
+      }
+    },
+    data(){
+      return {
+        cron: this.value,
+      }
+    },
+    watch:{
+      value(val){
+        this.cron = val
+      }
+    },
+    methods:{
+      openModal(){
+        this.$refs.innerVueCron.show();
+      },
+      handleOK(val){
+        this.cron = val;
+        this.$emit("change", this.cron);
+        //this.$emit("change", Object.assign({},  this.cron));
+      },
+      handleEmpty(){
+        this.handleOK('')
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>
+<style scoped>
+  .components-input-demo-presuffix .anticon-close-circle {
+    cursor: pointer;
+    color: #ccc;
+    transition: color 0.3s;
+    font-size: 12px;
+  }
+  .components-input-demo-presuffix .anticon-close-circle:hover {
+    color: #f5222d;
+  }
+  .components-input-demo-presuffix .anticon-close-circle:active {
+    color: #666;
+  }
+</style>

+ 360 - 228
ant-design-vue-jeecg/src/components/jeecg/JEditableTable.vue

@@ -1,5 +1,5 @@
 <!-- JEditableTable -->
-<!-- @version 1.4.2 -->
+<!-- @version 1.4.4 -->
 <!-- @author sjlei -->
 <template>
   <a-spin :spinning="loading">
@@ -26,6 +26,9 @@
       <div class="thead" ref="thead">
         <div class="tr" :style="{width: this.realTrWidth}">
           <!-- 左侧固定td  -->
+          <div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
+            <span></span>
+          </div>
           <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
             <!--:indeterminate="true"-->
             <a-checkbox
@@ -62,85 +65,95 @@
           <div v-if="rows.length===0" class="tr-nodata">
             <span>暂无数据</span>
           </div>
-          <!-- 动态生成tr -->
-          <template v-for="(row,rowIndex) in rows">
-            <!-- tr 只加载可见的和预加载的总共十条数据 -->
-            <div
-              v-if="
+          <draggable v-model="rows" handle=".td-ds-icons" @end="handleDragMoveEnd">
+
+            <!-- 动态生成tr -->
+            <template v-for="(row,rowIndex) in rows">
+              <!-- tr 只加载可见的和预加载的总共十条数据 -->
+              <div
+                v-if="
                 rowIndex >= parseInt(`${(scrollTop-rowHeight) / rowHeight}`) &&
                   (parseInt(`${scrollTop / rowHeight}`) + 9) > rowIndex
               "
-              :id="`${caseId}tbody-tr-${rowIndex}`"
-              :data-idx="rowIndex"
-              class="tr"
-              :class="selectedRowIds.indexOf(row.id) !== -1 ? 'tr-checked' : ''"
-              :style="buildTrStyle(rowIndex)"
-              :key="row.id">
-              <!-- 左侧固定td  -->
-              <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
-                <!-- 此 v-for 只是为了拼接 id 字符串 -->
-                <template v-for="(id,i) in [`${row.id}`]">
-                  <a-checkbox
-                    :id="id"
-                    :key="i"
-                    :checked="selectedRowIds.indexOf(id) !== -1"
-                    @change="handleChangeLeftCheckbox"/>
-                </template>
-              </div>
-              <div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
-                <span>{{ rowIndex+1 }}</span>
-              </div>
-              <!-- 右侧动态生成td -->
-              <div
-                class="td"
-                v-for="col in columns"
-                v-show="col.type !== formTypes.hidden"
-                :key="col.key"
-                :style="buildTdStyle(col)">
-
-                <!-- 此 v-for 只是为了拼接 id 字符串 -->
-                <template v-for="(id,i) in [`${col.key}${row.id}`]">
-
-                  <!-- native input -->
-                  <label :key="i" v-if="col.type === formTypes.input || col.type === formTypes.inputNumber">
-                    <a-tooltip
-                      :id="id"
-                      placement="top"
-                      :title="(tooltips[id] || {}).title"
-                      :visible="(tooltips[id] || {}).visible || false"
-                      :autoAdjustOverflow="true">
-
-                      <input
-                        :id="id"
-                        v-bind="buildProps(row,col)"
-                        :data-input-number="col.type === formTypes.inputNumber"
-                        :placeholder="replaceProps(col, col.placeholder)"
-                        @input="(e)=>{handleInputCommono(e.target,rowIndex,row,col)}"
-                        @mouseover="()=>{handleMouseoverCommono(row,col)}"
-                        @mouseout="()=>{handleMouseoutCommono(row,col)}"/>
-
-                    </a-tooltip>
+                :id="`${caseId}tbody-tr-${rowIndex}`"
+                :data-idx="rowIndex"
+                class="tr"
+                :class="selectedRowIds.indexOf(row.id) !== -1 ? 'tr-checked' : ''"
+                :style="buildTrStyle(rowIndex)"
+                :key="row.id">
+                <!-- 左侧固定td  -->
+
+                <div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
+                  <div class="td-ds-icons">
+                    <a-icon type="align-left"/>
+                    <a-icon type="align-right"/>
+                  </div>
+                </div>
 
-                  </label>
-                  <!-- checkbox -->
-                  <template v-else-if="col.type === formTypes.checkbox">
+                <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
+                  <!-- 此 v-for 只是为了拼接 id 字符串 -->
+                  <template v-for="(id,i) in [`${row.id}`]">
                     <a-checkbox
-                      :key="i"
                       :id="id"
-                      v-bind="buildProps(row,col)"
-                      :checked="checkboxValues[id]"
-                      @change="(e)=>handleChangeCheckboxCommon(e,row,col)"
-                    />
-                  </template>
-                  <!-- select -->
-                  <template v-else-if="col.type === formTypes.select">
-                    <a-tooltip
                       :key="i"
-                      :id="id"
-                      placement="top"
-                      :title="(tooltips[id] || {}).title"
-                      :visible="(tooltips[id] || {}).visible || false"
-                      :autoAdjustOverflow="true">
+                      :checked="selectedRowIds.indexOf(id) !== -1"
+                      @change="handleChangeLeftCheckbox"/>
+                  </template>
+                </div>
+                <div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
+                  <span>{{ rowIndex+1 }}</span>
+                </div>
+                <!-- 右侧动态生成td -->
+                <div
+                  class="td"
+                  v-for="col in columns"
+                  v-show="col.type !== formTypes.hidden"
+                  :key="col.key"
+                  :style="buildTdStyle(col)">
+
+                  <!-- 此 v-for 只是为了拼接 id 字符串 -->
+                  <template v-for="(id,i) in [`${col.key}${row.id}`]">
+
+                    <!-- native input -->
+                    <label :key="i" v-if="col.type === formTypes.input || col.type === formTypes.inputNumber">
+                      <a-tooltip
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true">
+
+                        <input
+                          :id="id"
+                          v-bind="buildProps(row,col)"
+                          :data-input-number="col.type === formTypes.inputNumber"
+                          :placeholder="replaceProps(col, col.placeholder)"
+                          @input="(e)=>{handleInputCommono(e.target,rowIndex,row,col)}"
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}"/>
+
+                      </a-tooltip>
+
+                    </label>
+                    <!-- checkbox -->
+                    <template v-else-if="col.type === formTypes.checkbox">
+                      <a-checkbox
+                        :key="i"
+                        :id="id"
+                        v-bind="buildProps(row,col)"
+                        :checked="checkboxValues[id]"
+                        @change="(e)=>handleChangeCheckboxCommon(e,row,col)"
+                      />
+                    </template>
+                    <!-- select -->
+                    <template v-else-if="col.type === formTypes.select">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true">
 
                       <span
                         @mouseover="()=>{handleMouseoverCommono(row,col)}"
@@ -165,17 +178,17 @@
                           <!--</template>-->
                         </a-select>
                       </span>
-                    </a-tooltip>
-                  </template>
-                  <!-- date -->
-                  <template v-else-if="col.type === formTypes.date || col.type === formTypes.datetime">
-                    <a-tooltip
-                      :key="i"
-                      :id="id"
-                      placement="top"
-                      :title="(tooltips[id] || {}).title"
-                      :visible="(tooltips[id] || {}).visible || false"
-                      :autoAdjustOverflow="true">
+                      </a-tooltip>
+                    </template>
+                    <!-- date -->
+                    <template v-else-if="col.type === formTypes.date || col.type === formTypes.datetime">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true">
 
                       <span
                         @mouseover="()=>{handleMouseoverCommono(row,col)}"
@@ -195,80 +208,81 @@
                           @change="(v)=>handleChangeJDateCommon(v,id,row,col,col.type === formTypes.datetime)"/>
 
                       </span>
-                    </a-tooltip>
-                  </template>
-
-                  <div v-else-if="col.type === formTypes.upload" :key="i">
-                    <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
-                      <a-input
-                        :key="fileKey"
-                        :readOnly="true"
-                        :value="file.name"
-                      >
-
-                        <template slot="addonBefore" style="width: 30px">
-                          <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
-                            <a-icon type="loading"/>
-                          </a-tooltip>
-                          <a-tooltip v-else-if="file.status==='done'" title="上传完成">
-                            <a-icon type="check-circle" style="color:#00DB00;"/>
-                          </a-tooltip>
-                          <a-tooltip v-else title="上传失败">
-                            <a-icon type="exclamation-circle" style="color:red;"/>
-                          </a-tooltip>
-                        </template>
-
-                        <template slot="addonAfter" style="width: 30px">
-                          <a-tooltip title="删除并重新上传">
-                            <a-icon
-                              v-if="file.status!=='uploading'"
-                              type="close-circle"
-                              style="cursor: pointer;"
-                              @click="()=>handleClickDelFile(id)"/>
-                          </a-tooltip>
-                        </template>
-
-                      </a-input>
+                      </a-tooltip>
                     </template>
 
-                    <div :hidden="uploadValues[id] != null">
+                    <div v-else-if="col.type === formTypes.upload" :key="i">
+                      <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
+                        <a-input
+                          :key="fileKey"
+                          :readOnly="true"
+                          :value="file.name"
+                        >
 
-                      <a-upload
-                        name="file"
-                        :data="{'isup':1}"
-                        :multiple="false"
-                        :action="col.action"
-                        :headers="uploadGetHeaders(row,col)"
-                        :showUploadList="false"
-                        v-bind="buildProps(row,col)"
-                        @change="(v)=>handleChangeUpload(v,id,row,col)"
-                      >
-                        <a-button icon="upload">{{ col.placeholder }}</a-button>
-                      </a-upload>
-                    </div>
+                          <template slot="addonBefore" style="width: 30px">
+                            <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
+                              <a-icon type="loading"/>
+                            </a-tooltip>
+                            <a-tooltip v-else-if="file.status==='done'" title="上传完成">
+                              <a-icon type="check-circle" style="color:#00DB00;"/>
+                            </a-tooltip>
+                            <a-tooltip v-else title="上传失败">
+                              <a-icon type="exclamation-circle" style="color:red;"/>
+                            </a-tooltip>
+                          </template>
+
+                          <template slot="addonAfter" style="width: 30px">
+                            <a-tooltip title="删除并重新上传">
+                              <a-icon
+                                v-if="file.status!=='uploading'"
+                                type="close-circle"
+                                style="cursor: pointer;"
+                                @click="()=>handleClickDelFile(id)"/>
+                            </a-tooltip>
+                          </template>
+
+                        </a-input>
+                      </template>
+
+                      <div :hidden="uploadValues[id] != null">
+
+                        <a-upload
+                          name="file"
+                          :data="{'isup':1}"
+                          :multiple="false"
+                          :action="col.action"
+                          :headers="uploadGetHeaders(row,col)"
+                          :showUploadList="false"
+                          v-bind="buildProps(row,col)"
+                          @change="(v)=>handleChangeUpload(v,id,row,col)"
+                        >
+                          <a-button icon="upload">{{ col.placeholder }}</a-button>
+                        </a-upload>
+                      </div>
 
-                  </div>
+                    </div>
 
-                  <div v-else-if="col.type === formTypes.slot" :key="i">
-                    <slot
-                      :name="(col.slot || col.slotName) || col.key"
-                      :text="inputValues[rowIndex][col.key]"
-                      :column="col"
-                      :rowId="removeCaseId(row.id)"
-                      :getValue="()=>_getValueForSlot(row.id)"
-                      :target="getVM()"
-                    />
-                  </div>
+                    <div v-else-if="col.type === formTypes.slot" :key="i">
+                      <slot
+                        :name="(col.slot || col.slotName) || col.key"
+                        :index="rowIndex"
+                        :text="inputValues[rowIndex][col.key]"
+                        :column="col"
+                        :rowId="removeCaseId(row.id)"
+                        :getValue="()=>_getValueForSlot(row.id)"
+                        :target="getVM()"
+                      />
+                    </div>
 
-                  <!-- else (normal) -->
-                  <span v-else :key="i">{{ inputValues[rowIndex][col.key] }}</span>
-                </template>
+                    <!-- else (normal) -->
+                    <span v-else :key="i">{{ inputValues[rowIndex][col.key] }}</span>
+                  </template>
+                </div>
               </div>
-            </div>
-            <!-- -- tr end -- -->
-
-          </template>
+              <!-- -- tr end -- -->
 
+            </template>
+          </draggable>
 
         </div>
       </div>
@@ -278,6 +292,7 @@
 
 <script>
   import Vue from 'vue'
+  import Draggable from 'vuedraggable'
   import { ACCESS_TOKEN } from '@/store/mutation-types'
   import { FormTypes, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
   import { cloneObject, randomString } from '@/utils/util'
@@ -289,7 +304,7 @@
 
   export default {
     name: 'JEditableTable',
-    components: { JDate },
+    components: { JDate, Draggable },
     props: {
       // 列信息
       columns: {
@@ -338,7 +353,16 @@
       disabled: {
         type: Boolean,
         default: false
-      }
+      },
+      // 是否可拖拽排序
+      dragSort: {
+        type: Boolean,
+        default: false
+      },
+      dragSortKey: {
+        type: String,
+        default: 'orderNum'
+      },
     },
     data() {
       return {
@@ -354,7 +378,8 @@
           // 'max-height': '400px'
           tbody: { left: '0px' },
           // 左侧固定td的style
-          tdLeft: { 'min-width': '4%', 'max-width': '45px' }
+          tdLeft: { 'min-width': '4%', 'max-width': '45px' },
+          tdLeftDs: { 'min-width': '30px', 'max-width': '35px' },
         },
         // 表单的类型
         formTypes: FormTypes,
@@ -455,89 +480,92 @@
     },
     // 侦听器
     watch: {
-      dataSource: function (newValue) {
-        this.initialize()
-
-        let rows = []
-        let checkboxValues = {}
-        let selectValues = {}
-        let jdateValues = {}
-        // 禁用行的id
-        let disabledRowIds = (this.disabledRowIds || [])
-        newValue.forEach((data, newValueIndex) => {
-          // 判断源数据是否带有id
-          if (data.id == null || data.id === '') {
-            data.id = this.removeCaseId(this.generateId() + newValueIndex)
-          }
+      dataSource: {
+        immediate: true,
+        handler: function (newValue) {
+          this.initialize()
+
+          let rows = []
+          let checkboxValues = {}
+          let selectValues = {}
+          let jdateValues = {}
+          // 禁用行的id
+          let disabledRowIds = (this.disabledRowIds || [])
+          newValue.forEach((data, newValueIndex) => {
+            // 判断源数据是否带有id
+            if (data.id == null || data.id === '') {
+              data.id = this.removeCaseId(this.generateId() + newValueIndex)
+            }
 
-          let value = { id: this.caseId + data.id }
-          let row = { id: value.id }
-          let disabled = false
-          this.columns.forEach(column => {
-            let inputId = column.key + value.id
-            let sourceValue = (data[column.key] == null ? '' : data[column.key]).toString()
-            if (column.type === FormTypes.checkbox) {
+            let value = { id: this.caseId + data.id }
+            let row = { id: value.id }
+            let disabled = false
+            this.columns.forEach(column => {
+              let inputId = column.key + value.id
+              let sourceValue = (data[column.key] == null ? '' : data[column.key]).toString()
+              if (column.type === FormTypes.checkbox) {
+
+                // 判断是否设定了customValue(自定义值)
+                if (column.customValue instanceof Array) {
+                  let customValue = (column.customValue[0] || '').toString()
+                  checkboxValues[inputId] = (sourceValue === customValue)
+                } else {
+                  checkboxValues[inputId] = sourceValue
+                }
 
-              // 判断是否设定了customValue(自定义值)
-              if (column.customValue instanceof Array) {
-                let customValue = (column.customValue[0] || '').toString()
-                checkboxValues[inputId] = (sourceValue === customValue)
-              } else {
-                checkboxValues[inputId] = sourceValue
-              }
+              } else if (column.type === FormTypes.select) {
+                if (sourceValue) {
+                  // 判断是否是多选
+                  selectValues[inputId] = (column.props || {})['mode'] === 'multiple' ? sourceValue.split(',') : sourceValue
+                } else {
+                  selectValues[inputId] = undefined
+                }
 
-            } else if (column.type === FormTypes.select) {
-              if (sourceValue) {
-                // 判断是否是多选
-                selectValues[inputId] = (column.props || {})['mode'] === 'multiple' ? sourceValue.split(',') : sourceValue
-              } else {
-                selectValues[inputId] = undefined
-              }
+              } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
+                jdateValues[inputId] = sourceValue
 
-            } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
-              jdateValues[inputId] = sourceValue
+              } else if (column.type === FormTypes.slot) {
+                if (sourceValue !== 0 && !sourceValue) {
+                  value[column.key] = column.defaultValue
+                } else {
+                  value[column.key] = sourceValue
+                }
 
-            } else if (column.type === FormTypes.slot) {
-              if (sourceValue !== 0 && !sourceValue) {
-                value[column.key] = column.defaultValue
               } else {
                 value[column.key] = sourceValue
               }
 
-            } else {
-              value[column.key] = sourceValue
-            }
-
-            // 解析disabledRows
-            for (let columnKey in this.disabledRows) {
-              // 判断是否有该属性
-              if (this.disabledRows.hasOwnProperty(columnKey) && data.hasOwnProperty(columnKey)) {
-                // row[columnKey] =
+              // 解析disabledRows
+              for (let columnKey in this.disabledRows) {
+                // 判断是否有该属性
+                if (this.disabledRows.hasOwnProperty(columnKey) && data.hasOwnProperty(columnKey)) {
+                  // row[columnKey] =
 
-                if (disabled !== true) {
-                  disabled = this.disabledRows[columnKey] === data[columnKey]
-                  if (disabled) {
-                    disabledRowIds.push(row.id)
+                  if (disabled !== true) {
+                    disabled = this.disabledRows[columnKey] === data[columnKey]
+                    if (disabled) {
+                      disabledRowIds.push(row.id)
+                    }
                   }
-                }
 
+                }
               }
-            }
+            })
+            this.inputValues.push(value)
+            rows.push(row)
           })
-          this.inputValues.push(value)
-          rows.push(row)
-        })
-        this.disabledRowIds = disabledRowIds
-        this.checkboxValues = checkboxValues
-        this.selectValues = selectValues
-        this.jdateValues = jdateValues
-        this.rows = rows
+          this.disabledRowIds = disabledRowIds
+          this.checkboxValues = checkboxValues
+          this.selectValues = selectValues
+          this.jdateValues = jdateValues
+          this.rows = rows
 
-        // 更新form表单的值
-        this.$nextTick(() => {
-          this.updateFormValues()
-        })
+          // 更新form表单的值
+          this.$nextTick(() => {
+            this.updateFormValues()
+          })
 
+        }
       },
       columns: {
         immediate: true,
@@ -722,6 +750,12 @@
         this.selectValues = selectValues
         this.jdateValues = jdateValues
 
+        if (this.dragSort) {
+          this.inputValues.forEach((item, index) => {
+            item[this.dragSortKey] = (index + 1)
+          })
+        }
+
         if (update) {
           this.rows = rows
           this.$nextTick(() => {
@@ -751,6 +785,7 @@
       },
       /** 添加一行 */
       add(num = 1, forceScrollToBottom = false) {
+        if (num < 1) return
         // let timestamp = new Date().getTime()
         let rows = this.rows
         let row
@@ -1230,6 +1265,31 @@
         }
       },
 
+      /** 拖动结束,交换inputValue中的值 */
+      handleDragMoveEnd(event) {
+        let { oldIndex, newIndex } = event
+
+        let values = this.inputValues
+        // 存储旧数据,并删除旧项目
+        let temp = values[oldIndex]
+        values.splice(oldIndex, 1)
+        // 向新项目里添加旧数据
+        values.splice(newIndex, 0, temp)
+
+        values.forEach((item, index) => {
+          item[this.dragSortKey] = (index + 1)
+        })
+
+        this.forceUpdateFormValues()
+
+        // 触发已拖动事件
+        this.$emit('dragged', {
+          oldIndex,
+          newIndex,
+          target: this
+        })
+      },
+
       /* --- common function begin --- */
 
       /** 鼠标移入 */
@@ -1355,7 +1415,12 @@
       _loadDictConcatToOptions(column) {
         initDictOptions(column.dictCode).then((res) => {
           if (res.success) {
-            column.options = (column.options || []).concat(res.result)
+            let newOptions = (column.options || [])// .concat(res.result)
+            res.result.forEach(item => {
+              for (let option of newOptions) if (option.value === item.value) return
+              newOptions.push(item)
+            })
+            column.options = newOptions
           } else {
             console.group(`JEditableTable 查询字典(${column.dictCode})发生异常`)
             console.log(res.message)
@@ -1377,14 +1442,46 @@
 
       /** 辅助方法:指定a-select 和 j-data 的父容器 */
       getParentContainer(node) {
-        if (this.$el && this.$el.nodeType !== 8) {
-          return this.$el
-        }
-        let doc = document.getElementById(this.caseId + 'inputTable')
-        if (doc != null) {
-          return doc
+        let element = (() => {
+          // nodeType 8	: Comment	: 注释
+          if (this.$el && this.$el.nodeType !== 8) {
+            return this.$el
+          }
+          let doc = document.getElementById(this.caseId + 'inputTable')
+          if (doc != null) {
+            return doc
+          }
+          return node.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
+        })()
+
+        // 递归判断是否带有 overflow: hidden;的父元素
+        const ifParent = (child) => {
+          let currentOverflow = null
+          if (child['currentStyle']) {
+            currentOverflow = child['currentStyle']['overflow']
+          } else if (window.getComputedStyle) {
+            currentOverflow = window.getComputedStyle(child)['overflow']
+          }
+          if (currentOverflow != null) {
+            if (currentOverflow === 'hidden') {
+              // 找到了带有 hidden 的标签,判断它的父级是否还有 hidden,直到遇到完全没有 hidden 或 body 的时候才停止递归
+              let temp = ifParent(child.parentNode)
+              return temp != null ? temp : child.parentNode
+            } else
+            // 当前标签没有 hidden ,如果有父级并且父级不是 body 的话就继续递归判断父级
+            if (child.parentNode && child.parentNode.tagName.toLocaleLowerCase() !== 'body') {
+              return ifParent(child.parentNode)
+            } else {
+              // 直到 body 都没有遇到有 hidden 的标签
+              return null
+            }
+          } else {
+            return child
+          }
         }
-        return node.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
+
+        let temp = ifParent(element)
+        return (temp != null) ? temp : element
       },
 
       /** 辅助方法:替换${...}变量 */
@@ -1440,6 +1537,9 @@
           props['showSearch'] = true
         }
 
+        // 判断是否是禁用的列
+        props['disabled'] = !!col['disabled']
+
         // 判断是否为禁用的行
         if (props['disabled'] !== true) {
           props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
@@ -1515,6 +1615,38 @@
           align-items: center;
         }
 
+        &.td-ds {
+          margin-right: 0;
+          padding-left: 0;
+          padding-right: 0;
+          justify-content: center;
+          align-items: center;
+
+          .td-ds-icons {
+            position: relative;
+            cursor: move;
+            width: 100%;
+            /*padding: 25% 0;*/
+            height: 100%;
+
+            .anticon-align-left,
+            .anticon-align-right {
+              position: absolute;
+              top: 30%;
+            }
+
+            .anticon-align-left {
+              left: 25%;
+            }
+
+            .anticon-align-right {
+              right: 25%;
+            }
+          }
+
+
+        }
+
       }
 
     }

+ 1 - 1
ant-design-vue-jeecg/src/components/jeecg/JEditor.vue

@@ -84,7 +84,7 @@
     },
     watch: {
       value(newValue) {
-        this.myValue = newValue
+        this.myValue = (newValue == null ? '' : newValue)
       },
       myValue(newValue) {
         console.log(newValue)

+ 32 - 27
ant-design-vue-jeecg/src/components/jeecg/README_JEditableTable.md

@@ -2,30 +2,33 @@
 
 ## 参数配置
 
-| 参数         | 类型    | 必填 | 说明                                                           |
-|--------------|---------|------|----------------------------------------------------------------|
-| columns      | array   | ✔️    | 表格列的配置描述,具体项见下表                                 |
-| dataSource   | array   | ✔️    | 表格数据                                                       |
-| loading      | boolean |      | 是否正在加载,加载中不会显示任何行,默认false                  |
-| actionButton | boolean |      | 是否显示操作按钮,包括"新增"、"删除",默认false                |
-| rowNumber    | boolean |      | 是否显示行号,默认false                                        |
-| rowSelection | boolean |      | 是否可选择行,默认false                                        |
-| maxHeight    | number  |      | 设定最大高度(px),默认400                                      |
-| disabledRows | object  |      | 设定禁用的行,被禁用的行无法被选择和编辑,配置方法可以查看示例 |
-| disabled     | boolean |      | 是否禁用所有行,默认false                                      |
+| 参数         | 类型    | 必填 | 说明                                                                            |
+|--------------|---------|------|---------------------------------------------------------------------------------|
+| columns      | array   | ✔️    | 表格列的配置描述,具体项见下表                                                  |
+| dataSource   | array   | ✔️    | 表格数据                                                                        |
+| loading      | boolean |      | 是否正在加载,加载中不会显示任何行,默认false                                   |
+| actionButton | boolean |      | 是否显示操作按钮,包括"新增"、"删除",默认false                                 |
+| rowNumber    | boolean |      | 是否显示行号,默认false                                                         |
+| rowSelection | boolean |      | 是否可选择行,默认false                                                         |
+| dragSort     | boolean |      | 是否可拖动排序,默认false                                                       |
+| dragSortKey  | string  |      | 拖动排序存储的Key,无需定义在columns内也能在getValues()时获取到值,默认orderNum |
+| maxHeight    | number  |      | 设定最大高度(px),默认400                                                       |
+| disabledRows | object  |      | 设定禁用的行,被禁用的行无法被选择和编辑,配置方法可以查看示例                  |
+| disabled     | boolean |      | 是否禁用所有行,默认false                                                       |
 
 ### columns 参数详解
 
-| 参数          | 类型   | 必填 | 说明                                                                                                                                                   |
-|---------------|--------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
-| title         | string | ✔️    | 表格列头显示的问题                                                                                                                                     |
-| key           | string | ✔️    | 列数据在数据项中对应的 key,必须是唯一的                                                                                                               |
-| type          | string | ✔️    | 表单的类型,可以通过`JEditableTableUtil.FormTypes`赋值                                                                                                 |
-| width         | string |      | 列的宽度,可以是百分比,也可以是`px`或其他单位,建议设置为百分比,且每一列的宽度加起来不应超过100%,否则可能会不能达到预期的效果。留空会自动计算百分比 |
-| placeholder   | string |      | 表单预期值的提示信息,可以使用`${...}`变量替换文本(详见`${...} 变量使用方式`)                                                                        |
-| defaultValue  | string |      | 默认值,在新增一行时生效                                                                                                                               |
-| validateRules | array  |      | 表单验证规则,配置方式见[validateRules 配置规则](#validaterules-配置规则)                                                                              |
-| props         | object |      | 设置添加给表单元素的自定义属性,例如:`props:{title: 'show title'}`                                                                                     |
+| 参数          | 类型    | 必填 | 说明                                                                                                                                                   |
+|---------------|---------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
+| title         | string  | ✔️    | 表格列头显示的问题                                                                                                                                     |
+| key           | string  | ✔️    | 列数据在数据项中对应的 key,必须是唯一的                                                                                                               |
+| type          | string  | ✔️    | 表单的类型,可以通过`JEditableTableUtil.FormTypes`赋值                                                                                                 |
+| width         | string  |      | 列的宽度,可以是百分比,也可以是`px`或其他单位,建议设置为百分比,且每一列的宽度加起来不应超过100%,否则可能会不能达到预期的效果。留空会自动计算百分比 |
+| placeholder   | string  |      | 表单预期值的提示信息,可以使用`${...}`变量替换文本(详见`${...} 变量使用方式`)                                                                        |
+| defaultValue  | string  |      | 默认值,在新增一行时生效                                                                                                                               |
+| validateRules | array   |      | 表单验证规则,配置方式见[validateRules 配置规则](#validaterules-配置规则)                                                                              |
+| props         | object  |      | 设置添加给表单元素的自定义属性,例如:`props:{title: 'show title'}`                                                                                     |
+| disabled      | boolean |      | 是否禁用当前列,默认false                                                                                                                              |
 
 #### 当 type=checkbox 时所需的参数
 
@@ -40,7 +43,7 @@
 |------------|---------|------|----------------------------------------------------|
 | options    | array   | ✔️    | 下拉选项列表,详见下表                             |
 | allowInput | boolean |      | 是否允许用户输入内容,并创建新的内容               |
-| dictCode   | String  |      | 数据字典Code,若options也有值,则拼接options后面 |
+| dictCode   | String  |      | 数据字典Code,若options也有值,则拼接options后面 |
 
 ##### options 所需参数
 
@@ -75,11 +78,12 @@
 
 ## 事件
 
-| 事件名          | 触发时机                                           | 参数                          |
-|-----------------|----------------------------------------------------|-------------------------------|
-| added           | 当添加行操作完成后触发                             |                               |
-| deleted         | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id    |
-| selectRowChange | 当行被选中或取消选中时触发                         | `selectedRowIds` 被选中行的id |
+| 事件名          | 触发时机                                           | 参数                                             |
+|-----------------|----------------------------------------------------|--------------------------------------------------|
+| added           | 当添加行操作完成后触发                             |                                                  |
+| deleted         | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id                       |
+| selectRowChange | 当行被选中或取消选中时触发                         | `selectedRowIds` 被选中行的id                    |
+| valueChange     | 当数据发生改变的时候触发的事件                     | `{ type, row, column, value, target }` Event对象 |
 
 ## 方法
 
@@ -490,6 +494,7 @@ this.$refs.editableTable.getValues((error, values) => {
             /* a 标签的点击事件,删除当前选中的行 */
             handleDelete(props) {
                 // 参数解释
+                // props.index :当前行的下标
                 // props.text :当前值,可能是defaultValue定义的值,也可能是从dataSource中取出的值
                 // props.rowId :当前选中行的id,如果是新增行则是临时id
                 // props.column :当前操作的列

+ 928 - 0
ant-design-vue-jeecg/src/components/jeecg/modal/JCronModal.vue

@@ -0,0 +1,928 @@
+<template>
+  <a-modal
+          title="corn表达式"
+          :width="modalWidth"
+          :visible="visible"
+          :confirmLoading="confirmLoading"
+          @ok="handleSubmit"
+          @cancel="close"
+          cancelText="关闭">
+    <div class="card-container">
+      <a-tabs type="card">
+        <a-tab-pane key="1" type="card">
+          <span slot="tab"><a-icon type="schedule" /> 秒</span>
+          <a-radio-group v-model="result.second.cronEvery">
+            <a-row>
+              <a-radio value="1">每一秒钟</a-radio>
+            </a-row>
+            <a-row>
+              <a-radio value="2">每隔
+                <a-input-number size="small" v-model="result.second.incrementIncrement" :min="1" :max="60"></a-input-number>
+                秒执行 从
+                <a-input-number size="small" v-model="result.second.incrementStart" :min="0" :max="59"></a-input-number>
+                秒开始
+              </a-radio>
+            </a-row>
+            <a-row>
+              <a-radio value="3">具体秒数(可多选)</a-radio>
+              <a-select style="width:354px;" size="small" mode="multiple" v-model="result.second.specificSpecific">
+                <a-select-option v-for="(val,index) in 60" :key="index" :value="index">{{ index }}</a-select-option>
+              </a-select>
+            </a-row>
+            <a-row>
+              <a-radio value="4">周期从
+                <a-input-number size="small" v-model="result.second.rangeStart" :min="1" :max="60"></a-input-number>
+                到
+                <a-input-number size="small" v-model="result.second.rangeEnd" :min="0" :max="59"></a-input-number>
+                秒
+              </a-radio>
+            </a-row>
+          </a-radio-group>
+        </a-tab-pane>
+        <a-tab-pane key="2">
+          <span slot="tab"><a-icon type="schedule" />分</span>
+          <div class="tabBody">
+            <a-radio-group v-model="result.minute.cronEvery">
+              <a-row>
+                <a-radio value="1">每一分钟</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="2">每隔
+                  <a-input-number size="small" v-model="result.minute.incrementIncrement" :min="1" :max="60"></a-input-number>
+                  分执行 从
+                  <a-input-number size="small" v-model="result.minute.incrementStart" :min="0" :max="59"></a-input-number>
+                  分开始
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="3">具体分钟数(可多选)</a-radio>
+                <a-select style="width:340px;" size="small" mode="multiple" v-model="result.minute.specificSpecific">
+                  <a-select-option v-for="(val,index) in Array(60)" :key="index" :value="index"> {{ index }}</a-select-option>
+                </a-select>
+              </a-row>
+              <a-row>
+                <a-radio value="4">周期从
+                  <a-input-number size="small" v-model="result.minute.rangeStart" :min="1" :max="60"></a-input-number>
+                  到
+                  <a-input-number size="small" v-model="result.minute.rangeEnd" :min="0" :max="59"></a-input-number>
+                  分
+                </a-radio>
+              </a-row>
+            </a-radio-group>
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="3">
+          <span slot="tab"><a-icon type="schedule" /> 时</span>
+          <div class="tabBody">
+            <a-radio-group v-model="result.hour.cronEvery">
+              <a-row>
+                <a-radio value="1">每一小时</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="2">每隔
+                  <a-input-number size="small" v-model="result.hour.incrementIncrement" :min="0" :max="23"></a-input-number>
+                  小时执行 从
+                  <a-input-number size="small" v-model="result.hour.incrementStart" :min="0" :max="23"></a-input-number>
+                  小时开始
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio class="long" value="3">具体小时数(可多选)</a-radio>
+                <a-select style="width:340px;" size="small" mode="multiple" v-model="result.hour.specificSpecific">
+                  <a-select-option v-for="(val,index) in Array(24)" :key="index" >{{ index }}</a-select-option>
+                </a-select>
+              </a-row>
+              <a-row>
+                <a-radio value="4">周期从
+                  <a-input-number size="small" v-model="result.hour.rangeStart" :min="0" :max="23"></a-input-number>
+                  到
+                  <a-input-number size="small" v-model="result.hour.rangeEnd" :min="0" :max="23"></a-input-number>
+                  小时
+                </a-radio>
+              </a-row>
+            </a-radio-group>
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="4">
+          <span slot="tab"><a-icon type="schedule" />  天</span>
+          <div class="tabBody">
+            <a-radio-group v-model="result.day.cronEvery">
+              <a-row>
+                <a-radio value="1">每一天</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="2">每隔
+                  <a-input-number size="small" v-model="result.week.incrementIncrement" :min="1" :max="7"></a-input-number>
+                  周执行 从
+                  <a-select size="small" v-model="result.week.incrementStart">
+                    <a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
+                  </a-select>
+                  开始
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="3">每隔
+                  <a-input-number size="small" v-model="result.day.incrementIncrement" :min="1" :max="31"></a-input-number>
+                  天执行 从
+                  <a-input-number size="small" v-model="result.day.incrementStart" :min="1" :max="31"></a-input-number>
+                  天开始
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio class="long" value="4">具体星期几(可多选)</a-radio>
+                <a-select style="width:340px;" size="small" mode="multiple" v-model="result.week.specificSpecific">
+                  <a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
+                </a-select>
+              </a-row>
+              <a-row>
+                <a-radio class="long" value="5">具体天数(可多选)</a-radio>
+                <a-select style="width:354px;" size="small" mode="multiple" v-model="result.day.specificSpecific">
+                  <a-select-option v-for="(val,index) in Array(31)" :key="index" :value="index+1">{{ index+1 }}</a-select-option>
+                </a-select>
+              </a-row>
+              <a-row>
+                <a-radio value="6">在这个月的最后一天</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="7">在这个月的最后一个工作日</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="8">在这个月的最后一个
+                  <a-select size="small" v-model="result.day.cronLastSpecificDomDay">
+                    <a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
+                  </a-select>
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="9">
+                  在本月底前
+                  <a-input-number size="small" v-model="result.day.cronDaysBeforeEomMinus" :min="1" :max="31"></a-input-number>
+                  天
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="10">最近的工作日(周一至周五)至本月
+                  <a-input-number size="small" v-model="result.day.cronDaysNearestWeekday" :min="1" :max="31"></a-input-number>
+                  日
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="11">在这个月的第
+                  <a-input-number size="small" v-model="result.week.cronNthDayNth" :min="1" :max="5"></a-input-number>
+                  个
+                  <a-select size="small" v-model="result.week.cronNthDayDay">
+                    <a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
+                  </a-select>
+
+                </a-radio>
+              </a-row>
+            </a-radio-group>
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="5">
+          <span slot="tab"><a-icon type="schedule" /> 月</span>
+          <div class="tabBody">
+            <a-radio-group v-model="result.month.cronEvery">
+              <a-row>
+                <a-radio value="1">每一月</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="2">每隔
+                  <a-input-number size="small" v-model="result.month.incrementIncrement" :min="0" :max="12"></a-input-number>
+                  月执行 从
+                  <a-input-number size="small" v-model="result.month.incrementStart" :min="0" :max="12"></a-input-number>
+                  月开始
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio class="long" value="3">具体月数(可多选)</a-radio>
+                <a-select style="width:354px;" size="small" filterable mode="multiple" v-model="result.month.specificSpecific">
+                  <a-select-option v-for="(val,index) in Array(12)" :key="index" :value="index+1">{{ index+1 }}</a-select-option>
+                </a-select>
+              </a-row>
+              <a-row>
+                <a-radio value="4">从
+                  <a-input-number size="small" v-model="result.month.rangeStart" :min="1" :max="12"></a-input-number>
+                  到
+                  <a-input-number size="small" v-model="result.month.rangeEnd" :min="1" :max="12"></a-input-number>
+                  月之间的每个月
+                </a-radio>
+              </a-row>
+            </a-radio-group>
+          </div>
+        </a-tab-pane>
+        <a-tab-pane key="6">
+          <span slot="tab"><a-icon type="schedule" /> 年</span>
+          <div class="tabBody">
+            <a-radio-group v-model="result.year.cronEvery">
+              <a-row>
+                <a-radio value="1">每一年</a-radio>
+              </a-row>
+              <a-row>
+                <a-radio value="2">每隔
+                  <a-input-number size="small" v-model="result.year.incrementIncrement" :min="1" :max="99"></a-input-number>
+                  年执行 从
+                  <a-input-number size="small" v-model="result.year.incrementStart" :min="2019" :max="2119"></a-input-number>
+                  年开始
+                </a-radio>
+              </a-row>
+              <a-row>
+                <a-radio class="long" value="3">具体年份(可多选)</a-radio>
+                <a-select style="width:354px;" size="small" filterable mode="multiple" v-model="result.year.specificSpecific">
+                  <a-select-option v-for="(val,index) in Array(100)" :key="index" :value="2019+index">{{ 2019+index }}</a-select-option>
+                </a-select>
+              </a-row>
+              <a-row>
+                <a-radio value="4">从
+                  <a-input-number size="small" v-model="result.year.rangeStart" :min="2019" :max="2119"></a-input-number>
+                  到
+                  <a-input-number size="small" v-model="result.year.rangeEnd" :min="2019" :max="2119"></a-input-number>
+                  年之间的每一年
+                </a-radio>
+              </a-row>
+            </a-radio-group>
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+      <div class="bottom">
+        <span class="value">{{this.cron }}</span>
+      </div>
+    </div>
+  </a-modal>
+</template>
+<script>
+  export default {
+    name:'VueCron',
+    props:['data'],
+    data(){
+      return {
+        visible: false,
+        confirmLoading:false,
+        size:'large',
+        weekDays:['天','一','二','三','四','五','六'].map(val=>'星期'+val),
+        result: {
+          second:{},
+          minute:{},
+          hour:{},
+          day:{},
+          week:{},
+          month:{},
+          year:{}
+        },
+        defaultValue: {
+          second:{
+            cronEvery:'',
+            incrementStart:3,
+            incrementIncrement:5,
+            rangeStart:1,
+            rangeEnd:0,
+            specificSpecific:[],
+          },
+          minute:{
+            cronEvery:'',
+            incrementStart:3,
+            incrementIncrement:5,
+            rangeStart:1,
+            rangeEnd:'0',
+            specificSpecific:[],
+          },
+          hour:{
+            cronEvery:'',
+            incrementStart:3,
+            incrementIncrement:5,
+            rangeStart:'0',
+            rangeEnd:'0',
+            specificSpecific:[],
+          },
+          day:{
+            cronEvery:'',
+            incrementStart:1,
+            incrementIncrement:'1',
+            rangeStart:'',
+            rangeEnd:'',
+            specificSpecific:[],
+            cronLastSpecificDomDay:1,
+            cronDaysBeforeEomMinus:1,
+            cronDaysNearestWeekday:1,
+          },
+          week:{
+            cronEvery:'',
+            incrementStart:1,
+            incrementIncrement:1,
+            specificSpecific:[],
+            cronNthDayDay:1,
+            cronNthDayNth:1,
+          },
+          month:{
+            cronEvery:'',
+            incrementStart:3,
+            incrementIncrement:5,
+            rangeStart:1,
+            rangeEnd:1,
+            specificSpecific:[],
+          },
+          year:{
+            cronEvery:'',
+            incrementStart:2017,
+            incrementIncrement:1,
+            rangeStart:2019,
+            rangeEnd: 2019,
+            specificSpecific:[],
+          },
+          label:''
+        }
+      }
+    },
+    computed: {
+      modalWidth(){
+        return 608;
+      },
+      secondsText() {
+        let seconds = '';
+        let cronEvery=this.result.second.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+            seconds = '*';
+            break;
+          case '2':
+            seconds = this.result.second.incrementStart+'/'+this.result.second.incrementIncrement;
+            break;
+          case '3':
+            this.result.second.specificSpecific.map(val=> {seconds += val+','});
+            seconds = seconds.slice(0, -1);
+            break;
+          case '4':
+            seconds = this.result.second.rangeStart+'-'+this.result.second.rangeEnd;
+            break;
+        }
+        return seconds;
+      },
+      minutesText() {
+        let minutes = '';
+        let cronEvery=this.result.minute.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+            minutes = '*';
+            break;
+          case '2':
+            minutes = this.result.minute.incrementStart+'/'+this.result.minute.incrementIncrement;
+            break;
+          case '3':
+            this.result.minute.specificSpecific.map(val=> {
+              minutes += val+','
+            });
+            minutes = minutes.slice(0, -1);
+            break;
+          case '4':
+            minutes = this.result.minute.rangeStart+'-'+this.result.minute.rangeEnd;
+            break;
+        }
+        return minutes;
+      },
+      hoursText() {
+        let hours = '';
+        let cronEvery=this.result.hour.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+            hours = '*';
+            break;
+          case '2':
+            hours = this.result.hour.incrementStart+'/'+this.result.hour.incrementIncrement;
+            break;
+          case '3':
+            this.result.hour.specificSpecific.map(val=> {
+              hours += val+','
+            });
+            hours = hours.slice(0, -1);
+            break;
+          case '4':
+            hours = this.result.hour.rangeStart+'-'+this.result.hour.rangeEnd;
+            break;
+        }
+        return hours;
+      },
+      daysText() {
+        let days='';
+        let cronEvery=this.result.day.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+            break;
+          case '2':
+          case '4':
+          case '11':
+            days = '?';
+            break;
+          case '3':
+            days = this.result.day.incrementStart+'/'+this.result.day.incrementIncrement;
+            break;
+          case '5':
+            this.result.day.specificSpecific.map(val=> {
+              days += val+','
+            });
+            days = days.slice(0, -1);
+            break;
+          case '6':
+            days = "L";
+            break;
+          case '7':
+            days = "LW";
+            break;
+          case '8':
+            days = this.result.day.cronLastSpecificDomDay + 'L';
+            break;
+          case '9':
+            days = 'L-' + this.result.day.cronDaysBeforeEomMinus;
+            break;
+          case '10':
+            days = this.result.day.cronDaysNearestWeekday+"W";
+            break
+        }
+        return days;
+      },
+      weeksText() {
+        let weeks = '';
+        let cronEvery=this.result.day.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+          case '3':
+          case '5':
+            weeks = '?';
+            break;
+          case '2':
+            weeks = this.result.week.incrementStart+'/'+this.result.week.incrementIncrement;
+            break;
+          case '4':
+            this.result.week.specificSpecific.map(val=> {
+              weeks += val+','
+            });
+            weeks = weeks.slice(0, -1);
+            break;
+          case '6':
+          case '7':
+          case '8':
+          case '9':
+          case '10':
+            weeks = "?";
+            break;
+          case '11':
+            weeks = this.result.week.cronNthDayDay+"#"+this.result.week.cronNthDayNth;
+            break;
+        }
+        return weeks;
+      },
+      monthsText() {
+        let months = '';
+        let cronEvery=this.result.month.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+            months = '*';
+            break;
+          case '2':
+            months = this.result.month.incrementStart+'/'+this.result.month.incrementIncrement;
+            break;
+          case '3':
+            this.result.month.specificSpecific.map(val=> {
+              months += val+','
+            });
+            months = months.slice(0, -1);
+            break;
+          case '4':
+            months = this.result.month.rangeStart+'-'+this.result.month.rangeEnd;
+            break;
+        }
+        return months;
+      },
+      yearsText() {
+        let years = '';
+        let cronEvery=this.result.year.cronEvery||'';
+        switch (cronEvery.toString()){
+          case '1':
+            years = '*';
+            break;
+          case '2':
+            years = this.result.year.incrementStart+'/'+this.result.year.incrementIncrement;
+            break;
+          case '3':
+            this.result.year.specificSpecific.map(val=> {
+              years += val+','
+            });
+            years = years.slice(0, -1);
+            break;
+          case '4':
+            years = this.result.year.rangeStart+'-'+this.result.year.rangeEnd;
+            break;
+        }
+        return years;
+      },
+      cron(){
+        return `${this.secondsText||'*'} ${this.minutesText||'*'} ${this.hoursText||'*'} ${this.daysText||'*'} ${this.monthsText||'*'} ${this.weeksText||'?'} ${this.yearsText||'*'}`
+      },
+    },
+    watch:{
+      visible:{
+        handler() {
+          // if(this.data){
+          //   //this. result = Object.keys(this.data.value).length>0?this.deepCopy(this.data.value):this.deepCopy(this.defaultValue);
+          //   //this.result = Object.keys(this.data.value).length>0?clone(this.data.value):clone(this.defaultValue);
+          //   //this.result = Object.keys(this.data.value).length>0?clone(JSON.parse(this.data.value)):clone(this.defaultValue);
+          //   this.result = Object.keys(this.data.value).length>0?JSON.parse(this.data.value):JSON.parse(JSON.stringify(this.defaultValue));
+          // }else{
+          //   //this.result = this.deepCopy(this.defaultValue);
+          //   //this.result = clone(this.defaultValue);
+          //   this.result = JSON.parse(JSON.stringify(this.defaultValue));
+          // }
+          let label = this.data;
+          if(label){
+            this.secondsReverseExp(label)
+            this.minutesReverseExp(label);
+            this.hoursReverseExp(label);
+            this.daysReverseExp(label);
+            this.daysReverseExp(label);
+            this.monthsReverseExp(label);
+            this.yearReverseExp(label);
+            JSON.parse(JSON.stringify(label));
+          }else {
+            this.result = JSON.parse(JSON.stringify(this.defaultValue));
+          }
+        }
+      }
+    },
+    methods: {
+      show(){
+        this.visible = true;
+        // console.log('secondsReverseExp',this.secondsReverseExp(this.data));
+        // console.log('minutesReverseExp',this.minutesReverseExp(this.data));
+        // console.log('hoursReverseExp',this.hoursReverseExp(this.data));
+        // console.log('daysReverseExp',this.daysReverseExp(this.data));
+        // console.log('monthsReverseExp',this.monthsReverseExp(this.data));
+        // console.log('yearReverseExp',this.yearReverseExp(this.data));
+
+      },
+      handleSubmit(){
+        this.$emit('ok',this.cron);
+        this.close();
+        this.visible = false;
+      },
+      close(){
+        this.visible = false;
+      },
+      secondsReverseExp(seconds) {
+        let val =  seconds.split(" ")[0];
+        //alert(val);
+        let second = {
+          cronEvery:'',
+          incrementStart:3,
+          incrementIncrement:5,
+          rangeStart:1,
+          rangeEnd:0,
+          specificSpecific:[]
+        };
+        switch (true) {
+          case val.includes('*'):
+            second.cronEvery = '1';
+            break;
+          case val.includes('/'):
+            second.cronEvery = '2';
+            second.incrementStart = val.split('/')[0];
+            second.incrementIncrement = val.split('/')[1];
+            break;
+          case val.includes(','):
+            second.cronEvery = '3';
+            second.specificSpecific = val.split(',').map(Number).sort();
+            break;
+          case val.includes('-'):
+            second.cronEvery = '4';
+            second.rangeStart = val.split('-')[0];
+            second.rangeEnd = val.split('-')[1];
+            break;
+          default:
+            second.cronEvery = '1';
+        }
+        this.result.second = second;
+      },
+      minutesReverseExp(minutes) {
+        let val =  minutes.split(" ")[1];
+        let minute = {
+          cronEvery:'',
+          incrementStart:3,
+          incrementIncrement:5,
+          rangeStart:1,
+          rangeEnd:0,
+          specificSpecific:[],
+        }
+        switch (true) {
+          case val.includes('*'):
+            minute.cronEvery = '1';
+            break;
+          case val.includes('/'):
+            minute.cronEvery = '2';
+            minute.incrementStart = val.split('/')[0];
+            minute.incrementIncrement = val.split('/')[1];
+            break;
+          case val.includes(','):
+            minute.cronEvery = '3';
+            minute.specificSpecific = val.split(',').map(Number).sort();
+            break;
+          case val.includes('-'):
+            minute.cronEvery = '4';
+            minute.rangeStart = val.split('-')[0];
+            minute.rangeEnd = val.split('-')[1];
+            break;
+          default:
+            minute.cronEvery = '1';
+        }
+        this.result.minute = minute;
+      },
+      hoursReverseExp(hours) {
+        let val =  hours.split(" ")[2];
+        let hour ={
+            cronEvery:'',
+            incrementStart:3,
+            incrementIncrement:5,
+            rangeStart:1,
+            rangeEnd:'0',
+            specificSpecific:[],
+          };
+        switch (true) {
+          case val.includes('*'):
+            hour.cronEvery = '1';
+            break;
+          case val.includes('/'):
+            hour.cronEvery = '2';
+            hour.incrementStart = val.split('/')[0];
+            hour.incrementIncrement = val.split('/')[1];
+            break;
+          case val.includes(','):
+            hour.cronEvery = '3';
+            hour.specificSpecific = val.split(',').map(Number).sort();
+            break;
+          case val.includes('-'):
+            hour.cronEvery = '4';
+            hour.rangeStart = val.split('-')[0];
+            hour.rangeEnd = val.split('-')[1];
+            break;
+          default:
+            hour.cronEvery = '1';
+          }
+        this.result.hour = hour;
+      },
+      daysReverseExp(cron) {
+        let days =  cron.split(" ")[3];
+        let weeks =  cron.split(" ")[5];
+        let day ={
+          cronEvery:'',
+          incrementStart:1,
+          incrementIncrement:1,
+          rangeStart:1,
+          rangeEnd:1,
+          specificSpecific:[],
+          cronLastSpecificDomDay:1,
+          cronDaysBeforeEomMinus:1,
+          cronDaysNearestWeekday:1,
+        };
+        let week = {
+            cronEvery:'',
+            incrementStart:1,
+            incrementIncrement:1,
+            specificSpecific:[],
+            cronNthDayDay:1,
+            cronNthDayNth:'1',
+        };
+        if (!days.includes('?')) {
+          switch (true) {
+            case days.includes('*'):
+              day.cronEvery = '1';
+              break;
+            case days.includes('?'):
+              // 2、4、11
+              break;
+            case days.includes('/'):
+              day.cronEvery = '3';
+              day.incrementStart = days.split('/')[0];
+              day.incrementIncrement = days.split('/')[1];
+              break;
+            case days.includes(','):
+              day.cronEvery = '5';
+              day.specificSpecific = days.split(',').map(Number).sort();
+              // day.specificSpecific.forEach(function (value, index) {
+              //   day.specificSpecific[index] = value -1;
+              // });
+              break;
+            case days.includes('LW'):
+              day.cronEvery = '7';
+              break;
+            case days.includes('L-'):
+              day.cronEvery = '9';
+              day.cronDaysBeforeEomMinus = days.split('L-')[1];
+              break;
+            case days.includes('L'):
+
+              //alert(days);
+              if(days.len == 1){
+                day.cronEvery = '6';
+                day.cronLastSpecificDomDay = '1';
+              }
+              else
+              {
+                day.cronEvery = '8';
+                day.cronLastSpecificDomDay = Number(days.split('L')[0]);
+              }
+              break;
+            case days.includes('W'):
+              day.cronEvery = '10';
+              day.cronDaysNearestWeekday = days.split('W')[0];
+              break;
+            default:
+              day.cronEvery = '1';
+          }
+        }else {
+          switch (true){
+            case weeks.includes('/'):
+              day.cronEvery = '2';
+              week.incrementStart = weeks.split("/")[0];
+              week.incrementIncrement = weeks.split("/")[1];
+              break;
+            case weeks.includes(','):
+              day.cronEvery = '4';
+              week.specificSpecific = weeks.split(',').map(Number).sort();
+              break;
+            case '#':
+              day.cronEvery = '11';
+              week.cronNthDayDay = weeks.split("#")[0];
+              week.cronNthDayNth = weeks.split("#")[1];
+              break;
+            default:
+              day.cronEvery = '1';
+              week.cronEvery = '1';
+          }
+        }
+        this.result.day = day;
+        this.result.week = week;
+      },
+      monthsReverseExp(cron) {
+        let months = cron.split(" ")[4];
+        let month = {
+          cronEvery:'',
+            incrementStart:3,
+            incrementIncrement:5,
+            rangeStart:1,
+            rangeEnd:1,
+            specificSpecific:[],
+        };
+        switch (true){
+          case months.includes('*'):
+            month.cronEvery = '1';
+            break;
+          case months.includes('/'):
+            month.cronEvery = '2';
+            month.incrementStart = months.split('/')[0];
+            month.incrementIncrement = months.split('/')[1];
+            break;
+          case months.includes(','):
+            month.cronEvery = '3';
+            month.specificSpecific = months.split(',').map(Number).sort();
+            break;
+          case months.includes('-'):
+            month.cronEvery = '4';
+            month.rangeStart = months.split('-')[0];
+            month.rangeEnd = months.split('-')[1];
+            break;
+          default:
+            month.cronEvery = '1';
+        }
+        this.result.month = month;
+      },
+      yearReverseExp(cron) {
+        let years = cron.split(" ")[6];
+        let year = {
+          cronEvery:'',
+          incrementStart:3,
+          incrementIncrement:5,
+          rangeStart:2019,
+          rangeEnd:2019,
+          specificSpecific:[],
+        };
+        switch (true){
+          case years.includes('*'):
+            year.cronEvery = '1';
+            break;
+          case years.includes('/'):
+            year.cronEvery = '2';
+            year.incrementStart = years.split('/')[0];
+            year.incrementIncrement = years.split('/')[1];
+            break;
+          case years.includes(','):
+            year.cronEvery = '3';
+            year.specificSpecific = years.split(',').map(Number).sort();
+            break;
+          case years.includes('-'):
+            year.cronEvery = '4';
+            year.rangeStart = years.split('-')[0];
+            year.rangeEnd = years.split('-')[1];
+            break;
+          default:
+            year.cronEvery = '1';
+        }
+        this.result.year = year;
+      }
+    }
+  }
+</script>
+
+<style lang="scss">
+  .card-container {
+    background: #fff;
+    overflow: hidden;
+    padding: 12px;
+    position: relative;
+    width: 100%;
+    .ant-tabs{
+      border:1px solid #e6ebf5;
+      padding: 0;
+      .ant-tabs-bar {
+        margin: 0;
+        outline: none;
+        border-bottom: none;
+        .ant-tabs-nav-container{
+          margin: 0;
+          .ant-tabs-tab {
+            padding: 0 24px!important;
+            background-color: #f5f7fa!important;
+            margin-right: 0px!important;
+            border-radius: 0;
+            line-height: 38px;
+            border: 1px solid transparent!important;
+            border-bottom: 1px solid #e6ebf5!important;
+          }
+          .ant-tabs-tab-active.ant-tabs-tab{
+            color: #409eff;
+            background-color: #fff!important;
+            border-right:1px solid #e6ebf5!important;
+            border-left:1px solid #e6ebf5!important;
+            border-bottom:1px solid #fff!important;
+            font-weight: normal;
+            transition:none!important;
+          }
+        }
+      }
+      .ant-tabs-tabpane{
+        padding: 15px;
+        .ant-row{
+          margin: 10px 0;
+        }
+        .ant-select,.ant-input-number{
+          width: 100px;
+        }
+      }
+    }
+  }
+</style>
+<style lang="scss" scoped>
+  .container-widthEn{
+    width: 755px;
+  }
+  .container-widthCn{
+    width: 608px;
+  }
+  .language{
+    text-align: center;
+    position: absolute;
+    right: 13px;
+    top: 13px;
+    border: 1px solid transparent;
+    height: 40px;
+    line-height: 38px;
+    font-size: 16px;
+    color: #409eff;
+    z-index: 1;
+    background: #f5f7fa;
+    outline: none;
+    width: 47px;
+    border-bottom: 1px solid #e6ebf5;
+    border-radius: 0;
+  }
+  .card-container{
+    .bottom{
+      display: flex;
+      justify-content: center;
+      padding: 10px 0 0 0;
+      .cronButton{
+        margin: 0 10px;
+        line-height: 40px;
+      }
+    }
+  }
+  .tabBody{
+    .a-row{
+      margin: 10px 0;
+      .long{
+        .a-select{
+          width:354px;
+        }
+      }
+      .a-input-number{
+        width: 110px;
+      }
+    }
+  }
+</style>

+ 10 - 0
ant-design-vue-jeecg/src/components/jeecgbiz/modal/JSelectDepartModal.vue

@@ -58,6 +58,16 @@
     watch:{
       departId(){
         this.initDepartComponent()
+      },
+      visible: {
+        handler() {
+          if (this.departId) {
+            this.checkedKeys = this.departId.split(",");
+            console.log('this.departId', this.departId)
+          } else {
+            this.checkedKeys = [];
+          }
+        }
       }
     },
     methods:{

+ 18 - 1
ant-design-vue-jeecg/src/components/menu/SideMenu.vue

@@ -148,11 +148,28 @@
 <style lang="scss">
   .ant-menu.ant-menu-root {
     & > .ant-menu-item:first-child {
-      background-color: white;
+      background-color: transparent;
 
       & > a, & > a:hover {
         color: rgba(0, 0, 0, 0.65);
+      }
+
+      &.ant-menu-item-selected {
+        & > a, & > a:hover {
+          color: #1890ff;
+        }
+      }
+    }
 
+    &.ant-menu-dark > .ant-menu-item:first-child {
+      & > a, & > a:hover {
+        color: rgba(255, 255, 255, 0.65);
+      }
+
+      &.ant-menu-item-selected {
+        & > a, & > a:hover {
+          color: rgba(255, 255, 255, 1);
+        }
       }
     }
   }

+ 4 - 3
ant-design-vue-jeecg/src/components/setting/SettingDrawer.vue

@@ -153,9 +153,10 @@
           </a-alert>
         </div>
       </div>
-      <div class="setting-drawer-index-handle" @click="toggle">
-        <a-icon type="setting" v-if="!visible"/>
-        <a-icon type="close" v-else/>
+      <div class="setting-drawer-index-handle" @click="toggle" v-if="visible">
+<!--        <a-icon type="setting" v-if="!visible"/>-->
+<!--        <a-icon type="close" v-else/>-->
+        <a-icon type="close" />
       </div>
     </a-drawer>
   </div>

+ 10 - 2
ant-design-vue-jeecg/src/components/tools/DepartSelect.vue

@@ -1,6 +1,6 @@
 <template>
   <a-modal
-    :title="title"
+    :title="currTitle"
     :width="450"
     :visible="visible"
     :closable="false"
@@ -41,6 +41,9 @@
 
 <script>
   import { getAction,putAction } from '@/api/manage'
+  import Vue from 'vue'
+  import store from '@/store/'
+  import { USER_INFO } from "@/store/mutation-types"
 
   export default {
     name: 'DepartSelect',
@@ -70,6 +73,7 @@
     },
     data(){
       return {
+        currTitle:this.title,
         visible:false,
         departList:[],
         departSelected:"",
@@ -100,7 +104,7 @@
               this.departSelected = orgCode
               this.departList  = departs
               if(this.currDepartName){
-                this.title ="部门切换(当前部门 : "+this.currDepartName+")"
+                this.currTitle ="部门切换(当前部门 : "+this.currDepartName+")"
               }
 
             }
@@ -122,6 +126,10 @@
         }
         putAction("/sys/selectDepart",obj).then(res=>{
           if(res.success){
+            const userInfo = res.result.userInfo;
+            Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000);
+            store.commit('SET_INFO', userInfo);
+            //console.log("---切换组织机构---userInfo-------",store.getters.userInfo.orgCode);
             this.departClear()
           }
         })

+ 93 - 6
ant-design-vue-jeecg/src/components/tools/HeaderNotice.vue

@@ -78,6 +78,7 @@
 <script>
   import { getAction,putAction } from '@/api/manage'
   import ShowAnnouncement from './ShowAnnouncement'
+  import store from '@/store/'
 
   export default {
     name: "HeaderNotice",
@@ -89,7 +90,8 @@
         loadding: false,
         url:{
           listCementByUser:"/sys/annountCement/listByUser",
-          editCementSend:"/system/sysAnnouncementSend/editByAnntIdAndUserId",
+          editCementSend:"/sys/sysAnnouncementSend/editByAnntIdAndUserId",
+          queryById:"/sys/annountCement/queryById",
         },
         hovered: false,
         announcement1:[],
@@ -98,6 +100,7 @@
         msg2Count:"0",
         msg1Title:"通知(3)",
         msg2Title:"",
+        stopTimer:false,
       }
     },
     computed:{
@@ -105,15 +108,25 @@
         return parseInt(this.msg1Count)+parseInt(this.msg2Count);
       }
     },
-    created() {
+    mounted() {
       this.loadData();
-      this.timer();
+      //this.timerFun();
+      this.initWebSocket();
+    },
+    destroyed: function () { // 离开页面生命周期函数
+      this.websocketclose();
     },
     methods: {
-      timer() {
-        return setInterval(()=>{
+      timerFun() {
+        this.stopTimer = false;
+        let myTimer = setInterval(()=>{
+          // 停止定时器
+          if (this.stopTimer == true) {
+            clearInterval(myTimer);
+            return;
+          }
           this.loadData()
-        },60000)
+        },6000)
       },
       loadData (){
         try {
@@ -127,8 +140,14 @@
               this.msg2Count = res.result.sysMsgTotal;
               this.msg2Title = "系统消息(" + res.result.sysMsgTotal + ")";
             }
+          }).catch(error => {
+            console.log("系统消息通知异常",error);//这行打印permissionName is undefined
+            this.stopTimer = true;
+            console.log("清理timer");
           });
         } catch (err) {
+          this.stopTimer = true;
+          console.log("通知异常",err);
         }
       },
       fetchNotice () {
@@ -163,6 +182,74 @@
         this.hovered = visible;
       },
 
+      initWebSocket: function () {
+        // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
+        var userId = store.getters.userInfo.id;
+        var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://")+"/websocket/"+userId;
+        //console.log(url);
+        this.websock = new WebSocket(url);
+        this.websock.onopen = this.websocketonopen;
+        this.websock.onerror = this.websocketonerror;
+        this.websock.onmessage = this.websocketonmessage;
+        this.websock.onclose = this.websocketclose;
+      },
+      websocketonopen: function () {
+        console.log("WebSocket连接成功");
+      },
+      websocketonerror: function (e) {
+        console.log("WebSocket连接发生错误");
+      },
+      websocketonmessage: function (e) {
+        console.log("-----接收消息-------",e.data);
+        var data = eval("(" + e.data + ")"); //解析对象
+        this.loadData();
+        //if(data.cmd == "topic"){
+          //系统通知
+            this.openNotification(data);
+        //}else if(data.cmd == "user"){
+          //用户消息
+        //  this.openNotification(data);
+        //}
+
+
+      },
+      websocketclose: function (e) {
+        console.log("connection closed (" + e.code + ")");
+      },
+
+      openNotification (data) {
+        var text = data.msgTxt;
+        const key = `open${Date.now()}`;
+        this.$notification.open({
+          message: '消息提醒',
+          placement:'bottomRight',
+          description: text,
+          key,
+          btn: (h)=>{
+            return h('a-button', {
+              props: {
+                type: 'primary',
+                size: 'small',
+              },
+              on: {
+                click: () => this.showDetail(key,data)
+              }
+            }, '查看详情')
+          },
+        });
+      },
+
+      showDetail(key,data){
+        this.$notification.close(key);
+        var id = data.msgId;
+        getAction(this.url.queryById,{id:id}).then((res) => {
+          if (res.success) {
+            var record = res.result;
+            this.showAnnouncement(record);
+          }
+        })
+
+      },
     }
   }
 </script>

+ 9 - 1
ant-design-vue-jeecg/src/components/tools/Logo.vue

@@ -1,15 +1,23 @@
 <template>
   <div class="logo">
     <router-link :to="{name:'dashboard'}">
-      <img src="~@/assets/logo.svg" alt="logo">
+
+      <!-- update-begin- author:sunjianlei --- date:20190814 --- for: logo颜色根据主题颜色变化 -->
+      <img v-if="navTheme === 'dark'" src="~@/assets/logo-white.png" alt="logo">
+      <img v-else src="~@/assets/logo.svg" alt="logo">
+      <!-- update-begin- author:sunjianlei --- date:20190814 --- for: logo颜色根据主题颜色变化 -->
+
       <h1 v-if="showTitle">{{ title }}</h1>
     </router-link>
   </div>
 </template>
 
 <script>
+  import { mixin } from '@/utils/mixin.js'
+
   export default {
     name: 'Logo',
+    mixins: [mixin],
     props: {
       title: {
         type: String,

+ 73 - 29
ant-design-vue-jeecg/src/components/tools/ShowAnnouncement.vue

@@ -1,20 +1,23 @@
 <template>
   <a-modal
-    width="60%"
+    class="announcementCustomModal"
+    :width="modelStyle.width"
     :visible="visible"
     :bodyStyle ="bodyStyle"
     @cancel="handleCancel"
     destroyOnClose
     :footer="null">
-
-    <a-card style="width: 100%;height: 100%" class="daily-article" :loading="loading">
+    <template slot="title">
+      <a-button icon="fullscreen" class="custom-btn" @click="handleClickToggleFullScreen"/>
+    </template>
+    <a-card class="daily-article" :loading="loading">
       <a-card-meta
         :title="record.titile"
-        :description="'发布人:'+record.sender + ' 发布时间: ' + record.sendTime"/>
+        :description="'发布人:'+record.sender + ' 发布时间: ' + record.sendTime">
+      </a-card-meta>
       <a-divider />
       <span v-html="record.msgContent" class="article-content"></span>
     </a-card>
-
   </a-modal>
 </template>
 
@@ -40,8 +43,14 @@
         bodyStyle:{
           padding: "0",
           height:(window.innerHeight*0.8)+"px",
-          "overflow-y":"auto"
+          "overflow-y":"auto",
+
         },
+        modelStyle:{
+          width: '60%',
+          style: { top: '20px' },
+          fullScreen: false
+        }
       }
     },
     created () {
@@ -54,33 +63,68 @@
       handleCancel () {
         this.visible = false;
       },
+      /** 切换全屏显示 */
+      handleClickToggleFullScreen() {
+        let mode = !this.modelStyle.fullScreen
+        if (mode) {
+          this.modelStyle.width = '100%'
+          this.modelStyle.style.top = '20px'
+        } else {
+          this.modelStyle.width = '60%'
+          this.modelStyle.style.top = '50px'
+        }
+        this.modelStyle.fullScreen = mode
+      }
     }
   }
 </script>
 
+<style lang="less">
+  .announcementCustomModal{
+    .ant-modal-header {
+      border: none;
+      display: inline-block;
+      position: absolute;
+      z-index: 1;
+      right: 56px;
+      padding: 0;
+      .ant-modal-title{
+        .custom-btn{
+          width: 56px;
+          height: 56px;
+          border: none;
+          box-shadow: none;
+        }
+      }
+    }
+    .daily-article{
+      border-bottom: 0;
+    }
+  }
+</style>
 <style scoped lang="less">
   .daily-article {
-  .article-button {
-    font-size: 1.2rem !important;
-  }
-  .ant-card-body {
-    padding: 18px !important;
-  }
-  .ant-card-head {
-    padding: 0 1rem;
-  }
-  .ant-card-meta {
-    margin-bottom: 1rem;
-  }
-  .article-content {
-  p {
-    word-wrap: break-word;
-    word-break: break-all;
-    text-overflow: initial;
-    white-space: normal;
-    font-size: .9rem !important;
-    margin-bottom: .8rem;
-  }
-  }
+    .article-button {
+      font-size: 1.2rem !important;
+    }
+    .ant-card-body {
+      padding: 18px !important;
+    }
+    .ant-card-head {
+      padding: 0 1rem;
+    }
+    .ant-card-meta {
+      margin-bottom: 1rem;
+    }
+    .article-content {
+      p {
+        word-wrap: break-word;
+        word-break: break-all;
+        text-overflow: initial;
+        white-space: normal;
+        font-size: .9rem !important;
+        margin-bottom: .8rem;
+      }
+    }
   }
-</style>
+</style>

+ 13 - 3
ant-design-vue-jeecg/src/components/tools/UserMenu.vue

@@ -24,11 +24,15 @@
             <span>账户设置</span>
           </router-link>
         </a-menu-item>
-        <a-menu-item key="2" @click="updatePassword">
+        <a-menu-item key="3"  @click="systemSetting">
+           <a-icon type="tool"/>
+           <span>系统设置</span>
+        </a-menu-item>
+        <a-menu-item key="4" @click="updatePassword">
           <a-icon type="setting"/>
           <span>密码修改</span>
         </a-menu-item>
-        <a-menu-item key="3" @click="updateCurrentDepart">
+        <a-menu-item key="5" @click="updateCurrentDepart">
           <a-icon type="cluster"/>
           <span>切换部门</span>
         </a-menu-item>
@@ -53,12 +57,14 @@
     </span>
     <user-password ref="userPassword"></user-password>
     <depart-select ref="departSelect" :closable="true" title="部门切换"></depart-select>
+    <setting-drawer ref="settingDrawer" :closable="true" title="系统设置"></setting-drawer>
   </div>
 </template>
 
 <script>
   import HeaderNotice from './HeaderNotice'
   import UserPassword from './UserPassword'
+  import SettingDrawer from "@/components/setting/SettingDrawer";
   import DepartSelect from './DepartSelect'
   import { mapActions, mapGetters } from 'vuex'
   import { mixinDevice } from '@/utils/mixin.js'
@@ -69,7 +75,8 @@
     components: {
       HeaderNotice,
       UserPassword,
-      DepartSelect
+      DepartSelect,
+      SettingDrawer
     },
     props: {
       theme: {
@@ -112,6 +119,9 @@
       },
       updateCurrentDepart(){
         this.$refs.departSelect.show()
+      },
+      systemSetting(){
+        this.$refs.settingDrawer.showDrawer()
       }
     }
   }

+ 0 - 24
ant-design-vue-jeecg/src/config/router.config.js

@@ -323,30 +323,6 @@ export const constantRouterMap = [
     ]
   },
 
-  // {
-  //   path: '/',
-  //   name: 'index',
-  //   component: TabLayout,
-  //   meta: {title: '首页'},
-  //   redirect: '/dashboard/workplace',
-  //   children: [
-  //     {
-  //       path: '/online',
-  //       name: 'online',
-  //       redirect: '/online',
-  //       component: RouteView,
-  //       meta: {title: '在线开发', icon: 'dashboard', permission: ['dashboard']},
-  //       children: [
-  //         {
-  //           path: '/online/auto/:code',
-  //           name: 'report',
-  //           component: () => import('@/views/modules/online/cgreport/OnlCgreportAutoList')
-  //         },
-  //       ]
-  //     },
-  //   ]
-  // },
-
   {
     path: '/test',
     component: BlankLayout,

+ 3 - 0
ant-design-vue-jeecg/src/main.js

@@ -18,6 +18,9 @@ import VueApexCharts from 'vue-apexcharts'
 
 import preview from 'vue-photo-preview'
 import 'vue-photo-preview/dist/skin.css'
+import "@jeecg/antd-onine"
+import '@jeecg/antd-onine/dist/OnlineForm.css'
+
 
 import {
   ACCESS_TOKEN,

+ 28 - 0
ant-design-vue-jeecg/src/store/modules/user.js

@@ -3,6 +3,7 @@ import { login, logout, phoneLogin } from "@/api/login"
 import { ACCESS_TOKEN, USER_NAME,USER_INFO,USER_AUTH,SYS_BUTTON_AUTH } from "@/store/mutation-types"
 import { welcome } from "@/utils/util"
 import { queryPermissionsByUser } from '@/api/api'
+import { getAction } from '@/api/manage'
 
 const user = {
   state: {
@@ -36,6 +37,30 @@ const user = {
   },
 
   actions: {
+    // CAS验证登录
+    ValidateLogin({ commit }, userInfo) {
+      return new Promise((resolve, reject) => {
+        getAction("/cas/client/validateLogin",userInfo).then(response => {
+          console.log("----cas 登录--------",response);
+          if(response.success){
+            const result = response.result
+            const userInfo = result.userInfo
+            Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
+            Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
+            Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
+            commit('SET_TOKEN', result.token)
+            commit('SET_INFO', userInfo)
+            commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname, welcome: welcome() })
+            commit('SET_AVATAR', userInfo.avatar)
+            resolve(response)
+          }else{
+            resolve(response)
+          }
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
     // 登录
     Login({ commit }, userInfo) {
       return new Promise((resolve, reject) => {
@@ -115,6 +140,9 @@ const user = {
         Vue.ls.remove(ACCESS_TOKEN)
         //console.log('logoutToken: '+ logoutToken)
         logout(logoutToken).then(() => {
+          //var sevice = "http://"+window.location.host+"/";
+          //var serviceUrl = encodeURIComponent(sevice);
+          //window.location.href = window._CONFIG['casPrefixUrl']+"/logout?service="+serviceUrl;
           resolve()
         }).catch(() => {
           resolve()

+ 5 - 3
ant-design-vue-jeecg/src/utils/request.js

@@ -78,9 +78,11 @@ service.interceptors.request.use(config => {
     config.headers[ 'X-Access-Token' ] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
   }
   if(config.method=='get'){
-    config.params = {
-      _t: Date.parse(new Date())/1000,
-      ...config.params
+    if(config.url.indexOf("sys/dict/getDictItems")<0){
+      config.params = {
+        _t: Date.parse(new Date())/1000,
+        ...config.params
+      }
     }
   }
   return config

+ 19 - 0
ant-design-vue-jeecg/src/utils/util.js

@@ -232,4 +232,23 @@ export function showDealBtn(bpmStatus){
     return true;
   }
   return false;
+}
+
+/**
+ * 增强CSS,可以在页面上输出全局css
+ * @param css 要增强的css
+ * @param id style标签的id,可以用来清除旧样式
+ */
+export function cssExpand(css, id) {
+  let style = document.createElement('style')
+  style.type = "text/css"
+  style.innerHTML = `@charset "UTF-8"; ${css}`
+  // 清除旧样式
+  if (id) {
+    let $style = document.getElementById(id)
+    if ($style != null) $style.outerHTML = ''
+    style.id = id
+  }
+  // 应用新样式
+  document.head.appendChild(style)
 }

+ 4 - 4
ant-design-vue-jeecg/src/views/dashboard/Analysis.vue

@@ -209,10 +209,10 @@
         })
         getVisitInfo().then(res=>{
           if(res.success){
-            console.log("aaaaaa",res.result)
-            this.visitInfo = res.result;
-          }
-        })
+             console.log("aaaaaa",res.result)
+             this.visitInfo = res.result;
+           }
+         })
       },
     }
   }

+ 19 - 289
ant-design-vue-jeecg/src/views/jeecg/JeecgEditableTableExample.vue

@@ -1,307 +1,37 @@
 <template>
-  <div>
-    <a-card :borderd="false">
+  <a-card :bordered="false">
 
+    <a-tabs>
 
-      <a-button @click="handleTableCheck" type="primary">表单验证</a-button>
-      <span style="padding-left:8px;"></span>
-      <a-tooltip placement="top" title="获取值,忽略表单验证" :autoAdjustOverflow="true">
-        <a-button @click="handleTableGet" type="primary">获取值</a-button>
-      </a-tooltip>
-      <span style="padding-left:8px;"></span>
-      <a-tooltip placement="top" title="模拟加载1000条数据" :autoAdjustOverflow="true">
-        <a-button @click="handleTableSet" type="primary">设置值</a-button>
-      </a-tooltip>
+      <a-tab-pane tab="普通列表" key="1">
+        <default-table/>
+      </a-tab-pane>
 
+      <a-tab-pane tab="只读列表" key="2">
+        <read-only-table/>
+      </a-tab-pane>
 
-      <j-editable-table
-        ref="editableTable"
-        :loading="loading"
-        :columns="columns"
-        :dataSource="dataSource"
-        :rowNumber="true"
-        :rowSelection="true"
-        :actionButton="true"
-        style="margin-top: 8px;"
-        @selectRowChange="handleSelectRowChange">
+      <a-tab-pane tab="三级联动" key="3">
+        <three-linkage/>
+      </a-tab-pane>
 
-        <template v-slot:action="props">
-          <a @click="handleDelete(props)">{{ props.text }}</a>
-        </template>
+    </a-tabs>
 
-      </j-editable-table>
-
-
-    </a-card>
-    <br>
-    <a-card title="只读列表">
-      <j-editable-table
-        :columns="columns1"
-        :dataSource="dataSource1"
-        :rowNumber="true"
-        :rowSelection="true"
-        :maxHeight="200"
-        :disabled="true"
-      />
-    </a-card>
-  </div>
+  </a-card>
 </template>
 
 <script>
-  import moment from 'moment'
-  import JEditableTable from '@/components/jeecg/JEditableTable'
-  import { FormTypes } from '@/utils/JEditableTableUtil'
-  import { randomUUID, randomNumber } from '@/utils/util'
+  import DefaultTable from './modules/JEditableTable/DefaultTable'
+  import ReadOnlyTable from './modules/JEditableTable/ReadOnlyTable'
+  import ThreeLinkage from './modules/JEditableTable/ThreeLinkage'
 
   export default {
     name: 'JeecgEditableTableExample',
-    components: {
-      JEditableTable
-    },
+    components: { DefaultTable, ReadOnlyTable, ThreeLinkage },
     data() {
-      return {
-        loading: false,
-        columns: [
-          {
-            title: '字段名称',
-            key: 'dbFieldName',
-            // width: '19%',
-            width: '300px',
-            type: FormTypes.input,
-            defaultValue: '',
-            placeholder: '请输入${title}',
-            validateRules: [
-              {
-                required: true, // 必填
-                message: '请输入${title}' // 显示的文本
-              },
-              {
-                pattern: /^[a-z|A-Z][a-z|A-Z\d_-]{0,}$/, // 正则
-                message: '${title}必须以字母开头,可包含数字、下划线、横杠'
-              }
-            ]
-          },
-          {
-            title: '文件域',
-            key: 'upload',
-            type: FormTypes.upload,
-            // width: '19%',
-            width: '300px',
-            placeholder: '点击上传',
-            token: true,
-            responseName: 'message',
-            action: window._CONFIG['domianURL'] + '/sys/common/upload'
-          },
-          {
-            title: '字段类型',
-            key: 'dbFieldType',
-            // width: '18%',
-            width: '300px',
-            type: FormTypes.select,
-            options: [ // 下拉选项
-              { title: 'String', value: 'string' },
-              { title: 'Integer', value: 'int' },
-              { title: 'Double', value: 'double' },
-              { title: 'Boolean', value: 'boolean' }
-            ],
-            allowInput: true,
-            defaultValue: '',
-            placeholder: '请选择${title}',
-            validateRules: [{ required: true, message: '请选择${title}' }]
-          },
-          {
-            title: '性别(字典)',
-            key: 'sex_dict',
-            width: '300px',
-            type: FormTypes.select,
-            options: [],
-            dictCode: 'sex',
-            placeholder: '请选择${title}',
-            validateRules: [{ required: true, message: '请选择${title}' }]
-          },
-          {
-            title: '多选测试',
-            key: 'multipleSelect',
-            // width: '18%',
-            width: '300px',
-            type: FormTypes.select,
-            props: { 'mode': 'multiple' }, // 支持多选
-            options: [
-              { title: 'String', value: 'string' },
-              { title: 'Integer', value: 'int' },
-              { title: 'Double', value: 'double' },
-              { title: 'Boolean', value: 'boolean' }
-            ],
-            defaultValue: ['int', 'boolean'], // 多个默认项
-            // defaultValue: 'string,double,int', // 也可使用这种方式
-            placeholder: '这里可以多选',
-            validateRules: [{ required: true, message: '请选择${title}' }]
-          },
-          {
-            title: '字段长度',
-            key: 'dbLength',
-            // width: '8%',
-            width: '100px',
-            type: FormTypes.inputNumber,
-            defaultValue: 32,
-            placeholder: '${title}',
-            validateRules: [{ required: true, message: '请输入${title}' }]
-          },
-          {
-            title: '日期',
-            key: 'datetime',
-            // width: '22%',
-            width: '320px',
-            type: FormTypes.datetime,
-            defaultValue: '2019-4-30 14:52:22',
-            placeholder: '请选择${title}',
-            validateRules: [{ required: true, message: '请选择${title}' }]
-          },
-          {
-            title: '可以为空',
-            key: 'isNull',
-            // width: '8%',
-            width: '100px',
-            type: FormTypes.checkbox,
-            customValue: ['Y', 'N'], // true ,false
-            defaultChecked: false
-          },
-          {
-            title: '操作',
-            key: 'action',
-            // width: '8%',
-            width: '100px',
-            type: FormTypes.slot,
-            slotName: 'action',
-            defaultValue: '删除'
-          }
-
-        ],
-        dataSource: [],
-        selectedRowIds: [],
-        // table2
-        columns1: [
-          {
-            title: '输入框',
-            key: 'input',
-            type: FormTypes.input,
-            placeholder: '清输入'
-          },
-          {
-            title: '下拉框',
-            key: 'select',
-            type: FormTypes.select,
-            options: [
-              { title: 'String', value: 'string' },
-              { title: 'Integer', value: 'int' },
-              { title: 'Double', value: 'double' },
-              { title: 'Boolean', value: 'boolean' }
-            ],
-            placeholder: '请选择'
-          },
-          {
-            title: '多选框',
-            key: 'checkbox',
-            type: FormTypes.checkbox,
-            customValue: [true, false]
-          },
-          {
-            title: '日期',
-            key: 'datetime',
-            type: FormTypes.datetime
-          }
-        ],
-        dataSource1: []
-      }
-    },
-    created() {
-
-    },
-    mounted() {
-      this.randomData(23, false)
-      this.dataSource1 = [
-        { input: 'hello', select: 'int', checkbox: true, datetime: '2019-6-17 14:50:48' },
-        { input: 'world', select: 'string', checkbox: false, datetime: '2019-6-16 14:50:48' },
-        { input: 'one', select: 'double', checkbox: true, datetime: '2019-6-17 15:50:48' },
-        { input: 'two', select: 'boolean', checkbox: false, datetime: '2019-6-14 14:50:48' },
-        { input: 'three', select: '', checkbox: false, datetime: '2019-6-13 14:50:48' }
-      ]
+      return {}
     },
-    methods: {
-
-      /** 表单验证 */
-      handleTableCheck() {
-        this.$refs.editableTable.getValues((error) => {
-          if (error === 0) {
-            this.$message.success('验证通过')
-          } else {
-            this.$message.error('验证未通过')
-          }
-        })
-      },
-      /** 获取值,忽略表单验证 */
-      handleTableGet() {
-        this.$refs.editableTable.getValues((error, values) => {
-          console.log('values:', values)
-        }, false)
-        console.log('deleteIds:', this.$refs.editableTable.getDeleteIds())
-
-        this.$message.info('获取值成功,请看控制台输出')
-
-      },
-      /** 模拟加载1000条数据 */
-      handleTableSet() {
-        this.randomData(1000, true)
-      },
-
-      handleSelectRowChange(selectedRowIds) {
-        this.selectedRowIds = selectedRowIds
-      },
-
-      /* 随机生成数据 */
-      randomData(size, loading = false) {
-        if (loading) {
-          this.loading = true
-        }
-
-        let randomDatetime = () => {
-          let time = parseInt(randomNumber(1000, 9999999999999))
-          return moment(new Date(time)).format('YYYY-MM-DD HH:mm:ss')
-        }
-
-        let begin = Date.now()
-        let values = []
-        for (let i = 0; i < size; i++) {
-          values.push({
-            id: randomUUID(),
-            dbFieldName: `name_${i + 1}`,
-            // dbFieldTxt: randomString(10),
-            multipleSelect: ['string', ['int', 'double', 'boolean'][randomNumber(0, 2)]],
-            dbFieldType: ['string', 'int', 'double', 'boolean'][randomNumber(0, 3)],
-            dbLength: randomNumber(0, 233),
-            datetime: randomDatetime(),
-            isNull: ['Y', 'N'][randomNumber(0, 1)]
-          })
-        }
-
-        this.dataSource = values
-        let end = Date.now()
-        let diff = end - begin
-
-        if (loading && diff < size) {
-          setTimeout(() => {
-            this.loading = false
-          }, size - diff)
-        }
-
-      },
-
-      handleDelete(props) {
-        let { rowId, target } = props
-        target.removeRows(rowId)
-      }
-
-    }
+    methods: {}
   }
 </script>
 

+ 11 - 18
ant-design-vue-jeecg/src/views/jeecg/SelectDemo.vue

@@ -49,7 +49,7 @@
         <a-row :gutter="24">
           <a-col :span="12">
             <a-form-item label="选择部门">
-              <j-select-depart v-model="departId"></j-select-depart>
+              <j-select-depart v-model="departId" :multi="true"></j-select-depart>
             </a-form-item>
           </a-col>
           <a-col :span="12">选中的部门ID(v-model):{{ departId }}</a-col>
@@ -217,10 +217,8 @@
         <a-row :gutter="24">
           <a-col :span="12">
             <a-form-item label="cron表达式">
-              <a-input @click="openModal" placeholder="corn表达式" v-model="cron.label" readOnly >
-                <a-icon slot="prefix" type="schedule" title="corn控件"/>
-              </a-input>
-              <VueCron ref="innerVueCron" :data="cron" @change="changeCron" ></VueCron>
+              <j-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?'}]"  @change="setCorn"></j-cron>
+              <!--              <j-cron ref="innerVueCron" v-model="cron" @change="setCorn"></j-cron>-->
             </a-form-item>
           </a-col>
         </a-row>
@@ -245,7 +243,7 @@
   import JSlider from '@/components/jeecg/JSlider'
   import JSelectMultiple from '@/components/jeecg/JSelectMultiple'
   import JTreeDict from "../../components/jeecg/JTreeDict.vue";
-  import VueCron from "./modules/VueCronModal.vue";
+  import JCron from "@/components/jeecg/JCron.vue";
   export default {
     name: 'SelectDemo',
     components: {
@@ -257,7 +255,7 @@
       JCheckbox,
       JCodeEditor,
       JDate, JEditor, JEllipsis, JGraphicCode, JSlider, JSelectMultiple,
-      VueCron
+      JCron
     },
     data() {
       return {
@@ -314,10 +312,7 @@ sayHi('hello, world!')`
           style: { top: '20px' },
           fullScreen: true
         },
-        cron: {
-          label: '',
-          value: {}
-        }
+        cron: '',
       }
     },
     computed: {
@@ -372,14 +367,12 @@ sayHi('hello, world!')`
         }
         this.modal.fullScreen = mode
       },
-      openModal(){
-        this.$refs.innerVueCron.show()
-      },
-      changeCron(val){
-        this.cron=val;
-        console.log(val);
-      }
+      setCorn(data){
 
+        this.$nextTick(() => {
+          this.form.cronExpression = data;
+        })
+      }
     }
   }
 </script>

+ 91 - 0
ant-design-vue-jeecg/src/views/jeecg/TableTotal.vue

@@ -0,0 +1,91 @@
+<template>
+  <a-card :bordered="false">
+    <a-table
+      rowKey="id"
+      bordered
+      :columns="columns"
+      :dataSource="dataSource"
+      :pagination="false"
+    >
+    </a-table>
+  </a-card>
+</template>
+
+<script>
+  export default {
+    name: 'TableTotal',
+    data() {
+      return {
+        columns: [
+          {
+            title: '#',
+            width: '180px',
+            align: 'center',
+            dataIndex: 'rowIndex',
+            customRender: function (text, r, index) {
+              return (text !== '合计') ? (parseInt(index) + 1) : text
+            }
+          },
+          {
+            title: '姓名',
+            dataIndex: 'name',
+          },
+          {
+            title: '贡献点',
+            dataIndex: 'point',
+          },
+          {
+            title: '等级',
+            dataIndex: 'level',
+          },
+          {
+            title: '更新时间',
+            dataIndex: 'updateTime',
+          },
+        ],
+        dataSource: [
+          { name: '张三', point: 23, level: 3, updateTime: '2019-8-14' },
+          { name: '小王', point: 6, level: 1, updateTime: '2019-8-13' },
+          { name: '李四', point: 53, level: 8, updateTime: '2019-8-12' },
+          { name: '小红', point: 44, level: 5, updateTime: '2019-8-11' },
+          { name: '王五', point: 97, level: 10, updateTime: '2019-8-10' },
+          { name: '小明', point: 33, level: 2, updateTime: '2019-8-10' },
+        ]
+      }
+    },
+    mounted() {
+      this.tableAddTotalRow(this.columns, this.dataSource)
+    },
+    methods: {
+
+      /** 表格增加合计行 */
+      tableAddTotalRow(columns, dataSource) {
+        let numKey = 'rowIndex'
+        let totalRow = { [numKey]: '合计' }
+        columns.forEach(column => {
+          let { key, dataIndex } = column
+          if (![key, dataIndex].includes(numKey)) {
+
+            let total = 0
+            dataSource.forEach(data => {
+              total += /^\d+\.?\d?$/.test(data[dataIndex]) ? Number.parseInt(data[dataIndex]) : Number.NaN
+              console.log(data[dataIndex], ':', (/^\d+\.?\d?$/.test(data[dataIndex]) ? Number.parseInt(data[dataIndex]) : Number.NaN))
+            })
+
+            if (Number.isNaN(total)) {
+              total = '-'
+            }
+            totalRow[dataIndex] = total
+          }
+        })
+
+        dataSource.push(totalRow)
+      }
+
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 250 - 0
ant-design-vue-jeecg/src/views/jeecg/modules/JEditableTable/DefaultTable.vue

@@ -0,0 +1,250 @@
+<template>
+
+  <div>
+    <a-button @click="handleTableCheck" type="primary">表单验证</a-button>
+    <span style="padding-left:8px;"></span>
+    <a-tooltip placement="top" title="获取值,忽略表单验证" :autoAdjustOverflow="true">
+      <a-button @click="handleTableGet" type="primary">获取值</a-button>
+    </a-tooltip>
+    <span style="padding-left:8px;"></span>
+    <a-tooltip placement="top" title="模拟加载1000条数据" :autoAdjustOverflow="true">
+      <a-button @click="handleTableSet" type="primary">设置值</a-button>
+    </a-tooltip>
+
+
+    <j-editable-table
+      ref="editableTable"
+      :loading="loading"
+      :columns="columns"
+      :dataSource="dataSource"
+      :rowNumber="true"
+      :rowSelection="true"
+      :actionButton="true"
+      :dragSort="true"
+      style="margin-top: 8px;"
+      @selectRowChange="handleSelectRowChange">
+
+      <template v-slot:action="props">
+        <a @click="handleDelete(props)">{{ props.text }}</a>
+      </template>
+
+    </j-editable-table>
+  </div>
+
+</template>
+
+<script>
+  import moment from 'moment'
+  import { FormTypes } from '@/utils/JEditableTableUtil'
+  import { randomUUID, randomNumber } from '@/utils/util'
+  import JEditableTable from '@/components/jeecg/JEditableTable'
+
+  export default {
+    name: 'DefaultTable',
+    components: { JEditableTable },
+    data() {
+      return {
+        loading: false,
+        columns: [
+          {
+            title: '字段名称',
+            key: 'dbFieldName',
+            // width: '19%',
+            width: '300px',
+            type: FormTypes.input,
+            defaultValue: '',
+            placeholder: '请输入${title}',
+            validateRules: [
+              {
+                required: true, // 必填
+                message: '请输入${title}' // 显示的文本
+              },
+              {
+                pattern: /^[a-z|A-Z][a-z|A-Z\d_-]{0,}$/, // 正则
+                message: '${title}必须以字母开头,可包含数字、下划线、横杠'
+              }
+            ]
+          },
+          {
+            title: '文件域',
+            key: 'upload',
+            type: FormTypes.upload,
+            // width: '19%',
+            width: '300px',
+            placeholder: '点击上传',
+            token: true,
+            responseName: 'message',
+            action: window._CONFIG['domianURL'] + '/sys/common/upload'
+          },
+          {
+            title: '字段类型',
+            key: 'dbFieldType',
+            // width: '18%',
+            width: '300px',
+            type: FormTypes.select,
+            options: [ // 下拉选项
+              { title: 'String', value: 'string' },
+              { title: 'Integer', value: 'int' },
+              { title: 'Double', value: 'double' },
+              { title: 'Boolean', value: 'boolean' }
+            ],
+            allowInput: true,
+            defaultValue: '',
+            placeholder: '请选择${title}',
+            validateRules: [{ required: true, message: '请选择${title}' }]
+          },
+          {
+            title: '性别(字典)',
+            key: 'sex_dict',
+            width: '300px',
+            type: FormTypes.select,
+            options: [],
+            dictCode: 'sex',
+            placeholder: '请选择${title}',
+            validateRules: [{ required: true, message: '请选择${title}' }]
+          },
+          {
+            title: '多选测试',
+            key: 'multipleSelect',
+            // width: '18%',
+            width: '300px',
+            type: FormTypes.select,
+            props: { 'mode': 'multiple' }, // 支持多选
+            options: [
+              { title: 'String', value: 'string' },
+              { title: 'Integer', value: 'int' },
+              { title: 'Double', value: 'double' },
+              { title: 'Boolean', value: 'boolean' }
+            ],
+            defaultValue: ['int', 'boolean'], // 多个默认项
+            // defaultValue: 'string,double,int', // 也可使用这种方式
+            placeholder: '这里可以多选',
+            validateRules: [{ required: true, message: '请选择${title}' }]
+          },
+          {
+            title: '字段长度',
+            key: 'dbLength',
+            // width: '8%',
+            width: '100px',
+            type: FormTypes.inputNumber,
+            defaultValue: 32,
+            placeholder: '${title}',
+            validateRules: [{ required: true, message: '请输入${title}' }]
+          },
+          {
+            title: '日期',
+            key: 'datetime',
+            // width: '22%',
+            width: '320px',
+            type: FormTypes.datetime,
+            defaultValue: '2019-4-30 14:52:22',
+            placeholder: '请选择${title}',
+            validateRules: [{ required: true, message: '请选择${title}' }]
+          },
+          {
+            title: '可以为空',
+            key: 'isNull',
+            // width: '8%',
+            width: '100px',
+            type: FormTypes.checkbox,
+            customValue: ['Y', 'N'], // true ,false
+            defaultChecked: false
+          },
+          {
+            title: '操作',
+            key: 'action',
+            // width: '8%',
+            width: '100px',
+            type: FormTypes.slot,
+            slotName: 'action',
+            defaultValue: '删除'
+          }
+
+        ],
+        dataSource: [],
+        selectedRowIds: []
+      }
+    },
+    mounted() {
+      this.randomData(23, false)
+    },
+    methods: {
+
+      /** 表单验证 */
+      handleTableCheck() {
+        this.$refs.editableTable.getValues((error) => {
+          if (error === 0) {
+            this.$message.success('验证通过')
+          } else {
+            this.$message.error('验证未通过')
+          }
+        })
+      },
+      /** 获取值,忽略表单验证 */
+      handleTableGet() {
+        this.$refs.editableTable.getValues((error, values) => {
+          console.log('values:', values)
+        }, false)
+        console.log('deleteIds:', this.$refs.editableTable.getDeleteIds())
+
+        this.$message.info('获取值成功,请看控制台输出')
+
+      },
+      /** 模拟加载1000条数据 */
+      handleTableSet() {
+        this.randomData(1000, true)
+      },
+
+      handleSelectRowChange(selectedRowIds) {
+        this.selectedRowIds = selectedRowIds
+      },
+
+      /* 随机生成数据 */
+      randomData(size, loading = false) {
+        if (loading) {
+          this.loading = true
+        }
+
+        let randomDatetime = () => {
+          let time = parseInt(randomNumber(1000, 9999999999999))
+          return moment(new Date(time)).format('YYYY-MM-DD HH:mm:ss')
+        }
+
+        let begin = Date.now()
+        let values = []
+        for (let i = 0; i < size; i++) {
+          values.push({
+            id: randomUUID(),
+            dbFieldName: `name_${i + 1}`,
+            // dbFieldTxt: randomString(10),
+            multipleSelect: ['string', ['int', 'double', 'boolean'][randomNumber(0, 2)]],
+            dbFieldType: ['string', 'int', 'double', 'boolean'][randomNumber(0, 3)],
+            dbLength: randomNumber(0, 233),
+            datetime: randomDatetime(),
+            isNull: ['Y', 'N'][randomNumber(0, 1)]
+          })
+        }
+
+        this.dataSource = values
+        let end = Date.now()
+        let diff = end - begin
+
+        if (loading && diff < size) {
+          setTimeout(() => {
+            this.loading = false
+          }, size - diff)
+        }
+
+      },
+
+      handleDelete(props) {
+        let { rowId, target } = props
+        target.removeRows(rowId)
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 70 - 0
ant-design-vue-jeecg/src/views/jeecg/modules/JEditableTable/ReadOnlyTable.vue

@@ -0,0 +1,70 @@
+<template>
+  <j-editable-table
+    :columns="columns"
+    :dataSource="dataSource"
+    :rowNumber="true"
+    :rowSelection="true"
+    :maxHeight="400"
+    :disabled="true"
+  />
+</template>
+
+<script>
+  import { FormTypes } from '@/utils/JEditableTableUtil'
+  import JEditableTable from '@/components/jeecg/JEditableTable'
+
+  export default {
+    name: 'ReadOnlyTable',
+    components: { JEditableTable },
+    data() {
+      return {
+
+        columns: [
+          {
+            title: '输入框',
+            key: 'input',
+            type: FormTypes.input,
+            placeholder: '清输入'
+          },
+          {
+            title: '下拉框',
+            key: 'select',
+            type: FormTypes.select,
+            options: [
+              { title: 'String', value: 'string' },
+              { title: 'Integer', value: 'int' },
+              { title: 'Double', value: 'double' },
+              { title: 'Boolean', value: 'boolean' }
+            ],
+            placeholder: '请选择'
+          },
+          {
+            title: '多选框',
+            key: 'checkbox',
+            type: FormTypes.checkbox,
+            customValue: [true, false]
+          },
+          {
+            title: '日期',
+            key: 'datetime',
+            type: FormTypes.datetime
+          }
+        ],
+        dataSource: [
+          { input: 'hello', select: 'int', checkbox: true, datetime: '2019-6-17 14:50:48' },
+          { input: 'world', select: 'string', checkbox: false, datetime: '2019-6-16 14:50:48' },
+          { input: 'one', select: 'double', checkbox: true, datetime: '2019-6-17 15:50:48' },
+          { input: 'two', select: 'boolean', checkbox: false, datetime: '2019-6-14 14:50:48' },
+          { input: 'three', select: '', checkbox: false, datetime: '2019-6-13 14:50:48' }
+        ]
+      }
+    },
+    mounted() {
+
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 129 - 0
ant-design-vue-jeecg/src/views/jeecg/modules/JEditableTable/ThreeLinkage.vue

@@ -0,0 +1,129 @@
+<template>
+  <j-editable-table
+    :columns="columns"
+    :dataSource="dataSource"
+    :rowNumber="true"
+    :actionButton="true"
+    :rowSelection="true"
+    :maxHeight="400"
+    @valueChange="handleValueChange"
+  />
+</template>
+
+<script>
+  import { FormTypes } from '@/utils/JEditableTableUtil'
+  import JEditableTable from '@/components/jeecg/JEditableTable'
+
+  export default {
+    name: 'ThreeLinkage',
+    components: { JEditableTable },
+    data() {
+      return {
+        columns: [
+          {
+            title: '省/直辖市/自治区',
+            key: 's1',
+            type: FormTypes.select,
+            width: '240px',
+            options: [],
+            placeholder: '请选择${title}'
+          },
+          {
+            title: '市',
+            key: 's2',
+            type: FormTypes.select,
+            width: '240px',
+            options: [],
+            placeholder: '请选择${title}'
+          },
+          {
+            title: '县/区',
+            key: 's3',
+            type: FormTypes.select,
+            width: '240px',
+            options: [],
+            placeholder: '请选择${title}'
+          }
+        ],
+        dataSource: [],
+
+        mockData: [
+          { label: '北京市', value: '110000', parent: null },
+          { label: '天津市', value: '120000', parent: null },
+          { label: '河北省', value: '130000', parent: null },
+          { label: '上海市', value: '310000', parent: null },
+
+          { label: '北京市', value: '110100', parent: '110000' },
+          { label: '天津市市', value: '120100', parent: '120000' },
+          { label: '石家庄市', value: '130100', parent: '130000' },
+          { label: '唐山市', value: '130200', parent: '130000' },
+          { label: '秦皇岛市', value: '130300', parent: '130000' },
+          { label: '上海市', value: '310100', parent: '310000' },
+
+          { label: '东城区', value: '110101', parent: '110100' },
+          { label: '西城区', value: '110102', parent: '110100' },
+          { label: '朝阳区', value: '110105', parent: '110100' },
+          { label: '和平区', value: '120101', parent: '120000' },
+          { label: '河东区', value: '120102', parent: '120000' },
+          { label: '河西区', value: '120103', parent: '120000' },
+          { label: '黄浦区', value: '310101', parent: '310100' },
+          { label: '徐汇区', value: '310104', parent: '310100' },
+          { label: '长宁区', value: '310105', parent: '310100' },
+          { label: '长安区', value: '130102', parent: '130100' },
+          { label: '桥西区', value: '130104', parent: '130100' },
+          { label: '新华区', value: '130105', parent: '130100' },
+          { label: '路南区', value: '130202', parent: '130200' },
+          { label: '路北区', value: '130203', parent: '130200' },
+          { label: '古冶区', value: '130204', parent: '130200' },
+          { label: '海港区', value: '130302', parent: '130300' },
+          { label: '山海关区', value: '130303', parent: '130300' },
+          { label: '北戴河区', value: '130304', parent: '130300' },
+        ]
+      }
+    },
+    mounted() {
+      // 初始化数据
+      this.columns[0].options = this.request(null)
+    },
+    methods: {
+
+      request(parentId) {
+        return this.mockData.filter(i => i.parent === parentId)
+      },
+
+      /** 当选项被改变时,联动其他组件 */
+      handleValueChange(event) {
+        const { type, row, column, value, target } = event
+
+        if (type === FormTypes.select) {
+
+          // 第一列
+          if (column.key === 's1') {
+            // 设置第二列的 options
+            this.columns[1].options = this.request(value)
+            // 清空后两列的数据
+            target.setValues([{
+              rowKey: row.id,
+              values: { s2: '', s3: '' }
+            }])
+            this.columns[2].options = []
+          } else
+          // 第二列
+          if (column.key === 's2') {
+            this.columns[2].options = this.request(value)
+            target.setValues([{
+              rowKey: row.id,
+              values: { s3: '' }
+            }])
+          }
+        }
+
+      }
+
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 1 - 1
ant-design-vue-jeecg/src/views/jeecg/modules/VueCronModal.vue

@@ -486,7 +486,7 @@
                         days = this.result.day.cronLastSpecificDomDay + 'L';
                         break;
                     case '9':
-                        days = 'L-' + this.day.cronDaysBeforeEomMinus;
+                        days = 'L-' + this.result.day.cronDaysBeforeEomMinus;
                         break;
                     case '10':
                         days = this.result.day.cronDaysNearestWeekday+"W";

+ 435 - 0
ant-design-vue-jeecg/src/views/modules/online/cgform/OnlCgformHeadList.vue

@@ -0,0 +1,435 @@
+<template>
+  <a-card :bordered="false">
+
+    <!-- 查询区域 -->
+    <div class="table-page-search-wrapper">
+      <a-form layout="inline">
+        <a-row :gutter="24">
+
+          <a-col :md="6" :sm="24">
+            <a-form-item label="表名">
+              <a-input placeholder="请输入表名" v-model="queryParam.tableName"></a-input>
+            </a-form-item>
+          </a-col>
+          <a-col :md="6" :sm="24">
+            <a-form-item label="表类型">
+              <j-dict-select-tag dictCode="cgform_table_type" v-model="queryParam.tableType"/>
+            </a-form-item>
+          </a-col>
+
+          <a-col :md="6" :sm="24">
+            <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+              <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
+              <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
+            </span>
+          </a-col>
+
+        </a-row>
+      </a-form>
+    </div>
+
+    <!-- 操作按钮区域 -->
+    <div class="table-operator">
+      <a-button @click="handleAdd" type="primary" icon="plus">新增</a-button>
+      <a-button @click="doCgformButton" type="primary" icon="highlight" style="margin-left:8px">自定义按钮</a-button>
+      <a-button @click="doEnhanceJs" type="primary" icon="strikethrough" style="margin-left:8px">JS增强</a-button>
+      <a-button @click="doEnhanceSql" type="primary" icon="filter" v-has="'online:sql'" style="margin-left:8px">SQL增强</a-button>
+      <a-button @click="doEnhanceJava" type="primary" icon="tool" style="margin-left:8px">Java增强</a-button>
+      <a-button @click="importOnlineForm" type="primary" icon="database" style="margin-left:8px">从数据库导入表单</a-button>
+      <a-button @click="goGenerateCode" v-has="'online:goGenerateCode'" type="primary" icon="database" style="margin-left:8px">代码生成</a-button>
+
+      <a-dropdown v-if="selectedRowKeys.length > 0">
+        <a-menu slot="overlay">
+          <a-menu-item key="1" @click="batchDel">
+            <a-icon type="delete"/>
+            删除
+          </a-menu-item>
+        </a-menu>
+        <a-button style="margin-left: 8px"> 批量操作
+          <a-icon type="down"/>
+        </a-button>
+      </a-dropdown>
+    </div>
+
+    <!-- table区域-begin -->
+    <div>
+      <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
+        <i class="anticon anticon-info-circle ant-alert-icon"></i>
+        已选择
+        <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>
+        项
+        <a style="margin-left: 24px" @click="onClearSelected">清空</a>
+      </div>
+
+      <a-table
+        ref="table"
+        size="middle"
+        bordered
+        rowKey="id"
+        :columns="columns"
+        :dataSource="dataSource"
+        :pagination="ipagination"
+        :loading="loading"
+        :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
+        @change="handleTableChange">
+
+        <template slot="action" slot-scope="text, record">
+          <a @click="handleEdit(record)">编辑</a>
+
+          <a-divider type="vertical"/>
+          <a-dropdown>
+            <a class="ant-dropdown-link">更多
+              <a-icon type="down"/>
+            </a>
+            <a-menu slot="overlay">
+              <a-menu-item v-if="record.isDbSynch!='Y'">
+                <a @click="openSyncModal(record.id)">同步数据库</a>
+              </a-menu-item>
+
+              <template v-if="record.isDbSynch=='Y' && record.tableType !== 3">
+                <a-menu-item>
+                  <a @click="goPageOnline(record)">功能测试</a>
+                </a-menu-item>
+                <a-menu-item>
+                  <a @click="handleOnlineUrlShow(record)">配置地址</a>
+                </a-menu-item>
+              </template>
+
+              <a-menu-item>
+                <a @click="handleRemoveRecord(record.id)">移除</a>
+              </a-menu-item>
+
+              <a-menu-item>
+                <a @click="handleDelete(record.id)">删除</a>
+              </a-menu-item>
+
+            </a-menu>
+          </a-dropdown>
+        </template>
+
+        <template slot="dbsync" slot-scope="text">
+          <span v-if="text==='Y'" style="color:limegreen">已同步</span>
+          <span v-if="text==='N'" style="color:red">未同步</span>
+        </template>
+
+      </a-table>
+    </div>
+    <!-- table区域-end -->
+
+    <!-- 表单区域 -->
+    <onl-cgform-head-modal ref="modalForm" @ok="modalFormOk"></onl-cgform-head-modal>
+
+    <!-- 同步数据库提示框 -->
+    <a-modal
+      :width="500"
+      :height="300"
+      title="同步数据库"
+      :visible="syncModalVisible"
+      @cancel="handleCancleDbSync"
+      style="top:5%;height: 95%;">
+      <template slot="footer">
+        <a-button @click="handleCancleDbSync">关闭</a-button>
+        <a-button type="primary" :loading="syncLoading" @click="handleDbSync">
+          确定
+        </a-button>
+      </template>
+      <a-radio-group v-model="synMethod">
+        <a-radio style="display: block;width: 30px;height: 30px" value="normal">普通同步(保留表数据)</a-radio>
+        <a-radio style="display: block;width: 30px;height: 30px" value="force">强制同步(删除表,重新生成)</a-radio>
+      </a-radio-group>
+    </a-modal>
+
+    <!-- 提示online报表链接 -->
+    <a-modal
+      :title="onlineUrlTitle"
+      :visible="onlineUrlVisible"
+      @cancel="handleOnlineUrlClose">
+      <template slot="footer">
+        <a-button @click="handleOnlineUrlClose">关闭</a-button>
+        <a-button type="primary" class="copy-this-text" :data-clipboard-text="onlineUrl" @click="onCopyUrl">复制</a-button>
+      </template>
+      <p>{{ onlineUrl }}</p>
+    </a-modal>
+
+    <enhance-js ref="ehjs"></enhance-js>
+    <enhance-sql ref="ehsql"></enhance-sql>
+    <enhance-java ref="ehjava"></enhance-java>
+    <trans-db2-online ref="transd2o" @ok="transOk"></trans-db2-online>
+    <code-generator ref="cg"></code-generator>
+
+    <onl-cgform-button-list ref="btnList"></onl-cgform-button-list>
+  </a-card>
+</template>
+
+<script>
+  import { initDictOptions, filterDictText } from '@/components/dict/JDictSelectUtil'
+  import { deleteAction, postAction } from '@/api/manage'
+  import JDictSelectTag from '../../../../components/dict/JDictSelectTag.vue'
+  import { JeecgListMixin } from '@/mixins/JeecgListMixin'
+  import Clipboard from 'clipboard'
+
+  export default {
+    name: 'OnlCgformHeadList',
+    mixins: [JeecgListMixin],
+    components: {
+      JDictSelectTag,
+    },
+    data() {
+      return {
+        description: 'Online表单开发管理页面',
+        // 表头
+        columns: [
+          {
+            title: '#',
+            dataIndex: '',
+            key: 'rowIndex',
+            width: 60,
+            align: 'center',
+            customRender: function(t, r, index) {
+              return parseInt(index) + 1
+            }
+          },
+          {
+            title: '表类型',
+            align: 'center',
+            dataIndex: 'tableType',
+            customRender: (text) => {
+              return filterDictText(this.tableTypeDictOptions, `${text}`)
+            }
+          },
+          {
+            title: '表名',
+            align: 'center',
+            dataIndex: 'tableName'
+          },
+          {
+            title: '表描述',
+            align: 'center',
+            dataIndex: 'tableTxt'
+          },
+          {
+            title: '版本',
+            align: 'center',
+            dataIndex: 'tableVersion'
+          },
+
+          {
+            title: '同步数据库状态',
+            align: 'center',
+            dataIndex: 'isDbSynch',
+            scopedSlots: { customRender: 'dbsync' }
+          },
+          {
+            title: '操作',
+            dataIndex: 'action',
+            align: 'center',
+            scopedSlots: { customRender: 'action' }
+          }
+        ],
+        url: {
+          list: '/online/cgform/head/list',
+          delete: '/online/cgform/head/delete',
+          deleteBatch: '/online/cgform/head/deleteBatch',
+          doDbSynch: '/online/cgform/api/doDbSynch/',
+          removeRecord: '/online/cgform/head/removeRecord'
+        },
+        tableTypeDictOptions: [],
+        sexDictOptions: [],
+        syncModalVisible: false,
+        syncFormId: '',
+        synMethod: 'normal',
+        syncLoading: false,
+        onlineUrlTitle: '',
+        onlineUrlVisible: false,
+        onlineUrl: '',
+        selectedRowKeys: [],
+        selectedRows: []
+      }
+    },
+    created() {
+      //初始化字典 - 表类型
+      initDictOptions('cgform_table_type').then((res) => {
+        if (res.success) {
+          this.tableTypeDictOptions = res.result
+        }
+      })
+      this.loadData()
+    },
+    methods: {
+      doDbSynch(id) {
+        postAction(this.url.doDbSynch + id, { synMethod: '1' }).then((res) => {
+          if (res.success) {
+            this.$message.success(res.message)
+            this.loadData()
+          } else {
+            this.$message.warning(res.message)
+          }
+        })
+      },
+      handleCancleDbSync() {
+        this.syncModalVisible = false
+      },
+      handleDbSync() {
+        this.syncLoading = true
+        postAction(this.url.doDbSynch + this.syncFormId + '/' + this.synMethod).then((res) => {
+          this.syncModalVisible = false
+          this.syncLoading = false
+          if (res.success) {
+            this.$message.success(res.message)
+            this.loadData()
+          } else {
+            this.$message.warning(res.message)
+          }
+        })
+      },
+      openSyncModal(id) {
+        this.syncModalVisible = true
+        this.syncFormId = id
+      },
+      goPageOnline(rd) {
+        if(rd.isTree=='Y'){
+          this.$router.push({ path: '/online/cgformTreeList/' + rd.id })
+        }else{
+          this.$router.push({ path: '/online/cgformList/' + rd.id })
+        }
+      },
+      handleOnlineUrlClose() {
+        this.onlineUrlTitle = ''
+        this.onlineUrlVisible = false
+      },
+      handleOnlineUrlShow(record) {
+        if(record.isTree=='Y'){
+          this.onlineUrl = `/online/cgformTreeList/${record.id}`
+        }else{
+          this.onlineUrl = `/online/cgformList/${record.id}`
+        }
+        this.onlineUrlVisible = true
+        this.onlineUrlTitle = '菜单链接[' + record.tableTxt + ']'
+      },
+      handleRemoveRecord(id) {
+        let that = this
+        this.$confirm({
+          title: '确认要移除此记录?',
+          onOk() {
+            deleteAction(that.url.removeRecord, { id: id }).then((res) => {
+              if (res.success) {
+                that.$message.success('移除成功')
+                that.loadData()
+              } else {
+                that.$message.warning(res.message)
+              }
+            })
+          },
+          onCancel() {
+          }
+        })
+      },
+      doEnhanceJs() {
+        if (!this.selectedRowKeys || this.selectedRowKeys.length != 1) {
+          this.$message.warning('请先选中一条记录')
+          return
+        }
+        this.$refs.ehjs.show(this.selectedRowKeys[0])
+      },
+      doEnhanceSql() {
+        if (!this.selectedRowKeys || this.selectedRowKeys.length != 1) {
+          this.$message.warning('请先选中一条记录')
+          return
+        }
+        this.$refs.ehsql.show(this.selectedRowKeys[0])
+      },
+      doEnhanceJava() {
+        if (!this.selectedRowKeys || this.selectedRowKeys.length != 1) {
+          this.$message.warning('请先选中一条记录')
+          return
+        }
+        this.$refs.ehjava.show(this.selectedRowKeys[0])
+      },
+      doCgformButton() {
+        if (!this.selectedRowKeys || this.selectedRowKeys.length != 1) {
+          this.$message.warning('请先选中一条记录')
+          return
+        }
+        this.$refs.btnList.show(this.selectedRowKeys[0])
+
+        //this.$router.push({ path: '/online/cgformButton/' + this.selectedRowKeys[0] })
+      },
+      importOnlineForm() {
+        this.$refs.transd2o.show()
+      },
+      transOk() {
+        this.loadData()
+      },
+      goGenerateCode() {
+        if (!this.selectedRowKeys || this.selectedRowKeys.length != 1) {
+          this.$message.warning('请先选中一条记录')
+          return
+        }
+        let row = this.selectedRows[0]
+        if (!row.isDbSynch || row.isDbSynch == 'N') {
+          this.$message.warning('请先同步数据库!')
+          return
+        }
+        if (row.tableType == 3) {
+          this.$message.warning('请选中该表对应的主表生成代码')
+          return
+        }
+        this.$refs.cg.show(this.selectedRowKeys[0])
+      },
+      onSelectChange(keys, rows) {
+        this.selectedRowKeys = keys
+        this.selectedRows = rows
+      },
+      onCopyUrl(){
+        var clipboard = new Clipboard('.copy-this-text')
+        clipboard.on('success', () => {
+          clipboard.destroy()
+          this.$message.success('复制成功')
+          this.handleOnlineUrlClose()
+        })
+        clipboard.on('error', () => {
+          this.$message.error('该浏览器不支持自动复制')
+          clipboard.destroy()
+        })
+      }
+    }
+  }
+</script>
+<style lang="less">
+  .ant-card-body .table-operator {
+    margin-bottom: 18px;
+  }
+
+  .ant-table-tbody .ant-table-row td {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+
+  .anty-row-operator button {
+    margin: 0 5px
+  }
+
+  .ant-btn-danger {
+    background-color: #ffffff
+  }
+
+  .ant-modal-cust-warp {
+    height: 100%
+  }
+
+  .ant-modal-cust-warp .ant-modal-body {
+    height: calc(100% - 110px) !important;
+    overflow-y: auto
+  }
+
+  .ant-modal-cust-warp .ant-modal-content {
+    height: 90% !important;
+    overflow-y: hidden
+  }
+
+  .valid-error-cust{
+    .ant-select-selection{
+      border:2px solid #f5222d;
+    }
+  }
+</style>

+ 619 - 0
ant-design-vue-jeecg/src/views/modules/online/cgform/auto/OnlCgformAutoList.vue

@@ -0,0 +1,619 @@
+<template>
+  <a-card :bordered="false" style="height: 100%">
+    <div class="table-page-search-wrapper">
+      <a-form layout="inline">
+        <a-row :gutter="24" v-if="queryInfo && queryInfo.length>0">
+          <template v-for="(item,index) in queryInfo">
+            <template v-if=" item.hidden==='1' ">
+              <a-col v-if="item.view=='datetime'" :md="12" :sm="16" :key=" 'query'+index " v-show="toggleSearchStatus">
+                <online-query-form-item :queryParam="queryParam" :item="item" :dictOptions="dictOptions"></online-query-form-item>
+              </a-col>
+              <a-col v-else :md="6" :sm="8" :key=" 'query'+index " v-show="toggleSearchStatus">
+                <online-query-form-item :queryParam="queryParam" :item="item" :dictOptions="dictOptions"></online-query-form-item>
+              </a-col>
+            </template>
+            <template v-else>
+              <a-col v-if="item.view=='datetime'" :md="12" :sm="16" :key=" 'query'+index ">
+                <online-query-form-item :queryParam="queryParam" :item="item" :dictOptions="dictOptions"></online-query-form-item>
+              </a-col>
+              <a-col v-else :md="6" :sm="8" :key=" 'query'+index ">
+                <online-query-form-item :queryParam="queryParam" :item="item" :dictOptions="dictOptions"></online-query-form-item>
+              </a-col>
+            </template>
+          </template>
+
+          <a-col :md="6" :sm="8">
+            <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+              <a-button type="primary" @click="searchByquery" icon="search">查询</a-button>
+              <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
+              <a @click="handleToggleSearch" style="margin-left: 8px">
+                {{ toggleSearchStatus ? '收起' : '展开' }}
+                <a-icon :type="toggleSearchStatus ? 'up' : 'down'"/>
+              </a>
+            </span>
+          </a-col>
+
+        </a-row>
+      </a-form>
+    </div>
+
+    <!-- 操作按钮区域 -->
+    <div class="table-operator">
+      <a-button v-if="buttonSwitch.add" @click="handleAdd" type="primary" icon="plus">新增</a-button>
+      <a-button v-if="buttonSwitch.import" @click="handleImportXls" type="primary" icon="upload" style="margin-left:8px">导入</a-button>
+      <a-button v-if="buttonSwitch.export" @click="handleExportXls" type="primary" icon="download" style="margin-left:8px">导出</a-button>
+      <template v-if="cgButtonList && cgButtonList.length>0" v-for="(item,index) in cgButtonList">
+        <a-button
+          v-if=" item.optType=='js' "
+          :key=" 'cgbtn'+index "
+          @click="cgButtonJsHandler(item.buttonCode)"
+          type="primary"
+          :icon="item.buttonIcon"
+          style="margin-left:8px">
+          {{ item.buttonName }}
+        </a-button>
+        <a-button
+          v-else-if=" item.optType=='action' "
+          :key=" 'cgbtn'+index "
+          @click="cgButtonActionHandler(item.buttonCode)"
+          type="primary"
+          :icon="item.buttonIcon"
+          style="margin-left:8px">
+          {{ item.buttonName }}
+        </a-button>
+      </template>
+
+      <a-button
+        v-if="buttonSwitch.batch_delete"
+        @click="handleDelBatch"
+        style="margin-left:8px"
+        v-show="table.selectedRowKeys.length > 0"
+        ghost
+        type="primary"
+        icon="delete">批量删除</a-button>
+    </div>
+
+    <div>
+      <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
+        <i class="anticon anticon-info-circle ant-alert-icon"></i>
+        已选择&nbsp;<a style="font-weight: 600">{{ table.selectedRowKeys.length }}</a>项&nbsp;&nbsp;
+        <a style="margin-left: 24px" @click="onClearSelected">清空</a>
+      </div>
+
+      <a-table
+        ref="cgformAutoList"
+        bordered
+        size="middle"
+        rowKey="id"
+        :columns="table.columns"
+        :dataSource="table.dataSource"
+        :pagination="table.pagination"
+        :loading="table.loading"
+        :rowSelection="{selectedRowKeys:table.selectedRowKeys, onChange: handleChangeInTableSelect}"
+        @change="handleTableChange"
+        style="min-height: 300px">
+
+        <template slot="dateSlot" slot-scope="text">
+          <span>{{ getFormatDate(text) }}</span>
+        </template>
+
+        <template slot="htmlSlot" slot-scope="text">
+          <div v-html="text"></div>
+        </template>
+
+        <template slot="imgSlot" slot-scope="text">
+          <span v-if="!text" style="font-size: 12px;font-style: italic;">无此图片</span>
+          <img v-else :src="getImgView(text)" height="25px" alt="图片不存在" style="max-width:80px;font-size: 12px;font-style: italic;"/>
+        </template>
+
+        <template slot="fileSlot" slot-scope="text">
+          <span v-if="!text" style="font-size: 12px;font-style: italic;">无此文件</span>
+          <a-button
+            v-else
+            :ghost="true"
+            type="primary"
+            icon="download"
+            size="small"
+            @click="uploadFile(text)">
+            下载
+          </a-button>
+        </template>
+
+        <span slot="action" slot-scope="text, record">
+          <template v-if="hasBpmStatus">
+            <template v-if="record.bpm_status == '1'||record.bpm_status == ''|| record.bpm_status == null">
+              <template v-if="buttonSwitch.update">
+              <a @click="handleEdit(record)">编辑</a>
+              <a-divider type="vertical"/>
+            </template>
+            </template>
+          </template>
+          <template v-else>
+            <template v-if="buttonSwitch.update">
+              <a @click="handleEdit(record)">编辑</a>
+              <a-divider type="vertical"/>
+            </template>
+          </template>
+          <a-dropdown>
+            <a class="ant-dropdown-link">
+              更多 <a-icon type="down" />
+            </a>
+            <a-menu slot="overlay">
+              <a-menu-item >
+                <a href="javascript:;" @click="handleDetail(record)">详情</a>
+              </a-menu-item>
+              <template v-if="hasBpmStatus">
+              </template>
+              <template v-else>
+                <a-menu-item v-if="buttonSwitch.delete">
+                  <a-popconfirm title="确定删除吗?" @confirm="() => handleDeleteOne(record)">
+                    <a>删除</a>
+                  </a-popconfirm>
+                </a-menu-item>
+               </template>
+              <template v-if="cgButtonLinkList && cgButtonLinkList.length>0" v-for="(btnItem,btnIndex) in cgButtonLinkList">
+                <a-menu-item :key=" 'cgbtnLink'+btnIndex ">
+                  <a href="javascript:void(0);" @click="cgButtonLinkHandler(record,btnItem.buttonCode,btnItem.optType)">
+                    <a-icon v-if="btnItem.buttonIcon" :type="btnItem.buttonIcon" />
+                    {{ btnItem.buttonName }}
+                  </a>
+                </a-menu-item>
+              </template>
+
+            </a-menu>
+          </a-dropdown>
+        </span>
+      </a-table>
+
+      <OnlCgformAutoModal @success="handleFormSuccess" ref="modal" :code="code"></OnlCgformAutoModal>
+
+      <j-import-modal ref="importModal" :url="getImportUrl()" @ok="importOk"></j-import-modal>
+
+    </div>
+  </a-card>
+</template>
+
+<script>
+
+  import { postAction,getAction,deleteAction,downFile } from '@/api/manage'
+  import { filterMultiDictText } from '@/components/dict/JDictSelectUtil'
+  import { filterObj } from '@/utils/util';
+  import JImportModal from '@/components/jeecg/JImportModal'
+
+  export default {
+    name: 'OnlCgFormAutoList',
+    components: {
+      JImportModal
+    },
+    data() {
+      return {
+        code: '',
+        description: '在线报表功能测试页面',
+        currentTableName:"",
+        url: {
+          getQueryInfo:'/online/cgform/api/getQueryInfo/',
+          getColumns: '/online/cgform/api/getColumns/',
+          getData: '/online/cgform/api/getData/',
+          optPre:"/online/cgform/api/form/",
+          exportXls:'/online/cgform/api/exportXls/',
+          buttonAction:'/online/cgform/api/doButton',
+        },
+        flowCodePre:"onl_",
+        isorter:{
+          column: 'createTime',
+          order: 'desc',
+        },
+        //dictOptions:{fieldName:[]}
+        dictOptions:{
+
+        },
+        cgButtonLinkList:[],
+        cgButtonList:[],
+        queryInfo:[],
+        queryParam:{
+
+        },
+        toggleSearchStatus:false,
+        table: {
+          loading: true,
+          // 表头
+          columns: [],
+          //数据集
+          dataSource: [],
+          // 选择器
+          selectedRowKeys: [],
+          selectionRows: [],
+          // 分页参数
+          pagination: {
+             current: 1,
+             pageSize: 10,
+             pageSizeOptions: ['10', '20', '30'],
+             showTotal: (total, range) => {
+               return range[0] + '-' + range[1] + ' 共' + total + '条'
+             },
+             showQuickJumper: true,
+             showSizeChanger: true,
+             total: 0
+          }
+        },
+        actionColumn:{
+          title: '操作',
+          dataIndex: 'action',
+          scopedSlots: { customRender: 'action' },
+          fixed:"right",
+          align:"center",
+          width:150
+        },
+        formTemplate:"99",
+        EnhanceJS:'',
+        hideColumns:[],
+        buttonSwitch:{
+          add:true,
+          update:true,
+          delete:true,
+          batch_delete:true,
+          import:true,
+          export:true
+        },
+        hasBpmStatus:false,
+      }
+    },
+    created() {
+      this.initAutoList();
+    },
+    mounted(){
+      this.cgButtonJsHandler('mounted')
+    },
+    watch: {
+      '$route'() {
+        // 刷新参数放到这里去触发,就可以刷新相同界面了
+        this.initAutoList()
+      }
+    },
+    methods: {
+      hasBpmStatusFilter(){
+        var columnObjs = this.table.columns;
+        let columns = [];
+        for (var item of columnObjs) {
+          columns.push(item.dataIndex);
+        }
+        if(columns.includes('bpm_status')||columns.includes('BPM_STATUS')){
+          this.hasBpmStatus = true;
+        }else{
+          this.hasBpmStatus = false;
+        }
+      },
+      initQueryInfo(){
+        getAction(`${this.url.getQueryInfo}${this.code}`).then((res)=>{
+          console.log("--onlineList-获取查询条件配置",res);
+          if(res.success){
+            this.queryInfo = res.result
+          }else{
+            this.$message.warning(res.message)
+          }
+        })
+      },
+      initAutoList(){
+        if(!this.$route.params.code){
+          return false
+        }
+        this.table.loading = true
+        this.code = this.$route.params.code
+        getAction(`${this.url.getColumns}${this.code}`).then((res)=>{
+          console.log("--onlineList-加载动态列>>",res);
+          if(res.success){
+            this.dictOptions = res.result.dictOptions
+            this.formTemplate = res.result.formTemplate
+            this.description = res.result.description
+            this.currentTableName = res.result.currentTableName
+            this.initCgButtonList(res.result.cgButtonList)
+            this.initCgEnhanceJs(res.result.enhanceJs)
+            this.initButtonSwitch(res.result.hideColumns)
+            let currColumns = res.result.columns
+            for(let a=0;a<currColumns.length;a++){
+              if(currColumns[a].customRender){
+                let dictCode = currColumns[a].customRender;
+                currColumns[a].customRender=(text)=>{
+                  return filterMultiDictText(this.dictOptions[dictCode], text);
+                }
+              }
+            }
+            currColumns.push(this.actionColumn);
+            this.table.columns = [...currColumns]
+            this.hasBpmStatusFilter();
+            this.loadData();
+            this.initQueryInfo();
+          }else{
+            this.$message.warning(res.message)
+          }
+        })
+      },
+      loadData(arg){
+        if(arg==1){
+          this.table.pagination.current=1
+        }
+        let params = this.getQueryParams();//查询条件
+        console.log("--onlineList-查询条件-->",params)
+        getAction(`${this.url.getData}${this.code}`,params).then((res)=>{
+          console.log("--onlineList-列表数据",res)
+          if(res.success){
+            let result = res.result;
+            if(Number(result.total)>0){
+              this.table.pagination.total = Number(result.total)
+              this.table.dataSource = result.records
+            }else{
+              this.table.pagination.total=0;
+              this.table.dataSource=[]
+              //this.$message.warning("查无数据")
+            }
+          }else{
+            this.$message.warning(res.message)
+          }
+          this.table.loading = false
+        })
+      },
+      getQueryParams() {
+        let param = Object.assign({}, this.queryParam,this.isorter);
+        param.pageNo = this.table.pagination.current;
+        param.pageSize = this.table.pagination.pageSize;
+        return filterObj(param);
+      },
+      handleChangeInTableSelect(selectedRowKeys, selectionRows) {
+        this.table.selectedRowKeys = selectedRowKeys
+        this.table.selectionRows = selectionRows
+      },
+      handleTableChange(pagination, filters, sorter){
+        //TODO 筛选
+        if (Object.keys(sorter).length>0){
+          this.isorter.column = sorter.field;
+          this.isorter.order = "ascend"==sorter.order?"asc":"desc"
+        }
+        this.table.pagination = pagination;
+        this.loadData();
+      },
+      handleAdd(){
+        this.cgButtonJsHandler('beforeAdd')
+        this.$refs.modal.add(this.formTemplate);
+      },
+      handleImportXls(){
+        this.$refs.importModal.show()
+      },
+      importOk(){
+        this.loadData(1)
+      },
+      handleExportXls2(){
+        let param = this.queryParam;
+        if(this.table.selectedRowKeys && this.table.selectedRowKeys.length>0){
+          param['selections'] = this.table.selectedRowKeys.join(",")
+        }
+        let paramsStr = encodeURI(JSON.stringify(param));
+        console.log('paramsStr: ' + paramsStr)
+        let url = window._CONFIG['domianURL']+this.url.exportXls+this.code+"?paramsStr="+paramsStr
+        window.location.href = url;
+      },
+      handleExportXls(){
+        let param = this.queryParam;
+        if(this.table.selectedRowKeys && this.table.selectedRowKeys.length>0){
+          param['selections'] = this.table.selectedRowKeys.join(",")
+        }
+        console.log("导出参数",param)
+        let paramsStr = JSON.stringify(filterObj(param));
+        downFile(this.url.exportXls+this.code,{paramsStr:paramsStr}).then((data)=>{
+          if (!data) {
+            this.$message.warning("文件下载失败")
+            return
+          }
+          if (typeof window.navigator.msSaveBlob !== 'undefined') {
+            window.navigator.msSaveBlob(new Blob([data]), this.description+'.xls')
+          }else{
+            let url = window.URL.createObjectURL(new Blob([data]))
+            let link = document.createElement('a')
+            link.style.display = 'none'
+            link.href = url
+            link.setAttribute('download', this.description+'.xls')
+            document.body.appendChild(link)
+            link.click()
+            document.body.removeChild(link); //下载完成移除元素
+            window.URL.revokeObjectURL(url); //释放掉blob对象
+          }
+        })
+      },
+      handleEdit(record){
+        this.cgButtonLinkHandler(record,"beforeEdit","js")
+        this.$refs.modal.edit(this.formTemplate,record.id);
+      },
+      handleDetail(record){
+        this.$refs.modal.detail(this.formTemplate,record.id);
+      },
+      handleDeleteOne(record){
+        this.cgButtonLinkHandler(record,"beforeDelete","js")
+        this.handleDelete(record.id)
+      },
+      handleDelete(id){
+        deleteAction(this.url.optPre+this.code+"/"+id).then((res)=>{
+          if(res.success){
+            this.$message.success(res.message)
+            this.loadData()
+          }else{
+            this.$message.warning(res.message)
+          }
+        })
+      },
+
+      handleFormSuccess(){
+        this.loadData()
+      },
+      onClearSelected(){
+        this.table.selectedRowKeys = []
+        this.table.selectionRows = []
+      },
+      getImgView(text){
+        if(text && text.indexOf(",")>0){
+          text = text.substring(0,text.indexOf(","))
+        }
+        return window._CONFIG['imgDomainURL']+"/"+text
+      },
+      uploadFile(text){
+        if(!text){
+          this.$message.warning("未知的文件")
+          return;
+        }
+        if(text.indexOf(",")>0){
+          text = text.substring(0,text.indexOf(","))
+        }
+        window.open(window._CONFIG['imgDomainURL']+"/"+text);//TODO 下载的方法
+      },
+      handleDelBatch(){
+        if(this.table.selectedRowKeys.length<=0){
+          this.$message.warning('请选择一条记录!');
+          return false;
+        }else{
+          let ids = "";
+          let that = this;
+          that.table.selectedRowKeys.forEach(function(val) {
+            ids+=val+",";
+          });
+          that.$confirm({
+            title:"确认删除",
+            content:"是否删除选中数据?",
+            onOk: function(){
+              that.handleDelete(ids)
+              that.onClearSelected();
+            }
+          });
+        }
+      },
+
+      searchByquery(){
+        this.loadData(1);
+      },
+      searchReset(){
+        this.queryParam = {}
+        this.loadData(1);
+      },
+      handleToggleSearch(){
+        this.toggleSearchStatus = !this.toggleSearchStatus;
+      },
+      getFormatDate(text){
+        if(!text){
+          return ''
+        }
+        let a = text;
+        if(a.length>10){
+          a = a.substring(0,10);
+        }
+        return a;
+      },
+      getImportUrl(){
+        return '/online/cgform/api/importXls/'+this.code
+      },
+      initCgEnhanceJs(enhanceJs){
+        //console.log("--onlineList-js增强",enhanceJs)
+        if(enhanceJs){
+          let Obj = eval ("(" + enhanceJs + ")");
+          this.EnhanceJS = new Obj(getAction,postAction,deleteAction);
+          this.cgButtonJsHandler('created')
+        }else{
+          this.EnhanceJS = ''
+        }
+      },
+      initCgButtonList(btnList){
+        let linkArr = []
+        let buttonArr = []
+        if(btnList && btnList.length>0){
+          for(let i=0;i<btnList.length;i++){
+            let temp = btnList[i]
+            if(temp.buttonStyle=='button'){
+              buttonArr.push(temp)
+            }else if(temp.buttonStyle=='link'){
+              linkArr.push(temp)
+            }
+          }
+        }
+        this.cgButtonLinkList = [...linkArr]
+        this.cgButtonList=[...buttonArr]
+      },
+      cgButtonJsHandler(buttonCode){
+        if(this.EnhanceJS[buttonCode]){
+          this.EnhanceJS[buttonCode](this)
+        }
+      },
+      cgButtonActionHandler(buttonCode){
+        //处理自定义button的 需要配置该button自定义sql
+        if(!this.table.selectedRowKeys || this.table.selectedRowKeys.length==0){
+          this.$message.warning("请先选中一条记录")
+          return false
+        }
+        if(this.table.selectedRowKeys.length>1){
+          this.$message.warning("请只选中一条记录")
+          return false
+        }
+        let params = {
+          formId:this.code,
+          buttonCode:buttonCode,
+          dataId:this.table.selectedRowKeys[0]
+        }
+        console.log("自定义按钮请求后台参数:",params)
+        postAction(this.url.buttonAction,params).then(res=>{
+          if(res.success){
+            this.loadData()
+            this.$message.success("处理完成!")
+          }else{
+            this.$message.warning("处理失败!")
+          }
+        })
+
+      },
+      cgButtonLinkHandler(record,buttonCode,optType){
+        if(optType=="js"){
+          if(this.EnhanceJS[buttonCode]){
+            this.EnhanceJS[buttonCode](this,record)
+          }
+        }else if(optType=="action"){
+          let params = {
+            formId:this.code,
+            buttonCode:buttonCode,
+            dataId:record.id
+          }
+          console.log("自定义按钮link请求后台参数:",params)
+          postAction(this.url.buttonAction,params).then(res=>{
+            if(res.success){
+              this.loadData()
+              this.$message.success("处理完成!")
+            }else{
+              this.$message.warning("处理失败!")
+            }
+          })
+        }
+      },
+      initButtonSwitch(hideColumns){
+        if(hideColumns && hideColumns.length>0){
+          Object.keys(this.buttonSwitch).forEach(key=>{
+            if(hideColumns.indexOf(key)>=0){
+              this.buttonSwitch[key]=false
+            }
+          })
+
+        }
+      }
+
+    }
+  }
+</script>
+<style>
+  .ant-card-body .table-operator{
+    margin-bottom: 18px;
+  }
+  .ant-table-tbody .ant-table-row td{
+    padding-top:15px;
+    padding-bottom:15px;
+  }
+  .anty-row-operator button{margin: 0 5px}
+  .ant-btn-danger{background-color: #ffffff}
+
+  .anty-img-wrap{height:25px;position: relative;}
+  .anty-img-wrap > img{max-height:100%;}
+  .ant-modal-cust-warp{height: 100%}
+  .ant-modal-cust-warp .ant-modal-body{height:calc(100% - 110px) !important;overflow-y: auto}
+  .ant-modal-cust-warp .ant-modal-content{height:90% !important;overflow-y: hidden}
+</style>

+ 638 - 0
ant-design-vue-jeecg/src/views/modules/online/cgform/auto/OnlCgformTreeList.vue

@@ -0,0 +1,638 @@
+<template>
+  <a-card :bordered="false" style="height: 100%">
+    <!-- 操作按钮区域 -->
+    <div class="table-operator">
+      <a-button v-if="buttonSwitch.add" @click="handleAdd" type="primary" icon="plus">新增</a-button>
+      <a-button v-if="buttonSwitch.import" @click="handleImportXls" type="primary" icon="upload" style="margin-left:8px">导入</a-button>
+      <a-button v-if="buttonSwitch.export" @click="handleExportXls" type="primary" icon="download" style="margin-left:8px">导出</a-button>
+      <template v-if="cgButtonList && cgButtonList.length>0" v-for="(item,index) in cgButtonList">
+        <a-button
+          v-if=" item.optType=='js' "
+          :key=" 'cgbtn'+index "
+          @click="cgButtonJsHandler(item.buttonCode)"
+          type="primary"
+          :icon="item.buttonIcon"
+          style="margin-left:8px">
+          {{ item.buttonName }}
+        </a-button>
+        <a-button
+          v-else-if=" item.optType=='action' "
+          :key=" 'cgbtn'+index "
+          @click="cgButtonActionHandler(item.buttonCode)"
+          type="primary"
+          :icon="item.buttonIcon"
+          style="margin-left:8px">
+          {{ item.buttonName }}
+        </a-button>
+      </template>
+
+      <a-button
+        v-if="buttonSwitch.batch_delete"
+        @click="handleDelBatch"
+        style="margin-left:8px"
+        v-show="selectedRowKeys.length > 0"
+        ghost
+        type="primary"
+        icon="delete">批量删除</a-button>
+    </div>
+
+    <div>
+      <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
+        <i class="anticon anticon-info-circle ant-alert-icon"></i>
+        已选择&nbsp;<a style="font-weight: 600">{{ selectedRowKeys.length }}</a>项&nbsp;&nbsp;
+        <a style="margin-left: 24px" @click="onClearSelected">清空</a>
+      </div>
+
+      <a-table
+        ref="cgformTreeList"
+        size="middle"
+        rowKey="id"
+        :columns="columns"
+        :dataSource="dataSource"
+        :pagination="pagination"
+        :loading="loading"
+        @change="handleTableChange"
+        v-bind="tableProps"
+        @expand="handleExpand"
+        style="min-height: 300px"
+        :expandedRowKeys="expandedRowKeys">
+
+        <template slot="dateSlot" slot-scope="text">
+          <span>{{ getDateNoTime(text) }}</span>
+        </template>
+
+        <template slot="htmlSlot" slot-scope="text">
+          <div v-html="text"></div>
+        </template>
+
+        <template slot="imgSlot" slot-scope="text">
+          <span v-if="!text" style="font-size: 12px;font-style: italic;">无此图片</span>
+          <img v-else :src="getImgView(text)" height="25px" alt="图片不存在" style="max-width:80px;font-size: 12px;font-style: italic;"/>
+        </template>
+
+        <template slot="fileSlot" slot-scope="text">
+          <span v-if="!text" style="font-size: 12px;font-style: italic;">无此文件</span>
+          <a-button
+            v-else
+            :ghost="true"
+            type="primary"
+            icon="download"
+            size="small"
+            @click="uploadFile(text)">
+            下载
+          </a-button>
+        </template>
+
+        <span slot="action" slot-scope="text, record">
+          <template v-if="buttonSwitch.update">
+            <a @click="handleEdit(record)">编辑</a>
+            <a-divider type="vertical"/>
+          </template>
+          <a-dropdown>
+            <a class="ant-dropdown-link">
+              更多 <a-icon type="down" />
+            </a>
+            <a-menu slot="overlay">
+              <a-menu-item >
+                <a @click="handleDetail(record)">详情</a>
+              </a-menu-item>
+              <a-menu-item v-if="buttonSwitch.delete">
+                <a-popconfirm title="确定删除吗?" @confirm="() => handleDeleteOne(record)">
+                  <a>删除</a>
+                </a-popconfirm>
+              </a-menu-item>
+              <!-- 自定义按钮 -->
+              <template v-if="cgButtonLinkList && cgButtonLinkList.length>0" v-for="(btnItem,btnIndex) in cgButtonLinkList">
+                <a-menu-item :key=" 'cgbtnLink'+btnIndex ">
+                  <a href="javascript:void(0);" @click="cgButtonLinkHandler(record,btnItem.buttonCode,btnItem.optType)">
+                    <a-icon v-if="btnItem.buttonIcon" :type="btnItem.buttonIcon" />
+                    {{ btnItem.buttonName }}
+                  </a>
+                </a-menu-item>
+              </template>
+
+            </a-menu>
+          </a-dropdown>
+        </span>
+
+      </a-table>
+
+      <onl-cgform-auto-modal @success="handleFormSuccess" ref="modal" :code="code"></onl-cgform-auto-modal>
+
+      <j-import-modal ref="importModal" :url="getImportUrl()" @ok="importOk"></j-import-modal>
+    </div>
+  </a-card>
+</template>
+
+<script>
+
+  import { getAction,postAction,deleteAction,downFile } from '@/api/manage'
+  import { filterMultiDictText } from '@/components/dict/JDictSelectUtil'
+  import { filterObj } from '@/utils/util';
+  import JImportModal from '@/components/jeecg/JImportModal'
+
+  export default {
+    name: 'OnlCgformTreeList',
+    components: {
+      JImportModal
+    },
+    data() {
+      return {
+        code: '87b55a515d3441b6b98e48e5b35474a6',
+        description: '在线报表功能测试页面',
+        currentTableName:"",
+        pidField:"",
+        hasChildrenField:"",
+        textField:'',
+        loading: false,
+        // 表头
+        columns: [],
+        //数据集
+        dataSource: [],
+        // 选择器
+        selectedRowKeys: [],
+        selectionRows: [],
+        // 分页参数
+        pagination: {
+          current: 1,
+          pageSize: 10,
+          pageSizeOptions: ['10', '20', '30'],
+          showTotal: (total, range) => {
+            return range[0] + '-' + range[1] + ' 共' + total + '条'
+          },
+          showQuickJumper: true,
+          showSizeChanger: true,
+          total: 0
+        },
+
+        url: {
+          getColumns: '/online/cgform/api/getColumns/',
+          getTreeData: '/online/cgform/api/getTreeData/',
+          optPre:"/online/cgform/api/form/",
+          exportXls:'/online/cgform/api/exportXls/',
+          buttonAction:'/online/cgform/api/doButton',
+          startProcess: "/process/extActProcess/startMutilProcess",
+        },
+        isorter:{
+          column: 'create_time',
+          order: 'desc',
+        },
+        dictOptions:{
+
+        },
+
+        queryParam:{
+
+        },
+        actionColumn:{
+          title: '操作',
+          dataIndex: 'action',
+          scopedSlots: { customRender: 'action' },
+          fixed:"right",
+          align:"center",
+          width:150
+        },
+        formTemplate:"99",
+
+        /*自定义按钮-link*/
+        cgButtonLinkList:[],
+        /*自定义按钮-button*/
+        cgButtonList:[],
+        /*JS增强*/
+        EnhanceJS:'',
+        /*操作按钮权限*/
+        buttonSwitch:{
+          add:true,
+          update:true,
+          delete:true,
+          batch_delete:true,
+          import:true,
+          export:true
+        },
+        expandedRowKeys:[]
+
+      }
+    },
+    created() {
+      this.initAutoListConfig().then(()=>{
+        this.loadData(1)
+      }).catch(msg=>{
+        console.log(msg)
+      })
+    },
+    mounted(){
+      //this.cgButtonJsHandler('mounted')
+    },
+    watch: {
+      '$route'() {
+        // 刷新参数放到这里去触发,就可以刷新相同界面了
+        this.initAutoListConfig().then(()=>{
+          this.loadData(1)
+        }).catch(msg=>{
+          console.log(msg)
+        })
+      }
+    },
+    computed: {
+      tableProps() {
+        let _this = this
+        return {
+          // 列表项是否可选择
+          // https://vue.ant.design/components/table-cn/#rowSelection
+          rowSelection: {
+            selectedRowKeys: _this.selectedRowKeys,
+            onChange: (selectedRowKeys) => _this.selectedRowKeys = selectedRowKeys
+          }
+        }
+      }
+    },
+    methods: {
+      resetData(){
+        this.description=''
+        this.currentTableName=''
+        this.pidField=''
+        this.hasChildrenField=''
+        this.textField=''
+        this.columns = []
+        this.dataSource = []
+        this.selectedRowKeys=[]
+        this.selectionRows=[]
+      },
+      initAutoListConfig() {
+        return new Promise((resolve, reject) => {
+          if (!this.$route.params.code) {
+            reject("列表加载需要参数CODE为空!")
+          } else {
+            this.resetData()
+            this.loading = true
+            this.code = this.$route.params.code
+            getAction(`${this.url.getColumns}${this.code}`)
+              .then(res => {
+                console.log("--onlineList-加载动态列>>", res);
+                if(res.success){
+                  this.configInfohandler(res)
+                  resolve();
+                }else{
+                  reject("onlineList-加载表配置信息失败")
+                }
+                this.loading = false
+              })
+              .catch(err => {
+                reject(err)
+              })
+          }
+        })
+      },
+      configInfohandler(res){
+        this.dictOptions = res.result.dictOptions
+        this.formTemplate = res.result.formTemplate
+        this.description = res.result.description
+        this.currentTableName = res.result.currentTableName
+        this.pidField = res.result.pidField
+        this.hasChildrenField = res.result.hasChildrenField
+        this.textField = res.result.textField
+        //自定义按钮
+        this.initCgButtonList(res.result.cgButtonList)
+        //JS增强
+        this.initCgEnhanceJs(res.result.enhanceJs)
+        //操作按钮权限
+        this.initButtonSwitch(res.result.hideColumns)
+        let currColumns = res.result.columns
+        let textFieldIndex = -1
+        for(let a=0;a<currColumns.length;a++){
+          currColumns[a].align = 'left'
+          if(this.textField==currColumns[a].dataIndex){
+            textFieldIndex = a
+          }
+          if(currColumns[a].customRender){
+            let dictCode = currColumns[a].customRender;
+            currColumns[a].customRender=(text)=>{
+              return filterMultiDictText(this.dictOptions[dictCode], text);
+            }
+          }
+        }
+        if(textFieldIndex!=-1){
+          let textFieldColumn = currColumns.splice(textFieldIndex,1)
+          currColumns.unshift(textFieldColumn[0])
+        }
+        currColumns.push(this.actionColumn);
+        this.columns = [...currColumns]
+      },
+      //加载根节点
+      loadData(arg){
+        if(arg==1){
+          this.pagination.current=1
+        }
+        this.loading = true
+        this.expandedRowKeys=[]
+        let params = this.getQueryParams();//查询条件
+        params[this.pidField]='0'
+        console.log("--onlineList-查询条件-->",params)
+        getAction(`${this.url.getTreeData}${this.code}`,params).then((res)=>{
+          console.log("--onlineList-列表数据",res)
+          if(res.success){
+            let result = res.result;
+            if(Number(result.total)>0){
+              this.pagination.total = Number(result.total)
+              let dataSource = res.result.records.map(item => {
+                // 判断是否标记了带有子级
+                if (item[this.hasChildrenField] === true || item[this.hasChildrenField]=='1') {
+                  let loadChild = { id: `${item.id}_loadChild`, name: 'loading...', isLoading: true }
+                  item.children = [loadChild]
+                }
+                return item
+              })
+              this.dataSource = dataSource
+            }else{
+              this.pagination.total=0;
+              this.dataSource=[]
+            }
+          }else{
+            this.$message.warning(res.message)
+          }
+          this.loading = false
+        })
+      },
+      //加载叶子节点
+      handleExpand(expanded, record) {
+        // 判断是否是展开状态
+        if (expanded) {
+          this.expandedRowKeys.push(record.id)
+          if (record.children.length>0 && record.children[0].isLoading === true) {
+            let params = this.getQueryParams();//查询条件
+            params[this.pidField] = record.id
+            getAction(`${this.url.getTreeData}${this.code}`,params).then((res)=>{
+              if(res.success){
+                if(Number(res.result.total)>0){
+                  let dataSource = res.result.records.map(item => {
+                    // 判断是否标记了带有子级
+                    if (item[this.hasChildrenField] === true || item[this.hasChildrenField]=='1') {
+                      let loadChild = { id: `${item.id}_loadChild`, name: 'loading...', isLoading: true }
+                      item.children = [loadChild]
+                    }
+                    return item
+                  })
+                  record.children = dataSource
+                }else{
+                  record.children=''
+                  record.hasChildrenField='0'
+                }
+              }else{
+                this.$message.warning(res.message)
+              }
+            })
+          }
+        }else{
+          let keyIndex = this.expandedRowKeys.indexOf(record.id)
+          if(keyIndex>=0){
+            this.expandedRowKeys.splice(keyIndex, 1);
+          }
+        }
+      },
+      getQueryParams() {
+        let param = Object.assign({}, this.queryParam,this.isorter);
+        param.pageNo = this.pagination.current;
+        param.pageSize = this.pagination.pageSize;
+        return filterObj(param);
+      },
+      initCgButtonList(btnList){
+        let linkArr = []
+        let buttonArr = []
+        if(btnList && btnList.length>0){
+          for(let i=0;i<btnList.length;i++){
+            let temp = btnList[i]
+            if(temp.buttonStyle=='button'){
+              buttonArr.push(temp)
+            }else if(temp.buttonStyle=='link'){
+              linkArr.push(temp)
+            }
+          }
+        }
+        this.cgButtonLinkList = [...linkArr]
+        this.cgButtonList=[...buttonArr]
+      },
+      initCgEnhanceJs(enhanceJs){
+        //console.log("--onlineList-js增强",enhanceJs)
+        if(enhanceJs){
+          let Obj = eval ("(" + enhanceJs + ")");
+          this.EnhanceJS = new Obj(getAction,postAction,deleteAction);
+          this.cgButtonJsHandler('created')
+        }else{
+          this.EnhanceJS = ''
+        }
+      },
+      initButtonSwitch(hideColumns){
+        if(hideColumns && hideColumns.length>0){
+          Object.keys(this.buttonSwitch).forEach(key=>{
+            if(hideColumns.indexOf(key)>=0){
+              this.buttonSwitch[key]=false
+            }
+          })
+
+        }
+      },
+      onClearSelected(){
+        this.selectedRowKeys = []
+        this.selectionRows = []
+      },
+      handleTableChange(pagination, filters, sorter){
+        //TODO 筛选
+        if (Object.keys(sorter).length>0){
+          this.isorter.column = sorter.field;
+          this.isorter.order = "ascend"==sorter.order?"asc":"desc"
+        }
+        this.pagination = pagination;
+        this.loadData();
+      },
+      /*-------数据格式化-begin----------*/
+      getDateNoTime(text){
+        if(!text){
+          return ''
+        }
+        let a = text;
+        if(a.length>10){
+          a = a.substring(0,10);
+        }
+        return a;
+      },
+      getImgView(text){
+        if(text && text.indexOf(",")>0){
+          text = text.substring(0,text.indexOf(","))
+        }
+        return window._CONFIG['imgDomainURL']+"/"+text
+      },
+      uploadFile(text){
+        if(!text){
+          this.$message.warning("未知的文件")
+          return;
+        }
+        if(text.indexOf(",")>0){
+          text = text.substring(0,text.indexOf(","))
+        }
+        window.open(window._CONFIG['domianURL'] + "/sys/common/download/"+text);
+      },
+      /*-------数据格式化-end----------*/
+
+      /*-------功能按钮触发事件-begin----------*/
+      handleEdit(record){
+        this.cgButtonLinkHandler(record,"beforeEdit","js")
+        this.$refs.modal.edit(this.formTemplate,record.id);
+      },
+      handleDetail(record){
+        this.$refs.modal.detail(this.formTemplate,record.id);
+      },
+      handleDeleteOne(record){
+        this.cgButtonLinkHandler(record,"beforeDelete","js")
+        this.handleDelete(record.id)
+      },
+      handleDelete(id){
+        deleteAction(this.url.optPre+this.code+"/"+id).then((res)=>{
+          if(res.success){
+            this.$message.success(res.message)
+            this.loadData()
+          }else{
+            this.$message.warning(res.message)
+          }
+        })
+      },
+      handleAdd(){
+        this.cgButtonJsHandler('beforeAdd')
+        this.$refs.modal.add(this.formTemplate);
+      },
+      handleFormSuccess(){
+        this.loadData()
+      },
+      handleImportXls(){
+        this.$refs.importModal.show()
+      },
+      importOk(){
+        this.loadData(1)
+      },
+      getImportUrl(){
+        return '/online/cgform/api/importXls/'+this.code
+      },
+      handleExportXls(){
+        let param = this.queryParam;
+        if(this.selectedRowKeys && this.selectedRowKeys.length>0){
+          param['selections'] = this.selectedRowKeys.join(",")
+        }
+        console.log("导出参数",param)
+        let paramsStr = JSON.stringify(filterObj(param));
+        downFile(this.url.exportXls+this.code,{paramsStr:paramsStr}).then((data)=>{
+          if (!data) {
+            this.$message.warning("文件下载失败")
+            return
+          }
+          if (typeof window.navigator.msSaveBlob !== 'undefined') {
+            window.navigator.msSaveBlob(new Blob([data]), this.description+'.xls')
+          }else{
+            let url = window.URL.createObjectURL(new Blob([data]))
+            let link = document.createElement('a')
+            link.style.display = 'none'
+            link.href = url
+            link.setAttribute('download', this.description+'.xls')
+            document.body.appendChild(link)
+            link.click()
+            document.body.removeChild(link); //下载完成移除元素
+            window.URL.revokeObjectURL(url); //释放掉blob对象
+          }
+        })
+      },
+      handleDelBatch(){
+        if(this.selectedRowKeys.length<=0){
+          this.$message.warning('请选择一条记录!');
+          return false;
+        }else{
+          let ids = "";
+          let that = this;
+          that.selectedRowKeys.forEach(function(val) {
+            ids+=val+",";
+          });
+          that.$confirm({
+            title:"确认删除",
+            content:"是否删除选中数据?",
+            onOk: function(){
+              that.handleDelete(ids)
+              that.onClearSelected();
+            }
+          });
+        }
+      },
+      /*-------功能按钮触发事件-begin----------*/
+
+      /*-------JS增强-begin----------*/
+      cgButtonLinkHandler(record,buttonCode,optType){
+        if(optType=="js"){
+          if(this.EnhanceJS[buttonCode]){
+            this.EnhanceJS[buttonCode](this,record)
+          }
+        }else if(optType=="action"){
+          let params = {
+            formId:this.code,
+            buttonCode:buttonCode,
+            dataId:record.id
+          }
+          console.log("自定义按钮link请求后台参数:",params)
+          postAction(this.url.buttonAction,params).then(res=>{
+            if(res.success){
+              this.loadData()
+              this.$message.success("处理完成!")
+            }else{
+              this.$message.warning("处理失败!")
+            }
+          })
+        }
+      },
+      cgButtonJsHandler(buttonCode){
+        if(this.EnhanceJS[buttonCode]){
+          this.EnhanceJS[buttonCode](this)
+        }
+      },
+      cgButtonActionHandler(buttonCode){
+        //处理自定义button的 需要配置该button自定义sql
+        if(!this.selectedRowKeys || this.selectedRowKeys.length==0){
+          this.$message.warning("请先选中一条记录")
+          return false
+        }
+        if(this.selectedRowKeys.length>1){
+          this.$message.warning("请只选中一条记录")
+          return false
+        }
+        let params = {
+          formId:this.code,
+          buttonCode:buttonCode,
+          dataId:this.selectedRowKeys[0]
+        }
+        console.log("自定义按钮请求后台参数:",params)
+        postAction(this.url.buttonAction,params).then(res=>{
+          if(res.success){
+            this.loadData()
+            this.$message.success("处理完成!")
+          }else{
+            this.$message.warning("处理失败!")
+          }
+        })
+
+      },
+      /*-------JS增强-end----------*/
+
+    }
+  }
+</script>
+<style>
+  .ant-card-body .table-operator{
+    margin-bottom: 18px;
+  }
+  .ant-table-tbody .ant-table-row td{
+    padding-top:15px;
+    padding-bottom:15px;
+  }
+  .anty-row-operator button{margin: 0 5px}
+  .ant-btn-danger{background-color: #ffffff}
+
+  .anty-img-wrap{height:25px;position: relative;}
+  .anty-img-wrap > img{max-height:100%;}
+  .ant-modal-cust-warp{height: 100%}
+  .ant-modal-cust-warp .ant-modal-body{height:calc(100% - 110px) !important;overflow-y: auto}
+  .ant-modal-cust-warp .ant-modal-content{height:90% !important;overflow-y: hidden}
+</style>

+ 268 - 0
ant-design-vue-jeecg/src/views/modules/online/cgform/util/TableUtils.js

@@ -0,0 +1,268 @@
+/**
+ * 同步列表,可以同步新增、修改、删除
+ * @author sunjianlei
+ * */
+export function syncAllTable(vm, table1) {
+  vm.$refs.editableTable.resetScrollTop()
+  let deleteIds = table1.$refs.editableTable.getDeleteIds()
+  let table1Value
+  table1.$refs.editableTable.getValuesPromise(false).then((values) => {
+    table1Value = values
+    return vm.$refs.editableTable.getValuesPromise(false)
+  }).then((values) => {
+
+    table1Value.forEach(value => {
+      let flag = false
+      values.forEach((thisValue) => {
+        if (value.id === thisValue.id) {
+
+          // 判断是否修改了值
+          let dbFieldName = thisValue['dbFieldName']
+          let dbFieldTxt = thisValue['dbFieldTxt']
+
+          // return
+
+          if (value.dbFieldName !== dbFieldName
+            || value.dbFieldTxt !== dbFieldTxt) {
+
+            // 修改了
+            vm.$refs.editableTable.setValues([{
+              rowKey: thisValue.id,
+              values: {
+                dbFieldName: value.dbFieldName,
+                dbFieldTxt: value.dbFieldTxt
+              }
+            }])
+
+          }
+          flag = true
+        } else {
+          // id不匹配则有可能是新增也有可能是删除了的
+          // 遍历传进来的 deleteIds 进行对比
+          deleteIds.forEach(delId => {
+            // 对比成功,则删除该条数据
+            if (delId === thisValue.id) {
+              vm.$refs.editableTable.removeRows(vm.$refs.editableTable.caseId + delId)
+              flag = true
+            }
+          })
+        }
+      })
+      // return
+      // 判断是否操作了该条数据,若没有操作则代表要执行新增操作
+      if (!flag) {
+        let record = Object.assign({}, value)
+        vm.columns.forEach(column => {
+          if (
+            column.dataIndex !== 'dbFieldName' &&
+            column.dataIndex !== 'dbFieldTxt'
+          ) {
+            record[column.dataIndex] = column.defaultValue
+          }
+        })
+        vm.$refs.editableTable.push(record)
+      }
+    })
+  })
+
+}
+
+/**
+ * 将数据分类并Set进dataSource
+ * @author sunjianlei
+ **/
+export function setDataSource(vm, queryData) {
+  let dataSource = []
+  // 遍历查询出来的数据
+  queryData.forEach(value => {
+
+    let data = { id: value['id'] }
+    vm.columns.forEach(column => {
+      let key = column.key
+      if (key) {
+        data[key] = value[key]
+
+        // 由于多选下拉框返回的是一个数组,所以需要改成 [1,2,3] 数组的形式,否则组件不识别
+        // if (key === 'indexField') {
+        //   data[key] = value[key].split(',')
+        // }
+
+      }
+    })
+    dataSource.push(data)
+  })
+  vm.dataSource = dataSource
+}
+
+/** 获取主表的初始化数据 */
+export function getMasterTableInitialData() {
+  return [
+    {
+      dbFieldName: 'id',
+      dbFieldTxt: '主键',
+      dbLength: 36,
+      dbPointLength: 0,
+      dbDefaultVal: '',
+      dbType: 'string',
+      dbIsKey: '1',
+      dbIsNull: '0',
+      // table2
+      isShowForm: '0',
+      isShowList: '0',
+      fieldShowType: 'text',
+      fieldLength: '120',
+      queryMode: 'single',
+      orderNum: 1
+    },
+    {
+      dbFieldName: 'create_by',
+      dbFieldTxt: '创建人',
+      dbLength: 50,
+      dbPointLength: 0,
+      dbDefaultVal: '',
+      dbType: 'string',
+      dbIsKey: '0',
+      dbIsNull: '1',
+      // table2
+      isShowForm: '0',
+      isShowList: '0',
+      fieldShowType: 'text',
+      fieldLength: '120',
+      queryMode: 'single',
+      orderNum: 2
+    },
+    {
+      dbFieldName: 'create_time',
+      dbFieldTxt: '创建日期',
+      dbLength: 20,
+      dbPointLength: 0,
+      dbDefaultVal: '',
+      dbType: 'Date',
+      dbIsKey: '0',
+      dbIsNull: '1',
+      // table2
+      isShowForm: '0',
+      isShowList: '0',
+      fieldShowType: 'datetime',
+      fieldLength: '120',
+      queryMode: 'single',
+      orderNum: 3
+    },
+    {
+      dbFieldName: 'update_by',
+      dbFieldTxt: '更新人',
+      dbLength: 50,
+      dbPointLength: 0,
+      dbDefaultVal: '',
+      dbType: 'string',
+      dbIsKey: '0',
+      dbIsNull: '1',
+      // table2
+      isShowForm: '0',
+      isShowList: '0',
+      fieldShowType: 'text',
+      fieldLength: '120',
+      queryMode: 'single',
+      orderNum: 4
+    },
+    {
+      dbFieldName: 'update_time',
+      dbFieldTxt: '更新日期',
+      dbLength: 20,
+      dbPointLength: 0,
+      dbDefaultVal: '',
+      dbType: 'Date',
+      dbIsKey: '0',
+      dbIsNull: '1',
+      // table2
+      isShowForm: '0',
+      isShowList: '0',
+      fieldShowType: 'datetime',
+      fieldLength: '120',
+      queryMode: 'single',
+      orderNum: 5
+    },{
+      dbFieldName: 'sys_org_code',
+      dbFieldTxt: '所属部门',
+      dbLength: 64,
+      dbPointLength: 0,
+      dbDefaultVal: '',
+      dbType: 'string',
+      dbIsKey: '0',
+      dbIsNull: '1',
+      // table2
+      isShowForm: '0',
+      isShowList: '0',
+      fieldShowType: 'text',
+      fieldLength: '120',
+      queryMode: 'single',
+      orderNum: 6
+    }
+    // {
+    //   dbFieldName: 'sys_org_code',
+    //   dbFieldTxt: '所属部门',
+    //   dbLength: 50,
+    //   dbPointLength: 0,
+    //   dbDefaultVal: '',
+    //   dbType: 'string',
+    //   dbIsKey: false,
+    //   dbIsNull: true
+    // }, {
+    //   dbFieldName: 'sys_company_code',
+    //   dbFieldTxt: '所属公司',
+    //   dbLength: 50,
+    //   dbPointLength: 0,
+    //   dbDefaultVal: '',
+    //   dbType: 'string',
+    //   dbIsKey: false,
+    //   dbIsNull: true
+    // }, {
+    //   dbFieldName: 'bpm_status',
+    //   dbFieldTxt: '流程状态',
+    //   dbLength: 32,
+    //   dbPointLength: 0,
+    //   dbDefaultVal: '',
+    //   dbType: 'string',
+    //   dbIsKey: false,
+    //   dbIsNull: true
+    // }
+  ]
+}
+/** 获取树的初始化数据 */
+export function getTreeNeedFields() {
+  return [{
+    dbFieldName: 'pid',
+    dbFieldTxt: '父级节点',
+    dbLength: 32,
+    dbPointLength: 0,
+    dbDefaultVal: '',
+    dbType: 'string',
+    dbIsKey: '0',
+    dbIsNull: '1',
+    // table2
+    isShowForm: '1',
+    isShowList: '0',
+    fieldShowType: 'text',
+    fieldLength: '120',
+    queryMode: 'single',
+    orderNum: 7
+  },{
+    dbFieldName: 'has_child',
+    dbFieldTxt: '是否有子节点',
+    dbLength: 3,
+    dbPointLength: 0,
+    dbDefaultVal: '',
+    dbType: 'string',
+    dbIsKey: '0',
+    dbIsNull: '1',
+    // table2
+    isShowForm: '0',
+    isShowList: '0',
+    fieldShowType: 'list',
+    fieldLength: '120',
+    queryMode: 'single',
+    orderNum: 8,
+    // table3
+    dictField:"yn"
+  }]
+}

+ 6 - 6
ant-design-vue-jeecg/src/views/system/DepartList.vue

@@ -205,11 +205,11 @@
           mobile: {rules: [{validator: this.validateMobile}]}
         },
         url: {
-          delete: '/sysdepart/sysDepart/delete',
-          edit: '/sysdepart/sysDepart/edit',
-          deleteBatch: '/sysdepart/sysDepart/deleteBatch',
-          exportXlsUrl: "sysdepart/sysDepart/exportXls",
-          importExcelUrl: "sysdepart/sysDepart/importExcel",
+          delete: '/sys/sysDepart/delete',
+          edit: '/sys/sysDepart/edit',
+          deleteBatch: '/sys/sysDepart/deleteBatch',
+          exportXlsUrl: "sys/sysDepart/exportXls",
+          importExcelUrl: "sys/sysDepart/importExcel",
         },
       }
     },
@@ -292,7 +292,7 @@
           var that = this
           this.$confirm({
             title: '确认删除',
-            content: '确定要删除所选中的 ' + this.checkedKeys.length + ' 条数据?',
+            content: '确定要删除所选中的 ' + this.checkedKeys.length + ' 条数据,以及子节点数据吗?',
             onOk: function () {
               deleteAction(that.url.deleteBatch, {ids: ids}).then((res) => {
                 if (res.success) {

+ 3 - 3
ant-design-vue-jeecg/src/views/system/DepartList2.vue

@@ -148,9 +148,9 @@
         selectedRowKeys: [],
         selectedRows: [],
         url: {
-          list: "/sysdepart/sysDepart/list",
-          delete: "/sysdepart/sysDepart/delete",
-          deleteBatch: "/sysdepart/sysDepart/deleteBatch",
+          list: "/sys/sysDepart/list",
+          delete: "/sys/sysDepart/delete",
+          deleteBatch: "/sys/sysDepart/deleteBatch",
         },
 
       }

+ 39 - 8
ant-design-vue-jeecg/src/views/system/LogList.vue

@@ -19,7 +19,7 @@
             </a-form-item>
           </a-col>
 
-          <a-col :md="8" :sm="10">
+          <a-col :md="6" :sm="10">
             <a-form-item label="创建时间" :labelCol="labelCol" :wrapperCol="wrapperCol">
               <a-range-picker
                 style="width: 210px"
@@ -31,14 +31,19 @@
               />
             </a-form-item>
           </a-col>
-
-          <a-col :md="8" :sm="10" >
-            <span style="float: right;" class="table-page-search-submitButtons">
-              <a-button type="primary" style="left: -35px" @click="searchQuery" icon="search">查询</a-button>
-              <a-button type="primary"  @click="searchReset" icon="reload" style="margin-left: 8px;left: -35px">重置</a-button>
-            </span>
+          <a-col :md="5" :sm="8" v-if="tabKey === '2'">
+            <a-form-item label="操作类型" style="left: 10px">
+              <j-dict-select-tag v-model="queryParam.operateType" placeholder="请选择操作类型" dictCode="operate_type"/>
+            </a-form-item>
           </a-col>
 
+          <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+            <a-col :md="6" :sm="24" >
+                <a-button type="primary"  style="left: 10px" @click="searchQuery" icon="search">查询</a-button>
+                <a-button type="primary"  @click="searchReset" icon="reload" style="margin-left: 8px;left: 10px">重置</a-button>
+            </a-col>
+          </span>
+
         </a-row>
       </a-form>
     </div>
@@ -58,7 +63,10 @@
         <div style="margin-bottom: 5px"><a-badge status="success" style="vertical-align: middle;"/><span style="vertical-align: middle;">请求方法:{{ record.method }}</span></div>
         <div><a-badge status="processing" style="vertical-align: middle;"/><span style="vertical-align: middle;">请求参数:{{ record.requestParam }}</span></div>
       </div>
-
+      <!-- 字符串超长截取省略号显示-->
+      <span slot="logContent" slot-scope="text, record">
+          <j-ellipsis :value="text" :length="40"/>
+        </span>
     </a-table>
     <!-- table区域-end -->
   </a-card>
@@ -67,10 +75,14 @@
 <script>
   import { filterObj } from '@/utils/util';
   import { JeecgListMixin } from '@/mixins/JeecgListMixin'
+  import JEllipsis from '@/components/jeecg/JEllipsis'
 
   export default {
     name: "LogList",
     mixins:[JeecgListMixin],
+    components: {
+      JEllipsis
+    },
     data () {
       return {
         description: '这是日志管理页面',
@@ -81,6 +93,7 @@
           logType:'1',
           keyWord:'',
         },
+        tabKey: "1",
         // 表头
         columns: [
           {
@@ -96,6 +109,7 @@
             title: '日志内容',
             align:"left",
             dataIndex: 'logContent',
+            scopedSlots: { customRender: 'logContent' },
             sorter: true
           },
           {
@@ -143,6 +157,12 @@
             sorter: true
           }
         ],
+        operateColumn:
+        {
+          title: '操作类型',
+          dataIndex: 'operateType_dictText',
+          align:"center",
+        },
         labelCol: {
           xs: { span: 1 },
           sm: { span: 2 },
@@ -177,6 +197,17 @@
       },
       // 日志类型
       callback(key){
+
+        // 动态添加操作类型列
+        if (key == 2) {
+          this.tabKey = '2';
+          this.columns.splice(7, 0, this.operateColumn);
+        }else if(this.columns.length == 9)
+        {
+          this.tabKey = '1';
+          this.columns.splice(7,1);
+        }
+
         let that=this;
         that.queryParam.logType=key;
         that.loadData();

+ 1 - 1
ant-design-vue-jeecg/src/views/system/PermissionList.vue

@@ -98,7 +98,7 @@
         } else if (text == 1) {
           return '菜单'
         } else if (text == 2) {
-          return '按钮'
+          return '按钮/权限'
         } else {
           return text
         }

+ 3 - 3
ant-design-vue-jeecg/src/views/system/SysAnnouncementList.vue

@@ -68,13 +68,13 @@
         @change="handleTableChange">
 
         <span slot="action" slot-scope="text, record">
-          <a @click="handleEdit(record)">编辑</a>
+          <a  v-if="record.sendStatus == 0" @click="handleEdit(record)">编辑</a>
 
-          <a-divider type="vertical"/>
+          <a-divider type="vertical" v-if="record.sendStatus == 0"/>
           <a-dropdown>
             <a class="ant-dropdown-link">更多 <a-icon type="down"/></a>
             <a-menu slot="overlay">
-              <a-menu-item>
+              <a-menu-item v-if="record.sendStatus != 1">
                 <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
                   <a>删除</a>
                 </a-popconfirm>

+ 3 - 3
ant-design-vue-jeecg/src/views/system/UserAnnouncementList.vue

@@ -125,9 +125,9 @@
           scopedSlots: { customRender: 'action' },
         }],
 		    url: {
-          list: "/system/sysAnnouncementSend/getMyAnnouncementSend",
-          editCementSend:"system/sysAnnouncementSend/editByAnntIdAndUserId",
-          readAllMsg:"system/sysAnnouncementSend/readAll",
+          list: "/sys/sysAnnouncementSend/getMyAnnouncementSend",
+          editCementSend:"sys/sysAnnouncementSend/editByAnntIdAndUserId",
+          readAllMsg:"sys/sysAnnouncementSend/readAll",
         },
         loading:false,
       }

+ 22 - 6
ant-design-vue-jeecg/src/views/system/UserList.vue

@@ -3,7 +3,7 @@
 
     <!-- 查询区域 -->
     <div class="table-page-search-wrapper">
-      <a-form layout="inline">
+      <a-form layout="inline" @submit.prevent="searchQuery">
         <a-row :gutter="24">
 
           <a-col :md="6" :sm="12">
@@ -117,8 +117,9 @@
         </template>
 
         <span slot="action" slot-scope="text, record">
-          <a @click="handleEdit(record)">编辑</a>
-          <a-divider type="vertical"/>
+          <a @click="handleEdit(record)" v-has="'user:edit'">编辑</a>
+
+          <a-divider type="vertical" v-has="'user:edit'"/>
 
           <a-dropdown>
             <a class="ant-dropdown-link">
@@ -140,13 +141,13 @@
               </a-menu-item>
 
               <a-menu-item v-if="record.status==1">
-                <a-popconfirm title="确定冻结吗?" @confirm="() => handleFrozen(record.id,2)">
+                <a-popconfirm title="确定冻结吗?" @confirm="() => handleFrozen(record.id,2,record.username)">
                   <a>冻结</a>
                 </a-popconfirm>
               </a-menu-item>
 
               <a-menu-item v-if="record.status==2">
-                <a-popconfirm title="确定解冻吗?" @confirm="() => handleFrozen(record.id,1)">
+                <a-popconfirm title="确定解冻吗?" @confirm="() => handleFrozen(record.id,1,record.username)">
                   <a>解冻</a>
                 </a-popconfirm>
               </a-menu-item>
@@ -297,6 +298,16 @@
         } else {
           let ids = "";
           let that = this;
+          let isAdmin = false;
+          that.selectionRows.forEach(function (row) {
+            if (row.username == 'admin') {
+              isAdmin = true;
+            }
+          });
+          if (isAdmin) {
+            that.$message.warning('管理员账号不允许此操作,请重新选择!');
+            return;
+          }
           that.selectedRowKeys.forEach(function (val) {
             ids += val + ",";
           });
@@ -326,8 +337,13 @@
           this.batchFrozen(1);
         }
       },
-      handleFrozen: function (id, status) {
+      handleFrozen: function (id, status, username) {
         let that = this;
+        //TODO 后台校验管理员角色
+        if ('admin' == username) {
+          that.$message.warning('管理员账号不允许此操作!');
+          return;
+        }
         frozenBatch({ids: id, status: status}).then((res) => {
           if (res.success) {
             that.$message.success(res.message);

+ 1 - 1
ant-design-vue-jeecg/src/views/system/modules/DepartModal.vue

@@ -105,7 +105,7 @@
          mobile:{rules: [{validator:this.validateMobile}]}
         },
         url: {
-          add: "/sysdepart/sysDepart/add",
+          add: "/sys/sysDepart/add",
         },
       }
     },

+ 1 - 1
ant-design-vue-jeecg/src/views/system/modules/PermissionModal.vue

@@ -213,7 +213,7 @@
           component:{rules: [{ required: this.show, message: '请输入前端组件!' }]},
           url:{rules: [{ required: this.show, message: '请输入菜单路径!' }]},
           permsType:{rules: [{ required: true, message: '请输入授权策略!' }]},
-          sortNo:{rules: [{initialValue:1.0,validator: this.validateNumber}]},
+          sortNo:{initialValue:1.0,rules: [{validator: this.validateNumber}]},
         }
       }
     },

+ 49 - 14
ant-design-vue-jeecg/src/views/system/modules/QuartzJobModal.vue

@@ -8,7 +8,7 @@
     @cancel="handleCancel"
     okText="保存并安排任务"
     cancelText="关闭">
-    
+
     <a-spin :spinning="confirmLoading">
       <a-form :form="form">
 
@@ -23,11 +23,12 @@
           :labelCol="labelCol"
           :wrapperCol="wrapperCol"
           label="cron表达式">
-          <a-input placeholder="请输入cron表达式" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?',rules: [{ required: true, message: '请输入任务类名!' }]}]" />
-          <a target="_blank" href="http://cron.qqe2.com/">
-            <a-icon type="share-alt" />
-            在线cron表达式生成
-          </a>
+<!--                    <a-input placeholder="请输入cron表达式" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?',rules: [{ required: true, message: '请输入任务类名!' }]}]" />-->
+<!--                    <a target="_blank" href="http://cron.qqe2.com/">-->
+<!--                      <a-icon type="share-alt" />-->
+<!--                      在线cron表达式生成-->
+<!--                    </a>-->
+          <j-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?',rules: [{ required: true, message: '请输入cron表达式!' }]}]"  @change="setCorn"></j-cron>
         </a-form-item>
         <a-form-item
           :labelCol="labelCol"
@@ -52,7 +53,7 @@
             <a-radio-button :value="-1">停止</a-radio-button>
           </a-radio-group>
         </a-form-item>
-		
+
       </a-form>
     </a-spin>
   </a-modal>
@@ -60,11 +61,15 @@
 
 <script>
   import { httpAction } from '@/api/manage'
+  import JCron from "@/components/jeecg/JCron.vue";
   import pick from 'lodash.pick'
   import moment from "moment"
 
   export default {
     name: "QuartzJobModal",
+    components: {
+      JCron
+    },
     data () {
       return {
         title:"操作",
@@ -78,10 +83,18 @@
           xs: { span: 24 },
           sm: { span: 16 },
         },
-
+        cron: {
+          label: '',
+          value: ''
+        },
         confirmLoading: false,
         form: this.$form.createForm(this),
-        validatorRules:{
+        validatorRules: {
+          cron: {
+            rules: [{
+              required: true, message: '请输入cron表达式!'
+            }]
+          }
         },
         url: {
           add: "/sys/quartzJob/add",
@@ -96,8 +109,7 @@
         this.edit({});
       },
       edit (record) {
-        this.form.resetFields();
-        this.model = Object.assign({}, record);
+        this.model = Object.assign({},record);
         console.log(this.model)
         this.visible = true;
         this.$nextTick(() => {
@@ -113,7 +125,13 @@
         const that = this;
         // 触发表单验证
         this.form.validateFields((err, values) => {
+          console.log('values',values)
           if (!err) {
+            // if (typeof values.cronExpression == "undefined" || Object.keys(values.cronExpression).length==0 ) {
+            //   this.$message.warning('请输入cron表达式!');
+            //   return false;
+            // }
+
             that.confirmLoading = true;
             let httpurl = '';
             let method = '';
@@ -122,12 +140,12 @@
               method = 'post';
             }else{
               httpurl+=this.url.edit;
-               method = 'put';
+              method = 'put';
             }
             let formData = Object.assign(this.model, values);
             //时间格式化
-            
-            console.log(formData)
+
+            console.log('提交参数',formData)
             httpAction(httpurl,formData,method).then((res)=>{
               if(res.success){
                 that.$message.success(res.message);
@@ -146,7 +164,24 @@
       handleCancel () {
         this.close()
       },
+      setCorn(data){
+        console.log('data)',data);
+        this.$nextTick(() => {
+          this.model.cronExpression = data;
+        })
 
+        // console.log(Object.keys(data).length==0);
+        // if (Object.keys(data).length==0) {
+        //   this.$message.warning('请输入cron表达式!');
+        // }
+      },
+      validateCron(rule, value, callback){
+        if(!value){
+          callback()
+        }else if (Object.keys(value).length==0) {
+          callback("请输入cron表达式!");
+        }
+      },
 
     }
   }

+ 5 - 1
ant-design-vue-jeecg/src/views/system/modules/SelectUserListModal.vue

@@ -196,8 +196,12 @@
       getQueryParams(){
         let param = Object.assign({}, this.queryParam,this.isorter);
         param.field = this.getQueryField();
-        param.current = this.ipagination.current;
+        //--update-begin----author:scott---date:20190818------for:新建公告时指定特定用户翻页错误SelectUserListModal #379----
+        // param.current = this.ipagination.current;
+        // param.pageSize = this.ipagination.pageSize;
+        param.pageNo = this.ipagination.current;
         param.pageSize = this.ipagination.pageSize;
+        //--update-end----author:scott---date:20190818------for:新建公告时指定特定用户翻页错误SelectUserListModal #379---
         return filterObj(param);
       },
       getQueryField(){

+ 3 - 3
ant-design-vue-jeecg/src/views/system/modules/SysUserAgentModal.vue

@@ -99,9 +99,9 @@
           endTime:{rules: [{ required: true, message: '请输入代理结束时间!' }]},
         },
         url: {
-          add: "/system/sysUserAgent/add",
-          edit: "/system/sysUserAgent/edit",
-          queryByUserName:"/system/sysUserAgent/queryByUserName",
+          add: "/sys/sysUserAgent/add",
+          edit: "/sys/sysUserAgent/edit",
+          queryByUserName:"/sys/sysUserAgent/queryByUserName",
         },
       }
     },

+ 2 - 1
ant-design-vue-jeecg/src/views/system/modules/UserModal.vue

@@ -45,6 +45,7 @@
             mode="multiple"
             style="width: 100%"
             placeholder="请选择用户角色"
+            optionFilterProp = "children"
             v-model="selectedRole">
             <a-select-option v-for="(role,roleindex) in roleList" :key="roleindex.toString()" :value="role.id">
               {{ role.roleName }}
@@ -411,7 +412,7 @@
         if(!value){
           callback()
         }else{
-          if(new RegExp(/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/).test(value)){
+          if(new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/).test(value)){
             var params = {
               tableName: 'sys_user',
               fieldName: 'email',

+ 1 - 1
ant-design-vue-jeecg/src/views/user/Alteration.vue

@@ -1,5 +1,5 @@
 <template>
-  <a-card :bordered="false">
+  <a-card :bordered="false" style="width: 160%;text-align: center;margin-left:-25%">
     <a-steps class="steps" :current="currentTab">
       <a-step title="账户信息" />
       <a-step title="身份验证" />

+ 11 - 3
ant-design-vue-jeecg/src/views/user/Login.vue

@@ -85,12 +85,12 @@
 
       <a-form-item>
         <a-checkbox v-model="formLogin.rememberMe">自动登陆</a-checkbox>
-        <router-link :to="{ name: 'alteration'}" class="forge-password" style="float: right;">
+  <!--      <router-link :to="{ name: 'alteration'}" class="forge-password" style="float: right;">
           忘记密码
         </router-link>
         <router-link :to="{ name: 'register'}" class="forge-password" style="float: right;margin-right: 10px" >
           注册账户
-        </router-link>
+        </router-link>-->
       </a-form-item>
 
       <a-form-item style="margin-top:24px">
@@ -176,6 +176,8 @@
   import { putAction } from '@/api/manage'
   import { postAction } from '@/api/manage'
   import { encryption , getEncryptedString } from '@/utils/encryption/aesEncrypt'
+  import store from '@/store/'
+  import { USER_INFO } from "@/store/mutation-types"
 
   export default {
     components: {
@@ -342,7 +344,9 @@
         })
       },
       loginSuccess () {
-        this.loginBtn = false
+        // update-begin- author:sunjianlei --- date:20190812 --- for: 登录成功后不解除禁用按钮,防止多次点击
+        // this.loginBtn = false
+        // update-end- author:sunjianlei --- date:20190812 --- for: 登录成功后不解除禁用按钮,防止多次点击
         this.$router.push({ name: "dashboard" })
         this.$notification.success({
           message: '欢迎',
@@ -425,6 +429,10 @@
         }
         putAction("/sys/selectDepart",obj).then(res=>{
           if(res.success){
+            const userInfo = res.result.userInfo;
+            Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000);
+            store.commit('SET_INFO', userInfo);
+            //console.log("---切换组织机构---userInfo-------",store.getters.userInfo.orgCode);
             this.departClear()
             this.loginSuccess()
           }else{

+ 11 - 16
ant-design-vue-jeecg/src/views/user/Step1.vue

@@ -41,7 +41,7 @@
 <script>
   import JGraphicCode from '@/components/jeecg/JGraphicCode'
   import { getAction } from  '@/api/manage'
-  import {duplicateCheck } from '@/api/api'
+  import {checkOnlyUser } from '@/api/api'
   export default {
     name: "Step1",
     components: {
@@ -116,14 +116,12 @@
           callback("请输入用户名和手机号!");
         }
 
+        //判断用户输入账号还是手机号码
         if(reg.test(value)){
           var params = {
-            tableName: 'sys_user',
-            fieldName: 'phone',
-            fieldVal: value,
-            dataId: null
+            phone : value,
           };
-          duplicateCheck(params).then((res) => {
+          checkOnlyUser(params).then((res) => {
             if (res.success) {
             callback("用户名不存在!")
           } else {
@@ -132,18 +130,15 @@
         })
         }else{
           var params = {
-            tableName: 'sys_user',
-            fieldName: 'username',
-            fieldVal: value,
-            dataId: null
+            username: value,
           };
-          duplicateCheck(params).then((res) => {
+          checkOnlyUser(params).then((res) => {
             if (res.success) {
-            callback("用户名不存在!")
-          } else {
-            callback()
-          }
-        })
+              callback("用户名不存在!")
+            } else {
+              callback()
+            }
+          })
         }
       },
 

+ 5 - 5
ant-design-vue-jeecg/src/views/user/Step2.vue

@@ -30,22 +30,22 @@
       </a-input>
     </a-form-item>
       <a-form-item
+        label="验证码"
         :labelCol="{span: 5}"
         :wrapperCol="{span: 19}"
-       style="margin-left:106px"
         v-if="show">
-          <a-row :gutter="16" >
-            <a-col class="gutter-row" :span="16">
+          <a-row :gutter="16" style="margin-left: 35px">
+            <a-col class="gutter-row" :span="10">
                 <a-input
                   v-decorator="['captcha',validatorRules.captcha]"
                   type="text"
-                  placeholder="请输入验证码" >
+                  placeholder="手机短信验证码" >
                 </a-input>
             </a-col>
             <a-col class="gutter-row" :span="8" >
               <a-button
-                class="getCaptcha"
                 tabindex="-1"
+                size="default"
                 :disabled="state.smsSendBtn"
                 @click.stop.prevent="getCaptcha"
                 v-text="!state.smsSendBtn && '获取验证码' || (state.time+' s')"></a-button>

+ 6 - 2
ant-design-vue-jeecg/vue.config.js

@@ -21,8 +21,12 @@ module.exports = {
     }
   },
   */
-  configureWebpack: {},
-
+  configureWebpack: config => {
+    //生产环境取消 console.log
+    if (process.env.NODE_ENV === 'production') {
+      config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
+    }
+  },
   chainWebpack: (config) => {
     config.resolve.alias
       .set('@$', resolve('src'))

+ 37 - 0
ant-design-vue-jeecg/yarn.lock

@@ -841,6 +841,43 @@
     cssnano-preset-default "^4.0.0"
     postcss "^7.0.0"
 
+"@jeecg/antd-onine@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/@jeecg/antd-onine/-/antd-onine-1.0.1.tgz#bc9e54e75e8e0eff3ee361a1c022cb8672357a8d"
+  integrity sha512-l9HuoxX8sQ/XQZQfpTDnWuuqqfd2RY4EwOvf0SG45eOk6mDYzaR4P0BRT4QEC59lT3cRu5w5rKH2dn3RHEFzUw==
+  dependencies:
+    "@antv/data-set" "^0.10.2"
+    "@tinymce/tinymce-vue" "^2.0.0"
+    ant-design-vue "^1.3.9"
+    apexcharts "^3.6.5"
+    axios "^0.18.0"
+    clipboard "^2.0.4"
+    codemirror "^5.46.0"
+    dayjs "^1.8.0"
+    enquire.js "^2.1.6"
+    js-cookie "^2.2.0"
+    lodash.get "^4.4.2"
+    lodash.pick "^4.4.0"
+    md5 "^2.2.1"
+    nprogress "^0.2.0"
+    tinymce "^5.0.2"
+    viser-vue "^2.4.4"
+    vue "^2.6.10"
+    vue-apexcharts "^1.3.2"
+    vue-class-component "^6.0.0"
+    vue-cropper "^0.4.8"
+    vue-i18n "^8.7.0"
+    vue-loader "^15.7.0"
+    vue-ls "^3.2.0"
+    vue-photo-preview "^1.1.3"
+    vue-print-nb-jeecg "^1.0.8"
+    vue-property-decorator "^7.3.0"
+    vue-router "^3.0.1"
+    vue-splitpane "^1.0.4"
+    vuedraggable "^2.20.0"
+    vuex "^3.0.1"
+    vuex-class "^0.3.1"
+
 "@mrmlnc/readdir-enhanced@^2.2.1":
   version "2.2.1"
   resolved "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"

+ 1 - 1
jeecg-boot/README.md

@@ -1,7 +1,7 @@
 Jeecg-Boot 快速开发平台
 ===============
 
-当前最新版本: 2.0.2(发布日期:20190708
+当前最新版本: 2.1.0(发布日期:20190826
 
 
 ## 后端技术架构

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 2273
jeecg-boot/db/jeecg-boot-mysql-20190705.sql


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 3129 - 0
jeecg-boot/db/jeecg-boot-mysql-20190823.sql


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 3112
jeecg-boot/db/jeecg-boot-oracle_11g.sql


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 9967
jeecg-boot/db/jeecg-boot-sqlserver_2008.sql


+ 2 - 0
jeecg-boot/db/schema_mysql.sql

@@ -0,0 +1,2 @@
+-- 创建mysql库
+create database `jeecg-boot-os` default character set utf8mb4 collate utf8mb4_general_ci;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 45
jeecg-boot/db/增量升级SQL——mysql/jeecgboot2.0.1到2.0.2增量升级SQL


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 717 - 0
jeecg-boot/db/增量升级SQL——mysql/jeecgboot2.0.2到2.1增量升级.sql


+ 2 - 2
jeecg-boot/jeecg-boot-base-common/pom.xml

@@ -3,12 +3,12 @@
 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>jeecg-boot-base-common</artifactId>
-	<version>2.0.2</version>
+	<version>2.1.0</version>
 
 	<parent>
 		<groupId>org.jeecgframework.boot</groupId>
 		<artifactId>jeecg-boot-parent</artifactId>
-		<version>2.0.2</version>
+		<version>2.1.0</version>
 	</parent>
 	
 	<repositories>

+ 7 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/aspect/annotation/AutoLog.java

@@ -33,4 +33,11 @@ public @interface AutoLog {
 	 * @return 0:操作日志;1:登录日志;2:定时任务;
 	 */
 	int logType() default CommonConstant.LOG_TYPE_2;
+	
+	/**
+	 * 操作日志类型
+	 * 
+	 * @return (1查询,2添加,3修改,4删除)
+	 */
+	int operateType() default 0;
 }

+ 10 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/constant/CacheConstant.java

@@ -22,5 +22,15 @@ public interface CacheConstant {
 	 */
     public static final String LOGIN_USER_RULES_CACHE = "loginUser_cacheRules";
 
+	/**
+	 * 部门信息缓存
+	 */
+	public static final String DEPART_INFO_CACHE = "departCache_info";
+
+
+	/**
+	 * 部门id信息缓存
+	 */
+	public static final String DEPART_IDMODEL_CACHE = "departCache_idmodel";
 
 }

+ 31 - 1
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/constant/CommonConstant.java

@@ -26,11 +26,41 @@ public interface CommonConstant {
 	 * 系统日志类型: 登录
 	 */
 	public static final int LOG_TYPE_1 = 1;
-
+	
 	/**
 	 * 系统日志类型: 操作
 	 */
 	public static final int LOG_TYPE_2 = 2;
+
+	/**
+	 * 操作日志类型: 查询
+	 */
+	public static final int OPERATE_TYPE_1 = 1;
+	
+	/**
+	 * 操作日志类型: 添加
+	 */
+	public static final int OPERATE_TYPE_2 = 2;
+	
+	/**
+	 * 操作日志类型: 更新
+	 */
+	public static final int OPERATE_TYPE_3 = 3;
+	
+	/**
+	 * 操作日志类型: 删除
+	 */
+	public static final int OPERATE_TYPE_4 = 4;
+	
+	/**
+	 * 操作日志类型: 倒入
+	 */
+	public static final int OPERATE_TYPE_5 = 5;
+	
+	/**
+	 * 操作日志类型: 导出
+	 */
+	public static final int OPERATE_TYPE_6 = 6;
 	
 	
 	/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */

+ 5 - 2
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/constant/DataBaseConstant.java

@@ -9,15 +9,18 @@ public interface DataBaseConstant {
 	 * 数据-所属机构编码
 	 */
 	public static final String SYS_ORG_CODE = "sysOrgCode";
+	/**
+	 * 数据-所属机构编码
+	 */
+	public static final String SYS_ORG_CODE_TABLE = "sys_org_code";
 	/**
 	 * 数据-所属机构编码
 	 */
 	public static final String SYS_MULTI_ORG_CODE = "sysMultiOrgCode";
-	
 	/**
 	 * 数据-所属机构编码
 	 */
-	public static final String SYS_ORG_CODE_TABLE = "sys_org_code";
+	public static final String SYS_MULTI_ORG_CODE_TABLE = "sys_multi_org_code";
 	/**
 	 * 数据-系统用户编码(对应登录用户账号)
 	 */

+ 10 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java

@@ -7,6 +7,7 @@ import org.springframework.dao.DuplicateKeyException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
 import org.springframework.web.servlet.NoHandlerFoundException;
 
 import lombok.extern.slf4j.Slf4j;
@@ -64,5 +65,14 @@ public class JeecgBootExceptionHandler {
 		log.error(e.getMessage(), e);
 		return Result.error("没有权限,请联系管理员授权");
 	}
+	
+	 /** 
+	  * spring默认上传大小100MB 超出大小捕获异常MaxUploadSizeExceededException 
+	  */
+    @ExceptionHandler(MaxUploadSizeExceededException.class)
+    public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
+    	log.error(e.getMessage(), e);
+        return Result.error("文件大小超出10MB限制, 请压缩或降低文件质量! ");
+    }
 
 }

+ 1 - 1
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/query/QueryGenerator.java

@@ -55,7 +55,7 @@ public class QueryGenerator {
 	private static SimpleDateFormat getTime(){
 		SimpleDateFormat time = local.get();
 		if(time == null){
-			time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+			time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 			local.set(time);
 		}
 		return time;

+ 11 - 6
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/util/JwtUtil.java

@@ -155,7 +155,7 @@ public class JwtUtil {
 			}
 		}
 		//替换为系统登录用户真实名字
-		if (key.equals(DataBaseConstant.SYS_USER_NAME)|| key.equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
+		else if (key.equals(DataBaseConstant.SYS_USER_NAME)|| key.equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
 			if(user==null) {
 				returnValue = sysUser.getRealname();
 			}else {
@@ -164,7 +164,7 @@ public class JwtUtil {
 		}
 		
 		//替换为系统用户登录所使用的机构编码
-		if (key.equals(DataBaseConstant.SYS_ORG_CODE)|| key.equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
+		else if (key.equals(DataBaseConstant.SYS_ORG_CODE)|| key.equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
 			if(user==null) {
 				returnValue = sysUser.getOrgCode();
 			}else {
@@ -172,7 +172,7 @@ public class JwtUtil {
 			}
 		}
 		//替换为系统用户所拥有的所有机构编码
-		if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)|| key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)) {
+		else if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)|| key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE)) {
 			if(user.isOneDepart()) {
 				returnValue = user.getSysMultiOrgCode().get(0);
 			}else {
@@ -180,18 +180,23 @@ public class JwtUtil {
 			}
 		}
 		//替换为当前系统时间(年月日)
-		if (key.equals(DataBaseConstant.SYS_DATE)|| key.equals(DataBaseConstant.SYS_DATE_TABLE)) {
+		else if (key.equals(DataBaseConstant.SYS_DATE)|| key.equals(DataBaseConstant.SYS_DATE_TABLE)) {
 			returnValue = user.getSysDate();
 		}
 		//替换为当前系统时间(年月日时分秒)
-		if (key.equals(DataBaseConstant.SYS_TIME)|| key.equals(DataBaseConstant.SYS_TIME_TABLE)) {
+		else if (key.equals(DataBaseConstant.SYS_TIME)|| key.equals(DataBaseConstant.SYS_TIME_TABLE)) {
 			returnValue = user.getSysTime();
 		}
 		//流程状态默认值(默认未发起)
-		if (key.equals(DataBaseConstant.BPM_STATUS_TABLE)|| key.equals(DataBaseConstant.BPM_STATUS_TABLE)) {
+		else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.equals(DataBaseConstant.BPM_STATUS_TABLE)) {
 			returnValue = "1";
 		}
 		if(returnValue!=null){returnValue = returnValue + moshi;}
 		return returnValue;
 	}
+	
+	public static void main(String[] args) {
+		 String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjUzMzY1MTMsInVzZXJuYW1lIjoiYWRtaW4ifQ.xjhud_tWCNYBOg_aRlMgOdlZoWFFKB_givNElHNw3X0";
+		 System.out.println(JwtUtil.getUsername(token));
+	}
 }

+ 3 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/vo/DictModel.java

@@ -2,6 +2,8 @@ package org.jeecg.common.system.vo;
 
 import java.io.Serializable;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
@@ -9,6 +11,7 @@ import lombok.experimental.Accessors;
 @Data
 @EqualsAndHashCode(callSuper = false)
 @Accessors(chain = true)
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class DictModel implements Serializable{
 	private static final long serialVersionUID = 1L;
 

+ 5 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/system/vo/LoginUser.java

@@ -38,6 +38,11 @@ public class LoginUser {
 	 */
 	private String realname;
 
+	/**
+	 * 登录人密码
+	 */
+	private String password;
+
      /**
       * 当前登录部门code
       */

+ 66 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/DySmsEnum.java

@@ -0,0 +1,66 @@
+package org.jeecg.common.util;
+
+import org.apache.commons.lang.StringUtils;
+
+public enum DySmsEnum {
+	
+	LOGIN_TEMPLATE_CODE("SMS_167040816","JEECG","code"),
+	FORGET_PASSWORD_TEMPLATE_CODE("SMS_167040816","JEECG","code"),
+	REGISTER_TEMPLATE_CODE("SMS_144146309","JEECG","code");
+	
+	/**
+	 * 短信模板编码
+	 */
+	private String templateCode;
+	/**
+	 * 签名
+	 */
+	private String signName;
+	/**
+	 * 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验
+	 */
+	private String keys;
+	
+	private DySmsEnum(String templateCode,String signName,String keys) {
+		this.templateCode = templateCode;
+		this.signName = signName;
+		this.keys = keys;
+	}
+	
+	public String getTemplateCode() {
+		return templateCode;
+	}
+	
+	public void setTemplateCode(String templateCode) {
+		this.templateCode = templateCode;
+	}
+	
+	public String getSignName() {
+		return signName;
+	}
+	
+	public void setSignName(String signName) {
+		this.signName = signName;
+	}
+	
+	public String getKeys() {
+		return keys;
+	}
+
+	public void setKeys(String keys) {
+		this.keys = keys;
+	}
+
+	public static DySmsEnum toEnum(String templateCode) {
+		if(StringUtils.isEmpty(templateCode)){
+			return null;
+		}
+		for(DySmsEnum item : DySmsEnum.values()) {
+			if(item.getTemplateCode().equals(templateCode)) {
+				return item;
+			}
+		}
+		return null;
+	}
+}
+

+ 40 - 29
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/DySmsHelper.java

@@ -2,6 +2,8 @@ package org.jeecg.common.util;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
+import com.alibaba.fastjson.JSONObject;
 import com.aliyuncs.DefaultAcsClient;
 import com.aliyuncs.IAcsClient;
 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
@@ -31,32 +33,27 @@ public class DySmsHelper {
     static final String domain = "dysmsapi.aliyuncs.com";
 
     // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
-    static final String accessKeyId = "?";
-    static final String accessKeySecret = "?";
-    
-    /**
-         * 登陆时采用的短信发送模板编码
-     */
-    public static final String LOGIN_TEMPLATE_CODE="SMS_167040816";
-    
-    /**
-     * 忘记密码时采用的短信发送模板编码
-     */
-    public static final String FORGET_PASSWORD_TEMPLATE_CODE="SMS_167040816";
-    
-    
-    /**
-     * 注册时采用的短信发送模板编码
-     */
-    public static final String REGISTER_TEMPLATE_CODE="SMS_144146309";
-    
-    /**
-         * 必填:短信签名-可在短信控制台中找到
-     */
-    public static final String signName="JEECG";
+    static  String accessKeyId;
+    static  String accessKeySecret;
+
+    public static void setAccessKeyId(String accessKeyId) {
+        DySmsHelper.accessKeyId = accessKeyId;
+    }
+
+    public static void setAccessKeySecret(String accessKeySecret) {
+        DySmsHelper.accessKeySecret = accessKeySecret;
+    }
+
+    public static String getAccessKeyId() {
+        return accessKeyId;
+    }
+
+    public static String getAccessKeySecret() {
+        return accessKeySecret;
+    }
     
     
-    public static boolean sendSms(String phone,String code,String templateCode) throws ClientException {
+    public static boolean sendSms(String phone,JSONObject templateParamJson,DySmsEnum dySmsEnum) throws ClientException {
     	//可自助调整超时时间
         System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
         System.setProperty("sun.net.client.defaultReadTimeout", "10000");
@@ -66,16 +63,19 @@ public class DySmsHelper {
         DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
         IAcsClient acsClient = new DefaultAcsClient(profile);
         
+        //验证json参数
+        validateParam(templateParamJson,dySmsEnum);
+        
         //组装请求对象-具体描述见控制台-文档部分内容
         SendSmsRequest request = new SendSmsRequest();
         //必填:待发送手机号
         request.setPhoneNumbers(phone);
         //必填:短信签名-可在短信控制台中找到
-        request.setSignName(signName);
+        request.setSignName(dySmsEnum.getSignName());
         //必填:短信模板-可在短信控制台中找到
-        request.setTemplateCode("SMS_167040816");
+        request.setTemplateCode(dySmsEnum.getTemplateCode());
         //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
-        request.setTemplateParam("{\"code\":\""+code+"\"}");
+        request.setTemplateParam(templateParamJson.toJSONString());
         
         //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
         //request.setSmsUpExtendCode("90997");
@@ -96,10 +96,21 @@ public class DySmsHelper {
         
     }
     
+    private static void validateParam(JSONObject templateParamJson,DySmsEnum dySmsEnum) {
+    	String keys = dySmsEnum.getKeys();
+    	String [] keyArr = keys.split(",");
+    	for(String item :keyArr) {
+    		if(!templateParamJson.containsKey(item)) {
+    			throw new RuntimeException("模板缺少参数:"+item);
+    		}
+    	}
+    }
+    
 
     public static void main(String[] args) throws ClientException, InterruptedException {
-    	
-    	sendSms("13800138000", "123456", FORGET_PASSWORD_TEMPLATE_CODE);
+    	JSONObject obj = new JSONObject();
+    	obj.put("code", "1234");
+    	sendSms("13800138000", obj, DySmsEnum.FORGET_PASSWORD_TEMPLATE_CODE);
     	
     }
 }

+ 3 - 2
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/PasswordUtil.java

@@ -88,8 +88,9 @@ public class PasswordUtil {
 			Cipher cipher = Cipher.getInstance(ALGORITHM);
 
 			cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
-
-			encipheredData = cipher.doFinal(plaintext.getBytes());
+			//update-begin-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
+			encipheredData = cipher.doFinal(plaintext.getBytes("utf-8"));
+			//update-end-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
 		} catch (Exception e) {
 		}
 		return bytesToHexString(encipheredData);

+ 24 - 1
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java

@@ -62,7 +62,30 @@ public class SqlInjectionUtil {
 	 */
 	@Deprecated
 	public static void specialFilterContent(String value) {
-		String specialXssStr = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|,";
+		String specialXssStr = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|";
+		String[] xssArr = specialXssStr.split("\\|");
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+	
+	
+	/**
+	 * @特殊方法(不通用) 仅用于Online报表SQL解析,注入过滤
+	 * @param value
+	 * @return
+	 */
+	@Deprecated
+	public static void specialFilterContentForOnlineReport(String value) {
+		String specialXssStr = "exec |insert |delete |update |drop |chr |mid |master |truncate |char |declare |";
 		String[] xssArr = specialXssStr.split("\\|");
 		if (value == null || "".equals(value)) {
 			return;

+ 77 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/SecurityTools.java

@@ -0,0 +1,77 @@
+package org.jeecg.common.util.security;
+
+import cn.hutool.core.codec.Base64Decoder;
+import cn.hutool.core.codec.Base64Encoder;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import cn.hutool.crypto.asymmetric.Sign;
+import cn.hutool.crypto.asymmetric.SignAlgorithm;
+import cn.hutool.crypto.symmetric.AES;
+import cn.hutool.json.JSONObject;
+import org.jeecg.common.util.security.entity.*;
+
+import javax.crypto.SecretKey;
+import java.security.KeyPair;
+
+public class SecurityTools {
+    public static final String ALGORITHM = "AES/ECB/PKCS5Padding";
+
+    public static SecurityResp valid(SecurityReq req) {
+        SecurityResp resp=new SecurityResp();
+        String pubKey=req.getPubKey();
+        String aesKey=req.getAesKey();
+        String data=req.getData();
+        String signData=req.getSignData();
+        RSA rsa=new RSA(null, Base64Decoder.decode(pubKey));
+        Sign sign= new Sign(SignAlgorithm.SHA1withRSA,null,pubKey);
+
+
+
+        byte[] decryptAes = rsa.decrypt(aesKey, KeyType.PublicKey);
+        //log.info("rsa解密后的秘钥"+ Base64Encoder.encode(decryptAes));
+        AES aes = SecureUtil.aes(decryptAes);
+
+        String dencrptValue =aes.decryptStr(data);
+        //log.info("解密后报文"+dencrptValue);
+        resp.setData(new JSONObject(dencrptValue));
+
+        boolean verify = sign.verify(dencrptValue.getBytes(), Base64Decoder.decode(signData));
+        resp.setSuccess(verify);
+        return resp;
+    }
+
+    public static SecuritySignResp sign(SecuritySignReq req) {
+        SecretKey secretKey = SecureUtil.generateKey(ALGORITHM);
+        byte[] key= secretKey.getEncoded();
+        String prikey=req.getPrikey();
+        String data=req.getData();
+
+        AES aes = SecureUtil.aes(key);
+        aes.getSecretKey().getEncoded();
+        String encrptData =aes.encryptBase64(data);
+        RSA rsa=new RSA(prikey,null);
+        byte[] encryptAesKey = rsa.encrypt(secretKey.getEncoded(), KeyType.PrivateKey);
+        //log.info(("rsa加密过的秘钥=="+Base64Encoder.encode(encryptAesKey));
+
+        Sign sign= new Sign(SignAlgorithm.SHA1withRSA,prikey,null);
+        byte[] signed = sign.sign(data.getBytes());
+
+        //log.info(("签名数据===》》"+Base64Encoder.encode(signed));
+
+        SecuritySignResp resp=new SecuritySignResp();
+        resp.setAesKey(Base64Encoder.encode(encryptAesKey));
+        resp.setData(encrptData);
+        resp.setSignData(Base64Encoder.encode(signed));
+        return resp;
+    }
+    public static MyKeyPair generateKeyPair(){
+        KeyPair keyPair= SecureUtil.generateKeyPair(SignAlgorithm.SHA1withRSA.getValue(),2048);
+        String priKey= Base64Encoder.encode(keyPair.getPrivate().getEncoded());
+        String pubkey= Base64Encoder.encode(keyPair.getPublic().getEncoded());
+        MyKeyPair resp=new MyKeyPair();
+        resp.setPriKey(priKey);
+        resp.setPubKey(pubkey);
+        return resp;
+    }
+}

+ 9 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/MyKeyPair.java

@@ -0,0 +1,9 @@
+package org.jeecg.common.util.security.entity;
+
+import lombok.Data;
+
+@Data
+public class MyKeyPair {
+    private String priKey;
+    private String pubKey;
+}

+ 11 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecurityReq.java

@@ -0,0 +1,11 @@
+package org.jeecg.common.util.security.entity;
+
+import lombok.Data;
+
+@Data
+public class SecurityReq {
+    private String data;
+    private String pubKey;
+    private String signData;
+    private String aesKey;
+}

+ 10 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecurityResp.java

@@ -0,0 +1,10 @@
+package org.jeecg.common.util.security.entity;
+
+import cn.hutool.json.JSONObject;
+import lombok.Data;
+
+@Data
+public class SecurityResp {
+    private Boolean success;
+    private JSONObject data;
+}

+ 9 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecuritySignReq.java

@@ -0,0 +1,9 @@
+package org.jeecg.common.util.security.entity;
+
+import lombok.Data;
+
+@Data
+public class SecuritySignReq {
+    private String data;
+    private String prikey;
+}

+ 10 - 0
jeecg-boot/jeecg-boot-base-common/src/main/java/org/jeecg/common/util/security/entity/SecuritySignResp.java

@@ -0,0 +1,10 @@
+package org.jeecg.common.util.security.entity;
+
+import lombok.Data;
+
+@Data
+public class SecuritySignResp {
+    private String data;
+    private String signData;
+    private String aesKey;
+}

+ 4 - 0
jeecg-boot/jeecg-boot-module-system/.gitattributes

@@ -0,0 +1,4 @@
+*.js linguist-language=Java
+*.css linguist-language=Java
+*.html linguist-language=Java
+*.vue linguist-language=Java

+ 20 - 2
jeecg-boot/jeecg-boot-module-system/pom.xml

@@ -3,12 +3,12 @@
 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>jeecg-boot-module-system</artifactId>
-	<version>2.0.2</version>
+	<version>2.1.0</version>
 
 	<parent>
 		<groupId>org.jeecgframework.boot</groupId>
 		<artifactId>jeecg-boot-parent</artifactId>
-		<version>2.0.2</version>
+		<version>2.1.0</version>
 	</parent>
 
 	<repositories>
@@ -35,6 +35,24 @@
 			<groupId>org.jeecgframework.boot</groupId>
 			<artifactId>jeecg-boot-base-common</artifactId>
 		</dependency>
+		<!--  online form-->
+		<dependency>
+			<groupId>org.jeecgframework.boot</groupId>
+			<artifactId>online-form</artifactId>
+			<version>1.0.2</version>
+		</dependency>
+		<dependency>
+			<groupId>org.hibernate</groupId>
+			<artifactId>hibernate-core</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>commons-collections</groupId>
+					<artifactId>commons-collections</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<!--  online form -->
+
 	</dependencies>
 	
 	<build>

+ 19 - 22
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/JeecgApplication.java

@@ -1,37 +1,34 @@
 package org.jeecg;
 
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.core.env.Environment;
-
-import lombok.extern.slf4j.Slf4j;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
 @Slf4j
 @EnableSwagger2
 @SpringBootApplication
 public class JeecgApplication {
 
-  public static void main(String[] args) throws UnknownHostException {
-    //System.setProperty("spring.devtools.restart.enabled", "true");
-
-    ConfigurableApplicationContext application = SpringApplication.run(JeecgApplication.class, args);
-    Environment env = application.getEnvironment();
-    String ip = InetAddress.getLocalHost().getHostAddress();
-    String port = env.getProperty("server.port");
-    String path = env.getProperty("server.servlet.context-path");
-    log.info("\n----------------------------------------------------------\n\t" +
-        "Application Jeecg-Boot is running! Access URLs:\n\t" +
-        "Local: \t\thttp://localhost:" + port + path + "/\n\t" +
-        "External: \thttp://" + ip + ":" + port + path + "/\n\t" +
-        "swagger-ui: \thttp://" + ip + ":" + port + path + "/swagger-ui.html\n\t" +
-        "Doc: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
-        "----------------------------------------------------------");
+    public static void main(String[] args) throws UnknownHostException {
+        //System.setProperty("spring.devtools.restart.enabled", "true");
 
-  }
-  
+        ConfigurableApplicationContext application = SpringApplication.run(JeecgApplication.class, args);
+        Environment env = application.getEnvironment();
+        String ip = InetAddress.getLocalHost().getHostAddress();
+        String port = env.getProperty("server.port");
+        String path = env.getProperty("server.servlet.context-path");
+        log.info("\n----------------------------------------------------------\n\t" +
+                "Application Jeecg-Boot is running! Access URLs:\n\t" +
+                "Local: \t\thttp://localhost:" + port + path + "/\n\t" +
+                "External: \thttp://" + ip + ":" + port + path + "/\n\t" +
+                "swagger-ui: \thttp://" + ip + ":" + port + path + "/swagger-ui.html\n\t" +
+                "Doc: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
+                "----------------------------------------------------------");
+    }
 }

+ 10 - 3
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/RedisConfig.java

@@ -17,7 +17,9 @@ import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.cache.RedisCacheWriter;
 import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
@@ -82,13 +84,18 @@ public class RedisConfig extends CachingConfigurerSupport {
 	 */
 	@Bean
 	public CacheManager cacheManager(LettuceConnectionFactory factory) {
+        // 配置序列化
+        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
+        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
+                												.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
+        
 		// 以锁写入的方式创建RedisCacheWriter对象
-		RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
+		//RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
 		// 创建默认缓存配置对象
 		/* 默认配置,设置缓存有效期 1小时*/
-		RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
+		//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
 		/* 配置test的超时时间为120s*/
-		RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(lettuceConnectionFactory)).cacheDefaults(defaultCacheConfig)
+		RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration)
 				.withInitialCacheConfigurations(singletonMap("test", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120)).disableCachingNullValues()))
 				.transactionAware().build();
 		return cacheManager;

+ 23 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/ShiroConfig.java

@@ -42,8 +42,11 @@ public class ShiroConfig {
 		shiroFilterFactoryBean.setSecurityManager(securityManager);
 		// 拦截器
 		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
+		//cas验证登录
+		filterChainDefinitionMap.put("/cas/client/validateLogin", "anon");
 		// 配置不会被拦截的链接 顺序判断
 		filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
+		filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
 		filterChainDefinitionMap.put("/sys/getEncryptedString", "anon"); //获取加密串
 		filterChainDefinitionMap.put("/sys/sms", "anon");//短信验证码
 		filterChainDefinitionMap.put("/sys/phoneLogin", "anon");//手机登录		
@@ -63,9 +66,16 @@ public class ShiroConfig {
 		filterChainDefinitionMap.put("/**/*.css", "anon");
 		filterChainDefinitionMap.put("/**/*.html", "anon");
 		filterChainDefinitionMap.put("/**/*.svg", "anon");
+		filterChainDefinitionMap.put("/**/*.pdf", "anon");
 		filterChainDefinitionMap.put("/**/*.jpg", "anon");
 		filterChainDefinitionMap.put("/**/*.png", "anon");
 		filterChainDefinitionMap.put("/**/*.ico", "anon");
+
+		// update-begin--Author:sunjianlei Date:20190813 for:排除字体格式的后缀
+		filterChainDefinitionMap.put("/**/*.ttf", "anon");
+		filterChainDefinitionMap.put("/**/*.woff", "anon");
+		// update-begin--Author:sunjianlei Date:20190813 for:排除字体格式的后缀
+
 		filterChainDefinitionMap.put("/druid/**", "anon");
 		filterChainDefinitionMap.put("/swagger-ui.html", "anon");
 		filterChainDefinitionMap.put("/swagger**/**", "anon");
@@ -76,7 +86,20 @@ public class ShiroConfig {
 		filterChainDefinitionMap.put("/actuator/metrics/**", "anon");
 		filterChainDefinitionMap.put("/actuator/httptrace/**", "anon");
 		filterChainDefinitionMap.put("/actuator/redis/**", "anon");
+
+
+		filterChainDefinitionMap.put("/test/jeecgDemo/demo3", "anon"); //模板测试
+		filterChainDefinitionMap.put("/test/jeecgDemo/redisDemo/**", "anon"); //redis测试
 		
+
+
+		//排除Online请求
+		filterChainDefinitionMap.put("/auto/cgform/**", "anon");
+		//websocket排除
+		filterChainDefinitionMap.put("/websocket/**", "anon");
+		
+		
+	
 		// 添加自己的过滤器并且取名为jwt
 		Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
 		filterMap.put("jwt", new JwtFilter());

+ 25 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/StaticConfig.java

@@ -0,0 +1,25 @@
+package org.jeecg.config;
+
+import org.jeecg.common.util.DySmsHelper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 设置静态参数初始化
+ */
+@Configuration
+public class StaticConfig {
+
+    @Value("${jeecg.sms.accessKeyId}")
+    private String accessKeyId;
+
+    @Value("${jeecg.sms.accessKeySecret}")
+    private String accessKeySecret;
+
+    @Bean
+    public void initStatic() {
+        DySmsHelper.setAccessKeyId(accessKeyId);
+        DySmsHelper.setAccessKeySecret(accessKeySecret);
+    }
+}

+ 18 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/WebSocketConfig.java

@@ -0,0 +1,18 @@
+package org.jeecg.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+    /**
+     * 	注入ServerEndpointExporter,
+     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
+     */
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+    
+}

+ 2 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/mybatis/MybatisInterceptor.java

@@ -106,11 +106,13 @@ public class MybatisInterceptor implements Interceptor {
 			Field[] fields = null;
 			if (parameter instanceof ParamMap) {
 				ParamMap<?> p = (ParamMap<?>) parameter;
+				//update-begin-author:scott date:20190729 for:批量更新报错issues/IZA3Q--
 				if (p.containsKey("et")) {
 					parameter = p.get("et");
 				} else {
 					parameter = p.get("param1");
 				}
+				//update-end-author:scott date:20190729 for:批量更新报错issues/IZA3Q-
 				fields = oConvertUtils.getAllFields(parameter);
 			} else {
 				fields = oConvertUtils.getAllFields(parameter);

+ 110 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/cas/controller/CasClientController.java

@@ -0,0 +1,110 @@
+package org.jeecg.modules.cas.controller;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.util.JwtUtil;
+import org.jeecg.common.util.RedisUtil;
+import org.jeecg.modules.cas.util.CASServiceUtil;
+import org.jeecg.modules.cas.util.XmlUtils;
+import org.jeecg.modules.system.entity.SysDepart;
+import org.jeecg.modules.system.entity.SysUser;
+import org.jeecg.modules.system.service.ISysDepartService;
+import org.jeecg.modules.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.alibaba.fastjson.JSONObject;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * <p>
+ * CAS单点登录客户端登录认证
+ * </p>
+ *
+ * @Author zhoujf
+ * @since 2018-12-20
+ */
+@Slf4j
+@RestController
+@RequestMapping("/cas/client")
+public class CasClientController {
+
+	@Autowired
+	private ISysUserService sysUserService;
+	@Autowired
+    private ISysDepartService sysDepartService;
+	@Autowired
+    private RedisUtil redisUtil;
+	
+	@Value("${cas.prefixUrl}")
+    private String prefixUrl;
+	
+	
+	@GetMapping("/validateLogin")
+	public Object validateLogin(@RequestParam(name="ticket") String ticket,
+								@RequestParam(name="service") String service,
+								HttpServletRequest request,
+								HttpServletResponse response) throws Exception {
+		Result<JSONObject> result = new Result<JSONObject>();
+		log.info("Rest api login.");
+		try {
+			String validateUrl = prefixUrl+"/p3/serviceValidate";
+			String res = CASServiceUtil.getSTValidate(validateUrl, ticket, service);
+			log.info("res."+res);
+			final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
+			if(StringUtils.isNotEmpty(error)) {
+				throw new Exception(error);
+			}
+			final String principal = XmlUtils.getTextForElement(res, "user");
+			if (StringUtils.isEmpty(principal)) {
+	            throw new Exception("No principal was found in the response from the CAS server.");
+	        }
+			log.info("-------token----username---"+principal);
+		    //1. 校验用户是否有效
+	  		SysUser sysUser = sysUserService.getUserByName(principal);
+	  		result = sysUserService.checkUserIsEffective(sysUser);
+	  		if(!result.isSuccess()) {
+	  			return result;
+	  		}
+	 		String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword());
+	 		redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
+	 		// 设置超时时间
+	 		redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
+	  	// 获取用户部门信息
+			JSONObject obj = new JSONObject();
+			List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
+			obj.put("departs", departs);
+			if (departs == null || departs.size() == 0) {
+				obj.put("multi_depart", 0);
+			} else if (departs.size() == 1) {
+				sysUserService.updateUserDepart(principal, departs.get(0).getOrgCode());
+				obj.put("multi_depart", 1);
+			} else {
+				obj.put("multi_depart", 2);
+			}
+			obj.put("token", token);
+			obj.put("userInfo", sysUser);
+			result.setResult(obj);
+			result.success("登录成功");
+	  		
+		} catch (Exception e) {
+			//e.printStackTrace();
+			result.error500(e.getMessage());
+		}
+		return new HttpEntity<>(result);
+	}
+
+	
+}

+ 103 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/cas/util/CASServiceUtil.java

@@ -0,0 +1,103 @@
+package org.jeecg.modules.cas.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+public class CASServiceUtil {
+	
+	public static void main(String[] args) {
+		String serviceUrl = "https://cas.8f8.com.cn:8443/cas/p3/serviceValidate";
+		String service = "http://localhost:3003/user/login";
+		String ticket = "ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3";
+		String res = getSTValidate(serviceUrl,ticket, service);
+		
+		System.out.println("---------res-----"+res);
+	}
+	
+	
+	/**
+     * 验证ST
+     */
+    public static String getSTValidate(String url,String st, String service){
+		try {
+			url = url+"?service="+service+"&ticket="+st;
+			CloseableHttpClient httpclient = createHttpClientWithNoSsl();
+			HttpGet httpget = new HttpGet(url);
+			HttpResponse response = httpclient.execute(httpget);
+	        String res = readResponse(response);
+	        return res == null ? null : (res == "" ? null : res);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return "";
+	}
+
+    
+    /**
+     * 读取 response body 内容为字符串
+     *
+     * @param response
+     * @return
+     * @throws IOException
+     */
+    private static String readResponse(HttpResponse response) throws IOException {
+        BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
+        String result = new String();
+        String line;
+        while ((line = in.readLine()) != null) {
+            result += line;
+        }
+        return result;
+    }
+    
+    
+    /**
+     * 创建模拟客户端(针对 https 客户端禁用 SSL 验证)
+     *
+     * @param cookieStore 缓存的 Cookies 信息
+     * @return
+     * @throws Exception
+     */
+    private static CloseableHttpClient createHttpClientWithNoSsl() throws Exception {
+        // Create a trust manager that does not validate certificate chains
+        TrustManager[] trustAllCerts = new TrustManager[]{
+                new X509TrustManager() {
+                    @Override
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+
+                    @Override
+                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+
+                    @Override
+                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+                }
+        };
+
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, trustAllCerts, null);
+        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
+        return HttpClients.custom()
+                .setSSLSocketFactory(sslSocketFactory)
+                .build();
+    }
+
+}

+ 292 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/cas/util/XmlUtils.java

@@ -0,0 +1,292 @@
+package org.jeecg.modules.cas.util;
+
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 解析cas,ST验证后的xml
+ *
+ */
+@Slf4j
+public final class XmlUtils {
+
+    /**
+     * Creates a new namespace-aware DOM document object by parsing the given XML.
+     *
+     * @param xml XML content.
+     *
+     * @return DOM document.
+     */
+    public static Document newDocument(final String xml) {
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        final Map<String, Boolean> features = new HashMap<String, Boolean>();
+        features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        for (final Map.Entry<String, Boolean> entry : features.entrySet()) {
+            try {
+                factory.setFeature(entry.getKey(), entry.getValue());
+            } catch (ParserConfigurationException e) {
+                log.warn("Failed setting XML feature {}: {}", entry.getKey(), e);
+            }
+        }
+        factory.setNamespaceAware(true);
+        try {
+            return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
+        } catch (Exception e) {
+            throw new RuntimeException("XML parsing error: " + e);
+        }
+    }
+
+    /**
+     * Get an instance of an XML reader from the XMLReaderFactory.
+     *
+     * @return the XMLReader.
+     */
+    public static XMLReader getXmlReader() {
+        try {
+            final XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+            reader.setFeature("http://xml.org/sax/features/namespaces", true);
+            reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+            reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+            return reader;
+        } catch (final Exception e) {
+            throw new RuntimeException("Unable to create XMLReader", e);
+        }
+    }
+
+
+    /**
+     * Retrieve the text for a group of elements. Each text element is an entry
+     * in a list.
+     * <p>This method is currently optimized for the use case of two elements in a list.
+     *
+     * @param xmlAsString the xml response
+     * @param element     the element to look for
+     * @return the list of text from the elements.
+     */
+    public static List<String> getTextForElements(final String xmlAsString, final String element) {
+        final List<String> elements = new ArrayList<String>(2);
+        final XMLReader reader = getXmlReader();
+
+        final DefaultHandler handler = new DefaultHandler() {
+
+            private boolean foundElement = false;
+
+            private StringBuilder buffer = new StringBuilder();
+
+            public void startElement(final String uri, final String localName, final String qName,
+                    final Attributes attributes) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = true;
+                }
+            }
+
+            public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = false;
+                    elements.add(this.buffer.toString());
+                    this.buffer = new StringBuilder();
+                }
+            }
+
+            public void characters(char[] ch, int start, int length) throws SAXException {
+                if (this.foundElement) {
+                    this.buffer.append(ch, start, length);
+                }
+            }
+        };
+
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+
+        try {
+            reader.parse(new InputSource(new StringReader(xmlAsString)));
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+
+        return elements;
+    }
+
+    /**
+     * Retrieve the text for a specific element (when we know there is only
+     * one).
+     *
+     * @param xmlAsString the xml response
+     * @param element     the element to look for
+     * @return the text value of the element.
+     */
+    public static String getTextForElement(final String xmlAsString, final String element) {
+        final XMLReader reader = getXmlReader();
+        final StringBuilder builder = new StringBuilder();
+
+        final DefaultHandler handler = new DefaultHandler() {
+
+            private boolean foundElement = false;
+
+            public void startElement(final String uri, final String localName, final String qName,
+                    final Attributes attributes) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = true;
+                }
+            }
+
+            public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+                if (localName.equals(element)) {
+                    this.foundElement = false;
+                }
+            }
+
+            public void characters(char[] ch, int start, int length) throws SAXException {
+                if (this.foundElement) {
+                    builder.append(ch, start, length);
+                }
+            }
+        };
+
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+
+        try {
+            reader.parse(new InputSource(new StringReader(xmlAsString)));
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+
+        return builder.toString();
+    }
+    
+    
+    public static Map<String, Object> extractCustomAttributes(final String xml) {
+        final SAXParserFactory spf = SAXParserFactory.newInstance();
+        spf.setNamespaceAware(true);
+        spf.setValidating(false);
+        try {
+            final SAXParser saxParser = spf.newSAXParser();
+            final XMLReader xmlReader = saxParser.getXMLReader();
+            final CustomAttributeHandler handler = new CustomAttributeHandler();
+            xmlReader.setContentHandler(handler);
+            xmlReader.parse(new InputSource(new StringReader(xml)));
+            return handler.getAttributes();
+        } catch (final Exception e) {
+        	log.error(e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
+    
+    private static class CustomAttributeHandler extends DefaultHandler {
+
+        private Map<String, Object> attributes;
+
+        private boolean foundAttributes;
+
+        private String currentAttribute;
+
+        private StringBuilder value;
+
+        @Override
+        public void startDocument() throws SAXException {
+            this.attributes = new HashMap<String, Object>();
+        }
+
+        @Override
+        public void startElement(final String namespaceURI, final String localName, final String qName,
+                final Attributes attributes) throws SAXException {
+            if ("attributes".equals(localName)) {
+                this.foundAttributes = true;
+            } else if (this.foundAttributes) {
+                this.value = new StringBuilder();
+                this.currentAttribute = localName;
+            }
+        }
+
+        @Override
+        public void characters(final char[] chars, final int start, final int length) throws SAXException {
+            if (this.currentAttribute != null) {
+                value.append(chars, start, length);
+            }
+        }
+
+        @Override
+        public void endElement(final String namespaceURI, final String localName, final String qName)
+                throws SAXException {
+            if ("attributes".equals(localName)) {
+                this.foundAttributes = false;
+                this.currentAttribute = null;
+            } else if (this.foundAttributes) {
+                final Object o = this.attributes.get(this.currentAttribute);
+
+                if (o == null) {
+                    this.attributes.put(this.currentAttribute, this.value.toString());
+                } else {
+                    final List<Object> items;
+                    if (o instanceof List) {
+                        items = (List<Object>) o;
+                    } else {
+                        items = new LinkedList<Object>();
+                        items.add(o);
+                        this.attributes.put(this.currentAttribute, items);
+                    }
+                    items.add(this.value.toString());
+                }
+            }
+        }
+
+        public Map<String, Object> getAttributes() {
+            return this.attributes;
+        }
+    }
+    
+    
+    public static void main(String[] args) {
+		String result = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\r\n" + 
+				"    <cas:authenticationSuccess>\r\n" + 
+				"        <cas:user>admin</cas:user>\r\n" + 
+				"        <cas:attributes>\r\n" + 
+				"            <cas:credentialType>UsernamePasswordCredential</cas:credentialType>\r\n" + 
+				"            <cas:isFromNewLogin>true</cas:isFromNewLogin>\r\n" + 
+				"            <cas:authenticationDate>2019-08-01T19:33:21.527+08:00[Asia/Shanghai]</cas:authenticationDate>\r\n" + 
+				"            <cas:authenticationMethod>RestAuthenticationHandler</cas:authenticationMethod>\r\n" + 
+				"            <cas:successfulAuthenticationHandlers>RestAuthenticationHandler</cas:successfulAuthenticationHandlers>\r\n" + 
+				"            <cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>\r\n" + 
+				"        </cas:attributes>\r\n" + 
+				"    </cas:authenticationSuccess>\r\n" + 
+				"</cas:serviceResponse>";
+		
+		String errorRes = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\r\n" + 
+				"    <cas:authenticationFailure code=\"INVALID_TICKET\">未能够识别出目标 &#39;ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3&#39;票根</cas:authenticationFailure>\r\n" + 
+				"</cas:serviceResponse>";
+		
+		String error = XmlUtils.getTextForElement(errorRes, "authenticationFailure");
+		System.out.println("------"+error);
+		
+		String error2 = XmlUtils.getTextForElement(result, "authenticationFailure");
+		System.out.println("------"+error2);
+		String principal = XmlUtils.getTextForElement(result, "user");
+		System.out.println("---principal---"+principal);
+		Map<String, Object> attributes = XmlUtils.extractCustomAttributes(result);
+		System.out.println("---attributes---"+attributes);
+	}
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/demo/mock/json/area_mini.json


+ 3 - 1
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/demo/test/controller/JeecgDemoController.java

@@ -11,6 +11,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.AutoLog;
 import org.jeecg.common.aspect.annotation.PermissionData;
+import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.base.controller.JeecgController;
 import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.util.DateUtils;
@@ -143,7 +144,8 @@ public class JeecgDemoController extends JeecgController<JeecgDemo,IJeecgDemoSer
 	 */
 	@PutMapping(value = "/edit")
 	@ApiOperation(value = "编辑DEMO", notes = "编辑DEMO")
-	public Result<JeecgDemo> eidt(@RequestBody JeecgDemo jeecgDemo) {
+	@AutoLog(value = "编辑DEMO",operateType= CommonConstant.OPERATE_TYPE_3)
+	public Result<JeecgDemo> edit(@RequestBody JeecgDemo jeecgDemo) {
 		Result<JeecgDemo> result = new Result<JeecgDemo>();
 		JeecgDemo jeecgDemoEntity = jeecgDemoService.getById(jeecgDemo.getId());
 		if (jeecgDemoEntity == null) {

+ 0 - 0
jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/modules/message/entity/SysMessage.java


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.