瀏覽代碼

前端代码初始化

syh 6 年之前
父節點
當前提交
15ce0123f6
共有 100 個文件被更改,包括 22398 次插入27 次删除
  1. 39 0
      .editorconfig
  2. 1 0
      .eslintignore
  3. 1 0
      .gitattributes
  4. 21 0
      .gitignore
  5. 5 0
      .prettierrc
  6. 21 0
      LICENSE
  7. 95 27
      README.md
  8. 5 0
      babel.config.js
  9. 24 0
      idea.config.js
  10. 112 0
      package.json
  11. 二進制
      public/avatar2.jpg
  12. 9055 0
      public/color.less
  13. 二進制
      public/goright.png
  14. 300 0
      public/index.html
  15. 二進制
      public/logo.png
  16. 261 0
      public/tinymce/langs/zh_CN.js
  17. 554 0
      public/tinymce/skins/lightgray/content.min.css
  18. 2159 0
      public/tinymce/skins/lightgray/skin.min.css
  19. 1 0
      public/v2.js
  20. 43 0
      src/App.vue
  21. 147 0
      src/api/api.js
  22. 10 0
      src/api/index.js
  23. 58 0
      src/api/login.js
  24. 114 0
      src/api/manage.js
  25. 121 0
      src/assets/background.svg
  26. 54 0
      src/assets/less/common.less
  27. 34 0
      src/assets/less/index.less
  28. 二進制
      src/assets/logo.png
  29. 174 0
      src/assets/logo.svg
  30. 二進制
      src/assets/pdf4.jpg
  31. 46 0
      src/components/AvatarList/Item.vue
  32. 118 0
      src/components/AvatarList/List.vue
  33. 4 0
      src/components/AvatarList/index.js
  34. 60 0
      src/components/AvatarList/index.less
  35. 111 0
      src/components/ChartCard.vue
  36. 103 0
      src/components/CountDown/CountDown.vue
  37. 3 0
      src/components/CountDown/index.js
  38. 71 0
      src/components/Ellipsis/Ellipsis.vue
  39. 3 0
      src/components/Ellipsis/index.js
  40. 54 0
      src/components/NumberInfo/NumberInfo.vue
  41. 3 0
      src/components/NumberInfo/index.js
  42. 58 0
      src/components/NumberInfo/index.less
  43. 41 0
      src/components/README.md
  44. 41 0
      src/components/Trend/Trend.vue
  45. 3 0
      src/components/Trend/index.js
  46. 44 0
      src/components/Trend/index.less
  47. 35 0
      src/components/_util/StringUtil.js
  48. 12 0
      src/components/_util/util.js
  49. 35 0
      src/components/bpm/DynamicComponent.vue
  50. 73 0
      src/components/bpm/FormCommonModal.vue
  51. 121 0
      src/components/bpm/ProcNodeInfoModel.vue
  52. 137 0
      src/components/bpm/ProcessInstPicModal.vue
  53. 88 0
      src/components/chart/AreaChartTy.vue
  54. 50 0
      src/components/chart/Bar.vue
  55. 57 0
      src/components/chart/BarAndLine.vue
  56. 90 0
      src/components/chart/BarMultid.vue
  57. 187 0
      src/components/chart/DashChartDemo.vue
  58. 77 0
      src/components/chart/LineChartMultid.vue
  59. 80 0
      src/components/chart/Liquid.vue
  60. 69 0
      src/components/chart/MiniArea.vue
  61. 76 0
      src/components/chart/MiniBar.vue
  62. 76 0
      src/components/chart/MiniProgress.vue
  63. 67 0
      src/components/chart/Pie.vue
  64. 367 0
      src/components/chart/README.md
  65. 90 0
      src/components/chart/Radar.vue
  66. 83 0
      src/components/chart/RankList.vue
  67. 66 0
      src/components/chart/TransferBar.vue
  68. 85 0
      src/components/chart/Trend.vue
  69. 13 0
      src/components/chart/chart.scss
  70. 81 0
      src/components/dict/JDictSelectTag.vue
  71. 87 0
      src/components/dict/JDictSelectUtil.js
  72. 96 0
      src/components/dict/JMultiSelectTag.vue
  73. 175 0
      src/components/dict/JSearchSelectTag.vue
  74. 180 0
      src/components/dict/README.md
  75. 8 0
      src/components/dict/index.js
  76. 4 0
      src/components/index.less
  77. 43 0
      src/components/jeecg/JCheckbox.vue
  78. 416 0
      src/components/jeecg/JCodeEditor.vue
  79. 86 0
      src/components/jeecg/JDate.vue
  80. 1669 0
      src/components/jeecg/JEditableTable.vue
  81. 105 0
      src/components/jeecg/JEditor.vue
  82. 29 0
      src/components/jeecg/JEllipsis.vue
  83. 50 0
      src/components/jeecg/JFormContainer.vue
  84. 163 0
      src/components/jeecg/JGraphicCode.vue
  85. 111 0
      src/components/jeecg/JImportModal.vue
  86. 65 0
      src/components/jeecg/JSelectMultiple.vue
  87. 120 0
      src/components/jeecg/JSlider.vue
  88. 157 0
      src/components/jeecg/JSuperQuery.vue
  89. 195 0
      src/components/jeecg/JTreeDict.vue
  90. 206 0
      src/components/jeecg/JTreeSelect.vue
  91. 140 0
      src/components/jeecg/JTreeTable.vue
  92. 174 0
      src/components/jeecg/JUpload.vue
  93. 519 0
      src/components/jeecg/README.md
  94. 517 0
      src/components/jeecg/README_JEditableTable.md
  95. 6 0
      src/components/jeecg/index.js
  96. 309 0
      src/components/jeecgbiz/JSearchUserByDep.vue
  97. 122 0
      src/components/jeecgbiz/JSelectDepart.vue
  98. 66 0
      src/components/jeecgbiz/JSelectMultiUser.vue
  99. 293 0
      src/components/jeecgbiz/JSelectMultipleUser.vue
  100. 0 0
      src/components/jeecgbiz/JSelectUser.vue

+ 39 - 0
.editorconfig

@@ -0,0 +1,39 @@
+[*]
+charset = utf-8
+end_of_line = crlf
+insert_final_newline = false
+indent_style = space
+indent_size = 2
+
+[{*.ng, *.sht, *.html, *.shtm, *.shtml, *.htm}]
+indent_style = space
+indent_size = 2
+
+[{*.jhm, *.xslt, *.xul, *.rng, *.xsl, *.xsd, *.ant, *.tld, *.fxml, *.jrxml, *.xml, *.jnlp, *.wsdl}]
+indent_style = space
+indent_size = 2
+
+[{.babelrc, .stylelintrc, jest.config, .eslintrc, .prettierrc, *.json, *.jsb3, *.jsb2, *.bowerrc}]
+indent_style = space
+indent_size = 2
+
+[*.svg]
+indent_style = space
+indent_size = 2
+
+[*.js.map]
+indent_style = space
+indent_size = 2
+
+[*.less]
+indent_style = space
+indent_size = 2
+
+[*.vue]
+indent_style = space
+indent_size = 2
+
+[{.analysis_options, *.yml, *.yaml}]
+indent_style = space
+indent_size = 2
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+/src

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+public/* linguist-vendored

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*

+ 5 - 0
.prettierrc

@@ -0,0 +1,5 @@
+{
+  "printWidth": 120,
+  "semi": false,
+  "singleQuote": true
+}

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 DaiHao Zhang
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 95 - 27
README.md

@@ -1,39 +1,107 @@
-# 广告平台前端
+Ant Design Jeecg Vue
+====
 
-#### 介绍
-{**以下是码云平台说明,您可以替换此简介**
-码云是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
-无论是个人、团队、或是企业,都能够用码云实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
 
-#### 软件架构
-软件架构说明
+当前最新版本: 2.0.2(发布日期:20190708)
 
+Overview
+----
 
-#### 安装教程
+基于 [Ant Design of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 实现的 Ant Design Pro  Vue 版
+Jeecg-boot 的前段UI框架,采用前后端分离方案,提供强大代码生成器的快速开发平台。
+前端页面代码和后端功能代码一键生成,不需要写任何代码,保持jeecg一贯的强大!!
 
-1. xxxx
-2. xxxx
-3. xxxx
 
-#### 使用说明
 
-1. xxxx
-2. xxxx
-3. xxxx
+#### 前端技术
+ 
+- 基础框架:[ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
+- JavaScript框架:Vue
+- Webpack
+- node
+- yarn
+- eslint
+- @vue/cli 3.2.1
+- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
+- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
+- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation)  - antv/g2 封装实现
 
-#### 参与贡献
 
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
 
+项目下载和运行
+----
 
-#### 码云特技
+- 拉取项目代码
+```bash
+git clone https://github.com/zhangdaiscott/jeecg-boot.git
+cd  jeecg-boot/ant-design-jeecg-vue
+```
 
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
-5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+- 安装依赖
+```
+yarn install
+```
+
+- 开发模式运行
+```
+yarn run serve
+```
+
+- 编译项目
+```
+yarn run build
+```
+
+- Lints and fixes files
+```
+yarn run lint
+```
+
+
+
+其他说明
+----
+
+- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
+
+- 关闭 Eslint (不推荐) 移除 `package.json` 中 `eslintConfig` 整个节点代码
+
+- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
+```ecmascript 6
+  css: {
+    loaderOptions: {
+      less: {
+        modifyVars: {
+          /* less 变量覆盖,用于自定义 ant design 主题 */
+
+          'primary-color': '#F5222D',
+          'link-color': '#F5222D',
+          'border-radius-base': '4px',
+        },
+        javascriptEnabled: true,
+      }
+    }
+  }
+```
+
+
+
+附属文档
+----
+- [Ant Design Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn)
+
+- [报表 viser-vue](https://viserjs.github.io/demo.html#/viser/bar/basic-bar)
+
+- [Vue](https://cn.vuejs.org/v2/guide)
+
+- [路由/菜单说明](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/router/README.md)
+
+- [ANTD 默认配置项](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/defaultSettings.js)
+
+- 其他待补充...
+
+
+备注
+----
+
+> @vue/cli 升级后,eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules

+ 5 - 0
babel.config.js

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

+ 24 - 0
idea.config.js

@@ -0,0 +1,24 @@
+'use strict'
+const path = require('path')
+
+function resolve(dir) {
+  return path.join(__dirname, '.', dir)
+}
+
+module.exports = {
+  context: path.resolve(__dirname, './'),
+  resolve: {
+    extensions: ['.js', '.vue', '.json'],
+    alias: {
+      'config': resolve('config'),
+      '@': resolve('src'),
+      '@views': resolve('src/views'),
+      '@comp': resolve('src/components'),
+      '@core': resolve('src/core'),
+      '@utils': resolve('src/utils'),
+      '@entry': resolve('src/entry'),
+      '@router': resolve('src/router'),
+      '@store': resolve('src/store')
+    }
+  },
+}

+ 112 - 0
package.json

@@ -0,0 +1,112 @@
+{
+  "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"
+  },
+  "dependencies": {
+    "@antv/data-set": "^0.10.2",
+    "@tinymce/tinymce-vue": "^2.0.0",
+    "ant-design-vue": "^1.3.9",
+    "apexcharts": "^3.6.5",
+    "axios": "^0.18.0",
+    "clipboard": "^2.0.4",
+    "codemirror": "^5.46.0",
+    "dayjs": "^1.8.0",
+    "enquire.js": "^2.1.6",
+    "jquery": "^3.4.1",
+    "js-cookie": "^2.2.0",
+    "lodash.get": "^4.4.2",
+    "lodash.pick": "^4.4.0",
+    "md5": "^2.2.1",
+    "nprogress": "^0.2.0",
+    "tinymce": "^5.0.2",
+    "viser-vue": "^2.4.4",
+    "vue": "^2.6.10",
+    "vue-apexcharts": "^1.3.2",
+    "vue-class-component": "^6.0.0",
+    "vue-cropper": "^0.4.8",
+    "vue-i18n": "^8.7.0",
+    "vue-loader": "^15.7.0",
+    "vue-ls": "^3.2.0",
+    "vue-photo-preview": "^1.1.3",
+    "vue-print-nb-jeecg": "^1.0.8",
+    "vue-property-decorator": "^7.3.0",
+    "vue-router": "^3.0.1",
+    "vue-splitpane": "^1.0.4",
+    "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"
+  },
+  "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",
+    "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
+          }
+        }
+      ],
+      "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"
+  ]
+}

二進制
public/avatar2.jpg


文件差異過大導致無法顯示
+ 9055 - 0
public/color.less


二進制
public/goright.png


+ 300 - 0
public/index.html

@@ -0,0 +1,300 @@
+<!DOCTYPE html>
+<html lang="zh-cmn-Hans">
+
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width,initial-scale=1.0">
+  <title>汇创投放助手</title>
+  <link rel="icon" href="<%= BASE_URL %>logo.png">
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.js"></script>
+  <style>
+    html,
+    body,
+    #app {
+      height: 100%;
+      margin: 0px;
+      padding: 0px;
+    }
+
+    .chromeframe {
+      margin: 0.2em 0;
+      background: #ccc;
+      color: #000;
+      padding: 0.2em 0;
+    }
+
+    #loader-wrapper {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 999999;
+    }
+
+    #loader {
+      display: block;
+      position: relative;
+      left: 50%;
+      top: 50%;
+      width: 120px;
+      height: 120px;
+      margin: -75px 0 0 -75px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      /* COLOR 1 */
+      border-top-color: #FFF;
+      -webkit-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -ms-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -moz-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -o-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      animation: spin 2s linear infinite;
+      /* Chrome, Firefox 16+, IE 10+, Opera */
+      z-index: 1001;
+    }
+
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      /* COLOR 2 */
+      border-top-color: #FFF;
+      -webkit-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -moz-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -o-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -ms-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      animation: spin 3s linear infinite;
+      /* Chrome, Firefox 16+, IE 10+, Opera */
+    }
+
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      /* COLOR 3 */
+      -moz-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -o-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -ms-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -webkit-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      animation: spin 1.5s linear infinite;
+      /* Chrome, Firefox 16+, IE 10+, Opera */
+    }
+
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(0deg);
+        /* IE 9 */
+        transform: rotate(0deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(360deg);
+        /* IE 9 */
+        transform: rotate(360deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+    }
+
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(0deg);
+        /* IE 9 */
+        transform: rotate(0deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(360deg);
+        /* IE 9 */
+        transform: rotate(360deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+    }
+
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #49a9ee;
+      /* Old browsers */
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateX(0);
+      /* IE 9 */
+      transform: translateX(0);
+      /* Firefox 16+, IE 10+, Opera */
+    }
+
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+
+    /* Loaded */
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateX(-100%);
+      /* IE 9 */
+      transform: translateX(-100%);
+      /* Firefox 16+, IE 10+, Opera */
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader-wrapper .loader-section.section-right {
+      -webkit-transform: translateX(100%);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateX(100%);
+      /* IE 9 */
+      transform: translateX(100%);
+      /* Firefox 16+, IE 10+, Opera */
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader {
+      opacity: 0;
+      -webkit-transition: all 0.3s ease-out;
+      transition: all 0.3s ease-out;
+    }
+
+    .loaded #loader-wrapper {
+      visibility: hidden;
+      -webkit-transform: translateY(-100%);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateY(-100%);
+      /* IE 9 */
+      transform: translateY(-100%);
+      /* Firefox 16+, IE 10+, Opera */
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+
+    /* JavaScript Turned Off */
+    .no-js #loader-wrapper {
+      display: none;
+    }
+
+    .no-js h1 {
+      color: #222222;
+    }
+
+    #loader-wrapper .load_title {
+      font-family: 'Open Sans';
+      color: #FFF;
+      font-size: 14px;
+      width: 100%;
+      text-align: center;
+      z-index: 9999999999999;
+      position: absolute;
+      top: 60%;
+      opacity: 1;
+      line-height: 30px;
+    }
+
+    #loader-wrapper .load_title span {
+      font-weight: normal;
+      font-style: italic;
+      font-size: 14px;
+      color: #FFF;
+      opacity: 0.5;
+    }
+
+    /* 滚动条优化 start */
+    ::-webkit-scrollbar {
+      width: 8px;
+      height: 8px;
+    }
+
+    ::-webkit-scrollbar-track {
+      background: #f6f6f6;
+      border-radius: 2px;
+    }
+
+    ::-webkit-scrollbar-thumb {
+      background: #cdcdcd;
+      border-radius: 2px;
+    }
+
+    ::-webkit-scrollbar-thumb:hover {
+      background: #747474;
+    }
+
+    ::-webkit-scrollbar-corner {
+      background: #f6f6f6;
+    }
+
+    /* 滚动条优化 end */
+  </style>
+  <!-- 全局配置 -->
+  <script>
+    window._CONFIG = {};
+    window._CONFIG['domianURL'] = 'http://localhost:8080/jeecg-boot';
+    window._CONFIG['imgDomainURL'] = 'http://localhost:8080/jeecg-boot/sys/common/view';
+    window._CONFIG['pdfDomainURL'] = 'http://localhost:8080/jeecg-boot/sys/common/pdf/pdfPreviewIframe';
+  </script>
+</head>
+
+<body>
+<!-- built files will be auto injected -->
+<div id="app">
+  <div id="loader-wrapper">
+    <div id="loader"></div>
+    <div class="loader-section section-left"></div>
+    <div class="loader-section section-right"></div>
+
+    <div class="load_title">正在加载数据,请耐心等待
+
+
+    </div>
+  </div>
+</div>
+
+<!-- update_begin author:sunjianlei date:20190524 for: 去指定页面的加载动画 -->
+<script>
+  // 去指定页面的加载动画
+  if (location.href.indexOf('online/desform/pureview') !== -1) {
+    document.getElementById('loader-wrapper').style.display = 'none'
+  }
+</script>
+<!-- update_end author:sunjianlei date:20190524 for: 去指定页面的加载动画 -->
+</body>
+
+</html>

二進制
public/logo.png


+ 261 - 0
public/tinymce/langs/zh_CN.js

@@ -0,0 +1,261 @@
+tinymce.addI18n('zh_CN', {
+  "Redo": "\u91cd\u590d",
+  "Undo": "\u64a4\u6d88",
+  "Cut": "\u526a\u5207",
+  "Copy": "\u590d\u5236",
+  "Paste": "\u7c98\u8d34",
+  "Select all": "\u5168\u9009",
+  "New document": "\u65b0\u6587\u6863",
+  "Ok": "\u786e\u5b9a",
+  "Cancel": "\u53d6\u6d88",
+  "Visual aids": "\u7f51\u683c\u7ebf",
+  "Bold": "\u7c97\u4f53",
+  "Italic": "\u659c\u4f53",
+  "Underline": "\u4e0b\u5212\u7ebf",
+  "Strikethrough": "\u5220\u9664\u7ebf",
+  "Superscript": "\u4e0a\u6807",
+  "Subscript": "\u4e0b\u6807",
+  "Clear formatting": "\u6e05\u9664\u683c\u5f0f",
+  "Align left": "\u5de6\u5bf9\u9f50",
+  "Align center": "\u5c45\u4e2d",
+  "Align right": "\u53f3\u5bf9\u9f50",
+  "Justify": "\u4e24\u7aef\u5bf9\u9f50",
+  "Bullet list": "\u9879\u76ee\u7b26\u53f7",
+  "Numbered list": "\u7f16\u53f7\u5217\u8868",
+  "Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
+  "Increase indent": "\u589e\u52a0\u7f29\u8fdb",
+  "Close": "\u5173\u95ed",
+  "Formats": "\u683c\u5f0f",
+  "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u5bf9\u526a\u8d34\u677f\u7684\u8bbf\u95ee\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u952e\u8fdb\u884c\u590d\u5236\u7c98\u8d34\u3002",
+  "Headers": "\u6807\u9898",
+  "Header 1": "\u6807\u98981",
+  "Header 2": "\u6807\u98982",
+  "Header 3": "\u6807\u98983",
+  "Header 4": "\u6807\u98984",
+  "Header 5": "\u6807\u98985",
+  "Header 6": "\u6807\u98986",
+  "Headings": "\u6807\u9898",
+  "Heading 1": "\u6807\u98981",
+  "Heading 2": "\u6807\u98982",
+  "Heading 3": "\u6807\u98983",
+  "Heading 4": "\u6807\u98984",
+  "Heading 5": "\u6807\u98985",
+  "Heading 6": "\u6807\u98986",
+  "Preformatted": "\u9884\u683c\u5f0f\u5316",
+  "Div": "Div\u533a\u5757",
+  "Pre": "\u9884\u683c\u5f0f\u6587\u672c",
+  "Code": "\u4ee3\u7801",
+  "Paragraph": "\u6bb5\u843d",
+  "Blockquote": "\u5f15\u7528",
+  "Inline": "\u6587\u672c",
+  "Blocks": "\u533a\u5757",
+  "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
+  "Font Family": "\u5b57\u4f53",
+  "Font Sizes": "\u5b57\u53f7",
+  "Class": "Class",
+  "Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
+  "OR": "\u6216",
+  "Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
+  "Upload": "\u4e0a\u4f20",
+  "Block": "\u5757",
+  "Align": "\u5bf9\u9f50",
+  "Default": "\u9ed8\u8ba4",
+  "Circle": "\u7a7a\u5fc3\u5706",
+  "Disc": "\u5b9e\u5fc3\u5706",
+  "Square": "\u65b9\u5757",
+  "Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
+  "Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
+  "Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
+  "Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
+  "Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
+  "Anchor": "\u951a\u70b9",
+  "Name": "\u540d\u79f0",
+  "Id": "\u6807\u8bc6\u7b26",
+  "Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
+  "You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
+  "Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
+  "Special character": "\u7279\u6b8a\u7b26\u53f7",
+  "Source code": "\u6e90\u4ee3\u7801",
+  "Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
+  "Language": "\u8bed\u8a00",
+  "Code sample": "\u4ee3\u7801\u793a\u4f8b",
+  "Color": "\u989c\u8272",
+  "R": "R",
+  "G": "G",
+  "B": "B",
+  "Left to right": "\u4ece\u5de6\u5230\u53f3",
+  "Right to left": "\u4ece\u53f3\u5230\u5de6",
+  "Emoticons": "\u8868\u60c5",
+  "Document properties": "\u6587\u6863\u5c5e\u6027",
+  "Title": "\u6807\u9898",
+  "Keywords": "\u5173\u952e\u8bcd",
+  "Description": "\u63cf\u8ff0",
+  "Robots": "\u673a\u5668\u4eba",
+  "Author": "\u4f5c\u8005",
+  "Encoding": "\u7f16\u7801",
+  "Fullscreen": "\u5168\u5c4f",
+  "Action": "\u64cd\u4f5c",
+  "Shortcut": "\u5feb\u6377\u952e",
+  "Help": "\u5e2e\u52a9",
+  "Address": "\u5730\u5740",
+  "Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
+  "Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
+  "Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
+  "Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
+  "Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+  "Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+  "Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+  "Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
+  "Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
+  "Learn more...": "\u4e86\u89e3\u66f4\u591a...",
+  "You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
+  "Plugins": "\u63d2\u4ef6",
+  "Handy Shortcuts": "\u5feb\u6377\u952e",
+  "Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
+  "Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
+  "Image description": "\u56fe\u7247\u63cf\u8ff0",
+  "Source": "\u5730\u5740",
+  "Dimensions": "\u5927\u5c0f",
+  "Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
+  "General": "\u666e\u901a",
+  "Advanced": "\u9ad8\u7ea7",
+  "Style": "\u6837\u5f0f",
+  "Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
+  "Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
+  "Border": "\u8fb9\u6846",
+  "Insert image": "\u63d2\u5165\u56fe\u7247",
+  "Image": "\u56fe\u7247",
+  "Image list": "\u56fe\u7247\u5217\u8868",
+  "Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
+  "Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
+  "Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
+  "Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
+  "Edit image": "\u7f16\u8f91\u56fe\u7247",
+  "Image options": "\u56fe\u7247\u9009\u9879",
+  "Zoom in": "\u653e\u5927",
+  "Zoom out": "\u7f29\u5c0f",
+  "Crop": "\u88c1\u526a",
+  "Resize": "\u8c03\u6574\u5927\u5c0f",
+  "Orientation": "\u65b9\u5411",
+  "Brightness": "\u4eae\u5ea6",
+  "Sharpen": "\u9510\u5316",
+  "Contrast": "\u5bf9\u6bd4\u5ea6",
+  "Color levels": "\u989c\u8272\u5c42\u6b21",
+  "Gamma": "\u4f3d\u9a6c\u503c",
+  "Invert": "\u53cd\u8f6c",
+  "Apply": "\u5e94\u7528",
+  "Back": "\u540e\u9000",
+  "Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
+  "Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
+  "Insert link": "\u63d2\u5165\u94fe\u63a5",
+  "Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
+  "Text to display": "\u663e\u793a\u6587\u5b57",
+  "Url": "\u5730\u5740",
+  "Target": "\u6253\u5f00\u65b9\u5f0f",
+  "None": "\u65e0",
+  "New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
+  "Remove link": "\u5220\u9664\u94fe\u63a5",
+  "Anchors": "\u951a\u70b9",
+  "Link": "\u94fe\u63a5",
+  "Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
+  "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
+  "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
+  "Link list": "\u94fe\u63a5\u5217\u8868",
+  "Insert video": "\u63d2\u5165\u89c6\u9891",
+  "Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
+  "Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
+  "Alternative source": "\u955c\u50cf",
+  "Poster": "\u5c01\u9762",
+  "Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
+  "Embed": "\u5185\u5d4c",
+  "Media": "\u5a92\u4f53",
+  "Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
+  "Page break": "\u5206\u9875\u7b26",
+  "Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
+  "Preview": "\u9884\u89c8",
+  "Print": "\u6253\u5370",
+  "Save": "\u4fdd\u5b58",
+  "Find": "\u67e5\u627e",
+  "Replace with": "\u66ff\u6362\u4e3a",
+  "Replace": "\u66ff\u6362",
+  "Replace all": "\u5168\u90e8\u66ff\u6362",
+  "Prev": "\u4e0a\u4e00\u4e2a",
+  "Next": "\u4e0b\u4e00\u4e2a",
+  "Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
+  "Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
+  "Match case": "\u533a\u5206\u5927\u5c0f\u5199",
+  "Whole words": "\u5168\u5b57\u5339\u914d",
+  "Spellcheck": "\u62fc\u5199\u68c0\u67e5",
+  "Ignore": "\u5ffd\u7565",
+  "Ignore all": "\u5168\u90e8\u5ffd\u7565",
+  "Finish": "\u5b8c\u6210",
+  "Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
+  "Insert table": "\u63d2\u5165\u8868\u683c",
+  "Table properties": "\u8868\u683c\u5c5e\u6027",
+  "Delete table": "\u5220\u9664\u8868\u683c",
+  "Cell": "\u5355\u5143\u683c",
+  "Row": "\u884c",
+  "Column": "\u5217",
+  "Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
+  "Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
+  "Split cell": "\u62c6\u5206\u5355\u5143\u683c",
+  "Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
+  "Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
+  "Delete row": "\u5220\u9664\u884c",
+  "Row properties": "\u884c\u5c5e\u6027",
+  "Cut row": "\u526a\u5207\u884c",
+  "Copy row": "\u590d\u5236\u884c",
+  "Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
+  "Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
+  "Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
+  "Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
+  "Delete column": "\u5220\u9664\u5217",
+  "Cols": "\u5217",
+  "Rows": "\u884c",
+  "Width": "\u5bbd",
+  "Height": "\u9ad8",
+  "Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
+  "Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
+  "Caption": "\u6807\u9898",
+  "Left": "\u5de6\u5bf9\u9f50",
+  "Center": "\u5c45\u4e2d",
+  "Right": "\u53f3\u5bf9\u9f50",
+  "Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
+  "Scope": "\u8303\u56f4",
+  "Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
+  "H Align": "\u6c34\u5e73\u5bf9\u9f50",
+  "V Align": "\u5782\u76f4\u5bf9\u9f50",
+  "Top": "\u9876\u90e8\u5bf9\u9f50",
+  "Middle": "\u5782\u76f4\u5c45\u4e2d",
+  "Bottom": "\u5e95\u90e8\u5bf9\u9f50",
+  "Header cell": "\u8868\u5934\u5355\u5143\u683c",
+  "Row group": "\u884c\u7ec4",
+  "Column group": "\u5217\u7ec4",
+  "Row type": "\u884c\u7c7b\u578b",
+  "Header": "\u8868\u5934",
+  "Body": "\u8868\u4f53",
+  "Footer": "\u8868\u5c3e",
+  "Border color": "\u8fb9\u6846\u989c\u8272",
+  "Insert template": "\u63d2\u5165\u6a21\u677f",
+  "Templates": "\u6a21\u677f",
+  "Template": "\u6a21\u677f",
+  "Text color": "\u6587\u5b57\u989c\u8272",
+  "Background color": "\u80cc\u666f\u8272",
+  "Custom...": "\u81ea\u5b9a\u4e49...",
+  "Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
+  "No color": "\u65e0",
+  "Table of Contents": "\u5185\u5bb9\u5217\u8868",
+  "Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
+  "Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
+  "Words: {0}": "\u5b57\u6570\uff1a{0}",
+  "{0} words": "{0} \u5b57",
+  "File": "\u6587\u4ef6",
+  "Edit": "\u7f16\u8f91",
+  "Insert": "\u63d2\u5165",
+  "View": "\u89c6\u56fe",
+  "Format": "\u683c\u5f0f",
+  "Table": "\u8868\u683c",
+  "Tools": "\u5de5\u5177",
+  "Powered by {0}": "\u7531{0}\u9a71\u52a8",
+  "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9"
+});

文件差異過大導致無法顯示
+ 554 - 0
public/tinymce/skins/lightgray/content.min.css


文件差異過大導致無法顯示
+ 2159 - 0
public/tinymce/skins/lightgray/skin.min.css


文件差異過大導致無法顯示
+ 1 - 0
public/v2.js


+ 43 - 0
src/App.vue

@@ -0,0 +1,43 @@
+<template>
+  <a-locale-provider :locale="locale">
+    <div id="app">
+      <router-view/>
+    </div>
+  </a-locale-provider>
+</template>
+<script>
+  import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
+  import enquireScreen from '@/utils/device'
+
+  export default {
+    data() {
+      return {
+        locale: zhCN,
+      }
+    },
+    created() {
+      let that = this
+      enquireScreen(deviceType => {
+        // tablet
+        if (deviceType === 0) {
+          that.$store.commit('TOGGLE_DEVICE', 'mobile')
+          that.$store.dispatch('setSidebar', false)
+        }
+        // mobile
+        else if (deviceType === 1) {
+          that.$store.commit('TOGGLE_DEVICE', 'mobile')
+          that.$store.dispatch('setSidebar', false)
+        } else {
+          that.$store.commit('TOGGLE_DEVICE', 'desktop')
+          that.$store.dispatch('setSidebar', true)
+        }
+
+      })
+    }
+  }
+</script>
+<style>
+  #app {
+    height: 100%;
+  }
+</style>

+ 147 - 0
src/api/api.js

@@ -0,0 +1,147 @@
+import {getAction, deleteAction, putAction, postAction} from '@/api/manage'
+
+////根路径
+// 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 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 changPassword = (params) => putAction("/sys/user/changPassword", 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 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 getLogList = (params)=>getAction("/sys/log/list",params);
+const deleteLog = (params) => deleteAction("/sys/log/delete", params);
+const deleteLogList = (params) => deleteAction("/sys/log/deleteBatch", params);
+
+//数据字典
+const addDict = (params) => postAction("/sys/dict/add", params);
+const editDict = (params) => putAction("/sys/dict/edit", params);
+//const getDictList = (params)=>getAction("/sys/dict/list",params);
+const treeList = (params) => getAction("/sys/dict/treeList", params);
+// const delDict = (params)=>deleteAction("/sys/dict/delete",params);
+//const getDictItemList = (params)=>getAction("/sys/dictItem/list",params);
+const addDictItem = (params) => postAction("/sys/dictItem/add", params);
+const editDictItem = (params) => putAction("/sys/dictItem/edit", params);
+//const delDictItem = (params)=>deleteAction("/sys/dictItem/delete",params);
+//const delDictItemList = (params)=>deleteAction("/sys/dictItem/deleteBatch",params);
+
+//字典标签专用(通过code获取字典数组)
+export const ajaxGetDictItems = (code, params) => getAction(`/sys/dict/getDictItems/${code}`, params);
+
+//系统通告
+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 queryUserByDepId = (params) => getAction("/sys/user/queryUserByDepId", params);
+
+// 查询用户角色表里的所有信息
+const queryUserRoleMap = (params) => getAction("/sys/user/queryUserRoleMap", params);
+// 重复校验
+const duplicateCheck = (params) => getAction("/sys/duplicate/check", params);
+
+export {
+  // imgView,
+  // doMian,
+  addRole,
+  editRole,
+  checkRoleCode,
+  addUser,
+  editUser,
+  queryUserRole,
+  getUserList,
+  queryall,
+  frozenBatch,
+  checkOnlyUser,
+  changPassword,
+  getPermissionList,
+  addPermission,
+  editPermission,
+  queryTreeList,
+  queryListAsync,
+  queryRolePermission,
+  saveRolePermission,
+  queryPermissionsByUser,
+  loadAllRoleIds,
+  getPermissionRuleList,
+  queryPermissionRule,
+  queryDepartTreeList,
+  queryIdTree,
+  queryParentName,
+  searchByKeywords,
+  deleteByDepartId,
+  deleteLog,
+  deleteLogList,
+  addDict,
+  editDict,
+  treeList,
+  addDictItem,
+  editDictItem,
+  doReleaseData,
+  doReovkeData,
+  getLoginfo,
+
+  getVisitInfo,
+  queryUserByDepId,
+  queryUserRoleMap,
+  duplicateCheck,
+  queryTreeListForRole,
+  getIndustryList,
+  getIndustryTreeList
+
+}
+
+
+

+ 10 - 0
src/api/index.js

@@ -0,0 +1,10 @@
+const api = {
+  Login: '/sys/login',
+  Logout: '/sys/logout',
+  ForgePassword: '/auth/forge-password',
+  Register: '/auth/register',
+  SendSms: '/account/sms',
+  // get my info
+  UserInfo: '/user/info'
+}
+export default api

+ 58 - 0
src/api/login.js

@@ -0,0 +1,58 @@
+import api from './index'
+import {axios} from '@/utils/request'
+
+/**
+ * login func
+ * parameter: {
+ *     username: '',
+ *     password: '',
+ *     remember_me: true,
+ *     captcha: '12345'
+ * }
+ * @param parameter
+ * @returns {*}
+ */
+export function login(parameter) {
+  return axios({
+    url: '/sys/login',
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function phoneLogin(parameter) {
+  return axios({
+    url: '/sys/phoneLogin',
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function getSmsCaptcha(parameter) {
+  return axios({
+    url: api.SendSms,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function getInfo() {
+  return axios({
+    url: '/api/user/info',
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+export function logout(logoutToken) {
+  return axios({
+    url: '/sys/logout',
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+      'X-Access-Token': logoutToken
+    }
+  })
+}

+ 114 - 0
src/api/manage.js

@@ -0,0 +1,114 @@
+import {axios} from '@/utils/request'
+
+const api = {
+  user: '/api/user',
+  role: '/api/role',
+  service: '/api/service',
+  permission: '/api/permission',
+  permissionNoPager: '/api/permission/no-pager'
+}
+
+export default api
+
+//post
+export function postAction(url, parameter) {
+  return axios({
+    url: url,
+    method: 'post',
+    data: parameter
+  })
+}
+
+//post method= {post | put}
+export function httpAction(url, parameter, method) {
+  return axios({
+    url: url,
+    method: method,
+    data: parameter
+  })
+}
+
+//put
+export function putAction(url, parameter) {
+  return axios({
+    url: url,
+    method: 'put',
+    data: parameter
+  })
+}
+
+//get
+export function getAction(url, parameter) {
+  return axios({
+    url: url,
+    method: 'get',
+    params: parameter
+  })
+}
+
+//deleteAction
+export function deleteAction(url, parameter) {
+  return axios({
+    url: url,
+    method: 'delete',
+    params: parameter
+  })
+}
+
+export function getUserList(parameter) {
+  return axios({
+    url: api.user,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getRoleList(parameter) {
+  return axios({
+    url: api.role,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getServiceList(parameter) {
+  return axios({
+    url: api.service,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getPermissions(parameter) {
+  return axios({
+    url: api.permissionNoPager,
+    method: 'get',
+    params: parameter
+  })
+}
+
+// id == 0 add     post
+// id != 0 update  put
+export function saveService(parameter) {
+  return axios({
+    url: api.service,
+    method: parameter.id == 0 ? 'post' : 'put',
+    data: parameter
+  })
+}
+
+/**
+ * 下载文件 用于excel导出
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export function downFile(url, parameter) {
+  return axios({
+    url: url,
+    params: parameter,
+    method: 'get',
+    responseType: 'blob'
+  })
+}
+

+ 121 - 0
src/assets/background.svg

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+  <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
+  <title>Group 21</title>
+  <desc>Created with Sketch.</desc>
+  <defs></defs>
+  <g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
+      <g id="Group-21" transform="translate(77.000000, 73.000000)">
+        <g id="Group-18" opacity="0.8"
+           transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
+          <ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479"
+                   ry="21.766008"></ellipse>
+          <ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913"
+                   ry="5.21330997"></ellipse>
+          <path
+            d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z"
+            id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
+          <path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6"
+                stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
+          <path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7"
+                stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round"
+                stroke-dasharray="1.405357899873153,2.108036953469981"></path>
+          <path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9"
+                stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round"
+                stroke-dasharray="1.405357899873153,2.108036953469981"></path>
+          <g id="Group-17"
+             transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)"
+             fill="#CFDAE6">
+            <ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653"
+                     ry="9.12768076"></ellipse>
+            <path
+              d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z"
+              id="Oval-4"
+              transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
+          </g>
+        </g>
+        <g id="Group-14"
+           transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
+          <ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471"
+                   ry="29.1402439"></ellipse>
+          <ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275"
+                   ry="21.5853659"></ellipse>
+          <ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902"
+                   ry="23.7439024"></ellipse>
+          <ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137"
+                   ry="10.7926829"></ellipse>
+          <path
+            d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z"
+            id="Oval-2" fill="#BACAD9"></path>
+          <g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
+            <ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
+            <path
+              d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z"
+              id="Oval-2-Copy-2"
+              transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
+          </g>
+          <ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706"
+                   ry="1.61890244"></ellipse>
+          <ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706"
+                   ry="1.61890244"></ellipse>
+          <ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275"
+                   ry="2.15853659"></ellipse>
+          <path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
+        </g>
+        <g id="Group-10" opacity="0.799999952"
+           transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
+          <ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407"
+                   ry="11.2941176"></ellipse>
+          <g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
+            <ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627"
+                     ry="8.55614973"></ellipse>
+            <path
+              d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z"
+              id="Oval-7"></path>
+          </g>
+          <path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6"
+                stroke-width="0.941176471"></path>
+          <ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186"
+                   ry="3.29411765"></ellipse>
+          <ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017"
+                   ry="2.82352941"></ellipse>
+          <path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6"
+                stroke-width="0.941176471"></path>
+        </g>
+        <g id="Group-19" opacity="0.33"
+           transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
+          <g id="Group-17"
+             transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)"
+             fill="#BACAD9">
+            <circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
+            <path
+              d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z"
+              id="Oval-4"
+              transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
+          </g>
+          <circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
+          <path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9"
+                stroke-width="1.16666667"></path>
+          <path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9"
+                stroke-width="1.16666667"></path>
+          <polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667"
+                    points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
+          <path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667"
+                opacity="0.6"></path>
+          <path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9"
+                stroke-width="1.16666667"></path>
+          <path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6"
+                stroke-width="1.16666667"></path>
+          <circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
+          <circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
+          <circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
+          <circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333"
+                  r="5.83333333"></circle>
+          <circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

+ 54 - 0
src/assets/less/common.less

@@ -0,0 +1,54 @@
+/*列表上方操作按钮区域*/
+.ant-card-body .table-operator {
+  margin-bottom: 18px;
+}
+
+/** Button按钮间距 */
+.table-operator .ant-btn {
+  margin-right: 6px
+}
+
+/*列表td的padding设置 可以控制列表大小*/
+.ant-table-tbody .ant-table-row td {
+  padding-top: 15px;
+  padding-bottom: 15px;
+}
+
+/*列表页面弹出modal*/
+.ant-modal-cust-warp {
+  height: 100%
+}
+
+/*弹出modal Y轴滚动条*/
+.ant-modal-cust-warp .ant-modal-body {
+  height: calc(100% - 110px) !important;
+  overflow-y: auto
+}
+
+/*弹出modal 先有content后有body 故滚动条控制在body上*/
+.ant-modal-cust-warp .ant-modal-content {
+  height: 90% !important;
+  overflow-y: hidden
+}
+
+/*列表中有图片的加这个样式 参考用户管理*/
+.anty-img-wrap {
+  height: 25px;
+  position: relative;
+}
+
+.anty-img-wrap > img {
+  max-height: 100%;
+}
+
+/*列表中范围查询样式*/
+.query-group-cust {
+  width: calc(50% - 10px)
+}
+
+.query-group-split-cust:before {
+  content: "~";
+  width: 20px;
+  display: inline-block;
+  text-align: center
+}

+ 34 - 0
src/assets/less/index.less

@@ -0,0 +1,34 @@
+/**
+ * 列表查询通用样式,移动端自适应
+ */
+.search {
+  margin-bottom: 54px;
+}
+
+.fold {
+  width: calc(100% - 216px);
+  display: inline-block
+}
+
+.operator {
+  margin-bottom: 18px;
+}
+
+@media screen and (max-width: 900px) {
+  .fold {
+    width: 100%;
+  }
+}
+
+.operator button {
+  margin-right: 5px;
+}
+
+i {
+  cursor: pointer;
+}
+
+.trcolor {
+  background-color: rgba(255, 192, 203, 0.31);
+  color: red;
+}

二進制
src/assets/logo.png


+ 174 - 0
src/assets/logo.svg

@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
+     y="0px" width="293px" height="293px" viewBox="0 0 293 293" enable-background="new 0 0 293 293"
+     xml:space="preserve">  <image id="image0" width="293" height="293" x="0" y="0"
+                                   href="data:image/png;base64,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
+OjAwP26CAAAAAABJRU5ErkJggg=="/>
+</svg>

二進制
src/assets/pdf4.jpg


+ 46 - 0
src/components/AvatarList/Item.vue

@@ -0,0 +1,46 @@
+<template>
+  <tooltip v-if="tips !== ''">
+    <template slot="title">{{ tips }}</template>
+    <avatar :size="avatarSize" :src="src"/>
+  </tooltip>
+  <avatar v-else :size="avatarSize" :src="src"/>
+</template>
+
+<script>
+  import Avatar from 'ant-design-vue/es/avatar'
+  import Tooltip from 'ant-design-vue/es/tooltip'
+
+  export default {
+    name: "AvatarItem",
+    components: {
+      Avatar,
+      Tooltip
+    },
+    props: {
+      tips: {
+        type: String,
+        default: '',
+        required: false
+      },
+      src: {
+        type: String,
+        default: ''
+      }
+    },
+    data() {
+      return {
+        size: this.$parent.size
+      }
+    },
+    computed: {
+      avatarSize() {
+        return this.size !== 'mini' && this.size || 20
+      }
+    },
+    watch: {
+      '$parent.size'(val) {
+        this.size = val
+      }
+    }
+  }
+</script>

+ 118 - 0
src/components/AvatarList/List.vue

@@ -0,0 +1,118 @@
+<!--
+<template>
+  <div :class="[prefixCls]">
+    <ul>
+      <slot></slot>
+      <template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
+
+
+      <template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
+        <avatar-item :size="size">
+          <avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
+        </avatar-item>
+      </template>
+    </ul>
+  </div>
+</template>
+-->
+
+<script>
+  import Avatar from 'ant-design-vue/es/avatar'
+  import AvatarItem from './Item'
+  import {filterEmpty} from '@/components/_util/util'
+
+  export default {
+    AvatarItem,
+    name: "AvatarList",
+    components: {
+      Avatar,
+      AvatarItem
+    },
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-avatar-list'
+      },
+      /**
+       * 头像大小 类型: large、small 、mini, default
+       * 默认值: default
+       */
+      size: {
+        type: [String, Number],
+        default: 'default'
+      },
+      /**
+       * 要显示的最大项目
+       */
+      maxLength: {
+        type: Number,
+        default: 0
+      },
+      /**
+       * 多余的项目风格
+       */
+      excessItemsStyle: {
+        type: Object,
+        default: () => {
+          return {
+            color: '#f56a00',
+            backgroundColor: '#fde3cf'
+          }
+        }
+      }
+    },
+    data() {
+      return {}
+    },
+    methods: {
+      getItems(items) {
+        const classString = {
+          [`${this.prefixCls}-item`]: true,
+          [`${this.size}`]: true
+        }
+
+        if (this.maxLength > 0) {
+          items = items.slice(0, this.maxLength)
+          items.push(( < Avatar
+          size = {this.size}
+          style = {this.excessItemsStyle} > {`+${this.maxLength}`
+        }<
+          /Avatar>))
+        }
+        const itemList = items.map((item) => (
+          < li
+      class
+        = {classString} > {item} < /li>
+      ))
+        return itemList
+      }
+    },
+    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
+
+      return (
+        < div
+    class
+      = {classString} >
+        {itemsDom}
+        < /div>
+    )
+    }
+  }
+</script>

+ 4 - 0
src/components/AvatarList/index.js

@@ -0,0 +1,4 @@
+import AvatarList from './List'
+import "./index.less"
+
+export default AvatarList

+ 60 - 0
src/components/AvatarList/index.less

@@ -0,0 +1,60 @@
+@import "../index";
+
+@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
+@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
+
+.@{avatar-list-prefix-cls} {
+  display: inline-block;
+
+  ul {
+    list-style: none;
+    display: inline-block;
+    padding: 0;
+    margin: 0 0 0 8px;
+    font-size: 0;
+  }
+}
+
+.@{avatar-list-item-prefix-cls} {
+  display: inline-block;
+  font-size: @font-size-base;
+  margin-left: -8px;
+  width: @avatar-size-base;
+  height: @avatar-size-base;
+
+  :global {
+    .ant-avatar {
+      border: 1px solid #fff;
+      cursor: pointer;
+    }
+  }
+
+  &.large {
+    width: @avatar-size-lg;
+    height: @avatar-size-lg;
+  }
+
+  &.small {
+    width: @avatar-size-sm;
+    height: @avatar-size-sm;
+  }
+
+  &.mini {
+    width: 20px;
+    height: 20px;
+
+    :global {
+      .ant-avatar {
+        width: 20px;
+        height: 20px;
+        line-height: 20px;
+
+        .ant-avatar-string {
+          font-size: 12px;
+          line-height: 18px;
+        }
+      }
+    }
+  }
+}
+

+ 111 - 0
src/components/ChartCard.vue

@@ -0,0 +1,111 @@
+<template>
+  <a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
+    <div class="chart-card-header">
+      <div class="meta">
+        <span class="chart-card-title">{{ title }}</span>
+        <span class="chart-card-action">
+          <slot name="action"></slot>
+        </span>
+      </div>
+      <div class="total"><span>{{ total }}</span></div>
+    </div>
+    <div class="chart-card-content">
+      <div class="content-fix">
+        <slot></slot>
+      </div>
+    </div>
+    <div class="chart-card-footer">
+      <div class="field">
+        <slot name="footer"></slot>
+      </div>
+    </div>
+  </a-card>
+</template>
+
+<script>
+  export default {
+    name: "ChartCard",
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      total: {
+        type: String,
+        default: ''
+      },
+      loading: {
+        type: Boolean,
+        default: false
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .chart-card-header {
+    position: relative;
+    overflow: hidden;
+    width: 100%;
+
+    .meta {
+      position: relative;
+      overflow: hidden;
+      width: 100%;
+      color: rgba(0, 0, 0, .45);
+      font-size: 14px;
+      line-height: 22px;
+    }
+  }
+
+  .chart-card-action {
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    right: 0;
+  }
+
+  .chart-card-footer {
+    border-top: 1px solid #e8e8e8;
+    padding-top: 9px;
+    margin-top: 8px;
+
+    > * {
+      position: relative;
+    }
+
+    .field {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin: 0;
+    }
+  }
+
+  .chart-card-content {
+    margin-bottom: 12px;
+    position: relative;
+    height: 46px;
+    width: 100%;
+
+    .content-fix {
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      width: 100%;
+    }
+  }
+
+  .total {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+    color: #000;
+    margin-top: 4px;
+    margin-bottom: 0;
+    font-size: 30px;
+    line-height: 38px;
+    height: 38px;
+  }
+</style>

+ 103 - 0
src/components/CountDown/CountDown.vue

@@ -0,0 +1,103 @@
+<template>
+  <span>
+    {{ lastTime | format }}
+  </span>
+</template>
+
+<script>
+
+  function fixedZero(val) {
+    return val * 1 < 10 ? `0${val}` : val;
+  }
+
+  export default {
+    name: "CountDown",
+    props: {
+      format: {
+        type: Function,
+        default: undefined
+      },
+      target: {
+        type: [Date, Number],
+        required: true,
+      },
+      onEnd: {
+        type: Function,
+        default: () => {
+        }
+      }
+    },
+    data() {
+      return {
+        dateTime: '0',
+        originTargetTime: 0,
+        lastTime: 0,
+        timer: 0,
+        interval: 1000
+      }
+    },
+    filters: {
+      format(time) {
+        const hours = 60 * 60 * 1000;
+        const minutes = 60 * 1000;
+
+        const h = Math.floor(time / hours);
+        const m = Math.floor((time - h * hours) / minutes);
+        const s = Math.floor((time - h * hours - m * minutes) / 1000);
+        return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
+      }
+    },
+    created() {
+      this.initTime()
+      this.tick()
+    },
+    methods: {
+      initTime() {
+        let lastTime = 0;
+        let targetTime = 0;
+        this.originTargetTime = this.target
+        try {
+          if (Object.prototype.toString.call(this.target) === '[object Date]') {
+            targetTime = this.target
+          } else {
+            targetTime = new Date(this.target).getTime()
+          }
+        } catch (e) {
+          throw new Error('invalid target prop')
+        }
+
+        lastTime = targetTime - new Date().getTime();
+
+        this.lastTime = lastTime < 0 ? 0 : lastTime
+      },
+      tick() {
+        const {onEnd} = this
+
+        this.timer = setTimeout(() => {
+          if (this.lastTime < this.interval) {
+            clearTimeout(this.timer)
+            this.lastTime = 0
+            if (typeof onEnd === 'function') {
+              onEnd();
+            }
+          } else {
+            this.lastTime -= this.interval
+            this.tick()
+          }
+        }, this.interval)
+      }
+    },
+    beforeUpdate() {
+      if (this.originTargetTime !== this.target) {
+        this.initTime()
+      }
+    },
+    beforeDestroy() {
+      clearTimeout(this.timer)
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 3 - 0
src/components/CountDown/index.js

@@ -0,0 +1,3 @@
+import CountDown from './CountDown'
+
+export default CountDown

+ 71 - 0
src/components/Ellipsis/Ellipsis.vue

@@ -0,0 +1,71 @@
+<script>
+  import Tooltip from 'ant-design-vue/es/tooltip'
+  import {cutStrByFullLength, getStrFullLength} from '@/components/_util/StringUtil'
+  /*
+    const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
+
+    const TooltipOverlayStyle = {
+      overflowWrap: 'break-word',
+      wordWrap: 'break-word',
+    };
+  */
+
+  export default {
+    name: 'Ellipsis',
+    components: {
+      Tooltip
+    },
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-ellipsis'
+      },
+      tooltip: {
+        type: Boolean
+      },
+      length: {
+        type: Number,
+        required: true
+      },
+      lines: {
+        type: Number,
+        default: 1
+      },
+      fullWidthRecognition: {
+        type: Boolean,
+        default: false
+      }
+    },
+    methods: {
+      getStrDom(str) {
+        return (
+          < span > {cutStrByFullLength(str, this.length
+      )
+        +'...'
+      }<
+        /span>
+      )
+      },
+      getTooltip(fullStr) {
+        return (
+          < Tooltip >
+          < template
+        slot = "title" > {fullStr} < /template>
+        {
+          this.getStrDom(fullStr)
+        }
+      <
+        /Tooltip>
+      )
+      }
+    },
+    render() {
+      const {tooltip, length} = this.$props
+      let str = this.$slots.default.map(vNode => vNode.text).join("")
+      const strDom = tooltip && getStrFullLength(str) > length ? this.getTooltip(str) : this.getStrDom(str);
+      return (
+        strDom
+      )
+    }
+  }
+</script>

+ 3 - 0
src/components/Ellipsis/index.js

@@ -0,0 +1,3 @@
+import Ellipsis from './Ellipsis'
+
+export default Ellipsis

+ 54 - 0
src/components/NumberInfo/NumberInfo.vue

@@ -0,0 +1,54 @@
+<template>
+  <div :class="[prefixCls]">
+    <slot name="subtitle">
+      <div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
+    </slot>
+    <div class="number-info-value">
+      <span>{{ total }}</span>
+      <span class="sub-total">
+        {{ subTotal }}
+        <icon :type="`caret-${status}`"/>
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+  import Icon from 'ant-design-vue/es/icon'
+
+  export default {
+    name: 'NumberInfo',
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-number-info'
+      },
+      total: {
+        type: Number,
+        required: true
+      },
+      subTotal: {
+        type: Number,
+        required: true
+      },
+      subTitle: {
+        type: [String, Function],
+        default: ''
+      },
+      status: {
+        type: String,
+        default: 'up'
+      }
+    },
+    components: {
+      Icon
+    },
+    data() {
+      return {}
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  @import "index";
+</style>

+ 3 - 0
src/components/NumberInfo/index.js

@@ -0,0 +1,3 @@
+import NumberInfo from './NumberInfo'
+
+export default NumberInfo

+ 58 - 0
src/components/NumberInfo/index.less

@@ -0,0 +1,58 @@
+@import "../index";
+
+@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
+
+.@{numberInfo-prefix-cls} {
+
+  .ant-pro-number-info-subtitle {
+    color: @text-color-secondary;
+    font-size: @font-size-base;
+    height: 22px;
+    line-height: 22px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+
+  .number-info-value {
+    margin-top: 4px;
+    font-size: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+
+    & > span {
+      color: @heading-color;
+      display: inline-block;
+      line-height: 32px;
+      height: 32px;
+      font-size: 24px;
+      margin-right: 32px;
+    }
+
+    .sub-total {
+      color: @text-color-secondary;
+      font-size: @font-size-lg;
+      vertical-align: top;
+      margin-right: 0;
+
+      i {
+        font-size: 12px;
+        transform: scale(0.82);
+        margin-left: 4px;
+      }
+
+      :global {
+        .anticon-caret-up {
+          color: @red-6;
+        }
+
+        .anticon-caret-down {
+          color: @green-6;
+        }
+      }
+    }
+  }
+}

+ 41 - 0
src/components/README.md

@@ -0,0 +1,41 @@
+####1._util包:存放自定义函数 详细见代码注释
+####2.AvatarList:显示头像群并支持tip,用法参考src\views\Home.vue(如下图)
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12181253_O0Xi.png "在这里输入图片标题")
+####3.chart包:存放各种图表相关的组件,条形图柱形图折线图等等 具体用法参考首页
+####4.countDown包:一个倒计时组件,用法参考home页,简单描述,该组件有3个属性,
+target(时间/毫秒数)必填,
+format(function,该方法接收一个毫秒数的参数,用于格式化显示当前倒计时时间)非必填,
+onEnd倒计时结束触发函数
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12182046_mwqJ.png "在这里输入图片标题")
+####5.dict包:数据字典专用,用法参考文件夹下readme文件
+####6.Ellipsis包:字符串截取组件,可以指定字符串的显示长度,并将全部内容显示到tip中,简单使用参考src\views\system\PermissionList.vue
+####7.jeecg包:该包下自定义了很多列表/表单中用到的组件 参考包下readme文件
+####8.jeecgbiz包:该包下定义了一些业务相关的组件,比如选择用户弹框,根据部门选择用户等等
+####9.layouts+page包:系统页面布局相关组件,比如登陆进去之后页面顶部显示什么,底部显示什么,菜单点击触发多个tab的布局等等 一般情况不需要修改
+####10.menun包:菜单组件,俩个,一个折叠菜单一个正常显示的菜单
+####11.NumberInfo:数字信息显示组件 如下图
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12185858_uvJ5.png "在这里输入图片标题")
+####12.online包:该包下封装了online表单的相关组件,用于展示表单各种控件,验证表单等等,相关用法参考readme
+####13.setting包:该包下封装了首页风格切换等功能如下图
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12190520_jySG.png "在这里输入图片标题")
+####14.table包:一个二次封装的table组件,用于展示列表,参考readme
+####15.tools包:
+Breadcrumb.vue:面包屑二次封装,支持路由跳转
+DetailList.vue:详情展示用法参考src\views\profile\advanced\Advanced.vue(效果如下图)
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12193954_Uar6.png "在这里输入图片标题")
+````
+个人认为该页面代码有两点值得学习:
+1.vue provide/inject的使用
+2.该页面css定义方式,只定义一个顶层class,其余样式都定义在其下,这样只要顶层class不和别的页面冲突,整个页面的样式都是唯一生效的
+````
+FooterToolBar.vue:fixed定位的底部,通过是否定义内部控件的属性slot="extra"决定是左浮动或是右浮动
+HeaderNotice.vue:首页通知(如下图)
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12195340_fPe0.png "在这里输入图片标题")
+HeaderInfo.vue:上下文字布局(如下图)
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12195638_dG5o.png "在这里输入图片标题")
+Logo.vue:首页左上侧的log图
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12200908_ihv3.png "在这里输入图片标题")
+UserMenu.vue:首页右上侧的内容
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12201226_laQK.png "在这里输入图片标题")
+####16.trend包 趋势显示组件(如下图)
+![输入图片说明](https://static.oschina.net/uploads/img/201904/12201600_Wo8K.png "在这里输入图片标题")

+ 41 - 0
src/components/Trend/Trend.vue

@@ -0,0 +1,41 @@
+<template>
+  <div :class="[prefixCls, reverseColor && 'reverse-color' ]">
+    <span>
+      <slot name="term"></slot>
+      <span class="item-text">
+        <slot></slot>
+      </span>
+    </span>
+    <span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Trend",
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-trend'
+      },
+      /**
+       * 上升下降标识:up|down
+       */
+      flag: {
+        type: String,
+        required: true
+      },
+      /**
+       * 颜色反转
+       */
+      reverseColor: {
+        type: Boolean,
+        default: false
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  @import "index";
+</style>

+ 3 - 0
src/components/Trend/index.js

@@ -0,0 +1,3 @@
+import Trend from './Trend.vue'
+
+export default Trend

+ 44 - 0
src/components/Trend/index.less

@@ -0,0 +1,44 @@
+@import "../index";
+
+@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
+
+.@{trend-prefix-cls} {
+  display: inline-block;
+  font-size: @font-size-base;
+  line-height: 22px;
+
+  .up,
+  .down {
+    margin-left: 4px;
+    position: relative;
+    top: 1px;
+
+    i {
+      font-size: 12px;
+      transform: scale(0.83);
+    }
+  }
+
+  .item-text {
+    display: inline-block;
+    margin-left: 8px;
+    color: rgba(0, 0, 0, .85);
+  }
+
+  .up {
+    color: @red-6;
+  }
+
+  .down {
+    color: @green-6;
+    top: -1px;
+  }
+
+  &.reverse-color .up {
+    color: @green-6;
+  }
+
+  &.reverse-color .down {
+    color: @red-6;
+  }
+}

+ 35 - 0
src/components/_util/StringUtil.js

@@ -0,0 +1,35 @@
+/**
+ * 获取字符串的长度ascii长度为1 中文长度为2
+ * @param str
+ * @returns {number}
+ */
+export const getStrFullLength = (str = '') =>
+  str.split('').reduce((pre, cur) => {
+    const charCode = cur.charCodeAt(0)
+    if (charCode >= 0 && charCode <= 128) {
+      return pre + 1
+    }
+    return pre + 2
+  }, 0)
+
+/**
+ * 给定一个字符串和一个长度,将此字符串按指定长度截取
+ * @param str
+ * @param maxLength
+ * @returns {string}
+ */
+export const cutStrByFullLength = (str = '', maxLength) => {
+  let showLength = 0
+  return str.split('').reduce((pre, cur) => {
+    const charCode = cur.charCodeAt(0)
+    if (charCode >= 0 && charCode <= 128) {
+      showLength += 1
+    } else {
+      showLength += 2
+    }
+    if (showLength <= maxLength) {
+      return pre + cur
+    }
+    return pre
+  }, '')
+}

+ 12 - 0
src/components/_util/util.js

@@ -0,0 +1,12 @@
+/**
+ * components util
+ */
+
+/**
+ * 清理空值,对象
+ * @param children
+ * @returns {*[]}
+ */
+export function filterEmpty(children = []) {
+  return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
+}

+ 35 - 0
src/components/bpm/DynamicComponent.vue

@@ -0,0 +1,35 @@
+<template>
+  <component ref="compModel" :is="comp" :formData="formData" v-if="comp" @ok="callBackOk"
+             @close="callBackClose"></component>
+</template>
+<script>
+  export default {
+    name: 'DynamicComponent',
+    data() {
+      return {
+        compName: this.path
+      }
+    },
+    computed: {
+      comp: function () {
+        return () => import(`@/views/${this.compName}.vue`)
+      }
+    },
+    props: ['path', 'formData'],
+    methods: {
+      add() {
+        this.$refs.compModel.add();
+      },
+      callBackClose() {
+        this.$emit('close');
+      },
+      handleOk() {
+        this.$refs.compModel.handleOk();
+      },
+      callBackOk() {
+        this.$emit('ok');
+        this.close();
+      },
+    }
+  }
+</script>

+ 73 - 0
src/components/bpm/FormCommonModal.vue

@@ -0,0 +1,73 @@
+<template>
+  <a-modal
+    :title="title"
+    :width="width"
+    :visible="visible"
+    :confirmLoading="confirmLoading"
+    @ok="handleOk"
+    @cancel="handleCancel"
+    destroyOnClose
+    cancelText="关闭">
+    <a-spin :spinning="confirmLoading">
+      <dynamic-component ref="dynamiclink" :path="path" :formData="formData" @ok="callBackOk"
+                         @close="callBackClose"></dynamic-component>
+    </a-spin>
+  </a-modal>
+</template>
+
+<script>
+
+  import DynamicComponent from "./DynamicComponent";
+
+  export default {
+    name: "FormCommonModal",
+    props: ['path'],
+    components: {
+      DynamicComponent
+    },
+    data() {
+      return {
+        title: "操作",
+        width: "80%",
+        visible: false,
+        confirmLoading: false,
+        formData: {},
+      }
+    },
+    created() {
+    },
+    methods: {
+      add() {
+        this.formData = [];
+        this.title = "新增";
+        this.visible = true;
+        this.$refs.dynamiclink.add();
+      },
+      edit(record) {
+        var data = {
+          dataId: record.id,
+        }
+        this.formData = data;
+        this.visible = true;
+      },
+      callBackClose() {
+        this.$emit('close');
+        this.visible = false;
+      },
+      handleOk() {
+        this.$refs.dynamiclink.handleOk();
+      },
+      callBackOk() {
+        this.$emit('ok');
+        this.callBackClose();
+      },
+      handleCancel() {
+        this.callBackClose()
+      },
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 121 - 0
src/components/bpm/ProcNodeInfoModel.vue

@@ -0,0 +1,121 @@
+<template>
+  <a-modal
+    :title="title"
+    :width="280"
+    :visible="visible"
+    :confirmLoading="confirmLoading"
+    :bodyStyle="bodyStyle"
+    :mask="false"
+    destroyOnClose
+    :footer="null"
+    @cancel="handleCancel"
+    cancelText="关闭">
+
+    <a-spin :spinning="confirmLoading">
+      <div style="height: 300px;overflow: hidden;overflow-y: auto;overflow-x: auto;">
+        <template v-for="(item, key, index) in nodeInfos">
+          <table class="gridtable">
+            <tbody>
+            <tr>
+              <th width="90">任务名称</th>
+              <td width="150">{{ item.taskName}}</td>
+            </tr>
+            <tr>
+              <th width="90">执行人</th>
+              <td width="150">{{ item.taskAssigneeId}}</td>
+            </tr>
+            <tr>
+              <th width="90">开始时间</th>
+              <td width="150">{{ item.taskBeginTime }}</td>
+            </tr>
+            <tr>
+              <th width="90">结束时间</th>
+              <td width="150">{{ item.taskEndTime }}</td>
+            </tr>
+            <tr>
+              <th width="90">耗时</th>
+              <td width="150">{{ item.durationStr }}</td>
+            </tr>
+            <tr>
+              <th width="90">意见</th>
+              <td width="150">{{ item.remarks }}</td>
+            </tr>
+            </tbody>
+          </table>
+        </template>
+      </div>
+    </a-spin>
+  </a-modal>
+</template>
+
+<script>
+  import {httpAction} from '@/api/manage'
+  import pick from 'lodash.pick'
+
+  export default {
+    name: "ProcNodeInfoModel",
+    data() {
+      return {
+        title: "任务审批详情",
+        visible: false,
+        bodyStyle: {
+          padding: "0",
+        },
+        confirmLoading: false,
+        validatorRules: {},
+        nodeInfos: [],
+      }
+    },
+    created() {
+    },
+    methods: {
+      showInfo(record, taskId) {
+        this.nodeInfos = [];
+        for (var item of record) {
+          if (item.taskId == taskId) {
+            this.nodeInfos.push(item);
+          }
+        }
+        this.visible = true;
+      },
+      close() {
+        this.nodeInfos = [];
+        this.visible = false;
+      },
+      handleCancel() {
+        this.nodeInfos = [];
+        this.visible = false;
+      },
+
+    }
+  }
+</script>
+
+<style scoped>
+  table.gridtable {
+    margin: 0 auto;
+    margin-top: 10px;
+    font-family: verdana, arial, sans-serif;
+    font-size: 12px;
+    color: #333333;
+    border-width: 1px;
+    border-color: #ddd;
+    border-collapse: collapse;
+  }
+
+  table.gridtable th {
+    border-width: 1px;
+    padding: 8px;
+    border-style: solid;
+    border-color: #ddd;
+    background-color: #eee;
+  }
+
+  table.gridtable td {
+    border-width: 1px;
+    padding: 8px;
+    border-style: solid;
+    border-color: #ddd;
+    background-color: #ffffff;
+  }
+</style>

+ 137 - 0
src/components/bpm/ProcessInstPicModal.vue

@@ -0,0 +1,137 @@
+<template>
+  <a-modal
+    :title="title"
+    :width="900"
+    :visible="visible"
+    :confirmLoading="confirmLoading"
+    @cancel="handleCancel"
+    :bodyStyle="bodyStyle"
+    style="top: 50px;"
+    destroyOnClose
+    :footer="null"
+    cancelText="关闭">
+
+    <a-spin :spinning="confirmLoading">
+      <img :src="picUrl" alt="流程图" usemap="#planetmap"/>
+      <map name="planetmap">
+        <template v-for="(item, key, index) in nodePositionInfo.positionList">
+          <area shape="rect" :coords="item.coords" title="Venus"
+                @mouseover="showNodeInfo(nodePositionInfo.hisTasks,item.id)">
+        </template>
+      </map>
+    </a-spin>
+    <proc-node-info-model ref="nodeInfoModel"></proc-node-info-model>
+  </a-modal>
+</template>
+
+<script>
+  import {getAction} from '@/api/manage'
+  import qs from 'qs';
+  import ProcNodeInfoModel from "./ProcNodeInfoModel.vue";
+
+  export default {
+    components: {ProcNodeInfoModel},
+    name: "ProcessInstPicModal",
+    data() {
+      return {
+        title: "操作",
+        visible: false,
+        nodePositionInfo: {},
+        model: {},
+        labelCol: {
+          xs: {span: 24},
+          sm: {span: 5},
+        },
+        wrapperCol: {
+          xs: {span: 24},
+          sm: {span: 16},
+        },
+        bodyStyle: {
+          "overflow-y": "auto",
+          "overflow-x": "auto",
+          height: (window.innerHeight - 280) + "px",
+        },
+        confirmLoading: false,
+        picUrl: "",
+        url: {
+          getProcessInfo: "/process/extActFlowData/getProcessInfo",
+          getNodePositionInfo: "/act/task/getNodePositionInfo",
+        },
+      }
+    },
+    created() {
+    },
+    methods: {
+      preview(flowCode, dataId) {
+        this.visible = true;
+        var params = {
+          flowCode: flowCode,
+          dataId: dataId
+        };//查询条件
+        this.confirmLoading = true;
+        getAction(this.url.getProcessInfo, params).then((res) => {
+          if (res.success) {
+            var processInstanceId = res.result.processInstanceId;
+            this.picUrl = this.getResourceURL(processInstanceId);
+            this.getNodePositionInfoData(processInstanceId);
+            console.log("---流程图----", this.picUrl)
+          } else {
+            this.$message.warning(res.message);
+          }
+        }).catch(e => {
+          console.error(e)
+        }).then(() => {
+          this.confirmLoading = false;
+        })
+
+
+      },
+      close() {
+        this.$emit('close');
+        this.visible = false;
+      },
+      handleCancel() {
+        this.close()
+      },
+      // 获取静态资源访问地址
+      getResourceURL(processInstanceId) {
+        var params = qs.stringify({
+          //'token': Cookies.get('token'),
+          '_t': Date.parse(new Date()) / 1000,
+          'processInstanceId': processInstanceId
+        })
+        return `${window._CONFIG['domianURL']}/act/process/processPic?${params}`
+      },
+      // 获取静态资源访问地址
+      getResourceURL(processInstanceId) {
+        var params = qs.stringify({
+          //'token': Cookies.get('token'),
+          '_t': Date.parse(new Date()) / 1000,
+          'processInstanceId': processInstanceId
+        })
+        return `${window._CONFIG['domianURL']}/act/process/processPic?${params}`
+      },
+
+      // 查询坐标信息数据
+      getNodePositionInfoData(processInstanceId) {
+        var params = {processInstanceId: processInstanceId};//查询条件
+        getAction(this.url.getNodePositionInfo, params).then(res => {
+          if (res.success) {
+            this.nodePositionInfo = res.result
+          }
+        }).catch(e => {
+          console.error(e)
+        }).then(() => {
+        })
+      },
+      showNodeInfo(data, taskId) {
+        this.$refs.nodeInfoModel.close();
+        this.$refs.nodeInfoModel.showInfo(data, taskId);
+      },
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 88 - 0
src/components/chart/AreaChartTy.vue

@@ -0,0 +1,88 @@
+<template>
+  <div :style="{ padding: '0' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+
+    <v-chart ref="chart" :forceFit="true" :height="height" :data="dataSource" :scale="scale">
+      <v-tooltip :shared="false"/>
+      <v-axis/>
+      <v-line position="x*y" :size="lineSize" :color="lineColor"/>
+      <v-area position="x*y" :color="color"/>
+    </v-chart>
+
+  </div>
+</template>
+
+<script>
+  import {triggerWindowResizeEvent} from '@/utils/util'
+
+  export default {
+    name: 'AreaChartTy',
+    props: {
+      // 图表数据
+      dataSource: {
+        type: Array,
+        required: true
+      },
+      // 图表标题
+      title: {
+        type: String,
+        default: ''
+      },
+      // x 轴别名
+      x: {
+        type: String,
+        default: 'x'
+      },
+      // y 轴别名
+      y: {
+        type: String,
+        default: 'y'
+      },
+      // Y轴最小值
+      min: {
+        type: Number,
+        default: 0
+      },
+      // Y轴最大值
+      max: {
+        type: Number,
+        default: null
+      },
+      // 图表高度
+      height: {
+        type: Number,
+        default: 254
+      },
+      // 线的粗细
+      lineSize: {
+        type: Number,
+        default: 2
+      },
+      // 面积的颜色
+      color: {
+        type: String,
+        default: ''
+      },
+      // 线的颜色
+      lineColor: {
+        type: String,
+        default: ''
+      }
+    },
+    computed: {
+      scale() {
+        return [
+          {dataKey: 'x', title: this.x, alias: this.x},
+          {dataKey: 'y', title: this.y, alias: this.y, min: this.min, max: this.max}
+        ]
+      }
+    },
+    mounted() {
+      triggerWindowResizeEvent()
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "chart";
+</style>

+ 50 - 0
src/components/chart/Bar.vue

@@ -0,0 +1,50 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart :forceFit="true" :height="height" :data="dataSource" :scale="scale" :padding="padding">
+      <v-tooltip/>
+      <v-axis/>
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import {triggerWindowResizeEvent} from '@/utils/util'
+
+  export default {
+    name: 'Bar',
+    props: {
+      dataSource: {
+        type: Array,
+        required: true
+      },
+      yaxisText: {
+        type: String,
+        default: 'y'
+      },
+      title: {
+        type: String,
+        default: ''
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {padding: ['auto', 'auto', '40', '50']}
+    },
+    computed: {
+      scale() {
+        return [{
+          dataKey: 'y',
+          alias: this.yaxisText
+        }]
+      }
+    },
+    mounted() {
+      triggerWindowResizeEvent()
+    }
+  }
+</script>

+ 57 - 0
src/components/chart/BarAndLine.vue

@@ -0,0 +1,57 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
+      <v-tooltip/>
+      <v-legend/>
+      <v-axis/>
+      <v-bar position="type*bar"/>
+      <v-line position="type*line" color="#2fc25b" :size="3"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+
+  export default {
+    name: 'BarMultid',
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      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}
+        ]
+      },
+      height: {
+        type: Number,
+        default: 400
+      }
+    },
+    data() {
+      return {
+        scale: [{
+          dataKey: 'bar',
+          min: 0
+        }, {
+          dataKey: 'line',
+          min: 0
+        }]
+      }
+    },
+    computed: {
+      data() {
+        return this.dataSource
+      }
+    }
+  }
+</script>

+ 90 - 0
src/components/chart/BarMultid.vue

@@ -0,0 +1,90 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart :forceFit="true" :height="height" :data="data">
+      <v-tooltip/>
+      <v-axis/>
+      <v-legend/>
+      <v-bar position="x*y" color="type" :adjust="adjust"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import {DataSet} from '@antv/data-set'
+
+  export default {
+    name: 'BarMultid',
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      dataSource: {
+        type: Array,
+        default: () => [
+          {
+            type: 'Jeecg',
+            'Jan.': 18.9,
+            'Feb.': 28.8,
+            'Mar.': 39.3,
+            'Apr.': 81.4,
+            'May': 47,
+            'Jun.': 20.3,
+            'Jul.': 24,
+            'Aug.': 35.6
+          },
+          {
+            type: 'Jeebt',
+            'Jan.': 12.4,
+            'Feb.': 23.2,
+            'Mar.': 34.5,
+            'Apr.': 99.7,
+            'May': 52.6,
+            'Jun.': 35.5,
+            'Jul.': 37.4,
+            'Aug.': 42.4
+          }
+        ]
+      },
+      fields: {
+        type: Array,
+        default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {
+        adjust: [{
+          type: 'dodge',
+          marginRatio: 1 / 32
+        }]
+      }
+    },
+    computed: {
+      data() {
+        const dv = new DataSet.View().source(this.dataSource)
+        dv.transform({
+          type: 'fold',
+          fields: this.fields,
+          key: 'x',
+          value: 'y'
+        })
+
+        // bar 使用不了 - 和 / 所以替换下
+        return dv.rows.map(row => {
+          row.x = row.x.replace(/[-/]/g, '_')
+          return row
+        })
+
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 187 - 0
src/components/chart/DashChartDemo.vue

@@ -0,0 +1,187 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <v-chart :forceFit="true" :height="350" :data="chartData" :scale="scale">
+      <v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
+      <v-axis
+        dataKey="value"
+        :zIndex="2"
+        :line="null"
+        :label="axisLabel"
+        :subTickCount="4"
+        :subTickLine="axisSubTickLine"
+        :tickLine="axisTickLine"
+        :grid="null"
+      ></v-axis>
+      <v-axis dataKey="1" :show="false"></v-axis>
+      <v-series
+        gemo="point"
+        position="value*1"
+        shape="pointer"
+        color="#1890FF"
+        :active="false"
+      ></v-series>
+      <v-guide
+        type="arc"
+        :zIndex="0"
+        :top="false"
+        :start="arcGuide1Start"
+        :end="arcGuide1End"
+        :vStyle="arcGuide1Style"
+      ></v-guide>
+      <v-guide
+        type="arc"
+        :zIndex="1"
+        :start="arcGuide2Start"
+        :end="getArcGuide2End"
+        :vStyle="arcGuide2Style"
+      ></v-guide>
+      <v-guide
+        type="html"
+        :position="htmlGuidePosition"
+        :html="getHtmlGuideHtml()"
+      ></v-guide>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import {registerShape} from 'viser-vue';
+
+  registerShape('point', 'pointer', {
+    draw(cfg, container) {
+      let point = cfg.points[0];
+      point = this.parsePoint(point);
+      const center = this.parsePoint({
+        x: 0,
+        y: 0,
+      });
+      container.addShape('line', {
+        attrs: {
+          x1: center.x,
+          y1: center.y,
+          x2: point.x,
+          y2: point.y + 15,
+          stroke: cfg.color,
+          lineWidth: 5,
+          lineCap: 'round',
+        }
+      });
+      return container.addShape('circle', {
+        attrs: {
+          x: center.x,
+          y: center.y,
+          r: 9.75,
+          stroke: cfg.color,
+          lineWidth: 4.5,
+          fill: '#fff',
+        }
+      });
+    }
+  });
+
+  const scale = [{
+    dataKey: 'value',
+    min: 0,
+    max: 9,
+    tickInterval: 1,
+    nice: false,
+  }];
+
+  const data = [
+    {value: 7.0},
+  ];
+
+  export default {
+    name: "DashChartDemo",
+    props: {
+      datasource: {
+        type: Number,
+        default: 7
+      },
+      title: {
+        type: String,
+        default: ''
+      }
+    },
+    created() {
+      if (!this.datasource) {
+        this.chartData = data;
+      } else {
+        this.chartData = [
+          {value: this.datasource},
+        ];
+      }
+      this.getChartData()
+    },
+    watch: {
+      'datasource': function (val) {
+        this.chartData = [
+          {value: val},
+        ];
+        this.getChartData();
+      }
+    },
+    methods: {
+      getChartData() {
+        if (this.chartData && this.chartData.length > 0) {
+          this.abcd = this.chartData[0].value * 10
+        } else {
+          this.abcd = 70
+        }
+      },
+      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' +
+          '</div>'
+      },
+      getArcGuide2End() {
+        return [this.chartData[0].value, 0.945]
+      }
+    },
+    data() {
+      return {
+        chartData: [],
+        height: 400,
+        scale: scale,
+        abcd: 70,
+        axisLabel: {
+          offset: -16,
+          textStyle: {
+            fontSize: 18,
+            textAlign: 'center',
+            textBaseline: 'middle'
+          }
+        },
+        axisSubTickLine: {
+          length: -8,
+          stroke: '#fff',
+          strokeOpacity: 1,
+        },
+        axisTickLine: {
+          length: -17,
+          stroke: '#fff',
+          strokeOpacity: 1,
+        },
+        arcGuide1Start: [0, 0.945],
+        arcGuide1End: [9, 0.945],
+        arcGuide1Style: {
+          stroke: '#CBCBCB',
+          lineWidth: 18,
+        },
+        arcGuide2Start: [0, 0.945],
+        arcGuide2Style: {
+          stroke: '#1890FF',
+          lineWidth: 18,
+        },
+        htmlGuidePosition: ['50%', '100%'],
+        htmlGuideHtml: `
+        <div style="width: 300px;text-align: center;">
+          <p style="font-size: 14px;color: #545454;margin: 0;">${this.title}</p>
+          <p style="font-size: 36px;color: #545454;margin: 0;">${this.abcd}%</p>
+        </div>
+      `,
+      };
+    },
+  };
+</script>

+ 77 - 0
src/components/chart/LineChartMultid.vue

@@ -0,0 +1,77 @@
+<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-tooltip/>
+      <v-axis/>
+      <v-legend/>
+      <v-line position="type*y" color="x"/>
+      <v-point position="type*y" color="x" :size="4" :v-style="style" :shape="'circle'"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import {DataSet} from '@antv/data-set'
+
+  export default {
+    name: 'LineChartMultid',
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      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}
+        ]
+      },
+      fields: {
+        type: Array,
+        default: () => ['jeecg', 'jeebt']
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {
+        scale: [{
+          dataKey: 'x',
+          min: 0,
+          max: 1
+        }],
+        style: {stroke: '#fff', lineWidth: 1}
+      }
+    },
+    computed: {
+      data() {
+        const dv = new DataSet.View().source(this.dataSource)
+        dv.transform({
+          type: 'fold',
+          fields: this.fields,
+          key: 'x',
+          value: 'y'
+        })
+        return dv.rows
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 80 - 0
src/components/chart/Liquid.vue

@@ -0,0 +1,80 @@
+<template>
+  <div>
+    <v-chart
+      :forceFit="true"
+      :height="height"
+      :width="width"
+      :data="data"
+      :scale="scale"
+      :padding="0">
+      <v-tooltip/>
+      <v-interval
+        :shape="['liquid-fill-gauge']"
+        position="transfer*value"
+        color=""
+        :v-style="{
+          lineWidth: 8,
+          opacity: 0.75
+        }"
+        :tooltip="[
+          'transfer*value',
+          (transfer, value) => {
+            return {
+              name: transfer,
+              value,
+            };
+          },
+        ]"
+      ></v-interval>
+      <v-guide
+        v-for="(row, index) in data"
+        :key="index"
+        type="text"
+        :top="true"
+        :position="{
+          gender: row.transfer,
+          value: 45
+        }"
+        :content="row.value + '%'"
+        :v-style="{
+          fontSize: 100,
+          textAlign: 'center',
+          opacity: 0.75,
+        }"
+      />
+    </v-chart>
+  </div>
+</template>
+
+<script>
+
+  const sourceDataConst = [
+    {transfer: '一月', value: 813},
+    {transfer: '二月', value: 233},
+    {transfer: '三月', value: 561}
+  ]
+
+  export default {
+    name: 'Liquid',
+    props: {
+      height: {
+        type: Number,
+        default: 0
+      },
+      width: {
+        type: Number,
+        default: 0
+      }
+    },
+    data() {
+      return {
+        data: sourceDataConst,
+        scale: []
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 69 - 0
src/components/chart/MiniArea.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="antv-chart-mini">
+    <div class="chart-wrapper" :style="{ height: 46 }">
+      <v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :padding="[36, 0, 18, 0]">
+        <v-tooltip/>
+        <v-smooth-area position="x*y"/>
+      </v-chart>
+    </div>
+  </div>
+</template>
+
+<script>
+  import moment from 'dayjs'
+
+  const sourceData = []
+  const beginDay = new Date().getTime()
+
+  for (let i = 0; i < 10; i++) {
+    sourceData.push({
+      x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
+      y: Math.round(Math.random() * 10)
+    })
+  }
+
+  export default {
+    name: 'MiniArea',
+    props: {
+      dataSource: {
+        type: Array,
+        default: () => []
+      },
+      // x 轴别名
+      x: {
+        type: String,
+        default: 'x'
+      },
+      // y 轴别名
+      y: {
+        type: String,
+        default: 'y'
+      }
+    },
+    data() {
+      return {
+        data: [],
+        height: 100
+      }
+    },
+    computed: {
+      scale() {
+        return [
+          {dataKey: 'x', title: this.x, alias: this.x},
+          {dataKey: 'y', title: this.y, alias: this.y}
+        ]
+      }
+    },
+    created() {
+      if (this.dataSource.length === 0) {
+        this.data = sourceData
+      } else {
+        this.data = this.dataSource
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "chart";
+</style>

+ 76 - 0
src/components/chart/MiniBar.vue

@@ -0,0 +1,76 @@
+<template>
+  <div :style="{'width':width==null?'auto':width+'px'}">
+    <v-chart :forceFit="width==null" :height="height" :data="data" padding="0">
+      <v-tooltip/>
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import moment from 'dayjs'
+
+  const sourceData = []
+  const beginDay = new Date().getTime()
+
+  for (let i = 0; i < 10; i++) {
+    sourceData.push({
+      x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
+      y: Math.round(Math.random() * 10)
+    })
+  }
+
+  const tooltip = [
+    'x*y',
+    (x, y) => ({
+      name: x,
+      value: y
+    })
+  ]
+
+  const scale = [{
+    dataKey: 'x',
+    min: 2
+  }, {
+    dataKey: 'y',
+    title: '时间',
+    min: 1,
+    max: 30
+  }]
+
+  export default {
+    name: 'MiniBar',
+    props: {
+      dataSource: {
+        type: Array,
+        default: () => []
+      },
+      width: {
+        type: Number,
+        default: null
+      },
+      height: {
+        type: Number,
+        default: 200
+      }
+    },
+    created() {
+      if (this.dataSource.length === 0) {
+        this.data = sourceData
+      } else {
+        this.data = this.dataSource
+      }
+    },
+    data() {
+      return {
+        tooltip,
+        data: [],
+        scale
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "chart";
+</style>

+ 76 - 0
src/components/chart/MiniProgress.vue

@@ -0,0 +1,76 @@
+<template>
+  <div class="chart-mini-progress">
+    <div class="target" :style="{ left: target + '%'}">
+      <span :style="{ backgroundColor: color }"/>
+      <span :style="{ backgroundColor: color }"/>
+    </div>
+    <div class="progress-wrapper">
+      <div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height+'px' }"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'MiniProgress',
+    props: {
+      target: {
+        type: Number,
+        default: 0
+      },
+      height: {
+        type: Number,
+        default: 10
+      },
+      color: {
+        type: String,
+        default: '#13C2C2'
+      },
+      percentage: {
+        type: Number,
+        default: 0
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .chart-mini-progress {
+    padding: 5px 0;
+    position: relative;
+    width: 100%;
+
+    .target {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+
+      span {
+        border-radius: 100px;
+        position: absolute;
+        top: 0;
+        left: 0;
+        height: 4px;
+        width: 2px;
+
+        &:last-child {
+          top: auto;
+          bottom: 0;
+        }
+      }
+    }
+
+    .progress-wrapper {
+      background-color: #f5f5f5;
+      position: relative;
+
+      .progress {
+        transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
+        border-radius: 1px 0 0 1px;
+        background-color: #1890ff;
+        width: 0;
+        height: 100%;
+      }
+    }
+  }
+</style>

+ 67 - 0
src/components/chart/Pie.vue

@@ -0,0 +1,67 @@
+<template>
+  <v-chart :forceFit="true" :height="height" :data="data" :scale="scale">
+    <v-tooltip :showTitle="false" dataKey="item*percent"/>
+    <v-axis/>
+    <v-legend dataKey="item"/>
+    <v-pie position="percent" color="item" :v-style="pieStyle" :label="labelConfig"/>
+    <v-coord type="theta"/>
+  </v-chart>
+</template>
+
+<script>
+  const DataSet = require('@antv/data-set')
+
+  export default {
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      height: {
+        type: Number,
+        default: 254
+      },
+      dataSource: {
+        type: Array,
+        default: () => [
+          {item: '示例一', count: 40},
+          {item: '示例二', count: 21},
+          {item: '示例三', count: 17},
+          {item: '示例四', count: 13},
+          {item: '示例五', count: 9}
+        ]
+      }
+    },
+    data() {
+      return {
+        scale: [{
+          dataKey: 'percent',
+          min: 0,
+          formatter: '.0%'
+        }],
+        pieStyle: {
+          stroke: '#fff',
+          lineWidth: 1
+        },
+        labelConfig: ['percent', {
+          formatter: (val, item) => {
+            return item.point.item + ': ' + val
+          }
+        }]
+      }
+    },
+    computed: {
+      data() {
+        let dv = new DataSet.View().source(this.dataSource)
+        // 计算数据百分比
+        dv.transform({
+          type: 'percent',
+          field: 'count',
+          dimension: 'item',
+          as: 'percent'
+        })
+        return dv.rows
+      }
+    }
+  }
+</script>

+ 367 - 0
src/components/chart/README.md

@@ -0,0 +1,367 @@
+# 报表组件文档
+
+## 柱状图
+
+##### 引用方式
+
+```js 
+import Bar from '@/components/chart/Bar'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明         |
+|------------|--------|----|------------|
+| title      | string |    | 报表标题       |
+| dataSource | array  | ✔️ | 报表数据源      |
+| height     | number |    | 报表高度,默认254 |
+
+##### dataSource 示例
+
+```json
+[
+    {
+        "x": "1月",
+        "y": 320
+    },
+    {
+        "x": "2月",
+        "y": 457
+    },
+    {
+        "x": "3月",
+        "y": 182
+    }
+]
+```
+
+##### 代码示例
+
+```html
+<template>
+    <bar title="柱状图" :dataSource="dataSource" :height="420"/>
+</template>
+
+<script>
+    import Bar from '@/components/chart/Bar'
+
+    export default {
+        name: 'ChartDemo',
+        components: {
+            Bar
+        },
+        data() {
+            return {
+                dataSource: [
+                    {
+                        "x": "1月",
+                        "y": 320
+                    },
+                    {
+                        "x": "2月",
+                        "y": 457
+                    },
+                    {
+                        "x": "3月",
+                        "y": 182
+                    }
+                ]
+            }
+        }
+    }
+</script>
+
+<style></style>
+```
+
+## 多列柱状图
+
+##### 引用方式
+
+```js 
+import BarMultid from '@/components/chart/BarMultid'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明         |
+|------------|--------|----|------------|
+| title      | string |    | 报表标题       |
+| fields     | array  |    | 主列字段列表     |
+| dataSource | array  |    | 报表数据源      |
+| height     | number |    | 报表高度,默认254 |
+
+##### fields 示例
+
+```json
+["Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug."]
+```
+
+##### dataSource 示例
+
+```json
+[
+    {
+        "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
+    }
+]
+```
+
+## 迷你柱状图
+
+不带标题和数据轴的柱状图
+
+##### 引用方式
+
+```js 
+import MiniBar from '@/components/chart/MiniBar'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明            |
+|------------|--------|----|---------------|
+| width      | number |    | 报表宽度度,默认自适应宽度 |
+| height     | number |    | 报表高度,默认200    |
+| dataSource | array  |    | 报表数据源         |
+
+##### dataSource 示例
+
+```json
+[
+    {
+        "x": "1月",
+        "y": 320
+    },
+    {
+        "x": "2月",
+        "y": 457
+    },
+    {
+        "x": "3月",
+        "y": 182
+    }
+]
+```
+
+## 面积图
+
+##### 引用方式
+
+```js 
+import AreaChartTy from '@/components/chart/AreaChartTy'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明         |
+|------------|--------|----|------------|
+| title      | string |    | 报表标题       |
+| dataSource | array  | ✔️ | 报表数据源      |
+| height     | number |    | 报表高度,默认254 |
+| lineSize   | number |    | 线的粗细,默认2   |
+
+##### dataSource 示例
+
+```json
+[
+    {
+        "x": "1月",
+        "y": 320
+    },
+    {
+        "x": "2月",
+        "y": 457
+    },
+    {
+        "x": "3月",
+        "y": 182
+    }
+]
+```
+
+## 多行折线图
+
+##### 引用方式
+
+```js 
+import LineChartMultid from '@/components/chart/LineChartMultid'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明         |
+|------------|--------|----|------------|
+| title      | string |    | 报表标题       |
+| fields     | array  |    | 主列字段列表     |
+| dataSource | array  |    | 报表数据源      |
+| height     | number |    | 报表高度,默认254 |
+
+##### fields 示例
+
+```json
+["jeecg", "jeebt"]
+```
+
+##### dataSource 示例
+
+```json
+[
+    {
+        "type": "Jan", // 列名
+        "jeecg": 7,
+        "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 },
+    { "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 }
+]
+```
+
+## 饼状图
+
+##### 引用方式
+
+```js 
+import Pie from '@/components/chart/Pie'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明         |
+|------------|--------|----|------------|
+| dataSource | array  |    | 报表数据源      |
+| height     | number |    | 报表高度,默认254 |
+
+##### dataSource 示例
+
+```json
+[
+    // 所有的 percent 相加等于 100
+    { "item": "一月", "percent": 40 },
+    { "item": "二月", "percent": 21 },
+    { "item": "三月", "percent": 17 },
+    { "item": "四月", "percent": 13 },
+    { "item": "五月", "percent": 9 }
+]
+```
+
+## 雷达图
+
+##### 引用方式
+
+```js 
+import Radar from '@/components/chart/Radar'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明         |
+|------------|--------|----|------------|
+| dataSource | array  |    | 报表数据源      |
+| height     | number |    | 报表高度,默认254 |
+
+##### dataSource 示例
+
+```json
+[
+    // score 最小值为 0,最大值为 100
+    { "item": "一月", "score": 40 },
+    { "item": "二月", "score": 20 },
+    { "item": "三月", "score": 67 },
+    { "item": "四月", "score": 43 },
+    { "item": "五月", "score": 90 }
+]
+```
+
+## 进度条
+
+##### 引用方式
+
+```js 
+import MiniProgress from '@/components/chart/MiniProgress'
+```
+
+##### 参数列表
+
+| 参数名        | 类型     | 必填 | 说明                |
+|------------|--------|----|-------------------|
+| percentage | number |    | 当前进度百分比,默认0,最高100 |
+| target     | number |    | 目标值,默认10          |
+| height     | number |    | 进度条高度,默认10        |
+| color      | string |    | 进度条颜色,默认 #13C2C2  |
+
+## 仪表盘
+
+##### 引用方式
+
+```js 
+import DashChartDemo from '@/components/chart/DashChartDemo'
+```
+
+##### 参数列表
+
+| 参数名    | 类型     | 必填 | 说明             |
+|--------|--------|----|----------------|
+| title  | string |    | 报表标题           |
+| value  | number |    | 当前值,默认6.7,最大为9 |
+| height | number |    | 报表高度,默认254     |
+
+## 排名列表
+
+##### 引用方式
+
+```js 
+import RankList from '@/components/chart/RankList'
+```
+
+##### 参数列表
+
+| 参数名    | 类型     | 必填 | 说明           |
+|--------|--------|----|--------------|
+| title  | string |    | 报表标题         |
+| list   | array  |    | 排名列表数据       |
+| height | number |    | 报表高度,默认自适应高度 |
+
+##### list 示例
+
+```json
+[
+    {
+        "name": "北京朝阳 1 号店",
+        "total": 1981
+    },
+    { "name": "北京朝阳 2 号店", "total": 1359 },
+    { "name": "北京朝阳 3 号店", "total": 1354 },
+    { "name": "北京朝阳 4 号店", "total": 263 },
+    { "name": "北京朝阳 5 号店", "total": 446 },
+    { "name": "北京朝阳 6 号店", "total": 796 }
+]
+```

+ 90 - 0
src/components/chart/Radar.vue

@@ -0,0 +1,90 @@
+<template>
+  <v-chart :forceFit="true" :height="height" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
+    <v-tooltip></v-tooltip>
+    <v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid"/>
+    <v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid"/>
+    <v-legend dataKey="user" marker="circle" :offset="30"/>
+    <v-coord type="polar" radius="0.8"/>
+    <v-line position="item*score" color="user" :size="2"/>
+    <v-point position="item*score" color="user" :size="4" shape="circle"/>
+  </v-chart>
+</template>
+
+<script>
+  const axis1Opts = {
+    dataKey: 'item',
+    line: null,
+    tickLine: null,
+    grid: {
+      lineStyle: {
+        lineDash: null
+      },
+      hideFirstLine: false
+    }
+  }
+  const axis2Opts = {
+    dataKey: 'score',
+    line: null,
+    tickLine: null,
+    grid: {
+      type: 'polygon',
+      lineStyle: {
+        lineDash: null
+      }
+    }
+  }
+
+  const scale = [
+    {
+      dataKey: 'score',
+      min: 0,
+      max: 100
+    }, {
+      dataKey: 'user',
+      alias: '类型'
+    }
+  ]
+
+  const sourceData = [
+    {item: '示例一', score: 40},
+    {item: '示例二', score: 20},
+    {item: '示例三', score: 67},
+    {item: '示例四', score: 43},
+    {item: '示例五', score: 90}
+  ]
+
+  export default {
+    name: 'Radar',
+    props: {
+      height: {
+        type: Number,
+        default: 254
+      },
+      dataSource: {
+        type: Array,
+        default: () => []
+      }
+    },
+    data() {
+      return {
+        axis1Opts,
+        axis2Opts,
+        scale,
+        data: sourceData
+      }
+    },
+    watch: {
+      dataSource(newVal) {
+        if (newVal.length === 0) {
+          this.data = sourceData
+        } else {
+          this.data = newVal
+        }
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 83 - 0
src/components/chart/RankList.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="rank">
+    <h4 class="title">{{ title }}</h4>
+    <ul class="list" :style="{height:height?`${height}px`:'auto',overflow:'auto'}">
+      <li :key="index" v-for="(item, index) in list">
+        <span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
+        <span>{{ item.name }}</span>
+        <span>{{ item.total }}</span>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "RankList",
+    // ['title', 'list']
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      list: {
+        type: Array,
+        default: null
+      },
+      height: {
+        type: Number,
+        default: null
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+
+  .rank {
+    padding: 0 32px 32px 72px;
+
+    .list {
+      margin: 25px 0 0;
+      padding: 0;
+      list-style: none;
+
+      li {
+        margin-top: 16px;
+
+        span {
+          color: rgba(0, 0, 0, .65);
+          font-size: 14px;
+          line-height: 22px;
+
+          &:first-child {
+            background-color: #f5f5f5;
+            border-radius: 20px;
+            display: inline-block;
+            font-size: 12px;
+            font-weight: 600;
+            margin-right: 24px;
+            height: 20px;
+            line-height: 20px;
+            width: 20px;
+            text-align: center;
+          }
+
+          &.active {
+            background-color: #314659;
+            color: #fff;
+          }
+
+          &:last-child {
+            float: right;
+          }
+        }
+      }
+    }
+  }
+
+  .mobile .rank {
+    padding: 0 32px 32px 32px;
+  }
+
+</style>

+ 66 - 0
src/components/chart/TransferBar.vue

@@ -0,0 +1,66 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart
+      :height="height"
+      :data="data"
+      :scale="scale"
+      :forceFit="true"
+      :padding="['auto', 'auto', '40', '50']">
+      <v-tooltip/>
+      <v-axis/>
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+
+  export default {
+    name: 'Bar',
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      x: {
+        type: String,
+        default: 'x'
+      },
+      y: {
+        type: String,
+        default: 'y'
+      },
+      data: {
+        type: Array,
+        default: () => []
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {}
+    },
+    computed: {
+      scale() {
+        return [
+          {dataKey: 'x', title: this.x, alias: this.x},
+          {dataKey: 'y', title: this.y, alias: this.y}
+        ]
+      }
+    },
+    created() {
+      // this.getMonthBar()
+    },
+    methods: {
+      // getMonthBar() {
+      //   this.$http.get('/analysis/month-bar')
+      //     .then(res => {
+      //       this.data = res.result
+      //     })
+      // }
+    }
+  }
+</script>

+ 85 - 0
src/components/chart/Trend.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="chart-trend">
+    {{ term }}
+    <span>{{ rate }}%</span>
+    <span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Trend",
+    props: {
+      // 同title
+      term: {
+        type: String,
+        default: '',
+        required: true
+      },
+      // 百分比
+      percentage: {
+        type: Number,
+        default: null
+      },
+      type: {
+        type: Boolean,
+        default: null
+      },
+      target: {
+        type: Number,
+        default: 0
+      },
+      value: {
+        type: Number,
+        default: 0
+      },
+      fixed: {
+        type: Number,
+        default: 2
+      }
+    },
+    data() {
+      return {
+        trend: this.type && 'up' || 'down',
+        rate: this.percentage
+      }
+    },
+    created() {
+      let type = this.type === null ? this.value >= this.target : this.type
+      this.trend = type ? 'up' : 'down';
+      this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .chart-trend {
+    display: inline-block;
+    font-size: 14px;
+    line-height: 22px;
+
+    .trend-icon {
+      font-size: 12px;
+
+      &.up, &.down {
+        margin-left: 4px;
+        position: relative;
+        top: 1px;
+
+        i {
+          font-size: 12px;
+          transform: scale(.83);
+        }
+      }
+
+      &.up {
+        color: #f5222d;
+      }
+
+      &.down {
+        color: #52c41a;
+        top: -1px;
+      }
+    }
+  }
+</style>

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

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

+ 81 - 0
src/components/dict/JDictSelectTag.vue

@@ -0,0 +1,81 @@
+<template>
+  <a-radio-group v-if="tagType=='radio'" @change="handleInput" :value="value" :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-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 }}
+      </span>
+    </a-select-option>
+  </a-select>
+</template>
+
+<script>
+  import {ajaxGetDictItems} from '@/api/api'
+
+  export default {
+    name: "JDictSelectTag",
+    props: {
+      dictCode: String,
+      placeholder: String,
+      triggerChange: Boolean,
+      disabled: Boolean,
+      value: String,
+      type: String
+    },
+    data() {
+      return {
+        dictOptions: [],
+        tagType: ""
+      }
+    },
+    created() {
+      console.log(this.dictCode);
+      if (!this.type || this.type === "list") {
+        this.tagType = "select"
+      } else {
+        this.tagType = this.type
+      }
+      //获取字典数据
+      this.initDictData();
+    },
+    methods: {
+      initDictData() {
+        //根据字典Code, 初始化字典数组
+        ajaxGetDictItems(this.dictCode, null).then((res) => {
+          if (res.success) {
+//                console.log(res.result);
+            this.dictOptions = res.result;
+          }
+        })
+      },
+      handleInput(e) {
+        let val;
+        if (this.tagType == "radio") {
+          val = e.target.value
+        } else {
+          val = e
+        }
+        console.log(val);
+        if (this.triggerChange) {
+          this.$emit('change', val);
+        } else {
+          this.$emit('input', val);
+        }
+      },
+      setCurrentDictOptions(dictOptions) {
+        this.dictOptions = dictOptions
+      },
+      getCurrentDictOptions() {
+        return this.dictOptions
+      }
+    }
+  }
+</script>
+
+<style scoped>
+</style>

+ 87 - 0
src/components/dict/JDictSelectUtil.js

@@ -0,0 +1,87 @@
+/**
+ * 字典 util
+ * author: scott
+ * date: 20190109
+ */
+
+import {ajaxGetDictItems} from '@/api/api'
+import {getAction} from '@/api/manage'
+
+/**
+ * 获取字典数组
+ * @param dictCode 字典Code
+ * @return List<Map>
+ */
+export async function initDictOptions(dictCode) {
+  if (!dictCode) {
+    return '字典Code不能为空!';
+  }
+  //获取字典数组
+  let res = await ajaxGetDictItems(dictCode);
+  return res;
+}
+
+/**
+ * 字典值替换文本通用方法
+ * @param dictOptions  字典数组
+ * @param text  字典值
+ * @return String
+ */
+export function filterDictText(dictOptions, text) {
+  let re = "";
+  dictOptions.forEach(function (option) {
+    if (text === option.value) {
+      re = option.text;
+    }
+  });
+  return re;
+}
+
+/**
+ * 字典值替换文本通用方法(多选)
+ * @param dictOptions  字典数组
+ * @param text  字典值
+ * @return String
+ */
+export function filterMultiDictText(dictOptions, text) {
+  if (!text) {
+    return ""
+  }
+  let re = "";
+  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 (re == "") {
+    return "";
+  }
+  return re.substring(0, re.length - 1);
+}
+
+/**
+ * 翻译字段值对应的文本
+ * @param children
+ * @returns string
+ */
+export async function ajaxFilterDictText(dictCode, key) {
+  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 '';
+  }
+}

+ 96 - 0
src/components/dict/JMultiSelectTag.vue

@@ -0,0 +1,96 @@
+<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-group>
+
+  <a-select
+    v-else-if="tagType=='select'"
+    :value="arrayValue"
+    @change="onChange"
+    :disabled="disabled"
+    mode="multiple"
+    :placeholder="placeholder">
+    <a-select-option
+      v-for="(item,index) in dictOptions"
+      :key="index"
+      :value="item.value">
+      <span style="display: inline-block;width: 100%" :title=" item.text || item.label ">
+        {{ item.text || item.label }}
+      </span>
+    </a-select-option>
+  </a-select>
+
+</template>
+
+<script>
+  import {ajaxGetDictItems} from '@/api/api'
+
+  export default {
+    name: 'JMultiSelectTag',
+    props: {
+      dictCode: String,
+      placeholder: String,
+      disabled: Boolean,
+      value: String,
+      type: String,
+      options: Array
+    },
+    data() {
+      return {
+        dictOptions: [],
+        tagType: "",
+        arrayValue: !this.value ? [] : this.value.split(",")
+      }
+    },
+    created() {
+      if (!this.type || this.type === "list_multi") {
+        this.tagType = "select"
+      } else {
+        this.tagType = this.type
+      }
+      //获取字典数据
+      this.initDictData();
+    },
+    watch: {
+      options: function (val) {
+        this.setCurrentDictOptions(val);
+      },
+      value(val) {
+        if (!val) {
+          this.arrayValue = []
+        } else {
+          this.arrayValue = this.value.split(",")
+        }
+      }
+    },
+    methods: {
+      initDictData() {
+        if (this.options && this.options.length > 0) {
+          this.dictOptions = [...this.options]
+        } else {
+          //根据字典Code, 初始化字典数组
+          ajaxGetDictItems(this.dictCode, null).then((res) => {
+            if (res.success) {
+              this.dictOptions = res.result;
+            }
+          })
+        }
+
+      },
+      onChange(selectedValue) {
+        this.$emit('change', selectedValue.join(","));
+      },
+      setCurrentDictOptions(dictOptions) {
+        this.dictOptions = dictOptions
+      },
+      getCurrentDictOptions() {
+        return this.dictOptions
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 175 - 0
src/components/dict/JSearchSelectTag.vue

@@ -0,0 +1,175 @@
+<template>
+
+  <a-select
+    v-if="async"
+    showSearch
+    labelInValue
+    @search="loadData"
+    :placeholder="placeholder"
+    v-model="selectedAsyncValue"
+    style="width: 100%"
+    :filterOption="false"
+    @change="handleAsyncChange"
+    :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>
+  </a-select>
+
+  <a-select
+    v-else
+    showSearch
+    :placeholder="placeholder"
+    optionFilterProp="children"
+    style="width: 100%"
+    @change="handleChange"
+    :filterOption="filterOption"
+    v-model="selectedValue"
+    :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>
+  </a-select>
+
+</template>
+
+<script>
+  import {ajaxGetDictItems} from '@/api/api'
+  import debounce from 'lodash/debounce';
+  import {getAction} from '../../api/manage'
+
+  export default {
+    name: 'JSearchSelectTag',
+    props: {
+
+      disabled: Boolean,
+      value: String,
+      dict: String,
+      dictOptions: Array,
+      async: Boolean,
+      placeholder: {
+        type: String,
+        default: "请选择",
+        required: false
+      }
+    },
+    data() {
+      this.loadData = debounce(this.loadData, 800);//消抖
+      this.lastLoad = 0;
+      return {
+        loading: false,
+        selectedValue: [],
+        selectedAsyncValue: [],
+        options: [],
+      }
+    },
+    created() {
+      this.initDictData();
+    },
+    watch: {
+      "value": {
+        immediate: true,
+        handler(val) {
+          if (!val) {
+            this.selectedValue = []
+            this.selectedAsyncValue = []
+          } else {
+            this.initSelectValue()
+          }
+        }
+
+      },
+      "dict": {
+        handler() {
+          this.initDictData()
+        }
+      }
+    },
+    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) {
+                let obj = {
+                  key: this.value,
+                  label: res.result
+                }
+                this.selectedAsyncValue = {...obj}
+              }
+            })
+          }
+        } else {
+          this.selectedValue = this.value
+        }
+      },
+      loadData(value) {
+        console.log("数据加载", value)
+        this.lastLoad += 1
+        const currentLoad = this.lastLoad
+        this.options = []
+        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) {
+              return
+            }
+            this.options = res.result
+            console.log("我是第一个", res)
+          } else {
+            this.$message.warning(res.message)
+          }
+
+        })
+
+      },
+      initDictData() {
+        if (!this.async) {
+          //如果字典项集合有数据
+          if (this.dictOptions && this.dictOptions.length > 0) {
+            this.options = [...this.dictOptions]
+          } else {
+            //根据字典Code, 初始化字典数组
+            ajaxGetDictItems(this.dict, 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)
+        this.selectedValue = selectedValue
+        this.callback()
+      },
+      handleAsyncChange(selectedObj) {
+        this.selectedAsyncValue = selectedObj
+        this.selectedValue = selectedObj.key
+        this.callback()
+      },
+      callback() {
+        this.$emit('change', this.selectedValue);
+      },
+      setCurrentDictOptions(dictOptions) {
+        this.options = dictOptions
+      },
+      getCurrentDictOptions() {
+        return this.options
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

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

@@ -0,0 +1,180 @@
+# JDictSelectTag 组件用法
+----
+- 从字典表获取数据,dictCode格式说明: 字典code
+```html
+<j-dict-select-tag  v-model="queryParam.sex" placeholder="请输入用户性别"
+                  dictCode="sex"/>
+```
+
+v-decorator用法:
+```html
+<j-dict-select-tag  v-decorator="['sex', {}]" :triggerChange="true" placeholder="请输入用户性别"
+                  dictCode="sex"/>
+```
+
+- 从数据库表获取字典数据,dictCode格式说明: 表名,文本字段,取值字段
+```html
+<j-dict-select-tag v-model="queryParam.username" placeholder="请选择用户名称" 
+                   dictCode="sys_user,realname,id"/>
+```
+
+
+
+# JDictSelectUtil.js 列表字典函数用法
+----
+
+- 第一步: 引入依赖方法
+```html
+       import {initDictOptions, filterDictText} from '@/components/dict/JDictSelectUtil'
+```
+
+- 第二步: 在created()初始化方法执行字典配置方法
+```html
+      //初始化字典配置
+      this.initDictConfig();
+```
+      
+- 第三步: 实现initDictConfig方法,加载列表所需要的字典(列表上有多个字典项,就执行多次initDictOptions方法)
+      
+```html
+      initDictConfig() {
+        //初始化字典 - 性别
+        initDictOptions('sex').then((res) => {
+          if (res.success) {
+            this.sexDictOptions = res.result;
+          }
+        });
+      },
+```
+      
+- 第四步: 实现字段的customRender方法
+```html
+     customRender: (text, record, index) => {
+       //字典值替换通用方法
+       return filterDictText(this.sexDictOptions, text);
+     }
+```
+
+
+# JMultiSelectTag 多选组件
+下拉/checkbox
+
+## 参数配置
+| 参数           | 类型   | 必填 |说明|
+|--------------|---------|----|---------|
+| placeholder      |string   | | placeholder |
+| disabled      |Boolean   | | 是否禁用 |
+| type      |string   | | 多选类型 select/checkbox 默认是select |
+| dictCode      |string   | | 数据字典编码或者表名,显示字段名,存储字段名拼接而成的字符串,如果提供了options参数 则此参数可不填|
+| options      |Array   | | 多选项,如果dictCode参数未提供,可以设置此参数加载多选项 |
+
+使用示例
+----
+```vue
+<template>
+  <a-form>
+    <a-form-item label="下拉多选" style="width: 300px">
+      <j-multi-select-tag
+        v-model="selectValue"
+        :options="dictOptions"
+        placeholder="请做出你的选择">
+      </j-multi-select-tag>
+      {{ selectValue }}
+    </a-form-item>
+
+    <a-form-item label="checkbox">
+      <j-multi-select-tag
+        v-model="checkboxValue"
+        :options="dictOptions"
+        type="checkbox">
+      </j-multi-select-tag>
+      {{ checkboxValue }}
+    </a-form-item>
+  </a-form >
+</template>
+
+<script>
+  import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
+  export default {
+    components: {JMultiSelectTag},
+    data() {
+      return {
+        selectValue:"",
+        checkboxValue:"",
+        dictOptions:[{
+          label:"选项一",
+          value:"1"
+        },{
+          label:"选项二",
+          value:"2"
+        },{
+          label:"选项三",
+          value:"3"
+        }]
+      }
+    }
+  }
+</script>
+```
+
+# JSearchSelectTag 字典表的搜索组件
+下拉搜索组件,支持异步加载,异步加载用于大数据量的字典表
+
+## 参数配置
+| 参数           | 类型   | 必填 |说明|
+|--------------|---------|----|---------|
+| placeholder      |string   | | placeholder |
+| disabled      |Boolean   | | 是否禁用 |
+| dict      |string   | | 表名,显示字段名,存储字段名拼接而成的字符串,如果提供了dictOptions参数 则此参数可不填|
+| dictOptions      |Array   | | 多选项,如果dict参数未提供,可以设置此参数加载多选项 |
+| async      |Boolean   | | 是否支持异步加载,设置成true,则通过输入的内容加载远程数据,否则在本地过滤数据,默认false|
+
+使用示例
+----
+```vue
+<template>
+  <a-form>
+    <a-form-item label="下拉搜索" style="width: 300px">
+      <j-search-select-tag
+        placeholder="请做出你的选择"
+        v-model="selectValue"
+        :dictOptions="dictOptions">
+      </j-search-select-tag>
+      {{ selectValue }}
+    </a-form-item>
+
+    <a-form-item label="异步加载" style="width: 300px">
+      <j-search-select-tag
+        placeholder="请做出你的选择"
+        v-model="asyncSelectValue"
+        dict="sys_depart,depart_name,id"
+        :async="true">
+      </j-search-select-tag>
+      {{ asyncSelectValue }}
+    </a-form-item>
+  </a-form >
+</template>
+
+<script>
+  import JSearchSelectTag from '@/components/dict/JSearchSelectTag'
+  export default {
+    components: {JSearchSelectTag},
+    data() {
+      return {
+        selectValue:"",
+        asyncSelectValue:"",
+        dictOptions:[{
+          text:"选项一",
+          value:"1"
+        },{
+          text:"选项二",
+          value:"2"
+        },{
+          text:"选项三",
+          value:"3"
+        }]
+      }
+    }
+  }
+</script>
+```

+ 8 - 0
src/components/dict/index.js

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

+ 4 - 0
src/components/index.less

@@ -0,0 +1,4 @@
+@import "~ant-design-vue/lib/style/index";
+
+// The prefix to use on all css classes from ant-pro.
+@ant-pro-prefix: ant-pro;

+ 43 - 0
src/components/jeecg/JCheckbox.vue

@@ -0,0 +1,43 @@
+<template>
+  <a-checkbox-group :options="options" :value="checkboxArray" @change="onChange"/>
+</template>
+
+<script>
+  export default {
+    name: 'JCheckbox',
+    props: {
+      value: {
+        type: String,
+        required: false
+      },
+      /*label value*/
+      options: {
+        type: Array,
+        required: true
+      }
+    },
+    data() {
+      return {
+        checkboxArray: !this.value ? [] : this.value.split(",")
+      }
+    },
+    watch: {
+      value(val) {
+        if (!val) {
+          this.checkboxArray = []
+        } else {
+          this.checkboxArray = this.value.split(",")
+        }
+      }
+    },
+    methods: {
+      onChange(checkedValues) {
+        this.$emit('change', checkedValues.join(","));
+      },
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 416 - 0
src/components/jeecg/JCodeEditor.vue

@@ -0,0 +1,416 @@
+<template>
+  <div v-bind="fullScreenParentProps">
+    <a-icon v-if="fullScreen" class="full-screen-icon" type="fullscreen" @click="()=>fullCoder=!fullCoder"/>
+
+    <div class="code-editor-cust full-screen-child">
+      <textarea ref="textarea"></textarea>
+      <span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
+      <template v-if="languageChange">
+        <a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
+          <a-select-option
+            v-for="mode in modes"
+            :key="mode.value"
+            :value="mode.value">
+            {{ mode.label }}
+          </a-select-option>
+        </a-select>
+      </template>
+
+    </div>
+  </div>
+</template>
+
+<script type="text/ecmascript-6">
+  // 引入全局实例
+  import _CodeMirror from 'codemirror'
+
+  // 核心样式
+  import 'codemirror/lib/codemirror.css'
+  // 引入主题后还需要在 options 中指定主题才会生效 darcula  gruvbox-dark hopscotch  monokai
+  import 'codemirror/theme/panda-syntax.css'
+  //提示css
+  import "codemirror/addon/hint/show-hint.css";
+
+  // 需要引入具体的语法高亮库才会有对应的语法高亮效果
+  // codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
+  // 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
+  import 'codemirror/mode/javascript/javascript.js'
+  import 'codemirror/mode/css/css.js'
+  import 'codemirror/mode/xml/xml.js'
+  import 'codemirror/mode/clike/clike.js'
+  import 'codemirror/mode/markdown/markdown.js'
+  import 'codemirror/mode/python/python.js'
+  import 'codemirror/mode/r/r.js'
+  import 'codemirror/mode/shell/shell.js'
+  import 'codemirror/mode/sql/sql.js'
+  import 'codemirror/mode/swift/swift.js'
+  import 'codemirror/mode/vue/vue.js'
+
+  // 尝试获取全局实例
+  const CodeMirror = window.CodeMirror || _CodeMirror
+
+  export default {
+    name: 'JCodeEditor',
+    props: {
+      // 外部传入的内容,用于实现双向绑定
+      value: {
+        type: String,
+        default: ''
+      },
+      // 外部传入的语法类型
+      language: {
+        type: String,
+        default: null
+      },
+      languageChange: {
+        type: Boolean,
+        default: false,
+        required: false
+      },
+      placeholder: {
+        type: String,
+        default: null
+      },
+      // 显示行号
+      lineNumbers: {
+        type: Boolean,
+        default: true
+      },
+      // 是否显示全屏按钮
+      fullScreen: {
+        type: Boolean,
+        default: false
+      },
+      // 全屏以后的z-index
+      zIndex: {
+        type: [Number, String],
+        default: 999
+      }
+    },
+    data() {
+      return {
+        // 内部真实的内容
+        code: '',
+        hasCode: false,
+        // 默认的语法类型
+        mode: 'javascript',
+        // 编辑器实例
+        coder: null,
+        // 默认配置
+        options: {
+          // 缩进格式
+          tabSize: 2,
+          // 主题,对应主题库 JS 需要提前引入
+          theme: 'panda-syntax',
+          line: true,
+          // extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
+          hintOptions: {
+            tables: {
+              users: ['name', 'score', 'birthDate'],
+              countries: ['name', 'population', 'size']
+            }
+          },
+        },
+        // 支持切换的语法高亮类型,对应 JS 已经提前引入
+        // 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
+        modes: [{
+          value: 'css',
+          label: 'CSS'
+        }, {
+          value: 'javascript',
+          label: 'Javascript'
+        }, {
+          value: 'html',
+          label: 'XML/HTML'
+        }, {
+          value: 'x-java',
+          label: 'Java'
+        }, {
+          value: 'x-objectivec',
+          label: 'Objective-C'
+        }, {
+          value: 'x-python',
+          label: 'Python'
+        }, {
+          value: 'x-rsrc',
+          label: 'R'
+        }, {
+          value: 'x-sh',
+          label: 'Shell'
+        }, {
+          value: 'x-sql',
+          label: 'SQL'
+        }, {
+          value: 'x-swift',
+          label: 'Swift'
+        }, {
+          value: 'x-vue',
+          label: 'Vue'
+        }, {
+          value: 'markdown',
+          label: 'Markdown'
+        }],
+        // code 编辑器 是否全屏
+        fullCoder: false
+      }
+    },
+    watch: {
+      // value: {
+      //   immediate: false,
+      //   handler(value) {
+      //     this._getCoder().then(() => {
+      //       this.coder.setValue(value)
+      //     })
+      //   }
+      // },
+      language: {
+        immediate: true,
+        handler(language) {
+          this._getCoder().then(() => {
+            // 尝试从父容器获取语法类型
+            if (language) {
+              // 获取具体的语法类型对象
+              let modeObj = this._getLanguage(language)
+
+              // 判断父容器传入的语法是否被支持
+              if (modeObj) {
+                this.mode = modeObj.label
+                this.coder.setOption('mode', `text/${modeObj.value}`)
+              }
+            }
+          })
+        }
+      }
+    },
+    computed: {
+      placeholderShow() {
+        if (this.placeholder == null) {
+          return `请在此输入${this.language}代码`
+        } else {
+          return this.placeholder
+        }
+      },
+      nullTipStyle() {
+        if (this.lineNumbers) {
+          return {left: '36px'}
+        } else {
+          return {left: '12px'}
+        }
+      },
+      // coder 配置
+      coderOptions() {
+        return {
+          tabSize: this.options.tabSize,
+          theme: this.options.theme,
+          lineNumbers: this.lineNumbers,
+          line: true,
+          hintOptions: this.options.hintOptions
+        }
+      },
+      fullScreenParentProps() {
+        let props = {
+          class: ['full-screen-parent', this.fullCoder ? 'full-screen' : ''],
+          style: {}
+        }
+        if (this.fullCoder) {
+          props.style['z-index'] = this.zIndex
+        }
+        return props
+      }
+    },
+    mounted() {
+      // 初始化
+      this._initialize()
+    },
+    methods: {
+      // 初始化
+      _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
+        }
+        // 支持双向绑定
+        this.coder.on('change', (coder) => {
+          this.code = coder.getValue()
+          if (this.code) {
+            this.hasCode = true
+          } else {
+            this.hasCode = false
+          }
+          if (this.$emit) {
+            this.$emit('input', this.code)
+          }
+        })
+        this.coder.on('focus', () => {
+          this.hasCode = true
+        })
+        this.coder.on('blur', () => {
+          if (this.code) {
+            this.hasCode = true
+          } else {
+            this.hasCode = false
+          }
+        })
+
+        /* this.coder.on('cursorActivity',()=>{
+           this.coder.showHint()
+         })*/
+
+      },
+      getCodeContent() {
+        return this.code
+      },
+      setCodeContent(val) {
+        this.coder.setValue(val)
+      },
+      // 获取当前语法类型
+      _getLanguage(language) {
+        // 在支持的语法类型列表中寻找传入的语法类型
+        return this.modes.find((mode) => {
+          // 所有的值都忽略大小写,方便比较
+          let currentLanguage = language.toLowerCase()
+          let currentLabel = mode.label.toLowerCase()
+          let currentValue = mode.value.toLowerCase()
+
+          // 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
+          return currentLabel === currentLanguage || currentValue === currentLanguage
+        })
+      },
+      _getCoder() {
+        let _this = this
+        return new Promise((resolve) => {
+          (function get() {
+            if (_this.coder) {
+              resolve(_this.coder)
+            } else {
+              setTimeout(get, 10)
+            }
+          })()
+        })
+      },
+      // 更改模式
+      changeMode(val) {
+        // 修改编辑器的语法配置
+        this.coder.setOption('mode', `text/${val}`)
+
+        // 获取修改后的语法
+        let label = this._getLanguage(val).label.toLowerCase()
+
+        // 允许父容器通过以下函数监听当前的语法值
+        this.$emit('language-change', label)
+      },
+      nullTipClick() {
+        this.coder.focus()
+      }
+    }
+  }
+</script>
+
+<style lang="less">
+  .code-editor-cust {
+    flex-grow: 1;
+    display: flex;
+    position: relative;
+    height: 100%;
+
+    .CodeMirror {
+      flex-grow: 1;
+      z-index: 1;
+
+      .CodeMirror-code {
+        line-height: 19px;
+      }
+
+    }
+
+    .code-mode-select {
+      position: absolute;
+      z-index: 2;
+      right: 10px;
+      top: 10px;
+      max-width: 130px;
+    }
+
+    .CodeMirror {
+      height: auto;
+      min-height: 100%;
+    }
+
+    .null-tip {
+      position: absolute;
+      top: 4px;
+      left: 36px;
+      z-index: 10;
+      color: #ffffffc9;
+      line-height: initial;
+    }
+
+    .null-tip-hidden {
+      display: none;
+    }
+  }
+
+  /* 全屏样式 */
+  .full-screen-parent {
+    position: relative;
+
+    .full-screen-icon {
+      opacity: 0;
+      color: black;
+      width: 20px;
+      height: 20px;
+      line-height: 24px;
+      background-color: white;
+      position: absolute;
+      top: 2px;
+      right: 2px;
+      z-index: 9;
+      cursor: pointer;
+      transition: opacity 0.3s;
+    }
+
+    &:hover {
+      .full-screen-icon {
+        opacity: 1;
+
+        &:hover {
+          background-color: rgba(255, 255, 255, 0.88);
+        }
+      }
+    }
+
+    &.full-screen {
+      position: fixed;
+      top: 10px;
+      left: 10px;
+      width: calc(100% - 20px);
+      height: calc(100% - 20px);
+      padding: 10px;
+      background-color: #f5f5f5;
+
+      .full-screen-icon {
+        top: 12px;
+        right: 12px;
+      }
+
+      .full-screen-child {
+        height: 100%;
+        max-height: 100%;
+        min-height: 100%;
+      }
+    }
+
+    .full-screen-child {
+      min-height: 120px;
+      max-height: 320px;
+    }
+
+  }
+
+
+</style>

+ 86 - 0
src/components/jeecg/JDate.vue

@@ -0,0 +1,86 @@
+<template>
+  <a-date-picker
+    :disabled="disabled || readOnly"
+    :placeholder="placeholder"
+    @change="handleDateChange"
+    :value="momVal"
+    :showTime="showTime"
+    :format="dateFormat"
+    :getCalendarContainer="getCalendarContainer"
+  />
+</template>
+<script>
+  import moment from 'moment'
+
+  export default {
+    name: 'JDate',
+    props: {
+      placeholder: {
+        type: String,
+        default: '',
+        required: false
+      },
+      value: {
+        type: String,
+        required: false
+      },
+      dateFormat: {
+        type: String,
+        default: 'YYYY-MM-DD',
+        required: false
+      },
+      //此属性可以被废弃了
+      triggerChange: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      readOnly: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      disabled: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      showTime: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      getCalendarContainer: {
+        type: Function,
+        default: () => document.body
+      }
+    },
+    data() {
+      let dateStr = this.value;
+      return {
+        decorator: "",
+        momVal: !dateStr ? null : moment(dateStr, this.dateFormat)
+      }
+    },
+    watch: {
+      value(val) {
+        if (!val) {
+          this.momVal = null
+        } else {
+          this.momVal = moment(val, this.dateFormat)
+        }
+      }
+    },
+    methods: {
+      moment,
+      handleDateChange(mom, dateStr) {
+        this.$emit('change', dateStr);
+      }
+    },
+    //2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

文件差異過大導致無法顯示
+ 1669 - 0
src/components/jeecg/JEditableTable.vue


+ 105 - 0
src/components/jeecg/JEditor.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="tinymce-editor">
+    <editor
+      v-model="myValue"
+      :init="init"
+      :disabled="disabled"
+      @onClick="onClick">
+    </editor>
+  </div>
+</template>
+
+<script>
+  import tinymce from 'tinymce/tinymce'
+  import Editor from '@tinymce/tinymce-vue'
+  import 'tinymce/themes/silver/theme'
+  import 'tinymce/plugins/image'
+  import 'tinymce/plugins/media'
+  import 'tinymce/plugins/table'
+  import 'tinymce/plugins/lists'
+  import 'tinymce/plugins/contextmenu'
+  import 'tinymce/plugins/wordcount'
+  import 'tinymce/plugins/colorpicker'
+  import 'tinymce/plugins/textcolor'
+  import 'tinymce/plugins/fullscreen'
+
+  export default {
+    components: {
+      Editor
+    },
+    props: {
+      value: {
+        type: String,
+        required: false
+      },
+      triggerChange: {
+        type: Boolean,
+        default: false,
+        required: false
+      },
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      plugins: {
+        type: [String, Array],
+        default: 'lists image 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'
+      }
+    },
+    data() {
+      return {
+        //初始化配置
+        init: {
+          language_url: '/tinymce/langs/zh_CN.js',
+          language: 'zh_CN',
+          skin_url: '/tinymce/skins/lightgray',
+          height: 300,
+          plugins: this.plugins,
+          toolbar: this.toolbar,
+          branding: false,
+          menubar: false,
+          images_upload_handler: (blobInfo, success) => {
+            const img = 'data:image/jpeg;base64,' + blobInfo.base64()
+            success(img)
+          }
+        },
+        myValue: this.value
+      }
+    },
+    mounted() {
+      tinymce.init({})
+    },
+    methods: {
+
+      onClick(e) {
+        this.$emit('onClick', e, tinymce)
+      },
+      //可以添加一些自己的自定义事件,如清空内容
+      clear() {
+        this.myValue = ''
+      }
+    },
+    watch: {
+      value(newValue) {
+        this.myValue = newValue
+      },
+      myValue(newValue) {
+        console.log(newValue)
+        if (this.triggerChange) {
+          console.log(1)
+          this.$emit('change', newValue)
+        } else {
+          console.log(2)
+          this.$emit('input', newValue)
+        }
+      }
+    }
+  }
+
+</script>
+<style scoped>
+</style>

+ 29 - 0
src/components/jeecg/JEllipsis.vue

@@ -0,0 +1,29 @@
+<template>
+  <a-tooltip placement="topLeft">
+    <template slot="title">
+      <span>{{value}}</span>
+    </template>
+    {{ value | ellipsis(length) }}
+  </a-tooltip>
+</template>
+
+<script>
+  export default {
+    name: 'JEllipsis',
+    props: {
+      value: {
+        type: String,
+        required: false,
+      },
+      length: {
+        type: Number,
+        required: false,
+        default: 25,
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 50 - 0
src/components/jeecg/JFormContainer.vue

@@ -0,0 +1,50 @@
+<template>
+  <div v-if="disabled" class="jeecg-form-container-disabled">
+    <fieldset disabled>
+      <slot></slot>
+    </fieldset>
+  </div>
+  <div v-else>
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+  /**
+   * 使用方法
+   * 在form下直接写这个组件就行了,
+   *<a-form layout="inline" :form="form" >
+   *     <j-form-container :disabled="true">
+   *         <!-- 表单内容省略..... -->
+   *     </j-form-container>
+   *</a-form>
+   */
+  export default {
+    name: 'JFormContainer',
+    props: {
+      disabled: {
+        type: Boolean,
+        default: false,
+        required: false
+      }
+    },
+    mounted() {
+      console.log("我是表单禁用专用组件,但是我并不支持表单中iframe的内容禁用")
+    }
+  }
+</script>
+<style>
+  .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 {
+    -ms-pointer-events: none;
+    pointer-events: none;
+  }
+</style>

+ 163 - 0
src/components/jeecg/JGraphicCode.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="gc-canvas" @click="reloadPic">
+    <canvas id="gc-canvas" :width="contentWidth" :height="contentHeight"></canvas>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'JGraphicCode',
+    props: {
+      length: {
+        type: Number,
+        default: 4
+      },
+      fontSizeMin: {
+        type: Number,
+        default: 20
+      },
+      fontSizeMax: {
+        type: Number,
+        default: 45
+      },
+      backgroundColorMin: {
+        type: Number,
+        default: 180
+      },
+      backgroundColorMax: {
+        type: Number,
+        default: 240
+      },
+      colorMin: {
+        type: Number,
+        default: 50
+      },
+      colorMax: {
+        type: Number,
+        default: 160
+      },
+      lineColorMin: {
+        type: Number,
+        default: 40
+      },
+      lineColorMax: {
+        type: Number,
+        default: 180
+      },
+      dotColorMin: {
+        type: Number,
+        default: 0
+      },
+      dotColorMax: {
+        type: Number,
+        default: 255
+      },
+      contentWidth: {
+        type: Number,
+        default: 136
+      },
+      contentHeight: {
+        type: Number,
+        default: 38
+      }
+    },
+    methods: {
+      // 生成一个随机数
+      randomNum(min, max) {
+        return Math.floor(Math.random() * (max - min) + min)
+      },
+      // 生成一个随机的颜色
+      randomColor(min, max) {
+        let r = this.randomNum(min, max)
+        let g = this.randomNum(min, max)
+        let b = this.randomNum(min, max)
+        return 'rgb(' + r + ',' + g + ',' + b + ')'
+      },
+      drawPic() {
+        this.randomCode()
+        let canvas = document.getElementById('gc-canvas')
+        let ctx = canvas.getContext('2d')
+        ctx.textBaseline = 'bottom'
+        // 绘制背景
+        ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
+        ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
+        // 绘制文字
+        for (let i = 0; i < this.code.length; i++) {
+          this.drawText(ctx, this.code[i], i)
+        }
+        this.drawLine(ctx)
+        this.drawDot(ctx)
+        this.$emit("success", this.code)
+      },
+      drawText(ctx, txt, i) {
+        ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
+        let fontSize = this.randomNum(this.fontSizeMin, this.fontSizeMax)
+        ctx.font = fontSize + 'px SimHei'
+        let padding = 10;
+        let offset = (this.contentWidth - 40) / (this.code.length - 1)
+        let x = padding;
+        if (i > 0) {
+          x = padding + (i * offset)
+        }
+        //let x = (i + 1) * (this.contentWidth / (this.code.length + 1))
+        let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
+        if (fontSize > 40) {
+          y = 40
+        }
+        var deg = this.randomNum(-10, 10)
+        // 修改坐标原点和旋转角度
+        ctx.translate(x, y)
+        ctx.rotate(deg * Math.PI / 180)
+        ctx.fillText(txt, 0, 0)
+        // 恢复坐标原点和旋转角度
+        ctx.rotate(-deg * Math.PI / 180)
+        ctx.translate(-x, -y)
+      },
+      drawLine(ctx) {
+        // 绘制干扰线
+        for (let i = 0; i < 1; i++) {
+          ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
+          ctx.beginPath()
+          ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
+          ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
+          ctx.stroke()
+        }
+      },
+      drawDot(ctx) {
+        // 绘制干扰点
+        for (let i = 0; i < 100; i++) {
+          ctx.fillStyle = this.randomColor(0, 255)
+          ctx.beginPath()
+          ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
+          ctx.fill()
+        }
+      },
+      reloadPic() {
+        this.drawPic()
+      },
+      randomCode() {
+        let random = ''
+        //去掉了I l i o O
+        let str = "QWERTYUPLKJHGFDSAZXCVBNMqwertyupkjhgfdsazxcvbnm1234567890"
+        for (let i = 0; i < this.length; i++) {
+          let index = Math.floor(Math.random() * 57);
+          random += str[index];
+        }
+        this.code = random
+      }
+    },
+    mounted() {
+      this.drawPic()
+    },
+    data() {
+      return {
+        code: ""
+      }
+    }
+
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 111 - 0
src/components/jeecg/JImportModal.vue

@@ -0,0 +1,111 @@
+<template>
+  <a-modal
+    title="导入EXCEL"
+    :width="600"
+    :visible="visible"
+    :confirmLoading="uploading"
+    @cancel="handleClose">
+
+    <a-upload
+      name="file"
+      :multiple="true"
+      accept=".xls,.xlsx"
+      :fileList="fileList"
+      :remove="handleRemove"
+      :beforeUpload="beforeUpload">
+      <a-button>
+        <a-icon type="upload"/>
+        选择导入文件
+      </a-button>
+    </a-upload>
+
+    <template slot="footer">
+      <a-button @click="handleClose">关闭</a-button>
+      <a-button
+        type="primary"
+        @click="handleImport"
+        :disabled="fileList.length === 0"
+        :loading="uploading">
+        {{ uploading ? '上传中...' : '开始上传' }}
+      </a-button>
+    </template>
+
+  </a-modal>
+</template>
+
+<script>
+  import {postAction} from '@/api/manage'
+
+  export default {
+    name: 'JImportModal',
+    props: {
+      url: {
+        type: String,
+        default: '',
+        required: false
+      }
+    },
+    data() {
+      return {
+        visible: false,
+        uploading: false,
+        fileList: [],
+        uploadAction: ''
+      }
+    },
+    watch: {
+      url(val) {
+        if (val) {
+          this.uploadAction = window._CONFIG['domianURL'] + val
+        }
+      }
+    },
+    created() {
+      this.uploadAction = window._CONFIG['domianURL'] + this.url
+    },
+
+    methods: {
+      handleClose() {
+        this.visible = false
+      },
+      show() {
+        this.fileList = []
+        this.uploading = false
+        this.visible = true
+      },
+      handleRemove(file) {
+        const index = this.fileList.indexOf(file);
+        const newFileList = this.fileList.slice();
+        newFileList.splice(index, 1);
+        this.fileList = newFileList
+      },
+      beforeUpload(file) {
+        this.fileList = [...this.fileList, file]
+        return false;
+      },
+      handleImport() {
+        const {fileList} = this;
+        const formData = new FormData();
+        fileList.forEach((file) => {
+          formData.append('files[]', file);
+        });
+        this.uploading = true
+        postAction(this.uploadAction, formData).then((res) => {
+          this.uploading = false
+          if (res.success) {
+            this.$message.success(res.message)
+            this.visible = false
+            this.$emit('ok')
+          } else {
+            this.$message.warning(res.message)
+          }
+        })
+      }
+
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 65 - 0
src/components/jeecg/JSelectMultiple.vue

@@ -0,0 +1,65 @@
+<template>
+  <a-select :value="arrayValue" @change="onChange" mode="multiple" :placeholder="placeholder">
+    <a-select-option
+      v-for="(item,index) in options"
+      :key="index"
+      :value="item.value">
+      {{ item.text || item.label }}
+    </a-select-option>
+  </a-select>
+</template>
+
+<script>
+  //option {label:,value:}
+  export default {
+    name: 'JSelectMultiple',
+    props: {
+      placeholder: {
+        type: String,
+        default: '',
+        required: false
+      },
+      value: {
+        type: String,
+        required: false
+      },
+      readOnly: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      options: {
+        type: Array,
+        required: true
+      },
+      triggerChange: {
+        type: Boolean,
+        required: false,
+        default: false
+      }
+    },
+    data() {
+      return {
+        arrayValue: !this.value ? [] : this.value.split(",")
+      }
+    },
+    watch: {
+      value(val) {
+        if (!val) {
+          this.arrayValue = []
+        } else {
+          this.arrayValue = this.value.split(",")
+        }
+      }
+    },
+    methods: {
+      onChange(selectedValue) {
+        if (this.triggerChange) {
+          this.$emit('change', selectedValue.join(","));
+        } else {
+          this.$emit('input', selectedValue.join(","));
+        }
+      },
+    },
+  }
+</script>

文件差異過大導致無法顯示
+ 120 - 0
src/components/jeecg/JSlider.vue


+ 157 - 0
src/components/jeecg/JSuperQuery.vue

@@ -0,0 +1,157 @@
+<template>
+  <a-modal
+    title="高级查询构造器"
+    :width="800"
+    :visible="visible"
+    :confirmLoading="confirmLoading"
+    @cancel="handleCancel"
+    :mask="false"
+    wrapClassName="ant-modal-cust-warp"
+    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>
+    </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>
+  </a-modal>
+</template>
+
+<script>
+  import ACol from 'ant-design-vue/es/grid/Col'
+  import JDate from '@/components/jeecg/JDate.vue';
+
+  export default {
+    name: 'JSuperQuery',
+    components: {
+      ACol,
+      JDate
+    },
+    data() {
+      return {
+        visible: false,
+        confirmLoading: false,
+        queryParamsModel: [{}]
+      }
+    },
+    props: {
+      /*  fieldList:[{value:'',text:'',type:''}]
+      * type:date datetime int number string
+      * */
+      fieldList: {
+        type: Array,
+        required: true
+      },
+      /*
+      * 这个回调函数接收一个数组参数 即查询条件
+      * */
+      callback: {
+        type: String,
+        required: false,
+        default: 'handleSuperQuery'
+      }
+    },
+    methods: {
+      show() {
+        if (!this.queryParamsModel || this.queryParamsModel.length == 0) {
+          this.queryParamsModel = [{}]
+        }
+        this.visible = true;
+      },
+      handleOk() {
+        console.log("---高级查询参数--->", this.queryParamsModel)
+        if (!this.isNullArray()) {
+          this.$emit(this.callback, this.queryParamsModel)
+        } else {
+          this.$emit(this.callback)
+        }
+      },
+      handleCancel() {
+        this.close()
+      },
+      close() {
+        this.$emit('close');
+        this.visible = false;
+      },
+      handleAdd() {
+        this.queryParamsModel.push({});
+      },
+      handleDel(index) {
+
+        this.queryParamsModel.splice(index, 1);
+        this.$message.warning("请关闭后重新打开")
+      },
+      handleSelected(option, item) {
+        item['type'] = option.data.attrs['data-type']
+      },
+      handleReset() {
+        this.queryParamsModel = [{}]
+        this.$emit(this.callback)
+      },
+      isNullArray() {
+        //判断是不是空数组对象
+        if (!this.queryParamsModel || this.queryParamsModel.length == 0) {
+          return true
+        }
+        if (this.queryParamsModel.length == 1) {
+          let obj = this.queryParamsModel[0]
+          if (!obj.field || !obj.val || !obj.rule) {
+            return true
+          }
+        }
+        return false;
+      }
+    }
+  }
+</script>
+
+<style>
+
+</style>

+ 195 - 0
src/components/jeecg/JTreeDict.vue

@@ -0,0 +1,195 @@
+<template>
+  <a-tree-select
+    allowClear
+    labelInValue
+    style="width: 100%"
+    :disabled="disabled"
+    :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+    :placeholder="placeholder"
+    :loadData="asyncLoadTreeData"
+    :value="treeValue"
+    :treeData="treeData"
+    @change="onChange"
+    @search="onSearch">
+  </a-tree-select>
+</template>
+
+<script>
+  import {getAction} from '@/api/manage'
+
+  export default {
+    name: 'JTreeDict',
+    data() {
+      return {
+        treeData: [],
+        treeValue: "",
+        url_root: "/sys/category/loadTreeRoot",
+        url_children: "/sys/category/loadTreeChildren",
+        url_view: '/sys/category/loadOne',
+      }
+    },
+    props: {
+      value: {
+        type: String,
+        required: false
+      },
+      placeholder: {
+        type: String,
+        default: '请选择',
+        required: false
+      },
+      parentCode: {
+        type: String,
+        default: '',
+        required: false
+      },
+      field: {
+        type: String,
+        default: 'id',
+        required: false
+      },
+      root: {
+        type: Object,
+        required: false,
+        default: () => {
+          return {
+            pid: '0'
+          }
+        }
+      },
+      async: {
+        type: Boolean,
+        default: false,
+        required: false
+      },
+      disabled: {
+        type: Boolean,
+        default: false,
+        required: false
+      }
+    },
+    watch: {
+      root: {
+        handler(val) {
+          console.log("root-change", val)
+        },
+        deep: true
+      },
+      parentCode: {
+        handler() {
+          this.loadRoot()
+        }
+      },
+      value: {
+        handler() {
+          this.loadViewInfo()
+        }
+      }
+    },
+    created() {
+      this.loadRoot()
+      this.loadViewInfo()
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    },
+    methods: {
+      loadViewInfo() {
+        if (!this.value || this.value == "0") {
+          this.treeValue = ""
+        } else {
+          let param = {
+            field: this.field,
+            val: this.value
+          }
+          getAction(this.url_view, param).then(res => {
+            if (res.success) {
+              this.treeValue = {
+                value: this.value,
+                label: res.result.name
+              }
+            }
+          })
+        }
+      },
+      loadRoot() {
+        let param = {
+          async: this.async,
+          pcode: this.parentCode
+        }
+        getAction(this.url_root, param).then(res => {
+          if (res.success) {
+            this.handleTreeNodeValue(res.result)
+            console.log("aaaa", res.result)
+            this.treeData = [...res.result]
+          } else {
+            this.$message.error(res.message)
+          }
+        })
+      },
+      asyncLoadTreeData(treeNode) {
+        return new Promise((resolve) => {
+          if (!this.async) {
+            resolve()
+            return
+          }
+          if (treeNode.$vnode.children) {
+            resolve()
+            return
+          }
+          let pid = treeNode.$vnode.key
+          let param = {
+            pid: pid
+          }
+          getAction(this.url_children, param).then(res => {
+            if (res.success) {
+              this.handleTreeNodeValue(res.result)
+              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.leaf = true
+              } else {
+                item.children = children
+              }
+              break
+            } else {
+              this.addChildren(pid, children, item.children)
+            }
+          }
+        }
+      },
+      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) {
+            this.handleTreeNodeValue(i.children)
+          }
+        }
+      },
+      onChange(value) {
+        console.log(value)
+        this.$emit('change', value.value);
+        this.treeValue = value
+      },
+      onSearch(value) {
+        console.log(value)
+      },
+      getCurrTreeData() {
+        return this.treeData
+      }
+    }
+
+  }
+</script>

+ 206 - 0
src/components/jeecg/JTreeSelect.vue

@@ -0,0 +1,206 @@
+<template>
+  <a-tree-select
+    allowClear
+    labelInValue
+    style="width: 100%"
+    :disabled="disabled"
+    :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+    :placeholder="placeholder"
+    :loadData="asyncLoadTreeData"
+    :value="treeValue"
+    :treeData="treeData"
+    @change="onChange"
+    @search="onSearch">
+  </a-tree-select>
+</template>
+<script>
+
+  /*
+  * 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
+  * <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
+  * */
+  import {getAction} from '@/api/manage'
+
+  export default {
+    name: 'JTreeSelect',
+    props: {
+      value: {
+        type: String,
+        required: false
+      },
+      placeholder: {
+        type: String,
+        default: '请选择',
+        required: false
+      },
+      dict: {
+        type: String,
+        default: '',
+        required: false
+      },
+      pidField: {
+        type: String,
+        default: 'pid',
+        required: false
+      },
+      pidValue: {
+        type: String,
+        default: '0',
+        required: false
+      },
+      disabled: {
+        type: Boolean,
+        default: false,
+        required: false
+      },
+      hasChildField: {
+        type: String,
+        default: '',
+        required: false
+      }
+    },
+    data() {
+      return {
+        treeValue: "",
+        treeData: [],
+        url: "/sys/dict/loadTreeData",
+        view: '/sys/dict/loadDictItem/',
+        tableName: "",
+        text: "",
+        code: "",
+
+      }
+    },
+    watch: {
+      value() {
+        this.loadItemByCode()
+      },
+      dict() {
+        this.initDictInfo()
+        this.loadRoot();
+      }
+    },
+    created() {
+      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 => {
+            if (res.success) {
+              this.treeValue = {
+                key: this.value,
+                value: this.value,
+                label: res.result
+              }
+            }
+          })
+        }
+      },
+      initDictInfo() {
+        let arr = this.dict.split(",")
+        this.tableName = arr[0]
+        this.text = arr[1]
+        this.code = arr[2]
+      },
+      asyncLoadTreeData(treeNode) {
+        return new Promise((resolve) => {
+          if (treeNode.$vnode.children) {
+            resolve()
+            return
+          }
+          let pid = treeNode.$vnode.key
+          let param = {
+            pid: pid,
+            tableName: this.tableName,
+            text: this.text,
+            code: this.code,
+            pidField: this.pidField,
+            hasChildField: this.hasChildField
+          }
+          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
+                }
+              }
+              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 {
+                item.children = children
+              }
+              break
+            } else {
+              this.addChildren(pid, children, item.children)
+            }
+          }
+        }
+      },
+      loadRoot() {
+        let param = {
+          pid: this.pidValue,
+          tableName: this.tableName,
+          text: this.text,
+          code: this.code,
+          pidField: this.pidField,
+          hasChildField: this.hasChildField
+        }
+        getAction(this.url, param).then(res => {
+          if (res.success && res.result) {
+            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
+              }
+            }
+            this.treeData = [...res.result]
+          } else {
+            console.log("数根节点查询结果-else", res)
+          }
+        })
+      },
+      onChange(value) {
+        if (!value) {
+          this.$emit('change', '');
+          this.treeValue = ''
+        } else {
+          this.$emit('change', value.value);
+          this.treeValue = value
+        }
+
+      },
+      onSearch(value) {
+        console.log(value)
+      },
+      getCurrTreeData() {
+        return this.treeData
+      }
+    },
+    //2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 140 - 0
src/components/jeecg/JTreeTable.vue

@@ -0,0 +1,140 @@
+<template>
+  <a-table
+    :rowKey="rowKey"
+    :columns="columns"
+    :dataSource="dataSource"
+    v-bind="tableProps"
+    @expand="handleExpand">
+
+    <template v-for="(slotItem) of slots" :slot="slotItem" slot-scope="text, record, index">
+      <slot :name="slotItem" v-bind="{text,record,index}"></slot>
+    </template>
+
+  </a-table>
+</template>
+
+<script>
+  import {getAction} from '@/api/manage'
+
+  export default {
+    name: 'JTreeTable',
+    props: {
+      rowKey: {
+        type: String,
+        default: 'id'
+      },
+      // 根据什么查询,如果传递 id 就根据 id 查询
+      queryKey: {
+        type: String,
+        default: 'parentId'
+      },
+      queryParams: {
+        type: Object,
+        default: () => {
+        }
+      },
+      // 查询顶级时的值,如果顶级为0,则传0
+      topValue: {
+        type: String,
+        default: null
+      },
+      columns: {
+        type: Array,
+        required: true
+      },
+      url: {
+        type: String,
+        required: true
+      },
+      childrenUrl: {
+        type: String,
+        default: null
+      },
+      tableProps: {
+        type: Object,
+        default: () => {
+        }
+      }
+    },
+    data() {
+      return {
+        dataSource: []
+      }
+    },
+    computed: {
+      getChildrenUrl() {
+        if (this.childrenUrl) {
+          return this.childrenUrl
+        } else {
+          return this.url
+        }
+      },
+      slots() {
+        let slots = []
+        for (let column of this.columns) {
+          if (column.scopedSlots && column.scopedSlots.customRender) {
+            slots.push(column.scopedSlots.customRender)
+          }
+        }
+        return slots
+      }
+    },
+    watch: {
+      queryParams: {
+        deep: true,
+        handler() {
+          this.loadData()
+        }
+      }
+    },
+    created() {
+      this.loadData()
+    },
+    methods: {
+
+      /** 加载数据*/
+      loadData(id = this.topValue, first = true, url = this.url) {
+        let params = Object.assign({}, this.queryParams || {})
+        params[this.queryKey] = id
+        return getAction(url, params).then(res => {
+          let dataSource = res.result.map(item => {
+            // 判断是否标记了带有子级
+            if (item.hasChildren === true) {
+              // 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
+              let loadChild = {id: `${item.id}_loadChild`, name: 'loading...', isLoading: true}
+              item.children = [loadChild]
+            }
+            return item
+          })
+          if (first) {
+            this.dataSource = dataSource
+          }
+          return Promise.resolve(dataSource)
+        })
+      },
+
+      /** 点击展开图标时触发 */
+      handleExpand(expanded, record) {
+        // 判断是否是展开状态
+        if (expanded) {
+          // 判断子级的首个项的标记是否是“正在加载中”,如果是就加载数据
+          if (record.children[0].isLoading === true) {
+            this.loadData(record.id, false, this.getChildrenUrl).then(dataSource => {
+              // 处理好的数据可直接赋值给children
+              if (dataSource.length === 0) {
+                record.children = null
+              } else {
+                record.children = dataSource
+              }
+            })
+          }
+        }
+      }
+
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 174 - 0
src/components/jeecg/JUpload.vue

@@ -0,0 +1,174 @@
+<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>
+</template>
+
+<script>
+
+  import Vue from 'vue'
+  import {ACCESS_TOKEN} from "@/store/mutation-types"
+
+  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 getFileName = (path) => {
+    if (path.lastIndexOf("\\") >= 0) {
+      let reg = new RegExp("\\\\", "g");
+      path = path.replace(reg, "/");
+    }
+    return path.substring(path.lastIndexOf("/") + 1);
+  }
+  export default {
+    name: 'JUpload',
+    data() {
+      return {
+        uploadAction: window._CONFIG['domianURL'] + "/sys/common/upload",
+        urlDownload: window._CONFIG['domianURL'] + "/sys/common/download/",
+        headers: {},
+        fileList: []
+      }
+    },
+    props: {
+      text: {
+        type: String,
+        required: false,
+        default: "点击上传"
+      },
+      fileType: {
+        type: String,
+        required: false,
+        default: FILE_TYPE_ALL
+      },
+      /*这个属性用于控制文件上传的业务路径*/
+      bizPath: {
+        type: String,
+        required: false,
+        default: "temp"
+      },
+      value: {
+        type: String,
+        required: false
+      },
+      //此属性被废弃了
+      triggerChange: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+    },
+    watch: {
+      value(val) {
+        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) {
+          return [];
+        }
+        let fileList = [];
+        let arr = paths.split(",")
+        for (var a = 0; a < arr.length; a++) {
+          fileList.push({
+            uid: uidGenerator(),
+            name: getFileName(arr[a]),
+            status: 'done',
+            url: this.urlDownload + arr[a],
+            response: {
+              status: "history",
+              message: arr[a]
+            }
+          })
+        }
+        this.fileList = fileList
+      },
+      handlePathChange() {
+        let uploadFiles = this.fileList
+        let path = ''
+        if (!uploadFiles || uploadFiles.length == 0) {
+          path = ''
+        }
+        let arr = [];
+
+        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);
+      },
+      beforeUpload(file) {
+        var fileType = file.type;
+        if (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('请上传文件');
+            return false;
+          }
+        }
+        //TODO 扩展功能验证文件大小
+        return true
+      },
+      handleChange(info) {
+        console.log("--文件列表改变--")
+        let fileList = info.fileList
+        if (info.file.status === 'done') {
+          if (info.file.response.success) {
+            fileList = fileList.map((file) => {
+              if (file.response) {
+                file.url = this.urlDownload + 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()
+        }
+      },
+      handleDelete(file) {
+        //如有需要新增 删除逻辑
+        console.log(file)
+      },
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

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

@@ -0,0 +1,519 @@
+# JDate 日期组件 使用文档
+  
+###### 说明: antd-vue日期组件需要用moment中转一下,用起来不是很方便,特二次封装,使用时只需要传字符串即可
+## 参数配置
+| 参数           | 类型      | 必填 |说明|
+|--------------|---------|----|---------|
+| placeholder      |string   | | placeholder      |
+| readOnly   | boolean   | | true/false 默认false                 |
+| value      | string | | 绑定v-model或是v-decorator后不需要设置    |
+| showTime | boolean | | 是否展示时间true/false 默认false  |
+| dateFormat    | string | |日期格式 默认'YYYY-MM-DD' 若showTime设置为true则需要将其设置成对应的时间格式(如:YYYY-MM-DD HH:mm:ss)               |
+| triggerChange | string | |触发组件值改变的事件是否是change,当使用v-decorator时且没有设置decorator的option.trigger为input需要设置该值为true               |
+使用示例
+----
+1.组件带有v-model的使用方法
+```vue
+<j-date v-model="dateStr"></j-date>
+```
+
+2.组件带有v-decorator的使用方法  
+  a).设置trigger-change属性为true
+  ```vue
+    <j-date :trigger-change="true" v-decorator="['dateStr',{}]"></j-date>
+  ```
+    
+  b).设置decorator的option.trigger为input
+   ```vue
+    <j-date v-decorator="['dateStr',{trigger:'input'}]"></j-date>
+   ```
+
+3.其他使用
+添加style
+```vue
+<j-date v-model="dateStr" style="width:100%"></j-date>
+```
+添加placeholder
+```vue
+<j-date v-model="dateStr" placeholder="请输入dateStr"></j-date>
+```
+添加readOnly
+```vue
+<j-date v-model="dateStr" :read-only="true"></j-date>
+```
+
+备注:
+script内需引入jdate
+```vue
+<script>
+  import JDate from '@/components/jeecg/JDate'
+  export default {
+    name: "demo",
+    components: {
+      JDate
+    }
+    //...
+  }
+</script>
+```
+
+
+ ---
+
+
+# JSuperQuery 高级查询 使用文档
+## 参数配置
+| 参数           | 类型      | 必填 | 说明                   |
+|--------------|---------|----|----------------------|
+| fieldList      | array   |✔| 需要查询的列集合示例如下,type类型有:date/datetime/string/int/number      |
+| callback   | array   |  | 回调函数名称(非必须)默认handleSuperQuery                |
+
+fieldList结构示例:
+```vue
+  const superQueryFieldList=[{
+    type:"date",
+    value:"birthday",
+    text:"生日"
+  },{
+    type:"string",
+    value:"name",
+    text:"用户名"
+  },{
+    type:"int",
+    value:"age",
+    text:"年龄"
+  }]
+```
+页面代码概述:  
+----
+1.import之后再components之内声明
+```vue
+import JSuperQuery from '@/components/jeecg/JSuperQuery.vue';
+  export default {
+    name: "JeecgDemoList",
+    components: {
+      JSuperQuery
+    },
+
+```
+2.页面引用
+```vue
+  <!-- 高级查询区域 -->
+  <j-super-query :fieldList="fieldList" ref="superQueryModal" @handleSuperQuery="handleSuperQuery"></j-super-query>
+```
+3.list页面data中需要定义三个属性:
+```vue
+  fieldList:superQueryFieldList,
+  superQueryFlag:false,
+  superQueryParams:""
+```
+4.list页面声明回调事件handleSuperQuery(与组件的callback对应即可)
+```vue
+//高级查询方法
+handleSuperQuery(arg) {
+  if(!arg){
+    this.superQueryParams=''
+    this.superQueryFlag = false
+  }else{
+    this.superQueryFlag = true
+    this.superQueryParams=JSON.stringify(arg)
+  }
+  this.loadData()
+},
+```
+5.改造list页面方法
+```vue
+  // 获取查询条件
+  getQueryParams() {
+    let sqp = {}
+    if(this.superQueryParams){
+      sqp['superQueryParams']=encodeURI(this.superQueryParams)
+    }
+    var param = Object.assign(sqp, this.queryParam, this.isorter);
+    param.field = this.getQueryField();
+    param.pageNo = this.ipagination.current;
+    param.pageSize = this.ipagination.pageSize;
+    return filterObj(param);
+  },
+```
+6.打开弹框调用show方法:
+```vue
+this.$refs.superQueryModal.show();
+```
+
+# JEllipsis 字符串超长截取省略号显示
+  
+###### 说明: 遇到超长文本展示,通过此标签可以截取省略号显示,鼠标放置会提示全文本
+## 参数配置
+| 参数  | 类型     | 必填 |    说明      |
+|--------|---------|----|----------------|
+| value  |string   | 必填   |  字符串文本|
+| length | number  | 非必填 |  默认25    |
+使用示例
+----
+1.组件带有v-model的使用方法
+```vue
+<j-ellipsis :value="text"/>
+
+
+# Modal弹框实现最大化功能  
+
+1.定义modal的宽度:
+```vue
+  <a-modal
+    :width="modalWidth"
+    
+    
+    />
+```
+2.自定义modal的title,居右显示切换图标
+```vue
+  <template slot="title">
+    <div style="width: 100%;">
+      <span>{{ title }}</span>
+      <span style="display:inline-block;width:calc(100% - 51px);padding-right:10px;text-align: right">
+        <a-button @click="toggleScreen" icon="appstore" style="height:20px;width:20px;border:0px"></a-button>
+      </span>
+    </div>
+  </template>
+```
+3.定义toggleScreen事件,用于切换modal宽度
+```vue
+  toggleScreen(){
+      if(this.modaltoggleFlag){
+        this.modalWidth = window.innerWidth;
+      }else{
+        this.modalWidth = 800;
+      }
+      this.modaltoggleFlag = !this.modaltoggleFlag;
+    },
+```
+4.data中声明上述用到的属性
+```vue
+    data () {
+      return {
+        modalWidth:800,
+        modaltoggleFlag:true,
+```
+
+# <a-select/> 下拉选项滚动错位的解决方法
+
+## 问题描述
+
+当使用了 `a-modal` 或其他带有滚动条的组件时,使用`a-select`组件并打开下拉框时滚动滚动条,就会导致错位的问题产生。
+
+## 解决方法
+
+大多数情况下,在 `a-select` 上添加一个 `getPopupContainer` 属性,值为`node => node.parentNode`即可解决。
+但是如果遇到 `a-select` 标签层级过深的情况,可能仍然会显示异常,只需要多加几个`.parentNode` (例:node => node.parentNode.parentNode.parentNode)多尝试几次直到解决问题即可。
+
+### 代码示例
+
+```html
+<a-select
+    placeholder="请选择展示模板"
+    :options="dicts.displayTemplate"
+    :getPopupContainer="node => node.parentNode"
+/>
+```
+
+# JAsyncTreeList 异步数列表组件使用说明
+
+## 引入组件
+
+```js
+import JTreeTable from '@/components/jeecg/JTreeTable'
+export default {
+  components: { JTreeTable }
+}
+```
+
+## 所需参数
+
+<<<<<<< HEAD
+| 参数          | 类型     | 必填  | 说明                                                 |
+|-------------|--------|-----|------------------------------------------------------------|
+| rowKey      | String | 非必填 | 表格行 key 的取值,默认为"id" |
+| columns     | Array  | 必填   | 表格列的配置描述,具体见Antd官方文档                   |
+| url         | String | 必填   | 数据查询url                                            |
+| childrenUrl | String | 非必填 | 查询子级时的url,若不填则使用url参数查询子级           |
+| tableProps  | Object | 非必填 | 自定义给内部table绑定的props                           |
+=======
+| 参数        | 类型   | 必填   | 说明                                                         |
+|-------------|--------|--------|--------------------------------------------------------------|
+| rowKey      | String | 非必填 | 表格行 key 的取值,默认为"id"                                |
+| columns     | Array  | 必填   | 表格列的配置描述,具体见Antd官方文档                         |
+| url         | String | 必填   | 数据查询url                                                  |
+| childrenUrl | String | 非必填 | 查询子级时的url,若不填则使用url参数查询子级                 |
+| queryKey    | String | 非必填 | 根据某个字段查询,如果传递 id 就根据 id 查询,默认为parentId |
+| queryParams | Object | 非必填 | 查询参数,当查询参数改变的时候会自动重新查询,默认为{}       |
+| topValue    | String | 非必填 | 查询顶级时的值,如果顶级为0,则传0,默认为null               |
+| tableProps  | Object | 非必填 | 自定义给内部table绑定的props                                 |
+>>>>>>> jeecg_master
+
+## 代码示例
+
+```html
+<template>
+  <a-card :bordered="false">
+    <j-tree-table :url="url" :columns="columns" :tableProps="tableProps"/>
+  </a-card>
+</template>
+
+<script>
+  import JTreeTable from '@/components/jeecg/JTreeTable'
+
+  export default {
+    name: 'AsyncTreeTable',
+    components: { JTreeTable },
+    data() {
+      return {
+        url: '/api/asynTreeList',
+        columns: [
+          { title: '菜单名称', dataIndex: 'name' },
+          { title: '组件', dataIndex: 'component' },
+          { title: '排序', dataIndex: 'orderNum' }
+        ],
+        selectedRowKeys: []
+      }
+    },
+     computed: {
+       tableProps() {
+         let _this = this
+         return {
+           // 列表项是否可选择
+           // 配置项见:https://vue.ant.design/components/table-cn/#rowSelection
+           rowSelection: {
+             selectedRowKeys: _this.selectedRowKeys,
+             onChange: (selectedRowKeys) => _this.selectedRowKeys = selectedRowKeys
+           }
+         }
+       }
+     }
+  }
+</script>
+```
+
+# JCheckbox 使用文档
+  
+###### 说明: antd-vue checkbox组件处理的是数组,用起来不是很方便,特二次封装,使用时只需处理字符串即可
+## 参数配置
+| 参数           | 类型   | 必填 |说明|
+|--------------|---------|----|---------|
+| options      |array   |✔| checkbox需要配置的项,是个数组,数组中每个对象包含两个属性:label(用于显示)和value(用于存储) |
+
+使用示例
+----
+```vue
+<template>
+  <a-form :form="form">
+    <a-form-item label="v-model式用法">
+      <j-checkbox v-model="sport" :options="sportOptions"></j-checkbox><span>{{ sport }}</span>
+    </a-form-item>
+
+    <a-form-item label="v-decorator式用法">
+      <j-checkbox v-decorator="['sport']" :options="sportOptions"></j-checkbox><span>{{ getFormFieldValue('sport') }}</span>
+    </a-form-item>
+  </a-form>
+</template>
+
+<script>
+  import JCheckbox from '@/components/jeecg/JCheckbox'
+  export default {
+    components: {JCheckbox},
+    data() {
+      return {
+        form: this.$form.createForm(this),
+        sport:'',
+        sportOptions:[
+          {
+            label:"足球",
+            value:"1"
+          },{
+            label:"篮球",
+            value:"2"
+          },{
+            label:"乒乓球",
+            value:"3"
+          }]
+      }
+    },
+    methods: {
+     getFormFieldValue(field){
+       return this.form.getFieldValue(field)
+     }
+    }
+  }
+</script>
+```
+
+# JCodeEditor 使用文档
+  
+###### 说明: 一个简易版的代码编辑器,支持语法高亮
+## 参数配置
+| 参数           | 类型   | 必填 |说明|
+|--------------|---------|----|---------|
+| language      |string   | | 表示当前编写代码的类型 javascript/html/css/sql |
+| placeholder      |string   | | placeholder |
+| lineNumbers      |Boolean   | | 是否显示行号 |
+| fullScreen      |Boolean   | | 是否显示全屏按钮 |
+| zIndex      |string   | | 全屏以后的z-index |
+
+使用示例
+----
+```vue
+<template>
+  <div>
+    <j-code-editor
+      language="javascript"
+      v-model="editorValue"
+      :fullScreen="true"
+      style="min-height: 100px"/>
+    {{ editorValue }}
+  </div>
+</template>
+
+<script>
+  import JCodeEditor from '@/components/jeecg/JCodeEditor'
+  export default {
+    components: {JCodeEditor},
+    data() {
+      return {
+        form: this.$form.createForm(this),
+        editorValue:'',
+      }
+    }
+  }
+</script>
+```
+
+# JFormContainer 使用文档
+  
+###### 说明: 暂用于表单禁用
+
+使用示例
+----
+```vue
+<!-- 在form下直接写这个组件,设置disabled为true就能将此form中的控件禁用 -->
+  <a-form layout="inline" :form="form" >
+    <j-form-container disabled>
+      <!-- 表单内容省略..... -->
+    </j-form-container>
+  </a-form>
+```
+
+# JImportModal 使用文档
+  
+###### 说明: 用于列表页面导入excel功能
+
+使用示例
+----
+```vue
+
+<template>
+  <!--  此处省略部分代码...... -->
+  <a-button @click="handleImportXls" type="primary" icon="upload">导入</a-button>
+  <!--  此处省略部分代码...... -->
+  <j-import-modal ref="importModal" :url="getImportUrl()" @ok="importOk"></j-import-modal>
+  <!--  此处省略部分代码...... -->
+</template>
+
+<script>
+  import JCodeEditor from '@/components/jeecg/JCodeEditor'
+  export default {
+    components: {JCodeEditor},
+    data() {
+      return {
+        //省略代码......
+      }
+    },
+    methods:{
+      //省略部分代码......
+      handleImportXls(){
+        this.$refs.importModal.show()
+      },
+      getImportUrl(){
+         return '你自己处理上传业务的后台地址'
+      },
+      importOk(){
+        this.loadData(1)
+      }
+    }
+  }
+</script>
+```
+
+# JSelectMultiple 多选下拉组件
+online用 实际开发请使用components/dict/JMultiSelectTag
+
+# JSlider 滑块验证码
+
+使用示例
+----
+```vue
+<template>
+  <div style="width: 300px">
+    <j-slider @onSuccess="sliderSuccess"></j-slider>
+  </div>
+</template>
+
+<script>
+  import JSlider from '@/components/jeecg/JSlider'
+  export default {
+    components: {JSlider},
+    data() {
+      return {
+        form: this.$form.createForm(this),
+        editorValue:'',
+      }
+    },
+    methods:{
+      sliderSuccess(){
+        console.log("验证完成")
+      }
+    }
+  }
+</script>
+```
+
+
+# JTreeSelect 树形下拉组件
+异步加载的树形下拉组件
+
+## 参数配置
+| 参数           | 类型   | 必填 |说明|
+|--------------|---------|----|---------|
+| placeholder      |string   | | placeholder |
+| dict      |string   | ✔| 表名,显示字段名,存储字段名拼接的字符串 |
+| pidField      |string   | ✔| 父ID的字段名 |
+| pidValue      |string   | | 根节点父ID的值 默认'0' 不可以设置为空,如果想使用此组件,而数据库根节点父ID为空,请修改之 |
+
+使用示例
+----
+```vue
+<template>
+  <a-form>
+    <a-form-item label="树形下拉测试" style="width: 300px">
+      <j-tree-select
+        v-model="departId"
+        placeholder="请选择部门"
+        dict="sys_depart,depart_name,id"
+        pidField="parent_id">
+      </j-tree-select>
+      {{ departId }}
+    </a-form-item>
+  </a-form >
+</template>
+
+<script>
+  import JTreeSelect from '@/components/jeecg/JTreeSelect'
+  export default {
+    components: {JTreeSelect},
+    data() {
+      return {
+        departId:""
+      }
+    }
+  }
+</script>
+```

+ 517 - 0
src/components/jeecg/README_JEditableTable.md

@@ -0,0 +1,517 @@
+# 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 参数详解
+
+| 参数          | 类型   | 必填 | 说明                                                                                                                                                   |
+|---------------|--------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 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'}`                                                                                     |
+
+#### 当 type=checkbox 时所需的参数
+
+| 参数           | 类型    | 必填 | 说明                                                                                                                                                                                                   |
+|----------------|---------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| defaultChecked | boolean |      | 默认值是否选中                                                                                                                                                                                         |
+| customValue    | array   |      | 自定义值,checkbox需要的是boolean值,如果数据是其他值(例如`'Y' or 'N'`)时,就会导致错误,所以提供了该属性进行转换,例:`customValue: ['Y','N']`,会将`true`转换为`'Y'`,`false`转换为`'N'`,反之亦然 |
+
+#### 当 type=select 时所需的参数
+
+| 参数       | 类型    | 必填 | 说明                                 |
+|------------|---------|------|--------------------------------------|
+| options    | array   | ✔️    | 下拉选项列表,详见下表               |
+| allowInput | boolean |      | 是否允许用户输入内容,并创建新的内容 |
+
+##### options 所需参数
+
+| 参数      | 类型       | 必填 | 说明                                                                 |
+|-----------|------------|------|----------------------------------------------------------------------|
+| text      | string     | ✔️    | 显示标题                                                             |
+| value     | string     | ✔️    | 真实值                                                               |
+| ~~title~~ | ~~string~~ |      | ~~显示标题(已废弃,若同时填写了 title 和 text 那么优先使用 text)~~ |
+
+#### 当 type=upload 时所需的参数
+
+| 参数         | 类型    | 必填 | 说明                                                                                 |
+|--------------|---------|------|--------------------------------------------------------------------------------------|
+| action       | string  | ✔️    | 上传文件路径                                                                         |
+| token        | boolean |      | 上传的时候是否传递token                                                              |
+| responseName | string  | ✔️    | 若要从上传成功后从response中取出返回的文件名,那么这里填后台返回的包含文件名的字段名 |
+
+#### 当 type=slot 时所需的参数
+
+| 参数     | 类型   | 必填 | 说明       |
+|----------|--------|------|------------|
+| slotName | string | ✔️    | slot的名称 |
+
+### validateRules 配置规则
+
+`validateRules` 需要的是一个数组,数组里每项都是一个规则,规则是object类型,规则的各个参数如下
+
+- `required` 是否必填,可选值为`true`or`false`
+- `pattern` 正则表达式验证,只有成功匹配该正则的值才能成功通过验证
+- `message` 当验证未通过时显示的提示文本,可以使用`${...}`变量替换文本(详见`${...} 变量使用方式`)
+- 配置示例请看[示例二](#示例二)
+
+## 事件
+
+| 事件名          | 触发时机                                           | 参数                          |
+|-----------------|----------------------------------------------------|-------------------------------|
+| added           | 当添加行操作完成后触发                             |                               |
+| deleted         | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id    |
+| selectRowChange | 当行被选中或取消选中时触发                         | `selectedRowIds` 被选中行的id |
+
+## 方法
+
+关于方法的如何调用的问题,请在**FAQ**中查看[方法如何调用](#方法如何调用)
+
+### initialize
+
+用于初始化表格(清空表格)
+
+- `参数:` 无
+- `返回值:` 无
+
+### resetScrollTop
+
+重置滚动条Top位置
+
+- `参数:`
+
+| 参数名 | 类型   | 必填 | 说明                                                                                                   |
+|--------|--------|------|--------------------------------------------------------------------------------------------------------|
+| top    | number |      | 新top位置,留空则滚动到上次记录的位置,用于解决切换tab选项卡时导致白屏以及自动将滚动条滚动到顶部的问题 |
+
+- `返回值:` 无
+
+### add
+
+主动添加行,默认情况下,当用户的滚动条已经在底部的时候,会将滚动条固定在底部,即添加后无需用户手动滚动,而会自动滚动到底部
+
+- `参数:`
+
+
+| 参数名              | 类型    | 必填 | 说明                                                                |
+|---------------------|---------|------|---------------------------------------------------------------------|
+| num                 | number  |      | 添加几行,默认为1                                                   |
+| forceScrollToBottom | boolean |      | 是否在添加后无论用户的滚动条在什么位置都强制滚动到底部,默认为false |
+
+
+- `返回值:` 无
+
+### removeRows
+
+主动删除一行或多行
+
+- `参数:`
+
+
+| 参数名 | 类型            | 必填 | 说明                                                                                       |
+|--------|-----------------|------|--------------------------------------------------------------------------------------------|
+| id     | string 或 array | ✔️    | 被删除行的id。如果要删除一个,可以直接传id,如果要删除多个,需要将多个id封装成一个数组传入 |
+
+
+- `返回值:` 无
+
+### removeSelectedRows
+
+主动删除被选中的行
+
+- `参数:` 无
+- `返回值:` 无
+
+### getValues
+
+用于获取表格里所有表单的值,可进行表单验证
+
+- `参数:`
+
+
+| 参数名   | 类型     | 必填 | 说明                                                                                                                                                      |
+|----------|----------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
+| callback | function | ✔️    | 获取值的回调方法,会传入`error`和`values`两个参数。`error`:未通过验证的数量,当等于`0`时代表验证通过;`values`:获取的值(即使未通过验证该字段也有数据) |
+| validate | boolean  |      | 是否进行表单验证,默认为`true`,设为`false`则代表忽略表单验证                                                                                             |
+| rowIds   | array    |      | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组                                      |
+
+
+- `返回值:` 无
+
+
+### getValuesSync
+
+`getValues`的同步版,会直接将获取到的数据返回
+
+- `参数:` 
+
+| 参数名  | 类型   | 必填 | 说明                   |
+|---------|--------|------|------------------------|
+| options | object |      | 选项,详见下方所需参数 |
+
+- - `options` 所需参数
+
+| 参数名   | 类型    | 必填 | 说明                                                                                                                 |
+|----------|---------|------|----------------------------------------------------------------------------------------------------------------------|
+| validate | boolean |      | 是否进行表单验证,默认为`true`,设为`false`则代表忽略表单验证                                                        |
+| rowIds   | array   |      | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组 |
+
+
+- `返回值:` object
+    - `error` 未通过验证的数量,当等于`0`时代表验证通过
+    - `values` 获取的值(即使未通过验证该字段也有数据)
+
+- `使用示例`
+
+```js
+let { error, values } = this.$refs.editableTable.getValuesSync({ validate: true, rowIds: ['rowId1', 'rowId2'] })
+if (error === 0) {
+    console.log('表单验证通过,数据:', values);
+} else {
+    console.log('未通过表单验证,数据:', values);
+}
+```
+
+### getValuesPromise
+
+`getValues`的promise版,会在`resolve`中传入获取到的值,会在`reject`中传入失败原因,例如`VALIDATE_NO_PASSED`
+
+- `参数:`
+
+| 参数名   | 类型    | 必填 | 说明                                                                                                                 |
+|----------|---------|------|----------------------------------------------------------------------------------------------------------------------|
+| validate | boolean |      | 同`getValues`的`validate`参数                                                                                        |
+| rowIds   | array   |      | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组 |
+
+- `返回值:` Promise
+
+### getDeleteIds
+
+用于获取被逻辑删除的行的id,返回一个数组,用户可将该数组传入后台,并进行批量删除
+
+- `参数:` 无
+- `返回值:` array
+
+### getAll
+
+获取所有的数据,包括values、deleteIds
+会在`resolve`中传入获取到的值:`{values, deleteIds}`
+会在`reject`中传入失败原因,例如`VALIDATE_NO_PASSED`
+
+- `参数:`
+
+| 参数名   | 类型    | 必填 | 说明                          |
+|----------|---------|------|-------------------------------|
+| validate | boolean |      | 同`getValues`的`validate`参数 |
+
+
+- `返回值:` Promise
+
+### setValues
+
+主动设置表格中某行某列的值
+
+- `参数:`
+
+
+| 参数名 | 类型  | 必填 | 说明                                                       |
+|--------|-------|------|------------------------------------------------------------|
+| values | array |      | 传入一个数组,数组中的每项都是一行的新值,具体见下面的示例 |
+
+
+- `返回值:` 无
+- `示例:`
+
+```js
+setValues([
+    {
+        rowKey: id1, // 行的id
+        values: { // 在这里 values 中的 name 是你 columns 中配置的 key
+            'name': 'zhangsan',
+            'age': '20'
+        }
+    },
+    {
+        rowKey: id2,
+        values: {
+            'name': 'lisi',
+            'age': '23'
+        }
+    }
+])
+```
+
+## ${...} 变量使用方式
+
+在`placeholder`和`message`这两个属性中可以使用`${...}`变量来替换文本
+在[示例二](#示例二)中,配置了`title`为`名称`的一列,而`placeholder`配置成了`请输入${title}`,那么最终显示效果为`请输入名称`
+这就是`${...}`变量的使用方式,在`${}`中可以使用的变量有`title`、`key`、`defaultValue`这三个属性的值
+
+## JEditableTableUtil 使用说明
+
+在之前配置`columns`时提到过`JEditableTableUtil`这个工具类,那么如果想要知道详细的使用说明就请看这里
+
+### export 的常量
+
+#### FormTypes
+
+这是配置`columns.type`时用到的常量值,其中包括
+
+- `normal` 默认,直接显示值,不渲染表单
+- `input` 显示输入框
+- `inputNumber` 显示数字输入框
+- `checkbox` 显示多选框
+- `select` 显示选择器(下拉框)
+- `date` 日期选择器
+- `datetime` 日期时间选择器
+- `upload` 上传组件(文件域)
+- `slot` 自定义插槽
+
+### VALIDATE_NO_PASSED
+
+在判断表单验证是否通过时使用,如果 reject 的值 === VALIDATE_NO_PASSED 则代表表单验证未通过,你可以做相应的其他处理,反之则可能是发生了报错,可以使用 `console.error` 输出
+
+### 封装的方法
+
+#### validateTables
+
+当你的页面中存在多个JEditableTable实例的时候,如果要获取每个实例的值、判断表单验证是否通过,就会让代码变得极其冗余、繁琐,于是我们就将该操作封装成了一个函数供你调用,它可以同时获取并验证多个JEditableTable实例的值,只有当所有实例的表单验证都通过后才会返回值,否则将会告诉你具体哪个实例没有通过验证。具体使用方法请看下面的示例
+
+- `参数:`
+
+| 参数名 | 类型  | 必填 | 说明                                                   |
+|--------|-------|------|--------------------------------------------------------|
+| cases  | array |      | 传入一个数组,数组中的每项都是一个JEditableTable的实例 |
+
+- `返回值:` Promise
+- `示例:`
+
+```js
+import { validateTables, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
+// 封装cases
+let cases = []
+cases.push(this.$refs.editableTable1)
+cases.push(this.$refs.editableTable2)
+cases.push(this.$refs.editableTable3)
+cases.push(this.$refs.editableTable4)
+cases.push(this.$refs.editableTable5)
+// 同时验证并获取多个实例的值
+validateTables(cases).then((all) => {
+    // all 是一个数组,每项都对应传入cases的下标,包含values和deleteIds
+    console.log('所有实例的值:', all)
+}).catch((e = {}) => {
+    // 判断表单验证是否未通过
+    if (e.error === VALIDATE_NO_PASSED) {
+        console.log('未通过验证的实例下标:', e.index)
+    } else {
+        console.error('发生异常:', e)
+    }
+})
+```
+
+## FAQ
+
+### 方法如何调用?
+
+在[示例一](#示例一)中,设定了一个 `ref="editableTable"` 的属性,那么在vue中就可以使用`this.$refs.editableTable`获取到该表格的实例,并调取其中的方法。
+假如我要调取`initialize`方法,就可以这么写:`this.$refs.editableTable.initialize()`
+
+### 如何获取表单的值?
+
+使用`getValue`方法进行获取,详见[示例三](#示例三)
+
+### 如何进行表单验证?
+
+在获取值的时候默认会进行表单验证操作,用户在输入的时候也会对正在输入的表单进行验证,只要配置好规则就可以了
+
+### 如何添加或删除一行?
+
+该功能已封装到组件中,你只需要将 `actionButton` 设置为 `true` 即可,当然你也可以在代码中主动调用新增方法或修改,具体见上方的方法介绍。
+
+### 为什么使用了ATab组件后,切换选项卡会导致白屏或滚动条位置会归零?
+
+在ATab组件中确实会导致滚动条位置归零,且不会触发`onscroll`方法,所以无法动态加载行,导致白屏的问题出现。
+解决方法是在ATab组件的`onChange`事件触发时执行实例提供的`resetScrollTop()`方法即可,但是需要注意的是:代码主动改变ATab的`activeKey`不会触发`onChange`事件,还需要你手动调用下。
+
+- `示例`
+
+```html
+<template>
+    <a-tabs @change="handleChangeTab">
+        <a-tab-pane tab="表格1" :forceRender="true" key="1">
+            <j-editable-table
+                ref="editableTable1"
+                :loading="tab1.loading"
+                :columns="tab1.columns"
+                :dataSource="tab1.dataSource"/>
+        </a-tab-pane>
+        <a-tab-pane tab="表格2" :forceRender="true" key="2">
+            <j-editable-table
+                ref="editableTable2"
+                :loading="tab2.loading"
+                :columns="tab2.columns"
+                :dataSource="tab2.dataSource"/>
+        </a-tab-pane>
+    </a-tabs>
+</template>
+```
+
+```js
+/*--- 忽略部分代码片段 ---*/
+methods: {
+
+      /** 切换tab选项卡的时候重置editableTable的滚动条状态 */
+      handleChangeTab(key) {
+        this.$refs[`editableTable${key}`].resetScrollTop()
+      }
+
+}
+/*--- 忽略部分代码片段 ---*/
+```
+
+### slot(自定义插槽)如何使用?
+
+代码示例请看:[示例四(slot)](#示例四(slot))
+
+----------------------------------------------------------------------------------------
+
+## 示例一
+
+```html
+<j-editable-table
+    ref="editableTable"
+    :loading="loading"
+    :columns="columns"
+    :dataSource="dataSource"
+    :rowNumber="true"
+    :rowSelection="true"
+    :actionButton="true"
+    style="margin-top: 8px;"
+    @selectRowChange="handleSelectRowChange"/>
+```
+
+## 示例二
+
+```js
+
+import { FormTypes } from '@/utils/JEditableTableUtil'
+
+/*--- 忽略部分代码片断 ---*/
+columns: [
+    {
+        title: '名称',
+        key: 'name',
+        type: FormTypes.input,
+        placeholder: '请输入${title}',
+        defaultValue: '称名',
+        // 表单验证规则
+        validateRules: [
+            {
+                required: true, // 必填
+                message: '${title}不能为空' // 提示的文本
+            },
+            {
+                pattern: /^[a-z|A-Z][a-z|A-Z\d_-]{0,}$/, // 正则
+                message: '${title}必须以字母开头,可包含数字、下划线、横杠'
+            }
+        ]
+    },
+    {
+        title: '年龄',
+        key: 'age',
+        type: FormTypes.inputNumber,
+        placeholder: '请输入${title}',
+        defaultValue: 18,
+        validateRules: [{required: true, message: '${title}不能为空'}]
+    }
+]
+/*--- 忽略部分代码片断 ---*/
+```
+
+## 示例三
+
+```js
+// 获取被逻辑删除的字段id
+let deleteIds = this.$refs.editableTable.getDeleteIds();
+// 获取所有表单的值,并进行验证
+this.$refs.editableTable.getValues((error, values) => {
+    // 错误数 = 0 则代表验证通过
+    if (error === 0) {
+        this.$message.success('验证通过')
+        // 将通过后的数组提交到后台或自行进行其他处理
+        console.log(deleteIds, values)
+    } else {
+        this.$message.error('验证未通过')
+    }
+})
+```
+
+## 示例四(slot)
+
+```html
+<template>
+    <j-editable-table :columns="columns" :dataSource="dataSource">
+        <!-- 定义插槽 -->
+        <!-- 这种定义插槽的写法是vue推荐的新版写法(https://cn.vuejs.org/v2/guide/components-slots.html#具名插槽),旧版已被废弃的写法不再支持 -->
+        <!-- 若webstorm这样写报错,请看这篇文章:https://blog.csdn.net/lxq_9532/article/details/81870651 -->
+        <template v-slot:action="props">
+            <a @click="handleDelete(props)">删除</a>
+        </template>
+    </j-editable-table>
+</template>
+<script>
+    import { FormTypes } from '@/utils/JEditableTableUtil'
+    import JEditableTable from '@/components/jeecg/JEditableTable'
+    export default {
+        components: { JEditableTable },
+        data() {
+            return {
+                columns: [
+                    // ...
+                    {
+                        title: '操作',
+                        key: 'action',
+                        width: '8%',
+                        type: FormTypes.slot, // 定义该列为 自定义插值列 
+                        slotName: 'action' // slot 的名称,对应 v-slot 冒号后面和等号前面的内容
+
+                    }
+                ]
+            }
+        },
+        methods: {
+            /* a 标签的点击事件,删除当前选中的行 */
+            handleDelete(props) {
+                // 参数解释
+                // props.text :当前值,可能是defaultValue定义的值,也可能是从dataSource中取出的值
+                // props.rowId :当前选中行的id,如果是新增行则是临时id
+                // props.column :当前操作的列
+                // props.getValue :这是一个function,执行后可以获取当前行的所有值(禁止在template中使用)
+                //                  例:const value = props.getValue()
+                // props.target :触发当前事件的实例,可直接调用该实例内的方法(禁止在template中使用)
+                //                  例:target.add()
+
+                // 使用实例:删除当前操作的行
+                let { rowId, target } = props
+                target.removeRows(rowId)
+            }
+        }
+    }
+</script>
+```

+ 6 - 0
src/components/jeecg/index.js

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

+ 309 - 0
src/components/jeecgbiz/JSearchUserByDep.vue

@@ -0,0 +1,309 @@
+<template>
+
+  <a-modal
+    :width="modalWidth"
+    :visible="visible"
+    :title="title"
+    @ok="handleSubmit"
+    @cancel="close"
+    cancelText="关闭"
+    style="margin-top: -70px"
+    wrapClassName="ant-modal-cust-warp"
+  >
+    <a-row :gutter="10" style="background-color: #ececec; padding: 10px; margin: -10px">
+      <a-col :md="6" :sm="24">
+        <a-card :bordered="false">
+          <!--组织机构-->
+          <a-directory-tree
+            selectable
+            :selectedKeys="selectedKeys"
+            :checkStrictly="true"
+            @select="this.onSelect"
+            :dropdownStyle="{maxHeight:'200px',overflow:'auto'}"
+            :treeData="departTree"
+          />
+        </a-card>
+      </a-col>
+      <a-col :md="18" :sm="24">
+        <a-card :bordered="false">
+          用户账号:
+          <a-input-search
+            :style="{width:'150px',marginBottom:'15px'}"
+            placeholder="请输入用户账号"
+            v-model="queryParam.username"
+            @search="onSearch"
+          ></a-input-search>
+          <a-button @click="searchReset(1)" style="margin-left: 20px" icon="redo">重置</a-button>
+          <!--用户列表-->
+          <a-table
+            ref="table"
+            :scroll="scrollTrigger"
+            size="middle"
+            rowKey="id"
+            :columns="columns"
+            :dataSource="dataSource"
+            :pagination="ipagination"
+            :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
+            @change="handleTableChange">
+          </a-table>
+        </a-card>
+      </a-col>
+    </a-row>
+  </a-modal>
+
+</template>
+
+<script>
+  import {filterObj} from '@/utils/util'
+  import {queryDepartTreeList, getUserList, queryUserByDepId, queryUserRoleMap} from '@/api/api'
+
+  export default {
+    name: 'JSearchUserByDep',
+    components: {},
+    data() {
+      return {
+        queryParam: {
+          username: "",
+        },
+        columns: [
+          {
+            title: '用户账号',
+            align: 'center',
+            dataIndex: 'username'
+          },
+          {
+            title: '真实姓名',
+            align: 'center',
+            dataIndex: 'realname'
+          },
+          {
+            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
+              }
+            }
+          },
+          {
+            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 + '条'
+          },
+          showQuickJumper: true,
+          showSizeChanger: true,
+          total: 0
+        },
+        isorter: {
+          column: 'createTime',
+          order: 'desc'
+        },
+        selectedRowKeys: [],
+        selectedRows: [],
+        modalWidth: 1250,
+        departTree: [],
+        visible: false,
+        form: this.$form.createForm(this)
+      }
+    },
+    created() {
+      // 该方法触发屏幕自适应
+      this.resetScreenSize();
+      this.queryUserRoleMap();
+    },
+    methods: {
+      loadData(arg) {
+        if (arg === 1) {
+          this.ipagination.current = 1;
+        }
+        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 = {};
+        }
+      },
+      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;
+      },
+      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;
+        for (let i = 0, len = this.selectedRowKeys.length; i < len; i++) {
+          this.getUserNames(this.selectedRowKeys[i]);
+        }
+        that.$emit('ok', that.userNameArr.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);
+          }
+        }
+      },
+      // 点击树节点,筛选出对应的用户
+      onSelect(selectedKeys) {
+        if (selectedKeys[0] != null) {
+          this.queryUserByDepId(selectedKeys); // 调用方法根据选选择的id查询用户信息
+          if (this.selectedKeys[0] !== selectedKeys[0]) {
+            this.selectedKeys = [selectedKeys[0]];
+          }
+        }
+      },
+      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);
+          }
+        })
+      },
+      // 传入用户id,找到匹配的角色名称
+      queryUserRole(userId) {
+        let map = this.userRolesMap;
+        let roleName = [];
+        for (var key in map) {
+          if (userId === key) {
+            roleName.push(map[key]);
+          }
+        }
+        return roleName.join(',');
+      },
+      queryDepartTree() {
+        queryDepartTreeList().then((res) => {
+          if (res.success) {
+            this.departTree = res.result;
+          }
+        })
+      },
+      // 为角色名称赋值
+      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;
+        }
+      },
+      modalFormOk() {
+        this.loadData();
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .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 .3s;
+  }
+</style>

+ 122 - 0
src/components/jeecgbiz/JSelectDepart.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="components-input-demo-presuffix">
+    <!---->
+    <a-input @click="openModal" placeholder="请点击选择部门" v-model="departNames" readOnly :disabled="disabled">
+      <a-icon slot="prefix" type="cluster" title="部门选择控件"/>
+      <a-icon v-if="departIds" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
+    </a-input>
+
+    <j-select-depart-modal
+      ref="innerDepartSelectModal"
+      :modal-width="modalWidth"
+      :multi="multi"
+      :rootOpened="rootOpened"
+      :depart-id="value"
+      @ok="handleOK"
+      @initComp="initComp"/>
+  </div>
+</template>
+
+<script>
+  import JSelectDepartModal from './modal/JSelectDepartModal'
+
+  export default {
+    name: 'JSelectDepart',
+    components: {
+      JSelectDepartModal
+    },
+    props: {
+      modalWidth: {
+        type: Number,
+        default: 500,
+        required: false
+      },
+      multi: {
+        type: Boolean,
+        default: false,
+        required: false
+      },
+      rootOpened: {
+        type: Boolean,
+        default: true,
+        required: false
+      },
+      value: {
+        type: String,
+        required: false
+      },
+      disabled: {
+        type: Boolean,
+        required: false,
+        default: false
+      }
+    },
+    data() {
+      return {
+        visible: false,
+        confirmLoading: false,
+        departNames: "",
+        departIds: ''
+      }
+    },
+    mounted() {
+      this.departIds = this.value
+    },
+    watch: {
+      value(val) {
+        this.departIds = val
+      }
+    },
+    methods: {
+      initComp(departNames) {
+        this.departNames = departNames
+      },
+      openModal() {
+        this.$refs.innerDepartSelectModal.show()
+      },
+      handleOK(rows, idstr) {
+        console.log("当前选中部门", rows)
+        console.log("当前选中部门ID", idstr)
+        if (!rows) {
+          this.departNames = ''
+          this.departIds = ''
+        } else {
+          let temp = ''
+          for (let item of rows) {
+            temp += ',' + item.departName
+          }
+          this.departNames = temp.substring(1)
+          this.departIds = idstr
+        }
+        this.$emit("change", this.departIds)
+      },
+      getDepartNames() {
+        return this.departNames
+      },
+      handleEmpty() {
+        this.handleOK('')
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>
+
+<style scoped>
+  .components-input-demo-presuffix .anticon-close-circle {
+    cursor: pointer;
+    color: #ccc;
+    transition: color 0.3s;
+    font-size: 12px;
+  }
+
+  .components-input-demo-presuffix .anticon-close-circle:hover {
+    color: #f5222d;
+  }
+
+  .components-input-demo-presuffix .anticon-close-circle:active {
+    color: #666;
+  }
+</style>

+ 66 - 0
src/components/jeecgbiz/JSelectMultiUser.vue

@@ -0,0 +1,66 @@
+<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>
+</template>
+
+<script>
+  import JSelectMultiUserModal from './modal/JSelectMultiUserModal'
+
+  export default {
+    name: 'JSelectMultiUser',
+    components: {JSelectMultiUserModal},
+    props: {
+      value: {
+        type: String,
+        required: false
+      }
+    },
+    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(",")
+        }
+        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;
+      }
+    }
+  }
+</script>

+ 293 - 0
src/components/jeecgbiz/JSelectMultipleUser.vue

@@ -0,0 +1,293 @@
+<template>
+  <div>
+    <a-modal
+      centered
+      :title="title"
+      :width="1000"
+      :visible="visible"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      cancelText="关闭">
+      <a-row :gutter="18">
+        <a-col :span="16">
+          <a-card title="选择人员" :bordered="true">
+            <!-- 查询区域 -->
+            <div class="table-page-search-wrapper">
+              <a-form layout="inline">
+                <a-row :gutter="24">
+
+                  <a-col :span="10">
+                    <a-form-item label="姓名">
+                      <a-input placeholder="请输入姓名" v-model="queryParam.name"></a-input>
+                    </a-form-item>
+                  </a-col>
+                  <a-col :span="8">
+                    <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+                      <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
+                      <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
+                    </span>
+                  </a-col>
+
+                </a-row>
+              </a-form>
+            </div>
+            <!-- table区域-begin -->
+            <div>
+              <a-table
+                size="small"
+                bordered
+                rowKey="id"
+                :columns="columns1"
+                :dataSource="dataSource1"
+                :pagination="ipagination"
+                :loading="loading"
+                :scroll="{ y: 240 }"
+                :rowSelection="{selectedRowKeys: selectedRowKeys,onSelectAll:onSelectAll,onSelect:onSelect,onChange: onSelectChange}"
+                @change="handleTableChange">
+
+              </a-table>
+            </div>
+            <!-- table区域-end -->
+          </a-card>
+        </a-col>
+        <a-col :span="8">
+          <a-card title="用户选择" :bordered="true">
+            <!-- table区域-begin -->
+            <div>
+              <a-table
+                size="small"
+                bordered
+                rowKey="id"
+                :columns="columns2"
+                :dataSource="dataSource2"
+                :loading="loading"
+                :scroll="{ y: 240 }"
+              >
+                <span slot="action" slot-scope="text, record">
+                  <a-button type="primary" size="small" @click="handleDelete(record)" icon="delete">删除</a-button>
+                </span>
+              </a-table>
+            </div>
+            <!-- table区域-end -->
+          </a-card>
+        </a-col>
+      </a-row>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+  import {filterObj} from '@/utils/util'
+  import {getAction} from '@/api/manage'
+
+  export default {
+    name: "SelectDemoModal",
+    data() {
+      return {
+        title: "用户列表",
+        names: [],
+        visible: false,
+        placement: 'right',
+        description: '人员管理页面',
+        // 查询条件
+        queryParam: {},
+        // 表头
+        columns1: [
+          {
+            title: '#',
+            dataIndex: '',
+            key: 'rowIndex',
+            width: 50,
+            align: "center",
+            customRender: function (t, r, index) {
+              return parseInt(index) + 1;
+            }
+          },
+          {
+            title: '姓名',
+            align: "center",
+            width: 113,
+            dataIndex: 'name'
+          },
+          {
+            title: '年龄',
+            align: "center",
+            width: 100,
+            dataIndex: 'age'
+          },
+          {
+            title: '出生日期',
+            align: "center",
+            width: 100,
+            dataIndex: 'birthday'
+          }
+        ],
+        columns2: [
+
+          {
+            title: '用户账号',
+            align: "center",
+            width: 100,
+            dataIndex: 'name'
+          },
+          {
+            title: '操作',
+            dataIndex: 'action',
+            align: "center",
+            width: 100,
+            scopedSlots: {customRender: 'action'},
+          }
+        ],
+        //数据集
+        dataSource1: [],
+        dataSource2: [],
+        // 分页参数
+        ipagination: {
+          current: 1,
+          pageSize: 10,
+          pageSizeOptions: ['10', '20', '30'],
+          showTotal: (total, range) => {
+            return range[0] + "-" + range[1] + " 共" + total + "条"
+          },
+          showQuickJumper: true,
+          showSizeChanger: true,
+          total: 0
+        },
+        isorter: {
+          column: 'createTime',
+          order: 'desc',
+        },
+        loading: false,
+        selectedRowKeys: [],
+        selectedRows: [],
+        url: {
+          list: "/test/jeecgDemo/list",
+        },
+
+      }
+    },
+    created() {
+      this.loadData();
+    },
+    methods: {
+      searchQuery() {
+        this.loadData(1);
+      },
+      searchReset() {
+        this.queryParam = {};
+        this.loadData(1);
+      },
+      handleCancel() {
+        this.visible = false;
+      },
+      handleOk() {
+        this.$emit("selectFinished", this.dataSource2);
+        this.visible = false;
+      },
+      add() {
+        this.visible = true;
+      },
+      loadData(arg) {
+        //加载数据 若传入参数1则加载第一页的内容
+        if (arg === 1) {
+          this.ipagination.current = 1;
+        }
+        var params = this.getQueryParams();//查询条件
+        getAction(this.url.list, params).then((res) => {
+          if (res.success) {
+            this.dataSource1 = res.result.records;
+            this.ipagination.total = res.result.total;
+          }
+        })
+      },
+      getQueryParams() {
+        var 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() {
+        //TODO 字段权限控制
+      },
+      onSelectAll(selected, selectedRows, changeRows) {
+        if (selected === true) {
+          for (var a = 0; a < changeRows.length; a++) {
+            this.dataSource2.push(changeRows[a]);
+          }
+        } else {
+          for (var b = 0; b < changeRows.length; b++) {
+            this.dataSource2.splice(this.dataSource2.indexOf(changeRows[b]), 1);
+          }
+        }
+        // console.log(selected, selectedRows, changeRows);
+      },
+      onSelect(record, selected) {
+        if (selected === true) {
+          this.dataSource2.push(record);
+        } else {
+          var index = this.dataSource2.indexOf(record);
+          //console.log();
+          if (index >= 0) {
+            this.dataSource2.splice(this.dataSource2.indexOf(record), 1);
+          }
+
+        }
+      },
+      onSelectChange(selectedRowKeys, selectedRows) {
+        this.selectedRowKeys = selectedRowKeys;
+        this.selectionRows = selectedRows;
+      },
+      onClearSelected() {
+        this.selectedRowKeys = [];
+        this.selectionRows = [];
+      },
+      handleDelete: function (record) {
+        this.dataSource2.splice(this.dataSource2.indexOf(record), 1);
+      },
+      handleTableChange(pagination, filters, sorter) {
+        //分页、排序、筛选变化时触发
+        console.log(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();
+      }
+    }
+  }
+</script>
+<style lang="less" scoped>
+  .ant-card-body .table-operator {
+    margin-bottom: 18px;
+  }
+
+  .ant-table-tbody .ant-table-row td {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+
+  .anty-row-operator button {
+    margin: 0 5px
+  }
+
+  .ant-btn-danger {
+    background-color: #ffffff
+  }
+
+  .ant-modal-cust-warp {
+    height: 100%
+  }
+
+  .ant-modal-cust-warp .ant-modal-body {
+    height: calc(100% - 110px) !important;
+    overflow-y: auto
+  }
+
+  .ant-modal-cust-warp .ant-modal-content {
+    height: 90% !important;
+    overflow-y: hidden
+  }
+</style>

+ 0 - 0
src/components/jeecgbiz/JSelectUser.vue


部分文件因文件數量過多而無法顯示