xuzuoyun il y a 4 ans
Parent
commit
414c0b3be6
100 fichiers modifiés avec 14313 ajouts et 2963 suppressions
  1. 1 2
      README.md
  2. 9 0
      adsp-vue-new.iml
  3. 132 126
      package.json
  4. 6953 0
      public/cdn/babel-polyfill/polyfill_7_2_5.js
  5. 30 0
      src/api/GroupRequest.js
  6. 87 80
      src/api/api.js
  7. 14 0
      src/api/login.js
  8. 131 71
      src/api/manage.js
  9. BIN
      src/assets/checkcode.png
  10. 259 0
      src/assets/less/JAreaLinkage.less
  11. 15 0
      src/assets/less/TableExpand.less
  12. 19 14
      src/assets/less/common.less
  13. 9 9
      src/cas/sso.js
  14. 12 30
      src/components/AvatarList/List.vue
  15. 2 24
      src/components/README.md
  16. 79 0
      src/components/_util/Area.js
  17. 13 10
      src/components/chart/BarAndLine.vue
  18. 25 27
      src/components/chart/BarMultid.vue
  19. 22 22
      src/components/chart/DashChartDemo.vue
  20. 33 16
      src/components/chart/LineChartMultid.vue
  21. 3 3
      src/components/chart/Liquid.vue
  22. 3 3
      src/components/chart/MiniArea.vue
  23. 1 1
      src/components/chart/MiniBar.vue
  24. 1 2
      src/components/chart/MiniProgress.vue
  25. 9 6
      src/components/chart/Pie.vue
  26. 54 0
      src/components/chart/StackBar.vue
  27. 13 0
      src/components/chart/chart.less
  28. 10 0
      src/components/chart/mixins/ChartMixins.js
  29. 39 17
      src/components/dict/JDictSelectTag.vue
  30. 82 27
      src/components/dict/JDictSelectUtil.js
  31. 32 21
      src/components/dict/JMultiSelectTag.vue
  32. 86 60
      src/components/dict/JSearchSelectTag.vue
  33. 1 0
      src/components/dict/README.md
  34. 156 0
      src/components/jeecg/JAreaLinkage.vue
  35. 1 3
      src/components/jeecg/JCategorySelect.vue
  36. 79 66
      src/components/jeecg/JCodeEditor.vue
  37. 16 17
      src/components/jeecg/JDate.vue
  38. 1787 490
      src/components/jeecg/JEditableTable.vue
  39. 27 14
      src/components/jeecg/JEditor.vue
  40. 25 14
      src/components/jeecg/JFormContainer.vue
  41. 201 0
      src/components/jeecg/JImageUpload.vue
  42. 35 23
      src/components/jeecg/JImportModal.vue
  43. 6 1
      src/components/jeecg/JInput.vue
  44. 225 0
      src/components/jeecg/JModal/index.vue
  45. 17 16
      src/components/jeecg/JSelectMultiple.vue
  46. 43 47
      src/components/jeecg/JSlider.vue
  47. 578 95
      src/components/jeecg/JSuperQuery.vue
  48. 57 0
      src/components/jeecg/JSwitch.vue
  49. 80 76
      src/components/jeecg/JTreeDict.vue
  50. 138 87
      src/components/jeecg/JTreeSelect.vue
  51. 52 12
      src/components/jeecg/JTreeTable.vue
  52. 343 83
      src/components/jeecg/JUpload.vue
  53. 3 0
      src/components/jeecg/README.md
  54. 101 41
      src/components/jeecg/README_JEditableTable.md
  55. 4 2
      src/components/jeecg/index.js
  56. 113 0
      src/components/jeecg/minipop/JFilePop.vue
  57. 98 0
      src/components/jeecg/minipop/JInputPop.vue
  58. 21 2
      src/components/jeecgbiz/JSelectBizComponent/JSelectBizComponentModal.vue
  59. 1 1
      src/components/jeecgbiz/JSelectBizComponent/README.md
  60. 17 10
      src/components/jeecgbiz/JSelectBizComponent/index.vue
  61. 45 45
      src/components/jeecgbiz/JSelectDepart.vue
  62. 31 50
      src/components/jeecgbiz/JSelectMultiUser.vue
  63. 1 1
      src/components/jeecgbiz/JSelectPosition.vue
  64. 1 1
      src/components/jeecgbiz/JSelectRole.vue
  65. 31 21
      src/components/jeecgbiz/JSelectUserByDep.vue
  66. 108 84
      src/components/jeecgbiz/modal/JSelectDepartModal.vue
  67. 257 252
      src/components/jeecgbiz/modal/JSelectUserByDepModal.vue
  68. 2 2
      src/components/jeecgbiz/modal/SelectUserListModal.vue
  69. 23 8
      src/components/layouts/IframePageView.vue
  70. 137 87
      src/components/layouts/TabLayout.vue
  71. 45 45
      src/components/menu/Contextmenu.vue
  72. 31 14
      src/components/menu/SideMenu.vue
  73. 35 80
      src/components/menu/index.js
  74. 2 2
      src/components/page/GlobalHeader.vue
  75. 35 18
      src/components/page/GlobalLayout.vue
  76. 34 38
      src/components/setting/SettingDrawer.vue
  77. 26 26
      src/components/table/StandardTable.vue
  78. 8 7
      src/components/table/index.js
  79. 68 61
      src/components/tools/DepartSelect.vue
  80. 16 29
      src/components/tools/DetailList.vue
  81. 36 0
      src/components/tools/DynamicNotice.vue
  82. 185 33
      src/components/tools/HeaderNotice.vue
  83. 82 35
      src/components/tools/ShowAnnouncement.vue
  84. 40 40
      src/components/tools/TwoStepCaptcha.vue
  85. 137 13
      src/components/tools/UserMenu.vue
  86. 33 34
      src/components/tools/UserPassword.vue
  87. 74 0
      src/config/holidays.js
  88. 7 8
      src/mixins/DisabledAuthFilterMixin.js
  89. 18 8
      src/mixins/JEditableTableMixin.js
  90. 118 137
      src/mixins/JeecgListMixin.js
  91. 26 0
      src/mixins/OnlineCommonUtil.js
  92. 33 35
      src/permission.js
  93. 10 15
      src/store/getters.js
  94. 12 4
      src/store/index.js
  95. 37 0
      src/store/modules/enhance.js
  96. 6 13
      src/store/modules/permission.js
  97. 95 38
      src/store/modules/user.js
  98. 5 1
      src/store/mutation-types.js
  99. 20 7
      src/utils/JEditableTableUtil.js
  100. 0 0
      src/utils/commonUploadFile.js

+ 1 - 2
README.md

@@ -1,8 +1,7 @@
 Ant Design Jeecg Vue
 ====
 
-
-当前最新版本: 2.0.2(发布日期:20190708)
+当前最新版本: 2.2.0(发布日期:2020-05-06)
 
 Overview
 ----

+ 9 - 0
adsp-vue-new.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 132 - 126
package.json

@@ -1,130 +1,136 @@
 {
-    "name": "ant-design-vue-jeecg",
-    "version": "2.0.2",
-    "private": true,
-    "scripts": {
-      "serve": "vue-cli-service serve --open",
-      "build": "vue-cli-service build",
-      "lint": "vue-cli-service lint",
-      "test:unit": "vue-cli-service test:unit",
-      "test:e2e": "vue-cli-service test:e2e"
+  "name": "vue-antd-jeecg",
+  "version": "2.2.0",
+  "private": true,
+  "scripts": {
+    "pre": "yarn --registry https://registry.npm.taobao.org || cnpm install || npm install --registry https://registry.npm.taobao.org ",
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@antv/data-set": "^0.11.2",
+    "@femessage/upload-to-ali": "^1.8.1",
+    "@riophae/vue-treeselect": "^0.4.0",
+    "@jeecg/antd-online-beta220": "^1.0.1",
+    "@tinymce/tinymce-vue": "^2.0.0",
+    "ali-oss": "^6.1.1",
+    "ant-design-vue": "1.5.2",
+    "apexcharts": "^3.6.5",
+    "area-data": "^5.0.6",
+    "axios": "^0.18.0",
+    "babel-loader": "^8.0.6",
+    "blueimp-md5": "^2.11.0",
+    "browser-md5-file": "^1.1.1",
+    "clipboard": "^2.0.4",
+    "codemirror": "^5.46.0",
+    "cos-js-sdk-v5": "^0.5.26",
+    "dayjs": "^1.8.0",
+    "echarts": "^4.3.0",
+    "element-ui": "^2.13.0",
+    "enquire.js": "^2.1.6",
+    "html2canvas": "^1.0.0-rc.3",
+    "iview": "2.0.0-rc.17",
+    "jquery": "^3.4.1",
+    "js-cookie": "^2.2.0",
+    "lodash.get": "^4.4.2",
+    "lodash.pick": "^4.4.0",
+    "mcanvas": "^1.3.9",
+    "md5": "^2.2.1",
+    "nprogress": "^0.2.0",
+    "promise.prototype.finally": "^3.1.1",
+    "tinymce": "^5.1.4",
+    "viser-vue": "^2.4.4",
+    "vue": "^2.6.10",
+    "vue-area-linkage": "^5.1.0",
+    "vue-apexcharts": "^1.3.2",
+    "vue-class-component": "^6.0.0",
+    "vue-count-to": "^1.0.13",
+    "vue-cropper": "^0.4.8",
+    "vue-i18n": "^8.7.0",
+    "vue-layer": "^1.0.2",
+    "vue-loader": "^15.7.0",
+    "vue-ls": "^3.2.0",
+    "vue-oss-uploader": "^0.1.10",
+    "vue-photo-preview": "^1.1.3",
+    "vue-preview": "^1.1.3",
+    "vue-print-nb-jeecg": "^1.0.9",
+    "vue-property-decorator": "^7.3.0",
+    "vue-router": "^3.0.1",
+    "vue-splitpane": "^1.0.4",
+    "vue-video-player": "^5.0.2",
+    "vue-waterfall-easy": "^2.4.3",
+    "vuedraggable": "^2.20.0",
+    "vuex": "^3.1.0",
+    "vuex-class": "^0.3.1"
+  },
+  "devDependencies": {
+    "@babel/polyfill": "^7.2.5",
+    "@vue/cli-plugin-babel": "^3.3.0",
+    "@vue/cli-plugin-eslint": "^3.3.0",
+    "@vue/cli-service": "^3.3.0",
+    "@vue/eslint-config-standard": "^4.0.0",
+    "babel-eslint": "^10.0.1",
+    "babel-plugin-component": "^1.1.1",
+    "compression-webpack-plugin": "^3.1.0",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^5.1.0",
+    "html-webpack-plugin": "^4.2.0",
+    "less": "^3.9.0",
+    "less-loader": "^4.1.0",
+    "node-sass": "^4.11.0",
+    "sass-loader": "^7.0.1",
+    "vue-template-compiler": "^2.6.10"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
     },
-    "dependencies": {
-      "@antv/data-set": "^0.10.2",
-      "@femessage/upload-to-ali": "^1.8.1",
-      "@riophae/vue-treeselect": "^0.4.0",
-      "@tinymce/tinymce-vue": "^2.0.0",
-      "ali-oss": "^6.1.1",
-      "ant-design-vue": "^1.3.9",
-      "apexcharts": "^3.6.5",
-      "axios": "^0.18.0",
-      "babel-loader": "^8.0.6",
-      "blueimp-md5": "^2.11.0",
-      "browser-md5-file": "^1.1.1",
-      "clipboard": "^2.0.4",
-      "codemirror": "^5.46.0",
-      "cos-js-sdk-v5": "^0.5.26",
-      "dayjs": "^1.8.0",
-      "echarts": "^4.3.0",
-      "element-ui": "^2.13.0",
-      "enquire.js": "^2.1.6",
-      "html2canvas": "^1.0.0-rc.3",
-      "iview": "2.0.0-rc.17",
-      "jquery": "^3.4.1",
-      "js-cookie": "^2.2.0",
-      "lodash.get": "^4.4.2",
-      "lodash.pick": "^4.4.0",
-      "mcanvas": "^1.3.9",
-      "md5": "^2.2.1",
-      "nprogress": "^0.2.0",
-      "promise.prototype.finally": "^3.1.1",
-      "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-count-to": "^1.0.13",
-      "vue-cropper": "^0.4.8",
-      "vue-i18n": "^8.7.0",
-      "vue-layer": "^1.0.2",
-      "vue-loader": "^15.7.0",
-      "vue-ls": "^3.2.0",
-      "vue-oss-uploader": "^0.1.10",
-      "vue-photo-preview": "^1.1.3",
-      "vue-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",
-      "vue-video-player": "^5.0.2",
-      "vue-waterfall-easy": "^2.4.3",
-      "vuedraggable": "^2.20.0",
-      "vuex": "^3.0.1",
-      "vuex-class": "^0.3.1"
+    "extends": [
+      "plugin:vue/strongly-recommended",
+      "@vue/standard",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
     },
-    "devDependencies": {
-      "@babel/polyfill": "^7.2.5",
-      "@vue/cli-plugin-babel": "^3.3.0",
-      "@vue/cli-plugin-eslint": "^3.3.0",
-      "@vue/cli-service": "^3.3.0",
-      "@vue/eslint-config-standard": "^4.0.0",
-      "babel-eslint": "^10.0.1",
-      "babel-plugin-component": "^1.1.1",
-      "eslint": "^5.16.0",
-      "eslint-plugin-vue": "^5.1.0",
-      "html-webpack-plugin": "^3.2.0",
-      "less": "^3.9.0",
-      "less-loader": "^4.1.0",
-      "node-sass": "^4.11.0",
-      "sass-loader": "^7.0.1",
-      "vue-template-compiler": "^2.6.10"
-    },
-    "eslintConfig": {
-      "root": true,
-      "env": {
-        "node": true
-      },
-      "extends": [
-        "plugin:vue/strongly-recommended",
-        "eslint:recommended"
-      ],
-      "parserOptions": {
-        "parser": "babel-eslint"
-      },
-      "rules": {
-        "generator-star-spacing": "off",
-        "no-mixed-operators": 0,
-        "vue/max-attributes-per-line": [
-          2,
-          {
-            "singleline": 5,
-            "multiline": {
-              "max": 1,
-              "allowFirstLine": false
-            }
+    "rules": {
+      "generator-star-spacing": "off",
+      "no-mixed-operators": 0,
+      "vue/max-attributes-per-line": [
+        2,
+        {
+          "singleline": 5,
+          "multiline": {
+            "max": 1,
+            "allowFirstLine": false
           }
-        ],
-        "vue/attribute-hyphenation": 0,
-        "vue/html-self-closing": 0,
-        "vue/component-name-in-template-casing": 0,
-        "vue/html-closing-bracket-spacing": 0,
-        "vue/singleline-html-element-content-newline": 0,
-        "vue/no-unused-components": 0,
-        "vue/multiline-html-element-content-newline": 0,
-        "vue/no-use-v-if-with-v-for": 0,
-        "vue/html-closing-bracket-newline": 0,
-        "vue/no-parsing-error": 0,
-        "no-console": 0
-      }
-    },
-    "postcss": {
-      "plugins": {
-        "autoprefixer": {}
-      }
-    },
-    "browserslist": [
-      "> 1%",
-      "last 2 versions",
-      "not ie <= 8"
-    ]
-  }
+        }
+      ],
+      "vue/attribute-hyphenation": 0,
+      "vue/html-self-closing": 0,
+      "vue/component-name-in-template-casing": 0,
+      "vue/html-closing-bracket-spacing": 0,
+      "vue/singleline-html-element-content-newline": 0,
+      "vue/no-unused-components": 0,
+      "vue/multiline-html-element-content-newline": 0,
+      "vue/no-use-v-if-with-v-for": 0,
+      "vue/html-closing-bracket-newline": 0,
+      "vue/no-parsing-error": 0,
+	  "no-console": 0,
+      "no-tabs": 0,
+      "indent": [1, 4]
+    }
+  },
+  "postcss": {
+    "plugins": {
+      "autoprefixer": {}
+    }
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ]
+}

Fichier diff supprimé car celui-ci est trop grand
+ 6953 - 0
public/cdn/babel-polyfill/polyfill_7_2_5.js


+ 30 - 0
src/api/GroupRequest.js

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

+ 87 - 80
src/api/api.js

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

+ 14 - 0
src/api/login.js

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

+ 131 - 71
src/api/manage.js

@@ -1,4 +1,5 @@
-import {axios} from '@/utils/request'
+import Vue from 'vue'
+import { axios } from '@/utils/request'
 
 const api = {
   user: '/api/user',
@@ -9,58 +10,39 @@ const api = {
 }
 
 export default api
-
-
-
-//post  不缓存
 export function postNoCacheAction(url, parameter) {
-    return axios({
-      url: url,
-      method: 'post',
-      data: parameter,
-      headers: {
-        'Cache-Control': 'no-cache'
-      }
-    })
-  }
-
-
-//post
-export function postAction(url, parameter) {
   return axios({
     url: url,
     method: 'post',
-    data: parameter
+    data: parameter,
+    headers: {
+      'Cache-Control': 'no-cache'
+    }
   })
 }
 
-//post method= {post | put}
-export function httpAction(url, parameter, method) {
+//post
+export function postAction(url,parameter) {
   return axios({
     url: url,
-    method: method,
+    method:'post' ,
     data: parameter
   })
 }
-
-//put
-export function putAction(url, parameter) {
+export function getAction(url, parameter) {
   return axios({
     url: url,
-    method: 'put',
-    data: parameter
+    method: 'get',
+    params: parameter
   })
 }
-
-//get
-export function getAction(url, parameter) {
+export function postFormAction(url,parameter) {
   return axios({
     url: url,
-    method: 'get',
+    method:'post' ,
     params: parameter
   })
 }
-
 //deleteAction
 export function deleteAction(url, parameter) {
   return axios({
@@ -69,6 +51,14 @@ export function deleteAction(url, parameter) {
     params: parameter
   })
 }
+//post method= {post | put}
+export function httpAction(url,parameter,method) {
+  return axios({
+    url: url,
+    method:method ,
+    data: parameter
+  })
+}
 
 export function getUserList(parameter) {
   return axios({
@@ -86,14 +76,6 @@ export function getRoleList(parameter) {
   })
 }
 
-export function getServiceList(parameter) {
-  return axios({
-    url: api.service,
-    method: 'get',
-    params: parameter
-  })
-}
-
 export function getPermissions(parameter) {
   return axios({
     url: api.permissionNoPager,
@@ -112,60 +94,138 @@ export function saveService(parameter) {
   })
 }
 
+//put
+export function putAction(url,parameter) {
+  return axios({
+    url: url,
+    method:'put',
+    data: parameter
+  })
+}
+
 /**
  * 下载文件 用于excel导出
  * @param url
  * @param parameter
  * @returns {*}
  */
-export function downFile(url, parameter) {
+export function downFilePost(url, parameter) {
   return axios({
     url: url,
-    params: parameter,
-    method: 'get',
+    data: parameter,
+    method: 'post',
     responseType: 'blob'
   })
 }
 
+export function getServiceList(parameter) {
+  return axios({
+    url: api.service,
+    method: 'get',
+    params: parameter
+  })
+}
+
 /**
  * 下载文件 用于excel导出
  * @param url
  * @param parameter
  * @returns {*}
  */
-export function downFilePost(url, parameter) {
-    return axios({
-      url: url,
-      data: parameter,
-      method: 'post',
-      responseType: 'blob'
-    })
-  }
-
+export function downFile(url, parameter) {
+  return axios({
+    url: url,
+    params: parameter,
+    method: 'get',
+    responseType: 'blob'
+  })
+}
 export function getProvince(url, parameter) {
-    return axios({
-      url: url,
-      method: 'get',
-      params: parameter
-    })
-  }
+  return axios({
+    url: url,
+    method: 'get',
+    params: parameter
+  })
+}
 
 export function getPackage(parameter) {
-    return axios({
-        url: "/template/creative/words/get",
-        method: 'get',
-        params: parameter
-    })
+  return axios({
+    url: "/template/creative/words/get",
+    method: 'get',
+    params: parameter
+  })
 }
 
 
+/**
+ * 下载文件
+ * @param url 文件路径
+ * @param fileName 文件名
+ * @param parameter
+ * @returns {*}
+ */
+export function downloadFile(url, fileName, parameter) {
+  return downFile(url, parameter).then((data) => {
+    if (!data || data.size === 0) {
+      Vue.prototype['$message'].warning('文件下载失败')
+      return
+    }
+    if (typeof window.navigator.msSaveBlob !== 'undefined') {
+      window.navigator.msSaveBlob(new Blob([data]), fileName)
+    } else {
+      let url = window.URL.createObjectURL(new Blob([data]))
+      let link = document.createElement('a')
+      link.style.display = 'none'
+      link.href = url
+      link.setAttribute('download', fileName)
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link) //下载完成移除元素
+      window.URL.revokeObjectURL(url) //释放掉blob对象
+    }
+  })
+}
+
+/**
+ * 文件上传 用于富文本上传图片
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export function uploadAction(url,parameter){
+  return axios({
+    url: url,
+    data: parameter,
+    method:'post' ,
+    headers: {
+      'Content-Type': 'multipart/form-data',  // 文件上传
+    },
+  })
+}
+
+/**
+ * 获取文件服务访问路径
+ * @param avatar
+ * @param subStr
+ * @returns {*}
+ */
+export function getFileAccessHttpUrl(avatar,subStr) {
+  if(!subStr) subStr = 'http'
+  if(avatar && avatar.startsWith(subStr)){
+    return avatar;
+  }else{
+    if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
+      return window._CONFIG['staticDomainURL'] + "/" + avatar;
+    }
+  }
+}
 export function postFile(URL,parameter) {
-    return axios({
-        url: URL,
-        method: 'post',
-        // headers: {
-        //     'Content-Type': 'multipart/form-data;charset=utf-8',
-        // },
-        data: parameter
-    })
+  return axios({
+    url: URL,
+    method: 'post',
+    // headers: {
+    //     'Content-Type': 'multipart/form-data;charset=utf-8',
+    // },
+    data: parameter
+  })
 }

BIN
src/assets/checkcode.png


+ 259 - 0
src/assets/less/JAreaLinkage.less

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

+ 15 - 0
src/assets/less/TableExpand.less

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

+ 19 - 14
src/assets/less/common.less

@@ -1,13 +1,19 @@
+
 /*列表上方操作按钮区域*/
 .ant-card-body .table-operator {
-  margin-bottom: 18px;
+  margin-bottom: 8px;
 }
-
 /** Button按钮间距 */
 .table-operator .ant-btn {
-  margin-right: 6px
+  margin: 0 8px 8px 0;
+}
+.table-operator .ant-btn-group .ant-btn {
+  margin: 0;
 }
 
+.table-operator .ant-btn-group .ant-btn:last-child {
+  margin: 0 8px 8px 0;
+}
 /*列表td的padding设置 可以控制列表大小*/
 .ant-table-tbody .ant-table-row td {
   padding-top: 15px;
@@ -36,19 +42,18 @@
   height: 25px;
   position: relative;
 }
-
 .anty-img-wrap > img {
   max-height: 100%;
 }
-
 /*列表中范围查询样式*/
-.query-group-cust {
-  width: calc(50% - 10px)
-}
+.query-group-cust{width: calc(50% - 10px)}
+.query-group-split-cust:before{content:"~";width: 20px;display: inline-block;text-align: center}
 
-.query-group-split-cust:before {
-  content: "~";
-  width: 20px;
-  display: inline-block;
-  text-align: center
-}
+
+/*erp风格子表外框padding设置*/
+.ant-card-wider-padding.cust-erp-sub-tab>.ant-card-body{padding:5px 12px}
+
+/* 内嵌子表背景颜色 */
+.j-inner-table-wrapper /deep/ .ant-table-expanded-row .ant-table-wrapper .ant-table-tbody .ant-table-row {
+  background-color: #FFFFFF;
+}

+ 9 - 9
src/cas/sso.js

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

+ 12 - 30
src/components/AvatarList/List.vue

@@ -19,7 +19,7 @@
 <script>
   import Avatar from 'ant-design-vue/es/avatar'
   import AvatarItem from './Item'
-  import {filterEmpty} from '@/components/_util/util'
+  import { filterEmpty } from '@/components/_util/util'
 
   export default {
     AvatarItem,
@@ -61,7 +61,7 @@
         }
       }
     },
-    data() {
+    data () {
       return {}
     },
     methods: {
@@ -73,46 +73,28 @@
 
         if (this.maxLength > 0) {
           items = items.slice(0, this.maxLength)
-          items.push(( < Avatar
-          size = {this.size}
-          style = {this.excessItemsStyle} > {`+${this.maxLength}`
-        }<
-          /Avatar>))
+          items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
         }
         const itemList = items.map((item) => (
-          < li
-      class
-        = {classString} > {item} < /li>
-      ))
+          <li class={ classString }>{ item }</li>
+        ))
         return itemList
       }
     },
-    render() {
-      const {prefixCls, size} = this.$props
+    render () {
+      const { prefixCls, size } = this.$props
       const classString = {
         [`${prefixCls}`]: true,
         [`${size}`]: true,
       }
       const items = filterEmpty(this.$slots.default)
-      const itemsDom = items && items.length ?
-    <
-      ul
-    class
-      = {`${prefixCls}-items`
-    }>
-      {
-        this.getItems(items)
-      }
-    <
-      /ul> : null
+      const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{ this.getItems(items) }</ul> : null
 
       return (
-        < div
-    class
-      = {classString} >
-        {itemsDom}
-        < /div>
-    )
+        <div class={ classString }>
+          { itemsDom }
+        </div>
+      )
     }
   }
 </script>

+ 2 - 24
src/components/README.md

@@ -39,27 +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 "在这里输入图片标题")
-
-
-uploadFile.vue  组件使用
-1.在需要使用组件的地方引入    import uploadFile from '@/components/uploadFile.vue'
-并且在components中定义这个组件 components:{uploadFile}
-2.在页面中使用
-  参数  uploadType:上传类型   默认为image     值  image/video
-        multiple:是否能多个上传,uploadType为video时,最好值为false  默认为true   值  true/false
-        fileCount:能上传的最大数量    默认值为10,可随意修改为大于0的值
-        checkFile:页面中上传方法进行判断                             
-  <uploadFile :checkFile="checkFile" @overUpload="overUpload" @removeUpload="removeUpload" />  
-      <!-- uploadType="video" :multiple="false" -->
-      overUpload(overFile) {
-        console.log(overFile)
-      },
-      removeUpload(removeFile) {
-        console.log(removeFile)
-      },
-      checkFile(file) {
-        console.log(file)
-        return new Promise(function (resolve, reject) {
-            resolve()//只有在promise中返回resolve()才会继续进行上传
-        })
-      }
+![corn表达式](https://oscimg.oschina.net/oscnet/661f9ac09016395f9f49286143af3241623.jpg)
+![corn控件添加清除按钮](https://oscimg.oschina.net/oscnet/15096e49f2e29bd829e304d56770025d03c.jpg)

+ 79 - 0
src/components/_util/Area.js

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

+ 13 - 10
src/components/chart/BarAndLine.vue

@@ -1,7 +1,7 @@
 <template>
-  <div :style="{ padding: '0 0 32px 32px' }">
+  <div :style="{ padding: '0 50px 32px 0' }">
     <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
-    <v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
+    <v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding" :onClick="handleClick">
       <v-tooltip/>
       <v-legend/>
       <v-axis/>
@@ -12,9 +12,11 @@
 </template>
 
 <script>
+  import { ChartEventMixins } from './mixins/ChartMixins'
 
   export default {
-    name: 'BarMultid',
+    name: 'BarAndLine',
+    mixins: [ChartEventMixins],
     props: {
       title: {
         type: String,
@@ -23,13 +25,13 @@
       dataSource: {
         type: Array,
         default: () => [
-          {type: '10:10', bar: 2, line: 2},
-          {type: '10:15', bar: 6, line: 3},
-          {type: '10:20', bar: 2, line: 5},
-          {type: '10:25', bar: 9, line: 1},
-          {type: '10:30', bar: 2, line: 3},
-          {type: '10:35', bar: 2, line: 1},
-          {type: '10:40', bar: 1, line: 2}
+          { type: '10:10', bar: 200, line: 1000 },
+          { type: '10:15', bar: 600, line: 1000},
+          { type: '10:20', bar: 200, line: 1000},
+          { type: '10:25', bar: 900, line: 1000},
+          { type: '10:30', bar: 200, line: 1000},
+          { type: '10:35', bar: 200, line: 1000},
+          { type: '10:40', bar: 100, line: 1000}
         ]
       },
       height: {
@@ -39,6 +41,7 @@
     },
     data() {
       return {
+        padding: { top:50, right:50, bottom:100, left:50 },
         scale: [{
           dataKey: 'bar',
           min: 0

+ 25 - 27
src/components/chart/BarMultid.vue

@@ -1,7 +1,7 @@
 <template>
   <div :style="{ padding: '0 0 32px 32px' }">
     <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
-    <v-chart :forceFit="true" :height="height" :data="data">
+    <v-chart :data="data" :height="height" :force-fit="true" :onClick="handleClick">
       <v-tooltip/>
       <v-axis/>
       <v-legend/>
@@ -11,10 +11,12 @@
 </template>
 
 <script>
-  import {DataSet} from '@antv/data-set'
+  import { DataSet } from '@antv/data-set'
+  import { ChartEventMixins } from './mixins/ChartMixins'
 
   export default {
     name: 'BarMultid',
+    mixins: [ChartEventMixins],
     props: {
       title: {
         type: String,
@@ -23,34 +25,19 @@
       dataSource: {
         type: Array,
         default: () => [
-          {
-            type: 'Jeecg',
-            'Jan.': 18.9,
-            'Feb.': 28.8,
-            'Mar.': 39.3,
-            'Apr.': 81.4,
-            'May': 47,
-            'Jun.': 20.3,
-            'Jul.': 24,
-            'Aug.': 35.6
-          },
-          {
-            type: 'Jeebt',
-            'Jan.': 12.4,
-            'Feb.': 23.2,
-            'Mar.': 34.5,
-            'Apr.': 99.7,
-            'May': 52.6,
-            'Jun.': 35.5,
-            'Jul.': 37.4,
-            'Aug.': 42.4
-          }
+          { type: 'Jeecg', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, 'May': 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
+          { type: 'Jeebt', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, 'May': 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 }
         ]
       },
       fields: {
         type: Array,
         default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
       },
+      // 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
+      aliases: {
+        type: Array,
+        default: () => []
+      },
       height: {
         type: Number,
         default: 254
@@ -75,11 +62,22 @@
         })
 
         // bar 使用不了 - 和 / 所以替换下
-        return dv.rows.map(row => {
-          row.x = row.x.replace(/[-/]/g, '_')
+        let rows = dv.rows.map(row => {
+          if (typeof row.x === 'string') {
+            row.x = row.x.replace(/[-/]/g, '_')
+          }
           return row
         })
-
+        // 替换别名
+        rows.forEach(row => {
+          for (let item of this.aliases) {
+            if (item.field === row.type) {
+              row.type = item.alias
+              break
+            }
+          }
+        })
+        return rows
       }
     }
   }

+ 22 - 22
src/components/chart/DashChartDemo.vue

@@ -1,6 +1,6 @@
 <template>
   <div :style="{ padding: '0 0 32px 32px' }">
-    <v-chart :forceFit="true" :height="350" :data="chartData" :scale="scale">
+    <v-chart :forceFit="true" :height="300" :data="chartData" :scale="scale">
       <v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
       <v-axis
         dataKey="value"
@@ -45,7 +45,7 @@
 </template>
 
 <script>
-  import {registerShape} from 'viser-vue';
+  import { registerShape } from 'viser-vue';
 
   registerShape('point', 'pointer', {
     draw(cfg, container) {
@@ -88,27 +88,27 @@
   }];
 
   const data = [
-    {value: 7.0},
+    { value: 7.0 },
   ];
 
   export default {
-    name: "DashChartDemo",
-    props: {
-      datasource: {
+    name:"DashChartDemo",
+    props:{
+      datasource:{
         type: Number,
-        default: 7
+        default:7
       },
       title: {
         type: String,
         default: ''
       }
     },
-    created() {
-      if (!this.datasource) {
+    created(){
+      if(!this.datasource){
         this.chartData = data;
-      } else {
+      }else{
         this.chartData = [
-          {value: this.datasource},
+          { value: this.datasource },
         ];
       }
       this.getChartData()
@@ -116,35 +116,35 @@
     watch: {
       'datasource': function (val) {
         this.chartData = [
-          {value: val},
+          { value: val},
         ];
         this.getChartData();
       }
     },
-    methods: {
-      getChartData() {
-        if (this.chartData && this.chartData.length > 0) {
+    methods:{
+      getChartData(){
+        if(this.chartData && this.chartData.length>0){
           this.abcd = this.chartData[0].value * 10
-        } else {
+        }else{
           this.abcd = 70
         }
       },
-      getHtmlGuideHtml() {
+      getHtmlGuideHtml(){
         return '<div style="width: 300px;text-align: center;">\n' +
-          '<p style="font-size: 14px;color: #545454;margin: 0;">' + this.title + '</p>\n' +
-          '<p style="font-size: 36px;color: #545454;margin: 0;">' + this.abcd + '%</p>\n' +
+          '<p style="font-size: 14px;color: #545454;margin: 0;">'+this.title+'</p>\n' +
+          '<p style="font-size: 36px;color: #545454;margin: 0;">'+this.abcd+'%</p>\n' +
           '</div>'
       },
-      getArcGuide2End() {
+      getArcGuide2End(){
         return [this.chartData[0].value, 0.945]
       }
     },
     data() {
       return {
-        chartData: [],
+        chartData:[],
         height: 400,
         scale: scale,
-        abcd: 70,
+        abcd:70,
         axisLabel: {
           offset: -16,
           textStyle: {

+ 33 - 16
src/components/chart/LineChartMultid.vue

@@ -1,7 +1,7 @@
 <template>
   <div :style="{ padding: '0 0 32px 32px' }">
     <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
-    <v-chart :force-fit="true" :height="height" :data="data" :scale="scale">
+    <v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
       <v-tooltip/>
       <v-axis/>
       <v-legend/>
@@ -12,10 +12,12 @@
 </template>
 
 <script>
-  import {DataSet} from '@antv/data-set'
+  import { DataSet } from '@antv/data-set'
+  import { ChartEventMixins } from './mixins/ChartMixins'
 
   export default {
     name: 'LineChartMultid',
+    mixins: [ChartEventMixins],
     props: {
       title: {
         type: String,
@@ -24,24 +26,29 @@
       dataSource: {
         type: Array,
         default: () => [
-          {type: 'Jan', jeecg: 7.0, jeebt: 3.9},
-          {type: 'Feb', jeecg: 6.9, jeebt: 4.2},
-          {type: 'Mar', jeecg: 9.5, jeebt: 5.7},
-          {type: 'Apr', jeecg: 14.5, jeebt: 8.5},
-          {type: 'May', jeecg: 18.4, jeebt: 11.9},
-          {type: 'Jun', jeecg: 21.5, jeebt: 15.2},
-          {type: 'Jul', jeecg: 25.2, jeebt: 17.0},
-          {type: 'Aug', jeecg: 26.5, jeebt: 16.6},
-          {type: 'Sep', jeecg: 23.3, jeebt: 14.2},
-          {type: 'Oct', jeecg: 18.3, jeebt: 10.3},
-          {type: 'Nov', jeecg: 13.9, jeebt: 6.6},
-          {type: 'Dec', jeecg: 9.6, jeebt: 4.8}
+          { type: 'Jan', jeecg: 7.0, jeebt: 3.9 },
+          { type: 'Feb', jeecg: 6.9, jeebt: 4.2 },
+          { type: 'Mar', jeecg: 9.5, jeebt: 5.7 },
+          { type: 'Apr', jeecg: 14.5, jeebt: 8.5 },
+          { type: 'May', jeecg: 18.4, jeebt: 11.9 },
+          { type: 'Jun', jeecg: 21.5, jeebt: 15.2 },
+          { type: 'Jul', jeecg: 25.2, jeebt: 17.0 },
+          { type: 'Aug', jeecg: 26.5, jeebt: 16.6 },
+          { type: 'Sep', jeecg: 23.3, jeebt: 14.2 },
+          { type: 'Oct', jeecg: 18.3, jeebt: 10.3 },
+          { type: 'Nov', jeecg: 13.9, jeebt: 6.6 },
+          { type: 'Dec', jeecg: 9.6, jeebt: 4.8 }
         ]
       },
       fields: {
         type: Array,
         default: () => ['jeecg', 'jeebt']
       },
+      // 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
+      aliases:{
+        type: Array,
+        default: () => []
+      },
       height: {
         type: Number,
         default: 254
@@ -54,7 +61,7 @@
           min: 0,
           max: 1
         }],
-        style: {stroke: '#fff', lineWidth: 1}
+        style: { stroke: '#fff', lineWidth: 1 }
       }
     },
     computed: {
@@ -66,7 +73,17 @@
           key: 'x',
           value: 'y'
         })
-        return dv.rows
+        let rows =  dv.rows
+        // 替换别名
+        rows.forEach(row => {
+          for (let item of this.aliases) {
+            if (item.field === row.x) {
+              row.x = item.alias
+              break
+            }
+          }
+        })
+        return rows
       }
     }
   }

+ 3 - 3
src/components/chart/Liquid.vue

@@ -49,9 +49,9 @@
 <script>
 
   const sourceDataConst = [
-    {transfer: '一月', value: 813},
-    {transfer: '二月', value: 233},
-    {transfer: '三月', value: 561}
+    { transfer: '一月', value: 813 },
+    { transfer: '二月', value: 233 },
+    { transfer: '三月', value: 561 }
   ]
 
   export default {

+ 3 - 3
src/components/chart/MiniArea.vue

@@ -49,8 +49,8 @@
     computed: {
       scale() {
         return [
-          {dataKey: 'x', title: this.x, alias: this.x},
-          {dataKey: 'y', title: this.y, alias: this.y}
+          { dataKey: 'x', title: this.x, alias: this.x },
+          { dataKey: 'y', title: this.y, alias: this.y }
         ]
       }
     },
@@ -64,6 +64,6 @@
   }
 </script>
 
-<style lang="scss" scoped>
+<style lang="less" scoped>
   @import "chart";
 </style>

+ 1 - 1
src/components/chart/MiniBar.vue

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

+ 1 - 2
src/components/chart/MiniProgress.vue

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

+ 9 - 6
src/components/chart/Pie.vue

@@ -1,5 +1,5 @@
 <template>
-  <v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
+  <v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
     <v-tooltip :showTitle="false" dataKey="item*percent"/>
     <v-axis/>
     <v-legend dataKey="item"/>
@@ -10,8 +10,11 @@
 
 <script>
   const DataSet = require('@antv/data-set')
+  import { ChartEventMixins } from './mixins/ChartMixins'
 
   export default {
+    name: 'Pie',
+    mixins: [ChartEventMixins],
     props: {
       title: {
         type: String,
@@ -24,11 +27,11 @@
       dataSource: {
         type: Array,
         default: () => [
-          {item: '示例一', count: 40},
-          {item: '示例二', count: 21},
-          {item: '示例三', count: 17},
-          {item: '示例四', count: 13},
-          {item: '示例五', count: 9}
+          { item: '示例一', count: 40 },
+          { item: '示例二', count: 21 },
+          { item: '示例三', count: 17 },
+          { item: '示例四', count: 13 },
+          { item: '示例五', count: 9 }
         ]
       }
     },

+ 54 - 0
src/components/chart/StackBar.vue

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

+ 13 - 0
src/components/chart/chart.less

@@ -0,0 +1,13 @@
+.antv-chart-mini {
+  position: relative;
+  width: 100%;
+
+  .chart-wrapper {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+
+/*    margin: 0 -5px;
+    overflow: hidden;*/
+  }
+}

+ 10 - 0
src/components/chart/mixins/ChartMixins.js

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

+ 39 - 17
src/components/dict/JDictSelectTag.vue

@@ -1,11 +1,14 @@
 <template>
-  <a-radio-group v-if="tagType=='radio'" @change="handleInput" :value="value" :disabled="disabled">
+  <a-radio-group v-if="tagType=='radio'" @change="handleInput" :value="getValueSting" :disabled="disabled">
     <a-radio v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio>
   </a-radio-group>
 
-  <a-select v-else-if="tagType=='select'" :placeholder="placeholder" :disabled="disabled" :value="value"
-            @change="handleInput">
-    <a-select-option value="">请选择</a-select-option>
+  <a-radio-group v-else-if="tagType=='radioButton'"  buttonStyle="solid" @change="handleInput" :value="getValueSting" :disabled="disabled">
+    <a-radio-button v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio-button>
+  </a-radio-group>
+
+  <a-select v-else-if="tagType=='select'" :getPopupContainer = "(target) => target.parentNode" :placeholder="placeholder" :disabled="disabled" :value="getValueSting" @change="handleInput">
+    <a-select-option :value="undefined">请选择</a-select-option>
     <a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value">
       <span style="display: inline-block;width: 100%" :title=" item.text || item.label ">
         {{ item.text || item.label }}
@@ -15,7 +18,7 @@
 </template>
 
 <script>
-  import {ajaxGetDictItems} from '@/api/api'
+  import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
 
   export default {
     name: "JDictSelectTag",
@@ -24,27 +27,46 @@
       placeholder: String,
       triggerChange: Boolean,
       disabled: Boolean,
-      value: String,
+      value: [String, Number],
       type: String
     },
     data() {
       return {
         dictOptions: [],
-        tagType: ""
+        tagType:""
+      }
+    },
+    watch:{
+      dictCode:{
+        immediate:true,
+        handler() {
+          this.initDictData()
+        },
       }
     },
     created() {
-      console.log(this.dictCode);
-      if (!this.type || this.type === "list") {
+      // console.log(this.dictCode);
+      if(!this.type || this.type==="list"){
         this.tagType = "select"
-      } else {
+      }else{
         this.tagType = this.type
       }
       //获取字典数据
-      this.initDictData();
+      // this.initDictData();
+    },
+    computed: {
+      getValueSting(){
+        return this.value != null ? this.value.toString() : null;
+      },
     },
     methods: {
       initDictData() {
+        //优先从缓存中读取字典配置
+        if(getDictItemsFromCache(this.dictCode)){
+          this.dictOptions = getDictItemsFromCache(this.dictCode);
+          return
+        }
+
         //根据字典Code, 初始化字典数组
         ajaxGetDictItems(this.dictCode, null).then((res) => {
           if (res.success) {
@@ -55,22 +77,22 @@
       },
       handleInput(e) {
         let val;
-        if (this.tagType == "radio") {
+        if(this.tagType=="radio"){
           val = e.target.value
-        } else {
+        }else{
           val = e
         }
         console.log(val);
-        if (this.triggerChange) {
+        if(this.triggerChange){
           this.$emit('change', val);
-        } else {
+        }else{
           this.$emit('input', val);
         }
       },
-      setCurrentDictOptions(dictOptions) {
+      setCurrentDictOptions(dictOptions){
         this.dictOptions = dictOptions
       },
-      getCurrentDictOptions() {
+      getCurrentDictOptions(){
         return this.dictOptions
       }
     }

+ 82 - 27
src/components/dict/JDictSelectUtil.js

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

+ 32 - 21
src/components/dict/JMultiSelectTag.vue

@@ -1,7 +1,6 @@
 <template>
   <a-checkbox-group v-if="tagType=='checkbox'" @change="onChange" :value="arrayValue" :disabled="disabled">
-    <a-checkbox v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text || item.label }}
-    </a-checkbox>
+    <a-checkbox v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text || item.label }}</a-checkbox>
   </a-checkbox-group>
 
   <a-select
@@ -10,7 +9,9 @@
     @change="onChange"
     :disabled="disabled"
     mode="multiple"
-    :placeholder="placeholder">
+    :placeholder="placeholder"
+    :getPopupContainer="(node) => node.parentNode"
+    allowClear>
     <a-select-option
       v-for="(item,index) in dictOptions"
       :key="index"
@@ -24,8 +25,7 @@
 </template>
 
 <script>
-  import {ajaxGetDictItems} from '@/api/api'
-
+  import {ajaxGetDictItems,getDictItemsFromCache} from '@/api/api'
   export default {
     name: 'JMultiSelectTag',
     props: {
@@ -34,41 +34,52 @@
       disabled: Boolean,
       value: String,
       type: String,
-      options: Array
+      options:Array
     },
     data() {
       return {
         dictOptions: [],
-        tagType: "",
-        arrayValue: !this.value ? [] : this.value.split(",")
+        tagType:"",
+        arrayValue:!this.value?[]:this.value.split(",")
       }
     },
     created() {
-      if (!this.type || this.type === "list_multi") {
+      if(!this.type || this.type==="list_multi"){
         this.tagType = "select"
-      } else {
+      }else{
         this.tagType = this.type
       }
       //获取字典数据
-      this.initDictData();
+      //this.initDictData();
     },
-    watch: {
-      options: function (val) {
+    watch:{
+      options: function(val){
         this.setCurrentDictOptions(val);
       },
-      value(val) {
-        if (!val) {
+      dictCode:{
+        immediate:true,
+        handler() {
+          this.initDictData()
+        },
+      },
+      value (val) {
+        if(!val){
           this.arrayValue = []
-        } else {
+        }else{
           this.arrayValue = this.value.split(",")
         }
       }
     },
     methods: {
       initDictData() {
-        if (this.options && this.options.length > 0) {
+        if(this.options && this.options.length>0){
           this.dictOptions = [...this.options]
-        } else {
+        }else{
+          //优先从缓存中读取字典配置
+          if(getDictItemsFromCache(this.dictCode)){
+            this.dictOptions = getDictItemsFromCache(this.dictCode);
+            return
+          }
           //根据字典Code, 初始化字典数组
           ajaxGetDictItems(this.dictCode, null).then((res) => {
             if (res.success) {
@@ -78,13 +89,13 @@
         }
 
       },
-      onChange(selectedValue) {
+      onChange (selectedValue) {
         this.$emit('change', selectedValue.join(","));
       },
-      setCurrentDictOptions(dictOptions) {
+      setCurrentDictOptions(dictOptions){
         this.dictOptions = dictOptions
       },
-      getCurrentDictOptions() {
+      getCurrentDictOptions(){
         return this.dictOptions
       }
     },

+ 86 - 60
src/components/dict/JSearchSelectTag.vue

@@ -4,12 +4,15 @@
     v-if="async"
     showSearch
     labelInValue
+    :disabled="disabled"
+    :getPopupContainer="(node) => node.parentNode"
     @search="loadData"
     :placeholder="placeholder"
     v-model="selectedAsyncValue"
     style="width: 100%"
     :filterOption="false"
     @change="handleAsyncChange"
+    allowClear
     :notFoundContent="loading ? undefined : null"
   >
     <a-spin v-if="loading" slot="notFoundContent" size="small"/>
@@ -18,13 +21,16 @@
 
   <a-select
     v-else
+    :getPopupContainer="(node) => node.parentNode"
     showSearch
+    :disabled="disabled"
     :placeholder="placeholder"
     optionFilterProp="children"
     style="width: 100%"
     @change="handleChange"
     :filterOption="filterOption"
     v-model="selectedValue"
+    allowClear
     :notFoundContent="loading ? undefined : null">
     <a-spin v-if="loading" slot="notFoundContent" size="small"/>
     <a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
@@ -33,135 +39,155 @@
 </template>
 
 <script>
-  import {ajaxGetDictItems} from '@/api/api'
+  import { ajaxGetDictItems,getDictItemsFromCache } from '@/api/api'
   import debounce from 'lodash/debounce';
-  import {getAction} from '../../api/manage'
+  import { getAction } from '../../api/manage'
 
   export default {
     name: 'JSearchSelectTag',
-    props: {
-
+    props:{
       disabled: Boolean,
-      value: String,
+      value: [String, Number],
       dict: String,
       dictOptions: Array,
       async: Boolean,
-      placeholder: {
-        type: String,
-        default: "请选择",
-        required: false
+      placeholder:{
+        type:String,
+        default:"请选择",
+        required:false
       }
     },
-    data() {
+    data(){
       this.loadData = debounce(this.loadData, 800);//消抖
       this.lastLoad = 0;
       return {
-        loading: false,
-        selectedValue: [],
-        selectedAsyncValue: [],
+        loading:false,
+        selectedValue:[],
+        selectedAsyncValue:[],
         options: [],
       }
     },
-    created() {
+    created(){
       this.initDictData();
     },
-    watch: {
-      "value": {
-        immediate: true,
-        handler(val) {
-          if (!val) {
-            this.selectedValue = []
-            this.selectedAsyncValue = []
-          } else {
+    watch:{
+      "value":{
+        immediate:true,
+        handler(val){
+          if(!val){
+            if(val==0){
+              this.initSelectValue()
+            }else{
+              this.selectedValue=[]
+              this.selectedAsyncValue=[]
+            }
+          }else{
             this.initSelectValue()
           }
         }
-
       },
-      "dict": {
-        handler() {
+      "dict":{
+        handler(){
           this.initDictData()
         }
       }
     },
-    methods: {
-      initSelectValue() {
-        if (this.async) {
-          if (!this.selectedAsyncValue || !this.selectedAsyncValue.key || this.selectedAsyncValue.key != this.value) {
+    methods:{
+      initSelectValue(){
+        if(this.async){
+          if(!this.selectedAsyncValue || !this.selectedAsyncValue.key || this.selectedAsyncValue.key!=this.value){
             console.log("这才请求后台")
-            getAction(`/sys/dict/loadDictItem/${this.dict}`, {key: this.value}).then(res => {
-              if (res.success) {
+            getAction(`/sys/dict/loadDictItem/${this.dict}`,{key:this.value}).then(res=>{
+              if(res.success){
                 let obj = {
-                  key: this.value,
-                  label: res.result
+                  key:this.value,
+                  label:res.result
                 }
                 this.selectedAsyncValue = {...obj}
               }
             })
           }
-        } else {
-          this.selectedValue = this.value
+        }else{
+          this.selectedValue = this.value.toString()
         }
       },
-      loadData(value) {
-        console.log("数据加载", value)
-        this.lastLoad += 1
+      loadData(value){
+        console.log("数据加载",value)
+        this.lastLoad +=1
         const currentLoad = this.lastLoad
         this.options = []
-        this.loading = true
+        this.loading=true
         // 字典code格式:table,text,code
-        getAction(`/sys/dict/loadDict/${this.dict}`, {keyword: value}).then(res => {
-          this.loading = false
-          if (res.success) {
-            if (currentLoad != this.lastLoad) {
+        getAction(`/sys/dict/loadDict/${this.dict}`,{keyword:value}).then(res=>{
+          this.loading=false
+          if(res.success){
+            if(currentLoad!=this.lastLoad){
               return
             }
             this.options = res.result
-            console.log("我是第一个", res)
-          } else {
+            console.log("我是第一个",res)
+          }else{
             this.$message.warning(res.message)
           }
 
         })
 
       },
-      initDictData() {
-        if (!this.async) {
+      initDictData(){
+        if(!this.async){
           //如果字典项集合有数据
-          if (this.dictOptions && this.dictOptions.length > 0) {
+          if(this.dictOptions && this.dictOptions.length>0){
             this.options = [...this.dictOptions]
-          } else {
+          }else{
             //根据字典Code, 初始化字典数组
-            ajaxGetDictItems(this.dict, null).then((res) => {
-              if (res.success) {
-                this.options = res.result;
-              }
-            })
+            let dictStr = ''
+            if(this.dict){
+                let arr = this.dict.split(',')
+                if(arr[0].indexOf('where')>0){
+                  let tbInfo = arr[0].split('where')
+                  dictStr = tbInfo[0].trim()+','+arr[1]+','+arr[2]+','+encodeURIComponent(tbInfo[1])
+                }else{
+                  dictStr = this.dict
+                }
+                if (this.dict.indexOf(",") == -1) {
+                  //优先从缓存中读取字典配置
+                  if (getDictItemsFromCache(this.dictCode)) {
+                    this.options = getDictItemsFromCache(this.dictCode);
+                    return
+                  }
+                }
+                ajaxGetDictItems(dictStr, null).then((res) => {
+                  if (res.success) {
+                    this.options = res.result;
+                  }
+                })
+            }
           }
         }
       },
       filterOption(input, option) {
         return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
       },
-      handleChange(selectedValue) {
-        console.log("selectedValue", selectedValue)
+      handleChange (selectedValue) {
+        console.log("selectedValue",selectedValue)
         this.selectedValue = selectedValue
         this.callback()
       },
-      handleAsyncChange(selectedObj) {
+      handleAsyncChange(selectedObj){
         this.selectedAsyncValue = selectedObj
         this.selectedValue = selectedObj.key
         this.callback()
       },
-      callback() {
+      callback(){
         this.$emit('change', this.selectedValue);
       },
-      setCurrentDictOptions(dictOptions) {
+      setCurrentDictOptions(dictOptions){
         this.options = dictOptions
       },
-      getCurrentDictOptions() {
+      getCurrentDictOptions(){
         return this.options
       }
+
     },
     model: {
       prop: 'value',

+ 1 - 0
src/components/dict/README.md

@@ -178,3 +178,4 @@ v-decorator用法:
   }
 </script>
 ```
+

+ 156 - 0
src/components/jeecg/JAreaLinkage.vue

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

+ 1 - 3
src/components/jeecg/JCategorySelect.vue

@@ -96,7 +96,7 @@
       loadRoot(){
         let param = {
           pid:this.pid,
-          pcode:this.pcode,
+          pcode:!this.pcode?'0':this.pcode,
           condition:this.condition
         }
         getAction(this.url,param).then(res=>{
@@ -122,8 +122,6 @@
           this.treeValue = []
         }else{
           getAction(this.view,{ids:this.value}).then(res=>{
-            console.log(124345)
-            console.log(124345,res)
             if(res.success){
               let values = this.value.split(',')
               this.treeValue = res.result.map((item, index) => ({

+ 79 - 66
src/components/jeecg/JCodeEditor.vue

@@ -1,6 +1,6 @@
 <template>
   <div v-bind="fullScreenParentProps">
-    <a-icon v-if="fullScreen" class="full-screen-icon" type="fullscreen" @click="()=>fullCoder=!fullCoder"/>
+    <a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
 
     <div class="code-editor-cust full-screen-child">
       <textarea ref="textarea"></textarea>
@@ -62,11 +62,11 @@
         type: String,
         default: null
       },
-      languageChange: {
-        type: Boolean,
-        default: false,
-        required: false
-      },
+     languageChange:{
+       type: Boolean,
+       default:false,
+       required:false
+     },
       placeholder: {
         type: String,
         default: null
@@ -87,11 +87,12 @@
         default: 999
       }
     },
-    data() {
+    data () {
       return {
         // 内部真实的内容
         code: '',
-        hasCode: false,
+        iconType: 'fullscreen',
+        hasCode:false,
         // 默认的语法类型
         mode: 'javascript',
         // 编辑器实例
@@ -103,7 +104,7 @@
           // 主题,对应主题库 JS 需要提前引入
           theme: 'panda-syntax',
           line: true,
-          // extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
+         // extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
           hintOptions: {
             tables: {
               users: ['name', 'score', 'birthDate'],
@@ -155,6 +156,15 @@
       }
     },
     watch: {
+      fullCoder:{
+        handler(value) {
+          if(value){
+            this.iconType="fullscreen-exit"
+          }else{
+            this.iconType="fullscreen"
+          }
+        }
+      },
       // value: {
       //   immediate: false,
       //   handler(value) {
@@ -190,11 +200,11 @@
           return this.placeholder
         }
       },
-      nullTipStyle() {
+      nullTipStyle(){
         if (this.lineNumbers) {
-          return {left: '36px'}
+          return { left: '36px' }
         } else {
-          return {left: '12px'}
+          return { left: '12px' }
         }
       },
       // coder 配置
@@ -207,7 +217,7 @@
           hintOptions: this.options.hintOptions
         }
       },
-      fullScreenParentProps() {
+      fullScreenParentProps(){
         let props = {
           class: ['full-screen-parent', this.fullCoder ? 'full-screen' : ''],
           style: {}
@@ -218,58 +228,65 @@
         return props
       }
     },
-    mounted() {
+    mounted () {
       // 初始化
       this._initialize()
     },
     methods: {
       // 初始化
-      _initialize() {
+      _initialize () {
         // 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
         this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
         // 编辑器赋值
-        this.coder.setValue(this.value || this.code)
-        if (this.value || this.code) {
-          this.hasCode = true
-        } else {
-          this.hasCode = false
+        if(this.value||this.code){
+          this.hasCode=true
+          this.coder.setValue(this.value || this.code)
+        }else{
+          this.coder.setValue('')
+          this.hasCode=false
         }
         // 支持双向绑定
         this.coder.on('change', (coder) => {
           this.code = coder.getValue()
-          if (this.code) {
-            this.hasCode = true
-          } else {
-            this.hasCode = false
+          if(this.code){
+            this.hasCode=true
+          }else{
+            this.hasCode=false
           }
           if (this.$emit) {
             this.$emit('input', this.code)
           }
         })
         this.coder.on('focus', () => {
-          this.hasCode = true
+          this.hasCode=true
         })
         this.coder.on('blur', () => {
-          if (this.code) {
-            this.hasCode = true
-          } else {
-            this.hasCode = false
+          if(this.code){
+            this.hasCode=true
+          }else{
+            this.hasCode=false
           }
         })
 
-        /* this.coder.on('cursorActivity',()=>{
-           this.coder.showHint()
-         })*/
+       /* this.coder.on('cursorActivity',()=>{
+          this.coder.showHint()
+        })*/
 
       },
-      getCodeContent() {
+      getCodeContent(){
         return this.code
       },
-      setCodeContent(val) {
-        this.coder.setValue(val)
+      setCodeContent(val){
+        setTimeout(()=>{
+          if(!val){
+            this.coder.setValue('')
+          }else{
+            this.coder.setValue(val)
+          }
+        },300)
       },
       // 获取当前语法类型
-      _getLanguage(language) {
+      _getLanguage (language) {
         // 在支持的语法类型列表中寻找传入的语法类型
         return this.modes.find((mode) => {
           // 所有的值都忽略大小写,方便比较
@@ -294,7 +311,7 @@
         })
       },
       // 更改模式
-      changeMode(val) {
+      changeMode (val) {
         // 修改编辑器的语法配置
         this.coder.setOption('mode', `text/${val}`)
 
@@ -304,7 +321,7 @@
         // 允许父容器通过以下函数监听当前的语法值
         this.$emit('language-change', label)
       },
-      nullTipClick() {
+      nullTipClick(){
         this.coder.focus()
       }
     }
@@ -312,36 +329,31 @@
 </script>
 
 <style lang="less">
-  .code-editor-cust {
-    flex-grow: 1;
-    display: flex;
-    position: relative;
-    height: 100%;
-
-    .CodeMirror {
-      flex-grow: 1;
-      z-index: 1;
-
-      .CodeMirror-code {
-        line-height: 19px;
+  .code-editor-cust{
+    flex-grow:1;
+    display:flex;
+    position:relative;
+    height:100%;
+    .CodeMirror{
+      flex-grow:1;
+      z-index:1;
+      .CodeMirror-code{
+        line-height:19px;
       }
 
     }
-
-    .code-mode-select {
-      position: absolute;
-      z-index: 2;
-      right: 10px;
-      top: 10px;
-      max-width: 130px;
+    .code-mode-select{
+      position:absolute;
+      z-index:2;
+      right:10px;
+      top:10px;
+      max-width:130px;
     }
-
-    .CodeMirror {
+    .CodeMirror{
       height: auto;
-      min-height: 100%;
+      min-height:100%;
     }
-
-    .null-tip {
+    .null-tip{
       position: absolute;
       top: 4px;
       left: 36px;
@@ -349,8 +361,7 @@
       color: #ffffffc9;
       line-height: initial;
     }
-
-    .null-tip-hidden {
+    .null-tip-hidden{
       display: none;
     }
   }
@@ -397,7 +408,6 @@
         top: 12px;
         right: 12px;
       }
-
       .full-screen-child {
         height: 100%;
         max-height: 100%;
@@ -408,9 +418,12 @@
     .full-screen-child {
       min-height: 120px;
       max-height: 320px;
+      overflow:hidden;
     }
 
   }
 
-
+.CodeMirror-cursor{
+  height:18.4px !important;
+}
 </style>

+ 16 - 17
src/components/jeecg/JDate.vue

@@ -11,69 +11,68 @@
 </template>
 <script>
   import moment from 'moment'
-
   export default {
     name: 'JDate',
     props: {
-      placeholder: {
+      placeholder:{
         type: String,
         default: '',
         required: false
       },
-      value: {
+      value:{
         type: String,
         required: false
       },
-      dateFormat: {
+      dateFormat:{
         type: String,
         default: 'YYYY-MM-DD',
         required: false
       },
       //此属性可以被废弃了
-      triggerChange: {
+      triggerChange:{
         type: Boolean,
         required: false,
         default: false
       },
-      readOnly: {
+      readOnly:{
         type: Boolean,
         required: false,
         default: false
       },
-      disabled: {
+      disabled:{
         type: Boolean,
         required: false,
         default: false
       },
-      showTime: {
+      showTime:{
         type: Boolean,
         required: false,
         default: false
       },
       getCalendarContainer: {
         type: Function,
-        default: () => document.body
+        default: (node) => node.parentNode
       }
     },
-    data() {
+    data () {
       let dateStr = this.value;
       return {
-        decorator: "",
-        momVal: !dateStr ? null : moment(dateStr, this.dateFormat)
+        decorator:"",
+        momVal:!dateStr?null:moment(dateStr,this.dateFormat)
       }
     },
     watch: {
-      value(val) {
-        if (!val) {
+      value (val) {
+        if(!val){
           this.momVal = null
-        } else {
-          this.momVal = moment(val, this.dateFormat)
+        }else{
+          this.momVal = moment(val,this.dateFormat)
         }
       }
     },
     methods: {
       moment,
-      handleDateChange(mom, dateStr) {
+      handleDateChange(mom,dateStr){
         this.$emit('change', dateStr);
       }
     },

Fichier diff supprimé car celui-ci est trop grand
+ 1787 - 490
src/components/jeecg/JEditableTable.vue


+ 27 - 14
src/components/jeecg/JEditor.vue

@@ -14,6 +14,7 @@
   import Editor from '@tinymce/tinymce-vue'
   import 'tinymce/themes/silver/theme'
   import 'tinymce/plugins/image'
+  import 'tinymce/plugins/link'
   import 'tinymce/plugins/media'
   import 'tinymce/plugins/table'
   import 'tinymce/plugins/lists'
@@ -22,7 +23,7 @@
   import 'tinymce/plugins/colorpicker'
   import 'tinymce/plugins/textcolor'
   import 'tinymce/plugins/fullscreen'
-
+  import { uploadAction,getFileAccessHttpUrl } from '@/api/manage'
   export default {
     components: {
       Editor
@@ -30,12 +31,12 @@
     props: {
       value: {
         type: String,
-        required: false
+        required:false
       },
-      triggerChange: {
+      triggerChange:{
         type: Boolean,
         default: false,
-        required: false
+        required:false
       },
       disabled: {
         type: Boolean,
@@ -43,11 +44,12 @@
       },
       plugins: {
         type: [String, Array],
-        default: 'lists image media table textcolor wordcount contextmenu fullscreen'
+        default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
       },
       toolbar: {
         type: [String, Array],
-        default: 'undo redo |  formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists image media table | removeformat | fullscreen'
+        default: 'undo redo |  formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
+        branding:false
       }
     },
     data() {
@@ -62,9 +64,23 @@
           toolbar: this.toolbar,
           branding: false,
           menubar: false,
+          toolbar_drawer: false,
           images_upload_handler: (blobInfo, success) => {
-            const img = 'data:image/jpeg;base64,' + blobInfo.base64()
-            success(img)
+            let formData = new FormData()
+            formData.append('file', blobInfo.blob(), blobInfo.filename());
+            formData.append('biz', "jeditor");
+            formData.append("jeditor","1");
+            uploadAction(window._CONFIG['domianURL']+"/sys/common/upload", formData).then((res) => {
+              if (res.success) {
+                if(res.message == 'local'){
+                  const img = 'data:image/jpeg;base64,' + blobInfo.base64()
+                  success(img)
+                }else{
+                  let img = getFileAccessHttpUrl(res.message)
+                  success(img)
+                }
+              }
+            })
           }
         },
         myValue: this.value
@@ -85,15 +101,12 @@
     },
     watch: {
       value(newValue) {
-        this.myValue = newValue
+        this.myValue = (newValue == null ? '' : newValue)
       },
       myValue(newValue) {
-        console.log(newValue)
-        if (this.triggerChange) {
-          console.log(1)
+        if(this.triggerChange){
           this.$emit('change', newValue)
-        } else {
-          console.log(2)
+        }else{
           this.$emit('input', newValue)
         }
       }

+ 25 - 14
src/components/jeecg/JFormContainer.vue

@@ -1,12 +1,13 @@
 <template>
-  <div v-if="disabled" class="jeecg-form-container-disabled">
+  <div :class="disabled?'jeecg-form-container-disabled':''">
+    <fieldset :disabled="disabled">
+      <slot name="detail"></slot>
+    </fieldset>
+    <slot name="edit"></slot>
     <fieldset disabled>
       <slot></slot>
     </fieldset>
   </div>
-  <div v-else>
-    <slot></slot>
-  </div>
 </template>
 
 <script>
@@ -21,30 +22,40 @@
    */
   export default {
     name: 'JFormContainer',
-    props: {
-      disabled: {
-        type: Boolean,
-        default: false,
-        required: false
+    props:{
+      disabled:{
+        type:Boolean,
+        default:false,
+        required:false
       }
     },
-    mounted() {
+    mounted(){
       console.log("我是表单禁用专用组件,但是我并不支持表单中iframe的内容禁用")
     }
   }
 </script>
 <style>
-  .jeecg-form-container-disabled {
+  .jeecg-form-container-disabled{
     cursor: not-allowed;
   }
-
   .jeecg-form-container-disabled fieldset[disabled] {
     -ms-pointer-events: none;
     pointer-events: none;
   }
-
-  .jeecg-form-container-disabled .ant-select {
+  .jeecg-form-container-disabled .ant-select{
     -ms-pointer-events: none;
     pointer-events: none;
   }
+
+  .jeecg-form-container-disabled .ant-upload-select{display:none}
+  .jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
+  .jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
+    -ms-pointer-events: auto !important;
+    pointer-events: auto !important;
+  }
+
+  .jeecg-form-container-disabled .ant-upload-list-item-actions .anticon-delete,
+  .jeecg-form-container-disabled .ant-upload-list-item .anticon-close{
+    display: none;
+  }
 </style>

+ 201 - 0
src/components/jeecg/JImageUpload.vue

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

+ 35 - 23
src/components/jeecg/JImportModal.vue

@@ -14,7 +14,7 @@
       :remove="handleRemove"
       :beforeUpload="beforeUpload">
       <a-button>
-        <a-icon type="upload"/>
+        <a-icon type="upload" />
         选择导入文件
       </a-button>
     </a-upload>
@@ -34,44 +34,50 @@
 </template>
 
 <script>
-  import {postAction} from '@/api/manage'
-
+  import { postAction } from '@/api/manage'
   export default {
     name: 'JImportModal',
-    props: {
-      url: {
+    props:{
+      url:{
+        type: String,
+        default: '',
+        required: false
+      },
+      biz:{
         type: String,
         default: '',
         required: false
       }
     },
-    data() {
+    data(){
       return {
-        visible: false,
-        uploading: false,
-        fileList: [],
-        uploadAction: ''
+        visible:false,
+        uploading:false,
+        fileList:[],
+        uploadAction:'',
+        foreignKeys:''
       }
     },
     watch: {
-      url(val) {
-        if (val) {
-          this.uploadAction = window._CONFIG['domianURL'] + val
+      url (val) {
+        if(val){
+         this.uploadAction = window._CONFIG['domianURL']+val
         }
       }
     },
-    created() {
-      this.uploadAction = window._CONFIG['domianURL'] + this.url
+    created () {
+      this.uploadAction = window._CONFIG['domianURL']+this.url
     },
 
-    methods: {
-      handleClose() {
-        this.visible = false
+    methods:{
+      handleClose(){
+        this.visible=false
       },
-      show() {
+      show(arg){
         this.fileList = []
         this.uploading = false
         this.visible = true
+        this.foreignKeys = arg;
       },
       handleRemove(file) {
         const index = this.fileList.indexOf(file);
@@ -84,19 +90,25 @@
         return false;
       },
       handleImport() {
-        const {fileList} = this;
+        const { fileList } = this;
         const formData = new FormData();
+        if(this.biz){
+          formData.append('isSingleTableImport',this.biz);
+        }
+        if(this.foreignKeys && this.foreignKeys.length>0){
+          formData.append('foreignKeys',this.foreignKeys);
+        }
         fileList.forEach((file) => {
           formData.append('files[]', file);
         });
         this.uploading = true
         postAction(this.uploadAction, formData).then((res) => {
           this.uploading = false
-          if (res.success) {
+          if(res.success){
             this.$message.success(res.message)
-            this.visible = false
+            this.visible=false
             this.$emit('ok')
-          } else {
+          }else{
             this.$message.warning(res.message)
           }
         })

+ 6 - 1
src/components/jeecg/JInput.vue

@@ -32,7 +32,12 @@
         handler:function(){
           this.initVal();
         }
-      }
+      },
+      // update-begin author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
+      type() {
+        this.backValue({ target: { value: this.inputVal } })
+      },
+      // update-end author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
     },
     model: {
       prop: 'value',

+ 225 - 0
src/components/jeecg/JModal/index.vue

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

+ 17 - 16
src/components/jeecg/JSelectMultiple.vue

@@ -14,52 +14,53 @@
   export default {
     name: 'JSelectMultiple',
     props: {
-      placeholder: {
+      placeholder:{
         type: String,
-        default: '',
+        default:'',
         required: false
       },
-      value: {
+      value:{
         type: String,
         required: false
       },
-      readOnly: {
+      readOnly:{
         type: Boolean,
         required: false,
         default: false
       },
-      options: {
+      options:{
         type: Array,
         required: true
       },
-      triggerChange: {
+      triggerChange:{
         type: Boolean,
         required: false,
         default: false
       }
     },
-    data() {
+    data(){
       return {
-        arrayValue: !this.value ? [] : this.value.split(",")
+        arrayValue:!this.value?[]:this.value.split(",")
       }
     },
-    watch: {
-      value(val) {
-        if (!val) {
+    watch:{
+      value (val) {
+        if(!val){
           this.arrayValue = []
-        } else {
+        }else{
           this.arrayValue = this.value.split(",")
         }
       }
     },
-    methods: {
-      onChange(selectedValue) {
-        if (this.triggerChange) {
+    methods:{
+      onChange (selectedValue) {
+        if(this.triggerChange){
           this.$emit('change', selectedValue.join(","));
-        } else {
+        }else{
           this.$emit('input', selectedValue.join(","));
         }
       },
     },
+
   }
 </script>

Fichier diff supprimé car celui-ci est trop grand
+ 43 - 47
src/components/jeecg/JSlider.vue


+ 578 - 95
src/components/jeecg/JSuperQuery.vue

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

+ 57 - 0
src/components/jeecg/JSwitch.vue

@@ -0,0 +1,57 @@
+<template>
+  <a-switch v-model="checkStatus" :disabled="disabled" @change="handleChange"/>
+</template>
+<script>
+
+  export default {
+    name: 'JSwitch',
+    props: {
+      value:{
+        type: String,
+        required: false
+      },
+      disabled:{
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      options:{
+        type:Array,
+        required:false,
+        default:()=>['Y','N']
+      }
+    },
+    data () {
+      return {
+        checkStatus: false
+      }
+    },
+    watch: {
+      value:{
+        immediate: true,
+        handler(val){
+          if(!val){
+            this.checkStatus = false
+            this.$emit('change', this.options[1]);
+          }else{
+            if(this.options[0]==val){
+              this.checkStatus = true
+            }else{
+              this.checkStatus = false
+            }
+          }
+        }
+      }
+    },
+    methods: {
+      handleChange(checked){
+        let flag = checked===false?this.options[1]:this.options[0];
+        this.$emit('change', flag);
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 80 - 76
src/components/jeecg/JTreeDict.vue

@@ -15,78 +15,78 @@
 </template>
 
 <script>
-  import {getAction} from '@/api/manage'
+  import { getAction } from '@/api/manage'
 
   export default {
     name: 'JTreeDict',
-    data() {
+    data(){
       return {
-        treeData: [],
-        treeValue: "",
-        url_root: "/sys/category/loadTreeRoot",
-        url_children: "/sys/category/loadTreeChildren",
-        url_view: '/sys/category/loadOne',
+        treeData:[],
+        treeValue: null,
+        url_root:"/sys/category/loadTreeRoot",
+        url_children:"/sys/category/loadTreeChildren",
+        url_view:'/sys/category/loadOne',
       }
     },
-    props: {
-      value: {
+    props:{
+      value:{
         type: String,
         required: false
       },
-      placeholder: {
+      placeholder:{
         type: String,
         default: '请选择',
         required: false
       },
-      parentCode: {
+      parentCode:{
         type: String,
         default: '',
         required: false
       },
-      field: {
+      field:{
         type: String,
         default: 'id',
         required: false
       },
-      root: {
-        type: Object,
-        required: false,
-        default: () => {
+      root:{
+        type:Object,
+        required:false,
+        default:()=>{
           return {
-            pid: '0'
+            pid:'0'
           }
         }
       },
-      async: {
-        type: Boolean,
-        default: false,
-        required: false
+      async:{
+        type:Boolean,
+        default:false,
+        required:false
       },
-      disabled: {
-        type: Boolean,
-        default: false,
-        required: false
+      disabled:{
+        type:Boolean,
+        default:false,
+        required:false
       }
     },
-    watch: {
-      root: {
-        handler(val) {
-          console.log("root-change", val)
+    watch:{
+      root:{
+        handler(val){
+          console.log("root-change",val)
         },
-        deep: true
+        deep:true
       },
-      parentCode: {
-        handler() {
+      parentCode:{
+        handler(){
           this.loadRoot()
         }
       },
-      value: {
-        handler() {
+      value:{
+        handler(){
           this.loadViewInfo()
         }
       }
     },
-    created() {
+    created(){
       this.loadRoot()
       this.loadViewInfo()
     },
@@ -94,43 +94,43 @@
       prop: 'value',
       event: 'change'
     },
-    methods: {
-      loadViewInfo() {
-        if (!this.value || this.value == "0") {
-          this.treeValue = ""
-        } else {
+    methods:{
+      loadViewInfo(){
+        if(!this.value || this.value=="0"){
+          this.treeValue = null
+        }else{
           let param = {
-            field: this.field,
-            val: this.value
+            field:this.field,
+            val:this.value
           }
-          getAction(this.url_view, param).then(res => {
-            if (res.success) {
+          getAction(this.url_view,param).then(res=>{
+            if(res.success){
               this.treeValue = {
-                value: this.value,
-                label: res.result.name
+                value:this.value,
+                label:res.result.name
               }
             }
           })
         }
       },
-      loadRoot() {
+      loadRoot(){
         let param = {
-          async: this.async,
-          pcode: this.parentCode
+          async:this.async,
+          pcode:this.parentCode
         }
-        getAction(this.url_root, param).then(res => {
-          if (res.success) {
+        getAction(this.url_root,param).then(res=>{
+          if(res.success){
             this.handleTreeNodeValue(res.result)
-            console.log("aaaa", res.result)
+            console.log("aaaa",res.result)
             this.treeData = [...res.result]
-          } else {
+          }else{
             this.$message.error(res.message)
           }
         })
       },
-      asyncLoadTreeData(treeNode) {
+      asyncLoadTreeData (treeNode) {
         return new Promise((resolve) => {
-          if (!this.async) {
+          if(!this.async){
             resolve()
             return
           }
@@ -140,53 +140,57 @@
           }
           let pid = treeNode.$vnode.key
           let param = {
-            pid: pid
+            pid:pid
           }
-          getAction(this.url_children, param).then(res => {
-            if (res.success) {
+          getAction(this.url_children,param).then(res=>{
+            if(res.success){
               this.handleTreeNodeValue(res.result)
-              this.addChildren(pid, res.result, this.treeData)
+              this.addChildren(pid,res.result,this.treeData)
               this.treeData = [...this.treeData]
             }
             resolve()
           })
         })
       },
-      addChildren(pid, children, treeArray) {
-        if (treeArray && treeArray.length > 0) {
-          for (let item of treeArray) {
-            if (item.key == pid) {
-              if (!children || children.length == 0) {
+      addChildren(pid,children,treeArray){
+        if(treeArray && treeArray.length>0){
+          for(let item of treeArray){
+            if(item.key == pid){
+              if(!children || children.length==0){
                 item.leaf = true
-              } else {
+              }else{
                 item.children = children
               }
               break
-            } else {
-              this.addChildren(pid, children, item.children)
+            }else{
+              this.addChildren(pid,children,item.children)
             }
           }
         }
       },
-      handleTreeNodeValue(result) {
-        let storeField = this.field == 'code' ? 'code' : 'key'
-        for (let i of result) {
+      handleTreeNodeValue(result){
+        let storeField = this.field=='code'?'code':'key'
+        for(let i of result){
           i.value = i[storeField]
-          i.isLeaf = (!i.leaf) ? false : true
-          if (i.children && i.children.length > 0) {
+          i.isLeaf = (!i.leaf)?false:true
+          if(i.children && i.children.length>0){
             this.handleTreeNodeValue(i.children)
           }
         }
       },
-      onChange(value) {
+      onChange(value){
         console.log(value)
-        this.$emit('change', value.value);
+        if(!value){
+          this.$emit('change', '');
+        }else{
+          this.$emit('change', value.value);
+        }
         this.treeValue = value
       },
-      onSearch(value) {
+      onSearch(value){
         console.log(value)
       },
-      getCurrTreeData() {
+      getCurrTreeData(){
         return this.treeData
       }
     }

+ 138 - 87
src/components/jeecg/JTreeSelect.vue

@@ -2,6 +2,7 @@
   <a-tree-select
     allowClear
     labelInValue
+    :getPopupContainer="(node) => node.parentNode"
     style="width: 100%"
     :disabled="disabled"
     :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
@@ -9,6 +10,7 @@
     :loadData="asyncLoadTreeData"
     :value="treeValue"
     :treeData="treeData"
+    :multiple="multiple"
     @change="onChange"
     @search="onSearch">
   </a-tree-select>
@@ -19,96 +21,120 @@
   * 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
   * <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
   * */
-  import {getAction} from '@/api/manage'
+  import { getAction } from '@/api/manage'
 
   export default {
     name: 'JTreeSelect',
     props: {
-      value: {
+      value:{
         type: String,
         required: false
       },
-      placeholder: {
+      placeholder:{
         type: String,
         default: '请选择',
         required: false
       },
-      dict: {
+      dict:{
         type: String,
         default: '',
         required: false
       },
-      pidField: {
+      pidField:{
         type: String,
         default: 'pid',
         required: false
       },
-      pidValue: {
+      pidValue:{
         type: String,
-        default: '0',
+        default: '',
         required: false
       },
-      disabled: {
-        type: Boolean,
-        default: false,
-        required: false
+      disabled:{
+        type:Boolean,
+        default:false,
+        required:false
       },
-      hasChildField: {
+      hasChildField:{
         type: String,
         default: '',
         required: false
+      },
+      condition:{
+        type:String,
+        default:'',
+        required:false
+      },
+      // 是否支持多选
+      multiple: {
+        type: Boolean,
+        default: false,
+      },
+      loadTriggleChange:{
+        type: Boolean,
+        default: false,
+        required:false
       }
     },
-    data() {
+    data () {
       return {
-        treeValue: "",
-        treeData: [],
-        url: "/sys/dict/loadTreeData",
-        view: '/sys/dict/loadDictItem/',
-        tableName: "",
-        text: "",
-        code: "",
+        treeValue: null,
+        treeData:[],
+        url:"/sys/dict/loadTreeData",
+        view:'/sys/dict/loadDictItem/',
+        tableName:"",
+        text:"",
+        code:"",
 
       }
     },
     watch: {
-      value() {
+      value () {
         this.loadItemByCode()
       },
-      dict() {
+      dict(){
         this.initDictInfo()
         this.loadRoot();
       }
     },
-    created() {
-      this.initDictInfo()
-      this.loadRoot()
-      this.loadItemByCode()
+    created(){
+      this.validateProp().then(()=>{
+        this.initDictInfo()
+        this.loadRoot()
+        this.loadItemByCode()
+      })
     },
     methods: {
-      loadItemByCode() {
-        if (!this.value || this.value == "0") {
-          this.treeValue = ""
-        } else {
-          getAction(`${this.view}${this.dict}`, {key: this.value}).then(res => {
-              console.log(res.result)
-            if (res.success) {
-              this.treeValue = {
-                key: this.value,
-                value: this.value,
-                label: res.result
-              }
+      loadItemByCode(){
+        if(!this.value || this.value=="0"){
+          this.treeValue = null
+        }else{
+          getAction(`${this.view}${this.dict}`,{key:this.value}).then(res=>{
+            if(res.success){
+              let values = this.value.split(',')
+              this.treeValue = res.result.map((item, index) => ({
+                key: values[index],
+                value: values[index],
+                label: item
+              }))
+              this.onLoadTriggleChange(res.result[0]);
             }
           })
         }
       },
-      initDictInfo() {
+      onLoadTriggleChange(text){
+        //只有单选才会触发
+        if(!this.multiple && this.loadTriggleChange){
+          this.$emit('change', this.value,text)
+        }
+      },
+      initDictInfo(){
         let arr = this.dict.split(",")
         this.tableName = arr[0]
         this.text = arr[1]
         this.code = arr[2]
       },
-      asyncLoadTreeData(treeNode) {
+      asyncLoadTreeData (treeNode) {
         return new Promise((resolve) => {
           if (treeNode.$vnode.children) {
             resolve()
@@ -116,88 +142,113 @@
           }
           let pid = treeNode.$vnode.key
           let param = {
-            pid: pid,
-            tableName: this.tableName,
-            text: this.text,
-            code: this.code,
-            pidField: this.pidField,
-            hasChildField: this.hasChildField
+            pid:pid,
+            tableName:this.tableName,
+            text:this.text,
+            code:this.code,
+            pidField:this.pidField,
+            hasChildField:this.hasChildField,
+            condition:this.condition
           }
-          getAction(this.url, param).then(res => {
-            if (res.success) {
-              for (let i of res.result) {
+          getAction(this.url,param).then(res=>{
+            if(res.success){
+              for(let i of res.result){
                 i.value = i.key
-                if (i.leaf == false) {
-                  i.isLeaf = false
-                } else if (i.leaf == true) {
-                  i.isLeaf = true
+                if(i.leaf==false){
+                  i.isLeaf=false
+                }else if(i.leaf==true){
+                  i.isLeaf=true
                 }
               }
-              this.addChildren(pid, res.result, this.treeData)
+              this.addChildren(pid,res.result,this.treeData)
               this.treeData = [...this.treeData]
             }
             resolve()
           })
         })
       },
-      addChildren(pid, children, treeArray) {
-        if (treeArray && treeArray.length > 0) {
-          for (let item of treeArray) {
-            if (item.key == pid) {
-              if (!children || children.length == 0) {
-                item.isLeaf = true
-              } else {
+      addChildren(pid,children,treeArray){
+        if(treeArray && treeArray.length>0){
+          for(let item of treeArray){
+            if(item.key == pid){
+              if(!children || children.length==0){
+                item.isLeaf=true
+              }else{
                 item.children = children
               }
               break
-            } else {
-              this.addChildren(pid, children, item.children)
+            }else{
+              this.addChildren(pid,children,item.children)
             }
           }
         }
       },
-      loadRoot() {
+      loadRoot(){
         let param = {
-          pid: this.pidValue,
-          tableName: this.tableName,
-          text: this.text,
-          code: this.code,
-          pidField: this.pidField,
-          hasChildField: this.hasChildField
+          pid:this.pidValue,
+          tableName:this.tableName,
+          text:this.text,
+          code:this.code,
+          pidField:this.pidField,
+          hasChildField:this.hasChildField,
+          condition:this.condition
         }
-        getAction(this.url, param).then(res => {
-          if (res.success && res.result) {
-              console.log(res.result)
-            for (let i of res.result) {
+        getAction(this.url,param).then(res=>{
+          if(res.success && res.result){
+            for(let i of res.result){
               i.value = i.key
-            //   i.disabled = true
-              if (i.leaf == false) {
-                i.isLeaf = false
-              } else if (i.leaf == true) {
-                i.isLeaf = true
+              if(i.leaf==false){
+                i.isLeaf=false
+              }else if(i.leaf==true){
+                i.isLeaf=true
               }
             }
             this.treeData = [...res.result]
-          } else {
-            console.log("数根节点查询结果-else", res)
+          }else{
+            console.log("数根节点查询结果-else",res)
           }
         })
       },
-      onChange(value) {
-        if (!value) {
+      onChange(value){
+        if(!value){
           this.$emit('change', '');
-          this.treeValue = ''
+          this.treeValue = null
+        } else if (value instanceof Array) {
+          this.$emit('change', value.map(item => item.value).join(','))
+          this.treeValue = value
         } else {
-          this.$emit('change', value.value);
+          this.$emit('change', value.value,value.label)
           this.treeValue = value
         }
 
       },
-      onSearch(value) {
+      onSearch(value){
         console.log(value)
       },
-      getCurrTreeData() {
+      getCurrTreeData(){
         return this.treeData
+      },
+      validateProp(){
+        let mycondition = this.condition
+        return new Promise((resolve,reject)=>{
+          if(!mycondition){
+            resolve();
+          }else{
+            try {
+              let test=JSON.parse(mycondition);
+              console.log("aaaaasdsdd",typeof test)
+              if(typeof test == 'object' && test){
+                resolve()
+              }else{
+                this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
+                reject()
+              }
+            } catch(e) {
+              this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
+              reject()
+            }
+          }
+        })
       }
     },
     //2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼

+ 52 - 12
src/components/jeecg/JTreeTable.vue

@@ -3,8 +3,11 @@
     :rowKey="rowKey"
     :columns="columns"
     :dataSource="dataSource"
-    v-bind="tableProps"
-    @expand="handleExpand">
+    :expandedRowKeys="expandedRowKeys"
+    v-bind="tableAttrs"
+    v-on="$listeners"
+    @expand="handleExpand"
+    @expandedRowsChange="expandedRowKeys=$event">
 
     <template v-for="(slotItem) of slots" :slot="slotItem" slot-scope="text, record, index">
       <slot :name="slotItem" v-bind="{text,record,index}"></slot>
@@ -14,7 +17,7 @@
 </template>
 
 <script>
-  import {getAction} from '@/api/manage'
+  import { getAction } from '@/api/manage'
 
   export default {
     name: 'JTreeTable',
@@ -30,8 +33,7 @@
       },
       queryParams: {
         type: Object,
-        default: () => {
-        }
+        default: () => ({})
       },
       // 查询顶级时的值,如果顶级为0,则传0
       topValue: {
@@ -52,13 +54,23 @@
       },
       tableProps: {
         type: Object,
-        default: () => {
-        }
+        default: () => ({})
+      },
+      /** 是否在创建组件的时候就查询数据 */
+      immediateRequest: {
+        type: Boolean,
+        default: true
+      },
+      condition:{
+        type:String,
+        default:'',
+        required:false
       }
     },
     data() {
       return {
-        dataSource: []
+        dataSource: [],
+        expandedRowKeys: []
       }
     },
     computed: {
@@ -77,6 +89,9 @@
           }
         }
         return slots
+      },
+      tableAttrs() {
+        return Object.assign(this.$attrs, this.tableProps)
       }
     },
     watch: {
@@ -88,20 +103,44 @@
       }
     },
     created() {
-      this.loadData()
+      if (this.immediateRequest) this.loadData()
     },
     methods: {
 
       /** 加载数据*/
       loadData(id = this.topValue, first = true, url = this.url) {
+        this.$emit('requestBefore', { first })
+
+        if (first) {
+          this.expandedRowKeys = []
+        }
+
         let params = Object.assign({}, this.queryParams || {})
         params[this.queryKey] = id
+        if(this.condition && this.condition.length>0){
+          params['condition'] = this.condition
+        }
+
         return getAction(url, params).then(res => {
-          let dataSource = res.result.map(item => {
+          let list = []
+          if (res.result instanceof Array) {
+            list = res.result
+          } else if (res.result.records instanceof Array) {
+            list = res.result.records
+          } else {
+            throw '返回数据类型不识别'
+          }
+          let dataSource = list.map(item => {
             // 判断是否标记了带有子级
             if (item.hasChildren === true) {
+              // 查找第一个带有dataIndex的值的列
+              let firstColumn
+              for (let column of this.columns) {
+                firstColumn = column.dataIndex
+                if (firstColumn) break
+              }
               // 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
-              let loadChild = {id: `${item.id}_loadChild`, name: 'loading...', isLoading: true}
+              let loadChild = { id: `${item.id}_loadChild`, [firstColumn]: 'loading...', isLoading: true }
               item.children = [loadChild]
             }
             return item
@@ -109,8 +148,9 @@
           if (first) {
             this.dataSource = dataSource
           }
+          this.$emit('requestSuccess', { first, dataSource, res })
           return Promise.resolve(dataSource)
-        })
+        }).finally(() => this.$emit('requestFinally', { first }))
       },
 
       /** 点击展开图标时触发 */

+ 343 - 83
src/components/jeecg/JUpload.vue

@@ -1,133 +1,244 @@
 <template>
-  <a-upload
-    name="file"
-    :multiple="true"
-    :action="uploadAction"
-    :headers="headers"
-    :data="{'isup':1,'bizPath':bizPath}"
-    :fileList="fileList"
-    :beforeUpload="beforeUpload"
-    @change="handleChange">
-    <a-button>
-      <a-icon type="upload"/>
-      {{ text }}
-    </a-button>
-  </a-upload>
+  <div :id="containerId" style="position: relative">
+
+    <!--  ---------------------------- begin 图片左右换位置 ------------------------------------- -->
+    <div class="movety-container" :style="{top:top+'px',left:left+'px',display:moveDisplay}" style="padding:0 8px;position: absolute;z-index: 91;height: 32px;width: 104px;text-align: center;">
+      <div :id="containerId+'-mover'" :class="showMoverTask?'uploadty-mover-mask':'movety-opt'" style="margin-top: 12px">
+        <a @click="moveLast" style="margin: 0 5px;"><a-icon type="arrow-left" style="color: #fff;font-size: 16px"/></a>
+        <a @click="moveNext" style="margin: 0 5px;"><a-icon type="arrow-right" style="color: #fff;font-size: 16px"/></a>
+      </div>
+    </div>
+    <!--  ---------------------------- end 图片左右换位置 ------------------------------------- -->
+
+    <a-upload
+      name="file"
+      :multiple="true"
+      :action="uploadAction"
+      :headers="headers"
+      :data="{'biz':bizPath}"
+      :fileList="fileList"
+      :beforeUpload="beforeUpload"
+      @change="handleChange"
+      :disabled="disabled"
+      :returnUrl="returnUrl"
+      :listType="complistType"
+      @preview="handlePreview"
+      :class="{'uploadty-disabled':disabled}">
+      <template>
+        <div v-if="isImageComp">
+          <a-icon type="plus" />
+          <div class="ant-upload-text">{{ text }}</div>
+        </div>
+        <a-button v-else-if="buttonVisible">
+         <a-icon type="upload" />{{ text }}
+        </a-button>
+      </template>
+    </a-upload>
+    <a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
+      <img alt="example" style="width: 100%" :src="previewImage" />
+    </a-modal>
+  </div>
 </template>
 
 <script>
 
   import Vue from 'vue'
-  import {ACCESS_TOKEN} from "@/store/mutation-types"
+  import { ACCESS_TOKEN } from "@/store/mutation-types"
+  import { getFileAccessHttpUrl } from '@/api/manage';
 
   const FILE_TYPE_ALL = "all"
   const FILE_TYPE_IMG = "image"
   const FILE_TYPE_TXT = "file"
-  const uidGenerator = () => {
-    return '-' + parseInt(Math.random() * 10000 + 1, 10);
+  const uidGenerator=()=>{
+    return '-'+parseInt(Math.random()*10000+1,10);
   }
-  const getFileName = (path) => {
-    if (path.lastIndexOf("\\") >= 0) {
-      let reg = new RegExp("\\\\", "g");
-      path = path.replace(reg, "/");
+  const getFileName=(path)=>{
+    if(path.lastIndexOf("\\")>=0){
+      let reg=new RegExp("\\\\","g");
+      path = path.replace(reg,"/");
     }
-    return path.substring(path.lastIndexOf("/") + 1);
+    return path.substring(path.lastIndexOf("/")+1);
   }
   export default {
     name: 'JUpload',
-    data() {
+    data(){
       return {
-        uploadAction: window._CONFIG['domianURL'] + "/sys/common/upload",
-        urlDownload: window._CONFIG['domianURL'] + "/sys/common/download/",
-        headers: {},
-        fileList: []
+        uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
+        headers:{},
+        fileList: [],
+        newFileList: [],
+        uploadGoOn:true,
+        previewVisible: false,
+        //---------------------------- begin 图片左右换位置 -------------------------------------
+        previewImage: '',
+        containerId:'',
+        top:'',
+        left:'',
+        moveDisplay:'none',
+        showMoverTask:false,
+        moverHold:false,
+        currentImg:''
+        //---------------------------- end 图片左右换位置 -------------------------------------
       }
     },
-    props: {
-      text: {
-        type: String,
-        required: false,
-        default: "点击上传"
+    props:{
+      text:{
+        type:String,
+        required:false,
+        default:"点击上传"
       },
-      fileType: {
-        type: String,
-        required: false,
-        default: FILE_TYPE_ALL
+      fileType:{
+        type:String,
+        required:false,
+        default:FILE_TYPE_ALL
       },
       /*这个属性用于控制文件上传的业务路径*/
-      bizPath: {
-        type: String,
-        required: false,
-        default: "temp"
+      bizPath:{
+        type:String,
+        required:false,
+        default:"temp"
+      },
+      value:{
+        type:[String,Array],
+        required:false
       },
-      value: {
-        type: String,
-        required: false
+      // update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
+      disabled:{
+        type:Boolean,
+        required:false,
+        default: false
       },
+      // update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
       //此属性被废弃了
-      triggerChange: {
+      triggerChange:{
         type: Boolean,
         required: false,
         default: false
       },
+      /**
+       * update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url,
+       * true:仅返回url
+       * false:返回fileName filePath fileSize
+       */
+      returnUrl:{
+        type:Boolean,
+        required:false,
+        default: true
+      },
+      number:{
+        type:Number,
+        required:false,
+        default: 0
+      },
+      buttonVisible:{
+        type:Boolean,
+        required:false,
+        default: true
+      },
     },
-    watch: {
-      value(val) {
-        this.initFileList(val)
+    watch:{
+      value:{
+        immediate: true,
+        handler() {
+          let val = this.value
+          if (val instanceof Array) {
+            if(this.returnUrl){
+              this.initFileList(val.join(','))
+            }else{
+              this.initFileListArr(val);
+            }
+          } else {
+            this.initFileList(val)
+          }
+        }
       }
     },
-    created() {
+    computed:{
+      isImageComp(){
+        return this.fileType === FILE_TYPE_IMG
+      },
+      complistType(){
+        return this.fileType === FILE_TYPE_IMG?'picture-card':'text'
+      }
+    },
+    created(){
       const token = Vue.ls.get(ACCESS_TOKEN);
-      this.headers = {"X-Access-Token": token}
+      //---------------------------- begin 图片左右换位置 -------------------------------------
+      this.headers = {"X-Access-Token":token};
+      this.containerId = 'container-ty-'+new Date().getTime();
+      //---------------------------- end 图片左右换位置 -------------------------------------
     },
 
-    methods: {
-      initFileList(paths) {
-        if (!paths || paths.length == 0) {
-          return [];
+    methods:{
+      initFileListArr(val){
+        if(!val || val.length==0){
+          this.fileList = [];
+          return;
+        }
+        let fileList = [];
+        for(var a=0;a<val.length;a++){
+          let url = getFileAccessHttpUrl(val[a].filePath);
+          fileList.push({
+            uid:uidGenerator(),
+            name:val[a].fileName,
+            status: 'done',
+            url: url,
+            response:{
+              status:"history",
+              message:val[a].filePath
+            }
+          })
+        }
+        this.fileList = fileList
+      },
+      initFileList(paths){
+        if(!paths || paths.length==0){
+          //return [];
+          // update-begin- --- author:os_chengtgen ------ date:20190729 ---- for:issues:326,Jupload组件初始化bug
+          this.fileList = [];
+          return;
+          // update-end- --- author:os_chengtgen ------ date:20190729 ---- for:issues:326,Jupload组件初始化bug
         }
         let fileList = [];
         let arr = paths.split(",")
-        for (var a = 0; a < arr.length; a++) {
+        for(var a=0;a<arr.length;a++){
+          let url = getFileAccessHttpUrl(arr[a]);
           fileList.push({
-            uid: uidGenerator(),
-            name: getFileName(arr[a]),
+            uid:uidGenerator(),
+            name:getFileName(arr[a]),
             status: 'done',
-            url: this.urlDownload + arr[a],
-            response: {
-              status: "history",
-              message: arr[a]
+            url: url,
+            response:{
+              status:"history",
+              message:arr[a]
             }
           })
         }
         this.fileList = fileList
       },
-      handlePathChange() {
+      handlePathChange(){
         let uploadFiles = this.fileList
         let path = ''
-        if (!uploadFiles || uploadFiles.length == 0) {
+        if(!uploadFiles || uploadFiles.length==0){
           path = ''
         }
         let arr = [];
 
-        for (var a = 0; a < uploadFiles.length; a++) {
+        for(var a=0;a<uploadFiles.length;a++){
           arr.push(uploadFiles[a].response.message)
         }
-        if (arr.length > 0) {
+        if(arr.length>0){
           path = arr.join(",")
         }
         this.$emit('change', path);
       },
-      beforeUpload(file) {
+      beforeUpload(file){
+        this.uploadGoOn=true
         var fileType = file.type;
-        if (fileType === FILE_TYPE_IMG) {
-          if (fileType.indexOf('image') < 0) {
+        if(this.fileType===FILE_TYPE_IMG){
+          if(fileType.indexOf('image')<0){
             this.$message.warning('请上传图片');
-            return false;
-          }
-        } else if (fileType === FILE_TYPE_TXT) {
-          if (fileType.indexOf('image') >= 0) {
-            this.$message.warning('请上传文件');
+            this.uploadGoOn=false
             return false;
           }
         }
@@ -136,31 +247,162 @@
       },
       handleChange(info) {
         console.log("--文件列表改变--")
+        if(!info.file.status && this.uploadGoOn === false){
+          info.fileList.pop();
+        }
         let fileList = info.fileList
-        if (info.file.status === 'done') {
-          if (info.file.response.success) {
+        if(info.file.status==='done'){
+          if(this.number>0){
+            fileList = fileList.slice(-this.number);
+          }
+          if(info.file.response.success){
             fileList = fileList.map((file) => {
               if (file.response) {
-                file.url = this.urlDownload + file.response.message;
+                let reUrl = file.response.message;
+                file.url = getFileAccessHttpUrl(reUrl);
               }
               return file;
             });
           }
-          this.$message.success(`${info.file.name} 上传成功!`);
-        } else if (info.file.status === 'error') {
+          //this.$message.success(`${info.file.name} 上传成功!`);
+        }else if (info.file.status === 'error') {
           this.$message.error(`${info.file.name} 上传失败.`);
-        } else if (info.file.status === 'removed') {
+        }else if(info.file.status === 'removed'){
           this.handleDelete(info.file)
         }
         this.fileList = fileList
-        if (info.file.status === 'done' || info.file.status === 'removed') {
-          this.handlePathChange()
+        if(info.file.status==='done' || info.file.status === 'removed'){
+          //returnUrl为true时仅返回文件路径
+          if(this.returnUrl){
+            this.handlePathChange()
+          }else{
+            //returnUrl为false时返回文件名称、文件路径及文件大小
+            this.newFileList = [];
+            for(var a=0;a<fileList.length;a++){
+              var fileJson = {
+                fileName:fileList[a].name,
+                filePath:fileList[a].response.message,
+                fileSize:fileList[a].size
+              };
+              this.newFileList.push(fileJson);
+            }
+            this.$emit('change', this.newFileList);
+          }
         }
       },
-      handleDelete(file) {
+      handleDelete(file){
         //如有需要新增 删除逻辑
         console.log(file)
       },
+      handlePreview(file){
+        if(this.fileType === FILE_TYPE_IMG){
+          this.previewImage = file.url || file.thumbUrl;
+          this.previewVisible = true;
+        }else{
+          location.href=file.url
+        }
+      },
+      handleCancel(){
+        this.previewVisible = false;
+      },
+      //---------------------------- begin 图片左右换位置 -------------------------------------
+      moveLast(){
+        //console.log(ev)
+        //console.log(this.fileList)
+        //console.log(this.currentImg)
+        let index = this.getIndexByUrl();
+        if(index==0){
+          this.$message.warn('未知的操作')
+        }else{
+          let curr = this.fileList[index].url;
+          let last = this.fileList[index-1].url;
+          let arr =[]
+          for(let i=0;i<this.fileList.length;i++){
+            if(i==index-1){
+              arr.push(curr)
+            }else if(i==index){
+              arr.push(last)
+            }else{
+              arr.push(this.fileList[i].url)
+            }
+          }
+          this.currentImg = last
+          this.$emit('change',arr.join(','))
+        }
+      },
+      moveNext(){
+        let index = this.getIndexByUrl();
+        if(index==this.fileList.length-1){
+          this.$message.warn('已到最后~')
+        }else{
+          let curr = this.fileList[index].url;
+          let next = this.fileList[index+1].url;
+          let arr =[]
+          for(let i=0;i<this.fileList.length;i++){
+            if(i==index+1){
+              arr.push(curr)
+            }else if(i==index){
+              arr.push(next)
+            }else{
+              arr.push(this.fileList[i].url)
+            }
+          }
+          this.currentImg = next
+          this.$emit('change',arr.join(','))
+        }
+      },
+      getIndexByUrl(){
+        for(let i=0;i<this.fileList.length;i++){
+          if(this.fileList[i].url === this.currentImg || encodeURI(this.fileList[i].url) === this.currentImg){
+            return i;
+          }
+        }
+        return -1;
+      }
+    },
+    mounted(){
+      const moverObj = document.getElementById(this.containerId+'-mover');
+      moverObj.addEventListener('mouseover',()=>{
+        this.moverHold = true
+        this.moveDisplay = 'block';
+      });
+      moverObj.addEventListener('mouseout',()=>{
+        this.moverHold = false
+        this.moveDisplay = 'none';
+      });
+      let picList = document.getElementById(this.containerId)?document.getElementById(this.containerId).getElementsByClassName('ant-upload-list-picture-card'):[];
+      if(picList && picList.length>0){
+        picList[0].addEventListener('mouseover',(ev)=>{
+          ev = ev || window.event;
+          let target = ev.target || ev.srcElement;
+          if('ant-upload-list-item-info' == target.className){
+            this.showMoverTask=false
+            let item = target.parentElement
+            this.left = item.offsetLeft
+            this.top=item.offsetTop+item.offsetHeight-50;
+            this.moveDisplay = 'block';
+            this.currentImg = target.getElementsByTagName('img')[0].src
+          }
+
+        });
+
+        picList[0].addEventListener('mouseout',(ev)=>{
+          ev = ev || window.event;
+          let target = ev.target || ev.srcElement;
+          //console.log('移除',target)
+          if('ant-upload-list-item-info' == target.className){
+            this.showMoverTask=true
+            setTimeout(()=>{
+              if(this.moverHold === false)
+                this.moveDisplay = 'none';
+            },100)
+          }
+          if('ant-upload-list-item ant-upload-list-item-done' == target.className || 'ant-upload-list ant-upload-list-picture-card'== target.className){
+            this.moveDisplay = 'none';
+          }
+        })
+        //---------------------------- end 图片左右换位置 -------------------------------------
+      }
     },
     model: {
       prop: 'value',
@@ -169,6 +411,24 @@
   }
 </script>
 
-<style scoped>
-
+<style lang="less">
+.uploadty-disabled{
+  .ant-upload-list-item {
+    .anticon-close{
+      display: none;
+    }
+    .anticon-delete{
+      display: none;
+    }
+  }
+}
+  //---------------------------- begin 图片左右换位置 -------------------------------------
+  .uploadty-mover-mask{
+    background-color: rgba(0, 0, 0, 0.5);
+    opacity: .8;
+    color: #fff;
+    height: 28px;
+    line-height: 28px;
+  }
+  //---------------------------- end 图片左右换位置 -------------------------------------
 </style>

+ 3 - 0
src/components/jeecg/README.md

@@ -477,6 +477,7 @@ online用 实际开发请使用components/dict/JMultiSelectTag
 | dict      |string   | ✔| 表名,显示字段名,存储字段名拼接的字符串 |
 | pidField      |string   | ✔| 父ID的字段名 |
 | pidValue      |string   | | 根节点父ID的值 默认'0' 不可以设置为空,如果想使用此组件,而数据库根节点父ID为空,请修改之 |
+| multiple      |boolean   | |是否支持多选 |
 
 使用示例
 ----
@@ -507,3 +508,5 @@ online用 实际开发请使用components/dict/JMultiSelectTag
   }
 </script>
 ```
+
+

+ 101 - 41
src/components/jeecg/README_JEditableTable.md

@@ -1,30 +1,34 @@
 # JEditableTable 帮助文档
 
 ## 参数配置
-| 参数         | 类型    | 必填 | 说明                                                           |
-|--------------|---------|------|----------------------------------------------------------------|
-| 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 时所需的参数
 
@@ -35,10 +39,11 @@
 
 #### 当 type=select 时所需的参数
 
-| 参数       | 类型    | 必填 | 说明                                 |
-|------------|---------|------|--------------------------------------|
-| options    | array   | ✔️    | 下拉选项列表,详见下表               |
-| allowInput | boolean |      | 是否允许用户输入内容,并创建新的内容 |
+| 参数       | 类型    | 必填 | 说明                                               |
+|------------|---------|------|----------------------------------------------------|
+| options    | array   | ✔️    | 下拉选项列表,详见下表                             |
+| allowInput | boolean |      | 是否允许用户输入内容,并创建新的内容               |
+| dictCode   | String  |      | 数据字典Code,若options也有值,则拼接在options后面 |
 
 ##### options 所需参数
 
@@ -68,16 +73,18 @@
 
 - `required` 是否必填,可选值为`true`or`false`
 - `pattern` 正则表达式验证,只有成功匹配该正则的值才能成功通过验证
+- `handler` 自定义函数校验,使用方法请见[示例五](#示例五)
 - `message` 当验证未通过时显示的提示文本,可以使用`${...}`变量替换文本(详见`${...} 变量使用方式`)
 - 配置示例请看[示例二](#示例二)
 
 ## 事件
 
-| 事件名          | 触发时机                                           | 参数                          |
-|-----------------|----------------------------------------------------|-------------------------------|
-| added           | 当添加行操作完成后触发                             |                               |
-| deleted         | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id    |
-| selectRowChange | 当行被选中或取消选中时触发                         | `selectedRowIds` 被选中行的id |
+| 事件名          | 触发时机                                           | 参数                                             |
+|-----------------|----------------------------------------------------|--------------------------------------------------|
+| added           | 当添加行操作完成后触发                             |                                                  |
+| deleted         | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id                       |
+| selectRowChange | 当行被选中或取消选中时触发                         | `selectedRowIds` 被选中行的id                    |
+| valueChange     | 当数据发生改变的时候触发的事件                     | `{ type, row, column, value, target }` Event对象 |
 
 ## 方法
 
@@ -108,13 +115,11 @@
 
 - `参数:`
 
-
 | 参数名              | 类型    | 必填 | 说明                                                                |
 |---------------------|---------|------|---------------------------------------------------------------------|
 | num                 | number  |      | 添加几行,默认为1                                                   |
 | forceScrollToBottom | boolean |      | 是否在添加后无论用户的滚动条在什么位置都强制滚动到底部,默认为false |
 
-
 - `返回值:` 无
 
 ### removeRows
@@ -123,12 +128,10 @@
 
 - `参数:`
 
-
 | 参数名 | 类型            | 必填 | 说明                                                                                       |
 |--------|-----------------|------|--------------------------------------------------------------------------------------------|
 | id     | string 或 array | ✔️    | 被删除行的id。如果要删除一个,可以直接传id,如果要删除多个,需要将多个id封装成一个数组传入 |
 
-
 - `返回值:` 无
 
 ### removeSelectedRows
@@ -144,14 +147,12 @@
 
 - `参数:`
 
-
 | 参数名   | 类型     | 必填 | 说明                                                                                                                                                      |
 |----------|----------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
 | callback | function | ✔️    | 获取值的回调方法,会传入`error`和`values`两个参数。`error`:未通过验证的数量,当等于`0`时代表验证通过;`values`:获取的值(即使未通过验证该字段也有数据) |
 | validate | boolean  |      | 是否进行表单验证,默认为`true`,设为`false`则代表忽略表单验证                                                                                             |
 | rowIds   | array    |      | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组                                      |
 
-
 - `返回值:` 无
 
 
@@ -172,7 +173,6 @@
 | validate | boolean |      | 是否进行表单验证,默认为`true`,设为`false`则代表忽略表单验证                                                        |
 | rowIds   | array   |      | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组 |
 
-
 - `返回值:` object
     - `error` 未通过验证的数量,当等于`0`时代表验证通过
     - `values` 获取的值(即使未通过验证该字段也有数据)
@@ -220,7 +220,6 @@ if (error === 0) {
 |----------|---------|------|-------------------------------|
 | validate | boolean |      | 同`getValues`的`validate`参数 |
 
-
 - `返回值:` Promise
 
 ### setValues
@@ -229,12 +228,10 @@ if (error === 0) {
 
 - `参数:`
 
-
 | 参数名 | 类型  | 必填 | 说明                                                       |
 |--------|-------|------|------------------------------------------------------------|
 | values | array |      | 传入一个数组,数组中的每项都是一行的新值,具体见下面的示例 |
 
-
 - `返回值:` 无
 - `示例:`
 
@@ -256,6 +253,19 @@ setValues([
     }
 ])
 ```
+### clearSelection
+
+主动清空选择的行
+
+- `参数:` 无
+- `返回值:` 无
+
+## 内置插槽
+
+| 插槽名       | 说明                                                 |
+|--------------|------------------------------------------------------|
+| buttonBefore | 在操作按钮的**前面**插入插槽,不受`actionButton`属性的影响 |
+| buttonAfter  | 在操作按钮的**后面**插入插槽,不受`actionButton`属性的影响 |
 
 ## ${...} 变量使用方式
 
@@ -490,7 +500,6 @@ this.$refs.editableTable.getValues((error, values) => {
                         width: '8%',
                         type: FormTypes.slot, // 定义该列为 自定义插值列 
                         slotName: 'action' // slot 的名称,对应 v-slot 冒号后面和等号前面的内容
-
                     }
                 ]
             }
@@ -499,6 +508,7 @@ this.$refs.editableTable.getValues((error, values) => {
             /* a 标签的点击事件,删除当前选中的行 */
             handleDelete(props) {
                 // 参数解释
+                // props.index :当前行的下标
                 // props.text :当前值,可能是defaultValue定义的值,也可能是从dataSource中取出的值
                 // props.rowId :当前选中行的id,如果是新增行则是临时id
                 // props.column :当前操作的列
@@ -514,4 +524,54 @@ this.$refs.editableTable.getValues((error, values) => {
         }
     }
 </script>
+```
+
+## 示例五
+
+```js
+// 该示例是自定义函数校验
+columns: [
+    {
+        title: '字段名称',
+        key: 'dbFieldName',
+        type: FormTypes.input,
+        defaultValue: '',
+        validateRules: [
+            {
+                // 自定义函数校验 handler
+                handler(type, value, row, column, callback, target) {
+                    // type 触发校验的类型(input、change、blur)
+                    // value 当前校验的值
+                    // callback(flag, message) 方法必须执行且只能执行一次
+                    //          flag = 是否通过了校验,不填写或者填写 null 代表不进行任何操作
+                    //          message = 提示的类型,默认使用配置的 message
+                    // target 行编辑的实例对象
+
+                    if (type === 'blur') {
+
+                        if (value === 'abc') {
+                            callback(false, '${title}不能是abc') // false = 未通过,可以跟自定义提示
+                            return
+                        }
+
+                        let { values } = target.getValuesSync({ validate: false })
+                        let count = 0
+                        for (let val of values) {
+                            if (val['dbFieldName'] === value) {
+                                if (++count >= 2) {
+                                    callback(false, '${title}不能重复')
+                                    return
+                                }
+                            }
+                        }
+                        callback(true) // true = 通过验证
+                    } else {
+                        callback() // 不填写或者填写 null 代表不进行任何操作
+                    }
+                },
+                message: '${title}默认提示'
+            }
+        ]
+    },
+]
 ```

+ 4 - 2
src/components/jeecg/index.js

@@ -1,6 +1,8 @@
-import T from './JFormContainer.vue'
+import JModal from './JModal'
+import JFormContainer from './JFormContainer.vue'
 
 let install = function (Vue) {
-  Vue.component('JFormContainer', T);
+  Vue.component('JFormContainer', JFormContainer)
+  Vue.component(JModal.name, JModal)
 }
 export default {install};

+ 113 - 0
src/components/jeecg/minipop/JFilePop.vue

@@ -0,0 +1,113 @@
+<template>
+  <div>
+    <a-modal
+      title="文件上传"
+      :width="width"
+      :visible="visible"
+      @ok="ok"
+      cancelText="取消"
+      @cancel="close">
+      <!--style="top: 20px;"-->
+      <j-upload :file-type="fileType" :value="filePath" @change="handleChange" :disabled="disabled"></j-upload>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+  import JUpload from '@/components/jeecg/JUpload'
+  import { getFileAccessHttpUrl } from '@/api/manage';
+
+  const getFileName=(path)=>{
+    if(path.lastIndexOf("\\")>=0){
+      let reg=new RegExp("\\\\","g");
+      path = path.replace(reg,"/");
+    }
+    return path.substring(path.lastIndexOf("/")+1);
+  }
+
+  export default {
+    name: 'JFilePop',
+    components: { JUpload },
+    props:{
+      title:{
+        type:String,
+        default:'',
+        required:false
+      },
+      position:{
+        type:String,
+        default:'right',
+        required:false
+      },
+      height:{
+        type:Number,
+        default:200,
+        required:false
+      },
+      width:{
+        type:Number,
+        default:520,
+        required:false
+      },
+
+      popContainer:{
+        type:String,
+        default:'',
+        required:false
+      },
+      disabled:{
+        type:Boolean,
+        default:false,
+        required:false
+      }
+    },
+    data(){
+      return {
+        visible:false,
+        filePath:'',
+        id:'',
+        fileType:'file'
+
+      }
+    },
+    methods:{
+      handleChange(value){
+        this.filePath = value;
+      },
+      show(id,value,flag){
+        this.id = id;
+        this.filePath = value;
+        this.visible=true
+        if(flag === 'img'){
+          this.fileType = 'image'
+        }else{
+          this.fileType = 'file'
+        }
+
+      },
+      ok(){
+        if(!this.filePath){
+          this.$message.error("未上传任何文件")
+          return false;
+        }
+        let arr = this.filePath.split(",")
+        let obj = {
+          name:getFileName(arr[0]),
+          url:getFileAccessHttpUrl(arr[0]),
+          path:this.filePath,
+          status: 'done',
+          id:this.id
+        }
+        this.$emit('ok',obj)
+        this.visible=false
+      },
+      close(){
+        this.visible=false
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 98 - 0
src/components/jeecg/minipop/JInputPop.vue

@@ -0,0 +1,98 @@
+<template>
+  <a-popover trigger="contextmenu" v-model="visible" :placement="position">
+    <!--"(node) => node.parentNode.parentNode"-->
+    <div slot="title">
+      <span>{{ title }}</span>
+      <span style="float: right" title="关闭">
+        <a-icon type="close" @click="visible=false"/>
+      </span>
+    </div>
+    <a-input :value="inputContent" @change="handleInputChange">
+      <a-icon slot="suffix" type="fullscreen" @click.stop="pop" />
+    </a-input>
+    <div slot="content">
+      <textarea :value="inputContent" @input="handleInputChange" :style="{ height: height + 'px', width: width + 'px' }"></textarea>
+    </div>
+  </a-popover>
+</template>
+
+<script>
+  export default {
+    name: 'JInputPop',
+    props:{
+      title:{
+        type:String,
+        default:'',
+        required:false
+      },
+      position:{
+        type:String,
+        default:'right',
+        required:false
+      },
+      height:{
+        type:Number,
+        default:200,
+        required:false
+      },
+      width:{
+        type:Number,
+        default:150,
+        required:false
+      },
+      value:{
+        type:String,
+        required:false
+      },
+      popContainer:{
+        type:String,
+        default:'',
+        required:false
+      }
+
+    },
+    data(){
+      return {
+        visible:false,
+        inputContent:''
+
+      }
+    },
+
+    watch:{
+      value:{
+        immediate:true,
+        handler:function(){
+          if(this.value && this.value.length>0){
+            this.inputContent = this.value;
+          }
+        }
+      },
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    },
+    methods:{
+      handleInputChange(event){
+        this.inputContent = event.target.value
+        this.$emit('change',this.inputContent)
+      },
+      pop(){
+        this.visible=true
+      },
+      getPopupContainer(node){
+        if(!this.popContainer){
+          return node.parentNode
+        }else{
+          return document.getElementById(this.popContainer)
+        }
+
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 21 - 2
src/components/jeecgbiz/JSelectBizComponent/JSelectBizComponentModal.vue

@@ -63,12 +63,14 @@
 
 <script>
   import { getAction } from '@/api/manage'
+  import Ellipsis from '@/components/Ellipsis'
   import { JeecgListMixin } from '@/mixins/JeecgListMixin'
   import { cloneObject, pushIfNotExist } from '@/utils/util'
 
   export default {
     name: 'JSelectBizComponentModal',
     mixins: [JeecgListMixin],
+    components: { Ellipsis },
     props: {
       value: {
         type: Array,
@@ -128,12 +130,15 @@
         type: String,
         default: 'id'
       },
+      // 过长裁剪长度,设置为 -1 代表不裁剪
+      ellipsisLength: {
+        type: Number,
+        default: 12
+      },
     },
     data() {
       return {
         innerValue: [],
-        // 表头
-        innerColumns: this.columns,
         // 已选择列表
         selectedTable: {
           pagination: false,
@@ -147,6 +152,7 @@
           ],
           dataSource: [],
         },
+        renderEllipsis: (value) => (<ellipsis length={this.ellipsisLength}>{value}</ellipsis>),
         url: { list: this.listUrl },
         /* 分页参数 */
         ipagination: {
@@ -164,6 +170,19 @@
         dataSourceMap: {},
       }
     },
+    computed: {
+      // 表头
+      innerColumns() {
+        let columns = cloneObject(this.columns)
+        columns.forEach(column => {
+          // 给所有的列加上过长裁剪
+          if (this.ellipsisLength !== -1) {
+            column.customRender = (text) => this.renderEllipsis(text)
+          }
+        })
+        return columns
+      },
+    },
     watch: {
       value: {
         deep: true,

+ 1 - 1
src/components/jeecgbiz/JSelectBizComponent/README.md

@@ -29,7 +29,7 @@ export default {
 | selectButtonText      | String  |      | "选择"       | 选择按钮的文字                                                                       |
 | queryParamText        | String  |      | null         | 查询条件显示文字,不传则使用 `name`                                                  |
 | columns               | Array   | 是   |              | 列配置项,与antd的table的配置完全一致。列的第一项会被配置成右侧已选择的列表上        |
-| columns[0].widthRight | Array   |      | null         | 仅列的第一项可以应用此配置,表示右侧已选择列表的宽度,建议 `70%`,不传则应用`width`  |
+| columns[0].widthRight | String   |      | null         | 仅列的第一项可以应用此配置,表示右侧已选择列表的宽度,建议 `70%`,不传则应用`width`  |
 | placeholder           | String  |      | "请选择"     | 占位符                                                                               |
 | disabled              | Boolean |      | false        | 是否禁用                                                                             |
 | multiple              | Boolean |      | false        | 是否可多选                                                                           |

+ 17 - 10
src/components/jeecgbiz/JSelectBizComponent/index.vue

@@ -9,8 +9,9 @@
           :options="selectOptions"
           allowClear
           :disabled="disabled"
-          :open="false"
+          :open="selectOpen"
           style="width: 100%;"
+          @dropdownVisibleChange="handleDropdownVisibleChange"
           @click.native="visible=(buttons?visible:true)"
         />
       </slot>
@@ -85,7 +86,8 @@
         selectValue: [],
         selectOptions: [],
         dataSourceMap: {},
-        visible: false
+        visible: false,
+        selectOpen: false,
       }
     },
     computed: {
@@ -128,31 +130,36 @@
         this.selectOptions = options
         this.dataSourceMap = dataSourceMap
       },
+      handleDropdownVisibleChange() {
+        // 解决antdv自己的bug —— open 设置为 false 了,点击后还是添加了 open 样式,导致点击事件失效
+        this.selectOpen = true
+        this.$nextTick(() => {
+          this.selectOpen = false
+        })
+      },
     }
   }
 </script>
 
-<style lang="scss" scoped>
+<style lang="less" scoped>
   .j-select-biz-component-box {
 
-    $width: 82px;
+    @width: 82px;
 
     .left {
-      width: calc(100% - #{$width} - 8px);
+      width: calc(100% - @width - 8px);
     }
 
     .right {
-      width: #{$width};
+      width: @width;
     }
 
     .full {
       width: 100%;
     }
 
-    /deep/ {
-      .ant-select-search__field {
-        display: none !important;
-      }
+    /deep/ .ant-select-search__field {
+      display: none !important;
     }
   }
 </style>

+ 45 - 45
src/components/jeecgbiz/JSelectDepart.vue

@@ -11,7 +11,7 @@
       :modal-width="modalWidth"
       :multi="multi"
       :rootOpened="rootOpened"
-      :depart-id="value"
+      :depart-id="departIds"
       @ok="handleOK"
       @initComp="initComp"/>
   </div>
@@ -19,81 +19,83 @@
 
 <script>
   import JSelectDepartModal from './modal/JSelectDepartModal'
-
   export default {
     name: 'JSelectDepart',
-    components: {
+    components:{
       JSelectDepartModal
     },
-    props: {
-      modalWidth: {
-        type: Number,
-        default: 500,
-        required: false
+    props:{
+      modalWidth:{
+        type:Number,
+        default:500,
+        required:false
       },
-      multi: {
-        type: Boolean,
-        default: false,
-        required: false
+      multi:{
+        type:Boolean,
+        default:false,
+        required:false
       },
-      rootOpened: {
-        type: Boolean,
-        default: true,
-        required: false
+      rootOpened:{
+        type:Boolean,
+        default:true,
+        required:false
       },
-      value: {
-        type: String,
-        required: false
+      value:{
+        type:String,
+        required:false
       },
-      disabled: {
+      disabled:{
         type: Boolean,
         required: false,
         default: false
+      },
+      // 自定义返回字段,默认返回 id
+      customReturnField: {
+        type: String,
+        default: 'id'
       }
     },
-    data() {
+    data(){
       return {
-        visible: false,
-        confirmLoading: false,
-        departNames: "",
-        departIds: ''
+        visible:false,
+        confirmLoading:false,
+        departNames:"",
+        departIds:''
       }
     },
-    mounted() {
+    mounted(){
       this.departIds = this.value
     },
-    watch: {
-      value(val) {
-        this.departIds = val
+    watch:{
+      value(val){
+        if (this.customReturnField === 'id') {
+          this.departIds = val
+        }
       }
     },
-    methods: {
-      initComp(departNames) {
+    methods:{
+      initComp(departNames){
         this.departNames = departNames
       },
-      openModal() {
+      openModal(){
         this.$refs.innerDepartSelectModal.show()
       },
       handleOK(rows, idstr) {
-        console.log("当前选中部门", rows)
-        console.log("当前选中部门ID", idstr)
-        if (!rows) {
+        let value = ''
+        if (!rows && rows.length <= 0) {
           this.departNames = ''
           this.departIds = ''
         } else {
-          let temp = ''
-          for (let item of rows) {
-            temp += ',' + item.departName
-          }
-          this.departNames = temp.substring(1)
+          value = rows.map(row => row[this.customReturnField]).join(',')
+          this.departNames = rows.map(row => row['departName']).join(',')
           this.departIds = idstr
         }
-        this.$emit("change", this.departIds)
+        this.$emit("change", value)
       },
-      getDepartNames() {
+      getDepartNames(){
         return this.departNames
       },
-      handleEmpty() {
+      handleEmpty(){
         this.handleOK('')
       }
     },
@@ -111,11 +113,9 @@
     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;
   }

+ 31 - 50
src/components/jeecgbiz/JSelectMultiUser.vue

@@ -1,66 +1,47 @@
 <template>
-  <div style="width: 100%;">
-    <a-select
-      mode="multiple"
-      placeholder="Please select"
-      :value="nameList"
-      style="width: calc(100% - 178px);">
-    </a-select>
-    <span style="display: inline-block;width:170px;float: right;overflow: hidden;">
-      <a-button type="primary" @click="handleSelect" icon="search" style="width: 81px">选择</a-button>
-      <a-button type="primary" @click="selectReset" icon="reload" style="margin-left: 8px;width: 81px">清空</a-button>
-    </span>
-
-    <!-- 选择多个用户支持排序 -->
-    <j-select-multi-user-modal ref="selectModal" @selectFinished="selectOK"/>
-  </div>
+  <!-- 定义在这里的参数都是不可在外部覆盖的,防止出现问题 -->
+  <j-select-biz-component
+    :value="value"
+    :ellipsisLength="25"
+    :listUrl="url.list"
+    :columns="columns"
+    v-on="$listeners"
+    v-bind="attrs"
+  />
 </template>
 
 <script>
-  import JSelectMultiUserModal from './modal/JSelectMultiUserModal'
+  import JSelectBizComponent from './JSelectBizComponent'
 
   export default {
     name: 'JSelectMultiUser',
-    components: {JSelectMultiUserModal},
-    props: {
-      value: {
-        type: String,
-        required: false
-      }
-    },
+    components: { JSelectBizComponent },
+    props: ['value'],
     data() {
       return {
-        selectList: [],
-      }
-    },
-    computed: {
-      nameList: function () {
-        var names = [];
-        for (var a = 0; a < this.selectList.length; a++) {
-          names.push(this.selectList[a].name);
-        }
-        let nameStr = ''
-        if (names.length > 0) {
-          nameStr = names.join(",")
+        url: { list: '/sys/user/list' },
+        columns: [
+          { title: '姓名', align: 'center', width: '25%', widthRight: '70%', dataIndex: 'realname' },
+          { title: '账号', align: 'center', width: '25%', dataIndex: 'username' },
+          { title: '电话', align: 'center', width: '20%', dataIndex: 'phone' },
+          { title: '出生日期', align: 'center', width: '20%', dataIndex: 'birthday' }
+        ],
+        // 定义在这里的参数都是可以在外部传递覆盖的,可以更灵活的定制化使用的组件
+        default: {
+          name: '用户',
+          width: 1200,
+          displayKey: 'realname',
+          returnKeys: ['id', 'username'],
+          queryParamText: '账号',
         }
-        this.$emit("change", nameStr)
-        return names;
       }
     },
-    model: {
-      prop: 'value',
-      event: 'change'
-    },
-    methods: {
-      handleSelect: function () {
-        this.$refs.selectModal.add();
-      },
-      selectReset() {
-        this.selectList = [];
-      },
-      selectOK: function (data) {
-        this.selectList = data;
+    computed: {
+      attrs() {
+        return Object.assign(this.default, this.$attrs)
       }
     }
   }
 </script>
+
+<style lang="less" scoped></style>

+ 1 - 1
src/components/jeecgbiz/JSelectPosition.vue

@@ -34,4 +34,4 @@
   }
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="less" scoped></style>

+ 1 - 1
src/components/jeecgbiz/JSelectRole.vue

@@ -35,4 +35,4 @@
   }
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="less" scoped></style>

+ 31 - 21
src/components/jeecgbiz/JSelectUserByDep.vue

@@ -1,17 +1,14 @@
 <template>
   <div>
     <a-input-search
-      v-model="selectedDepUsers"
+      v-model="userNames"
       placeholder="请先选择用户"
-      disabled
+      readOnly
+      unselectable="on"
       @search="onSearchDepUser">
       <a-button slot="enterButton" :disabled="disabled">选择用户</a-button>
     </a-input-search>
-    <j-select-user-by-dep-modal
-      ref="selectModal"
-      :modal-width="modalWidth"
-      :multiple="multiple"
-      @ok="onSearchDepUserCallBack"/>
+    <j-select-user-by-dep-modal ref="selectModal" :modal-width="modalWidth" :multi="multi" @ok="selectOK" :user-ids="value" @initComp="initComp"/>
   </div>
 </template>
 
@@ -36,24 +33,24 @@
         required: false,
         default: false
       },
-      multiple:{
-          type:Boolean,
-          default(){
-              return false
-          }
-      }
+      multi: {
+        type: Boolean,
+        default: true,
+        required: false
+      },
     },
     data() {
       return {
-        selectedDepUsers: "",
+        userIds: "",
+        userNames: ""
       }
     },
     mounted() {
-      this.selectedDepUsers = this.value
+      this.userIds = this.value
     },
     watch: {
       value(val) {
-        this.selectedDepUsers = val
+        this.userIds = val
       }
     },
     model: {
@@ -61,14 +58,27 @@
       event: 'change'
     },
     methods: {
-      //通过组织机构筛选选择用户
+      initComp(userNames) {
+        this.userNames = userNames
+      },
       onSearchDepUser() {
         this.$refs.selectModal.showModal()
-        this.onSearchDepUserCallBack(this.value,"")
       },
-      onSearchDepUserCallBack(selectedDepUsers,value) {
-        this.selectedDepUsers = selectedDepUsers
-        this.$emit("change", selectedDepUsers,value)
+      selectOK(rows, idstr) {
+        console.log("当前选中用户", rows)
+        console.log("当前选中用户ID", idstr)
+        if (!rows) {
+          this.userNames = ''
+          this.userIds = ''
+        } else {
+          let temp = ''
+          for (let item of rows) {
+            temp += ',' + item.realname
+          }
+          this.userNames = temp.substring(1)
+          this.userIds = idstr
+        }
+        this.$emit("change", this.userIds)
       }
     }
   }

+ 108 - 84
src/components/jeecgbiz/modal/JSelectDepartModal.vue

@@ -8,7 +8,7 @@
     @cancel="handleCancel"
     cancelText="关闭">
     <a-spin tip="Loading..." :spinning="false">
-      <a-input-search style="margin-bottom: 1px" placeholder="请输入部门名称按回车进行搜索" @search="onSearch"/>
+      <a-input-search style="margin-bottom: 1px" placeholder="请输入部门名称按回车进行搜索" @search="onSearch" />
       <a-tree
         checkable
         :treeData="treeData"
@@ -35,149 +35,147 @@
 </template>
 
 <script>
-  import {queryDepartTreeList} from '@/api/api'
-
+  import { queryDepartTreeList } from '@/api/api'
   export default {
     name: 'JSelectDepartModal',
-    props: ['modalWidth', 'multi', 'rootOpened', 'departId'],
-    data() {
+    props:['modalWidth','multi','rootOpened','departId'],
+    data(){
       return {
-        visible: false,
-        confirmLoading: false,
-        treeData: [],
-        autoExpandParent: true,
-        expandedKeys: [],
-        dataList: [],
-        checkedKeys: [],
-        checkedRows: [],
-        searchValue: ""
+        visible:false,
+        confirmLoading:false,
+        treeData:[],
+        autoExpandParent:true,
+        expandedKeys:[],
+        dataList:[],
+        checkedKeys:[],
+        checkedRows:[],
+        searchValue:""
       }
     },
-    created() {
+    created(){
       this.loadDepart();
     },
-    watch: {
-      departId() {
+    watch:{
+      departId(){
         this.initDepartComponent()
+      },
+      visible: {
+        handler() {
+          if (this.departId) {
+            this.checkedKeys = this.departId.split(",");
+            // console.log('this.departId', this.departId)
+          } else {
+            this.checkedKeys = [];
+          }
+        }
       }
     },
-    methods: {
-      show() {
-        this.visible = true
-        this.checkedRows = []
-        this.checkedKeys = []
-        console.log("this.multi", this.multi)
+    methods:{
+      show(){
+        this.visible=true
+        this.checkedRows=[]
+        this.checkedKeys=[]
       },
-      loadDepart() {
-        queryDepartTreeList().then(res => {
-          if (res.success) {
+      loadDepart(){
+        queryDepartTreeList().then(res=>{
+          if(res.success){
             let arr = [...res.result]
             this.reWriterWithSlot(arr)
             this.treeData = arr
             this.initDepartComponent()
-            if (this.rootOpened) {
+            if(this.rootOpened){
               this.initExpandedKeys(res.result)
             }
           }
         })
       },
-      initDepartComponent() {
+      initDepartComponent(){
         let names = ''
-        if (this.departId) {
+        if(this.departId){
           let currDepartId = this.departId
-          for (let item of this.dataList) {
-            if (currDepartId.indexOf(item.key) >= 0) {
-              names += "," + item.title
+          for(let item of this.dataList){
+            if(currDepartId.indexOf(item.key)>=0){
+              names+=","+item.title
             }
           }
-          if (names) {
+          if(names){
             names = names.substring(1)
           }
         }
-        this.$emit("initComp", names)
+        this.$emit("initComp",names)
       },
-      reWriterWithSlot(arr) {
-        for (let item of arr) {
-          if (item.children && item.children.length > 0) {
+      reWriterWithSlot(arr){
+        for(let item of arr){
+          if(item.children && item.children.length>0){
             this.reWriterWithSlot(item.children)
-            let temp = Object.assign({}, item)
+            let temp = Object.assign({},item)
             temp.children = {}
             this.dataList.push(temp)
-          } else {
+          }else{
             this.dataList.push(item)
-            item.scopedSlots = {title: 'title'}
+            item.scopedSlots={ title: 'title' }
           }
         }
       },
-      initExpandedKeys(arr) {
-        if (arr && arr.length > 0) {
+      initExpandedKeys(arr){
+        if(arr && arr.length>0){
           let keys = []
-          for (let item of arr) {
-            if (item.children && item.children.length > 0) {
+          for(let item of arr){
+            if(item.children && item.children.length>0){
               keys.push(item.id)
             }
           }
-          this.expandedKeys = [...keys]
-        } else {
-          this.expandedKeys = []
+          this.expandedKeys=[...keys]
+        }else{
+          this.expandedKeys=[]
         }
       },
-      onCheck(checkedKeys, info) {
-        if (!this.multi) {
-          let arr = checkedKeys.checked.filter(item => {
-            return this.checkedKeys.indexOf(item) < 0
-          })
+      onCheck (checkedKeys,info) {
+        if(!this.multi){
+          let arr = checkedKeys.checked.filter(item => this.checkedKeys.indexOf(item) < 0)
           this.checkedKeys = [...arr]
-          this.checkedRows = [info.node.dataRef]
-        } else {
+          this.checkedRows = (this.checkedKeys.length === 0) ? [] : [info.node.dataRef]
+        }else{
           this.checkedKeys = checkedKeys.checked
-          this.checkedRows.push(info.node.dataRef)
+          this.checkedRows = this.getCheckedRows(this.checkedKeys)
         }
-        //this.$emit("input",this.checkedKeys.join(","))
-        //console.log(this.checkedKeys.join(","))
       },
-      onSelect(selectedKeys, info) {
-        console.log(selectedKeys)
+      onSelect(selectedKeys,info) {
         let keys = []
         keys.push(selectedKeys[0])
-        if (!this.checkedKeys || this.checkedKeys.length == 0 || !this.multi) {
+        if(!this.checkedKeys || this.checkedKeys.length===0 || !this.multi){
           this.checkedKeys = [...keys]
-          this.checkedRows = [info.node.dataRef]
-        } else {
+          this.checkedRows=[info.node.dataRef]
+        }else{
           let currKey = info.node.dataRef.key
-          if (this.checkedKeys.indexOf(currKey) >= 0) {
-            this.checkedKeys = this.checkedKeys.filter(item => {
-              return item != currKey
-            })
-            this.checkedRows = this.checkedRows.filter(item => {
-              return item.key != currKey
-            })
-          } else {
-            this.checkedRows.push(info.node.dataRef)
+          if(this.checkedKeys.indexOf(currKey)>=0){
+            this.checkedKeys = this.checkedKeys.filter(item=> item !==currKey)
+          }else{
             this.checkedKeys.push(...keys)
           }
         }
+        this.checkedRows = this.getCheckedRows(this.checkedKeys)
       },
-      onExpand(expandedKeys) {
+      onExpand (expandedKeys) {
         this.expandedKeys = expandedKeys
         this.autoExpandParent = false
       },
-      handleSubmit() {
-        if (!this.checkedKeys || this.checkedKeys.length == 0) {
-          this.$emit("ok", '')
-        } else {
-          this.$emit("ok", this.checkedRows, this.checkedKeys.join(","))
+      handleSubmit(){
+        if(!this.checkedKeys || this.checkedKeys.length==0){
+          this.$emit("ok",'')
+        }else{
+          this.$emit("ok",this.checkedRows,this.checkedKeys.join(","))
         }
         this.handleClear()
       },
-      handleCancel() {
+      handleCancel(){
         this.handleClear()
       },
-      handleClear() {
-        this.visible = false
-        this.checkedKeys = []
+      handleClear(){
+        this.visible=false
+        this.checkedKeys=[]
       },
-      getParentKey(currKey, treeData) {
+      getParentKey(currKey,treeData){
         let parentKey
         for (let i = 0; i < treeData.length; i++) {
           const node = treeData[i]
@@ -191,10 +189,10 @@
         }
         return parentKey
       },
-      onSearch(value) {
+      onSearch(value){
         const expandedKeys = this.dataList.map((item) => {
           if (item.title.indexOf(value) > -1) {
-            return this.getParentKey(item.key, this.treeData)
+            return this.getParentKey(item.key,this.treeData)
           }
           return null
         }).filter((item, i, self) => item && self.indexOf(item) === i)
@@ -206,6 +204,32 @@
         })
 
 
+      },
+      // 根据 checkedKeys 获取 rows
+      getCheckedRows(checkedKeys) {
+        const forChildren = (list, key) => {
+          for (let item of list) {
+            if (item.id === key) {
+              return item
+            }
+            if (item.children instanceof Array) {
+              let value = forChildren(item.children, key)
+              if (value != null) {
+                return value
+              }
+            }
+          }
+          return null
+        }
+
+        let rows = []
+        for (let key of checkedKeys) {
+          let row = forChildren(this.treeData, key)
+          if (row != null) {
+            rows.push(row)
+          }
+        }
+        return rows
       }
     }
   }

+ 257 - 252
src/components/jeecgbiz/modal/JSelectUserByDepModal.vue

@@ -15,11 +15,13 @@
           <!--组织机构-->
           <a-directory-tree
             selectable
-            :selectedKeys="selectedKeys"
+            :selectedKeys="selectedDepIds"
             :checkStrictly="true"
-            @select="this.onSelect"
-            :dropdownStyle="{ maxHeight: '200px', overflow: 'auto' }"
+            :dropdownStyle="{maxHeight:'200px',overflow:'auto'}"
             :treeData="departTree"
+            :expandAction="false"
+            :expandedKeys.sync="expandedKeys"
+            @select="onDepSelect"
           />
         </a-card>
       </a-col>
@@ -27,8 +29,8 @@
         <a-card :bordered="false">
           用户账号:
           <a-input-search
-            :style="{ width: '150px', marginBottom: '15px' }"
-            placeholder="请输入用户账号"
+            :style="{width:'150px',marginBottom:'15px'}"
+            placeholder="请输入账号"
             v-model="queryParam.username"
             @search="onSearch"
           ></a-input-search>
@@ -42,13 +44,9 @@
             :columns="columns"
             :dataSource="dataSource"
             :pagination="ipagination"
-            :rowSelection="{
-              selectedRowKeys: selectedRowKeys,
-              onChange: onSelectChange,
-              type: multiple ? 'checkbox' : 'radio'
-            }"
-            @change="handleTableChange"
-          >
+            :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange,type: getType}"
+            :loading="loading"
+            @change="handleTableChange">
           </a-table>
         </a-card>
       </a-col>
@@ -57,268 +55,275 @@
 </template>
 
 <script>
-import { filterObj } from '@/utils/util'
-import { queryDepartTreeList, getUserList, queryUserByDepId, queryUserRoleMap } from '@/api/api'
+  import {filterObj} from '@/utils/util'
+  import {queryDepartTreeList, getUserList, queryUserByDepId} from '@/api/api'
 
-export default {
-  name: 'JSelectUserByDepModal',
-  components: {},
-  props: {
-    modalWidth: {
-      type: Number,
-      default: 1250,
-      required: false
-    },
-    multiple: {
-      type: Boolean,
-      default() {
-        return false
-      }
-    }
-  },
-  data() {
-    return {
-      queryParam: {
-        username: ''
-      },
-      columns: [
-        {
-          title: '用户账号',
-          align: 'center',
-          dataIndex: 'username'
-        },
-        {
-          title: '真实姓名',
-          align: 'center',
-          dataIndex: 'realname'
+  export default {
+    name: 'JSelectUserByDepModal',
+    components: {},
+    props: ['modalWidth', 'multi', 'userIds'],
+    data() {
+      return {
+        queryParam: {
+          username: "",
         },
-        {
-          title: '角色名称',
-          align: 'center',
-          dataIndex: 'roleName'
-        },
-        {
-          title: '性别',
-          align: 'center',
-          dataIndex: 'sex',
-          customRender: function(text) {
-            if (text === 1) {
-              return '男'
-            } else if (text === 2) {
-              return '女'
-            } else {
-              return text
+        columns: [
+          {
+            title: '用户账号',
+            align: 'center',
+            dataIndex: 'username'
+          },
+          {
+            title: '用户姓名',
+            align: 'center',
+            dataIndex: 'realname'
+          },
+          {
+            title: '性别',
+            align: 'center',
+            dataIndex: 'sex',
+            customRender: function (text) {
+              if (text === 1) {
+                return '男'
+              } else if (text === 2) {
+                return '女'
+              } else {
+                return text
+              }
             }
+          },
+          {
+            title: '手机',
+            align: 'center',
+            dataIndex: 'phone'
+          },
+          {
+            title: '部门',
+            align: 'center',
+            dataIndex: 'orgCode'
           }
+        ],
+        scrollTrigger: {},
+        dataSource: [],
+        selectedRowKeys: [],
+        selectUserRows: [],
+        selectUserIds: [],
+        title: '根据部门选择用户',
+        ipagination: {
+          current: 1,
+          pageSize: 10,
+          pageSizeOptions: ['10', '20', '30'],
+          showTotal: (total, range) => {
+            return range[0] + '-' + range[1] + ' 共' + total + '条'
+          },
+          showQuickJumper: true,
+          showSizeChanger: true,
+          total: 0
         },
-        {
-          title: '手机号码',
-          align: 'center',
-          dataIndex: 'phone'
-        },
-        {
-          title: '邮箱',
-          align: 'center',
-          dataIndex: 'email'
-        }
-      ],
-      scrollTrigger: {},
-      dataSource: [],
-      selectedKeys: [],
-      userNameArr: [],
-      departName: '',
-      userRolesMap: {},
-      title: '根据部门选择用户',
-      ipagination: {
-        current: 1,
-        pageSize: 10,
-        pageSizeOptions: ['10', '20', '30'],
-        showTotal: (total, range) => {
-          return range[0] + '-' + range[1] + ' 共' + total + '条'
+        isorter: {
+          column: 'createTime',
+          order: 'desc'
         },
-        showQuickJumper: true,
-        showSizeChanger: true,
-        total: 0
-      },
-      isorter: {
-        column: 'createTime',
-        order: 'desc'
-      },
-      selectedRowKeys: [],
-      selectedRows: [],
-      departTree: [],
-      visible: false,
-      form: this.$form.createForm(this)
-    }
-  },
-  created() {
-    // 该方法触发屏幕自适应
-    this.resetScreenSize()
-    this.queryUserRoleMap()
-  },
-  methods: {
-    loadData(arg) {
-      if (arg === 1) {
-        this.ipagination.current = 1
+        selectedDepIds: [],
+        departTree: [],
+        visible: false,
+        form: this.$form.createForm(this),
+        loading: false,
+        expandedKeys: [],
       }
-      let params = this.getQueryParams() //查询条件
-      getUserList(params).then(res => {
-        if (res.success) {
-          this.dataSource = res.result.records
-          this.assignRoleName(this.dataSource)
-          this.ipagination.total = res.result.total
-        }
-      })
-    },
-    queryUserRoleMap() {
-      queryUserRoleMap().then(res => {
-        if (res.success) {
-          this.userRolesMap = res.result
-          this.loadData()
-        }
-      })
     },
-    // 触发屏幕自适应
-    resetScreenSize() {
-      let screenWidth = document.body.clientWidth
-      if (screenWidth < 500) {
-        this.scrollTrigger = { x: 800 }
-      } else {
-        this.scrollTrigger = {}
+    computed: {
+      // 计算属性的 getter
+      getType: function () {
+        return this.multi == true ? 'checkbox' : 'radio';
       }
     },
-    showModal() {
-      this.visible = true
-      this.assignRoleName(this.dataSource)
-      this.queryDepartTree()
-      this.form.resetFields()
-    },
-    getQueryParams() {
-      let param = Object.assign({}, this.queryParam, this.isorter)
-      param.field = this.getQueryField()
-      param.pageNo = this.ipagination.current
-      param.pageSize = this.ipagination.pageSize
-      return filterObj(param)
-    },
-    getQueryField() {
-      let str = 'id,'
-      for (let a = 0; a < this.columns.length; a++) {
-        str += ',' + this.columns[a].dataIndex
-      }
-      return str
-    },
-    searchReset(num) {
-      let that = this
-      if (num !== 0) {
-        that.queryParam = {}
-        that.loadData(1)
-      }
-      that.selectedRowKeys = []
-      that.userNameArr = []
-      that.selectedKeys = []
-    },
-    close() {
-      this.searchReset(0)
-      this.visible = false
+    watch: {
+      userIds: {
+        immediate: true,
+        handler() {
+          this.initUserNames()
+        }
+      },
     },
-    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.ipagination = pagination
+    created() {
+      // 该方法触发屏幕自适应
+      this.resetScreenSize();
       this.loadData()
     },
-    handleSubmit() {
-      let that = this
-      for (let i = 0, len = this.selectedRowKeys.length; i < len; i++) {
-        this.getUserNames(this.selectedRowKeys[i])
-      }
-      that.$emit('ok', that.userNameArr.join(','), that.selectedRowKeys.join(','))
-      that.close()
-    },
-    // 遍历匹配,获取用户真实姓名
-    getUserNames(rowId) {
-      let dataSource = this.dataSource
-      for (let i = 0, len = dataSource.length; i < len; i++) {
-        if (rowId === dataSource[i].id) {
-          this.userNameArr.push(dataSource[i].realname)
+    methods: {
+      initUserNames() {
+        if (this.userIds) {
+          // 这里最后加一个 , 的原因是因为无论如何都要使用 in 查询,防止后台进行了模糊匹配,导致查询结果不准确
+          let values = this.userIds.split(',') + ','
+          getUserList({
+            username: values,
+            pageNo: 1,
+            pageSize: values.length
+          }).then((res) => {
+            if (res.success) {
+              let selectedRowKeys = []
+              let realNames = []
+              res.result.records.forEach(user => {
+                realNames.push(user['realname'])
+                selectedRowKeys.push(user['id'])
+              })
+              this.selectedRowKeys = selectedRowKeys
+              this.$emit('initComp', realNames.join(','))
+            }
+          })
+        } else {
+          // JSelectUserByDep组件bug issues/I16634
+          this.$emit('initComp', '')
         }
-      }
-    },
-    // 点击树节点,筛选出对应的用户
-    onSelect(selectedKeys) {
-      if (selectedKeys[0] != null) {
-        this.queryUserByDepId(selectedKeys) // 调用方法根据选选择的id查询用户信息
-        if (this.selectedKeys[0] !== selectedKeys[0]) {
-          this.selectedKeys = [selectedKeys[0]]
+      },
+      async loadData(arg) {
+        if (arg === 1) {
+          this.ipagination.current = 1;
         }
-      }
-    },
-    onSelectChange(selectedRowKeys, selectionRows) {
-      this.selectedRowKeys = selectedRowKeys
-      this.selectionRows = selectionRows
-    },
-    onSearch() {
-      this.loadData(1)
-    },
-    // 根据选择的id来查询用户信息
-    queryUserByDepId(selectedKeys) {
-      queryUserByDepId({ id: selectedKeys.toString() }).then(res => {
-        if (res.success) {
-          this.dataSource = res.result
-          this.ipagination.total = res.result.length
-          this.assignRoleName(this.dataSource)
+        if (this.selectedDepIds && this.selectedDepIds.length > 0) {
+          await this.initQueryUserByDepId(this.selectedDepIds)
+        } else {
+          this.loading = true
+          let params = this.getQueryParams()//查询条件
+          await getUserList(params).then((res) => {
+            if (res.success) {
+              this.dataSource = res.result.records
+              this.ipagination.total = res.result.total
+            }
+          }).finally(() => {
+            this.loading = false
+          })
         }
-      })
-    },
-    // 传入用户id,找到匹配的角色名称
-    queryUserRole(userId) {
-      let map = this.userRolesMap
-      let roleName = []
-      for (var key in map) {
-        if (userId === key) {
-          roleName.push(map[key])
+      },
+      // 触发屏幕自适应
+      resetScreenSize() {
+        let screenWidth = document.body.clientWidth;
+        if (screenWidth < 500) {
+          this.scrollTrigger = {x: 800};
+        } else {
+          this.scrollTrigger = {};
         }
-      }
-      return roleName.join(',')
-    },
-    queryDepartTree() {
-      queryDepartTreeList().then(res => {
-        if (res.success) {
-          this.departTree = res.result
+      },
+      showModal() {
+        this.visible = true;
+        this.queryDepartTree();
+        this.initUserNames()
+        this.loadData();
+        this.form.resetFields();
+      },
+      getQueryParams() {
+        let param = Object.assign({}, this.queryParam, this.isorter);
+        param.field = this.getQueryField();
+        param.pageNo = this.ipagination.current;
+        param.pageSize = this.ipagination.pageSize;
+        return filterObj(param);
+      },
+      getQueryField() {
+        let str = 'id,';
+        for (let a = 0; a < this.columns.length; a++) {
+          str += ',' + this.columns[a].dataIndex;
         }
-      })
-    },
-    // 为角色名称赋值
-    assignRoleName(data) {
-      let userId = ''
-      let role = ''
-      for (let i = 0, length = data.length; i < length; i++) {
-        userId = this.dataSource[i].id
-        role = this.queryUserRole(userId)
-        this.dataSource[i].roleName = role
+        return str;
+      },
+      searchReset(num) {
+        let that = this;
+        if (num !== 0) {
+          that.queryParam = {};
+          that.loadData(1);
+        }
+        that.selectedRowKeys = [];
+        that.selectUserIds = [];
+        that.selectedDepIds = [];
+      },
+      close() {
+        this.searchReset(0);
+        this.visible = false;
+      },
+      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.ipagination = pagination;
+        this.loadData();
+      },
+      handleSubmit() {
+        let that = this;
+        this.getSelectUserRows();
+        that.$emit('ok', that.selectUserRows, that.selectUserIds);
+        that.searchReset(0)
+        that.close();
+      },
+      //获取选择用户信息
+      getSelectUserRows(rowId) {
+        let dataSource = this.dataSource;
+        let userIds = "";
+        this.selectUserRows = [];
+        for (let i = 0, len = dataSource.length; i < len; i++) {
+          if (this.selectedRowKeys.includes(dataSource[i].id)) {
+            this.selectUserRows.push(dataSource[i]);
+            userIds = userIds + "," + dataSource[i].username
+          }
+        }
+        this.selectUserIds = userIds.substring(1);
+      },
+      // 点击树节点,筛选出对应的用户
+      onDepSelect(selectedDepIds) {
+        if (selectedDepIds[0] != null) {
+          this.initQueryUserByDepId(selectedDepIds); // 调用方法根据选选择的id查询用户信息
+          if (this.selectedDepIds[0] !== selectedDepIds[0]) {
+            this.selectedDepIds = [selectedDepIds[0]];
+          }
+        }
+      },
+      onSelectChange(selectedRowKeys, selectionRows) {
+        this.selectedRowKeys = selectedRowKeys;
+        this.selectionRows = selectionRows;
+      },
+      onSearch() {
+        this.loadData(1);
+      },
+      // 根据选择的id来查询用户信息
+      initQueryUserByDepId(selectedDepIds) {
+        this.loading = true
+        return queryUserByDepId({id: selectedDepIds.toString()}).then((res) => {
+          if (res.success) {
+            this.dataSource = res.result;
+            this.ipagination.total = res.result.length;
+          }
+        }).finally(() => {
+          this.loading = false
+        })
+      },
+      queryDepartTree() {
+        queryDepartTreeList().then((res) => {
+          if (res.success) {
+            this.departTree = res.result;
+            // 默认展开父节点
+            this.expandedKeys = this.departTree.map(item => item.id)
+          }
+        })
+      },
+      modalFormOk() {
+        this.loadData();
       }
-    },
-    modalFormOk() {
-      this.loadData()
     }
   }
-}
 </script>
 
 <style scoped>
-.ant-table-tbody .ant-table-row td {
-  padding-top: 10px;
-  padding-bottom: 10px;
-}
+  .ant-table-tbody .ant-table-row td {
+    padding-top: 10px;
+    padding-bottom: 10px;
+  }
 
-#components-layout-demo-custom-trigger .trigger {
-  font-size: 18px;
-  line-height: 64px;
-  padding: 0 24px;
-  cursor: pointer;
-  transition: color 0.3s;
-}
-</style>
+  #components-layout-demo-custom-trigger .trigger {
+    font-size: 18px;
+    line-height: 64px;
+    padding: 0 24px;
+    cursor: pointer;
+    transition: color .3s;
+  }
+</style>

+ 2 - 2
src/components/jeecgbiz/modal/SelectUserListModal.vue

@@ -16,7 +16,7 @@
       :dataSource="dataSource"
       :pagination="ipagination"
       :loading="loading"
-      :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange,type:'radio'}"></a-table>
+      :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"></a-table>
   </a-modal>
 </template>
 
@@ -46,7 +46,7 @@
             width: 200
           },
           {
-            title: '用户真实姓名',
+            title: '用户姓名',
             align: "center",
             dataIndex: 'realname',
           },

+ 23 - 8
src/components/layouts/IframePageView.vue

@@ -1,25 +1,28 @@
 <template>
 
-  <iframe :id="id" :src="url" frameborder="0" width="100%" height="800px" scrolling="auto"></iframe>
+    <iframe  :id="id" :src="url" frameborder="0" width="100%" height="800px" scrolling="auto"></iframe>
 
 </template>
 
 <script>
+  import Vue from 'vue'
+  import { ACCESS_TOKEN } from "@/store/mutation-types"
   import PageLayout from '../page/PageLayout'
   import RouteView from './RouteView'
 
   export default {
     name: "IframePageContent",
-    data() {
+    inject:['closeCurrent'],
+    data () {
       return {
         url: "",
-        id: ""
+        id:""
       }
     },
-    created() {
+    created () {
       this.goUrl()
     },
-    updated() {
+    updated () {
       this.goUrl()
     },
     watch: {
@@ -28,15 +31,27 @@
       }
     },
     methods: {
-      goUrl() {
+      goUrl () {
         let url = this.$route.meta.url
         let id = this.$route.path
         this.id = id
         //url = "http://www.baidu.com"
-        console.log("------url------" + url)
+        console.log("------url------"+url)
         if (url !== null && url !== undefined) {
           this.url = url;
-          //window.open(this.url);
+          /*update_begin author:wuxianquan date:20190908 for:判断打开方式,新窗口打开时this.$route.meta.internalOrExternal==true */
+          if(this.$route.meta.internalOrExternal != undefined && this.$route.meta.internalOrExternal==true){
+            this.closeCurrent();
+            //外部url加入token
+            let tokenStr = "${token}";
+            if(url.indexOf(tokenStr)!=-1){
+              let token = Vue.ls.get(ACCESS_TOKEN);
+               this.url = url.replace(tokenStr,token);
+            }
+            window.open(this.url);
+          }
+          /*update_end author:wuxianquan date:20190908 for:判断打开方式,新窗口打开时this.$route.meta.internalOrExternal==true */
+
         }
       }
     }

+ 137 - 87
src/components/layouts/TabLayout.vue

@@ -1,6 +1,8 @@
 <template>
-  <global-layout @dynamicRouterShow="dynamicRouterShow"  @removeTab="remove">
-    <contextmenu :itemList="menuItemList" :visible.sync="menuVisible" @select="onMenuSelect"/>
+  <global-layout @dynamicRouterShow="dynamicRouterShow">
+    <!-- update-begin- author:sunjianlei --- date:20191009 --- for: 提升右键菜单的层级 -->
+    <contextmenu :itemList="menuItemList" :visible.sync="menuVisible" style="z-index: 9999;" @select="onMenuSelect"/>
+    <!-- update-end- author:sunjianlei --- date:20191009 --- for: 提升右键菜单的层级 -->
     <a-tabs
       @contextmenu.native="e => onContextmenu(e)"
       v-if="multipage"
@@ -10,6 +12,7 @@
       :hide-add="true"
       type="editable-card"
       @change="changePage"
+      @tabClick="tabCallBack"
       @edit="editPage">
       <a-tab-pane :id="page.fullPath" :key="page.fullPath" v-for="page in pageList">
         <span slot="tab" :pagekey="page.fullPath">{{ page.meta.title }}</span>
@@ -17,10 +20,12 @@
     </a-tabs>
     <div style="margin: 12px 12px 0;">
       <transition name="page-toggle">
-        <keep-alive v-if="multipage"  >
-          <router-view @removeTab="remove"/>
+        <keep-alive v-if="multipage">
+          <router-view v-if="reloadFlag"/>
         </keep-alive>
-        <router-view v-else  @removeTab="remove"/>
+        <template v-else>
+          <router-view v-if="reloadFlag"/>
+        </template>
       </transition>
     </div>
   </global-layout>
@@ -29,7 +34,8 @@
 <script>
   import GlobalLayout from '@/components/page/GlobalLayout'
   import Contextmenu from '@/components/menu/Contextmenu'
-  import {mixin, mixinDevice} from '@/utils/mixin.js'
+  import { mixin, mixinDevice } from '@/utils/mixin.js'
+  import { triggerWindowResizeEvent } from '@/utils/util'
 
   const indexKey = '/dashboard/analysis'
 
@@ -47,12 +53,21 @@
         activePage: '',
         menuVisible: false,
         menuItemList: [
-          {key: '1', icon: 'arrow-left', text: '关闭左侧'},
-          {key: '2', icon: 'arrow-right', text: '关闭右侧'},
-          {key: '3', icon: 'close', text: '关闭其它'}
-        ]
+          { key: '4', icon: 'reload', text: '刷 新' },
+          { key: '1', icon: 'arrow-left', text: '关闭左侧' },
+          { key: '2', icon: 'arrow-right', text: '关闭右侧' },
+          { key: '3', icon: 'close', text: '关闭其它' }
+        ],
+        reloadFlag:true
       }
     },
+    /* update_begin author:wuxianquan date:20190828 for: 关闭当前tab页,供子页面调用 ->望菜单能配置外链,直接弹出新页面而不是嵌入iframe #428 */
+    provide(){
+      return{
+        closeCurrent:this.closeCurrent
+      }
+    },
+    /* update_end author:wuxianquan date:20190828 for: 关闭当前tab页,供子页面调用->望菜单能配置外链,直接弹出新页面而不是嵌入iframe #428 */
     computed: {
       multipage() {
         //判断如果是手机模式,自动切换为单页面模式
@@ -64,81 +79,109 @@
       }
     },
     created() {
-      this.$bus.$on('remove',this.remove)
       if (this.$route.path != indexKey) {
-        this.pageList.push({
-          name: 'dashboard-analysis',
-          path: indexKey,
-          fullPath: indexKey,
-          meta: {
-            icon: 'dashboard',
-            title: '首页'
-          }
-        })
-        this.linkList.push(indexKey)
+        this.addIndexToFirst()
+      }
+      // update-begin-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
+      let storeKey = 'route:title:' + this.$route.fullPath
+      let routeTitle = this.$ls.get(storeKey)
+      if (routeTitle) {
+        this.$route.meta.title = routeTitle
       }
+      // update-end-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
       this.pageList.push(this.$route)
       this.linkList.push(this.$route.fullPath)
       this.activePage = this.$route.fullPath
-
-      if(!this.linkList.includes("/account/stepForm")){
-          localStorage.removeItem("step")
-          localStorage.removeItem("campaignId")
-          localStorage.removeItem("pans")
-          localStorage.removeItem("pansKey")
-          localStorage.removeItem("appId")
-      }
-      if(!this.linkList.includes("/account/advertisingGroup")){
-          localStorage.removeItem("advertisingGroup")
-          localStorage.removeItem("advertisingGroupKey")
-      }
+    },
+    mounted() {
     },
     watch: {
-      '$route': function (newRoute) {
+      '$route': function(newRoute) {
+        //console.log("新的路由",newRoute)
         this.activePage = newRoute.fullPath
         if (!this.multipage) {
           this.linkList = [newRoute.fullPath]
-          this.pageList = [Object.assign({}, newRoute)]
-        } else if (this.linkList.indexOf(newRoute.fullPath) < 0) {
+          this.pageList = [Object.assign({},newRoute)]
+        // update-begin-author:taoyan date:20200211 for: TASK #3368 【路由缓存】首页的缓存设置有问题,需要根据后台的路由配置来实现是否缓存
+        } else if(indexKey==newRoute.fullPath) {
+          //首页时 判断是否缓存 没有缓存 刷新之
+          if (newRoute.meta.keepAlive === false) {
+            this.routeReload()
+          }
+        // update-end-author:taoyan date:20200211 for: TASK #3368 【路由缓存】首页的缓存设置有问题,需要根据后台的路由配置来实现是否缓存
+        }else if (this.linkList.indexOf(newRoute.fullPath) < 0) {
           this.linkList.push(newRoute.fullPath)
-          this.pageList.push(Object.assign({}, newRoute))
+          this.pageList.push(Object.assign({},newRoute))
+          // update-begin-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍
+          if (newRoute.meta.keepAlive) {
+            this.routeReload()
+          }
+          // update-end-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍
         } else if (this.linkList.indexOf(newRoute.fullPath) >= 0) {
           let oldIndex = this.linkList.indexOf(newRoute.fullPath)
           let oldPositionRoute = this.pageList[oldIndex]
-          this.pageList.splice(oldIndex, 1, Object.assign({}, newRoute, {meta: oldPositionRoute.meta}))
-        }
-        if(!this.linkList.includes("/account/stepForm")){
-          localStorage.removeItem("step")
-          localStorage.removeItem("campaignId")
-          localStorage.removeItem("pans")
-          localStorage.removeItem("pansKey")
-          localStorage.removeItem("appId")
-        }
-        if(!this.linkList.includes("/account/advertisingGroup")){
-          localStorage.removeItem("advertisingGroup")
-          localStorage.removeItem("advertisingGroupKey")
-        }
-        if(!this.linkList.includes("/account/originality")){
-          localStorage.removeItem("originality")
-          localStorage.removeItem("originalityKey")
+          this.pageList.splice(oldIndex, 1, Object.assign({},newRoute,{meta:oldPositionRoute.meta}))
         }
       },
-      'activePage': function (key) {
+      'activePage': function(key) {
         let index = this.linkList.lastIndexOf(key)
         let waitRouter = this.pageList[index]
-        this.$router.push(Object.assign({}, waitRouter));
+        this.$router.push(Object.assign({},waitRouter));
+        this.changeTitle(waitRouter.meta.title)
+      },
+      'multipage': function(newVal) {
+        if(this.reloadFlag){
+          if (!newVal) {
+            this.linkList = [this.$route.fullPath]
+            this.pageList = [this.$route]
+          }
+        }
       },
-      'multipage': function (newVal) {
-        if (!newVal) {
-          this.linkList = [this.$route.fullPath]
-          this.pageList = [this.$route]
+      // update-begin-author:sunjianlei date:20191223 for: 修复从单页模式切换回多页模式后首页不居第一位的 BUG
+      device() {
+        if (this.multipage && this.linkList.indexOf(indexKey) === -1) {
+          this.addIndexToFirst()
         }
-      }
+      },
+      // update-end-author:sunjianlei date:20191223 for: 修复从单页模式切换回多页模式后首页不居第一位的 BUG
     },
     methods: {
+      // update-begin-author:sunjianlei date:20191223 for: 修复从单页模式切换回多页模式后首页不居第一位的 BUG
+      // 将首页添加到第一位
+      addIndexToFirst() {
+        this.pageList.splice(0, 0, {
+          name: 'dashboard-analysis',
+          path: indexKey,
+          fullPath: indexKey,
+          meta: {
+            icon: 'dashboard',
+            title: '首页'
+          }
+        })
+        this.linkList.splice(0, 0, indexKey)
+      },
+      // update-end-author:sunjianlei date:20191223 for: 修复从单页模式切换回多页模式后首页不居第一位的 BUG
+
+      // update-begin-author:sunjianlei date:20200120 for: 动态更改页面标题
+      changeTitle(title) {
+        let projectTitle = "Jeecg-Boot 企业级快速开发平台"
+        // 首页特殊处理
+        if (this.$route.path === indexKey) {
+          document.title = projectTitle
+        } else {
+          document.title = title + ' · ' + projectTitle
+        }
+      },
+      // update-end-author:sunjianlei date:20200120 for: 动态更改页面标题
+
       changePage(key) {
         this.activePage = key
       },
+      tabCallBack() {
+        this.$nextTick(() => {
+          triggerWindowResizeEvent()
+        })
+      },
       editPage(key, action) {
         this[action](key)
       },
@@ -151,22 +194,7 @@
           this.$message.warning('这是最后一页,不能再关闭了啦')
           return
         }
-        if(key == "/account/advertisingGroup"){
-            localStorage.removeItem("advertisingGroup")
-            localStorage.removeItem("advertisingGroupKey")
-        }
-        if(key == "/account/stepForm"){
-            localStorage.removeItem("step")
-            localStorage.removeItem("pans")
-            localStorage.removeItem("pansKey")
-        }
-        if(key == "/account/originality"){
-            localStorage.removeItem("originality")
-            localStorage.removeItem("originalityKey")
-        }
-        if(key == "/modules/material/materialList"){
-            localStorage.removeItem("key")
-        }
+        console.log("this.pageList ",this.pageList );
         this.pageList = this.pageList.filter(item => item.fullPath !== key)
         let index = this.linkList.indexOf(key)
         this.linkList = this.linkList.filter(item => item !== key)
@@ -201,13 +229,21 @@
           case '3':
             this.closeOthers(pageKey)
             break
+          case '4':
+            this.routeReload()
+            break
           default:
             break
         }
       },
+      /* update_begin author:wuxianquan date:20190828 for: 关闭当前tab页,供子页面调用->望菜单能配置外链,直接弹出新页面而不是嵌入iframe #428 */
+      closeCurrent(){
+        this.remove(this.activePage);
+      },
+      /* update_end author:wuxianquan date:20190828 for: 关闭当前tab页,供子页面调用->望菜单能配置外链,直接弹出新页面而不是嵌入iframe #428 */
       closeOthers(pageKey) {
         let index = this.linkList.indexOf(pageKey)
-        if (pageKey == indexKey) {
+        if (pageKey == indexKey || pageKey.indexOf('?ticke=')>=0) {
           this.linkList = this.linkList.slice(index, index + 1)
           this.pageList = this.pageList.slice(index, index + 1)
           this.activePage = this.linkList[0]
@@ -215,7 +251,7 @@
           let indexContent = this.pageList.slice(0, 1)[0]
           this.linkList = this.linkList.slice(index, index + 1)
           this.pageList = this.pageList.slice(index, index + 1)
-          this.linkList.unshift(indexKey)
+          this.linkList.unshift(indexContent.fullPath)
           this.pageList.unshift(indexContent)
           this.activePage = this.linkList[1]
         }
@@ -229,7 +265,7 @@
         let index = this.linkList.indexOf(pageKey)
         this.linkList = this.linkList.slice(index)
         this.pageList = this.pageList.slice(index)
-        this.linkList.unshift(indexKey)
+        this.linkList.unshift(indexContent.fullPath)
         this.pageList.unshift(indexContent)
         if (this.linkList.indexOf(this.activePage) < 0) {
           this.activePage = this.linkList[0]
@@ -244,20 +280,35 @@
         }
       },
       //update-begin-author:taoyan date:20190430 for:动态路由title显示配置的菜单title而不是其对应路由的title
-      dynamicRouterShow(key, title) {
+      dynamicRouterShow(key,title){
         let keyIndex = this.linkList.indexOf(key)
-        if (keyIndex >= 0) {
+        if(keyIndex>=0){
           let currRouter = this.pageList[keyIndex]
-          let meta = Object.assign({}, currRouter.meta, {title: title})
-          this.pageList.splice(keyIndex, 1, Object.assign({}, currRouter, {meta: meta}))
+          let meta = Object.assign({},currRouter.meta,{title:title})
+          this.pageList.splice(keyIndex, 1, Object.assign({},currRouter,{meta:meta}))
+          if (key === this.activePage) {
+            this.changeTitle(title)
+          }
         }
-      }
+      },
       //update-end-author:taoyan date:20190430 for:动态路由title显示配置的菜单title而不是其对应路由的title
+
+      //update-begin-author:taoyan date:20191008 for:路由刷新
+      routeReload(){
+        this.reloadFlag = false
+        let ToggleMultipage = "ToggleMultipage"
+        this.$store.dispatch(ToggleMultipage,false)
+        this.$nextTick(()=>{
+          this.$store.dispatch(ToggleMultipage,true)
+          this.reloadFlag = true
+        })
+      }
+      //update-end-author:taoyan date:20191008 for:路由刷新
     }
   }
 </script>
 
-<style lang="scss">
+<style lang="less">
 
   /*
  * The following styles are auto-applied to elements with
@@ -334,9 +385,8 @@
       border: none !important;
       border-bottom: 1px solid transparent !important;
     }
-
     .ant-tabs-tab-active {
-      border-color: #1890ff !important;
+      border-color: @primary-color!important;
     }
   }
 

+ 45 - 45
src/components/menu/Contextmenu.vue

@@ -1,67 +1,67 @@
 <template>
   <a-menu :style="style" class="contextmenu" v-show="visible" @click="handleClick" :selectedKeys="selectedKeys">
     <a-menu-item :key="item.key" v-for="item in itemList">
-      <a-icon role="menuitemicon" v-if="item.icon" :type="item.icon"/>{{ item.text }}
+      <a-icon role="menuitemicon" v-if="item.icon" :type="item.icon" />{{ item.text }}
     </a-menu-item>
   </a-menu>
 </template>
 
 <script>
-  export default {
-    name: 'Contextmenu',
-    props: {
-      visible: {
-        type: Boolean,
-        required: false,
-        default: false
-      },
-      itemList: {
-        type: Array,
-        required: true,
-        default: () => []
-      }
+export default {
+  name: 'Contextmenu',
+  props: {
+    visible: {
+      type: Boolean,
+      required: false,
+      default: false
     },
-    data() {
+    itemList: {
+      type: Array,
+      required: true,
+      default: () => []
+    }
+  },
+  data () {
+    return {
+      left: 0,
+      top: 0,
+      target: null,
+      selectedKeys: []
+    }
+  },
+  computed: {
+    style () {
       return {
-        left: 0,
-        top: 0,
-        target: null,
-        selectedKeys: []
+        left: this.left + 'px',
+        top: this.top + 'px'
       }
-    },
-    computed: {
-      style() {
-        return {
-          left: this.left + 'px',
-          top: this.top + 'px'
-        }
+    }
+  },
+  created () {
+    window.addEventListener('mousedown', e => this.closeMenu(e))
+    window.addEventListener('contextmenu', e => this.setPosition(e))
+  },
+  methods: {
+    closeMenu (e) {
+      if (this.visible === true && ['menuitemicon', 'menuitem'].indexOf(e.target.getAttribute('role')) < 0) {
+        this.$emit('update:visible', false)
       }
     },
-    created() {
-      window.addEventListener('mousedown', e => this.closeMenu(e))
-      window.addEventListener('contextmenu', e => this.setPosition(e))
+    setPosition (e) {
+      this.left = e.clientX
+      this.top = e.clientY
+      this.target = e.target
     },
-    methods: {
-      closeMenu(e) {
-        if (['menuitemicon', 'menuitem'].indexOf(e.target.getAttribute('role')) < 0) {
-          this.$emit('update:visible', false)
-        }
-      },
-      setPosition(e) {
-        this.left = e.clientX
-        this.top = e.clientY
-        this.target = e.target
-      },
-      handleClick({key}) {
-        this.$emit('select', key, this.target)
-        this.$emit('update:visible', false)
-      }
+    handleClick ({key}) {
+      this.$emit('select', key, this.target)
+      this.$emit('update:visible', false)
     }
   }
+}
 </script>
 
 <style lang="less" scoped>
-  .contextmenu {
+  .contextmenu{
     position: fixed;
     z-index: 1;
     border: 1px solid #9e9e9e;

+ 31 - 14
src/components/menu/SideMenu.vue

@@ -5,7 +5,7 @@
     :collapsible="collapsible"
     v-model="collapsed"
     :trigger="null">
-    <logo/>
+    <logo />
     <s-menu
       :collapsed="collapsed"
       :menu="menus"
@@ -22,11 +22,11 @@
   import ALayoutSider from "ant-design-vue/es/layout/Sider"
   import Logo from '../tools/Logo'
   import SMenu from './index'
-  import {mixin, mixinDevice} from '@/utils/mixin.js'
+  import { mixin, mixinDevice } from '@/utils/mixin.js'
 
   export default {
     name: "SideMenu",
-    components: {ALayoutSider, Logo, SMenu},
+    components: { ALayoutSider, Logo, SMenu },
     mixins: [mixin, mixinDevice],
     props: {
       mode: {
@@ -54,9 +54,9 @@
         required: true
       }
     },
-    computed: {
+    computed:{
       smenuStyle() {
-        let style = {'padding': '0'}
+        let style = { 'padding': '0' }
         if (this.fixSiderbar) {
           style['height'] = 'calc(100% - 59px)'
           style['overflow'] = 'auto'
@@ -66,24 +66,24 @@
       }
     },
     methods: {
-      onSelect(obj) {
+      onSelect (obj) {
         this.$emit('menuSelect', obj)
       }
     }
   }
 </script>
-<style lang="scss" scoped>
+<style lang="less" scoped>
 
   /* update_begin author:sunjianlei date:20190509 for: 修改侧边导航栏滚动条的样式 */
   .sider {
-    $scrollBarSize: 10px;
+    @scrollBarSize: 10px;
 
     ul.ant-menu {
 
       /* 定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
       &::-webkit-scrollbar {
-        width: $scrollBarSize;
-        height: $scrollBarSize;
+        width: @scrollBarSize;
+        height: @scrollBarSize;
         background-color: transparent;
         display: none;
       }
@@ -109,7 +109,7 @@
 
       /* 定义滑块 */
       &::-webkit-scrollbar-thumb {
-        border-radius: $scrollBarSize;
+        border-radius: @scrollBarSize;
         background-color: #eee;
         box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
 
@@ -145,16 +145,33 @@
 </style>
 
 <!-- update_begin author:sunjianlei date:20190530 for: 选中首页的时候不显示背景颜色 -->
-<style lang="scss">
+<style lang="less">
   .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: @primary-color;
+        }
+      }
+    }
 
+    &.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);
+        }
       }
     }
   }
 </style>
-<!-- update_end author:sunjianlei date:20190530 for: 选中首页的时候不显示背景颜色 -->
+<!-- update_end author:sunjianlei date:20190530 for: 选中首页的时候不显示背景颜色 -->

+ 35 - 80
src/components/menu/index.js

@@ -1,7 +1,7 @@
 import Menu from 'ant-design-vue/es/menu'
 import Icon from 'ant-design-vue/es/icon'
 
-const {Item, SubMenu} = Menu
+const { Item, SubMenu } = Menu
 
 export default {
   name: 'SMenu',
@@ -26,7 +26,7 @@ export default {
       default: false
     }
   },
-  data() {
+  data () {
     return {
       openKeys: [],
       selectedKeys: [],
@@ -40,11 +40,11 @@ export default {
       return keys
     }
   },
-  mounted() {
+  mounted () {
     this.updateMenu()
   },
   watch: {
-    collapsed(val) {
+    collapsed (val) {
       if (val) {
         this.cachedOpenKeys = this.openKeys.concat()
         this.openKeys = []
@@ -58,7 +58,7 @@ export default {
   },
   methods: {
     // select menu item
-    onOpenChange(openKeys) {
+    onOpenChange (openKeys) {
 
       // 在水平模式下时执行,并且不再执行后续
       if (this.mode === 'horizontal') {
@@ -73,9 +73,9 @@ export default {
         this.openKeys = latestOpenKey ? [latestOpenKey] : []
       }
     },
-    updateMenu() {
+    updateMenu () {
       const routes = this.$route.matched.concat()
-      const {hidden} = this.$route.meta
+      const { hidden } = this.$route.meta
       if (routes.length >= 3 && hidden) {
         routes.pop()
         this.selectedKeys = [routes[routes.length - 1].path]
@@ -89,109 +89,76 @@ export default {
         })
       }
       //update-begin-author:taoyan date:20190510 for:online表单菜单点击展开的一级目录不对
-      if (!this.selectedKeys || this.selectedKeys[0].indexOf(":") < 0) {
+      if(!this.selectedKeys || this.selectedKeys[0].indexOf(":")<0){
         this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
       }
       //update-end-author:taoyan date:20190510 for:online表单菜单点击展开的一级目录不对
     },
 
     // render
-    renderItem(menu) {
+    renderItem (menu) {
       if (!menu.hidden) {
         return menu.children && !menu.alwaysShow ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
       }
       return null
     },
-    renderMenuItem(menu) {
+    renderMenuItem (menu) {
       const target = menu.meta.target || null
       const tag = target && 'a' || 'router-link'
-      let props = {to: {name: menu.name}}
-      if (menu.route && menu.route === '0') {
-        props = {to: {path: menu.path}}
+      let props = { to: { name: menu.name } }
+      if(menu.route && menu.route === '0'){
+        props = { to: { path: menu.path } }
       }
 
-      const attrs = {href: menu.path, target: menu.meta.target}
+      const attrs = { href: menu.path, target: menu.meta.target }
 
       if (menu.children && menu.alwaysShow) {
         // 把有子菜单的 并且 父菜单是要隐藏子菜单的
         // 都给子菜单增加一个 hidden 属性
         // 用来给刷新页面时, selectedKeys 做控制用
         menu.children.forEach(item => {
-          item.meta = Object.assign(item.meta, {hidden: true})
+          item.meta = Object.assign(item.meta, { hidden: true })
         })
       }
 
       return (
-        < Item
-      {...
-        {
-          key: menu.path
-        }
-      }
-    >
-    <
-      tag
-      {...
-        {
-          props, attrs
-        }
-      }
-    >
-      {
-        this.renderIcon(menu.meta.icon)
-      }
-    <
-      span > {menu.meta.title} < /span>
-      < /tag>
-      < /Item>
+        <Item {...{ key: menu.path }}>
+    <tag {...{ props, attrs }}>
+      {this.renderIcon(menu.meta.icon)}
+    <span>{menu.meta.title}</span>
+      </tag>
+      </Item>
     )
     },
-    renderSubMenu(menu) {
+    renderSubMenu (menu) {
       const itemArr = []
       if (!menu.alwaysShow) {
         menu.children.forEach(item => itemArr.push(this.renderItem(item)))
       }
       return (
-        < SubMenu
-      {...
-        {
-          key: menu.path
-        }
-      }
-    >
-    <
-      span
-      slot = "title" >
+        <SubMenu {...{ key: menu.path }}>
+    <span slot="title">
         {this.renderIcon(menu.meta.icon)}
-        < span > {menu.meta.title} < /span>
-        < /span>
-      {
-        itemArr
-      }
-    <
-      /SubMenu>
+        <span>{menu.meta.title}</span>
+        </span>
+      {itemArr}
+    </SubMenu>
     )
     },
-    renderIcon(icon) {
+    renderIcon (icon) {
       if (icon === 'none' || icon === undefined) {
         return null
       }
       const props = {}
       typeof (icon) === 'object' ? props.component = icon : props.type = icon
       return (
-        < Icon
-      {...
-        {
-          props
-        }
-      }
-      />
+        <Icon {... { props } }/>
     )
     }
   },
 
-  render() {
-    const {mode, theme, menu} = this
+  render () {
+    const { mode, theme, menu } = this
     const props = {
       mode: mode,
       theme: theme,
@@ -213,21 +180,9 @@ export default {
     })
     // {...{ props, on: on }}
     return (
-      < Menu
-    vModel = {this.selectedKeys}
-    {...
-      {
-        props, on
-      :
-        on
-      }
-    }
-  >
-    {
-      menuTree
-    }
-  <
-    /Menu>
+      <Menu vModel={this.selectedKeys} {...{ props, on: on }}>
+    {menuTree}
+  </Menu>
   )
   }
 }

+ 2 - 2
src/components/page/GlobalHeader.vue

@@ -10,7 +10,7 @@
         v-if="device==='mobile'"
         class="trigger"
         :type="collapsed ? 'menu-fold' : 'menu-unfold'"
-        @click.native="toggle"></a-icon>
+        @click="toggle"></a-icon>
       <a-icon
         v-else
         class="trigger"
@@ -38,7 +38,7 @@
             v-else
             class="trigger"
             :type="collapsed ? 'menu-fold' : 'menu-unfold'"
-            @click.native="toggle"></a-icon>
+            @click="toggle"></a-icon>
         </div>
         <user-menu class="header-index-right" :theme="theme" :style="topMenuStyle.headerIndexRight"/>
       </div>

+ 35 - 18
src/components/page/GlobalLayout.vue

@@ -82,10 +82,14 @@
   import SideMenu from '@/components/menu/SideMenu'
   import GlobalHeader from '@/components/page/GlobalHeader'
   import GlobalFooter from '@/components/page/GlobalFooter'
-  import SettingDrawer from '@/components/setting/SettingDrawer'
-  import {triggerWindowResizeEvent} from '@/utils/util'
-  import {mapState, mapActions} from 'vuex'
-  import {mixin, mixinDevice} from '@/utils/mixin.js'
+  // update-start---- author:os_chengtgen -- date:20190830 --  for:issues/463 -编译主题颜色已生效,但还一直转圈,显示主题 正在编译 ------
+  // import SettingDrawer from '@/components/setting/SettingDrawer'
+  // 注释这个因为在个人设置模块已经加载了SettingDrawer页面
+  // update-end ---- author:os_chengtgen -- date:20190830 --  for:issues/463 -编译主题颜色已生效,但还一直转圈,显示主题 正在编译 ------
+
+  import { triggerWindowResizeEvent } from '@/utils/util'
+  import { mapState, mapActions } from 'vuex'
+  import { mixin, mixinDevice } from '@/utils/mixin.js'
 
   export default {
     name: 'GlobalLayout',
@@ -93,7 +97,11 @@
       SideMenu,
       GlobalHeader,
       GlobalFooter,
-      SettingDrawer
+      // update-start---- author:os_chengtgen -- date:20190830 --  for:issues/463 -编译主题颜色已生效,但还一直转圈,显示主题 正在编译 ------
+      // // SettingDrawer
+      // 注释这个因为在个人设置模块已经加载了SettingDrawer页面
+      // update-end ---- author:os_chengtgen -- date:20190830 --  for:issues/463 -编译主题颜色已生效,但还一直转圈,显示主题 正在编译 ------
+
     },
     mixins: [mixin, mixinDevice],
     data() {
@@ -119,15 +127,12 @@
     created() {
       //--update-begin----author:scott---date:20190320------for:根据后台菜单配置,判断是否路由菜单字段,动态选择是否生成路由(为了支持参数URL菜单)------
       //this.menus = this.mainRouters.find((item) => item.path === '/').children;
-      if(this.permissionMenuList){
-        this.menus = this.permissionMenuList
-      }
-      
+      this.menus = this.permissionMenuList
       // 根据后台配置菜单,重新排序加载路由信息
       console.log('----加载菜单逻辑----')
       console.log(this.mainRouters)
       console.log(this.permissionMenuList)
-      console.log('----navTheme------' + this.navTheme)
+      console.log('----navTheme------'+this.navTheme)
       //--update-end----author:scott---date:20190320------for:根据后台菜单配置,判断是否路由菜单字段,动态选择是否生成路由(为了支持参数URL菜单)------
     },
     methods: {
@@ -143,17 +148,21 @@
         }
       },
       //update-begin-author:taoyan date:20190430 for:动态路由title显示配置的菜单title而不是其对应路由的title
-      myMenuSelect(value) {
+      myMenuSelect(value){
         //此处触发动态路由被点击事件
-        this.findMenuBykey(this.menus, value.key)
-        this.$emit("dynamicRouterShow", value.key, this.activeMenu.meta.title)
+        this.findMenuBykey(this.menus,value.key)
+        this.$emit("dynamicRouterShow",value.key,this.activeMenu.meta.title)
+        // update-begin-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
+        let storeKey = 'route:title:' + this.activeMenu.path
+        this.$ls.set(storeKey, this.activeMenu.meta.title)
+        // update-end-author:sunjianlei date:20191223 for: 修复刷新后菜单Tab名字显示异常
       },
-      findMenuBykey(menus, key) {
-        for (let i of menus) {
-          if (i.path == key) {
+      findMenuBykey(menus,key){
+        for(let i of menus){
+          if(i.path==key){
             this.activeMenu = {...i}
-          } else if (i.children && i.children.length > 0) {
-            this.findMenuBykey(i.children, key)
+          }else if(i.children && i.children.length>0){
+            this.findMenuBykey(i.children,key)
           }
         }
       }
@@ -334,6 +343,10 @@
             font-size: 16px;
             padding: 4px;
           }
+
+          .anticon {
+            color: white;
+          }
         }
       }
 
@@ -346,6 +359,10 @@
             &:hover {
               background: rgba(0, 0, 0, 0.05);
             }
+
+            .anticon {
+              color: black;
+            }
           }
         }
       }

+ 34 - 38
src/components/setting/SettingDrawer.vue

@@ -55,7 +55,7 @@
 
           </div>
         </div>
-        <a-divider/>
+        <a-divider />
 
         <div :style="{ marginBottom: '24px' }">
           <h3 class="setting-drawer-index-title">导航模式</h3>
@@ -92,8 +92,7 @@
                   <template slot="title">
                     该设定仅 [顶部栏导航] 时有效
                   </template>
-                  <a-select size="small" style="width: 80px;" :defaultValue="contentWidth"
-                            @change="handleContentWidthChange">
+                  <a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange">
                     <a-select-option value="Fixed">固定</a-select-option>
                     <a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
                   </a-select>
@@ -103,45 +102,40 @@
                 </a-list-item-meta>
               </a-list-item>
               <a-list-item>
-                <a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader"/>
+                <a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
                 <a-list-item-meta>
                   <div slot="title">固定 Header</div>
                 </a-list-item-meta>
               </a-list-item>
               <a-list-item>
-                <a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader"
-                          @change="handleFixedHeaderHidden"/>
+                <a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
                 <a-list-item-meta>
-                  <div slot="title" :style="{ textDecoration: !fixedHeader ? 'line-through' : 'unset' }">下滑时隐藏 Header
-                  </div>
+                  <div slot="title" :style="{ textDecoration: !fixedHeader ? 'line-through' : 'unset' }">下滑时隐藏 Header</div>
                 </a-list-item-meta>
               </a-list-item>
-              <a-list-item>
-                <a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :checked="dataFixSiderbar"
-                          @change="handleFixSiderbar"/>
+              <a-list-item >
+                <a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :checked="dataFixSiderbar" @change="handleFixSiderbar" />
                 <a-list-item-meta>
-                  <div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">
-                    固定侧边菜单
-                  </div>
+                  <div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
                 </a-list-item-meta>
               </a-list-item>
             </a-list>
           </div>
         </div>
-        <a-divider/>
+        <a-divider />
 
         <div :style="{ marginBottom: '24px' }">
           <h3 class="setting-drawer-index-title">其他设置</h3>
           <div>
             <a-list :split="false">
               <a-list-item>
-                <a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak"/>
+                <a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
                 <a-list-item-meta>
                   <div slot="title">色弱模式</div>
                 </a-list-item-meta>
               </a-list-item>
               <a-list-item>
-                <a-switch slot="actions" size="small" :defaultChecked="multipage" @change="onMultipageWeak"/>
+                <a-switch slot="actions" size="small" :defaultChecked="multipage" @change="onMultipageWeak" />
                 <a-list-item-meta>
                   <div slot="title">多页签模式</div>
                 </a-list-item-meta>
@@ -149,7 +143,7 @@
             </a-list>
           </div>
         </div>
-        <a-divider/>
+        <a-divider />
         <div :style="{ marginBottom: '24px' }">
           <a-alert type="warning">
             <span slot="message">
@@ -159,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>
@@ -171,9 +166,9 @@
   import DetailList from '@/components/tools/DetailList'
   import SettingItem from '@/components/setting/SettingItem'
   import config from '@/defaultSettings'
-  import {updateTheme, updateColorWeak, colorList} from '@/components/tools/setting'
-  import {mixin, mixinDevice} from '@/utils/mixin.js'
-  import {triggerWindowResizeEvent} from '@/utils/util'
+  import { updateTheme, updateColorWeak, colorList } from '@/components/tools/setting'
+  import { mixin, mixinDevice } from '@/utils/mixin.js'
+  import { triggerWindowResizeEvent } from '@/utils/util'
 
   export default {
     components: {
@@ -186,10 +181,12 @@
         visible: true,
         colorList,
         dataFixSiderbar: false
-      }
+    }
     },
-    watch: {},
-    mounted() {
+    watch: {
+
+    },
+    mounted () {
       const vm = this
       setTimeout(() => {
         vm.visible = false
@@ -215,39 +212,39 @@
       toggle() {
         this.visible = !this.visible
       },
-      onColorWeak(checked) {
+      onColorWeak (checked) {
         this.$store.dispatch('ToggleWeak', checked)
         updateColorWeak(checked)
       },
-      onMultipageWeak(checked) {
+      onMultipageWeak (checked) {
         this.$store.dispatch('ToggleMultipage', checked)
       },
-      handleMenuTheme(theme) {
+      handleMenuTheme (theme) {
         this.$store.dispatch('ToggleTheme', theme)
       },
-      handleLayout(mode) {
+      handleLayout (mode) {
         this.$store.dispatch('ToggleLayoutMode', mode)
         // 因为顶部菜单不能固定左侧菜单栏,所以强制关闭
         this.handleFixSiderbar(false)
         // 触发窗口resize事件
         triggerWindowResizeEvent()
       },
-      handleContentWidthChange(type) {
+      handleContentWidthChange (type) {
         this.$store.dispatch('ToggleContentWidth', type)
       },
-      changeColor(color) {
+      changeColor (color) {
         if (this.primaryColor !== color) {
           this.$store.dispatch('ToggleColor', color)
           updateTheme(color)
         }
       },
-      handleFixedHeader(fixed) {
+      handleFixedHeader (fixed) {
         this.$store.dispatch('ToggleFixedHeader', fixed)
       },
-      handleFixedHeaderHidden(autoHidden) {
+      handleFixedHeaderHidden (autoHidden) {
         this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
       },
-      handleFixSiderbar(fixed) {
+      handleFixSiderbar (fixed) {
         if (this.layoutMode === 'topmenu') {
           fixed = false
         }
@@ -258,7 +255,7 @@
   }
 </script>
 
-<style lang="scss" scoped>
+<style lang="less" scoped>
 
   .setting-drawer-index-content {
 
@@ -289,7 +286,6 @@
         }
       }
     }
-
     .setting-drawer-theme-color-colorBlock {
       width: 20px;
       height: 20px;

+ 26 - 26
src/components/table/StandardTable.vue

@@ -48,7 +48,7 @@
       },
       dataSource: {
         type: Array,
-        default() {
+        default () {
           return []
         }
       },
@@ -56,12 +56,12 @@
         type: Array,
         required: true
       },
-      /*      pagination: {
-              type: Object,
-              default () {
-                return {}
-              }
-            },*/
+/*      pagination: {
+        type: Object,
+        default () {
+          return {}
+        }
+      },*/
       pageSize: {
         type: Number,
         default: 10
@@ -72,13 +72,13 @@
       },
       pageSizeOptions: {
         type: Array,
-        default() {
+        default () {
           return ['10', '20', '30', '40', '50']
         }
       },
       responseParamsName: {
         type: Object,
-        default() {
+        default () {
           return {}
         }
       },
@@ -102,7 +102,7 @@
         default: null
       }
     },
-    data() {
+    data () {
       return {
         needTotalList: [],
         selectedRowKeys: [],
@@ -120,7 +120,7 @@
         paramsName: {},
       }
     },
-    created() {
+    created () {
       //数据请求参数配置
       this.paramsName = Object.assign(
         {},
@@ -137,10 +137,10 @@
       this.needTotalList = this.initTotalList(this.columns)
 
       // load data
-      this.loadData({pageNum: this.pageNumber})
+      this.loadData( { pageNum: this.pageNumber } )
     },
     methods: {
-      updateSelect(selectedRowKeys, selectedRows) {
+      updateSelect (selectedRowKeys, selectedRows) {
         this.selectedRowKeys = selectedRowKeys
         let list = this.needTotalList
         this.needTotalList = list.map(item => {
@@ -153,17 +153,17 @@
         })
         this.$emit('change', selectedRowKeys, selectedRows)
       },
-      initTotalList(columns) {
+      initTotalList (columns) {
         const totalList = []
         columns.forEach(column => {
           if (column.needTotal) {
-            totalList.push({...column, total: 0})
+            totalList.push({ ...column, total: 0 })
           }
         })
         return totalList
       },
 
-      loadData(params) {
+      loadData (params) {
         let that = this
         that.loading = true
         params = Object.assign({}, params)
@@ -180,7 +180,7 @@
 
         let dataPromise = that.data(remoteParams)
 
-        dataPromise.then(response => {
+        dataPromise.then( response => {
           if (!response) {
             that.loading = false
             return
@@ -202,22 +202,22 @@
         })
       },
       // eslint-disable-next-line
-      onPagerChange(page, pageSize) {
+      onPagerChange (page, pageSize) {
         this.pageNumber = page
-        this.loadData({pageNum: page})
+        this.loadData({ pageNum: page })
       },
-      onPagerSizeChange(current, size) {
+      onPagerSizeChange (current, size) {
         this.currentPageSize = size
         /*
         if (current === this.pageNumber) this.loadData()
         console.log('page-size-change', current, size)
         */
       },
-      onClearSelected() {
+      onClearSelected () {
         this.selectedRowKeys = []
         this.updateSelect([], [])
       },
-      pager() {
+      pager () {
         return {
           total: this.total,
           showTotal: total => `共有 ${total} 条`,
@@ -235,7 +235,7 @@
         this.needTotalList = this.needTotalList.map(item => {
           return {
             ...item,
-            total: selectedRows.reduce((sum, val) => {
+            total: selectedRows.reduce( (sum, val) => {
               return sum + val[item.dataIndex]
             }, 0)
           }
@@ -246,7 +246,7 @@
 </script>
 
 <style scoped>
-  .alert {
-    margin-bottom: 16px;
-  }
+    .alert {
+        margin-bottom: 16px;
+    }
 </style>

+ 8 - 7
src/components/table/index.js

@@ -1,6 +1,5 @@
 import T from "ant-design-vue/es/table/Table";
 import get from "lodash.get"
-
 export default {
   data() {
     return {
@@ -124,8 +123,7 @@ export default {
       const totalList = []
       columns && columns instanceof Array && columns.forEach(column => {
         if (column.needTotal) {
-          totalList.push({
-            ...column,
+          totalList.push({ ...column,
             total: 0
           })
         }
@@ -151,8 +149,11 @@ export default {
       this.selectedRows = []
     },
     onClearSelected() {
+      // 【TESTA-262】页面清空后还能才做所选行,增加  this.$emit('clearAll')
       this.selectedRowKeys = []
+      this.selectedRows = []
       this.updateSelect([], [])
+      this.$emit('clearAll')
     },
     renderMsg(h) {
       const _vm = this
@@ -178,12 +179,12 @@ export default {
             }
           },
           [
-            `${item.title}总计 `,
+            `${ item.title }总计 `,
             h('a', {
               style: {
                 fontWeight: 600
               }
-            }, `${!item.customRender ? item.total : item.customRender(item.total)}`)
+            }, `${ !item.customRender ? item.total : item.customRender(item.total) }`)
           ]))
       });
 
@@ -213,7 +214,7 @@ export default {
       localKeys = Object.keys(this.$data);
 
     Object.keys(T.props).forEach(k => {
-      let localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`;
+      let localKey = `local${k.substring(0,1).toUpperCase()}${k.substring(1)}`;
       if (localKeys.includes(localKey)) {
         return props[k] = _vm[localKey];
       }
@@ -228,7 +229,7 @@ export default {
         selectedRowKeys: this.selectedRowKeys,
         onChange: (selectedRowKeys, selectedRows) => {
           _vm.updateSelect(selectedRowKeys, selectedRows)
-          _vm.$emit('onSelect', {selectedRowKeys: selectedRowKeys, selectedRows: selectedRows})
+          _vm.$emit('onSelect', { selectedRowKeys: selectedRowKeys, selectedRows: selectedRows })
         }
       };
 

+ 68 - 61
src/components/tools/DepartSelect.vue

@@ -1,6 +1,6 @@
 <template>
   <a-modal
-    :title="title"
+    :title="currTitle"
     :width="450"
     :visible="visible"
     :closable="false"
@@ -16,15 +16,14 @@
         :wrapperCol="{span:20}"
         style="margin-bottom:10px"
         :validate-status="validate_status">
-        <a-tooltip placement="topLeft">
+        <a-tooltip placement="topLeft" >
           <template slot="title">
             <span>您隶属于多部门,请选择当前所在部门</span>
           </template>
-          <a-avatar style="backgroundColor:#87d068" icon="gold"/>
+          <a-avatar style="backgroundColor:#87d068" icon="gold" />
         </a-tooltip>
-        <a-select v-model="departSelected" :class="{'valid-error':validate_status=='error'}" placeholder="请选择登录部门"
-                  style="margin-left:10px;width: 80%">
-          <a-icon slot="suffixIcon" type="gold"/>
+        <a-select v-model="departSelected" :class="{'valid-error':validate_status=='error'}" placeholder="请选择登录部门" style="margin-left:10px;width: 80%">
+          <a-icon slot="suffixIcon" type="gold" />
           <a-select-option
             v-for="d in departList"
             :key="d.id"
@@ -37,71 +36,75 @@
 
 
   </a-modal>
-
+    
 </template>
 
 <script>
-  import {getAction, putAction} from '@/api/manage'
+  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',
-    props: {
-      title: {
-        type: String,
-        default: "部门选择",
-        required: false
+    props:{
+      title:{
+        type:String,
+        default:"部门选择",
+        required:false
       },
-      closable: {
-        type: Boolean,
-        default: false,
-        required: false
+      closable:{
+        type:Boolean,
+        default:false,
+        required:false
       },
-      username: {
-        type: String,
-        default: "",
-        required: false
+      username:{
+        type:String,
+        default:"",
+        required:false
       }
     },
-    watch: {
-      username(val) {
-        if (val) {
+    watch:{
+      username(val){
+        if(val){
           this.loadDepartList()
         }
       }
     },
-    data() {
+    data(){
       return {
-        visible: false,
-        departList: [],
-        departSelected: "",
-        validate_status: "",
-        currDepartName: "",
+        currTitle:this.title,
+        visible:false,
+        departList:[],
+        departSelected:"",
+        validate_status:"",
+        currDepartName:"",
       }
     },
-    created() {
+    created(){
       //this.loadDepartList()
     },
-    methods: {
-      loadDepartList() {
+    methods:{
+      loadDepartList(){
         return new Promise(resolve => {
           let url = "/sys/user/getCurrentUserDeparts"
-          this.currDepartName = ''
-          getAction(url).then(res => {
-            if (res.success) {
+          this.currDepartName=''
+          getAction(url).then(res=>{
+            if(res.success){
               let departs = res.result.list
               let orgCode = res.result.orgCode
-              if (departs && departs.length > 0) {
-                for (let i of departs) {
-                  if (i.orgCode == orgCode) {
+              if(departs && departs.length>0){
+                for(let i of departs){
+                  if(i.orgCode == orgCode){
                     this.currDepartName = i.departName
                     break
                   }
                 }
               }
               this.departSelected = orgCode
-              this.departList = departs
-              if (this.currDepartName) {
-                this.title = "部门切换(当前部门 : " + this.currDepartName + ")"
+              this.departList  = departs
+              if(this.currDepartName){
+                this.currTitle ="部门切换(当前部门 : "+this.currDepartName+")"
               }
 
             }
@@ -109,47 +112,51 @@
           })
         })
       },
-      close() {
+      close(){
         this.departClear()
       },
-      departOk() {
-        if (!this.departSelected) {
-          this.validate_status = 'error'
+      departOk(){
+        if(!this.departSelected){
+          this.validate_status='error'
           return false
         }
         let obj = {
-          orgCode: this.departSelected,
-          username: this.username
+          orgCode:this.departSelected,
+          username:this.username
         }
-        putAction("/sys/selectDepart", obj).then(res => {
-          if (res.success) {
+        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()
           }
         })
       },
-      show() {
+      show(){
         //如果组件传值username此处就不用loadDepartList了
-        this.loadDepartList().then(() => {
-          this.visible = true
-          if (!this.departList || this.departList.length <= 0) {
+        this.loadDepartList().then(()=>{
+          this.visible=true
+          if(!this.departList || this.departList.length<=0){
             this.$message.warning("您尚未设置部门信息!")
             this.departClear()
           }
         })
       },
-      departClear() {
-        this.departList = []
-        this.departSelected = ""
-        this.visible = false
-        this.validate_status = ''
-        this.currDepartName = ""
+      departClear(){
+        this.departList=[]
+        this.departSelected=""
+        this.visible=false
+        this.validate_status=''
+        this.currDepartName=""
       },
     }
 
   }
 </script>
 <style scoped>
-  .valid-error .ant-select-selection__placeholder {
+  .valid-error .ant-select-selection__placeholder{
     color: #f5222d;
   }
 </style>

+ 16 - 29
src/components/tools/DetailList.vue

@@ -8,7 +8,7 @@
 </template>
 
 <script>
-  import {Col} from 'ant-design-vue/es/grid/'
+  import { Col } from 'ant-design-vue/es/grid/'
 
   const Item = {
     name: 'DetailListItem',
@@ -24,32 +24,21 @@
         type: Number
       }
     },
-    render() {
+    render () {
       return (
-        < Col
-      {...
-        {
-          props: responsive[this.col]
-        }
-      }
-    >
-    <
-      div
-    class
-      = "term" > {this.$props.term} < /div>
-        < div
-    class
-      = "content" > {this.$slots.default} < /div>
-        < /Col>
-    )
+        <Col {...{props: responsive[this.col]}}>
+          <div class="term">{this.$props.term}</div>
+          <div class="content">{this.$slots.default}</div>
+        </Col>
+      )
     }
   }
 
   const responsive = {
-    1: {xs: 24},
-    2: {xs: 24, sm: 12},
-    3: {xs: 24, sm: 12, md: 8},
-    4: {xs: 24, sm: 12, md: 6}
+    1: { xs: 24 },
+    2: { xs: 24, sm: 12 },
+    3: { xs: 24, sm: 12, md: 8 },
+    4: { xs: 24, sm: 12, md: 6 }
   }
 
   export default {
@@ -80,7 +69,7 @@
         default: 'horizontal'
       }
     },
-    provide() {
+    provide () {
       return {
         col: this.col > 4 ? 4 : this.col
       }
@@ -88,19 +77,19 @@
   }
 </script>
 
-<style lang="scss">
+<style lang="less">
 
   .detail-list {
 
     .title {
-      color: rgba(0, 0, 0, .85);
+      color: rgba(0,0,0,.85);
       font-size: 14px;
       font-weight: 500;
       margin-bottom: 16px;
     }
 
     .term {
-      color: rgba(0, 0, 0, .85);
+      color: rgba(0,0,0,.85);
       display: table-cell;
       line-height: 20px;
       margin-right: 8px;
@@ -116,7 +105,7 @@
     }
 
     .content {
-      color: rgba(0, 0, 0, .65);
+      color: rgba(0,0,0,.65);
       display: table-cell;
       line-height: 22px;
       padding-bottom: 16px;
@@ -131,7 +120,6 @@
         font-weight: normal;
         margin-bottom: 12px;
       }
-
       .term, .content {
         padding-bottom: 8px;
       }
@@ -151,7 +139,6 @@
       .term {
         padding-bottom: 8px;
       }
-
       .term, .content {
         display: block;
       }

+ 36 - 0
src/components/tools/DynamicNotice.vue

@@ -0,0 +1,36 @@
+<template>
+  <component
+    :is="comp"
+    :formData="formData"
+    ref="compModel"
+    v-if="comp">
+  </component>
+</template>
+<script>
+  export default {
+    name: 'DynamicNotice',
+    data () {
+      return {
+        compName: this.path
+      }
+    },
+    computed: {
+      comp: function () {
+        if(!this.path){
+          return null;
+        }
+        return () => import(`@/views/${this.path}.vue`)
+      }
+    },
+    props: ['path','formData'],
+    methods: {
+      detail () {
+        setTimeout(() => {
+          if(this.path){
+            this.$refs.compModel.view(this.formData);
+          }
+        }, 200)
+      },
+    }
+  }
+</script>

+ 185 - 33
src/components/tools/HeaderNotice.vue

@@ -31,7 +31,7 @@
             <a-list>
               <a-list-item :key="index" v-for="(record, index) in announcement1">
                 <div style="margin-left: 5%;width: 80%">
-                  <p><a @click="showAnnouncement(record)">标题:{{ record.titile }}</a></p>
+                  <p><a @click="showAnnouncement(record)">{{ record.titile }}</a></p>
                   <p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p>
                 </div>
                 <div style="text-align: right">
@@ -49,7 +49,7 @@
             <a-list>
               <a-list-item :key="index" v-for="(record, index) in announcement2">
                 <div style="margin-left: 5%;width: 80%">
-                  <p><a @click="showAnnouncement(record)">标题:{{ record.titile }}</a></p>
+                  <p><a @click="showAnnouncement(record)">{{ record.titile }}</a></p>
                   <p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p>
                 </div>
                 <div style="text-align: right">
@@ -68,54 +68,77 @@
     </template>
     <span @click="fetchNotice" class="header-notice">
       <a-badge :count="msgTotal">
-        <a-icon style="font-size: 16px; padding: 4px" type="bell"/>
+        <a-icon style="font-size: 16px; padding: 4px" type="bell" />
       </a-badge>
     </span>
     <show-announcement ref="ShowAnnouncement" @ok="modalFormOk"></show-announcement>
+    <dynamic-notice ref="showDynamNotice" :path="openPath" :formData="formData"/>
   </a-popover>
 </template>
 
 <script>
-  import {getAction, putAction} from '@/api/manage'
+  import { getAction,putAction } from '@/api/manage'
   import ShowAnnouncement from './ShowAnnouncement'
+  import store from '@/store/'
+  import DynamicNotice from './DynamicNotice'
+
 
   export default {
     name: "HeaderNotice",
     components: {
+      DynamicNotice,
       ShowAnnouncement,
     },
-    data() {
+    data () {
       return {
         loadding: false,
-        url: {
-          listCementByUser: "/sys/annountCement/listByUser",
-          editCementSend: "/system/sysAnnouncementSend/editByAnntIdAndUserId",
+        url:{
+          listCementByUser:"/sys/annountCement/listByUser",
+          editCementSend:"/sys/sysAnnouncementSend/editByAnntIdAndUserId",
+          queryById:"/sys/annountCement/queryById",
         },
         hovered: false,
-        announcement1: [],
-        announcement2: [],
-        msg1Count: "3",
-        msg2Count: "0",
-        msg1Title: "通知(3)",
-        msg2Title: "",
+        announcement1:[],
+        announcement2:[],
+        msg1Count:"0",
+        msg2Count:"0",
+        msg1Title:"通知(0)",
+        msg2Title:"",
+        stopTimer:false,
+        websock: null,
+        lockReconnect:false,
+        heartCheck:null,
+        formData:{},
+        openPath:''
       }
     },
-    computed: {
-      msgTotal() {
-        return parseInt(this.msg1Count) + parseInt(this.msg2Count);
+    computed:{
+      msgTotal () {
+        return parseInt(this.msg1Count)+parseInt(this.msg2Count);
       }
     },
-    created() {
+    mounted() {
       this.loadData();
-      this.timer();
+      //this.timerFun();
+      this.initWebSocket();
+     // this.heartCheckFun();
+    },
+    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() {
+      loadData (){
         try {
           // 获取系统消息
           getAction(this.url.listCementByUser).then((res) => {
@@ -127,11 +150,17 @@
               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() {
+      fetchNotice () {
         if (this.loadding) {
           this.loadding = false
           return
@@ -141,28 +170,151 @@
           this.loadding = false
         }, 200)
       },
-      showAnnouncement(record) {
-        putAction(this.url.editCementSend, {anntId: record.id}).then((res) => {
-          if (res.success) {
+      showAnnouncement(record){
+        putAction(this.url.editCementSend,{anntId:record.id}).then((res)=>{
+          if(res.success){
             this.loadData();
           }
         });
         this.hovered = false;
-        this.$refs.ShowAnnouncement.detail(record);
+        if(record.openType==='component'){
+          this.openPath = record.openPage;
+          this.formData = {id:record.busId};
+          this.$refs.showDynamNotice.detail(record.openPage);
+        }else{
+          this.$refs.ShowAnnouncement.detail(record);
+        }
       },
-      toMyAnnouncement() {
+      toMyAnnouncement(){
 
         this.$router.push({
           path: '/isps/userAnnouncement',
           name: 'isps-userAnnouncement'
         });
       },
-      modalFormOk() {
+      modalFormOk(){
       },
-      handleHoverChange(visible) {
+      handleHoverChange (visible) {
         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.websocketOnclose;
+      },
+      websocketOnopen: function () {
+        console.log("WebSocket连接成功");
+        //心跳检测重置
+        //this.heartCheck.reset().start();
+      },
+      websocketOnerror: function (e) {
+        console.log("WebSocket连接发生错误");
+        this.reconnect();
+      },
+      websocketOnmessage: function (e) {
+        console.log("-----接收消息-------",e.data);
+        var data = eval("(" + e.data + ")"); //解析对象
+        if(data.cmd == "topic"){
+            //系统通知
+          this.loadData();
+        }else if(data.cmd == "user"){
+            //用户消息
+          this.loadData();
+        }
+        //心跳检测重置
+        //this.heartCheck.reset().start();
+      },
+      websocketOnclose: function (e) {
+        console.log("connection closed (" + e.code + ")");
+        this.reconnect();
+      },
+      websocketSend(text) { // 数据发送
+        try {
+          this.websock.send(text);
+        } catch (err) {
+          console.log("send failed (" + err.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)
+              }
+            }, '查看详情')
+          },
+        });
+      },
+
+      reconnect() {
+        var that = this;
+        if(that.lockReconnect) return;
+        that.lockReconnect = true;
+        //没连接上会一直重连,设置延迟避免请求过多
+        setTimeout(function () {
+          console.info("尝试重连...");
+          that.initWebSocket();
+          that.lockReconnect = false;
+        }, 5000);
+      },
+      heartCheckFun(){
+        var that = this;
+        //心跳检测,每20s心跳一次
+        that.heartCheck = {
+          timeout: 20000,
+          timeoutObj: null,
+          serverTimeoutObj: null,
+          reset: function(){
+            clearTimeout(this.timeoutObj);
+            //clearTimeout(this.serverTimeoutObj);
+            return this;
+          },
+          start: function(){
+            var self = this;
+            this.timeoutObj = setTimeout(function(){
+              //这里发送一个心跳,后端收到后,返回一个心跳消息,
+              //onmessage拿到返回的心跳就说明连接正常
+              that.websocketSend("HeartBeat");
+              console.info("客户端发送心跳");
+              //self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了
+              //  that.websock.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
+              //}, self.timeout)
+            }, this.timeout)
+          }
+        }
+      },
+
+
+      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>
@@ -172,8 +324,8 @@
     top: 50px !important;
   }
 </style>
-<style lang="scss" scoped>
-  .header-notice {
+<style lang="less" scoped>
+  .header-notice{
     display: inline-block;
     transition: all 0.3s;
 

+ 82 - 35
src/components/tools/ShowAnnouncement.vue

@@ -1,83 +1,130 @@
 <template>
-  <a-modal
+  <j-modal
     :title="title"
-    :width="600"
+    :width="modelStyle.width"
     :visible="visible"
+    :bodyStyle ="bodyStyle"
+    :switchFullscreen="switchFullscreen"
     @cancel="handleCancel"
-    :okButtonProps="okButtonProps"
-    cancelText="关闭">
-
-    <div class="message">
-      <div class="title">{{ record.titile }}</div>
-      <div class="createBy">发布人:{{ record.sender }}</div>
-      <div class="sendTime">发布时间:{{ record.sendTime }}</div>
-      <a-divider/>
-      <p v-html="record.msgContent"></p>
-    </div>
-
-
-  </a-modal>
+   >
+    <template slot="footer">
+      <a-button key="back" @click="handleCancel">关闭</a-button>
+      <a-button v-if="record.openType==='url'" type="primary" @click="toHandle">去处理</a-button>
+    </template>
+    <a-card class="daily-article" :loading="loading">
+      <a-card-meta
+        :title="record.titile"
+        :description="'发布人:'+record.sender + ' 发布时间: ' + record.sendTime">
+      </a-card-meta>
+      <a-divider />
+      <span v-html="record.msgContent" class="article-content"></span>
+    </a-card>
+  </j-modal>
 </template>
 
 <script>
   export default {
     name: "SysAnnouncementModal",
-    components: {},
-    data() {
+    components: {
+    },
+    data () {
       return {
-        okButtonProps:{},
-        title: "通知消息",
+        title:"通知消息",
         record: {},
         labelCol: {
-          xs: {span: 24},
-          sm: {span: 5},
+          xs: { span: 24 },
+          sm: { span: 5 },
         },
         wrapperCol: {
-          xs: {span: 24},
-          sm: {span: 16},
+          xs: { span: 24 },
+          sm: { span: 16 },
         },
         visible: false,
-
+        switchFullscreen: true,
         loading: false,
-        bodyStyle: {
+        bodyStyle:{
           padding: "0",
-          height: (window.innerHeight * 0.8) + "px",
-          "overflow-y": "auto"
+          height:(window.innerHeight*0.8)+"px",
+          "overflow-y":"auto",
+
         },
+        modelStyle:{
+          width: '60%',
+          style: { top: '20px' },
+          fullScreen: false
+        }
       }
     },
-    created() {
+    created () {
     },
     methods: {
-      detail(record) {
+      detail (record) {
         this.visible = true;
         this.record = record;
       },
-      handleCancel() {
+      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
+      },
+      toHandle(){
+        if(this.record.openType==='url'){
+          this.visible = false;
+          //链接跳转
+          this.$router.push({path: this.record.openPage})
+        }
+      },
     }
   }
 </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;
@@ -89,4 +136,4 @@
       }
     }
   }
-</style>
+</style>

+ 40 - 40
src/components/tools/TwoStepCaptcha.vue

@@ -19,7 +19,7 @@
     <a-spin :spinning="stepLoading">
       <a-form layout="vertical" :auto-form-create="(form)=>{this.form = form}">
         <div class="step-form-wrapper">
-          <p style="text-align: center" v-if="!stepLoading">请在手机中打开 Google Authenticator 或两步验证 APP<br/>输入 6 位动态码</p>
+          <p style="text-align: center" v-if="!stepLoading">请在手机中打开 Google Authenticator 或两步验证 APP<br />输入 6 位动态码</p>
           <p style="text-align: center" v-else>正在验证..<br/>请稍后</p>
           <a-form-item
             :style="{ textAlign: 'center' }"
@@ -27,7 +27,7 @@
             fieldDecoratorId="stepCode"
             :fieldDecoratorOptions="{rules: [{ required: true, message: '请输入 6 位动态码!', pattern: /^\d{6}$/, len: 6 }]}"
           >
-            <a-input :style="{ textAlign: 'center' }" @keyup.enter.native="handleStepOk" placeholder="000000"/>
+            <a-input :style="{ textAlign: 'center' }" @keyup.enter.native="handleStepOk" placeholder="000000" />
           </a-form-item>
           <p style="text-align: center">
             <a @click="onForgeStepCode">遗失手机?</a>
@@ -39,48 +39,48 @@
 </template>
 
 <script>
-  export default {
-    props: {
-      visible: {
-        type: Boolean,
-        default: false
-      }
-    },
-    data() {
-      return {
-        stepLoading: false,
+export default {
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      stepLoading: false,
 
-        form: null
-      };
+      form: null
+    };
+  },
+  methods: {
+    handleStepOk() {
+      const vm = this
+      this.stepLoading = true
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          console.log('values', values)
+          setTimeout( () => {
+            vm.stepLoading = false
+            vm.$emit('success', { values })
+          }, 2000)
+          return;
+        }
+        this.stepLoading = false
+        this.$emit('error', { err })
+      })
     },
-    methods: {
-      handleStepOk() {
-        const vm = this
-        this.stepLoading = true
-        this.form.validateFields((err, values) => {
-          if (!err) {
-            console.log('values', values)
-            setTimeout(() => {
-              vm.stepLoading = false
-              vm.$emit('success', {values})
-            }, 2000)
-            return;
-          }
-          this.stepLoading = false
-          this.$emit('error', {err})
-        })
-      },
-      handleCancel() {
-        this.visible = false
-        this.$emit('cancel')
-      },
-      onForgeStepCode() {
-
-      }
+    handleCancel () {
+      this.visible = false
+      this.$emit('cancel')
+    },
+    onForgeStepCode() {
+      
     }
-  };
+  }
+};
 </script>
-<style lang="scss" scoped>
+<style lang="less" scoped>
   .step-form-wrapper {
     margin: 0 auto;
     width: 80%;

+ 137 - 13
src/components/tools/UserMenu.vue

@@ -1,7 +1,33 @@
 <template>
   <div class="user-wrapper" :class="theme">
+    <!-- update_begin author:zhaoxin date:20191129 for: 做头部菜单栏导航 -->
+    <!-- update-begin author:sunjianlei date:20191@20 for: 解决全局样式冲突的问题 -->
+    <span class="action" @click="showClick">
+      <a-icon type="search"></a-icon>
+    </span>
+    <!-- update-begin author:sunjianlei date:20200219 for: 菜单搜索改为动态组件,在手机端呈现出弹出框 -->
+    <component :is="searchMenuComp" v-show="searchMenuVisible || isMobile()" class="borders" :visible="searchMenuVisible" title="搜索菜单" :footer="null" @cancel="searchMenuVisible=false">
+      <a-select
+        class="search-input"
+        showSearch
+        :showArrow="false"
+        placeholder="搜索菜单"
+        optionFilterProp="children"
+        :filterOption="filterOption"
+        :open="isMobile()?true:null"
+        :getPopupContainer="(node) => node.parentNode"
+        :style="isMobile()?{width: '100%',marginBottom:'50px'}:{}"
+        @change="searchMethods"
+        @blur="hiddenClick"
+      >
+        <a-select-option v-for="(site,index) in searchMenuOptions" :key="index" :value="site.id">{{site.meta.title}}</a-select-option>
+      </a-select>
+    </component>
+    <!-- update-end author:sunjianlei date:20200219 for: 菜单搜索改为动态组件,在手机端呈现出弹出框 -->
+    <!-- update-end author:sunjianlei date:20191220 for: 解决全局样式冲突的问题 -->
+    <!-- update_end  author:zhaoxin date:20191129 for: 做头部菜单栏导航 -->
     <span class="action">
-      <a class="logout_title" target="_blank" href="http://www.c-top.com.cn">
+      <a class="logout_title" target="_blank" href="http://doc.jeecg.com">
         <a-icon type="question-circle-o"></a-icon>
       </a>
     </span>
@@ -24,11 +50,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,23 +83,36 @@
     </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'
+  import { mapActions, mapGetters,mapState } from 'vuex'
+  import { mixinDevice } from '@/utils/mixin.js'
+  import { getFileAccessHttpUrl } from "@/api/manage"
 
   export default {
     name: "UserMenu",
     mixins: [mixinDevice],
+    data(){
+      return{
+        // update-begin author:sunjianlei date:20200219 for: 头部菜单搜索规范命名 --------------
+        searchMenuOptions:[],
+        searchMenuComp: 'span',
+        searchMenuVisible: false,
+        // update-begin author:sunjianlei date:20200219 for: 头部菜单搜索规范命名 --------------
+      }
+    },
     components: {
       HeaderNotice,
       UserPassword,
-      DepartSelect
+      DepartSelect,
+      SettingDrawer
     },
     props: {
       theme: {
@@ -78,12 +121,44 @@
         default: 'dark'
       }
     },
+    /* update_begin author:zhaoxin date:20191129 for: 做头部菜单栏导航*/
+    created() {
+      let lists = []
+      this.searchMenus(lists,this.permissionMenuList)
+      this.searchMenuOptions=[...lists]
+    },
+    computed: {
+      ...mapState({
+        // 后台菜单
+        permissionMenuList: state => state.user.permissionList
+
+      })
+    },
+    /* update_end author:zhaoxin date:20191129 for: 做头部菜单栏导航*/
+    watch: {
+      // update-begin author:sunjianlei date:20200219 for: 菜单搜索改为动态组件,在手机端呈现出弹出框
+      device: {
+        immediate: true,
+        handler() {
+          this.searchMenuVisible = false
+          this.searchMenuComp = this.isMobile() ? 'a-modal' : 'span'
+        },
+      },
+      // update-end author:sunjianlei date:20200219 for: 菜单搜索改为动态组件,在手机端呈现出弹出框
+    },
     methods: {
+      /* update_begin author:zhaoxin date:20191129 for: 做头部菜单栏导航*/
+      showClick() {
+        this.searchMenuVisible = true
+      },
+      hiddenClick(){
+        this.shows = false
+      },
+      /* update_end author:zhaoxin date:20191129 for: 做头部菜单栏导航*/
       ...mapActions(["Logout"]),
-      ...mapGetters(["nickname", "avatar", "userInfo"]),
-      getAvatar() {
-        console.log('url = ' + window._CONFIG['imgDomainURL'] + "/" + this.avatar())
-        return window._CONFIG['imgDomainURL'] + "/" + this.avatar()
+      ...mapGetters(["nickname", "avatar","userInfo"]),
+      getAvatar(){
+        return getFileAccessHttpUrl(this.avatar())
       },
       handleLogout() {
         const that = this
@@ -93,7 +168,7 @@
           content: '真的要注销登录吗 ?',
           onOk() {
             return that.Logout({}).then(() => {
-              window.location.href = "/";
+              window.location.href="/";
               //window.location.reload()
             }).catch(err => {
               that.$message.error({
@@ -106,17 +181,66 @@
           },
         });
       },
-      updatePassword() {
+      updatePassword(){
         let username = this.userInfo().username
         this.$refs.userPassword.show(username)
       },
-      updateCurrentDepart() {
+      updateCurrentDepart(){
         this.$refs.departSelect.show()
+      },
+      systemSetting(){
+        this.$refs.settingDrawer.showDrawer()
+      },
+      /* update_begin author:zhaoxin date:20191129 for: 做头部菜单栏导航*/
+      searchMenus(arr,menus){
+        for(let i of menus){
+          if(!i.hidden && "layouts/RouteView"!==i.component){
+            arr.push(i)
+          }
+          if(i.children&& i.children.length>0){
+            this.searchMenus(arr,i.children)
+          }
+        }
+      },
+      filterOption(input, option) {
+        return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+      },
+      // update_begin author:sunjianlei date:20191230 for: 解决外部链接打开失败的问题
+      searchMethods(value) {
+        let route = this.searchMenuOptions.filter(item => item.id === value)[0]
+        if (route.meta.internalOrExternal === true || route.component.includes('layouts/IframePageView')) {
+          window.open(route.meta.url, '_blank')
+        } else {
+          this.$router.push({ path: route.path })
+        }
+        this.searchMenuVisible = false
       }
+      // update_end author:sunjianlei date:20191230 for: 解决外部链接打开失败的问题
+      /*update_end author:zhaoxin date:20191129 for: 做头部菜单栏导航*/
     }
   }
 </script>
 
+<style lang="less" scoped>
+  /* update_begin author:zhaoxin date:20191129 for: 让搜索框颜色能随主题颜色变换*/
+  /* update-begin author:sunjianlei date:20191220 for: 解决全局样式冲突问题 */
+  .user-wrapper .search-input {
+    width: 180px;
+    color: inherit;
+
+    /deep/ .ant-select-selection {
+      background-color: inherit;
+      border: 0;
+      border-bottom: 1px solid white;
+      &__placeholder, &__field__placeholder {
+        color: inherit;
+      }
+    }
+  }
+  /* update-end author:sunjianlei date:20191220 for: 解决全局样式冲突问题 */
+  /* update_end author:zhaoxin date:20191129 for: 让搜索框颜色能随主题颜色变换*/
+</style>
+
 <style scoped>
   .logout_title {
     color: inherit;

+ 33 - 34
src/components/tools/UserPassword.vue

@@ -15,22 +15,21 @@
           :labelCol="labelCol"
           :wrapperCol="wrapperCol"
           label="旧密码">
-          <a-input type="password" placeholder="请输入旧密码" v-decorator="[ 'oldpassword', validatorRules.oldpassword]"/>
+          <a-input type="password" placeholder="请输入旧密码" v-decorator="[ 'oldpassword', validatorRules.oldpassword]" />
         </a-form-item>
 
         <a-form-item
           :labelCol="labelCol"
           :wrapperCol="wrapperCol"
           label="新密码">
-          <a-input type="password" placeholder="请输入新密码" v-decorator="[ 'password', validatorRules.password]"/>
+          <a-input type="password" placeholder="请输入新密码" v-decorator="[ 'password', validatorRules.password]" />
         </a-form-item>
 
         <a-form-item
           :labelCol="labelCol"
           :wrapperCol="wrapperCol"
           label="确认新密码">
-          <a-input type="password" @blur="handleConfirmBlur" placeholder="请确认新密码"
-                   v-decorator="[ 'confirmpassword', validatorRules.confirmpassword]"/>
+          <a-input type="password" @blur="handleConfirmBlur" placeholder="请确认新密码" v-decorator="[ 'confirmpassword', validatorRules.confirmpassword]"/>
         </a-form-item>
 
       </a-form>
@@ -40,30 +39,30 @@
 
 <script>
 
-  import {putAction} from '@/api/manage'
+  import { putAction } from '@/api/manage'
 
   export default {
     name: "UserPassword",
-    data() {
+    data () {
       return {
-        title: "修改密码",
-        modalWidth: 800,
+        title:"修改密码",
+        modalWidth:800,
         visible: false,
         confirmLoading: false,
-        validatorRules: {
-          oldpassword: {
+        validatorRules:{
+          oldpassword:{
             rules: [{
               required: true, message: '请输入旧密码!',
             }],
           },
-          password: {
+          password:{
             rules: [{
               required: true, message: '请输入新密码!',
             }, {
               validator: this.validateToNextPassword,
             }],
           },
-          confirmpassword: {
+          confirmpassword:{
             rules: [{
               required: true, message: '请确认新密码!',
             }, {
@@ -71,55 +70,55 @@
             }],
           }
         },
-        confirmDirty: false,
+        confirmDirty:false,
         labelCol: {
-          xs: {span: 24},
-          sm: {span: 5},
+          xs: { span: 24 },
+          sm: { span: 5 },
         },
         wrapperCol: {
-          xs: {span: 24},
-          sm: {span: 16},
+          xs: { span: 24 },
+          sm: { span: 16 },
         },
 
-        form: this.$form.createForm(this),
+        form:this.$form.createForm(this),
         url: "sys/user/updatePassword",
-        username: "",
+        username:"",
       }
     },
     methods: {
-      show(uname) {
-        if (!uname) {
+      show(uname){
+        if(!uname){
           this.$message.warning("当前系统无登陆用户!");
           return
-        } else {
+        }else{
           this.username = uname
           this.form.resetFields();
           this.visible = true;
         }
       },
-      handleCancel() {
+      handleCancel () {
         this.close()
       },
-      close() {
+      close () {
         this.$emit('close');
         this.visible = false;
         this.disableSubmit = false;
         this.selectedRole = [];
       },
-      handleOk() {
+      handleOk () {
         const that = this;
         // 触发表单验证
         this.form.validateFields((err, values) => {
           if (!err) {
             that.confirmLoading = true;
-            let params = Object.assign({username: this.username}, values)
-            console.log("修改密码提交数据", params)
-            putAction(this.url, params).then((res) => {
-              if (res.success) {
+            let params = Object.assign({username:this.username},values)
+            console.log("修改密码提交数据",params)
+            putAction(this.url,params).then((res)=>{
+              if(res.success){
                 console.log(res)
                 that.$message.success(res.message);
                 that.close();
-              } else {
+              }else{
                 that.$message.warning(res.message);
               }
             }).finally(() => {
@@ -128,14 +127,14 @@
           }
         })
       },
-      validateToNextPassword(rule, value, callback) {
+      validateToNextPassword  (rule, value, callback) {
         const form = this.form;
         if (value && this.confirmDirty) {
-          form.validateFields(['confirm'], {force: true})
+          form.validateFields(['confirm'], { force: true })
         }
         callback();
       },
-      compareToFirstPassword(rule, value, callback) {
+      compareToFirstPassword  (rule, value, callback) {
         const form = this.form;
         if (value && value !== form.getFieldValue('password')) {
           callback('两次输入的密码不一样!');
@@ -143,7 +142,7 @@
           callback()
         }
       },
-      handleConfirmBlur(e) {
+      handleConfirmBlur  (e) {
         const value = e.target.value
         this.confirmDirty = this.confirmDirty || !!value
       }

+ 74 - 0
src/config/holidays.js

@@ -0,0 +1,74 @@
+/*中国节日放假安排,1法定调休,2法定放假 数据来自国务院网站:http://www.gov.cn/zhengce*/
+const holidays = {
+    '2020':{
+     "20200101": 2,
+     "20200119": 1,
+     "20200124": 2,
+     "20200125": 2,
+     "20200126": 2,
+     "20200127": 2,
+     "20200128": 2,
+     "20200129": 2,
+     "20200130": 2,
+     "20200201": 1,
+     "20200404": 2,
+     "20200405": 2,
+     "20200406": 2,
+     "20200426": 1,
+     "20200501": 2,
+     "20200502": 2,
+     "20200503": 2,
+     "20200504": 2,
+     "20200505": 2,
+     "20200509": 1,
+     "20200625": 2,
+     "20200626": 2,
+     "20200627": 2,
+     "20200628": 1,
+     "20200927": 1,
+     "20201001": 2,
+     "20201002": 2,
+     "20201003": 2,
+     "20201004": 2,
+     "20201005": 2,
+     "20201006": 2,
+     "20201007": 2,
+     "20201008": 2,
+     "20201010": 1
+   },
+    '2019':{
+      "20191001": 2,
+      "20191002": 2,
+      "20191003": 2,
+      "20191004": 2,
+      "20191005": 2,
+      "20191006": 2,
+      "20191007": 2,
+      "20191012": 1,
+      "20190101": 2,
+      "20190202": 1,
+      "20190203": 1,
+      "20190204": 2,
+      "20190205": 2,
+      "20190206": 2,
+      "20190207": 2,
+      "20190208": 2,
+      "20190209": 2,
+      "20190210": 2,
+      "20190405": 2,
+      "20190406": 2,
+      "20190407": 2,
+      "20190501": 2,
+      "20190607": 2,
+      "20190608": 2,
+      "20190609": 2,
+      "20190913": 2,
+      "20190914": 2,
+      "20190915": 2,
+      "20190929": 1
+    },
+}
+
+export function getHolidaysByYear(year) {
+  return holidays[year];
+}

+ 7 - 8
src/mixins/DisabledAuthFilterMixin.js

@@ -1,19 +1,18 @@
 /**
  *
  */
-import {disabledAuthFilter} from "@/utils/authFilter"
+import { disabledAuthFilter } from "@/utils/authFilter"
 
 export const DisabledAuthFilterMixin = {
   props: ['formData'],
-  data() {
-    return {}
-  },
-  created() {
+  data(){
+    return {
 
+    }
   },
-  methods: {
-    isDisabledAuth(code) {
-      return disabledAuthFilter(code, this.formData);
+  methods:{
+    isDisabledAuth(code){
+      return disabledAuthFilter(code,this.formData);
     },
   }
 

+ 18 - 8
src/mixins/JEditableTableMixin.js

@@ -1,6 +1,6 @@
 import JEditableTable from '@/components/jeecg/JEditableTable'
-import {VALIDATE_NO_PASSED, getRefPromise, validateFormAndTables} from '@/utils/JEditableTableUtil'
-import {httpAction, getAction} from '@/api/manage'
+import { VALIDATE_NO_PASSED, getRefPromise, validateFormAndTables } from '@/utils/JEditableTableUtil'
+import { httpAction, getAction } from '@/api/manage'
 
 export const JEditableTableMixin = {
   components: {
@@ -14,12 +14,12 @@ export const JEditableTableMixin = {
       confirmLoading: false,
       model: {},
       labelCol: {
-        xs: {span: 24},
-        sm: {span: 6}
+        xs: { span: 24 },
+        sm: { span: 6 }
       },
       wrapperCol: {
-        xs: {span: 24},
-        sm: {span: 18}
+        xs: { span: 24 },
+        sm: { span: 18 }
       }
     }
   },
@@ -80,10 +80,20 @@ export const JEditableTableMixin = {
     },
 
     /** 查询某个tab的数据 */
-    requestSubTableData(url, params, tab) {
+    requestSubTableData(url, params, tab, success) {
       tab.loading = true
       getAction(url, params).then(res => {
-        tab.dataSource = res.result || []
+        let { result } = res
+        let dataSource = []
+        if (result) {
+          if (Array.isArray(result)) {
+            dataSource = result
+          } else if (Array.isArray(result.records)) {
+            dataSource = result.records
+          }
+        }
+        tab.dataSource = dataSource
+        typeof success === 'function' ? success(res) : ''
       }).finally(() => {
         tab.loading = false
       })

+ 118 - 137
src/mixins/JeecgListMixin.js

@@ -3,24 +3,22 @@
  * 高级查询按钮调用 superQuery方法  高级查询组件ref定义为superQueryModal
  * data中url定义 list为查询列表  delete为删除单条记录  deleteBatch为批量删除
  */
-import {filterObj} from '@/utils/util';
-import moment from 'moment'
-import {deleteAction, getAction, downFile} from '@/api/manage'
+import { filterObj } from '@/utils/util';
+import { deleteAction, getAction,downFile,getFileAccessHttpUrl } from '@/api/manage'
 import Vue from 'vue'
-import {ACCESS_TOKEN} from "@/store/mutation-types"
-import { transformTozTreeFormat } from '@/utils/util.js'
+import { ACCESS_TOKEN } from "@/store/mutation-types"
+
 export const JeecgListMixin = {
-  data() {
+  data(){
     return {
       //token header
       tokenHeader: {'X-Access-Token': Vue.ls.get(ACCESS_TOKEN)},
       /* 查询条件-请不要在queryParam中声明非字符串值的属性 */
       queryParam: {},
       /* 数据源 */
-      dataSource: [],
-      innerData:[],
+      dataSource:[],
       /* 分页参数 */
-      ipagination: {
+      ipagination:{
         current: 1,
         pageSize: 10,
         pageSizeOptions: ['10', '20', '30'],
@@ -28,73 +26,63 @@ export const JeecgListMixin = {
           return range[0] + "-" + range[1] + " 共" + total + "条"
         },
         showQuickJumper: true,
-        // showSizeChanger: true,
+        showSizeChanger: true,
         total: 0
       },
       /* 排序参数 */
-      isorter: {
+      isorter:{
         column: 'createTime',
         order: 'desc',
       },
       /* 筛选参数 */
       filters: {},
       /* table加载状态 */
-      loading: false,
+      loading:false,
       /* table选中keys*/
       selectedRowKeys: [],
       /* table选中records*/
       selectionRows: [],
       /* 查询折叠 */
-      toggleSearchStatus: false,
+      toggleSearchStatus:false,
       /* 高级查询条件生效状态 */
-      superQueryFlag: false,
+      superQueryFlag:false,
       /* 高级查询条件 */
-      superQueryParams: ""
-    }
-  },
-  mounted() {
-    if(this.startList){
-
-    }else{
-       this.loadData();
+      superQueryParams: '',
+      /** 高级查询拼接方式 */
+      superQueryMatchType: 'and',
     }
-    //初始化字典配置 在自己页面定义
-    this.initDictConfig();
   },
-  methods: {
-    // 二级树
-    expand(expanded,record){
-        if(this.url.listTwo){
-            var param = {}
-            param.field = this.getQueryField();
-            param.pageNo = this.ipagination.current;
-            param.pageSize = this.ipagination.pageSize;
-            param.parentId = record.id
-            if(expanded){
-                getAction(this.url.listTwo, filterObj(param)).then((res) => {
-                    if (res.success) {
-                        record.innerData=res.result.records.map((item,index)=>{
-                            return {
-                                ...item,
-                                key:index
-                            }
-                        })
-                        console.log(record.innerData)
-                        record.innerData = transformTozTreeFormat( record.innerData)
-                    }
-                    if (res.code === 510) {
-                      this.$message.warning(res.message)
-                    }
-                    this.loading = false;
-                })
-            }else{
-                record.innerData = []
-            }
-        }
+  computed:{
+    scroll:function(){
+      var width = window.innerWidth;
+      //ant-table
+      let $antTable = window.document.getElementsByClassName("ant-table");
+      if ($antTable[0]){
+        width = $antTable[0].clientWidth;
+      }
+      console.log("$antTable",$antTable)
+      return {
+          // x:'max-content',
+          x:width,
+          y:window.innerHeight/2,
+      }
+    },
+    innerHeight:function(){
+      var innerHeight = window.innerHeight;
+      return  innerHeight;
     },
-    async loadData(arg) {
-      this.dataSource = []
-      if (!this.url.list) {
+  },
+  created() {
+      if(!this.disableMixinCreated){
+        console.log(' -- mixin created -- ')
+        this.loadData();
+        //初始化字典配置 在自己页面定义
+        this.initDictConfig();
+      }
+  },
+  methods:{
+    loadData(arg) {
+      if(!this.url.list){
         this.$message.error("请设置url.list属性!")
         return
       }
@@ -103,61 +91,45 @@ export const JeecgListMixin = {
         this.ipagination.current = 1;
       }
       var params = this.getQueryParams();//查询条件
-      if(params.createTime){
-        params.createTime = moment(params.createTime).format('YYYY-MM-DD')
-      }
       this.loading = true;
-      await getAction(this.url.list, params).then((res) => {
+      getAction(this.url.list, params).then((res) => {
         if (res.success) {
-            this.dataSource = res.result.records.map(item=>{
-                return {
-                    ...item,
-                    innerData:[],
-                    showVideo:false
-                }
-            })
-            this.ipagination.total = res.result.total;
+          this.dataSource = res.result.records;
+          this.ipagination.total = res.result.total;
         }
-        if (res.code === 510) {
+        if(res.code===510){
           this.$message.warning(res.message)
         }
         this.loading = false;
       })
     },
-    initDictConfig() {
-    //   console.log("--这是一个假的方法!")
+    initDictConfig(){
+      console.log("--这是一个假的方法!")
     },
-    handleSuperQuery(arg) {
+    handleSuperQuery(params, matchType) {
       //高级查询方法
-      if (!arg) {
-        this.superQueryParams = ''
+      if(!params){
+        this.superQueryParams=''
         this.superQueryFlag = false
-      } else {
+      }else{
         this.superQueryFlag = true
-        this.superQueryParams = JSON.stringify(arg)
+        this.superQueryParams=JSON.stringify(params)
+        this.superQueryMatchType = matchType
       }
-      this.loadData()
-    },
-    editDate(value) {
-      let date = new Date(value)
-      let y = date.getFullYear()
-      let MM = date.getMonth() + 1
-      MM = MM < 10 ? '0' + MM : MM
-      let d = date.getDate()
-      d = d < 10 ? '0' + d : d
-      return y + '-' + MM + '-' + d
+      this.loadData(1)
     },
     getQueryParams() {
       //获取查询条件
       let sqp = {}
-      if (this.superQueryParams) {
-        sqp['superQueryParams'] = encodeURI(this.superQueryParams)
+      if(this.superQueryParams){
+        sqp['superQueryParams']=encodeURI(this.superQueryParams)
+        sqp['superQueryMatchType'] = this.superQueryMatchType
       }
-      console.log(this.queryParam)
-      var param = {...this.queryParam,...this.isorter, ...this.filters}
+      var param = Object.assign(sqp, this.queryParam, this.isorter ,this.filters);
+      param.field = this.getQueryField();
       param.pageNo = this.ipagination.current;
       param.pageSize = this.ipagination.pageSize;
-      return param
+      return filterObj(param);
     },
     getQueryField() {
       //TODO 字段权限控制
@@ -187,7 +159,7 @@ export const JeecgListMixin = {
       this.loadData(1);
     },
     batchDel: function () {
-      if (!this.url.deleteBatch) {
+      if(!this.url.deleteBatch){
         this.$message.error("请设置url.deleteBatch属性!")
         return
       }
@@ -204,6 +176,7 @@ export const JeecgListMixin = {
           title: "确认删除",
           content: "是否删除选中数据?",
           onOk: function () {
+            that.loading = true;
             deleteAction(that.url.deleteBatch, {ids: ids}).then((res) => {
               if (res.success) {
                 that.$message.success(res.message);
@@ -212,13 +185,15 @@ export const JeecgListMixin = {
               } else {
                 that.$message.warning(res.message);
               }
+            }).finally(() => {
+              that.loading = false;
             });
           }
         });
       }
     },
     handleDelete: function (id) {
-      if (!this.url.delete) {
+      if(!this.url.delete){
         this.$message.error("请设置url.delete属性!")
         return
       }
@@ -232,30 +207,16 @@ export const JeecgListMixin = {
         }
       });
     },
-    handleEdit: function (record,sign) {
-        console.log(record,sign)
-      this.$refs.modalForm.edit(record,sign);
+    handleEdit: function (record) {
+      this.$refs.modalForm.edit(record);
       this.$refs.modalForm.title = "编辑";
       this.$refs.modalForm.disableSubmit = false;
-      if(sign == 'preview'){
-        this.$refs.modalForm.title = "预览";
-      }
-    },
-    handleImagePreview: function (record) {
-        this.$refs.modalForm.imagePreview(record);
-        this.$refs.modalForm.title = "预览";
-        this.$refs.modalForm.disableSubmit = false;
     },
-    handleAdd: function (sign) {
-      this.$refs.modalForm.add(sign);
+    handleAdd: function () {
+      this.$refs.modalForm.add();
       this.$refs.modalForm.title = "新增";
       this.$refs.modalForm.disableSubmit = false;
     },
-    handleOpen: function () {
-      this.$refs.userForm.open();
-      this.$refs.userForm.title = "选择";
-      this.$refs.userForm.disableSubmit = false;
-    },
     handleTableChange(pagination, filters, sorter) {
       //分页、排序、筛选变化时触发
       //TODO 筛选
@@ -266,46 +227,50 @@ export const JeecgListMixin = {
       this.ipagination = pagination;
       this.loadData();
     },
-    handleToggleSearch() {
+    handleToggleSearch(){
       this.toggleSearchStatus = !this.toggleSearchStatus;
     },
+    // 给popup查询使用(查询区域不支持回填多个字段,限制只返回一个字段)
+    getPopupField(fields){
+      return fields.split(',')[0]
+    },
     modalFormOk() {
       // 新增/修改 成功时,重载列表
       this.loadData();
     },
-    handleDetail: function (record) {
+    handleDetail:function(record){
       this.$refs.modalForm.edit(record);
-      this.$refs.modalForm.title = "详情";
+      this.$refs.modalForm.title="详情";
       this.$refs.modalForm.disableSubmit = true;
     },
     /* 导出 */
-    handleExportXls2() {
+    handleExportXls2(){
       let paramsStr = encodeURI(JSON.stringify(this.getQueryParams()));
       let url = `${window._CONFIG['domianURL']}/${this.url.exportXlsUrl}?paramsStr=${paramsStr}`;
       window.location.href = url;
     },
-    handleExportXls(fileName) {
-      if (!fileName || typeof fileName != "string") {
+    handleExportXls(fileName){
+      if(!fileName || typeof fileName != "string"){
         fileName = "导出文件"
       }
       let param = {...this.queryParam};
-      if (this.selectedRowKeys && this.selectedRowKeys.length > 0) {
+      if(this.selectedRowKeys && this.selectedRowKeys.length>0){
         param['selections'] = this.selectedRowKeys.join(",")
       }
-      console.log("导出参数", param)
-      downFile(this.url.exportXlsUrl, param).then((data) => {
+      console.log("导出参数",param)
+      downFile(this.url.exportXlsUrl,param).then((data)=>{
         if (!data) {
           this.$message.warning("文件下载失败")
           return
         }
         if (typeof window.navigator.msSaveBlob !== 'undefined') {
-          window.navigator.msSaveBlob(new Blob([data]), fileName + '.xls')
-        } else {
-          let url = window.URL.createObjectURL(new Blob([data]))
+          window.navigator.msSaveBlob(new Blob([data],{type: 'application/vnd.ms-excel'}), fileName+'.xls')
+        }else{
+          let url = window.URL.createObjectURL(new Blob([data],{type: 'application/vnd.ms-excel'}))
           let link = document.createElement('a')
           link.style.display = 'none'
           link.href = url
-          link.setAttribute('download', fileName + '.xls')
+          link.setAttribute('download', fileName+'.xls')
           document.body.appendChild(link)
           link.click()
           document.body.removeChild(link); //下载完成移除元素
@@ -314,14 +279,29 @@ export const JeecgListMixin = {
       })
     },
     /* 导入 */
-    handleImportExcel(info) {
+    handleImportExcel(info){
       if (info.file.status !== 'uploading') {
         console.log(info.file, info.fileList);
       }
       if (info.file.status === 'done') {
         if (info.file.response.success) {
-          this.$message.success(`${info.file.name} 文件上传成功`);
-          this.loadData();
+          // this.$message.success(`${info.file.name} 文件上传成功`);
+          if (info.file.response.code === 201) {
+            let { message, result: { msg, fileUrl, fileName } } = info.file.response
+            let href = window._CONFIG['domianURL'] + fileUrl
+            this.$warning({
+              title: message,
+              content: (
+                `<div>
+                  <span>{msg}</span><br/>
+                  <span>具体详情请 <a href={href} target="_blank" download={fileName}>点击下载</a> </span>
+                </div>`
+              )
+            })
+          } else {
+            this.$message.success(info.file.response.message || `${info.file.name} 文件上传成功`)
+          }
+          this.loadData()
         } else {
           this.$message.error(`${info.file.name} ${info.file.response.message}.`);
         }
@@ -330,22 +310,23 @@ export const JeecgListMixin = {
       }
     },
     /* 图片预览 */
-    getImgView(text) {
-      if (text && text.indexOf(",") > 0) {
-        text = text.substring(0, text.indexOf(","))
+    getImgView(text){
+      if(text && text.indexOf(",")>0){
+        text = text.substring(0,text.indexOf(","))
       }
-      return window._CONFIG['imgDomainURL'] + "/" + text
+      return getFileAccessHttpUrl(text)
     },
     /* 文件下载 */
-    uploadFile(text) {
-      if (!text) {
+    uploadFile(text){
+      if(!text){
         this.$message.warning("未知的文件")
         return;
       }
-      if (text.indexOf(",") > 0) {
-        text = text.substring(0, text.indexOf(","))
+      if(text.indexOf(",")>0){
+        text = text.substring(0,text.indexOf(","))
       }
-      window.open(window._CONFIG['domianURL'] + "/sys/common/download/" + text);
+      let url = getFileAccessHttpUrl(text)
+      window.open(url);
     },
   }
 

+ 26 - 0
src/mixins/OnlineCommonUtil.js

@@ -0,0 +1,26 @@
+import { formatDate } from '@/utils/util'
+import Area from '@/components/_util/Area'
+
+const onlUtil = {
+  data(){
+    return {
+      mixin_pca:''
+    }
+  },
+  created(){
+    this.mixin_pca = new Area()
+  },
+  methods:{
+    simpleDateFormat(millisecond, format){
+      return formatDate(millisecond, format)
+    },
+    getPcaText(code){
+      return this.mixin_pca.getText(code);
+    },
+    getPcaCode(text){
+      return this.mixin_pca.getCode(text)
+    }
+  }
+}
+
+export  { onlUtil }

+ 33 - 35
src/permission.js

@@ -4,13 +4,12 @@ import store from './store'
 import NProgress from 'nprogress' // progress bar
 import 'nprogress/nprogress.css' // progress bar style
 import notification from 'ant-design-vue/es/notification'
-import {ACCESS_TOKEN} from '@/store/mutation-types'
-import {generateIndexRouter} from "@/utils/util"
+import { ACCESS_TOKEN,INDEX_MAIN_PAGE_PATH } from '@/store/mutation-types'
+import { generateIndexRouter } from "@/utils/util"
 
-NProgress.configure({showSpinner: false}) // NProgress Configuration
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
 
-
-const whiteList = ['/user/login', '/user/register', '/user/register-result', '/user/alteration','/user/transfer-local','/errorPage','http://192.168.0.111:8088/jeecg-boot/sys/feishu/url','/user/FStransfer'] // no redirect whitelist
+const whiteList = ['/user/login', '/user/register', '/user/register-result','/user/alteration'] // no redirect whitelist
 
 router.beforeEach((to, from, next) => {
   NProgress.start() // start progress bar
@@ -18,41 +17,40 @@ router.beforeEach((to, from, next) => {
   if (Vue.ls.get(ACCESS_TOKEN)) {
     /* has token */
     if (to.path === '/user/login') {
-      next({path: '/dashboard/workplace'})
+      next({ path: INDEX_MAIN_PAGE_PATH })
       NProgress.done()
     } else {
       if (store.getters.permissionList.length === 0) {
         store.dispatch('GetPermissionList').then(res => {
-          const menuData = res.result.menu;
-          localStorage.setItem("roleCode",res.result.roleCode)
-          console.log(res.message)
-          if (menuData === null || menuData === "" || menuData === undefined) {
-            return;
-          }
-          let constRoutes = [];
-          constRoutes = generateIndexRouter(menuData);
-          // 添加主界面路由
-          store.dispatch('UpdateAppRouter', {constRoutes}).then(() => {
-            // 根据roles权限生成可访问的路由表
-            // 动态添加可访问路由表
-            router.addRoutes(store.getters.addRouters)
-            const redirect = decodeURIComponent(from.query.redirect || to.path)
-            if (to.path === redirect) {
-              // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
-              next({...to, replace: true})
-            } else {
-              // 跳转到目的路由
-              next({path: redirect})
-            }
-          })
-        })
+              const menuData = res.result.menu;
+              console.log(res.message)
+              if (menuData === null || menuData === "" || menuData === undefined) {
+                return;
+              }
+              let constRoutes = [];
+              constRoutes = generateIndexRouter(menuData);
+              // 添加主界面路由
+              store.dispatch('UpdateAppRouter',  { constRoutes }).then(() => {
+                // 根据roles权限生成可访问的路由表
+                // 动态添加可访问路由表
+                router.addRoutes(store.getters.addRouters)
+                const redirect = decodeURIComponent(from.query.redirect || to.path)
+                if (to.path === redirect) {
+                  // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
+                  next({ ...to, replace: true })
+                } else {
+                  // 跳转到目的路由
+                  next({ path: redirect })
+                }
+              })
+            })
           .catch(() => {
-            /* notification.error({
-               message: '系统提示',
-               description: '请求用户信息失败,请重试!'
-             })*/
+           /* notification.error({
+              message: '系统提示',
+              description: '请求用户信息失败,请重试!'
+            })*/
             store.dispatch('Logout').then(() => {
-              next({path: '/user/login', query: {redirect: to.fullPath}})
+              next({ path: '/user/login', query: { redirect: to.fullPath } })
             })
           })
       } else {
@@ -64,7 +62,7 @@ router.beforeEach((to, from, next) => {
       // 在免登录白名单,直接进入
       next()
     } else {
-      next({path: '/user/login', query: {redirect: to.fullPath}})
+      next({ path: '/user/login', query: { redirect: to.fullPath } })
       NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
     }
   }

+ 10 - 15
src/store/getters.js

@@ -1,27 +1,22 @@
 import Vue from 'vue'
-import {USER_INFO} from "@/store/mutation-types"
-
+import { USER_INFO, ENHANCE_PRE } from "@/store/mutation-types"
 const getters = {
   device: state => state.app.device,
   theme: state => state.app.theme,
   color: state => state.app.color,
   token: state => state.user.token,
-  avatar: state => {
-    state.user.avatar = Vue.ls.get(USER_INFO).avatar;
-    return state.user.avatar
-  },
+  avatar: state => {state.user.avatar = Vue.ls.get(USER_INFO).avatar; return state.user.avatar},
   username: state => state.user.username,
-  nickname: state => {
-    state.user.realname = Vue.ls.get(USER_INFO).realname;
-    return state.user.realname
-  },
+  nickname: state => {state.user.realname = Vue.ls.get(USER_INFO).realname; return state.user.realname},
   welcome: state => state.user.welcome,
   permissionList: state => state.user.permissionList,
-  userInfo: state => {
-    state.user.info = Vue.ls.get(USER_INFO);
-    return state.user.info
-  },
-  addRouters: state => state.permission.addRouters
+  userInfo: state => {state.user.info = Vue.ls.get(USER_INFO); return state.user.info},
+  addRouters: state => state.permission.addRouters,
+  enhanceJs:(state) => (code) => {
+    state.enhance.enhanceJs[code] = Vue.ls.get(ENHANCE_PRE+code);
+    return state.enhance.enhanceJs[code]
+  }
+
 }
 
 export default getters

+ 12 - 4
src/store/index.js

@@ -4,6 +4,7 @@ import Vuex from 'vuex'
 import app from './modules/app'
 import user from './modules/user'
 import permission from './modules/permission'
+import enhance from './modules/enhance'
 import getters from './getters'
 
 Vue.use(Vuex)
@@ -12,10 +13,17 @@ export default new Vuex.Store({
   modules: {
     app,
     user,
-    permission
+    permission,
+    enhance
+  },
+  state: {
+
+  },
+  mutations: {
+
+  },
+  actions: {
+
   },
-  state: {},
-  mutations: {},
-  actions: {},
   getters
 })

+ 37 - 0
src/store/modules/enhance.js

@@ -0,0 +1,37 @@
+import Vue from 'vue'
+const enhance = {
+  state: {
+    enhanceJs:{
+
+    }
+  },
+  mutations: {
+    ADD_TABLE_ENHANCE: (state, record) => {
+      if(!state.enhanceJs){
+        let obj = {}
+        let arr = []
+        arr.push({...record})
+        obj[record.code] = arr
+        state.enhanceJs = obj
+      }else{
+        if(!state.enhanceJs[record.code]){
+          let arr = []
+          arr.push({...record})
+          state.enhanceJs[record.code] = arr
+        }
+        state.enhanceJs[record.code].push({...record})
+      }
+      let arr = state.enhanceJs[record.code]
+      while(arr.length>16){
+        arr.shift()
+      }
+      Vue.ls.set('enhance_'+record['code'], arr)
+    }
+  },
+  actions: {
+    addEhanceRecord({ commit }, record) {
+      commit('ADD_TABLE_ENHANCE', record)
+    }
+  }
+}
+export default enhance

+ 6 - 13
src/store/modules/permission.js

@@ -1,4 +1,4 @@
-import {asyncRouterMap, constantRouterMap} from "@/config/router.config"
+import { asyncRouterMap, constantRouterMap } from "@/config/router.config"
 
 /**
  * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
@@ -22,7 +22,6 @@ function hasPermission(permission, route) {
 }
 
 /**
- * 
  * 单账户多角色时,使用该方法可过滤角色不存在的菜单
  *
  * @param roles
@@ -65,9 +64,9 @@ const permission = {
     }
   },
   actions: {
-    GenerateRoutes({commit}, data) {
+    GenerateRoutes({ commit }, data) {
       return new Promise(resolve => {
-        const {roles} = data
+        const { roles } = data
         console.log('-----mutations---data----', data)
         let accessedRouters
         accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
@@ -77,18 +76,12 @@ const permission = {
       })
     },
     // 动态添加主界面路由,需要缓存
-    UpdateAppRouter({commit}, routes) {
-      return new Promise((resolve,reject) => {
+    UpdateAppRouter({ commit }, routes) {
+      return new Promise(resolve => {
         //const [ roles ] = routes.constRoutes
         let routelist = routes.constRoutes;
-        console.log(routelist,98471293847219)
         commit('SET_ROUTERS', routelist)
-        if(routelist){
-            resolve()
-        }else{
-            reject()
-        }
-        
+        resolve()
       })
     }
   }

+ 95 - 38
src/store/modules/user.js

@@ -1,8 +1,9 @@
 import Vue from 'vue'
-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 { login, logout, phoneLogin, thirdLogin } from "@/api/login"
+import { ACCESS_TOKEN, USER_NAME,USER_INFO,USER_AUTH,SYS_BUTTON_AUTH,UI_CACHE_DB_DICT_DATA } from "@/store/mutation-types"
+import { welcome } from "@/utils/util"
+import { queryPermissionsByUser } from '@/api/api'
+import { getAction } from '@/api/manage'
 
 const user = {
   state: {
@@ -11,7 +12,6 @@ const user = {
     realname: '',
     welcome: '',
     avatar: '',
-    orgcode : '',
     permissionList: [],
     info: {}
   },
@@ -20,7 +20,7 @@ const user = {
     SET_TOKEN: (state, token) => {
       state.token = token
     },
-    SET_NAME: (state, {username, realname, welcome}) => {
+    SET_NAME: (state, { username, realname, welcome }) => {
       state.username = username
       state.realname = realname
       state.welcome = welcome
@@ -37,11 +37,12 @@ const user = {
   },
 
   actions: {
-    // 登录
-    Login({commit}, userInfo) {
+    // CAS验证登录
+    ValidateLogin({ commit }, userInfo) {
       return new Promise((resolve, reject) => {
-        login(userInfo).then(response => {
-          if (response.code == '200') {
+        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)
@@ -49,61 +50,91 @@ const user = {
             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)           
+            commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname, welcome: welcome() })
+            commit('SET_AVATAR', userInfo.avatar)
+            resolve(response)
+          }else{
             resolve(response)
-          } else {
-            reject(response)
           }
+        }).catch(error => {
+          reject(error)
         })
       })
     },
-    //企业微信登录
-    wxLogin({commit}, userInfos) {
-        const result = userInfos.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)
-      },
-    //手机号登录
-    PhoneLogin({commit}, userInfo) {
+    // 登录
+    Login({ commit }, userInfo) {
       return new Promise((resolve, reject) => {
-        phoneLogin(userInfo).then(response => {
-          if (response.code == '200') {
+        login(userInfo).then(response => {
+          if(response.code =='200'){
             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)
+            Vue.ls.set(UI_CACHE_DB_DICT_DATA, result.sysAllDictItems, 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_NAME', { username: userInfo.username,realname: userInfo.realname, welcome: welcome() })
             commit('SET_AVATAR', userInfo.avatar)
             resolve(response)
-          } else {
+          }else{
             reject(response)
           }
+        }).catch(error => {
+          reject(error)
         })
       })
     },
+    //手机号登录
+    PhoneLogin({ commit }, userInfo) {
+      return new Promise((resolve, reject) => {
+          phoneLogin(userInfo).then(response => {
+          if(response.code =='200'){
+        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)
+        Vue.ls.set(UI_CACHE_DB_DICT_DATA, result.sysAllDictItems, 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{
+        reject(response)
+      }
+    }).catch(error => {
+        reject(error)
+      })
+    })
+    },
     // 获取用户信息
-    GetPermissionList({commit}) {
+    GetPermissionList({ commit }) {
       return new Promise((resolve, reject) => {
         let v_token = Vue.ls.get(ACCESS_TOKEN);
-        let params = {token: v_token};
+        let params = {token:v_token};
         queryPermissionsByUser(params).then(response => {
           const menuData = response.result.menu;
           const authData = response.result.auth;
           const allAuthData = response.result.allAuth;
           //Vue.ls.set(USER_AUTH,authData);
-          sessionStorage.setItem(USER_AUTH, JSON.stringify(authData));
-          sessionStorage.setItem(SYS_BUTTON_AUTH, JSON.stringify(allAuthData));
+          sessionStorage.setItem(USER_AUTH,JSON.stringify(authData));
+          sessionStorage.setItem(SYS_BUTTON_AUTH,JSON.stringify(allAuthData));
           if (menuData && menuData.length > 0) {
+            //update--begin--autor:qinfeng-----date:20200109------for:JEECG-63 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
+            menuData.forEach((item, index) => {
+              if (item["children"]) {
+                let hasChildrenMenu = item["children"].filter((i) => {
+                  return !i.hidden || i.hidden == false
+                })
+                if (hasChildrenMenu == null || hasChildrenMenu.length == 0) {
+                  item["hidden"] = true
+                }
+              }
+            })
+            console.log(" menu show json ", menuData)
+            //update--end--autor:qinfeng-----date:20200109------for:JEECG-63 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
             commit('SET_PERMISSIONLIST', menuData)
           } else {
             reject('getPermissionList: permissions must be a non-null array !')
@@ -116,21 +147,47 @@ const user = {
     },
 
     // 登出
-    Logout({commit, state}) {
+    Logout({ commit, state }) {
       return new Promise((resolve) => {
         let logoutToken = state.token;
         commit('SET_TOKEN', '')
         commit('SET_PERMISSIONLIST', [])
         Vue.ls.remove(ACCESS_TOKEN)
-        localStorage.removeItem("roleCode")
+        Vue.ls.remove(UI_CACHE_DB_DICT_DATA)
         //console.log('logoutToken: '+ logoutToken)
         logout(logoutToken).then(() => {
+          //let sevice = "http://"+window.location.host+"/";
+          //let serviceUrl = encodeURIComponent(sevice);
+          //window.location.href = window._CONFIG['casPrefixUrl']+"/logout?service="+serviceUrl;
           resolve()
         }).catch(() => {
           resolve()
         })
       })
     },
+    // 第三方登录
+    ThirdLogin({ commit }, token) {
+      return new Promise((resolve, reject) => {
+        thirdLogin(token).then(response => {
+          if(response.code =='200'){
+            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{
+            reject(response)
+          }
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
 
   }
 }

+ 5 - 1
src/store/mutation-types.js

@@ -5,7 +5,7 @@ export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
 export const DEFAULT_COLOR = 'DEFAULT_COLOR'
 export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK'
 export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
-export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
+export const DEFAULT_FIXED_SIDEMENU= 'DEFAULT_FIXED_SIDEMENU'
 export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
 export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
 export const DEFAULT_MULTI_PAGE = 'DEFAULT_MULTI_PAGE'
@@ -13,6 +13,10 @@ export const USER_NAME = 'Login_Username'
 export const USER_INFO = 'Login_Userinfo'
 export const USER_AUTH = 'LOGIN_USER_BUTTON_AUTH'
 export const SYS_BUTTON_AUTH = 'SYS_BUTTON_AUTH'
+export const ENCRYPTED_STRING = 'ENCRYPTED_STRING'
+export const ENHANCE_PRE = 'enhance_'
+export const UI_CACHE_DB_DICT_DATA = 'UI_CACHE_DB_DICT_DATA'
+export const INDEX_MAIN_PAGE_PATH =  "/dashboard/analysis"
 
 export const CONTENT_WIDTH_TYPE = {
   Fluid: 'Fluid',

+ 20 - 7
src/utils/JEditableTableUtil.js

@@ -7,11 +7,20 @@ const FormTypes = {
   date: 'date',
   datetime: 'datetime',
   upload: 'upload',
+  file: 'file',
+  image: 'image',
+  popup:'popup',
+  list_multi:"list_multi",
+  sel_search:"sel_search",
+  radio:'radio',
+  checkbox_meta:"checkbox_meta",
+  input_pop:'input_pop',
+
   slot: 'slot',
   hidden: 'hidden'
 }
 const VALIDATE_NO_PASSED = Symbol()
-export {FormTypes, VALIDATE_NO_PASSED}
+export { FormTypes, VALIDATE_NO_PASSED }
 
 /**
  * 获取指定的 $refs 对象
@@ -51,14 +60,14 @@ export function validateFormAndTables(form, cases) {
   return new Promise((resolve, reject) => {
     // 验证主表表单
     form.validateFields((err, values) => {
-      err ? reject({error: VALIDATE_NO_PASSED}) : resolve(values)
+      err ? reject({ error: VALIDATE_NO_PASSED }) : resolve(values)
     })
   }).then(values => {
-    Object.assign(options, {formValue: values})
+    Object.assign(options, { formValue: values })
     // 验证所有子表的表单
     return validateTables(cases)
   }).then(all => {
-    Object.assign(options, {tablesValue: all})
+    Object.assign(options, { tablesValue: all })
     return Promise.resolve(options)
   }).catch(error => {
     return Promise.reject(error)
@@ -69,18 +78,22 @@ export function validateFormAndTables(form, cases) {
 /**
  * 验证并获取一个或多个表格的所有值
  * @param cases 接收一个数组,每项都是一个JEditableTable实例
+ * @param deleteTempId 是否删除临时ID,如果设为true,行编辑就不返回新增行的ID,ID需要后台生成
  * @author sunjianlei
  */
-export function validateTables(cases) {
+export function validateTables(cases, deleteTempId) {
   if (!(cases instanceof Array)) {
     throw `'validateTables'函数的'cases'参数需要的是一个数组,而传入的却是${typeof cases}`
   }
   return new Promise((resolve, reject) => {
     let tables = []
     let index = 0;
+    if(!cases || cases.length==0){
+      resolve()
+    }
     (function next() {
       let vm = cases[index]
-      vm.getAll(true).then(all => {
+      vm.getAll(true, deleteTempId).then(all => {
         tables[index] = all
         // 判断校验是否全部完成,完成返回成功,否则继续进行下一步校验
         if (++index === cases.length) {
@@ -91,7 +104,7 @@ export function validateTables(cases) {
       }, error => {
         // 出现未验证通过的表单,不再进行下一步校验,直接返回失败并跳转到该表格
         if (error === VALIDATE_NO_PASSED) {
-          reject({error: VALIDATE_NO_PASSED, index})
+          reject({ error: VALIDATE_NO_PASSED, index })
         }
         reject(error)
       })

+ 0 - 0
src/utils/commonUploadFile.js


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff